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