001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/feature/schema/GMLSchema.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2006 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: mschneider $
076     * 
077     * @version $Revision: 7669 $, $Date: 2007-06-27 19:25:13 +0200 (Mi, 27 Jun 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         * Returns all non-abstract implementations of a given feature type that are defined in this schema.
152         * 
153         * @param featureType
154         * @return all non-abstract implementations of the feature type
155         */
156        public FeatureType[] getSubstitutions( FeatureType featureType ) {
157            FeatureType[] substitutions = new FeatureType[0];
158            List<FeatureType> featureTypeList = this.substitutionMap.get( featureType );
159            if ( featureTypeList != null ) {
160                substitutions = featureTypeList.toArray( new FeatureType[featureTypeList.size()] );
161            }
162            return substitutions;
163        }
164    
165        /**
166         * Returns whether the specified feature type is a valid substitution for the other specified feature type
167         * (according to the schema).
168         * 
169         * @param ft
170         * @param substitution
171         * @return true, if it is valid substitution, false otherwise
172         */
173        public boolean isValidSubstitution( FeatureType ft, FeatureType substitution ) {
174            FeatureType[] substitutions = getSubstitutions( ft );
175            for ( int i = 0; i < substitutions.length; i++ ) {
176                if ( substitutions[i].getName().equals( substitution.getName() ) ) {
177                    return true;
178                }
179            }
180            return false;
181        }
182    
183        /**
184         * Returns all types (abstract or concrete) that are substitutable by the given type.
185         * 
186         * TODO implement this a better way
187         * 
188         * @param substitution
189         * @return all types that are substitutable by <code>substitution</code>
190         */
191        public Set<FeatureType> getSubstitutables( FeatureType substitution ) {
192    
193            Set<FeatureType> ftSet = new HashSet<FeatureType>();
194            FeatureType[] allFts = getFeatureTypes();
195            for ( FeatureType ft : allFts ) {
196                if ( isValidSubstitution( ft, substitution ) ) {
197                    ftSet.add( ft );
198                }
199            }
200            return ftSet;
201        }
202    
203        /**
204         * Initializes the internal feature type map which is used to lookup feature types by name.
205         * 
206         * @param elementDeclarations
207         *            element declarations to process, only element declarations that are substitutable for "gml:_Feature"
208         *            are considered
209         * @throws XMLParsingException
210         * @throws UnknownCRSException
211         */
212        protected void buildFeatureTypeMap( ElementDeclaration[] elementDeclarations )
213                                throws XMLParsingException, UnknownCRSException {
214            for ( int i = 0; i < elementDeclarations.length; i++ ) {
215                LOG.logDebug( "Is element '" + elementDeclarations[i].getName() + "' a feature type definition?" );
216                if ( elementDeclarations[i].isSubstitutionFor( ABSTRACT_FEATURE ) ) {
217                    LOG.logDebug( "Yes." );
218                    FeatureType featureType = buildFeatureType( elementDeclarations[i] );
219                    featureTypeMap.put( featureType.getName(), featureType );
220                } else {
221                    LOG.logDebug( "No." );
222                }
223            }
224        }
225    
226        /**
227         * Initializes the internal feature type substitution map which is used to lookup substitutions for feature types.
228         * <p>
229         * NOTE: As this method relies on the feature type map, #initializeFeatureTypeMap(ElementDeclaration[]) must have
230         * been executed before.
231         * 
232         * @see #buildFeatureTypeMap(ElementDeclaration[])
233         * 
234         * @param elementDeclarations
235         *            element declarations of the feature types to process
236         */
237        protected void buildSubstitutionMap( ElementDeclaration[] elementDeclarations ) {
238            Iterator iter = featureTypeMap.values().iterator();
239            while ( iter.hasNext() ) {
240                FeatureType featureType = (FeatureType) iter.next();
241                List<FeatureType> substitutionList = new ArrayList<FeatureType>();
242                LOG.logDebug( "Collecting possible substitutions for feature type '" + featureType.getName() + "'." );
243                for ( int i = 0; i < elementDeclarations.length; i++ ) {
244                    if ( elementDeclarations[i].isAbstract() ) {
245                        LOG.logDebug( "Skipping '" + elementDeclarations[i].getName() + "' as it is abstract." );
246                    } else if ( elementDeclarations[i].isSubstitutionFor( featureType.getName() ) ) {
247                        LOG.logDebug( "Feature type '" + elementDeclarations[i].getName()
248                                      + "' is a concrete substitution for feature type '" + featureType.getName() + "'." );
249                        FeatureType substitution = this.featureTypeMap.get( elementDeclarations[i].getName() );
250                        substitutionList.add( substitution );
251                    }
252                }
253                this.substitutionMap.put( featureType, substitutionList );
254            }
255        }
256    
257        @SuppressWarnings("unused")
258        protected FeatureType buildFeatureType( ElementDeclaration element )
259                                throws XMLParsingException, UnknownCRSException {
260            LOG.logDebug( "Building feature type from element declaration '" + element.getName() + "'..." );
261            QualifiedName name = new QualifiedName( element.getName().getLocalName(), getTargetNamespace() );
262            ComplexTypeDeclaration complexType = (ComplexTypeDeclaration) element.getType().getTypeDeclaration();
263            ElementDeclaration[] subElements = complexType.getElements();
264            PropertyType[] properties = new PropertyType[subElements.length];
265            for ( int i = 0; i < properties.length; i++ ) {
266                properties[i] = buildPropertyType( subElements[i] );
267            }
268            return FeatureFactory.createFeatureType( name, element.isAbstract(), properties );
269        }
270    
271        protected PropertyType buildPropertyType( ElementDeclaration element )
272                                throws XMLSchemaException {
273            AbstractPropertyType propertyType = null;
274            QualifiedName propertyName = new QualifiedName( element.getName().getLocalName(), getTargetNamespace() );
275            QualifiedName typeName = element.getType().getName();
276            int type = determinePropertyType( element );
277            if ( typeName == null ) {
278                throw new XMLSchemaException( "No type defined for the property '" + propertyName
279                                              + "'. No inline definitions supported." );
280            }
281            if ( typeName.isInNamespace( XSDNS ) ) {
282                propertyType = FeatureFactory.createSimplePropertyType( propertyName, type, element.getMinOccurs(),
283                                                                        element.getMaxOccurs() );
284            } else {
285                switch ( type ) {
286                case Types.FEATURE: {
287                    propertyType = FeatureFactory.createFeaturePropertyType( propertyName, element.getMinOccurs(),
288                                                                             element.getMaxOccurs() );
289                    break;
290                }
291                case Types.GEOMETRY: {
292                    propertyType = FeatureFactory.createGeometryPropertyType( propertyName, typeName,
293                                                                              element.getMinOccurs(),
294                                                                              element.getMaxOccurs() );
295                    break;
296                }
297                default: {
298                    // hack to make extended simple types work...
299                    propertyType = FeatureFactory.createSimplePropertyType( propertyName, type, element.getMinOccurs(),
300                                                                            element.getMaxOccurs() );
301                    // throw new XMLSchemaException( "Unexpected type '"
302                    // + type + "' in buildPropertyType()." );
303                }
304                }
305            }
306            return propertyType;
307        }
308    
309        /**
310         * Heuristic method that tries to determine the type of GML property that is defined in an XSD element declaration.
311         * 
312         * @param element
313         *            <code>ElementDeclaration</code> that is a GML property definition
314         * @return type code from <code>Types</code>
315         * @throws UndefinedXSDTypeException
316         * 
317         * @see Types
318         */
319        protected final int determinePropertyType( ElementDeclaration element )
320                                throws UndefinedXSDTypeException {
321            QualifiedName typeName = element.getType().getName();
322            LOG.logDebug( "Determining property type code for property type='" + typeName + "'..." );
323            int type = Types.FEATURE;
324            if ( element.getType().isAnonymous() ) {
325                LOG.logDebug( "Inline declaration. Assuming generic GML feature of some kind." );
326            } else if ( typeName.isInNamespace( XSDNS ) ) {
327                LOG.logDebug( "Must be a basic XSD type." );
328                try {
329                    type = Types.getJavaTypeForXSDType( typeName.getLocalName() );
330                } catch ( UnknownTypeException e ) {
331                    throw new UndefinedXSDTypeException( e.getMessage(), e );
332                }
333            } else if ( typeName.isInNamespace( GMLNS ) ) {
334                LOG.logDebug( "Maybe a geometry property type?" );
335                try {
336                    type = Types.getJavaTypeForGMLType( typeName.getLocalName() );
337                    LOG.logDebug( "Yes." );
338                } catch ( UnknownTypeException e ) {
339                    LOG.logDebug( "No. Must be a generic GML feature of some kind." );
340                }
341            } else {
342                LOG.logDebug( "Should be a primitive type in our own namespace." );
343                if ( !typeName.isInNamespace( getTargetNamespace() ) ) {
344                    throw new UndefinedXSDTypeException( "Type '" + typeName
345                                                         + "' cannot be resolved (not in a supported namespace)." );
346                }
347                SimpleTypeDeclaration simpleType = getSimpleTypeDeclaration( typeName );
348                if ( simpleType == null ) {
349                    throw new UndefinedXSDTypeException( "Simple type '" + typeName + "' cannot be resolved." );
350                }
351                typeName = simpleType.getRestrictionBaseType().getName();
352                LOG.logDebug( "Simple base type: '" + typeName + "'. Must be a basic XSD Type." );
353                try {
354                    type = Types.getJavaTypeForXSDType( typeName.getLocalName() );
355                } catch ( UnknownTypeException e ) {
356                    throw new UndefinedXSDTypeException( e );
357                }
358            }
359            return type;
360        }
361    
362        /**
363         * Returns a string representation of the object.
364         * 
365         * @return a string representation of the object
366         */
367        @Override
368        public String toString() {
369    
370            Map<FeatureType, List<FeatureType>> substitutesMap = buildSubstitutesMap();
371    
372            StringBuffer sb = new StringBuffer( "GML schema targetNamespace='" );
373            sb.append( getTargetNamespace() );
374            sb.append( "'\n" );
375            sb.append( "\n*** " );
376            sb.append( featureTypeMap.size() );
377            sb.append( " feature type declarations ***\n" );
378            Iterator featureTypeIter = featureTypeMap.values().iterator();
379            while ( featureTypeIter.hasNext() ) {
380                FeatureType featureType = (FeatureType) featureTypeIter.next();
381                sb.append( featureTypeToString( featureType, substitutesMap ) );
382                if ( featureTypeIter.hasNext() ) {
383                    sb.append( "\n\n" );
384                }
385            }
386            return sb.toString();
387        }
388    
389        private Map<FeatureType, List<FeatureType>> buildSubstitutesMap() {
390    
391            Map<FeatureType, List<FeatureType>> substitutesMap = new HashMap<FeatureType, List<FeatureType>>();
392    
393            for ( FeatureType ft : getFeatureTypes() ) {
394                List<FeatureType> substitutesList = new ArrayList<FeatureType>();
395                for ( FeatureType substitution : getFeatureTypes() ) {
396                    if ( isValidSubstitution( substitution, ft ) ) {
397                        substitutesList.add( substitution );
398                    }
399                }
400                substitutesMap.put( ft, substitutesList );
401            }
402            return substitutesMap;
403        }
404    
405        private String featureTypeToString( FeatureType ft, Map<FeatureType, List<FeatureType>> substitutesMap ) {
406            StringBuffer sb = new StringBuffer( "- " );
407            if ( ft.isAbstract() ) {
408                sb.append( "(abstract) " );
409            }
410            sb.append( "Feature type '" );
411            sb.append( ft.getName() );
412            sb.append( "'\n" );
413    
414            FeatureType[] substFTs = getSubstitutions( ft );
415            if ( substFTs.length > 0 ) {
416                sb.append( "  is implemented by: " );
417                for ( int i = 0; i < substFTs.length; i++ ) {
418                    sb.append( "'" );
419                    sb.append( substFTs[i].getName().getLocalName() );
420                    if ( substFTs[i].isAbstract() ) {
421                        sb.append( " (abstract)" );
422                    }                
423                    sb.append( "'" );
424                    if ( i != substFTs.length - 1 ) {
425                        sb.append( "," );
426                    } else {
427                        sb.append( "\n" );
428                    }
429                }
430            } else {
431                sb.append( "  has no concrete implementations?!\n" );
432            }
433    
434            List<FeatureType> substitutesList = substitutesMap.get( ft );
435            sb.append( "  substitutes      : " );
436            for ( int i = 0; i < substitutesList.size(); i++ ) {
437                sb.append( "'" );
438                sb.append( substitutesList.get( i ).getName().getLocalName() );
439                if ( substitutesList.get( i ).isAbstract() ) {
440                    sb.append( " (abstract)" );
441                }
442                sb.append( "'" );
443                if ( i != substitutesList.size() - 1 ) {
444                    sb.append( "," );
445                }
446            }
447            sb.append( "\n" );
448    
449            PropertyType[] properties = ft.getProperties();
450            for ( int i = 0; i < properties.length; i++ ) {
451                PropertyType pt = properties[i];
452                sb.append( " + '" );
453                sb.append( pt.getName() );
454                if ( pt instanceof ComplexPropertyType ) {
455                    sb.append( "', Type: '" );
456                    sb.append( ( (ComplexPropertyType) pt ).getTypeName() );
457                }
458                sb.append( "', SQLType: " );
459                try {
460                    sb.append( Types.getTypeNameForSQLTypeCode( pt.getType() ) );
461                } catch ( UnknownTypeException e ) {
462                    sb.append( "unknown" );
463                }
464                sb.append( ", min: " );
465                sb.append( pt.getMinOccurs() );
466                sb.append( ", max: " );
467                sb.append( pt.getMaxOccurs() );
468                if ( i != properties.length - 1 ) {
469                    sb.append( "\n" );
470                }
471            }
472            return sb.toString();
473        }
474    }