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