001    //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/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: 30885 $, $Date: 2011-05-23 10:12:46 +0200 (Mo, 23 Mai 2011) $
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.TINYINT:
460            case Types.INTEGER:
461            case Types.SMALLINT: {
462                try {
463                    propertyValue = new Long( s );
464                } catch ( NumberFormatException e ) {
465                    String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, "Integer" );
466                    throw new XMLParsingException( msg );
467                }
468                break;
469            }
470            case Types.NUMERIC:
471            case Types.DOUBLE: {
472                try {
473                    propertyValue = new Double( s );
474                } catch ( NumberFormatException e ) {
475                    String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, "Double" );
476                    throw new XMLParsingException( msg );
477                }
478                break;
479            }
480            case Types.REAL:
481            case Types.DECIMAL:
482            case Types.FLOAT: {
483                try {
484                    propertyValue = new Float( s );
485                } catch ( NumberFormatException e ) {
486                    String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, "Float" );
487                    throw new XMLParsingException( msg );
488                }
489                break;
490            }
491            case Types.BOOLEAN: {
492                propertyValue = new Boolean( s );
493                break;
494            }
495            case Types.DATE:
496            case Types.TIMESTAMP: {
497                propertyValue = TimeTools.createCalendar( s ).getTime();
498                break;
499            }
500            default: {
501                String typeString = "" + typeCode;
502                try {
503                    typeString = Types.getTypeNameForSQLTypeCode( typeCode );
504                } catch ( UnknownTypeException e ) {
505                    LOG.logError( "No type name for code: " + typeCode );
506                }
507                String msg = Messages.format( "ERROR_UNHANDLED_TYPE", "" + typeString );
508                LOG.logError( msg );
509                throw new XMLParsingException( msg );
510            }
511            }
512            FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, propertyValue );
513            return property;
514        }
515    
516        /**
517         * Creates a geometry property from the given parameters.
518         *
519         * @param contentElement
520         *            child element of a geometry property to be converted
521         * @param propertyName
522         *            name of the geometry property
523         * @param srsName
524         *            default SRS for the geometry (may be overwritten in geometry elements)
525         * @return geometry property
526         * @throws XMLParsingException
527         */
528        private FeatureProperty createGeometryProperty( Element contentElement, QualifiedName propertyName, String srsName )
529                                throws XMLParsingException {
530    
531            Geometry propertyValue = null;
532            try {
533                propertyValue = GMLGeometryAdapter.wrap( contentElement, srsName );
534            } catch ( GeometryException e ) {
535                LOG.logError( e.getMessage(), e );
536                String msg = Messages.format( "ERROR_CONVERTING_GEOMETRY_PROPERTY", propertyName, "-", e.getMessage() );
537                throw new XMLParsingException( msg );
538            }
539    
540            FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, propertyValue );
541            return property;
542        }
543    
544        /**
545         * Determines and retrieves the GML schemas that the document refers to.
546         *
547         * @return the GML schemas that are attached to the document, keys are URIs (namespaces), values are GMLSchemas.
548         * @throws XMLParsingException
549         * @throws UnknownCRSException
550         */
551        protected Map<URI, GMLSchema> getGMLSchemas()
552                                throws XMLParsingException, UnknownCRSException {
553    
554            if ( this.gmlSchemaMap == null ) {
555                gmlSchemaMap = new HashMap<URI, GMLSchema>();
556                Map<URI, URL> schemaMap = getAttachedSchemas();
557                Iterator<URI> it = schemaMap.keySet().iterator();
558                while ( it.hasNext() ) {
559                    URI nsURI = it.next();
560                    URL schemaURL = schemaMap.get( nsURI );
561                    GMLSchemaDocument schemaDocument = new GMLSchemaDocument();
562                    LOG.logDebug( "Retrieving schema document for namespace '" + nsURI + "' from URL '" + schemaURL + "'." );
563                    try {
564                        schemaDocument.load( schemaURL );
565                        GMLSchema gmlSchema = schemaDocument.parseGMLSchema();
566                        gmlSchemaMap.put( nsURI, gmlSchema );
567                    } catch ( IOException e ) {
568                        String msg = Messages.format( "ERROR_RETRIEVING_SCHEMA", schemaURL, e.getMessage() );
569                        throw new XMLParsingException( msg );
570                    } catch ( SAXException e ) {
571                        String msg = Messages.format( "ERROR_SCHEMA_NOT_XML", schemaURL, e.getMessage() );
572                        throw new XMLParsingException( msg );
573                    } catch ( XMLParsingException e ) {
574                        String msg = Messages.format( "ERROR_SCHEMA_PARSING1", schemaURL, e.getMessage() );
575                        throw new XMLParsingException( msg );
576                    }
577                }
578            }
579    
580            return this.gmlSchemaMap;
581        }
582    
583        /**
584         * Returns the GML schema for the given namespace.
585         *
586         * @param ns
587         * @return the GML schema for the given namespace if it is declared, null otherwise.
588         * @throws XMLParsingException
589         * @throws UnknownCRSException
590         */
591        protected GMLSchema getSchemaForNamespace( URI ns )
592                                throws XMLParsingException, UnknownCRSException {
593            Map<URI, GMLSchema> gmlSchemaMap = getGMLSchemas();
594            GMLSchema schema = gmlSchemaMap.get( ns );
595            return schema;
596        }
597    
598        /**
599         * Returns the feature type with the given name.
600         * <p>
601         * If schema information is available and a feature type with the given name is not defined, an XMLParsingException
602         * is thrown.
603         *
604         * @param ftName
605         *            feature type to look up
606         * @return the feature type with the given name if it is declared, null otherwise.
607         * @throws XMLParsingException
608         * @throws UnknownCRSException
609         */
610        protected FeatureType getFeatureType( QualifiedName ftName )
611                                throws XMLParsingException, UnknownCRSException {
612            FeatureType featureType = null;
613            if ( this.gmlSchemaMap != null ) {
614                GMLSchema schema = getSchemaForNamespace( ftName.getNamespace() );
615                if ( schema == null ) {
616                    String msg = Messages.format( "ERROR_SCHEMA_NO_SCHEMA_FOR_NS", ftName.getNamespace() );
617                    //throw new XMLParsingException( msg );
618                    LOG.logWarning( msg );
619                    return null;
620                }
621                featureType = schema.getFeatureType( ftName );
622                if ( featureType == null ) {
623                    String msg = Messages.format( "ERROR_SCHEMA_FEATURE_TYPE_UNKNOWN", ftName );
624                    //throw new XMLParsingException( msg );
625                    LOG.logWarning( msg );
626                    return null;
627                }
628            }
629            return featureType;
630        }
631    
632        /**
633         * Parses the feature id attribute from the given feature element.
634         * <p>
635         * Looks after 'gml:id' (GML 3 style) first, if no such attribute is present, the 'fid' (GML 2 style) attribute is
636         * used.
637         *
638         * @param featureElement
639         * @return the feature id, this is "" if neither a 'gml:id' nor a 'fid' attribute is present
640         */
641        protected String parseFeatureId( Element featureElement ) {
642            String fid = featureElement.getAttributeNS( GMLID_NS, GMLID );
643            if ( fid.length() == 0 ) {
644                fid = featureElement.getAttribute( FID );
645            }
646    
647            // Check that the feature id has the correct form. "fid" and "gml:id" are both based
648            // on the XML type "ID": http://www.w3.org/TR/xmlschema11-2/#NCName
649            // Thus, they must match the NCName production rule. Basically, they may not contain
650            // a separating colon (only at the first position a colon is allowed) and must not
651            // start with a digit.
652            if ( fid != null && fid.length() > 0 && !fid.matches( "[^\\d][^:]+" ) ) {
653                String msg = Messages.format( "ERROR_INVALID_FEATUREID", fid );
654                throw new InvalidParameterValueException( msg, "gml:id", fid );
655            }
656    
657            return fid;
658        }
659    
660        /**
661         * Returns the feature type for the given feature element.
662         * <p>
663         * If a schema defines a feature type with the element's name, it is returned. Otherwise, a feature type is
664         * generated that matches the child elements (properties) of the feature.
665         *
666         * @param element
667         *            feature element
668         * @return the feature type.
669         * @throws XMLParsingException
670         * @throws UnknownCRSException
671         */
672        private FeatureType getFeatureType( Element element )
673                                throws XMLParsingException, UnknownCRSException {
674            QualifiedName ftName = getQualifiedName( element );
675            FeatureType featureType = getFeatureType( ftName );
676            if ( featureType == null ) {
677                LOG.logDebug( "Feature type '" + ftName
678                              + "' is not defined in schema. Generating feature type dynamically." );
679                featureType = generateFeatureType( element );
680            }
681            return featureType;
682        }
683    
684        /**
685         * Method to create a <code>FeatureType</code> from the child elements (properties) of the given feature element.
686         * Used if no schema (=FeatureType definition) is available.
687         *
688         * @param element
689         *            feature element
690         * @return the generated feature type.
691         * @throws XMLParsingException
692         */
693        private FeatureType generateFeatureType( Element element )
694                                throws XMLParsingException {
695            ElementList el = XMLTools.getChildElements( element );
696            ArrayList<PropertyType> propertyList = new ArrayList<PropertyType>( el.getLength() );
697    
698            for ( int i = 0; i < el.getLength(); i++ ) {
699                Element propertyElement = el.item( i );
700                QualifiedName propertyName = getQualifiedName( propertyElement );
701    
702                if ( !propertyName.equals( PROP_NAME_BOUNDED_BY ) && !propertyName.equals( PROP_NAME_NAME )
703                     && !propertyName.equals( PROP_NAME_DESCRIPTION ) ) {
704                    PropertyType propertyType = determinePropertyType( propertyElement, propertyName );
705                    if ( !propertyList.contains( propertyType ) ) {
706                        propertyList.add( propertyType );
707                    }
708                }
709            }
710    
711            PropertyType[] properties = new PropertyType[propertyList.size()];
712            properties = propertyList.toArray( properties );
713            QualifiedName ftName = getQualifiedName( element );
714            FeatureType featureType = FeatureFactory.createFeatureType( ftName, false, properties );
715    
716            return featureType;
717        }
718    
719        /**
720         * Determines the property type for the given property element heuristically.
721         *
722         * @param propertyElement
723         *            property element
724         * @param propertyName
725         *            qualified name of the property element
726         * @return the property type.
727         * @throws XMLParsingException
728         */
729        private PropertyType determinePropertyType( Element propertyElement, QualifiedName propertyName )
730                                throws XMLParsingException {
731    
732            PropertyType pt = null;
733            ElementList childList = XMLTools.getChildElements( propertyElement );
734    
735            // xlink attr present -> feature property
736            Attr xlink = (Attr) XMLTools.getNode( propertyElement, "@xlink:href", nsContext );
737    
738            // hack for determining properties of type "xsd:anyType"
739            String skipParsing = XMLTools.getNodeAsString( propertyElement, "@deegreewfs:skipParsing", nsContext, "false" );
740            if ( "true".equals( skipParsing ) ) {
741                pt = FeatureFactory.createSimplePropertyType( propertyName, Types.ANYTYPE, 0, -1 );
742                return pt;
743            }
744    
745            if ( childList.getLength() == 0 && xlink == null ) {
746                // no child elements -> simple property
747                String value = XMLTools.getStringValue( propertyElement );
748                if ( value != null ) {
749                    value = value.trim();
750                }
751                pt = guessSimplePropertyType( value, propertyName );
752            } else {
753                // geometry or feature property
754                if ( xlink != null ) {
755                    // TODO could be xlinked geometry as well
756                    pt = FeatureFactory.createFeaturePropertyType( propertyName, 0, -1 );
757                } else {
758                    QualifiedName elementName = getQualifiedName( childList.item( 0 ) );
759                    if ( isGeometry( elementName ) ) {
760                        pt = FeatureFactory.createGeometryPropertyType( propertyName, elementName, 0, -1 );
761                    } else {
762                        // feature property
763                        pt = FeatureFactory.createFeaturePropertyType( propertyName, 0, -1 );
764                    }
765                }
766            }
767            return pt;
768        }
769    
770        /**
771         * Heuristically determines the simple property type from the given property value.
772         * <p>
773         * NOTE: This method may produce unwanted results, for example if an "xsd:string" property contains a value that can
774         * be parsed as an integer, it is always determined as a numeric property.
775         *
776         * @param value
777         *            string value to be used to determine property type
778         * @param propertyName
779         *            name of the property
780         * @return the simple property type.
781         */
782        private SimplePropertyType guessSimplePropertyType( String value, QualifiedName propertyName ) {
783    
784            int typeCode = Types.VARCHAR;
785    
786            if ( this.guessSimpleTypes ) {
787                // parseable as integer?
788                try {
789                    Integer.parseInt( value );
790                    typeCode = Types.INTEGER;
791                } catch ( NumberFormatException e ) {
792                    // so it's not an integer
793                }
794    
795                // parseable as double?
796                if ( typeCode == Types.VARCHAR ) {
797                    try {
798                        Double.parseDouble( value );
799                        typeCode = Types.NUMERIC;
800                    } catch ( NumberFormatException e ) {
801                        // so it's not a double
802                    }
803                }
804    
805                // parseable as ISO date?
806                /*
807                 * if (typeCode == Types.VARCHAR) { try { TimeTools.createCalendar( value ); typeCode = Types.DATE; } catch
808                 * (Exception e) {} }
809                 */
810            }
811    
812            SimplePropertyType propertyType = FeatureFactory.createSimplePropertyType( propertyName, typeCode, 0, -1 );
813            return propertyType;
814        }
815    
816        /**
817         * Returns true if the given element name is a known GML geometry.
818         *
819         * @param elementName
820         * @return true if the given element name is a known GML geometry, false otherwise.
821         */
822        private boolean isGeometry( QualifiedName elementName ) {
823            boolean isGeometry = false;
824            if ( TYPE_NAME_BOX.equals( elementName ) || TYPE_NAME_LINESTRING.equals( elementName )
825                 || TYPE_NAME_MULTIGEOMETRY.equals( elementName ) || TYPE_NAME_MULTILINESTRING.equals( elementName )
826                 || TYPE_NAME_MULTIPOINT.equals( elementName ) || TYPE_NAME_MULTIPOLYGON.equals( elementName )
827                 || TYPE_NAME_POINT.equals( elementName ) || TYPE_NAME_POLYGON.equals( elementName )
828                 || TYPE_NAME_SURFACE.equals( elementName ) || TYPE_NAME_MULTISURFACE.equals( elementName )
829                 || TYPE_NAME_CURVE.equals( elementName ) || TYPE_NAME_MULTICURVE.equals( elementName ) ) {
830                isGeometry = true;
831            }
832            return isGeometry;
833        }
834    }