001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/model/feature/schema/GMLSchema.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.model.feature.schema;
044    
045    import java.net.URI;
046    import java.util.ArrayList;
047    import java.util.HashMap;
048    import java.util.HashSet;
049    import java.util.Iterator;
050    import java.util.List;
051    import java.util.Map;
052    import java.util.Set;
053    
054    import org.deegree.datatypes.QualifiedName;
055    import org.deegree.datatypes.Types;
056    import org.deegree.datatypes.UnknownTypeException;
057    import org.deegree.framework.log.ILogger;
058    import org.deegree.framework.log.LoggerFactory;
059    import org.deegree.framework.xml.XMLParsingException;
060    import org.deegree.framework.xml.schema.ComplexTypeDeclaration;
061    import org.deegree.framework.xml.schema.ElementDeclaration;
062    import org.deegree.framework.xml.schema.SimpleTypeDeclaration;
063    import org.deegree.framework.xml.schema.UndefinedXSDTypeException;
064    import org.deegree.framework.xml.schema.XMLSchema;
065    import org.deegree.framework.xml.schema.XMLSchemaException;
066    import org.deegree.model.crs.UnknownCRSException;
067    import org.deegree.model.feature.FeatureFactory;
068    import org.deegree.ogcbase.CommonNamespaces;
069    
070    /**
071     * Represents a GML application schema document to provide easy access to it's components, especially the
072     * {@link FeatureType} definitions.
073     * 
074     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
075     * @author last edited by: $Author: apoth $
076     * 
077     * @version $Revision: 9343 $, $Date: 2007-12-27 14:30:32 +0100 (Do, 27 Dez 2007) $
078     */
079    public class GMLSchema extends XMLSchema {
080    
081        private final static ILogger LOG = LoggerFactory.getLogger( GMLSchema.class );
082    
083        private static URI XSDNS = CommonNamespaces.XSNS;
084    
085        private static URI GMLNS = CommonNamespaces.GMLNS;
086    
087        private static final QualifiedName ABSTRACT_FEATURE = new QualifiedName( "_Feature", GMLNS );
088    
089        // keys: QualifiedNames (feature type names), values: FeatureTypes
090        protected Map<QualifiedName, FeatureType> featureTypeMap = new HashMap<QualifiedName, FeatureType>();
091    
092        // keys: FeatureTypes, values: List (of FeatureTypes)
093        protected Map<FeatureType, List<FeatureType>> substitutionMap = new HashMap<FeatureType, List<FeatureType>>();
094    
095        /**
096         * Creates a new <code>GMLSchema</code> instance from the given parameters.
097         * 
098         * @param targetNamespace
099         * @param simpleTypes
100         * @param complexTypes
101         * @param elementDeclarations
102         * @throws XMLParsingException
103         * @throws UnknownCRSException
104         */
105        public GMLSchema( URI targetNamespace, SimpleTypeDeclaration[] simpleTypes, ComplexTypeDeclaration[] complexTypes,
106                          ElementDeclaration[] elementDeclarations ) throws XMLParsingException, UnknownCRSException {
107            super( targetNamespace, simpleTypes, complexTypes, elementDeclarations );
108            buildFeatureTypeMap( elementDeclarations );
109            buildSubstitutionMap( elementDeclarations );
110        }
111    
112        // TODO remove this constructor
113        protected GMLSchema( ElementDeclaration[] elementDeclarations, URI targetNamespace,
114                             SimpleTypeDeclaration[] simpleTypes, ComplexTypeDeclaration[] complexTypes )
115                                throws XMLSchemaException {
116            super( targetNamespace, simpleTypes, complexTypes, elementDeclarations );
117        }
118    
119        /**
120         * Returns all {@link FeatureType}s that are defined in the schema.
121         * 
122         * @return all FeatureTypes
123         */
124        public FeatureType[] getFeatureTypes() {
125            return this.featureTypeMap.values().toArray( new FeatureType[this.featureTypeMap.size()] );
126        }
127    
128        /**
129         * Looks up the {@link FeatureType} with the given {@link QualifiedName}.
130         * 
131         * @param qName
132         *            the QualifiedName to look up
133         * @return the FeatureType, if it is defined in the document, null otherwise
134         */
135        public FeatureType getFeatureType( QualifiedName qName ) {
136            return this.featureTypeMap.get( qName );
137        }
138    
139        /**
140         * Looks up the {@link FeatureType} with the given local name.
141         * 
142         * @param localName
143         *            the name to look up
144         * @return the FeatureType, if it is defined in the document, null otherwise
145         */
146        public FeatureType getFeatureType( String localName ) {
147            return getFeatureType( new QualifiedName( localName, getTargetNamespace() ) );
148        }
149    
150        /**
151         * Return whether the given feature type has more than one concrete substitution.
152         * <p>
153         * Read as: Is there only one concrete feature type that all instances of this type must have? Or are there several
154         * possible concrete subtypes?
155         * 
156         * @param ft
157         *            feature type to check
158         * @return true, if the feature type has more than once concrete implementations, false otherwise
159         */
160        public boolean hasSeveralImplementations( FeatureType ft ) {
161            return getSubstitutions( ft ).length > 1;
162        }
163    
164        /**
165         * Returns all non-abstract implementations of a given feature type that are defined in this schema.
166         * 
167         * @param featureType
168         * @return all non-abstract implementations of the feature type
169         */
170        public FeatureType[] getSubstitutions( FeatureType featureType ) {
171            FeatureType[] substitutions = new FeatureType[0];
172            List<FeatureType> featureTypeList = this.substitutionMap.get( featureType );
173            if ( featureTypeList != null ) {
174                substitutions = featureTypeList.toArray( new FeatureType[featureTypeList.size()] );
175            }
176            return substitutions;
177        }
178    
179        /**
180         * Returns whether the specified feature type is a valid substitution for the other specified feature type
181         * (according to the schema).
182         * 
183         * @param ft
184         * @param substitution
185         * @return true, if it is valid substitution, false otherwise
186         */
187        public boolean isValidSubstitution( FeatureType ft, FeatureType substitution ) {
188            FeatureType[] substitutions = getSubstitutions( ft );
189            for ( int i = 0; i < substitutions.length; i++ ) {
190                if ( substitutions[i].getName().equals( substitution.getName() ) ) {
191                    return true;
192                }
193            }
194            return false;
195        }
196    
197        /**
198         * Returns all types (abstract or concrete) that are substitutable by the given type.
199         * 
200         * TODO implement this a better way
201         * 
202         * @param substitution
203         * @return all types that are substitutable by <code>substitution</code>
204         */
205        public Set<FeatureType> getSubstitutables( FeatureType substitution ) {
206    
207            Set<FeatureType> ftSet = new HashSet<FeatureType>();
208            FeatureType[] allFts = getFeatureTypes();
209            for ( FeatureType ft : allFts ) {
210                if ( isValidSubstitution( ft, substitution ) ) {
211                    ftSet.add( ft );
212                }
213            }
214            return ftSet;
215        }
216    
217        /**
218         * Initializes the internal feature type map which is used to lookup feature types by name.
219         * 
220         * @param elementDeclarations
221         *            element declarations to process, only element declarations that are substitutable for "gml:_Feature"
222         *            are considered
223         * @throws XMLParsingException
224         * @throws UnknownCRSException
225         */
226        protected void buildFeatureTypeMap( ElementDeclaration[] elementDeclarations )
227                                throws XMLParsingException, UnknownCRSException {
228            for ( int i = 0; i < elementDeclarations.length; i++ ) {
229                LOG.logDebug( "Is element '" + elementDeclarations[i].getName() + "' a feature type definition?" );
230                if ( elementDeclarations[i].isSubstitutionFor( ABSTRACT_FEATURE ) ) {
231                    LOG.logDebug( "Yes." );
232                    FeatureType featureType = buildFeatureType( elementDeclarations[i] );
233                    featureTypeMap.put( featureType.getName(), featureType );
234                } else {
235                    LOG.logDebug( "No." );
236                }
237            }
238        }
239    
240        /**
241         * Initializes the internal feature type substitution map which is used to lookup substitutions for feature types.
242         * <p>
243         * NOTE: As this method relies on the feature type map, #initializeFeatureTypeMap(ElementDeclaration[]) must have
244         * been executed before.
245         * 
246         * @see #buildFeatureTypeMap(ElementDeclaration[])
247         * 
248         * @param elementDeclarations
249         *            element declarations of the feature types to process
250         */
251        protected void buildSubstitutionMap( ElementDeclaration[] elementDeclarations ) {
252            Iterator iter = featureTypeMap.values().iterator();
253            while ( iter.hasNext() ) {
254                FeatureType featureType = (FeatureType) iter.next();
255                List<FeatureType> substitutionList = new ArrayList<FeatureType>();
256                LOG.logDebug( "Collecting possible substitutions for feature type '" + featureType.getName() + "'." );
257                for ( int i = 0; i < elementDeclarations.length; i++ ) {
258                    if ( elementDeclarations[i].isAbstract() ) {
259                        LOG.logDebug( "Skipping '" + elementDeclarations[i].getName() + "' as it is abstract." );
260                    } else if ( elementDeclarations[i].isSubstitutionFor( featureType.getName() ) ) {
261                        LOG.logDebug( "Feature type '" + elementDeclarations[i].getName()
262                                      + "' is a concrete substitution for feature type '" + featureType.getName() + "'." );
263                        FeatureType substitution = this.featureTypeMap.get( elementDeclarations[i].getName() );
264                        substitutionList.add( substitution );
265                    }
266                }
267                this.substitutionMap.put( featureType, substitutionList );
268            }
269        }
270    
271        @SuppressWarnings("unused")
272        protected FeatureType buildFeatureType( ElementDeclaration element )
273                                throws XMLParsingException, UnknownCRSException {
274            LOG.logDebug( "Building feature type from element declaration '" + element.getName() + "'..." );
275            QualifiedName name = new QualifiedName( element.getName().getLocalName(), getTargetNamespace() );
276            ComplexTypeDeclaration complexType = (ComplexTypeDeclaration) element.getType().getTypeDeclaration();
277            ElementDeclaration[] subElements = complexType.getElements();
278            PropertyType[] properties = new PropertyType[subElements.length];
279            for ( int i = 0; i < properties.length; i++ ) {
280                properties[i] = buildPropertyType( subElements[i] );
281            }
282            return FeatureFactory.createFeatureType( name, element.isAbstract(), properties );
283        }
284    
285        protected PropertyType buildPropertyType( ElementDeclaration element )
286                                throws XMLSchemaException {
287            AbstractPropertyType propertyType = null;
288            QualifiedName propertyName = new QualifiedName( element.getName().getLocalName(), getTargetNamespace() );
289            QualifiedName typeName = element.getType().getName();
290            int type = determinePropertyType( element );
291            if ( typeName == null ) {
292                throw new XMLSchemaException( "No type defined for the property '" + propertyName
293                                              + "'. No inline definitions supported." );
294            }
295            if ( typeName.isInNamespace( XSDNS ) ) {
296                propertyType = FeatureFactory.createSimplePropertyType( propertyName, type, element.getMinOccurs(),
297                                                                        element.getMaxOccurs() );
298            } else {
299                switch ( type ) {
300                case Types.FEATURE: {
301                    propertyType = FeatureFactory.createFeaturePropertyType( propertyName, element.getMinOccurs(),
302                                                                             element.getMaxOccurs() );
303                    break;
304                }
305                case Types.GEOMETRY: {
306                    propertyType = FeatureFactory.createGeometryPropertyType( propertyName, typeName,
307                                                                              element.getMinOccurs(),
308                                                                              element.getMaxOccurs() );
309                    break;
310                }
311                default: {
312                    // hack to make extended simple types work...
313                    propertyType = FeatureFactory.createSimplePropertyType( propertyName, type, element.getMinOccurs(),
314                                                                            element.getMaxOccurs() );
315                    // throw new XMLSchemaException( "Unexpected type '"
316                    // + type + "' in buildPropertyType()." );
317                }
318                }
319            }
320            return propertyType;
321        }
322    
323        /**
324         * Heuristic method that tries to determine the type of GML property that is defined in an XSD element declaration.
325         * 
326         * @param element
327         *            <code>ElementDeclaration</code> that is a GML property definition
328         * @return type code from <code>Types</code>
329         * @throws UndefinedXSDTypeException
330         * 
331         * @see Types
332         */
333        protected final int determinePropertyType( ElementDeclaration element )
334                                throws UndefinedXSDTypeException {
335            QualifiedName typeName = element.getType().getName();
336            LOG.logDebug( "Determining property type code for property type='" + typeName + "'..." );
337            int type = Types.FEATURE;
338            if ( element.getType().isAnonymous() ) {
339                LOG.logDebug( "Inline declaration. Assuming generic GML feature of some kind." );
340            } else if ( typeName.isInNamespace( XSDNS ) ) {
341                LOG.logDebug( "Must be a basic XSD type." );
342                try {
343                    type = Types.getJavaTypeForXSDType( typeName.getLocalName() );
344                } catch ( UnknownTypeException e ) {
345                    throw new UndefinedXSDTypeException( e.getMessage(), e );
346                }
347            } else if ( typeName.isInNamespace( GMLNS ) ) {
348                LOG.logDebug( "Maybe a geometry property type?" );
349                try {
350                    type = Types.getJavaTypeForGMLType( typeName.getLocalName() );
351                    LOG.logDebug( "Yes." );
352                } catch ( UnknownTypeException e ) {
353                    LOG.logDebug( "No. Must be a generic GML feature of some kind." );
354                }
355            } else {
356                LOG.logDebug( "Should be a primitive type in our own namespace." );
357                if ( !typeName.isInNamespace( getTargetNamespace() ) ) {
358                    throw new UndefinedXSDTypeException( "Type '" + typeName
359                                                         + "' cannot be resolved (not in a supported namespace)." );
360                }
361                SimpleTypeDeclaration simpleType = getSimpleTypeDeclaration( typeName );
362                if ( simpleType == null ) {
363                    throw new UndefinedXSDTypeException( "Simple type '" + typeName + "' cannot be resolved." );
364                }
365                typeName = simpleType.getRestrictionBaseType().getName();
366                LOG.logDebug( "Simple base type: '" + typeName + "'. Must be a basic XSD Type." );
367                try {
368                    type = Types.getJavaTypeForXSDType( typeName.getLocalName() );
369                } catch ( UnknownTypeException e ) {
370                    throw new UndefinedXSDTypeException( e );
371                }
372            }
373            return type;
374        }
375    
376        /**
377         * Returns a string representation of the object.
378         * 
379         * @return a string representation of the object
380         */
381        @Override
382        public String toString() {
383    
384            Map<FeatureType, List<FeatureType>> substitutesMap = buildSubstitutesMap();
385    
386            StringBuffer sb = new StringBuffer( "GML schema targetNamespace='" );
387            sb.append( getTargetNamespace() );
388            sb.append( "'\n" );
389            sb.append( "\n*** " );
390            sb.append( featureTypeMap.size() );
391            sb.append( " feature type declarations ***\n" );
392            Iterator featureTypeIter = featureTypeMap.values().iterator();
393            while ( featureTypeIter.hasNext() ) {
394                FeatureType featureType = (FeatureType) featureTypeIter.next();
395                sb.append( featureTypeToString( featureType, substitutesMap ) );
396                if ( featureTypeIter.hasNext() ) {
397                    sb.append( "\n\n" );
398                }
399            }
400            return sb.toString();
401        }
402    
403        private Map<FeatureType, List<FeatureType>> buildSubstitutesMap() {
404    
405            Map<FeatureType, List<FeatureType>> substitutesMap = new HashMap<FeatureType, List<FeatureType>>();
406    
407            for ( FeatureType ft : getFeatureTypes() ) {
408                List<FeatureType> substitutesList = new ArrayList<FeatureType>();
409                for ( FeatureType substitution : getFeatureTypes() ) {
410                    if ( isValidSubstitution( substitution, ft ) ) {
411                        substitutesList.add( substitution );
412                    }
413                }
414                substitutesMap.put( ft, substitutesList );
415            }
416            return substitutesMap;
417        }
418    
419        private String featureTypeToString( FeatureType ft, Map<FeatureType, List<FeatureType>> substitutesMap ) {
420            StringBuffer sb = new StringBuffer( "- " );
421            if ( ft.isAbstract() ) {
422                sb.append( "(abstract) " );
423            }
424            sb.append( "Feature type '" );
425            sb.append( ft.getName() );
426            sb.append( "'\n" );
427    
428            FeatureType[] substFTs = getSubstitutions( ft );
429            if ( substFTs.length > 0 ) {
430                sb.append( "  is implemented by: " );
431                for ( int i = 0; i < substFTs.length; i++ ) {
432                    sb.append( "'" );
433                    sb.append( substFTs[i].getName().getLocalName() );
434                    if ( substFTs[i].isAbstract() ) {
435                        sb.append( " (abstract)" );
436                    }
437                    sb.append( "'" );
438                    if ( i != substFTs.length - 1 ) {
439                        sb.append( "," );
440                    } else {
441                        sb.append( "\n" );
442                    }
443                }
444            } else {
445                sb.append( "  has no concrete implementations?!\n" );
446            }
447    
448            List<FeatureType> substitutesList = substitutesMap.get( ft );
449            sb.append( "  substitutes      : " );
450            for ( int i = 0; i < substitutesList.size(); i++ ) {
451                sb.append( "'" );
452                sb.append( substitutesList.get( i ).getName().getLocalName() );
453                if ( substitutesList.get( i ).isAbstract() ) {
454                    sb.append( " (abstract)" );
455                }
456                sb.append( "'" );
457                if ( i != substitutesList.size() - 1 ) {
458                    sb.append( "," );
459                }
460            }
461            sb.append( "\n" );
462    
463            PropertyType[] properties = ft.getProperties();
464            for ( int i = 0; i < properties.length; i++ ) {
465                PropertyType pt = properties[i];
466                sb.append( " + '" );
467                sb.append( pt.getName() );
468                if ( pt instanceof ComplexPropertyType ) {
469                    sb.append( "', Type: '" );
470                    sb.append( ( (ComplexPropertyType) pt ).getTypeName() );
471                }
472                sb.append( "', SQLType: " );
473                try {
474                    sb.append( Types.getTypeNameForSQLTypeCode( pt.getType() ) );
475                } catch ( UnknownTypeException e ) {
476                    sb.append( "unknown" );
477                }
478                sb.append( ", min: " );
479                sb.append( pt.getMinOccurs() );
480                sb.append( ", max: " );
481                sb.append( pt.getMaxOccurs() );
482                if ( i != properties.length - 1 ) {
483                    sb.append( "\n" );
484                }
485            }
486            return sb.toString();
487        }
488    }