001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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: apoth $
101     *
102     * @version $Revision: 27360 $, $Date: 2010-10-18 20:37:37 +0200 (Mo, 18 Okt 2010) $
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 Long( 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    }