001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/feature/GMLFeatureDocument.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2006 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.TimeTools;
062    import org.deegree.framework.xml.ElementList;
063    import org.deegree.framework.xml.XMLParsingException;
064    import org.deegree.framework.xml.XMLTools;
065    import org.deegree.model.crs.UnknownCRSException;
066    import org.deegree.model.feature.schema.FeaturePropertyType;
067    import org.deegree.model.feature.schema.FeatureType;
068    import org.deegree.model.feature.schema.GMLSchema;
069    import org.deegree.model.feature.schema.GMLSchemaDocument;
070    import org.deegree.model.feature.schema.GeometryPropertyType;
071    import org.deegree.model.feature.schema.MultiGeometryPropertyType;
072    import org.deegree.model.feature.schema.PropertyType;
073    import org.deegree.model.feature.schema.SimplePropertyType;
074    import org.deegree.model.spatialschema.GMLGeometryAdapter;
075    import org.deegree.model.spatialschema.Geometry;
076    import org.deegree.model.spatialschema.GeometryException;
077    import org.deegree.ogcbase.CommonNamespaces;
078    import org.deegree.ogcbase.GMLDocument;
079    import org.w3c.dom.Attr;
080    import org.w3c.dom.Element;
081    import org.w3c.dom.Text;
082    import org.xml.sax.SAXException;
083    
084    /**
085     * Parser and wrapper class for GML feature documents.
086     * <p>
087     * <b>Validation</b><br>
088     * Has validation capabilities: if the schema is provided or the document contains a reference to a
089     * schema the structure of the generated features is checked. If no schema information is available,
090     * feature + property types are heuristically determined from the feature instance in the document
091     * (guessing of simple property types can be turned off, because it may cause unwanted effects).
092     * </p>
093     * <p>
094     * <b>XLinks</b><br>
095     * Has some basic understanding of XLink: Supports internal XLinks (i.e. the content for a feature
096     * is given by a reference to a feature element in the same document). No support for external
097     * XLinks yet.
098     * </p>
099     * <p>
100     * <b>Propagation of srsName attribute</b><br>
101     * 
102     * </p>
103     * 
104     * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
105     * @author last edited by: $Author: bezema $
106     * 
107     * @version $Revision: 6259 $, $Date: 2007-03-20 10:15:15 +0100 (Di, 20 Mär 2007) $
108     */
109    public class GMLFeatureDocument extends GMLDocument {
110    
111        private static final long serialVersionUID = -7626943858143104276L;
112    
113        private final static ILogger LOG = LoggerFactory.getLogger( GMLFeatureDocument.class );
114    
115        private static String FID = "fid";
116    
117        private static String GMLID = "id";
118    
119        private static URI GMLNS = CommonNamespaces.GMLNS;
120    
121        private static String GMLID_NS = CommonNamespaces.GMLNS.toString();
122    
123        private static QualifiedName PROP_NAME_BOUNDED_BY = new QualifiedName( "boundedBy", GMLNS );
124    
125        private static QualifiedName PROP_NAME_DESCRIPTION = new QualifiedName( "description", GMLNS );
126    
127        private static QualifiedName PROP_NAME_NAME = new QualifiedName( "name", GMLNS );
128    
129        private static QualifiedName PROP_NAME_WKB_GEOM = new QualifiedName( "wkbGeom", GMLNS );
130    
131        private static QualifiedName TYPE_NAME_BOX = new QualifiedName( "Box", GMLNS );
132    
133        private static QualifiedName TYPE_NAME_LINESTRING = new QualifiedName( "LineString", GMLNS );
134    
135        private static QualifiedName TYPE_NAME_MULTIGEOMETRY = new QualifiedName( "MultiGeometry",
136                                                                                  GMLNS );
137    
138        private static QualifiedName TYPE_NAME_MULTILINESTRING = new QualifiedName( "MultiLineString",
139                                                                                    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,
265                                                srsName );
266    
267            ElementList childList = XMLTools.getChildElements( element );
268            Collection<FeatureProperty> propertyList = new ArrayList<FeatureProperty>(
269                                                                                       childList.getLength() );
270            for ( int i = 0; i < childList.getLength(); i++ ) {
271                Element propertyElement = childList.item( i );
272                QualifiedName propertyName = getQualifiedName( propertyElement );
273    
274                if ( PROP_NAME_BOUNDED_BY.equals( propertyName )
275                     || PROP_NAME_WKB_GEOM.equals( propertyName ) ) {
276                    // TODO
277                } else if ( PROP_NAME_NAME.equals( propertyName )
278                            || PROP_NAME_DESCRIPTION.equals( propertyName ) ) {
279                    String s = XMLTools.getStringValue( propertyElement );
280                    if ( s != null ) {
281                        s = s.trim();
282                    }
283                    FeatureProperty property = createSimpleProperty( s, propertyName, Types.VARCHAR );
284                    if ( property != null ) {
285                        propertyList.add( property );
286                    }
287                } else {
288                    FeatureProperty property = parseProperty( childList.item( i ), ft, srsName );
289                    if ( property != null ) {
290                        propertyList.add( property );
291                    }
292                }
293            }
294    
295            FeatureProperty[] featureProperties = propertyList.toArray( new FeatureProperty[propertyList.size()] );
296            feature = FeatureFactory.createFeature( fid, ft, featureProperties );
297    
298            if ( !"".equals( fid ) ) {
299                if ( this.featureMap.containsKey( fid ) ) {
300                    String msg = Messages.format( "ERROR_FEATURE_ID_NOT_UNIQUE", fid );
301                    throw new XMLParsingException( msg );
302                }
303                this.featureMap.put( fid, feature );
304            }
305    
306            return feature;
307        }
308    
309        /**
310         * Returns the object representation for the given property element.
311         * 
312         * @param propertyElement
313         *            property element
314         * @param ft
315         *            feature type of the feature that the property belongs to
316         * @return object representation for the given property element
317         * @throws XMLParsingException
318         * @throws UnknownCRSException
319         */
320        public FeatureProperty parseProperty( Element propertyElement, FeatureType ft )
321                                throws XMLParsingException, UnknownCRSException {
322            return parseProperty( propertyElement, ft, null );
323        }
324    
325        /**
326         * Returns the object representation for the given property element.
327         * 
328         * @param propertyElement
329         *            property element
330         * @param ft
331         *            feature type of the feature that the property belongs to
332         * @param srsName
333         *            default SRS for all a descendant geometry properties
334         * @return object representation for the given property element.
335         * @throws XMLParsingException
336         * @throws UnknownCRSException
337         */
338        public FeatureProperty parseProperty( Element propertyElement, FeatureType ft, String srsName )
339                                throws XMLParsingException, UnknownCRSException {
340    
341            FeatureProperty property = null;
342            QualifiedName propertyName = getQualifiedName( propertyElement );
343    
344            PropertyType propertyType = ft.getProperty( propertyName );
345            if ( propertyType == null ) {
346                throw new XMLParsingException( Messages.format( "ERROR_NO_PROPERTY_TYPE", propertyName ) );
347            }
348    
349            if ( propertyType instanceof SimplePropertyType ) {
350                int typeCode = propertyType.getType();
351                String s = XMLTools.getStringValue( propertyElement ).trim();
352                property = createSimpleProperty( s, propertyName, typeCode );
353            } else if ( propertyType instanceof GeometryPropertyType ) {
354                Element contentElement = XMLTools.getFirstChildElement( propertyElement );
355                if ( contentElement == null ) {
356                    String msg = Messages.format( "ERROR_PROPERTY_NO_CHILD", propertyName, "geometry" );
357                    throw new XMLParsingException( msg );
358                }
359                property = createGeometryProperty( contentElement, propertyName, srsName );
360            } else if ( propertyType instanceof MultiGeometryPropertyType ) {
361                throw new XMLParsingException( "Handling of MultiGeometryPropertyType not "
362                                               + "implemented in GMLFeatureDocument yet." );
363            } else if ( propertyType instanceof FeaturePropertyType ) {
364                List childElements = XMLTools.getNodes( propertyElement, "*", nsContext );
365                switch ( childElements.size() ) {
366                case 0: {
367                    // feature content must be xlinked
368                    Text xlinkHref = (Text) XMLTools.getNode( propertyElement, "@xlink:href/text()",
369                                                              nsContext );
370                    if ( xlinkHref == null ) {
371                        String msg = Messages.format( "ERROR_INVALID_FEATURE_PROPERTY", propertyName );
372                        throw new XMLParsingException( msg );
373                    }
374                    String href = xlinkHref.getData();
375                    if ( !href.startsWith( "#" ) ) {
376                        String msg = Messages.format( "ERROR_EXTERNAL_XLINK_NOT_SUPPORTED", href );
377                        throw new XMLParsingException( msg );
378                    }
379                    String fid = href.substring( 1 );
380                    property = new XLinkedFeatureProperty( propertyName, fid );
381                    xlinkPropertyList.add( (XLinkedFeatureProperty) property );
382                    break;
383                }
384                case 1: {
385                    // feature content is given inline
386                    Feature propertyValue = parseFeature( (Element) childElements.get( 0 ), srsName );
387                    property = FeatureFactory.createFeatureProperty( propertyName, propertyValue );
388                    break;
389                }
390                default: {
391                    String msg = Messages.format( "ERROR_INVALID_FEATURE_PROPERTY2", propertyName,
392                                                  childElements.size() );
393                    //throw new XMLParsingException( msg );
394                }
395                }
396            }
397            return property;
398        }
399    
400        protected void resolveXLinkReferences()
401                                throws XMLParsingException {
402            Iterator iter = this.xlinkPropertyList.iterator();
403            while ( iter.hasNext() ) {
404                XLinkedFeatureProperty xlinkProperty = (XLinkedFeatureProperty) iter.next();
405                String fid = xlinkProperty.getTargetFeatureId();
406                Feature targetFeature = this.featureMap.get( fid );
407                if ( targetFeature == null ) {
408                    String msg = Messages.format( "ERROR_XLINK_NOT_RESOLVABLE", fid );
409                    throw new XMLParsingException( msg );
410                }
411                xlinkProperty.setValue( targetFeature );
412            }
413        }
414    
415        /**
416         * Creates a simple property from the given parameters.
417         * <p>
418         * Converts the string value to the given target type.
419         * 
420         * @param s
421         *            string value from a simple property to be converted
422         * @param propertyName
423         *            name of the simple property
424         * @param typeCode
425         *            target type code
426         * @return property value in the given target type.
427         * @throws XMLParsingException
428         */
429        private FeatureProperty createSimpleProperty( String s, QualifiedName propertyName, int typeCode )
430                                throws XMLParsingException {
431    
432            Object propertyValue = null;
433            switch ( typeCode ) {
434            case Types.VARCHAR: {
435                propertyValue = s;
436                break;
437            }
438            case Types.INTEGER:
439            case Types.SMALLINT: {
440                try {
441                    propertyValue = new Integer( s );
442                } catch ( NumberFormatException e ) {
443                    String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName,
444                                                  "Integer" );
445                    throw new XMLParsingException( msg );
446                }
447                break;
448            }
449            case Types.NUMERIC:
450            case Types.DOUBLE: {
451                try {
452                    propertyValue = new Double( s );
453                } catch ( NumberFormatException e ) {
454                    String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName,
455                                                  "Double" );
456                    throw new XMLParsingException( msg );
457                }
458                break;
459            }
460            case Types.DECIMAL:
461            case Types.FLOAT: {
462                try {
463                    propertyValue = new Float( s );
464                } catch ( NumberFormatException e ) {
465                    String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, "Float" );
466                    throw new XMLParsingException( msg );
467                }
468                break;
469            }
470            case Types.BOOLEAN: {
471                propertyValue = new Boolean( s );
472                break;
473            }
474            case Types.DATE: {
475                propertyValue = TimeTools.createCalendar( s ).getTime();
476                break;
477            }
478            default: {
479                String typeString = "" + typeCode;
480                try {
481                    typeString = Types.getTypeNameForSQLTypeCode( typeCode );
482                } catch ( UnknownTypeException e ) {
483                    LOG.logError( "No type name for code: " + typeCode );
484                }
485                String msg = Messages.format( "ERROR_UNHANDLED_TYPE", "" + typeString );
486                LOG.logError( msg );
487                throw new XMLParsingException( msg );
488            }
489            }
490            FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName,
491                                                                             propertyValue );
492            return property;
493        }
494    
495        /**
496         * Creates a geometry property from the given parameters.
497         * 
498         * @param contentElement
499         *            child element of a geometry property to be converted
500         * @param propertyName
501         *            name of the geometry property
502         * @param srsName
503         *            default SRS for the geometry (may be overwritten in geometry elements)
504         * @return geometry property
505         * @throws XMLParsingException
506         */
507        private FeatureProperty createGeometryProperty( Element contentElement,
508                                                       QualifiedName propertyName, String srsName )
509                                throws XMLParsingException {
510    
511            Geometry propertyValue = null;
512            try {
513                propertyValue = GMLGeometryAdapter.wrap( contentElement, srsName );
514            } catch ( GeometryException e ) {
515                LOG.logError( e.getMessage(), e );
516                String msg = Messages.format( "ERROR_CONVERTING_GEOMETRY_PROPERTY", propertyName,
517                                              "-", e.getMessage() );
518                throw new XMLParsingException( msg );
519            }
520    
521            FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName,
522                                                                             propertyValue );
523            return property;
524        }
525    
526        /**
527         * Determines and retrieves the GML schemas that the document refers to.
528         * 
529         * @return the GML schemas that are attached to the document, keys are URIs (namespaces), values
530         *         are GMLSchemas.
531         * @throws XMLParsingException
532         * @throws UnknownCRSException
533         */
534        protected Map<URI, GMLSchema> getGMLSchemas()
535                                throws XMLParsingException, UnknownCRSException {
536    
537            if ( this.gmlSchemaMap == null ) {
538                gmlSchemaMap = new HashMap<URI, GMLSchema>();
539                Map schemaMap = getAttachedSchemas();
540                Iterator it = schemaMap.keySet().iterator();
541                while ( it.hasNext() ) {
542                    URI nsURI = (URI) it.next();
543                    URL schemaURL = (URL) schemaMap.get( nsURI );
544                    GMLSchemaDocument schemaDocument = new GMLSchemaDocument();
545                    LOG.logDebug( "Retrieving schema document for namespace '" + nsURI + "' from URL '"
546                                  + 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,
553                                                      e.getMessage() );
554                        throw new XMLParsingException( msg );
555                    } catch ( SAXException e ) {
556                        String msg = Messages.format( "ERROR_SCHEMA_NOT_XML", schemaURL, e.getMessage() );
557                        throw new XMLParsingException( msg );
558                    } catch ( XMLParsingException e ) {
559                        String msg = Messages.format( "ERROR_SCHEMA_PARSING1", schemaURL,
560                                                      e.getMessage() );
561                        throw new XMLParsingException( msg );
562                    }
563                }
564            }
565    
566            return this.gmlSchemaMap;
567        }
568    
569        /**
570         * Returns the GML schema for the given namespace.
571         * 
572         * @param ns
573         * @return the GML schema for the given namespace if it is declared, null otherwise.
574         * @throws XMLParsingException
575         * @throws UnknownCRSException
576         */
577        protected GMLSchema getSchemaForNamespace( URI ns )
578                                throws XMLParsingException, UnknownCRSException {
579            Map<URI, GMLSchema> gmlSchemaMap = getGMLSchemas();
580            GMLSchema schema = gmlSchemaMap.get( ns );
581            return schema;
582        }
583    
584        /**
585         * Returns the feature type with the given name.
586         * <p>
587         * If schema information is available and a feature type with the given name is not defined, an
588         * XMLParsingException is thrown.
589         * 
590         * @param ftName
591         *            feature type to look up
592         * @return the feature type with the given name if it is declared, null otherwise.
593         * @throws XMLParsingException
594         * @throws UnknownCRSException
595         */
596        protected FeatureType getFeatureType( QualifiedName ftName )
597                                throws XMLParsingException, UnknownCRSException {
598            FeatureType featureType = null;
599            if ( this.gmlSchemaMap != null ) {
600                GMLSchema schema = getSchemaForNamespace( ftName.getNamespace() );
601                if ( schema == null ) {
602                    String msg = Messages.format( "ERROR_SCHEMA_NO_SCHEMA_FOR_NS",
603                                                  ftName.getNamespace() );
604                    throw new XMLParsingException( msg );
605                }
606                featureType = schema.getFeatureType( ftName );
607                if ( featureType == null ) {
608                    String msg = Messages.format( "ERROR_SCHEMA_FEATURE_TYPE_UNKNOWN", ftName );
609                    throw new XMLParsingException( msg );
610                }
611            }
612            return featureType;
613        }
614    
615        /**
616         * Parses the feature id attribute from the given feature element.
617         * <p>
618         * Looks after 'gml:id' (GML 3 style) first, if no such attribute is present, the 'fid' (GML 2
619         * style) attribute is used.
620         * 
621         * @param featureElement
622         * @return the feature id, this is "" if neither a 'gml:id' nor a 'fid' attribute is present
623         */
624        protected String parseFeatureId( Element featureElement ) {
625            String fid = featureElement.getAttributeNS( GMLID_NS, GMLID );
626            if ( fid.length() == 0 ) {
627                fid = featureElement.getAttribute( FID );
628            }
629            
630            // TODO
631            // evaluate the regular expression. The pattern given at
632            // http://www.w3.org/TR/xmlschema11-2/ is not a regular expression
633            // even if it should be according to http://www.w3.org/TR/xmlschema11-2/#rf-pattern
634            if ( fid != null && fid.length() > 0 && !fid.matches( "[^\\d][^:]+" ) ) {
635                String msg = Messages.format( "ERROR_INVALID_FEATUREID", fid );
636                throw new InvalidParameterValueException( msg, "gml:id", fid );
637            }
638            
639            return fid;
640        }
641    
642        /**
643         * Returns the feature type for the given feature element.
644         * <p>
645         * If a schema defines a feature type with the element's name, it is returned. Otherwise, a
646         * feature type is generated that matches the child elements (properties) of the feature.
647         * 
648         * @param element
649         *            feature element
650         * @return the feature type.
651         * @throws XMLParsingException
652         * @throws UnknownCRSException
653         */
654        private FeatureType getFeatureType( Element element )
655                                throws XMLParsingException, UnknownCRSException {
656            QualifiedName ftName = getQualifiedName( element );
657            FeatureType featureType = getFeatureType( ftName );
658            if ( featureType == null ) {
659                LOG.logDebug( "Feature type '" + ftName
660                              + "' is not defined in schema. Generating feature type dynamically." );
661                featureType = generateFeatureType( element );
662            }
663            return featureType;
664        }
665    
666        /**
667         * Method to create a <code>FeatureType</code> from the child elements (properties) of the
668         * given feature element. Used if no schema (=FeatureType definition) is available.
669         * 
670         * @param element
671         *            feature element
672         * @return the generated feature type.
673         * @throws XMLParsingException
674         */
675        private FeatureType generateFeatureType( Element element )
676                                throws XMLParsingException {
677            ElementList el = XMLTools.getChildElements( element );
678            ArrayList<PropertyType> propertyList = new ArrayList<PropertyType>( el.getLength() );
679    
680            for ( int i = 0; i < el.getLength(); i++ ) {
681                Element propertyElement = el.item( i );
682                QualifiedName propertyName = getQualifiedName( propertyElement );
683    
684                if ( !propertyName.equals( PROP_NAME_BOUNDED_BY )
685                     && !propertyName.equals( PROP_NAME_NAME )
686                     && !propertyName.equals( PROP_NAME_DESCRIPTION ) ) {
687                    PropertyType propertyType = determinePropertyType( propertyElement, propertyName );
688                    if ( !propertyList.contains( propertyType ) ) {
689                        propertyList.add( propertyType );
690                    }
691                }
692            }
693    
694            PropertyType[] properties = new PropertyType[propertyList.size()];
695            properties = propertyList.toArray( properties );
696            QualifiedName ftName = getQualifiedName( element );
697            FeatureType featureType = FeatureFactory.createFeatureType( ftName, false, properties );
698    
699            return featureType;
700        }
701    
702        /**
703         * Determines the property type for the given property element heuristically.
704         * 
705         * @param propertyElement
706         *            property element
707         * @param propertyName
708         *            qualified name of the property element
709         * @return the property type.
710         * @throws XMLParsingException
711         */
712        private PropertyType determinePropertyType( Element propertyElement, QualifiedName propertyName )
713                                throws XMLParsingException {
714    
715            PropertyType propertyType = null;
716            ElementList childList = XMLTools.getChildElements( propertyElement );
717    
718            // xlink attr present -> feature property
719            Attr xlink = (Attr) XMLTools.getNode( propertyElement, "@xlink:href", nsContext );
720    
721            if ( childList.getLength() == 0 && xlink == null ) {
722                // no child elements -> simple property
723                String value = XMLTools.getStringValue( propertyElement );
724                if ( value != null ) {
725                    value = value.trim();
726                }
727                propertyType = guessSimplePropertyType( value, propertyName );
728            } else {
729                // geometry or feature property
730                if ( xlink != null ) {
731                    // TODO could be xlinked geometry as well
732                    propertyType = FeatureFactory.createFeaturePropertyType( propertyName, 0, -1 );
733                } else {
734                    QualifiedName elementName = getQualifiedName( childList.item( 0 ) );
735                    if ( isGeometry( elementName ) ) {
736                        propertyType = FeatureFactory.createGeometryPropertyType( propertyName,
737                                                                                  elementName, 0, -1 );
738                    } else {
739                        // feature property
740                        propertyType = FeatureFactory.createFeaturePropertyType( propertyName, 0, -1 );
741                    }
742                }
743            }
744            return propertyType;
745        }
746    
747        /**
748         * Heuristically determines the simple property type from the given property value.
749         * <p>
750         * NOTE: This method may produce unwanted results, for example if an "xsd:string" property
751         * contains a value that can be parsed as an integer, it is always determined as a numeric
752         * property.
753         * 
754         * @param value
755         *            string value to be used to determine property type
756         * @param propertyName
757         *            name of the property
758         * @return the simple property type.
759         */
760        private SimplePropertyType guessSimplePropertyType( String value, QualifiedName propertyName ) {
761    
762            int typeCode = Types.VARCHAR;
763    
764            if ( this.guessSimpleTypes ) {
765                // parseable as integer?
766                try {
767                    Integer.parseInt( value );
768                    typeCode = Types.INTEGER;
769                } catch ( NumberFormatException e ) {
770                    // so it's not an integer
771                }
772    
773                // parseable as double?
774                if ( typeCode == Types.VARCHAR ) {
775                    try {
776                        Double.parseDouble( value );
777                        typeCode = Types.NUMERIC;
778                    } catch ( NumberFormatException e ) {
779                        // so it's not a double
780                    }
781                }
782    
783                // parseable as ISO date?
784                /*
785                 * if (typeCode == Types.VARCHAR) { try { TimeTools.createCalendar( value ); typeCode =
786                 * Types.DATE; } catch (Exception e) {} }
787                 */
788            }
789    
790            SimplePropertyType propertyType = FeatureFactory.createSimplePropertyType( propertyName,
791                                                                                       typeCode, 0, -1 );
792            return propertyType;
793        }
794    
795        /**
796         * Returns true if the given element name is a known GML geometry.
797         * 
798         * @param elementName
799         * @return true if the given element name is a known GML geometry, false otherwise.
800         */
801        private boolean isGeometry( QualifiedName elementName ) {
802            boolean isGeometry = false;
803            if ( TYPE_NAME_BOX.equals( elementName ) || TYPE_NAME_LINESTRING.equals( elementName )
804                 || TYPE_NAME_MULTIGEOMETRY.equals( elementName )
805                 || TYPE_NAME_MULTILINESTRING.equals( elementName )
806                 || TYPE_NAME_MULTIPOINT.equals( elementName )
807                 || TYPE_NAME_MULTIPOLYGON.equals( elementName )
808                 || TYPE_NAME_POINT.equals( elementName ) || TYPE_NAME_POLYGON.equals( elementName )
809                 || TYPE_NAME_SURFACE.equals( elementName )
810                 || TYPE_NAME_MULTISURFACE.equals( elementName )
811                 || TYPE_NAME_CURVE.equals( elementName ) || TYPE_NAME_MULTICURVE.equals( elementName ) ) {
812                isGeometry = true;
813            }
814            return isGeometry;
815        }
816    }
817    
818    /***************************************************************************************************
819     * <code>
820     Changes to this class. What the people have been up to:
821     $Log$
822     Revision 1.38  2007/03/16 13:10:58  poth
823     *** empty log message ***
824    
825     Revision 1.37  2007/03/01 17:00:35  poth
826     evaluation for gml:id added
827    
828     Revision 1.36  2007/02/22 19:25:00  poth
829     throwing exception if a propety contains two complex values commented
830    
831     Revision 1.35  2007/02/21 13:47:24  mschneider
832     Added check and error message when parsing feature properties that contain more than one element.
833    
834     Revision 1.34  2007/01/23 16:20:33  mschneider
835     Improved handling of inherited srsName attributes.
836    
837     Revision 1.33  2006/12/07 18:23:18  mschneider
838     Empty properties have now the text value "" (never null).
839    
840     Revision 1.32  2006/11/27 09:07:52  poth
841     JNI integration of proj4 has been removed. The CRS functionality now will be done by native deegree code.
842    
843     Revision 1.31  2006/08/31 15:21:41  mschneider
844     Javadoc improvements.
845    
846     Revision 1.30  2006/08/31 15:00:26  mschneider
847     Added second constructor that allows to disable the guessing of simple types. Javadoc fixes.
848    
849     Revision 1.29  2006/08/21 15:47:59  mschneider
850     Refactored due to cleanup (and splitting) of org.deegree.io.datastore.schema package.
851    
852     Revision 1.28  2006/08/01 10:41:42  mschneider
853     Added handling of empty fids ("").
854    
855     Revision 1.27  2006/07/25 15:54:43  mschneider
856     Activated check for multiple features with same id in the document.
857    
858     Revision 1.26  2006/07/25 14:46:21  mschneider
859     Javadoc corrections.
860    
861     Revision 1.25  2006/05/23 22:39:46  mschneider
862     Fixed error message.
863    
864     Revision 1.24  2006/05/23 16:07:28  mschneider
865     Changed visibility of parseProperty( Element propertyElement, FeatureType featureType ) to public.
866    
867     Revision 1.23  2006/05/03 17:26:40  poth
868     *** empty log message ***
869    
870     Revision 1.22  2006/05/02 17:32:04  poth
871     *** empty log message ***
872    
873     Revision 1.21  2006/04/27 09:44:59  poth
874     *** empty log message ***
875    
876     Revision 1.20  2006/04/26 16:19:37  mschneider
877     Synchronized createSimpleProperty() with AbstractSQLDatastore.convertToSQLType().
878    
879     Revision 1.19  2006/04/06 20:25:27  poth
880     *** empty log message ***
881    
882     Revision 1.18  2006/04/04 20:39:42  poth
883     *** empty log message ***
884    
885     Revision 1.17  2006/04/04 17:51:55  mschneider
886     Fixed imports.
887    
888     Revision 1.16  2006/04/04 10:32:11  mschneider
889     Adapted to XMLSchemaException changes.
890    
891     Revision 1.15  2006/03/31 08:13:13  poth
892     *** empty log message ***
893    
894     Revision 1.14  2006/03/30 21:20:26  poth
895     *** empty log message ***
896    
897     Revision 1.13  2006/03/21 13:23:56  poth
898     *** empty log message ***
899    
900     Revision 1.12  2006/03/03 13:36:50  mschneider
901     Added handling of Floats. Improved error messages.
902    
903     Revision 1.11  2006/03/02 18:03:51  poth
904     *** empty log message ***
905    
906     Revision 1.10  2006/02/23 15:30:41  mschneider
907     Added hack to work around empty properties.
908    
909     Revision 1.9  2006/02/21 19:47:49  poth
910     *** empty log message ***
911    
912     Revision 1.8  2006/02/05 18:52:49  mschneider
913     Cleanup.
914    
915     Revision 1.7  2006/02/04 22:50:48  mschneider
916     Added setSchemas() to explicitly specify the application schemas for this document.
917    
918     Revision 1.6  2006/01/31 16:24:43  mschneider
919     Changes due to refactoring of org.deegree.model.feature package.
920    
921     Revision 1.5  2006/01/30 16:21:03  mschneider
922     Moved resolveXLinkReferences() here.
923    
924     Revision 1.4  2006/01/24 16:13:17  poth
925     *** empty log message ***
926    
927     Revision 1.3  2006/01/23 10:25:40  mschneider
928     Added heuristic for determining Integer / Double / Date property types.
929     Fixed handling of String property values.
930    
931     Revision 1.2  2006/01/20 18:13:47  mschneider
932     Moved parsing functionality from GMLFeatureAdapter here.
933    
934     Revision 1.1  2006/01/19 16:18:14  mschneider
935     Initial version.
936     * </code>
937     **************************************************************************************************/