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 **************************************************************************************************/