001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/io/datastore/schema/MappedGMLSchema.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.io.datastore.schema;
044    
045    import java.net.URI;
046    import java.util.Iterator;
047    import java.util.List;
048    import java.util.Properties;
049    
050    import org.deegree.datatypes.QualifiedName;
051    import org.deegree.datatypes.Types;
052    import org.deegree.framework.log.ILogger;
053    import org.deegree.framework.log.LoggerFactory;
054    import org.deegree.framework.xml.XMLParsingException;
055    import org.deegree.framework.xml.schema.ComplexTypeDeclaration;
056    import org.deegree.framework.xml.schema.ElementDeclaration;
057    import org.deegree.framework.xml.schema.SimpleTypeDeclaration;
058    import org.deegree.framework.xml.schema.XMLSchemaException;
059    import org.deegree.io.datastore.Datastore;
060    import org.deegree.io.datastore.DatastoreConfiguration;
061    import org.deegree.io.datastore.DatastoreException;
062    import org.deegree.io.datastore.DatastoreRegistry;
063    import org.deegree.io.datastore.idgenerator.IdGenerator;
064    import org.deegree.io.datastore.schema.MappedGMLId.IDPART_INFO;
065    import org.deegree.io.datastore.schema.content.MappingField;
066    import org.deegree.io.datastore.schema.content.MappingGeometryField;
067    import org.deegree.model.crs.CRSFactory;
068    import org.deegree.model.crs.CoordinateSystem;
069    import org.deegree.model.crs.UnknownCRSException;
070    import org.deegree.model.feature.schema.AbstractPropertyType;
071    import org.deegree.model.feature.schema.FeatureType;
072    import org.deegree.model.feature.schema.GMLSchema;
073    import org.deegree.model.feature.schema.PropertyType;
074    import org.deegree.model.feature.schema.UndefinedFeatureTypeException;
075    import org.deegree.ogcbase.CommonNamespaces;
076    import org.w3c.dom.Element;
077    
078    /**
079     * Represents a GML application schema document which is annotated with mapping (persistence)
080     * information.
081     * 
082     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
083     * @author last edited by: $Author: apoth $
084     * 
085     * @version $Revision: 9342 $, $Date: 2007-12-27 13:32:57 +0100 (Do, 27 Dez 2007) $
086     */
087    public class MappedGMLSchema extends GMLSchema {
088    
089        private final static ILogger LOG = LoggerFactory.getLogger( MappedGMLSchema.class );
090    
091        private static URI XSDNS = CommonNamespaces.XSNS;
092    
093        private MappedGMLSchemaDocument doc;
094    
095        private Datastore datastore;
096    
097        private boolean suppressXLinkOutput;
098    
099        private String namespacePrefix;
100    
101        private URI defaultSRS;
102    
103        private CoordinateSystem defaultCS;
104    
105        // TODO remove this hack (which is used to mark the first feature type as visible by default)
106        private boolean firstFeatureType = true;
107    
108        /**
109         * Creates a new <code>MappedGMLSchema</code> instance from the given parameters.
110         * 
111         * @param targetNamespace
112         * @param simpleTypes
113         * @param complexTypes
114         * @param elementDeclarations
115         * @param namespacePrefix
116         * @param defaultSRS
117         * @param backendConfiguration
118         * @param suppressXLinkOutput
119         * @param doc
120         * @throws XMLParsingException
121         * @throws UnknownCRSException
122         * @throws XMLSchemaException
123         */
124        MappedGMLSchema( URI targetNamespace, SimpleTypeDeclaration[] simpleTypes, ComplexTypeDeclaration[] complexTypes,
125                         ElementDeclaration[] elementDeclarations, String namespacePrefix, URI defaultSRS,
126                         DatastoreConfiguration backendConfiguration, boolean suppressXLinkOutput,
127                         MappedGMLSchemaDocument doc ) throws XMLParsingException, UnknownCRSException {
128    
129            super( elementDeclarations, targetNamespace, simpleTypes, complexTypes );
130    
131            this.doc = doc;
132            this.namespacePrefix = namespacePrefix;
133            this.defaultSRS = defaultSRS;
134            this.defaultCS = CRSFactory.create( defaultSRS.toString() );
135            this.datastore = registerDatastore( backendConfiguration );
136            this.suppressXLinkOutput = suppressXLinkOutput;
137    
138            buildFeatureTypeMap( elementDeclarations );
139            buildSubstitutionMap( elementDeclarations );
140            resolveFeatureTypeReferences();
141            resolveTargetTables();
142            checkIdentityPartConsistency();
143    
144            try {
145                this.datastore.bindSchema( this );
146            } catch ( DatastoreException e ) {
147                LOG.logError( e.getMessage(), e );
148                throw new XMLParsingException( e.getMessage() );
149            }
150        }
151    
152        /**
153         * Checks for all feature type definitions if it's featureIds 'identityPart' setting is valid:
154         * <ul>
155         * <li>if there is a direct fk from the feature's table to another feature table,
156         * 'identityPart' must be true</li>
157         * <li>if there is no explicit setting for the feature type, the implied setting is used,
158         * otherwise it is checked for validity</li>
159         * </ul>
160         * 
161         * @throws XMLSchemaException
162         */
163        private void checkIdentityPartConsistency()
164                                throws XMLSchemaException {
165            for ( FeatureType ft : this.featureTypeMap.values() ) {
166                MappedFeatureType mft = (MappedFeatureType) ft;
167                PropertyType[] properties = mft.getProperties();
168                for ( int i = 0; i < properties.length; i++ ) {
169                    MappedPropertyType property = (MappedPropertyType) properties[i];
170                    if ( property instanceof MappedFeaturePropertyType ) {
171                        MappedFeaturePropertyType featurePT = (MappedFeaturePropertyType) property;
172                        TableRelation[] relations = featurePT.getTableRelations();
173                        if ( relations.length == 1 ) {
174                            if ( relations[0].getFKInfo() == TableRelation.FK_INFO.fkIsToField ) {
175                                MappedFeatureType targetFT = featurePT.getFeatureTypeReference().getFeatureType();
176                                MappedGMLId id = targetFT.getGMLId();
177                                if ( id.getIdPartInfo() == IDPART_INFO.noIDInfo ) {
178                                    String msg = "FeatureId for feature type '" + targetFT.getName()
179                                                 + "' has to be part of the feature's identity - feature table "
180                                                 + "is a property of feature type '" + mft.getName() + "' and stores a fk.";
181                                    LOG.logInfo( msg );
182                                } else if ( id.getIdPartInfo() == IDPART_INFO.notIDPart ) {
183                                    String msg = "Invalid schema annotation: " + "FeatureId for feature type '"
184                                                 + targetFT.getName()
185                                                 + "' has to be part of the feature's identity - feature table "
186                                                 + "is a property of feature type '" + mft.getName()
187                                                 + "' and stores a fk. Set 'identityPart' to true for " + "feature type '"
188                                                 + targetFT.getName() + "'.";
189                                    throw new XMLSchemaException( msg );
190                                }
191                                id.setIdentityPart( true );
192                            }
193                        }
194                    }
195                }
196            }
197        }
198    
199        /**
200         * Retrieves a <code>Datastore</code> instance for the given configuration.
201         * <p>
202         * If a datastore with exactly the same configuration exists, the existing instance is returned.
203         * 
204         * @param backendConfiguration
205         * @throws XMLSchemaException
206         */
207        private Datastore registerDatastore( DatastoreConfiguration backendConfiguration )
208                                throws XMLSchemaException {
209            Datastore datastore = DatastoreRegistry.getDatastore( backendConfiguration );
210            if ( datastore == null ) {
211                try {
212                    datastore = (Datastore) backendConfiguration.getDatastoreClass().newInstance();
213                    datastore.configure( backendConfiguration );
214                } catch ( DatastoreException e ) {
215                    String msg = "Error configuring datastore with configuration '" + backendConfiguration + "'.";
216                    LOG.logError( msg, e );
217                    throw new XMLSchemaException( msg, e );
218                } catch ( Exception e ) {
219                    String msg = "Error instantiating datastore for class '" + backendConfiguration.getDatastoreClass()
220                                 + "'.";
221                    LOG.logError( msg, e );
222                    throw new XMLSchemaException( msg, e );
223                }
224                try {
225                    DatastoreRegistry.registerDatastore( datastore );
226                } catch ( DatastoreException e ) {
227                    String msg = "Error registering datastore with configuration '" + backendConfiguration + "'.";
228                    LOG.logError( msg, e );
229                    throw new XMLSchemaException( msg, e );
230                }
231            }
232            return datastore;
233        }
234    
235        /**
236         * Returns the underlying GML Application Schema document.
237         * 
238         * @return the underlying GML Application Schema document
239         */
240        public MappedGMLSchemaDocument getDocument() {
241            return this.doc;
242        }
243    
244        /**
245         * Returns the {@link Datastore} instance that handles this schema.
246         * 
247         * @return the Datastore instance that handles this schema
248         */
249        public Datastore getDatastore() {
250            return this.datastore;
251        }
252    
253        /**
254         * Returns whether GML output (of the associated datastore) will not use any XLinks.
255         * 
256         * @return true, if the GML output will not use XLinks, false otherwise
257         */
258        public boolean suppressXLinkOutput() {
259            return this.suppressXLinkOutput;
260        }
261    
262        /**
263         * Returns the default SRS for all geometry properties in the schema.
264         * 
265         * @return the default SRS for all geometry properties in the schema
266         */
267        public URI getDefaultSRS() {
268            return this.defaultSRS;
269        }
270    
271        /**
272         * Returns the default {@link CoordinateSystem} for all geometry properties in the schema.
273         * 
274         * @return the default CoordinateSystem for all geometry properties in the schema
275         */
276        public CoordinateSystem getDefaultCS() {
277            return this.defaultCS;
278        }
279    
280        /**
281         * Looks up the <code>FeatureType</code> with the given <code>QualifiedName</code>.
282         * 
283         * @param qName
284         *            the QualifiedName to look up
285         * @return the FeatureType, if it is defined in the document, null otherwise
286         */
287        @Override
288        public MappedFeatureType getFeatureType( QualifiedName qName ) {
289            return (MappedFeatureType) this.featureTypeMap.get( qName );
290        }
291    
292        /**
293         * Looks up the <code>FeatureType</code> with the given name.
294         * 
295         * @param localName
296         *            the name to look up
297         * @return the FeatureType, if it is defined in the document, null otherwise
298         */
299        @Override
300        public MappedFeatureType getFeatureType( String localName ) {
301            return getFeatureType( new QualifiedName( localName, getTargetNamespace() ) );
302        }
303    
304        /**
305         * Builds a {@link MappedFeatureType} from the given element declaration.
306         * 
307         * @param element
308         * @return feature type with persistence information
309         * @throws XMLParsingException
310         * @throws UnknownCRSException
311         */
312        @Override
313        protected MappedFeatureType buildFeatureType( ElementDeclaration element )
314                                throws XMLParsingException, UnknownCRSException {
315    
316            LOG.logDebug( "Building (mapped) feature type from element declaration '" + element.getName() + "'..." );
317    
318            int visibleCode = -1;
319            boolean isVisible = false;
320            boolean isUpdatable = false;
321            boolean isDeletable = false;
322            boolean isInsertable = false;
323            QualifiedName name = new QualifiedName( this.namespacePrefix, element.getName().getLocalName(),
324                                                    getTargetNamespace() );
325            MappedComplexTypeDeclaration complexType = (MappedComplexTypeDeclaration) element.getType().getTypeDeclaration();
326    
327            // extract mapping information from element annotation
328            Element annotationElement = ( (MappedElementDeclaration) element ).getAnnotation();
329            MappedGMLId gmlId = null;
330            String table = name.getLocalName().toLowerCase();
331            // use complexType annotation, if no element annotation present
332            if ( annotationElement == null ) {
333                annotationElement = complexType.getAnnotation();
334            }
335            // neither element nor complexType annotation, then use default mapping
336            if ( annotationElement == null ) {
337                LOG.logInfo( "Declaration of feature type '" + name
338                             + "' has no mapping information (annotation element). Defaulting to " + "table name '" + table
339                             + "' and gmlId field 'fid' (not identity part)." );
340                MappingField[] idFields = new MappingField[] { new MappingField( table, "fid", Types.VARCHAR ) };
341                IdGenerator idGenerator = IdGenerator.getInstance( IdGenerator.TYPE_UUID, new Properties() );
342                gmlId = new MappedGMLId( name.getLocalName().toUpperCase(), "_", idFields, idGenerator,
343                                         IDPART_INFO.noIDInfo );
344            } else {
345                gmlId = doc.extractGMLId( annotationElement, table );
346                table = gmlId.getIdFields()[0].getTable();
347                try {
348                    visibleCode = doc.parseVisible( annotationElement );
349                    isUpdatable = doc.parseIsUpdatable( annotationElement );
350                    isDeletable = doc.parseIsDeletable( annotationElement );
351                    isInsertable = doc.parseIsInsertable( annotationElement );
352                } catch ( XMLParsingException e ) {
353                    throw new XMLSchemaException( e );
354                }
355            }
356    
357            ElementDeclaration[] subElements = complexType.getElements();
358            PropertyType[] properties = new PropertyType[subElements.length];
359            for ( int i = 0; i < subElements.length; i++ ) {
360                MappedElementDeclaration subElement = (MappedElementDeclaration) subElements[i];
361                properties[i] = buildPropertyType( subElement, table );
362            }
363    
364            // default visibility for first feature type is true, for all others it's false
365            if ( this.firstFeatureType ) {
366                isVisible = true;
367                if ( visibleCode == 0 ) {
368                    isVisible = false;
369                }
370                this.firstFeatureType = false;
371            } else {
372                if ( visibleCode == 1 ) {
373                    isVisible = true;
374                }
375            }
376    
377            return new MappedFeatureType( name, element.isAbstract(), properties, table, gmlId, this, isVisible,
378                                          isUpdatable, isDeletable, isInsertable );
379        }
380    
381        protected PropertyType buildPropertyType( MappedElementDeclaration element, String table )
382                                throws XMLParsingException, UnknownCRSException {
383    
384            AbstractPropertyType propertyType;
385            QualifiedName propertyName = new QualifiedName( this.namespacePrefix, element.getName().getLocalName(),
386                                                            getTargetNamespace() );
387    
388            int minOccurs = element.getMinOccurs();
389            int maxOccurs = element.getMaxOccurs();
390    
391            QualifiedName typeName = element.getType().getName();
392            LOG.logDebug( "Building (mapped) property type from element declaration '" + propertyName + "', type='"
393                          + typeName + "'..." );
394            int type = determinePropertyType( element );
395    
396            // extract mapping annotation
397            Element annotationElement = element.getAnnotation();
398    
399            // get identityPart information from annotation
400            int identityCode = -1;
401            if ( annotationElement != null ) {
402                identityCode = doc.parseIdentityPart( annotationElement );
403            }
404    
405            if ( typeName.isInNamespace( XSDNS ) ) {
406                // simple property (basic xsd type)
407                if ( annotationElement == null ) {
408                    LOG.logDebug( "Using default mapping for property type '" + propertyName + "'." );
409                    String field = propertyName.getLocalName().toLowerCase();
410                    int typeCode = getDefaultSQLTypeForXSDType( typeName );
411                    MappingField mappingField = new MappingField( table, field, typeCode );
412                    propertyType = new MappedSimplePropertyType( propertyName, type, minOccurs, maxOccurs, true,
413                                                                 new TableRelation[0], mappingField );
414                } else {
415                    LOG.logDebug( "Parsing mapping information for simple property type." );
416                    boolean isIdentityPart = identityCode == 0 ? false : true;
417                    propertyType = doc.parseMappedSimplePropertyType( annotationElement, propertyName, type, minOccurs,
418                                                                      maxOccurs, isIdentityPart, table );
419                }
420            } else {
421                switch ( type ) {
422                case Types.GEOMETRY: {
423                    // geometry property
424                    if ( annotationElement == null ) {
425                        LOG.logDebug( "Using default mapping for property type '" + propertyName + "'." );
426                        String field = propertyName.getLocalName().toLowerCase();
427                        MappingGeometryField mappingField = new MappingGeometryField( table, field, Types.OTHER, -1 );
428                        propertyType = new MappedGeometryPropertyType( propertyName, typeName, type, minOccurs, maxOccurs,
429                                                                       false, this.defaultSRS, new TableRelation[0],
430                                                                       mappingField );
431                    } else {
432                        LOG.logDebug( "Parsing mapping information for geometry property type." );
433                        boolean isIdentityPart = identityCode == 1 ? true : false;
434                        propertyType = doc.parseMappedGeometryPropertyType( annotationElement, propertyName, typeName,
435                                                                            type, minOccurs, maxOccurs, isIdentityPart,
436                                                                            table );
437                    }
438                    break;
439                }
440                case Types.FEATURE: {
441                    // feature property
442                    if ( annotationElement == null ) {
443                        String msg = "Declaration of property type '" + propertyName
444                                     + "' has no mapping information (annotation element missing).";
445                        throw new XMLSchemaException( msg );
446                    }
447                    LOG.logDebug( "Parsing mapping information for feature property type." );
448                    boolean isIdentityPart = identityCode == 0 ? false : true;
449                    boolean isReferenceType = "ReferenceType".equals( typeName.getLocalName() );
450                    propertyType = doc.parseMappedFeaturePropertyType( annotationElement, propertyName, minOccurs,
451                                                                       maxOccurs, isIdentityPart, table, isReferenceType );
452                    break;
453                }
454                default: {
455                    // no known namespace -> assume simple property with user defined simple type
456                    // TODO check for inherited types
457    
458                    if ( annotationElement == null ) {
459                        LOG.logDebug( "Using default mapping for property type '" + propertyName + "'." );
460                        String field = propertyName.getLocalName().toLowerCase();
461                        int typeCode = getDefaultSQLTypeForXSDType( typeName );
462                        MappingField mappingField = new MappingField( table, field, typeCode );
463                        propertyType = new MappedSimplePropertyType( propertyName, type, minOccurs, maxOccurs, true,
464                                                                     new TableRelation[0], mappingField );
465                    } else {
466                        LOG.logDebug( "Parsing mapping information for simple property type." );
467                        boolean isIdentityPart = identityCode == 0 ? false : true;
468                        propertyType = doc.parseMappedSimplePropertyType( annotationElement, propertyName, type, minOccurs,
469                                                                          maxOccurs, isIdentityPart, table );
470                    }
471                }
472                }
473            }
474            return propertyType;
475        }
476    
477        /**
478         * @throws XMLSchemaException
479         */
480        private void resolveTargetTables()
481                                throws XMLSchemaException {
482            LOG.logDebug( "Resolving unspecified (null) table references for all FeaturePropertyTypes." );
483            Iterator iter = featureTypeMap.values().iterator();
484            while ( iter.hasNext() ) {
485                resolveTargetTables( (MappedFeatureType) iter.next() );
486            }
487        }
488    
489        private void resolveTargetTables( MappedFeatureType type )
490                                throws XMLSchemaException {
491            PropertyType[] properties = type.getProperties();
492            for ( int i = 0; i < properties.length; i++ ) {
493                MappedPropertyType property = (MappedPropertyType) properties[i];
494                if ( property instanceof MappedFeaturePropertyType ) {
495                    resolveTargetTables( (MappedFeaturePropertyType) property );
496                }
497            }
498        }
499    
500        private void resolveTargetTables( MappedFeaturePropertyType featurePT )
501                                throws XMLSchemaException {
502            MappedFeatureType targetFeatureType = featurePT.getFeatureTypeReference().getFeatureType();
503            if ( !targetFeatureType.isAbstract() ) {
504                TableRelation[] tableRelations = featurePT.getTableRelations();
505                if ( tableRelations.length == 0 ) {
506                    String msg = "Invalid feature property mapping '" + featurePT.getName()
507                                 + ": no relation elements - feature properties cannot be embedded in "
508                                 + "feature tables directly, but must use key relations to reference " + "subfeatures.";
509                    LOG.logError( msg );
510                    throw new XMLSchemaException( msg );
511                }
512                TableRelation lastRelation = tableRelations[tableRelations.length - 1];
513                MappingField[] targetFields = lastRelation.getToFields();
514                for ( int i = 0; i < targetFields.length; i++ ) {
515                    String table = targetFields[i].getTable();
516                    if ( table != null ) {
517                        if ( !targetFeatureType.getTable().equals( table ) ) {
518                            String msg = "Invalid feature property mapping: type '" + targetFeatureType.getName()
519                                         + "' is bound to table '" + targetFeatureType.getTable()
520                                         + "', but last table relation specifies table '" + table + "'.";
521                            LOG.logError( msg );
522                            throw new XMLSchemaException( msg );
523                        }
524                    }
525                    targetFields[i].setTable( targetFeatureType.getTable() );
526                }
527            }
528        }
529    
530        private void resolveFeatureTypeReferences()
531                                throws UndefinedFeatureTypeException {
532            LOG.logDebug( "Resolving (mapped) FeatureType references for namespace '" + getTargetNamespace() + "'." );
533            Iterator iter = featureTypeMap.values().iterator();
534            while ( iter.hasNext() ) {
535                resolveFeatureTypeReferences( (MappedFeatureType) iter.next() );
536            }
537        }
538    
539        private void resolveFeatureTypeReferences( MappedFeatureType featureType )
540                                throws UndefinedFeatureTypeException {
541            LOG.logDebug( "Resolving (mapped) FeatureType references in definition of FeatureType '"
542                          + featureType.getName() + "'." );
543            PropertyType[] properties = featureType.getProperties();
544            for ( int i = 0; i < properties.length; i++ ) {
545                if ( properties[i] instanceof MappedFeaturePropertyType ) {
546                    MappedFeaturePropertyType featurePT = (MappedFeaturePropertyType) properties[i];
547                    resolveFeatureTypeReferences( featurePT.getFeatureTypeReference() );
548                }
549            }
550        }
551    
552        private void resolveFeatureTypeReferences( MappedFeatureTypeReference reference )
553                                throws UndefinedFeatureTypeException {
554            LOG.logDebug( "Resolving (mapped) FeatureType references to FeatureType '" + reference.getName() + "'." );
555            if ( reference.isResolved() ) {
556                LOG.logDebug( "Already resolved." );
557            } else {
558                MappedFeatureType featureType = getFeatureType( reference.getName() );
559                if ( featureType == null ) {
560                    String msg = "Reference to feature type '" + reference.getName()
561                                 + "' in mapping annotation can not be resolved.";
562                    LOG.logDebug( msg );
563                    throw new UndefinedFeatureTypeException( msg );
564                }
565                reference.resolve( featureType );
566                resolveFeatureTypeReferences( featureType );
567            }
568        }
569    
570        /**
571         * Returns all non-abstract implementations of a given feature type that are defined in this
572         * schema.
573         * 
574         * @param ft
575         *            must be a <code>MappedFeatureType</code>
576         * @return all non-abstract implementations of the feature type
577         */
578        @Override
579        public MappedFeatureType[] getSubstitutions( FeatureType ft ) {
580            MappedFeatureType[] substitutions = new MappedFeatureType[0];
581            List<FeatureType> featureTypeList = this.substitutionMap.get( ft );
582            if ( featureTypeList != null ) {
583                substitutions = featureTypeList.toArray( new MappedFeatureType[featureTypeList.size()] );
584            }
585            return substitutions;
586        }
587    
588        // TODO: implement this
589        private int getDefaultSQLTypeForXSDType( @SuppressWarnings("unused")
590        QualifiedName xsdTypeName ) {
591            return -1;
592        }
593    
594        @Override
595        public String toString() {
596            StringBuffer sb = new StringBuffer( "GML schema targetNamespace='" );
597            sb.append( getTargetNamespace() );
598            sb.append( "'\n" );
599            sb.append( "\n*** " );
600            sb.append( featureTypeMap.size() );
601            sb.append( " feature type declarations ***\n" );
602            Iterator featureTypeIter = featureTypeMap.values().iterator();
603            while ( featureTypeIter.hasNext() ) {
604                MappedFeatureType featureType = (MappedFeatureType) featureTypeIter.next();
605                sb.append( featureTypeToString( featureType ) );
606                if ( featureTypeIter.hasNext() ) {
607                    sb.append( "\n\n" );
608                }
609            }
610            return sb.toString();
611        }
612    
613        private String featureTypeToString( MappedFeatureType ft ) {
614            StringBuffer sb = new StringBuffer( "- " );
615            if ( ft.isAbstract() ) {
616                sb.append( "(abstract) " );
617            }
618            sb.append( "Feature type '" );
619            sb.append( ft.getName() );
620            sb.append( "' -> TABLE: '" );
621            sb.append( ft.getTable() + "'" );
622            if ( ft.isUpdatable() ) {
623                sb.append( " updatable" );
624            }
625            if ( ft.isDeletable() ) {
626                sb.append( " deletable" );
627            }
628            if ( ft.isInsertable() ) {
629                sb.append( " insertable" );
630            }
631            sb.append( '\n' );
632            PropertyType[] properties = ft.getProperties();
633            for ( int i = 0; i < properties.length; i++ ) {
634                sb.append( " + '" );
635                sb.append( properties[i].getName() );
636                sb.append( "', type: " );
637                sb.append( properties[i].getType() );
638                sb.append( ", minOccurs: " );
639                sb.append( properties[i].getMinOccurs() );
640                sb.append( ", maxOccurs: " );
641                sb.append( properties[i].getMaxOccurs() );
642                sb.append( " -> " );
643                // sb.append( ( (MappedPropertyType) properties[i] ).getContents()[0] );
644                if ( i != properties.length - 1 ) {
645                    sb.append( "\n" );
646                }
647            }
648            return sb.toString();
649        }
650    }