001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/wmps/WMPSDatabase.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2001-2007 by:
006 EXSE, Department of Geography, University of Bonn
007 http://www.giub.uni-bonn.de/deegree/
008 lat/lon GmbH
009 http://www.lat-lon.de
010
011 This library is free software; you can redistribute it and/or
012 modify it under the terms of the GNU Lesser General Public
013 License as published by the Free Software Foundation; either
014 version 2.1 of the License, or (at your option) any later version.
015
016 This library is distributed in the hope that it will be useful,
017 but WITHOUT ANY WARRANTY; without even the implied warranty of
018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019 Lesser General Public License for more details.
020
021 You should have received a copy of the GNU Lesser General Public
022 License along with this library; if not, write to the Free Software
023 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024
025 Contact:
026
027 Andreas Poth
028 lat/lon GmbH
029 Aennchenstraße 19
030 53177 Bonn
031 Germany
032 E-Mail: poth@lat-lon.de
033
034 Prof. Dr. Klaus Greve
035 Department of Geography
036 University of Bonn
037 Meckenheimer Allee 166
038 53115 Bonn
039 Germany
040 E-Mail: greve@giub.uni-bonn.de
041
042 ---------------------------------------------------------------------------*/
043
044 package org.deegree.ogcwebservices.wmps;
045
046 import java.awt.Color;
047 import java.io.ByteArrayInputStream;
048 import java.io.ByteArrayOutputStream;
049 import java.io.IOException;
050 import java.io.ObjectInputStream;
051 import java.io.ObjectOutputStream;
052 import java.sql.Connection;
053 import java.sql.PreparedStatement;
054 import java.sql.ResultSet;
055 import java.sql.SQLException;
056 import java.sql.Statement;
057 import java.sql.Timestamp;
058 import java.util.HashMap;
059 import java.util.Map;
060
061 import org.deegree.framework.log.ILogger;
062 import org.deegree.framework.log.LoggerFactory;
063 import org.deegree.framework.util.StringTools;
064 import org.deegree.io.DBConnectionPool;
065 import org.deegree.model.crs.CRSFactory;
066 import org.deegree.model.crs.CoordinateSystem;
067 import org.deegree.model.crs.UnknownCRSException;
068 import org.deegree.model.spatialschema.Envelope;
069 import org.deegree.model.spatialschema.GeometryFactory;
070 import org.deegree.model.spatialschema.Point;
071 import org.deegree.ogcwebservices.wmps.configuration.CacheDatabase;
072 import org.deegree.ogcwebservices.wmps.operation.PrintMap;
073 import org.deegree.ogcwebservices.wmps.operation.TextArea;
074 import org.deegree.ogcwebservices.wms.operation.GetMap.Layer;
075
076 /**
077 * Provides database functionalities for the wmps.
078 *
079 * @author <a href="mailto:deshmukh@lat-lon.de">Anup Deshmukh</a>
080 *
081 * @author last edited by: $Author: apoth $
082 *
083 * @version 2.0, $Revision: 7232 $, $Date: 2007-05-22 09:52:00 +0200 (Di, 22 Mai 2007) $
084 *
085 * @since 2.0
086 */
087
088 public class WMPSDatabase {
089
090 private static final String WMPS_REQUEST_STORAGE_TABLE = "WMPS_REQUESTS";
091
092 private static ILogger LOG = LoggerFactory.getLogger( WMPSDatabase.class );
093
094 private CacheDatabase cacheDatabase;
095
096 private DBConnectionPool pool;
097
098 /**
099 * Creates a new WMPSDatabase instance.
100 *
101 * @param cacheDatabase
102 * @throws Exception
103 */
104 public WMPSDatabase( CacheDatabase cacheDatabase ) throws Exception {
105
106 this.cacheDatabase = cacheDatabase;
107 this.pool = DBConnectionPool.getInstance();
108 }
109
110 /**
111 * Creates a table, if no table exists. Used only for the HSQLDb
112 *
113 * @param connection
114 * @throws SQLException
115 * @throws PrintMapServiceException
116 */
117 private void createTable( Connection connection )
118 throws SQLException {
119 /*
120 * PrintMap table structure
121 * id,processed,timestamp,version,layers,srs,boundingbox,center,scaledenominator,
122 * transparent,bgcolor,title,copyright,legend,scaleBar,note,template,emailaddress,
123 * textAreas,vendor
124 */
125 StringBuffer sqlCreateQuery = new StringBuffer( 500 );
126 sqlCreateQuery.append( "CREATE TABLE " ).append( WMPS_REQUEST_STORAGE_TABLE ).append( " ( " );
127 sqlCreateQuery.append( "id VARCHAR(15), " ).append( "processed BOOLEAN, " );
128 sqlCreateQuery.append( "timestamp BIGINT, " ).append( "version VARCHAR(10), " );
129 sqlCreateQuery.append( "layers BINARY, " ).append( "srs VARCHAR(15), " );
130 sqlCreateQuery.append( "boundingbox VARCHAR(100), " ).append( "center VARCHAR(50), " );
131 sqlCreateQuery.append( "scaledenominator INTEGER, " ).append( "transparent BOOLEAN, " );
132 sqlCreateQuery.append( "bgcolor VARCHAR(10), " ).append( "title VARCHAR(100), " );
133 sqlCreateQuery.append( "copyright VARCHAR(50), " ).append( "legend BOOLEAN, " );
134 sqlCreateQuery.append( "scaleBar BOOLEAN, " ).append( "note VARCHAR(200), " );
135 sqlCreateQuery.append( "template VARCHAR(30), " ).append( "emailaddress VARCHAR(30), " );
136 sqlCreateQuery.append( "textAreas BINARY, " ).append( "vendor BINARY, " );
137 sqlCreateQuery.append( "PRIMARY KEY(id,timestamp) );" );
138
139 String sqlTableCreation = sqlCreateQuery.toString();
140
141 try {
142 Statement statement = connection.createStatement();
143 statement.execute( sqlTableCreation );
144 statement.close();
145 } catch ( SQLException e ) {
146 if ( !e.getMessage().startsWith( "Table already" ) ) {
147 LOG.logError( e.getMessage(), e );
148 throw new SQLException( "Unable to create a table for the sql command '" + sqlTableCreation + "'."
149 + e.getMessage() );
150 }
151 }
152
153 }
154
155 /**
156 * Inserts data into the table. Each incomming request is stored in the db.
157 *
158 * @param connection
159 * @param request
160 * @throws IOException
161 * @throws PrintMapServiceException
162 * @throws IOException
163 */
164 public void insertData( Connection connection, PrintMap request )
165 throws PrintMapServiceException, IOException {
166
167 /*
168 * PrintMap table structure
169 * id,processed,timestamp,version,layers,srs,boundingbox,center,scaledenominator,
170 * transparent,bgcolor,title,copyright,legend,scaleBar,note,template,emailaddress,
171 * textAreas,vendor
172 */
173 try {
174
175 String id = request.getId();
176 String version = request.getVersion();
177 Layer[] layers = request.getLayers();
178 String srs = request.getSRS();
179 Envelope bbox = request.getBBOX();
180 Point center = request.getCenter();
181 int scaleDenominator = request.getScaleDenominator();
182 boolean transparent = request.getTransparent();
183 Color bgColor = request.getBGColor();
184 String title = request.getTitle();
185 String copyright = request.getCopyright();
186 boolean legend = request.getLegend();
187 boolean scaleBar = request.getScaleBar();
188 String note = request.getNote();
189 String template = request.getTemplate();
190 String emailAddress = request.getEmailAddress();
191 TextArea[] textAreas = request.getTextAreas();
192
193 Map vendorSpecificParams = request.getVendorSpecificParameters();
194 if ( vendorSpecificParams == null ) {
195 vendorSpecificParams = new HashMap();
196 }
197 long timestamp = request.getTimestamp().getTime();
198 boolean processed = false;
199
200 PreparedStatement statement = connection.prepareStatement( "INSERT INTO " + WMPS_REQUEST_STORAGE_TABLE
201 + " values(?,?,?,?,?,?,?,?,"
202 + "?,?,?,?,?,?,?,?,?,?,?,?);" );
203
204 /*
205 * PrintMap table structure
206 * id,processed,timestamp,version,layers,srs,boundingbox,center,scaledenominator,
207 * transparent,bgcolor,title,copyright,legend,scaleBar,note,template,emailaddress,
208 * textAreas,vendor
209 */
210 statement.setString( 1, id );
211 statement.setBoolean( 2, processed );
212 statement.setLong( 3, timestamp );
213 statement.setString( 4, version );
214 statement.setBytes( 5, serialize( layers ) );
215 statement.setString( 6, srs );
216 if ( bbox != null ) {
217 String bboxString = bbox.getMin().getX() + "," + bbox.getMin().getY() + "," + bbox.getMax().getX()
218 + "," + bbox.getMax().getY() + "," + bbox.getCoordinateSystem().getPrefixedName();
219 statement.setString( 7, bboxString );
220 }
221 if ( center != null ) {
222 String centerString = center.getX() + "," + center.getY() + ","
223 + center.getCoordinateSystem().getPrefixedName();
224 statement.setString( 8, centerString );
225 }
226 statement.setInt( 9, scaleDenominator );
227 statement.setBoolean( 10, transparent );
228 if ( bgColor != null ) {
229 String color = convertColorToHexString( bgColor );
230 statement.setString( 11, color );
231 }
232 statement.setString( 12, title );
233 statement.setString( 13, copyright );
234 statement.setBoolean( 14, legend );
235 statement.setBoolean( 15, scaleBar );
236 statement.setString( 16, note );
237 statement.setString( 17, template );
238 statement.setString( 18, emailAddress );
239 statement.setBytes( 19, serialize( textAreas ) );
240
241 if ( vendorSpecificParams != null ) {
242 statement.setBytes( 20, serialize( vendorSpecificParams ) );
243 }
244
245 statement.execute();
246 connection.commit();
247 statement.close();
248
249 } catch ( SQLException e ) {
250 LOG.logError( e.getMessage(), e );
251 throw new PrintMapServiceException( "Error inserting data into the '" + WMPS_REQUEST_STORAGE_TABLE
252 + "' table. " + e.getMessage() );
253 }
254
255 }
256
257 /**
258 * Creates a valid db connection with properties read from the configuration file.
259 *
260 * @return Connection
261 * @throws Exception
262 */
263 public Connection acquireConnection()
264 throws Exception {
265
266 String driver = this.cacheDatabase.getDriver();
267 String url = this.cacheDatabase.getUrl();
268 if ( this.pool == null ) {
269 this.pool = DBConnectionPool.getInstance();
270 }
271 Connection conn = this.pool.acquireConnection( driver, url, this.cacheDatabase.getUser(),
272 this.cacheDatabase.getPassword() );
273
274 try {
275 if ( driver.equals( "org.hsqldb.jdbcDriver" ) ) {
276 createTable( conn );
277 }
278 } catch ( SQLException e ) {
279 LOG.logError( e.getMessage(), e );
280 throw new Exception( "Unable to build a valid connection to the 'hsqldb' "
281 + "database for the connection string '" + url + "'. " + e.getMessage() );
282 }
283
284 return conn;
285 }
286
287 /**
288 * Releases the current database connection.
289 *
290 * @param connection
291 * @throws SQLException
292 */
293 protected void releaseConnection( Connection connection )
294 throws SQLException {
295
296 try {
297 if ( this.pool != null ) {
298 this.pool.releaseConnection( connection, this.cacheDatabase.getDriver(), this.cacheDatabase.getUrl(),
299 this.cacheDatabase.getUser(), this.cacheDatabase.getPassword() );
300 }
301 } catch ( Exception e ) {
302 LOG.logError( e.getMessage(), e );
303 throw new SQLException( "Error releasing the open connection. " + e.getMessage() );
304 }
305
306 }
307
308 /**
309 * Select the PrintMap request that has been in the databank for the longest time. i.e the first
310 * in queue to be processed.
311 *
312 * @param connection
313 * @return PrintMap
314 * @throws PrintMapServiceException
315 */
316 public PrintMap selectPrintMapRequest( Connection connection )
317 throws PrintMapServiceException {
318
319 String sql = "SELECT MAX( timestamp ) FROM " + WMPS_REQUEST_STORAGE_TABLE + " WHERE processed = 'FALSE' ";
320 String selectionSQL = "SELECT id, timestamp FROM " + WMPS_REQUEST_STORAGE_TABLE + " WHERE timestamp = (" + sql
321 + ");";
322 String firstInQueue = null;
323 long timeStamp = -1;
324 try {
325 Statement statement = connection.createStatement();
326
327 ResultSet results = statement.executeQuery( selectionSQL );
328
329 while ( results.next() ) {
330 firstInQueue = results.getString( "id" );
331 timeStamp = results.getLong( 2 );
332 }
333 results.close();
334 statement.close();
335
336 } catch ( SQLException e ) {
337 LOG.logError( e.getMessage(), e );
338 throw new PrintMapServiceException( "Error retrieving data from the 'WMPSPrintMap' table for the "
339 + "selectionSQL statement '" + selectionSQL + "'. " + e.getMessage() );
340 }
341
342 return getPrintMapRequest( connection, firstInQueue, timeStamp );
343
344 }
345
346 /**
347 * Retrieve the PrintMap request from the DB for the id and convert the byte array back to a
348 * PrintMap request instance.
349 *
350 * @param connection
351 * @param firstInQueue
352 * @param timestamp
353 * @return PrintMapRequest
354 * @throws PrintMapServiceException
355 */
356 private PrintMap getPrintMapRequest( Connection connection, String firstInQueue, long timestamp )
357 throws PrintMapServiceException {
358
359 PrintMap request = null;
360 if ( firstInQueue == null ) {
361 return request;
362 }
363
364 /*
365 * PrintMap table structure
366 * id,version,layers,srs,boundingBox,center,scaleDenominator,transparent,bgColor,title,copyright,
367 * legend,scaleBar,note,template,emailaddress,textAreas
368 */
369 String selectRequest = "SELECT id, version, layers, srs, boundingbox, center,"
370 + "scaledenominator, transparent, bgcolor, title, copyright,"
371 + "legend, scalebar, note, template, emailaddress, " + "textAreas, vendor FROM "
372 + WMPS_REQUEST_STORAGE_TABLE + " WHERE id='" + firstInQueue + "' " + "AND timestamp="
373 + timestamp + ";";
374
375 try {
376 Statement statement = connection.createStatement();
377
378 ResultSet results = statement.executeQuery( selectRequest );
379
380 while ( results.next() ) {
381 String id = results.getString( 1 );
382 String version = results.getString( 2 );
383 byte[] b = results.getBytes( 3 );
384 Layer[] layers = null;
385 if ( b != null ) {
386 Object object = deserialize( b );
387 if ( object != null ) {
388 layers = (Layer[]) object;
389 }
390 }
391 String srs = results.getString( 4 );
392 String bboxString = results.getString( 5 );
393 Envelope bbox = null;
394 if ( bboxString != null ) {
395 String[] bboxArray = StringTools.toArray( bboxString, ",", false );
396 if ( bboxArray.length == 5 ) {
397 double minX = Double.valueOf( bboxArray[0] ).doubleValue();
398 double minY = Double.valueOf( bboxArray[1] ).doubleValue();
399 double maxX = Double.valueOf( bboxArray[2] ).doubleValue();
400 double maxY = Double.valueOf( bboxArray[3] ).doubleValue();
401 CoordinateSystem crs;
402 try {
403 crs = CRSFactory.create( bboxArray[4] );
404 } catch ( UnknownCRSException e ) {
405 throw new PrintMapServiceException( e.getMessage() );
406 }
407 bbox = GeometryFactory.createEnvelope( minX, minY, maxX, maxY, crs );
408 }
409 }
410 String centerString = results.getString( 6 );
411 Point center = null;
412 if ( centerString != null ) {
413 String[] centerArray = StringTools.toArray( centerString, ",", false );
414 if ( centerArray.length == 3 ) {
415 double x = Double.valueOf( centerArray[0] ).doubleValue();
416 double y = Double.valueOf( centerArray[1] ).doubleValue();
417 try {
418 CoordinateSystem crs = CRSFactory.create( centerArray[2] );
419 center = GeometryFactory.createPoint( x, y, crs );
420 } catch ( UnknownCRSException e ) {
421 throw new PrintMapServiceException( e.getMessage() );
422 }
423 }
424 }
425 /*
426 * "scaledenominator, transparent, bgcolor, title, copyright,legend, scalebar, note,
427 * template, emailaddress, textAreas, vendorspecificparams
428 */
429 int scaleDenominator = results.getInt( 7 );
430 boolean transparent = results.getBoolean( 8 );
431 String bgColorString = results.getString( 9 );
432 Color bgColor = null;
433 if ( bgColorString != null ) {
434 bgColor = convertStringToColor( bgColorString );
435 }
436 String title = results.getString( 10 );
437 String copyright = results.getString( 11 );
438 boolean legend = results.getBoolean( 12 );
439 boolean scaleBar = results.getBoolean( 13 );
440 String note = results.getString( 14 );
441 String template = results.getString( 15 );
442 String emailAddress = results.getString( 16 );
443 b = results.getBytes( 17 );
444 TextArea[] textAreas = null;
445 if ( b != null ) {
446 Object object = deserialize( b );
447 if ( object != null ) {
448 textAreas = (TextArea[]) object;
449 }
450 }
451 b = results.getBytes( 18 );
452 Map<String, String> vendorSpecificParameters = (Map) deserialize( b );
453
454 request = PrintMap.create( id, version, layers, srs, bbox, center, scaleDenominator, transparent,
455 bgColor, title, copyright, legend, scaleBar, note, template, emailAddress,
456 new Timestamp( timestamp ), textAreas, vendorSpecificParameters );
457 }
458 statement.close();
459
460 } catch ( SQLException e ) {
461 LOG.logError( e.getMessage(), e );
462 throw new PrintMapServiceException( "Error executing the sql statement '" + selectRequest + "'. "
463 + e.getMessage() );
464 } catch ( IOException e ) {
465 LOG.logError( e.getMessage(), e );
466 throw new PrintMapServiceException( "Error deserializing the result set. " + e.getMessage() );
467 } catch ( ClassNotFoundException e ) {
468 LOG.logError( e.getMessage(), e );
469 throw new PrintMapServiceException( "Error deserializing the result set. " + e.getMessage() );
470 }
471
472 return request;
473 }
474
475 /**
476 * Updating the processed field in the db to signify that the PrintMap request has been
477 * successfully carried out.
478 *
479 * @param connection
480 * @param id
481 * @param timeStamp
482 * @throws SQLException
483 * @throws PrintMapServiceException
484 */
485 public void updateDB( Connection connection, String id, Timestamp timeStamp )
486 throws SQLException {
487
488
489 String updateSQL = "UPDATE " + WMPS_REQUEST_STORAGE_TABLE + " SET processed='TRUE' " + "WHERE id='" + id + "' AND timestamp="
490 + timeStamp.getTime() + ";";
491
492 try {
493
494 Statement statement = connection.createStatement();
495 int i = statement.executeUpdate( updateSQL );
496 if ( i == 0 ) {
497
498 } else if ( i == -1 ) {
499 throw new SQLException( "Error executing the update statement. Could not update row in the DB for id='" +
500 id + "and timestamp='" + timeStamp + "." );
501 }
502 connection.commit();
503 statement.close();
504
505 } catch ( SQLException e ) {
506 LOG.logError( e.getMessage(), e );
507 throw new SQLException( "Error executing the update statement. Could not update row "
508 + "in the DB for id='" + id + "and timestamp='" + timeStamp + ". " + e.getMessage() );
509 }
510
511 }
512
513 /**
514 * Convert the object to a byte array.
515 *
516 * @param object
517 * @return byte[]
518 * @throws IOException
519 */
520 private synchronized byte[] serialize( Object object )
521 throws IOException {
522
523 byte[] b = null;
524 ByteArrayOutputStream bos = new ByteArrayOutputStream( 10000 );
525 try {
526 ObjectOutputStream oos = new ObjectOutputStream( bos );
527 oos.writeObject( object );
528 oos.close();
529 } catch ( IOException e ) {
530 LOG.logError( e.getMessage(), e );
531 throw new IOException( "Error converting the current object to an array of bytes. " + e.getMessage() );
532 }
533 b = bos.toByteArray();
534 bos.close();
535
536 return b;
537
538 }
539
540 /**
541 * Reserialize the byte array to a PrintMap instance.
542 *
543 * @param b
544 * @return Object
545 * @throws IOException
546 * @throws ClassNotFoundException
547 */
548 private synchronized Object deserialize( byte[] b )
549 throws IOException, ClassNotFoundException {
550
551 Object object = null;
552 try {
553 ByteArrayInputStream bai = new ByteArrayInputStream( b );
554 ObjectInputStream in = new ObjectInputStream( bai );
555 object = in.readObject();
556 in.close();
557 } catch ( IOException e ) {
558 LOG.logError( e.getMessage(), e );
559 throw new IOException( "Error opening ObjectInputStream to reserialize the byte "
560 + "array back to the original instance. " + e.getMessage() );
561 } catch ( ClassNotFoundException e ) {
562 LOG.logError( e.getMessage(), e );
563 throw new ClassNotFoundException( "Error recasting the ObjectInputStream "
564 + "retrieved Object to the original instance. "
565 + "The wrong data may have been stored in the DB "
566 + "or the DB instance is inconsistent. " + e.getMessage() );
567 }
568
569 return object;
570 }
571
572 /**
573 * Convert a "#FFFFFF" hex string to a Color. If the color specification is bad, an attempt will
574 * be made to fix it up.
575 *
576 * @param value
577 * @return Color
578 */
579 private Color hexToColor( String value ) {
580
581 if ( value.startsWith( "#" ) ) {
582 String digits = value.substring( 1, Math.min( value.length(), 7 ) );
583 String hstr = "0x" + digits;
584 return Color.decode( hstr );
585 }
586 return null;
587
588 }
589
590 /**
591 * Convert a color string "RED" or "#NNNNNN" to a Color. Note: This will only convert the
592 * HTML3.2 colors strings or string of length 7 otherwise, it will return Color.white.
593 *
594 * @param str
595 * @return Color
596 */
597 private Color convertStringToColor( String str ) {
598
599 if ( str != null ) {
600 if ( str.charAt( 0 ) == '#' ) {
601 return hexToColor( str );
602 } else if ( str.equalsIgnoreCase( "Black" ) ) {
603 return hexToColor( "#000000" );
604 } else if ( str.equalsIgnoreCase( "Silver" ) ) {
605 return hexToColor( "#C0C0C0" );
606 } else if ( str.equalsIgnoreCase( "Gray" ) ) {
607 return hexToColor( "#808080" );
608 } else if ( str.equalsIgnoreCase( "White" ) ) {
609 return hexToColor( "#FFFFFF" );
610 } else if ( str.equalsIgnoreCase( "Maroon" ) ) {
611 return hexToColor( "#800000" );
612 } else if ( str.equalsIgnoreCase( "Red" ) ) {
613 return hexToColor( "#FF0000" );
614 } else if ( str.equalsIgnoreCase( "Purple" ) ) {
615 return hexToColor( "#800080" );
616 } else if ( str.equalsIgnoreCase( "Fuchsia" ) ) {
617 return hexToColor( "#FF00FF" );
618 } else if ( str.equalsIgnoreCase( "Green" ) ) {
619 return hexToColor( "#008000" );
620 } else if ( str.equalsIgnoreCase( "Lime" ) ) {
621 return hexToColor( "#00FF00" );
622 } else if ( str.equalsIgnoreCase( "Olive" ) ) {
623 return hexToColor( "#808000" );
624 } else if ( str.equalsIgnoreCase( "Yellow" ) ) {
625 return hexToColor( "#FFFF00" );
626 } else if ( str.equalsIgnoreCase( "Navy" ) ) {
627 return hexToColor( "#000080" );
628 } else if ( str.equalsIgnoreCase( "Blue" ) ) {
629 return hexToColor( "#0000FF" );
630 } else if ( str.equalsIgnoreCase( "Teal" ) ) {
631 return hexToColor( "#008080" );
632 } else if ( str.equalsIgnoreCase( "Aqua" ) ) {
633 return hexToColor( "#00FFFF" );
634 }
635 }
636 return null;
637 }
638
639 /**
640 * convert a color to its hex string.
641 *
642 * @param c
643 * @return String
644 */
645 private String convertColorToHexString( Color c ) {
646 String str = Integer.toHexString( c.getRGB() & 0xFFFFFF );
647 return ( "#" + "000000".substring( str.length() ) + str.toUpperCase() );
648 }
649
650 }