    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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    }