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