001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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: apoth $ 101 * 102 * @version $Revision: 27360 $, $Date: 2010-10-18 20:37:37 +0200 (Mo, 18 Okt 2010) $ 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 Long( 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 }