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