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