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