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 }