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