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