001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/tools/datastore/DDLGenerator.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2001-2008 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 package org.deegree.tools.datastore;
044
045 import java.io.File;
046 import java.io.FileWriter;
047 import java.io.IOException;
048 import java.io.PrintWriter;
049 import java.net.MalformedURLException;
050 import java.net.URL;
051 import java.util.ArrayList;
052 import java.util.Collection;
053 import java.util.HashMap;
054 import java.util.HashSet;
055 import java.util.Iterator;
056 import java.util.LinkedHashMap;
057 import java.util.Map;
058 import java.util.Set;
059
060 import org.deegree.datatypes.Types;
061 import org.deegree.datatypes.UnknownTypeException;
062 import org.deegree.framework.xml.XMLParsingException;
063 import org.deegree.framework.xml.schema.XMLSchemaException;
064 import org.deegree.io.datastore.schema.MappedFeaturePropertyType;
065 import org.deegree.io.datastore.schema.MappedFeatureType;
066 import org.deegree.io.datastore.schema.MappedGMLId;
067 import org.deegree.io.datastore.schema.MappedGMLSchema;
068 import org.deegree.io.datastore.schema.MappedGMLSchemaDocument;
069 import org.deegree.io.datastore.schema.MappedGeometryPropertyType;
070 import org.deegree.io.datastore.schema.MappedPropertyType;
071 import org.deegree.io.datastore.schema.MappedSimplePropertyType;
072 import org.deegree.io.datastore.schema.TableRelation;
073 import org.deegree.io.datastore.schema.content.MappingField;
074 import org.deegree.io.datastore.schema.content.MappingGeometryField;
075 import org.deegree.io.datastore.schema.content.SimpleContent;
076 import org.deegree.io.datastore.sql.idgenerator.DBSeqIdGenerator;
077 import org.deegree.model.crs.UnknownCRSException;
078 import org.deegree.model.feature.schema.FeatureType;
079 import org.deegree.model.feature.schema.PropertyType;
080 import org.xml.sax.SAXException;
081
082 /**
083 * Abstract base class for DDL generation from annotated GML schema files.
084 * <p>
085 * This abstract base class only implements the functionality needed to retrieve the necessary
086 * tables and columns used in an annotated GML schema. Some DDL generation may be dependent on the
087 * specific SQL backend to be used, so this is implemented in concrete extensions of this class.
088 *
089 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
090 * @author last edited by: $Author: apoth $
091 *
092 * @version $Revision: 9346 $, $Date: 2007-12-27 17:39:07 +0100 (Do, 27 Dez 2007) $
093 */
094 public abstract class DDLGenerator {
095
096 protected static final String FT_PREFIX = "FT_";
097
098 protected static final int FEATURE_TYPE_TABLE = 0;
099
100 protected static final int JOIN_TABLE = 1;
101
102 protected static final int MULTI_PROPERTY_TABLE = 2;
103
104 protected MappedGMLSchema schema;
105
106 // key type: String (table names), value type: TableDefinition
107 protected Map<String, TableDefinition> tables = new HashMap<String, TableDefinition>();
108
109 // names of sequences (for id generation)
110 protected Set<String> sequences = new HashSet<String>();
111
112 /**
113 * Generates the DDL statements necessary for the creation of the given table definition. Must
114 * be overwritten by the concrete implementation.
115 *
116 * @param table
117 * @return the DDL statements necessary for the creation of the given table definition
118 */
119 protected abstract StringBuffer generateCreateTableStmt( TableDefinition table );
120
121 /**
122 * Generates the DDL statements necessary for the creation of standard indexes for the given
123 * table definition. Must be overwritten by the concrete implementation.
124 *
125 * @param table
126 * @return the DDL statements necessary for the creation of standard indexes for the given table
127 * definition
128 */
129 protected abstract StringBuffer generateCreateIndexStmts( TableDefinition table );
130
131 /**
132 * Generates the DDL statements necessary for the creation of the given sequence. May be
133 * overwritten by a concrete implementation.
134 *
135 * @param sequenceName
136 * @return the DDL statements necessary for the creation of the given sequence definition
137 */
138 protected StringBuffer generateCreateSequenceStmt( String sequenceName ) {
139 StringBuffer sb = new StringBuffer( "CREATE SEQUENCE " );
140 sb.append( sequenceName );
141 sb.append( ";\n" );
142 return sb;
143 }
144
145 /**
146 * Generates the DDL statements necessary for the removal of the given table definition. May be
147 * overwritten by a concrete implementation.
148 *
149 * @param table
150 * @return the DDL statements necessary for the removal of the given table definition
151 */
152 protected StringBuffer generateDropTableStmt( TableDefinition table ) {
153 StringBuffer sb = new StringBuffer();
154 sb.append( "DROP TABLE " );
155 sb.append( table.getName() );
156 sb.append( " CASCADE;\n" );
157 return sb;
158 }
159
160 /**
161 * Generates the DDL statements necessary for the dropping of standard indexes for the given
162 * table definition. May be overwritten by a concrete implementation.
163 *
164 * @param table
165 * @return the DDL statements necessary for the dropping of standard indexes for the given table
166 * definition
167 */
168 protected StringBuffer generateDropIndexStmts( TableDefinition table ) {
169 StringBuffer sb = new StringBuffer();
170
171 // build drop statements for geometry indexes
172 Collection<ColumnDefinition> geometryColumns = new ArrayList<ColumnDefinition>();
173 for ( ColumnDefinition column : table.getColumns() ) {
174 if ( column.isGeometry() ) {
175 geometryColumns.add( column );
176 }
177 }
178
179 Iterator<ColumnDefinition> iter = geometryColumns.iterator();
180 int spatialIdxCnt = 1;
181 while ( iter.hasNext() ) {
182 iter.next();
183 sb.append( "DROP INDEX " );
184 sb.append( table.getName() + ( spatialIdxCnt++ ) );
185 sb.append( "_SPATIAL_IDX;" );
186 sb.append( '\n' );
187 }
188
189 // build table type specific drop index statements
190 switch ( table.getType() ) {
191 case JOIN_TABLE: {
192 // create an index on every column
193 ColumnDefinition[] columns = table.getColumns();
194 for ( int i = 0; i < columns.length; i++ ) {
195 sb.append( "DROP INDEX " );
196 sb.append( table.getName().toUpperCase() );
197 sb.append( '_' );
198 sb.append( columns[i].getName() );
199 sb.append( ';' );
200 sb.append( '\n' );
201 }
202 break;
203 }
204 default: {
205 break;
206 }
207 }
208 return sb;
209 }
210
211 /**
212 * Generates the DDL statements necessary for the removal of the given sequence. May be
213 * overwritten by a concrete implementation.
214 *
215 * @param sequenceName
216 * @return the DDL statements necessary for the removal of the given sequence definition
217 */
218 protected StringBuffer generateDropSequenceStmt( String sequenceName ) {
219 StringBuffer sb = new StringBuffer( "DROP SEQUENCE " );
220 sb.append( sequenceName );
221 sb.append( ";\n" );
222 return sb;
223 }
224
225 /**
226 * Creates a new instance of <code>DDLGenerator</code> from the given parameters.
227 *
228 * @param schemaURL
229 * @throws MalformedURLException
230 * @throws IOException
231 * @throws SAXException
232 * @throws XMLParsingException
233 * @throws XMLSchemaException
234 * @throws UnknownCRSException
235 */
236 protected DDLGenerator( URL schemaURL ) throws MalformedURLException, IOException, SAXException,
237 XMLParsingException, XMLSchemaException, UnknownCRSException {
238
239 System.out.println( Messages.format( "LOADING_SCHEMA_FILE", schemaURL ) );
240 MappedGMLSchemaDocument schemaDoc = new MappedGMLSchemaDocument();
241 schemaDoc.load( schemaURL );
242 schema = schemaDoc.parseMappedGMLSchema();
243 FeatureType[] featureTypes = schema.getFeatureTypes();
244 int concreteCount = 0;
245 for ( int i = 0; i < featureTypes.length; i++ ) {
246 if ( !featureTypes[i].isAbstract() ) {
247 concreteCount++;
248 }
249 }
250 System.out.println( Messages.format( "SCHEMA_INFO", new Integer( featureTypes.length ),
251 new Integer( featureTypes.length - concreteCount ),
252 new Integer( concreteCount ) ) );
253 System.out.println( Messages.getString( "RETRIEVING_TABLES" ) );
254 buildTableMap();
255 }
256
257 /**
258 * Returns all table definitions of the given type.
259 *
260 * @param type
261 * FEATURE_TYPE_TABLE, JOIN_TABLE or MULTI_PROPERTY_TABLE
262 * @return all table definitions of the given type.
263 */
264 protected TableDefinition[] getTables( int type ) {
265 Collection<TableDefinition> tableList = new ArrayList<TableDefinition>();
266 Iterator<String> iter = this.tables.keySet().iterator();
267 while ( iter.hasNext() ) {
268 String tableName = iter.next();
269 TableDefinition table = this.tables.get( tableName );
270 if ( table.getType() == type ) {
271 tableList.add( table );
272 }
273 }
274 return tableList.toArray( new TableDefinition[tableList.size()] );
275 }
276
277 /**
278 * Returns the table definition for the table with the given name. If no such definition exists,
279 * a new table definition is created and added to the internal <code>tables</code> map.
280 *
281 * @param tableName
282 * table definition to look up
283 * @param type
284 * type of the table (only respected, if a new TableDefinition instance is created)
285 * @return the table definition for the table with the given name.
286 */
287 private TableDefinition lookupTableDefinition( String tableName, int type ) {
288 TableDefinition table = this.tables.get( tableName );
289 if ( table == null ) {
290 table = new TableDefinition( tableName, type );
291 this.tables.put( tableName, table );
292 }
293 return table;
294 }
295
296 /**
297 * Collects the referenced tables and their columns from the input schema. Builds the member map
298 * <code>tables</code> from this data.
299 */
300 private void buildTableMap() {
301 FeatureType[] featureTypes = schema.getFeatureTypes();
302 for ( int i = 0; i < featureTypes.length; i++ ) {
303 if ( !featureTypes[i].isAbstract() ) {
304 buildTableMap( (MappedFeatureType) featureTypes[i] );
305 }
306 }
307 }
308
309 /**
310 * Collects the tables and their columns used in the annotation of the given feature type.
311 * Builds the member map <code>tables</code> from this data.
312 *
313 * @param featureType
314 * feature type to process
315 */
316 private void buildTableMap( MappedFeatureType featureType ) {
317 TableDefinition table = lookupTableDefinition( featureType.getTable(), FEATURE_TYPE_TABLE );
318
319 MappedGMLId gmlId = featureType.getGMLId();
320 addGMLIdColumns( gmlId, table );
321
322 if ( gmlId.getIdGenerator() instanceof DBSeqIdGenerator ) {
323 extractSequence( (DBSeqIdGenerator) featureType.getGMLId().getIdGenerator() );
324 }
325
326 PropertyType[] properties = featureType.getProperties();
327 for ( int i = 0; i < properties.length; i++ ) {
328 MappedPropertyType property = (MappedPropertyType) properties[i];
329 if ( property instanceof MappedSimplePropertyType ) {
330 buildTableMap( (MappedSimplePropertyType) property, table );
331 } else if ( property instanceof MappedGeometryPropertyType ) {
332 buildTableMap( (MappedGeometryPropertyType) property, table );
333 } else if ( property instanceof MappedFeaturePropertyType ) {
334 buildTableMap( (MappedFeaturePropertyType) property, table );
335 } else {
336 throw new RuntimeException( Messages.format( "ERROR_UNEXPECTED_PROPERTY_TYPE",
337 property.getClass().getName() ) );
338 }
339 }
340 }
341
342 /**
343 * Adds the name of the sequence that the given {@link DBSeqIdGenerator} refers to.
344 *
345 * @param idGenerator
346 * generator instance
347 */
348 private void extractSequence( DBSeqIdGenerator idGenerator ) {
349 this.sequences.add( idGenerator.getSequenceName() );
350 }
351
352 /**
353 * Adds the columns used in the given <code>MappedGMLId</code> to the also given
354 * <code>TableDefinition</code>.
355 *
356 * @param gmlId
357 * columns are taken from this gmlId mapping
358 * @param table
359 * columns are added to this table definition
360 */
361 private void addGMLIdColumns( MappedGMLId gmlId, TableDefinition table ) {
362 MappingField[] idFields = gmlId.getIdFields();
363 for ( int i = 0; i < idFields.length; i++ ) {
364 ColumnDefinition column = new ColumnDefinition( idFields[i].getField(), idFields[i].getType(), false, true,
365 false, -1 );
366 table.addColumn( column );
367 }
368 }
369
370 /**
371 * Collects the tables and their columns used in the annotation of the given simple property
372 * type. Builds the <code>table</code> member map from this data.
373 * <p>
374 * If the data for the property is stored in a related table, the table and column information
375 * used on the path to this table is also added to the <code>tables</code> member map.
376 *
377 * @param simpleProperty
378 * simple property type to process
379 * @param table
380 * table definition associated with the property definition
381 */
382 private void buildTableMap( MappedSimplePropertyType simpleProperty, TableDefinition table ) {
383 Collection<ColumnDefinition> newColumns = new ArrayList<ColumnDefinition>();
384 // array must always have length 1
385 TableRelation[] relations = simpleProperty.getTableRelations();
386 if ( simpleProperty.getMaxOccurs() != 1 && ( relations == null || relations.length < 1 ) ) {
387 throw new RuntimeException( Messages.format( "ERROR_INVALID_PROPERTY_DEFINITION", simpleProperty.getName() ) );
388 }
389
390 SimpleContent content = simpleProperty.getContent();
391 if ( content instanceof MappingField ) {
392 MappingField mf = (MappingField) content;
393 if ( relations == null || relations.length == 0 ) {
394 newColumns.add( new ColumnDefinition( mf.getField(), mf.getType(), simpleProperty.getMinOccurs() == 0,
395 false, -1 ) );
396 } else {
397 TableRelation firstRelation = relations[0];
398 MappingField[] fromFields = firstRelation.getFromFields();
399 for ( int i = 0; i < fromFields.length; i++ ) {
400 MappingField fromField = fromFields[i];
401 newColumns.add( new ColumnDefinition( fromField.getField(), fromField.getType(), false, false, -1 ) );
402 }
403 buildTableMap( relations, mf );
404 }
405 } else {
406 String msg = "Ignoring property '" + simpleProperty + "' - has virtual content.";
407 System.out.println( msg );
408 }
409 table.addColumns( newColumns );
410 }
411
412 /**
413 * Collects the tables and their columns used in the annotation of the given geometry property
414 * type. Builds the <code>table</code> member map from this data.
415 * <p>
416 * If the geometry for the property is stored in a related table, the table and column
417 * information used on the path to this table is also added to the <code>tables</code> member
418 * map.
419 *
420 * @param geometryProperty
421 * feature property type to process
422 * @param table
423 * table definition associated with the property definition
424 */
425 private void buildTableMap( MappedGeometryPropertyType geometryProperty, TableDefinition table ) {
426 Collection<ColumnDefinition> newColumns = new ArrayList<ColumnDefinition>();
427 TableRelation[] relations = geometryProperty.getTableRelations();
428 if ( geometryProperty.getMaxOccurs() != 1 && ( relations == null || relations.length < 1 ) ) {
429 throw new RuntimeException( Messages.format( "ERROR_INVALID_PROPERTY_DEFINITION",
430 geometryProperty.getName() ) );
431 }
432 if ( relations == null || relations.length == 0 ) {
433 newColumns.add( new ColumnDefinition( geometryProperty.getMappingField().getField(),
434 geometryProperty.getMappingField().getType(),
435 geometryProperty.getMinOccurs() == 0, true,
436 geometryProperty.getMappingField().getSRS() ) );
437 } else {
438 TableRelation firstRelation = relations[0];
439 MappingField[] fromFields = firstRelation.getFromFields();
440 for ( int i = 0; i < fromFields.length; i++ ) {
441 MappingField fromField = fromFields[i];
442 newColumns.add( new ColumnDefinition( fromField.getField(), fromField.getType(), false, true,
443 geometryProperty.getMappingField().getSRS() ) );
444 }
445 buildTableMap( relations, geometryProperty.getMappingField() );
446 }
447 table.addColumns( newColumns );
448 }
449
450 /**
451 * Collects the tables and their columns used in the annotation of the given feature property
452 * type. Builds the <code>table</code> member map from this data.
453 * <p>
454 * The table and column information used on the path to the table of the feature type is also
455 * added to the <code>tables</code> member map.
456 *
457 * @param featureProperty
458 * feature property type to process
459 * @param table
460 * table definition associated with the property definition
461 */
462 private void buildTableMap( MappedFeaturePropertyType featureProperty, TableDefinition table ) {
463 Collection<ColumnDefinition> newColumns = new ArrayList<ColumnDefinition>();
464
465 // array must always have length 1
466 TableRelation[] relations = featureProperty.getTableRelations();
467
468 // target feature type table must always be accessed via 'Relation'-elements
469 if ( relations == null || relations.length < 1 ) {
470 throw new RuntimeException( Messages.format( "ERROR_INVALID_FEATURE_PROPERTY_DEFINITION_1",
471 featureProperty.getName() ) );
472 }
473
474 // maxOccurs > 1: target feature type table must be accessed via join table
475 if ( featureProperty.getMaxOccurs() != 1 && ( relations.length < 2 ) ) {
476 throw new RuntimeException( Messages.format( "ERROR_INVALID_FEATURE_PROPERTY_DEFINITION_2",
477 featureProperty.getName() ) );
478 }
479
480 // add this feature type's key columns to current table
481 TableRelation firstRelation = relations[0];
482 MappingField[] fromFields = firstRelation.getFromFields();
483 boolean isNullable = featureProperty.getMinOccurs() == 0 && relations.length == 1;
484 for ( int i = 0; i < fromFields.length; i++ ) {
485 MappingField fromField = fromFields[i];
486 newColumns.add( new ColumnDefinition( fromField.getField(), fromField.getType(), isNullable, false, -1 ) );
487 }
488 table.addColumns( newColumns );
489
490 MappedFeatureType contentType = featureProperty.getFeatureTypeReference().getFeatureType();
491 buildTableMap( relations, featureProperty, contentType );
492 }
493
494 /**
495 * Collects the tables and their columns used in the relation tables from a simple/geometry
496 * property to it's content table. Builds the <code>table</code> member map from this data.
497 *
498 * @param relations
499 * relation tables from annotation of property type
500 * @param targetField
501 * holds the properties data
502 */
503 private void buildTableMap( TableRelation[] relations, MappingField targetField ) {
504
505 // process tables used in 'To'-element of each 'Relation'-element
506 for ( int i = 0; i < relations.length; i++ ) {
507 String tableName = relations[i].getToTable();
508 TableDefinition table = lookupTableDefinition( tableName, MULTI_PROPERTY_TABLE );
509 MappingField[] toFields = relations[i].getToFields();
510 for ( int j = 0; j < toFields.length; j++ ) {
511 boolean toIsFK = relations[i].getFKInfo() == TableRelation.FK_INFO.fkIsToField;
512 ColumnDefinition column = new ColumnDefinition( toFields[j].getField(), toFields[j].getType(), false,
513 !toIsFK, false, -1 );
514 // schmitz: assuming not part of PK
515 table.addColumn( column );
516 }
517 }
518
519 // process table used in 'To'-element of last 'Relation'-element (targetField refers to
520 // this)
521 ColumnDefinition column = null;
522 if ( targetField instanceof MappingGeometryField ) {
523
524 column = new ColumnDefinition( targetField.getField(), targetField.getType(), false, true,
525 ( (MappingGeometryField) targetField ).getSRS() );
526 } else {
527 column = new ColumnDefinition( targetField.getField(), targetField.getType(), false, false, -1 );
528 }
529
530 TableDefinition table = lookupTableDefinition( relations[relations.length - 1].getToTable(),
531 MULTI_PROPERTY_TABLE );
532 table.addColumn( column );
533 }
534
535 /**
536 * Collects the tables and their columns used in the relation tables from a feature property to
537 * it's content feature type. Builds the <code>table</code> member map from this data.
538 *
539 * @param relations
540 * relation tables from annotation of feature property type
541 * @param property
542 * @param targetType
543 * type contained in the feature property
544 */
545 private void buildTableMap( TableRelation[] relations, MappedPropertyType property, MappedFeatureType targetType ) {
546
547 TableDefinition table = lookupTableDefinition( relations[0].getFromTable(), FEATURE_TYPE_TABLE );
548
549 // process tables used in 'To'-element of each 'Relation'-element (except the last)
550 for ( int i = 0; i < relations.length - 1; i++ ) {
551 String tableName = relations[i].getToTable();
552 table = lookupTableDefinition( tableName, JOIN_TABLE );
553 MappingField[] toFields = relations[i].getToFields();
554 for ( int j = 0; j < toFields.length; j++ ) {
555 // boolean toIsFK = relations[i].getFKInfo() == TableRelation.FK_INFO.fkIsToField;
556 ColumnDefinition column = new ColumnDefinition( toFields[j].getField(), toFields[j].getType(), false,
557 true, false, -1 );
558 // schmitz: assuming NOT part of PK
559 table.addColumn( column );
560 }
561 }
562
563 // process table used in 'To'-element of last 'Relation'-element
564 MappedFeatureType[] concreteTypes = targetType.getConcreteSubstitutions();
565 MappingField[] toFields = relations[relations.length - 1].getToFields();
566
567 // if it refers to several target tables (target feature type is abstract), an additional
568 // column is needed (which determines the target feature type)
569 if ( concreteTypes.length > 1 ) {
570 String typeColumn = "featuretype";
571 if ( relations.length == 1 ) {
572 typeColumn = FT_PREFIX + property.getTableRelations()[0].getFromFields()[0].getField();
573 }
574 ColumnDefinition column = new ColumnDefinition( typeColumn, Types.VARCHAR, property.getMinOccurs() == 0,
575 false, -1 );
576 table.addColumn( column );
577 }
578 for ( int i = 0; i < concreteTypes.length; i++ ) {
579 MappedFeatureType concreteType = concreteTypes[i];
580 String tableName = concreteType.getTable();
581 table = lookupTableDefinition( tableName, FEATURE_TYPE_TABLE );
582 for ( int j = 0; j < toFields.length; j++ ) {
583 ColumnDefinition column = new ColumnDefinition( toFields[j].getField(), toFields[j].getType(), false,
584 false, -1 );
585 table.addColumn( column );
586 }
587 }
588
589 // process tables used in 'From'-element of each 'Relation'-element (except the first)
590 for ( int i = 1; i < relations.length; i++ ) {
591 String tableName = relations[i].getFromTable();
592 if ( i != relations.length - 1 ) {
593 table = lookupTableDefinition( tableName, JOIN_TABLE );
594 } else {
595 table = lookupTableDefinition( tableName, FEATURE_TYPE_TABLE );
596 }
597 MappingField[] fromFields = relations[i].getFromFields();
598 for ( int j = 0; j < fromFields.length; j++ ) {
599 ColumnDefinition column = new ColumnDefinition( fromFields[j].getField(), fromFields[j].getType(),
600 false, true, false, -1 );
601 table.addColumn( column );
602 }
603 }
604 }
605
606 /**
607 * Generates the DDL statements that can be used to build a relational schema that backs the GML
608 * schema.
609 *
610 * @param outputFile
611 * @throws IOException
612 */
613 public void generateCreateScript( String outputFile )
614 throws IOException {
615 PrintWriter writer = new PrintWriter( new FileWriter( outputFile ) );
616
617 System.out.println( Messages.format( "CREATE_SEQUENCES", new Integer( sequences.size() ) ) );
618 if ( sequences.size() > 0 ) {
619 writer.print( "/* CREATE SEQUENCES (" + sequences.size() + ") */\n" );
620 for ( String sequenceName : sequences ) {
621 writer.print( '\n' );
622 writer.print( generateCreateSequenceStmt( sequenceName ) );
623 }
624 }
625
626 TableDefinition[] tables = getTables( FEATURE_TYPE_TABLE );
627 System.out.println( Messages.format( "CREATE_FEATURE_TYPE", new Integer( tables.length ) ) );
628 writer.print( "\n\n/* CREATE FEATURE TABLES (" + tables.length + ") */\n" );
629 for ( int i = 0; i < tables.length; i++ ) {
630 System.out.println( tables[i].tableName );
631 writer.print( '\n' );
632 writer.print( generateCreateTableStmt( tables[i] ) );
633 writer.print( generateCreateIndexStmts( tables[i] ) );
634 }
635
636 tables = getTables( JOIN_TABLE );
637 if ( tables.length != 0 ) {
638 writer.print( "\n\n/* CREATE JOIN TABLES (" + tables.length + ") */\n" );
639 }
640 System.out.println( Messages.format( "CREATE_JOIN_TABLES", new Integer( tables.length ) ) );
641 for ( int i = 0; i < tables.length; i++ ) {
642 System.out.println( tables[i].tableName );
643 writer.print( '\n' );
644 writer.print( generateCreateTableStmt( tables[i] ) );
645 writer.print( generateCreateIndexStmts( tables[i] ) );
646 }
647
648 tables = getTables( MULTI_PROPERTY_TABLE );
649 if ( tables.length != 0 ) {
650 writer.print( "\n\n/* CREATE PROPERTY TABLES (" + tables.length + ") */\n" );
651 }
652 System.out.println( Messages.format( "CREATE_PROPERTY_TABLES", new Integer( tables.length ) ) );
653 for ( int i = 0; i < tables.length; i++ ) {
654 System.out.println( tables[i].tableName );
655 writer.print( '\n' );
656 writer.print( generateCreateTableStmt( tables[i] ) );
657 writer.print( generateCreateIndexStmts( tables[i] ) );
658 }
659 writer.close();
660 }
661
662 /**
663 * Generates the DDL statements that can be used to remove the relational schema again.
664 *
665 * @param outputFile
666 * @throws IOException
667 */
668 public void generateDropScript( String outputFile )
669 throws IOException {
670 PrintWriter writer = new PrintWriter( new FileWriter( outputFile ) );
671
672 TableDefinition[] tables = getTables( FEATURE_TYPE_TABLE );
673 System.out.println( Messages.format( "DROP_FEATURE_TYPE", new Integer( tables.length ) ) );
674 writer.print( "/* DROP FEATURE TABLES (" + tables.length + ") */\n" );
675 for ( int i = 0; i < tables.length; i++ ) {
676 writer.print( '\n' );
677 writer.print( generateDropIndexStmts( tables[i] ) );
678 writer.print( generateDropTableStmt( tables[i] ) );
679 }
680
681 tables = getTables( JOIN_TABLE );
682 writer.print( "\n\n/* DROP JOIN TABLES (" + tables.length + ") */\n" );
683 System.out.println( Messages.format( "DROP_JOIN_TABLES", new Integer( tables.length ) ) );
684 for ( int i = 0; i < tables.length; i++ ) {
685 writer.print( '\n' );
686 writer.print( generateDropIndexStmts( tables[i] ) );
687 writer.print( generateDropTableStmt( tables[i] ) );
688 }
689
690 tables = getTables( MULTI_PROPERTY_TABLE );
691 writer.print( "\n\n/* DROP PROPERTY TABLES (" + tables.length + ") */\n" );
692 System.out.println( Messages.format( "DROP_PROPERTY_TABLES", new Integer( tables.length ) ) );
693 for ( int i = 0; i < tables.length; i++ ) {
694 writer.print( '\n' );
695 writer.print( generateDropIndexStmts( tables[i] ) );
696 writer.print( generateDropTableStmt( tables[i] ) );
697 }
698
699 System.out.println( Messages.format( "DROP_SEQUENCES", new Integer( sequences.size() ) ) );
700 if ( sequences.size() > 0 ) {
701 writer.print( "\n\n/* DROP SEQUENCES (" + sequences.size() + ") */\n" );
702 for ( String sequenceName : sequences ) {
703 writer.print( '\n' );
704 writer.print( generateDropSequenceStmt( sequenceName ) );
705 }
706 }
707
708 writer.close();
709 }
710
711 /**
712 * @param args
713 * @throws IOException
714 * @throws SAXException
715 * @throws XMLParsingException
716 * @throws XMLSchemaException
717 * @throws UnknownCRSException
718 */
719 public static void main( String[] args )
720 throws IOException, SAXException, XMLParsingException, XMLSchemaException,
721 UnknownCRSException {
722
723 if ( args.length != 4 ) {
724 System.out.println( "Usage: DDLGenerator [FLAVOUR] <input.xsd> <create.sql> <drop.sql>" );
725 System.exit( 0 );
726 }
727
728 String flavour = args[0];
729 String schemaFile = args[1];
730 String createFile = args[2];
731 String dropFile = args[3];
732
733 DDLGenerator generator = null;
734 if ( "POSTGIS".equals( flavour ) ) {
735 generator = new PostGISDDLGenerator( new File( schemaFile ).toURI().toURL() );
736 } else if ( "ORACLE".equals( flavour ) ) {
737 generator = new OracleDDLGenerator( new File( schemaFile ).toURI().toURL() );
738 } else {
739 System.out.println( Messages.format( "ERROR_UNSUPPORTED_FLAVOUR", flavour ) );
740 }
741
742 generator.generateCreateScript( createFile );
743 generator.generateDropScript( dropFile );
744 }
745
746 /**
747 * Returns a string representation of the object.
748 *
749 * @return a string representation of the object.
750 */
751 @Override
752 public String toString() {
753 StringBuffer sb = new StringBuffer( Messages.getString( "RELATIONAL_SCHEMA" ) );
754 sb.append( '\n' );
755
756 TableDefinition[] tables = getTables( FEATURE_TYPE_TABLE );
757 sb.append( '\n' );
758 sb.append( tables.length );
759 sb.append( " feature type tables\n\n" );
760 for ( int i = 0; i < tables.length; i++ ) {
761 sb.append( tables[i] );
762 sb.append( '\n' );
763 }
764
765 sb.append( '\n' );
766 tables = getTables( JOIN_TABLE );
767 sb.append( tables.length );
768 sb.append( " join tables\n\n" );
769 for ( int i = 0; i < tables.length; i++ ) {
770 sb.append( tables[i] );
771 sb.append( '\n' );
772 }
773
774 sb.append( '\n' );
775 tables = getTables( MULTI_PROPERTY_TABLE );
776 sb.append( tables.length );
777 sb.append( " property tables\n\n" );
778 for ( int i = 0; i < tables.length; i++ ) {
779 sb.append( tables[i] );
780 sb.append( '\n' );
781 }
782 return sb.toString();
783 }
784
785 class TableDefinition {
786
787 private int type;
788
789 String tableName;
790
791 private Map<String, ColumnDefinition> columnsMap = new LinkedHashMap<String, ColumnDefinition>();
792
793 TableDefinition( String tableName, int type ) {
794 this.type = type;
795 this.tableName = tableName;
796 }
797
798 String getName() {
799 return this.tableName;
800 }
801
802 int getType() {
803 return this.type;
804 }
805
806 ColumnDefinition[] getColumns() {
807 Collection<ColumnDefinition> columns = new ArrayList<ColumnDefinition>();
808 Iterator<String> iter = columnsMap.keySet().iterator();
809 while ( iter.hasNext() ) {
810 String columnName = iter.next();
811 columns.add( columnsMap.get( columnName ) );
812 }
813 return columns.toArray( new ColumnDefinition[columns.size()] );
814 }
815
816 ColumnDefinition[] getPKColumns() {
817 Collection<ColumnDefinition> columns = new ArrayList<ColumnDefinition>();
818 Iterator<String> iter = columnsMap.keySet().iterator();
819 while ( iter.hasNext() ) {
820 String columnName = iter.next();
821 ColumnDefinition column = columnsMap.get( columnName );
822 if ( column.isPartOfPK() ) {
823 columns.add( columnsMap.get( columnName ) );
824 }
825 }
826 return columns.toArray( new ColumnDefinition[columns.size()] );
827 }
828
829 ColumnDefinition getColumn( String name ) {
830 return columnsMap.get( name );
831 }
832
833 void addColumn( ColumnDefinition column ) {
834 ColumnDefinition oldColumn = columnsMap.get( column.getName() );
835 if ( oldColumn != null ) {
836 if ( !( column.getType() == oldColumn.getType() ) ) {
837 String msg = Messages.format( "ERROR_COLUMN_DEFINITION_TYPES", column.getName(),
838 oldColumn.isNullable() ? "NULLABLE" : "NOT NULLABLE",
839 column.isNullable() ? "NULLABLE" : "NOT NULLABLE" );
840 throw new RuntimeException( msg );
841
842 }
843 if ( oldColumn.isPartOfPK() ) {
844 column = oldColumn;
845 }
846 }
847 columnsMap.put( column.getName(), column );
848 }
849
850 void addColumns( Collection<ColumnDefinition> columns ) {
851 Iterator<ColumnDefinition> iter = columns.iterator();
852 while ( iter.hasNext() ) {
853 ColumnDefinition column = iter.next();
854 addColumn( column );
855 }
856 }
857
858 @Override
859 public String toString() {
860 StringBuffer sb = new StringBuffer();
861 sb.append( Messages.format( "TABLE", this.tableName ) );
862 sb.append( Messages.getString( "PRIMARY_KEY" ) );
863 ColumnDefinition[] pkColumns = getPKColumns();
864 for ( int i = 0; i < pkColumns.length; i++ ) {
865 sb.append( '"' );
866 sb.append( pkColumns[i].getName() );
867 sb.append( '"' );
868 if ( i != pkColumns.length - 1 ) {
869 sb.append( ", " );
870 }
871 }
872 sb.append( '\n' );
873 Iterator<String> columnNameIter = this.columnsMap.keySet().iterator();
874 while ( columnNameIter.hasNext() ) {
875 String columnName = columnNameIter.next();
876 ColumnDefinition column = this.columnsMap.get( columnName );
877 try {
878 sb.append( Messages.format( "COLUMN", columnName,
879 Types.getTypeNameForSQLTypeCode( column.getType() ) + ":"
880 + column.getType(),
881 new Boolean( column.isNullable() ) ) );
882 } catch ( UnknownTypeException e ) {
883 // TODO Auto-generated catch block
884 e.printStackTrace();
885 }
886 sb.append( '\n' );
887 }
888 return sb.toString();
889 }
890 }
891
892 class ColumnDefinition {
893
894 private String columnName;
895
896 private int type;
897
898 private boolean isNullable;
899
900 private boolean isGeometryColumn;
901
902 private int srsCode;
903
904 private boolean isPartOfPK;
905
906 ColumnDefinition( String columnName, int type, boolean isNullable, boolean isGeometryColumn, int srsCode ) {
907 this.columnName = columnName;
908 this.type = type;
909 this.isNullable = isNullable;
910 this.isGeometryColumn = isGeometryColumn;
911 this.srsCode = srsCode;
912 }
913
914 ColumnDefinition( String columnName, int type, boolean isNullable, boolean isPartOfPK,
915 boolean isGeometryColumn, int srsCode ) {
916 this( columnName, type, isNullable, isGeometryColumn, srsCode );
917 this.isPartOfPK = isPartOfPK;
918 }
919
920 String getName() {
921 return this.columnName;
922 }
923
924 int getType() {
925 return this.type;
926 }
927
928 boolean isNullable() {
929 return this.isNullable;
930 }
931
932 boolean isGeometry() {
933 return this.isGeometryColumn;
934 }
935
936 int getSRS() {
937 return this.srsCode;
938 }
939
940 boolean isPartOfPK() {
941 return this.isPartOfPK;
942 }
943 }
944 }