    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/model/feature/GMLFeatureDocument.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;
    037
    038    import java.io.IOException;
    039    import java.net.MalformedURLException;
    040    import java.net.URI;
    041    import java.net.URL;
    042    import java.util.ArrayList;
    043    import java.util.Collection;
    044    import java.util.HashMap;
    045    import java.util.Iterator;
    046    import java.util.List;
    047    import java.util.Map;
    048
    049    import org.deegree.datatypes.QualifiedName;
    050    import org.deegree.datatypes.Types;
    051    import org.deegree.datatypes.UnknownTypeException;
    052    import org.deegree.datatypes.parameter.InvalidParameterValueException;
    053    import org.deegree.framework.log.ILogger;
    054    import org.deegree.framework.log.LoggerFactory;
    055    import org.deegree.framework.util.CharsetUtils;
    056    import org.deegree.framework.util.TimeTools;
    057    import org.deegree.framework.xml.DOMPrinter;
    058    import org.deegree.framework.xml.ElementList;
    059    import org.deegree.framework.xml.XMLParsingException;
    060    import org.deegree.framework.xml.XMLTools;
    061    import org.deegree.model.crs.UnknownCRSException;
    062    import org.deegree.model.feature.schema.FeaturePropertyType;
    063    import org.deegree.model.feature.schema.FeatureType;
    064    import org.deegree.model.feature.schema.GMLSchema;
    065    import org.deegree.model.feature.schema.GMLSchemaDocument;
    066    import org.deegree.model.feature.schema.GeometryPropertyType;
    067    import org.deegree.model.feature.schema.MultiGeometryPropertyType;
    068    import org.deegree.model.feature.schema.PropertyType;
    069    import org.deegree.model.feature.schema.SimplePropertyType;
    070    import org.deegree.model.spatialschema.GMLGeometryAdapter;
    071    import org.deegree.model.spatialschema.Geometry;
    072    import org.deegree.model.spatialschema.GeometryException;
    073    import org.deegree.ogcbase.CommonNamespaces;
    074    import org.deegree.ogcbase.GMLDocument;
    075    import org.w3c.dom.Attr;
    076    import org.w3c.dom.Element;
    077    import org.w3c.dom.Node;
    078    import org.w3c.dom.Text;
    079    import org.xml.sax.SAXException;
    080
    081    /**
    082     * Parser and wrapper class for GML feature documents.
    083     * <p>
    084     * <b>Validation</b><br>
    085     * Has validation capabilities: if the schema is provided or the document contains a reference to a schema the structure
    086     * of the generated features is checked. If no schema information is available, feature + property types are
    087     * heuristically determined from the feature instance in the document (guessing of simple property types can be turned
    088     * off, because it may cause unwanted effects).
    089     * </p>
    090     * <p>
    091     * <b>XLinks</b><br>
    092     * Has some basic understanding of XLink: Supports internal XLinks (i.e. the content for a feature is given by a
    093     * reference to a feature element in the same document). No support for external XLinks yet.
    094     * </p>
    095     * <p>
    096     * <b>Propagation of srsName attribute</b><br>
    097     * </p>
    098     *
    099     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
    100     * @author last edited by: $Author: mschneider $
    101     *
    102     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
    103     */
    104    public class GMLFeatureDocument extends GMLDocument {
    105
    106        private static final long serialVersionUID = -7626943858143104276L;
    107
    108        private final static ILogger LOG = LoggerFactory.getLogger( GMLFeatureDocument.class );
    109
    110        private static String FID = "fid";
    111
    112        private static String GMLID = "id";
    113
    114        private static URI GMLNS = CommonNamespaces.GMLNS;
    115
    116        private static String GMLID_NS = CommonNamespaces.GMLNS.toString();
    117
    118        private static QualifiedName PROP_NAME_BOUNDED_BY = new QualifiedName( "boundedBy", GMLNS );
    119
    120        private static QualifiedName PROP_NAME_DESCRIPTION = new QualifiedName( "description", GMLNS );
    121
    122        private static QualifiedName PROP_NAME_NAME = new QualifiedName( "name", GMLNS );
    123
    124        private static QualifiedName PROP_NAME_WKB_GEOM = new QualifiedName( "wkbGeom", GMLNS );
    125
    126        private static QualifiedName TYPE_NAME_BOX = new QualifiedName( "Box", GMLNS );
    127
    128        private static QualifiedName TYPE_NAME_LINESTRING = new QualifiedName( "LineString", GMLNS );
    129
    130        private static QualifiedName TYPE_NAME_MULTIGEOMETRY = new QualifiedName( "MultiGeometry", GMLNS );
    131
    132        private static QualifiedName TYPE_NAME_MULTILINESTRING = new QualifiedName( "MultiLineString", GMLNS );
    133
    134        private static QualifiedName TYPE_NAME_MULTIPOINT = new QualifiedName( "MultiPoint", GMLNS );
    135
    136        private static QualifiedName TYPE_NAME_MULTIPOLYGON = new QualifiedName( "MultiPolygon", GMLNS );
    137
    138        private static QualifiedName TYPE_NAME_POINT = new QualifiedName( "Point", GMLNS );
    139
    140        private static QualifiedName TYPE_NAME_POLYGON = new QualifiedName( "Polygon", GMLNS );
    141
    142        private static QualifiedName TYPE_NAME_SURFACE = new QualifiedName( "Surface", GMLNS );
    143
    144        private static QualifiedName TYPE_NAME_CURVE = new QualifiedName( "Curve", GMLNS );
    145
    146        private static QualifiedName TYPE_NAME_MULTISURFACE = new QualifiedName( "MultiSurface", GMLNS );
    147
    148        private static QualifiedName TYPE_NAME_MULTICURVE = new QualifiedName( "MultiCurve", GMLNS );
    149
    150        // key: namespace URI, value: GMLSchema
    151        protected Map<URI, GMLSchema> gmlSchemaMap;
    152
    153        // key: feature id, value: Feature
    154        protected Map<String, Feature> featureMap = new HashMap<String, Feature>();
    155
    156        // value: XLinkedFeatureProperty
    157        protected Collection<XLinkedFeatureProperty> xlinkPropertyList = new ArrayList<XLinkedFeatureProperty>();
    158
    159        private boolean guessSimpleTypes = false;
    160
    161        /**
    162         * Creates a new instance of <code>GMLFeatureDocument</code>.
    163         * <p>
    164         * Simple types encountered during parsing are "guessed", i.e. the parser tries to convert the values to double,
    165         * integer, calendar, etc. However, this may lead to unwanted results, e.g. a property value of "054604" is
    166         * converted to "54604".
    167         */
    168        public GMLFeatureDocument() {
    169            super();
    170        }
    171
    172        /**
    173         * Creates a new instance of <code>GMLFeatureDocument</code>.
    174         * <p>
    175         *
    176         * @param guessSimpleTypes
    177         *            set to true, if simple types should be "guessed" during parsing
    178         */
    179        public GMLFeatureDocument( boolean guessSimpleTypes ) {
    180            super();
    181            this.guessSimpleTypes = guessSimpleTypes;
    182        }
    183
    184        /**
    185         * Explicitly sets the GML schema information that the document must comply to.
    186         * <p>
    187         * This overrides any schema information that the document refers to.
    188         *
    189         * @param gmlSchemaMap
    190         *            key: namespace URI, value: GMLSchema
    191         */
    192        public void setSchemas( Map<URI, GMLSchema> gmlSchemaMap ) {
    193            this.gmlSchemaMap = gmlSchemaMap;
    194        }
    195
    196        /**
    197         * Returns the object representation for the root feature element.
    198         *
    199         * @return object representation for the root feature element.
    200         * @throws XMLParsingException
    201         * @throws UnknownCRSException
    202         */
    203        public Feature parseFeature()
    204                                throws XMLParsingException, UnknownCRSException {
    205            return this.parseFeature( (String) null );
    206        }
    207
    208        /**
    209         * Returns the object representation for the root feature element.
    210         *
    211         * @param defaultSRS
    212         *            default SRS for all a descendant geometry properties
    213         * @return object representation for the root feature element.
    214         * @throws XMLParsingException
    215         * @throws UnknownCRSException
    216         */
    217        public Feature parseFeature( String defaultSRS )
    218                                throws XMLParsingException, UnknownCRSException {
    219            Feature feature = this.parseFeature( this.getRootElement(), defaultSRS );
    220            resolveXLinkReferences();
    221            return feature;
    222        }
    223
    224        /**
    225         * Returns the object representation for the given feature element.
    226         *
    227         * @param element
    228         *            feature element
    229         * @return object representation for the given feature element.
    230         * @throws XMLParsingException
    231         * @throws UnknownCRSException
    232         */
    233        protected Feature parseFeature( Element element )
    234                                throws XMLParsingException, UnknownCRSException {
    235            return parseFeature( element, null );
    236        }
    237
    238        /**
    239         * Returns the object representation for the given feature element.
    240         *
    241         * @param element
    242         *            feature element
    243         * @param srsName
    244         *            default SRS for all descendant geometry properties
    245         * @return object representation for the given feature element
    246         * @throws XMLParsingException
    247         * @throws UnknownCRSException
    248         */
    249        protected Feature parseFeature( Element element, String srsName )
    250                                throws XMLParsingException, UnknownCRSException {
    251
    252            Feature feature = null;
    253            String fid = parseFeatureId( element );
    254            FeatureType ft = getFeatureType( element );
    255
    256            // override defaultSRS with SRS information from boundedBy element (if present)
    257            srsName = XMLTools.getNodeAsString( element, "gml:boundedBy/*[1]/@srsName", nsContext, srsName );
    258
    259            ElementList childList = XMLTools.getChildElements( element );
    260            Collection<FeatureProperty> propertyList = new ArrayList<FeatureProperty>( childList.getLength() );
    261            for ( int i = 0; i < childList.getLength(); i++ ) {
    262                Element propertyElement = childList.item( i );
    263                QualifiedName propertyName = getQualifiedName( propertyElement );
    264
    265                if ( PROP_NAME_BOUNDED_BY.equals( propertyName ) || PROP_NAME_WKB_GEOM.equals( propertyName ) ) {
    266                    // TODO
    267                } else if ( PROP_NAME_NAME.equals( propertyName ) || PROP_NAME_DESCRIPTION.equals( propertyName ) ) {
    268                    String s = XMLTools.getStringValue( propertyElement );
    269                    if ( s != null ) {
    270                        s = s.trim();
    271                    }
    272                    FeatureProperty property = createSimpleProperty( s, propertyName, Types.VARCHAR );
    273                    if ( property != null ) {
    274                        propertyList.add( property );
    275                    }
    276                } else {
    277                    try {
    278                        FeatureProperty property = parseProperty( childList.item( i ), ft, srsName );
    279                        if ( property != null ) {
    280                            propertyList.add( property );
    281                        }
    282                    } catch ( XMLParsingException xmle ) {
    283                        LOG.logInfo( "An error occurred while trying to parse feature with fid: " + fid );
    284                        throw xmle;
    285                    }
    286                }
    287            }
    288
    289            FeatureProperty[] featureProperties = propertyList.toArray( new FeatureProperty[propertyList.size()] );
    290            feature = FeatureFactory.createFeature( fid, ft, featureProperties );
    291
    292            if ( !"".equals( fid ) ) {
    293                if ( this.featureMap.containsKey( fid ) ) {
    294                    String msg = Messages.format( "ERROR_FEATURE_ID_NOT_UNIQUE", fid );
    295                    throw new XMLParsingException( msg );
    296                }
    297                this.featureMap.put( fid, feature );
    298            }
    299
    300            return feature;
    301        }
    302
    303        /**
    304         * Returns the object representation for the given property element.
    305         *
    306         * @param propertyElement
    307         *            property element
    308         * @param ft
    309         *            feature type of the feature that the property belongs to
    310         * @return object representation for the given property element
    311         * @throws XMLParsingException
    312         * @throws UnknownCRSException
    313         */
    314        public FeatureProperty parseProperty( Element propertyElement, FeatureType ft )
    315                                throws XMLParsingException, UnknownCRSException {
    316            return parseProperty( propertyElement, ft, null );
    317        }
    318
    319        /**
    320         * Returns the object representation for the given property element.
    321         *
    322         * @param propertyElement
    323         *            property element
    324         * @param ft
    325         *            feature type of the feature that the property belongs to
    326         * @param srsName
    327         *            default SRS for all a descendant geometry properties
    328         * @return object representation for the given property element.
    329         * @throws XMLParsingException
    330         * @throws UnknownCRSException
    331         */
    332        public FeatureProperty parseProperty( Element propertyElement, FeatureType ft, String srsName )
    333                                throws XMLParsingException, UnknownCRSException {
    334
    335            FeatureProperty property = null;
    336            QualifiedName propertyName = getQualifiedName( propertyElement );
    337
    338            PropertyType propertyType = ft.getProperty( propertyName );
    339            if ( propertyType == null ) {
    340                throw new XMLParsingException( Messages.format( "ERROR_NO_PROPERTY_TYPE", ft.getName(), propertyName ) );
    341            }
    342
    343            if ( propertyType instanceof SimplePropertyType ) {
    344                int typeCode = propertyType.getType();
    345                String s = null;
    346                if ( typeCode == Types.ANYTYPE ) {
    347                    Element child = XMLTools.getRequiredElement( propertyElement, "*", nsContext );
    348                    s = DOMPrinter.nodeToString( child, CharsetUtils.getSystemCharset() );
    349                } else {
    350                    s = XMLTools.getStringValue( propertyElement ).trim();
    351                }
    352
    353                String nil = propertyElement.getAttributeNS( "http://www.w3.org/2001/XMLSchema-instance", "nil" );
    354
    355                if ( nil == null || !nil.equals( "true" ) ) {
    356                    property = createSimpleProperty( s, propertyName, typeCode );
    357                }
    358
    359            } else if ( propertyType instanceof GeometryPropertyType ) {
    360                Element contentElement = XMLTools.getFirstChildElement( propertyElement );
    361                if ( contentElement == null ) {
    362                    String msg = Messages.format( "ERROR_PROPERTY_NO_CHILD", propertyName, "geometry" );
    363                    throw new XMLParsingException( msg );
    364                }
    365
    366                String nil = propertyElement.getAttributeNS( "http://www.w3.org/2001/XMLSchema-instance", "nil" );
    367
    368                if ( nil == null || !nil.equals( "true" ) ) {
    369                    property = createGeometryProperty( contentElement, propertyName, srsName );
    370                }
    371            } else if ( propertyType instanceof MultiGeometryPropertyType ) {
    372                throw new XMLParsingException( "Handling of MultiGeometryPropertyType not "
    373                                               + "implemented in GMLFeatureDocument yet." );
    374            } else if ( propertyType instanceof FeaturePropertyType ) {
    375                String nil = propertyElement.getAttributeNS( "http://www.w3.org/2001/XMLSchema-instance", "nil" );
    376
    377                if ( nil != null && nil.equals( "true" ) ) {
    378                    return null;
    379                }
    380
    381                List<Node> childElements = XMLTools.getNodes( propertyElement, "*", nsContext );
    382                switch ( childElements.size() ) {
    383                case 0: {
    384                    // feature content must be xlinked
    385                    Text xlinkHref = (Text) XMLTools.getNode( propertyElement, "@xlink:href/text()", nsContext );
    386                    if ( xlinkHref == null ) {
    387                        String msg = Messages.format( "ERROR_INVALID_FEATURE_PROPERTY", propertyName );
    388                        throw new XMLParsingException( msg );
    389                    }
    390                    String href = xlinkHref.getData();
    391                    if ( !href.startsWith( "#" ) ) {
    392                        try {
    393                            property = FeatureFactory.createFeatureProperty( propertyName, new URL( href ) );
    394                            break;
    395                        } catch ( MalformedURLException e ) {
    396                            throw new XMLParsingException( Messages.format( "ERROR_XLINK_NOT_VALID", href ) );
    397                        }
    398                    }
    399                    String fid = href.substring( 1 );
    400                    property = new XLinkedFeatureProperty( propertyName, fid );
    401                    xlinkPropertyList.add( (XLinkedFeatureProperty) property );
    402                    break;
    403                }
    404                case 1: {
    405                    // feature content is given inline
    406                    Feature propertyValue = parseFeature( (Element) childElements.get( 0 ), srsName );
    407                    property = FeatureFactory.createFeatureProperty( propertyName, propertyValue );
    408                    break;
    409                }
    410                default: {
    411                    // String msg =
    412                    Messages.format( "ERROR_INVALID_FEATURE_PROPERTY2", propertyName, childElements.size() );
    413                    // throw new XMLParsingException( msg );
    414                }
    415                }
    416            }
    417            return property;
    418        }
    419
    420        protected void resolveXLinkReferences()
    421                                throws XMLParsingException {
    422            Iterator<XLinkedFeatureProperty> iter = this.xlinkPropertyList.iterator();
    423            while ( iter.hasNext() ) {
    424                XLinkedFeatureProperty xlinkProperty = iter.next();
    425                String fid = xlinkProperty.getTargetFeatureId();
    426                Feature targetFeature = this.featureMap.get( fid );
    427                if ( targetFeature == null ) {
    428                    String msg = Messages.format( "ERROR_XLINK_NOT_RESOLVABLE", fid );
    429                    throw new XMLParsingException( msg );
    430                }
    431                xlinkProperty.setValue( targetFeature );
    432            }
    433        }
    434
    435        /**
    436         * Creates a simple property from the given parameters.
    437         * <p>
    438         * Converts the string value to the given target type.
    439         *
    440         * @param s
    441         *            string value from a simple property to be converted
    442         * @param propertyName
    443         *            name of the simple property
    444         * @param typeCode
    445         *            target type code
    446         * @return property value in the given target type.
    447         * @throws XMLParsingException
    448         */
    449        private FeatureProperty createSimpleProperty( String s, QualifiedName propertyName, int typeCode )
    450                                throws XMLParsingException {
    451
    452            Object propertyValue = null;
    453            switch ( typeCode ) {
    454            case Types.VARCHAR:
    455            case Types.ANYTYPE: {
    456                propertyValue = s;
    457                break;
    458            }
    459            case Types.INTEGER:
    460            case Types.SMALLINT: {
    461                try {
    462                    propertyValue = new Integer( s );
    463                } catch ( NumberFormatException e ) {
    464                    String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, "Integer" );
    465                    throw new XMLParsingException( msg );
    466                }
    467                break;
    468            }
    469            case Types.NUMERIC:
    470            case Types.DOUBLE: {
    471                try {
    472                    propertyValue = new Double( s );
    473                } catch ( NumberFormatException e ) {
    474                    String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, "Double" );
    475                    throw new XMLParsingException( msg );
    476                }
    477                break;
    478            }
    479            case Types.DECIMAL:
    480            case Types.FLOAT: {
    481                try {
    482                    propertyValue = new Float( s );
    483                } catch ( NumberFormatException e ) {
    484                    String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, "Float" );
    485                    throw new XMLParsingException( msg );
    486                }
    487                break;
    488            }
    489            case Types.BOOLEAN: {
    490                propertyValue = new Boolean( s );
    491                break;
    492            }
    493            case Types.DATE:
    494            case Types.TIMESTAMP: {
    495                propertyValue = TimeTools.createCalendar( s ).getTime();
    496                break;
    497            }
    498            default: {
    499                String typeString = "" + typeCode;
    500                try {
    501                    typeString = Types.getTypeNameForSQLTypeCode( typeCode );
    502                } catch ( UnknownTypeException e ) {
    503                    LOG.logError( "No type name for code: " + typeCode );
    504                }
    505                String msg = Messages.format( "ERROR_UNHANDLED_TYPE", "" + typeString );
    506                LOG.logError( msg );
    507                throw new XMLParsingException( msg );
    508            }
    509            }
    510            FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, propertyValue );
    511            return property;
    512        }
    513
    514        /**
    515         * Creates a geometry property from the given parameters.
    516         *
    517         * @param contentElement
    518         *            child element of a geometry property to be converted
    519         * @param propertyName
    520         *            name of the geometry property
    521         * @param srsName
    522         *            default SRS for the geometry (may be overwritten in geometry elements)
    523         * @return geometry property
    524         * @throws XMLParsingException
    525         */
    526        private FeatureProperty createGeometryProperty( Element contentElement, QualifiedName propertyName, String srsName )
    527                                throws XMLParsingException {
    528
    529            Geometry propertyValue = null;
    530            try {
    531                propertyValue = GMLGeometryAdapter.wrap( contentElement, srsName );
    532            } catch ( GeometryException e ) {
    533                LOG.logError( e.getMessage(), e );
    534                String msg = Messages.format( "ERROR_CONVERTING_GEOMETRY_PROPERTY", propertyName, "-", e.getMessage() );
    535                throw new XMLParsingException( msg );
    536            }
    537
    538            FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, propertyValue );
    539            return property;
    540        }
    541
    542        /**
    543         * Determines and retrieves the GML schemas that the document refers to.
    544         *
    545         * @return the GML schemas that are attached to the document, keys are URIs (namespaces), values are GMLSchemas.
    546         * @throws XMLParsingException
    547         * @throws UnknownCRSException
    548         */
    549        protected Map<URI, GMLSchema> getGMLSchemas()
    550                                throws XMLParsingException, UnknownCRSException {
    551
    552            if ( this.gmlSchemaMap == null ) {
    553                gmlSchemaMap = new HashMap<URI, GMLSchema>();
    554                Map<URI, URL> schemaMap = getAttachedSchemas();
    555                Iterator<URI> it = schemaMap.keySet().iterator();
    556                while ( it.hasNext() ) {
    557                    URI nsURI = it.next();
    558                    URL schemaURL = schemaMap.get( nsURI );
    559                    GMLSchemaDocument schemaDocument = new GMLSchemaDocument();
    560                    LOG.logDebug( "Retrieving schema document for namespace '" + nsURI + "' from URL '" + schemaURL + "'." );
    561                    try {
    562                        schemaDocument.load( schemaURL );
    563                        GMLSchema gmlSchema = schemaDocument.parseGMLSchema();
    564                        gmlSchemaMap.put( nsURI, gmlSchema );
    565                    } catch ( IOException e ) {
    566                        String msg = Messages.format( "ERROR_RETRIEVING_SCHEMA", schemaURL, e.getMessage() );
    567                        throw new XMLParsingException( msg );
    568                    } catch ( SAXException e ) {
    569                        String msg = Messages.format( "ERROR_SCHEMA_NOT_XML", schemaURL, e.getMessage() );
    570                        throw new XMLParsingException( msg );
    571                    } catch ( XMLParsingException e ) {
    572                        String msg = Messages.format( "ERROR_SCHEMA_PARSING1", schemaURL, e.getMessage() );
    573                        throw new XMLParsingException( msg );
    574                    }
    575                }
    576            }
    577
    578            return this.gmlSchemaMap;
    579        }
    580
    581        /**
    582         * Returns the GML schema for the given namespace.
    583         *
    584         * @param ns
    585         * @return the GML schema for the given namespace if it is declared, null otherwise.
    586         * @throws XMLParsingException
    587         * @throws UnknownCRSException
    588         */
    589        protected GMLSchema getSchemaForNamespace( URI ns )
    590                                throws XMLParsingException, UnknownCRSException {
    591            Map<URI, GMLSchema> gmlSchemaMap = getGMLSchemas();
    592            GMLSchema schema = gmlSchemaMap.get( ns );
    593            return schema;
    594        }
    595
    596        /**
    597         * Returns the feature type with the given name.
    598         * <p>
    599         * If schema information is available and a feature type with the given name is not defined, an XMLParsingException
    600         * is thrown.
    601         *
    602         * @param ftName
    603         *            feature type to look up
    604         * @return the feature type with the given name if it is declared, null otherwise.
    605         * @throws XMLParsingException
    606         * @throws UnknownCRSException
    607         */
    608        protected FeatureType getFeatureType( QualifiedName ftName )
    609                                throws XMLParsingException, UnknownCRSException {
    610            FeatureType featureType = null;
    611            if ( this.gmlSchemaMap != null ) {
    612                GMLSchema schema = getSchemaForNamespace( ftName.getNamespace() );
    613                if ( schema == null ) {
    614                    String msg = Messages.format( "ERROR_SCHEMA_NO_SCHEMA_FOR_NS", ftName.getNamespace() );
    615                    throw new XMLParsingException( msg );
    616                }
    617                featureType = schema.getFeatureType( ftName );
    618                if ( featureType == null ) {
    619                    String msg = Messages.format( "ERROR_SCHEMA_FEATURE_TYPE_UNKNOWN", ftName );
    620                    throw new XMLParsingException( msg );
    621                }
    622            }
    623            return featureType;
    624        }
    625
    626        /**
    627         * Parses the feature id attribute from the given feature element.
    628         * <p>
    629         * Looks after 'gml:id' (GML 3 style) first, if no such attribute is present, the 'fid' (GML 2 style) attribute is
    630         * used.
    631         *
    632         * @param featureElement
    633         * @return the feature id, this is "" if neither a 'gml:id' nor a 'fid' attribute is present
    634         */
    635        protected String parseFeatureId( Element featureElement ) {
    636            String fid = featureElement.getAttributeNS( GMLID_NS, GMLID );
    637            if ( fid.length() == 0 ) {
    638                fid = featureElement.getAttribute( FID );
    639            }
    640
    641            // Check that the feature id has the correct form. "fid" and "gml:id" are both based
    642            // on the XML type "ID": http://www.w3.org/TR/xmlschema11-2/#NCName
    643            // Thus, they must match the NCName production rule. Basically, they may not contain
    644            // a separating colon (only at the first position a colon is allowed) and must not
    645            // start with a digit.
    646            if ( fid != null && fid.length() > 0 && !fid.matches( "[^\\d][^:]+" ) ) {
    647                String msg = Messages.format( "ERROR_INVALID_FEATUREID", fid );
    648                throw new InvalidParameterValueException( msg, "gml:id", fid );
    649            }
    650
    651            return fid;
    652        }
    653
    654        /**
    655         * Returns the feature type for the given feature element.
    656         * <p>
    657         * If a schema defines a feature type with the element's name, it is returned. Otherwise, a feature type is
    658         * generated that matches the child elements (properties) of the feature.
    659         *
    660         * @param element
    661         *            feature element
    662         * @return the feature type.
    663         * @throws XMLParsingException
    664         * @throws UnknownCRSException
    665         */
    666        private FeatureType getFeatureType( Element element )
    667                                throws XMLParsingException, UnknownCRSException {
    668            QualifiedName ftName = getQualifiedName( element );
    669            FeatureType featureType = getFeatureType( ftName );
    670            if ( featureType == null ) {
    671                LOG.logDebug( "Feature type '" + ftName
    672                              + "' is not defined in schema. Generating feature type dynamically." );
    673                featureType = generateFeatureType( element );
    674            }
    675            return featureType;
    676        }
    677
    678        /**
    679         * Method to create a <code>FeatureType</code> from the child elements (properties) of the given feature element.
    680         * Used if no schema (=FeatureType definition) is available.
    681         *
    682         * @param element
    683         *            feature element
    684         * @return the generated feature type.
    685         * @throws XMLParsingException
    686         */
    687        private FeatureType generateFeatureType( Element element )
    688                                throws XMLParsingException {
    689            ElementList el = XMLTools.getChildElements( element );
    690            ArrayList<PropertyType> propertyList = new ArrayList<PropertyType>( el.getLength() );
    691
    692            for ( int i = 0; i < el.getLength(); i++ ) {
    693                Element propertyElement = el.item( i );
    694                QualifiedName propertyName = getQualifiedName( propertyElement );
    695
    696                if ( !propertyName.equals( PROP_NAME_BOUNDED_BY ) && !propertyName.equals( PROP_NAME_NAME )
    697                     && !propertyName.equals( PROP_NAME_DESCRIPTION ) ) {
    698                    PropertyType propertyType = determinePropertyType( propertyElement, propertyName );
    699                    if ( !propertyList.contains( propertyType ) ) {
    700                        propertyList.add( propertyType );
    701                    }
    702                }
    703            }
    704
    705            PropertyType[] properties = new PropertyType[propertyList.size()];
    706            properties = propertyList.toArray( properties );
    707            QualifiedName ftName = getQualifiedName( element );
    708            FeatureType featureType = FeatureFactory.createFeatureType( ftName, false, properties );
    709
    710            return featureType;
    711        }
    712
    713        /**
    714         * Determines the property type for the given property element heuristically.
    715         *
    716         * @param propertyElement
    717         *            property element
    718         * @param propertyName
    719         *            qualified name of the property element
    720         * @return the property type.
    721         * @throws XMLParsingException
    722         */
    723        private PropertyType determinePropertyType( Element propertyElement, QualifiedName propertyName )
    724                                throws XMLParsingException {
    725
    726            PropertyType pt = null;
    727            ElementList childList = XMLTools.getChildElements( propertyElement );
    728
    729            // xlink attr present -> feature property
    730            Attr xlink = (Attr) XMLTools.getNode( propertyElement, "@xlink:href", nsContext );
    731
    732            // hack for determining properties of type "xsd:anyType"
    733            String skipParsing = XMLTools.getNodeAsString( propertyElement, "@deegreewfs:skipParsing", nsContext, "false" );
    734            if ( "true".equals( skipParsing ) ) {
    735                pt = FeatureFactory.createSimplePropertyType( propertyName, Types.ANYTYPE, 0, -1 );
    736                return pt;
    737            }
    738
    739            if ( childList.getLength() == 0 && xlink == null ) {
    740                // no child elements -> simple property
    741                String value = XMLTools.getStringValue( propertyElement );
    742                if ( value != null ) {
    743                    value = value.trim();
    744                }
    745                pt = guessSimplePropertyType( value, propertyName );
    746            } else {
    747                // geometry or feature property
    748                if ( xlink != null ) {
    749                    // TODO could be xlinked geometry as well
    750                    pt = FeatureFactory.createFeaturePropertyType( propertyName, 0, -1 );
    751                } else {
    752                    QualifiedName elementName = getQualifiedName( childList.item( 0 ) );
    753                    if ( isGeometry( elementName ) ) {
    754                        pt = FeatureFactory.createGeometryPropertyType( propertyName, elementName, 0, -1 );
    755                    } else {
    756                        // feature property
    757                        pt = FeatureFactory.createFeaturePropertyType( propertyName, 0, -1 );
    758                    }
    759                }
    760            }
    761            return pt;
    762        }
    763
    764        /**
    765         * Heuristically determines the simple property type from the given property value.
    766         * <p>
    767         * NOTE: This method may produce unwanted results, for example if an "xsd:string" property contains a value that can
    768         * be parsed as an integer, it is always determined as a numeric property.
    769         *
    770         * @param value
    771         *            string value to be used to determine property type
    772         * @param propertyName
    773         *            name of the property
    774         * @return the simple property type.
    775         */
    776        private SimplePropertyType guessSimplePropertyType( String value, QualifiedName propertyName ) {
    777
    778            int typeCode = Types.VARCHAR;
    779
    780            if ( this.guessSimpleTypes ) {
    781                // parseable as integer?
    782                try {
    783                    Integer.parseInt( value );
    784                    typeCode = Types.INTEGER;
    785                } catch ( NumberFormatException e ) {
    786                    // so it's not an integer
    787                }
    788
    789                // parseable as double?
    790                if ( typeCode == Types.VARCHAR ) {
    791                    try {
    792                        Double.parseDouble( value );
    793                        typeCode = Types.NUMERIC;
    794                    } catch ( NumberFormatException e ) {
    795                        // so it's not a double
    796                    }
    797                }
    798
    799                // parseable as ISO date?
    800                /*
    801                 * if (typeCode == Types.VARCHAR) { try { TimeTools.createCalendar( value ); typeCode = Types.DATE; } catch
    802                 * (Exception e) {} }
    803                 */
    804            }
    805
    806            SimplePropertyType propertyType = FeatureFactory.createSimplePropertyType( propertyName, typeCode, 0, -1 );
    807            return propertyType;
    808        }
    809
    810        /**
    811         * Returns true if the given element name is a known GML geometry.
    812         *
    813         * @param elementName
    814         * @return true if the given element name is a known GML geometry, false otherwise.
    815         */
    816        private boolean isGeometry( QualifiedName elementName ) {
    817            boolean isGeometry = false;
    818            if ( TYPE_NAME_BOX.equals( elementName ) || TYPE_NAME_LINESTRING.equals( elementName )
    819                 || TYPE_NAME_MULTIGEOMETRY.equals( elementName ) || TYPE_NAME_MULTILINESTRING.equals( elementName )
    820                 || TYPE_NAME_MULTIPOINT.equals( elementName ) || TYPE_NAME_MULTIPOLYGON.equals( elementName )
    821                 || TYPE_NAME_POINT.equals( elementName ) || TYPE_NAME_POLYGON.equals( elementName )
    822                 || TYPE_NAME_SURFACE.equals( elementName ) || TYPE_NAME_MULTISURFACE.equals( elementName )
    823                 || TYPE_NAME_CURVE.equals( elementName ) || TYPE_NAME_MULTICURVE.equals( elementName ) ) {
    824                isGeometry = true;
    825            }
    826            return isGeometry;
    827        }
    828    }