001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/feature/GMLFeatureDocument.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2001-2006 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.TimeTools; 062 import org.deegree.framework.xml.ElementList; 063 import org.deegree.framework.xml.XMLParsingException; 064 import org.deegree.framework.xml.XMLTools; 065 import org.deegree.model.crs.UnknownCRSException; 066 import org.deegree.model.feature.schema.FeaturePropertyType; 067 import org.deegree.model.feature.schema.FeatureType; 068 import org.deegree.model.feature.schema.GMLSchema; 069 import org.deegree.model.feature.schema.GMLSchemaDocument; 070 import org.deegree.model.feature.schema.GeometryPropertyType; 071 import org.deegree.model.feature.schema.MultiGeometryPropertyType; 072 import org.deegree.model.feature.schema.PropertyType; 073 import org.deegree.model.feature.schema.SimplePropertyType; 074 import org.deegree.model.spatialschema.GMLGeometryAdapter; 075 import org.deegree.model.spatialschema.Geometry; 076 import org.deegree.model.spatialschema.GeometryException; 077 import org.deegree.ogcbase.CommonNamespaces; 078 import org.deegree.ogcbase.GMLDocument; 079 import org.w3c.dom.Attr; 080 import org.w3c.dom.Element; 081 import org.w3c.dom.Text; 082 import org.xml.sax.SAXException; 083 084 /** 085 * Parser and wrapper class for GML feature documents. 086 * <p> 087 * <b>Validation</b><br> 088 * Has validation capabilities: if the schema is provided or the document contains a reference to a 089 * schema the structure of the generated features is checked. If no schema information is available, 090 * feature + property types are heuristically determined from the feature instance in the document 091 * (guessing of simple property types can be turned off, because it may cause unwanted effects). 092 * </p> 093 * <p> 094 * <b>XLinks</b><br> 095 * Has some basic understanding of XLink: Supports internal XLinks (i.e. the content for a feature 096 * is given by a reference to a feature element in the same document). No support for external 097 * XLinks yet. 098 * </p> 099 * <p> 100 * <b>Propagation of srsName attribute</b><br> 101 * 102 * </p> 103 * 104 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a> 105 * @author last edited by: $Author: bezema $ 106 * 107 * @version $Revision: 6259 $, $Date: 2007-03-20 10:15:15 +0100 (Di, 20 Mär 2007) $ 108 */ 109 public class GMLFeatureDocument extends GMLDocument { 110 111 private static final long serialVersionUID = -7626943858143104276L; 112 113 private final static ILogger LOG = LoggerFactory.getLogger( GMLFeatureDocument.class ); 114 115 private static String FID = "fid"; 116 117 private static String GMLID = "id"; 118 119 private static URI GMLNS = CommonNamespaces.GMLNS; 120 121 private static String GMLID_NS = CommonNamespaces.GMLNS.toString(); 122 123 private static QualifiedName PROP_NAME_BOUNDED_BY = new QualifiedName( "boundedBy", GMLNS ); 124 125 private static QualifiedName PROP_NAME_DESCRIPTION = new QualifiedName( "description", GMLNS ); 126 127 private static QualifiedName PROP_NAME_NAME = new QualifiedName( "name", GMLNS ); 128 129 private static QualifiedName PROP_NAME_WKB_GEOM = new QualifiedName( "wkbGeom", GMLNS ); 130 131 private static QualifiedName TYPE_NAME_BOX = new QualifiedName( "Box", GMLNS ); 132 133 private static QualifiedName TYPE_NAME_LINESTRING = new QualifiedName( "LineString", GMLNS ); 134 135 private static QualifiedName TYPE_NAME_MULTIGEOMETRY = new QualifiedName( "MultiGeometry", 136 GMLNS ); 137 138 private static QualifiedName TYPE_NAME_MULTILINESTRING = new QualifiedName( "MultiLineString", 139 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, 265 srsName ); 266 267 ElementList childList = XMLTools.getChildElements( element ); 268 Collection<FeatureProperty> propertyList = new ArrayList<FeatureProperty>( 269 childList.getLength() ); 270 for ( int i = 0; i < childList.getLength(); i++ ) { 271 Element propertyElement = childList.item( i ); 272 QualifiedName propertyName = getQualifiedName( propertyElement ); 273 274 if ( PROP_NAME_BOUNDED_BY.equals( propertyName ) 275 || PROP_NAME_WKB_GEOM.equals( propertyName ) ) { 276 // TODO 277 } else if ( PROP_NAME_NAME.equals( propertyName ) 278 || PROP_NAME_DESCRIPTION.equals( propertyName ) ) { 279 String s = XMLTools.getStringValue( propertyElement ); 280 if ( s != null ) { 281 s = s.trim(); 282 } 283 FeatureProperty property = createSimpleProperty( s, propertyName, Types.VARCHAR ); 284 if ( property != null ) { 285 propertyList.add( property ); 286 } 287 } else { 288 FeatureProperty property = parseProperty( childList.item( i ), ft, srsName ); 289 if ( property != null ) { 290 propertyList.add( property ); 291 } 292 } 293 } 294 295 FeatureProperty[] featureProperties = propertyList.toArray( new FeatureProperty[propertyList.size()] ); 296 feature = FeatureFactory.createFeature( fid, ft, featureProperties ); 297 298 if ( !"".equals( fid ) ) { 299 if ( this.featureMap.containsKey( fid ) ) { 300 String msg = Messages.format( "ERROR_FEATURE_ID_NOT_UNIQUE", fid ); 301 throw new XMLParsingException( msg ); 302 } 303 this.featureMap.put( fid, feature ); 304 } 305 306 return feature; 307 } 308 309 /** 310 * Returns the object representation for the given property element. 311 * 312 * @param propertyElement 313 * property element 314 * @param ft 315 * feature type of the feature that the property belongs to 316 * @return object representation for the given property element 317 * @throws XMLParsingException 318 * @throws UnknownCRSException 319 */ 320 public FeatureProperty parseProperty( Element propertyElement, FeatureType ft ) 321 throws XMLParsingException, UnknownCRSException { 322 return parseProperty( propertyElement, ft, null ); 323 } 324 325 /** 326 * Returns the object representation for the given property element. 327 * 328 * @param propertyElement 329 * property element 330 * @param ft 331 * feature type of the feature that the property belongs to 332 * @param srsName 333 * default SRS for all a descendant geometry properties 334 * @return object representation for the given property element. 335 * @throws XMLParsingException 336 * @throws UnknownCRSException 337 */ 338 public FeatureProperty parseProperty( Element propertyElement, FeatureType ft, String srsName ) 339 throws XMLParsingException, UnknownCRSException { 340 341 FeatureProperty property = null; 342 QualifiedName propertyName = getQualifiedName( propertyElement ); 343 344 PropertyType propertyType = ft.getProperty( propertyName ); 345 if ( propertyType == null ) { 346 throw new XMLParsingException( Messages.format( "ERROR_NO_PROPERTY_TYPE", propertyName ) ); 347 } 348 349 if ( propertyType instanceof SimplePropertyType ) { 350 int typeCode = propertyType.getType(); 351 String s = XMLTools.getStringValue( propertyElement ).trim(); 352 property = createSimpleProperty( s, propertyName, typeCode ); 353 } else if ( propertyType instanceof GeometryPropertyType ) { 354 Element contentElement = XMLTools.getFirstChildElement( propertyElement ); 355 if ( contentElement == null ) { 356 String msg = Messages.format( "ERROR_PROPERTY_NO_CHILD", propertyName, "geometry" ); 357 throw new XMLParsingException( msg ); 358 } 359 property = createGeometryProperty( contentElement, propertyName, srsName ); 360 } else if ( propertyType instanceof MultiGeometryPropertyType ) { 361 throw new XMLParsingException( "Handling of MultiGeometryPropertyType not " 362 + "implemented in GMLFeatureDocument yet." ); 363 } else if ( propertyType instanceof FeaturePropertyType ) { 364 List childElements = XMLTools.getNodes( propertyElement, "*", nsContext ); 365 switch ( childElements.size() ) { 366 case 0: { 367 // feature content must be xlinked 368 Text xlinkHref = (Text) XMLTools.getNode( propertyElement, "@xlink:href/text()", 369 nsContext ); 370 if ( xlinkHref == null ) { 371 String msg = Messages.format( "ERROR_INVALID_FEATURE_PROPERTY", propertyName ); 372 throw new XMLParsingException( msg ); 373 } 374 String href = xlinkHref.getData(); 375 if ( !href.startsWith( "#" ) ) { 376 String msg = Messages.format( "ERROR_EXTERNAL_XLINK_NOT_SUPPORTED", href ); 377 throw new XMLParsingException( msg ); 378 } 379 String fid = href.substring( 1 ); 380 property = new XLinkedFeatureProperty( propertyName, fid ); 381 xlinkPropertyList.add( (XLinkedFeatureProperty) property ); 382 break; 383 } 384 case 1: { 385 // feature content is given inline 386 Feature propertyValue = parseFeature( (Element) childElements.get( 0 ), srsName ); 387 property = FeatureFactory.createFeatureProperty( propertyName, propertyValue ); 388 break; 389 } 390 default: { 391 String msg = Messages.format( "ERROR_INVALID_FEATURE_PROPERTY2", propertyName, 392 childElements.size() ); 393 //throw new XMLParsingException( msg ); 394 } 395 } 396 } 397 return property; 398 } 399 400 protected void resolveXLinkReferences() 401 throws XMLParsingException { 402 Iterator iter = this.xlinkPropertyList.iterator(); 403 while ( iter.hasNext() ) { 404 XLinkedFeatureProperty xlinkProperty = (XLinkedFeatureProperty) iter.next(); 405 String fid = xlinkProperty.getTargetFeatureId(); 406 Feature targetFeature = this.featureMap.get( fid ); 407 if ( targetFeature == null ) { 408 String msg = Messages.format( "ERROR_XLINK_NOT_RESOLVABLE", fid ); 409 throw new XMLParsingException( msg ); 410 } 411 xlinkProperty.setValue( targetFeature ); 412 } 413 } 414 415 /** 416 * Creates a simple property from the given parameters. 417 * <p> 418 * Converts the string value to the given target type. 419 * 420 * @param s 421 * string value from a simple property to be converted 422 * @param propertyName 423 * name of the simple property 424 * @param typeCode 425 * target type code 426 * @return property value in the given target type. 427 * @throws XMLParsingException 428 */ 429 private FeatureProperty createSimpleProperty( String s, QualifiedName propertyName, int typeCode ) 430 throws XMLParsingException { 431 432 Object propertyValue = null; 433 switch ( typeCode ) { 434 case Types.VARCHAR: { 435 propertyValue = s; 436 break; 437 } 438 case Types.INTEGER: 439 case Types.SMALLINT: { 440 try { 441 propertyValue = new Integer( s ); 442 } catch ( NumberFormatException e ) { 443 String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, 444 "Integer" ); 445 throw new XMLParsingException( msg ); 446 } 447 break; 448 } 449 case Types.NUMERIC: 450 case Types.DOUBLE: { 451 try { 452 propertyValue = new Double( s ); 453 } catch ( NumberFormatException e ) { 454 String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, 455 "Double" ); 456 throw new XMLParsingException( msg ); 457 } 458 break; 459 } 460 case Types.DECIMAL: 461 case Types.FLOAT: { 462 try { 463 propertyValue = new Float( s ); 464 } catch ( NumberFormatException e ) { 465 String msg = Messages.format( "ERROR_CONVERTING_PROPERTY", s, propertyName, "Float" ); 466 throw new XMLParsingException( msg ); 467 } 468 break; 469 } 470 case Types.BOOLEAN: { 471 propertyValue = new Boolean( s ); 472 break; 473 } 474 case Types.DATE: { 475 propertyValue = TimeTools.createCalendar( s ).getTime(); 476 break; 477 } 478 default: { 479 String typeString = "" + typeCode; 480 try { 481 typeString = Types.getTypeNameForSQLTypeCode( typeCode ); 482 } catch ( UnknownTypeException e ) { 483 LOG.logError( "No type name for code: " + typeCode ); 484 } 485 String msg = Messages.format( "ERROR_UNHANDLED_TYPE", "" + typeString ); 486 LOG.logError( msg ); 487 throw new XMLParsingException( msg ); 488 } 489 } 490 FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, 491 propertyValue ); 492 return property; 493 } 494 495 /** 496 * Creates a geometry property from the given parameters. 497 * 498 * @param contentElement 499 * child element of a geometry property to be converted 500 * @param propertyName 501 * name of the geometry property 502 * @param srsName 503 * default SRS for the geometry (may be overwritten in geometry elements) 504 * @return geometry property 505 * @throws XMLParsingException 506 */ 507 private FeatureProperty createGeometryProperty( Element contentElement, 508 QualifiedName propertyName, String srsName ) 509 throws XMLParsingException { 510 511 Geometry propertyValue = null; 512 try { 513 propertyValue = GMLGeometryAdapter.wrap( contentElement, srsName ); 514 } catch ( GeometryException e ) { 515 LOG.logError( e.getMessage(), e ); 516 String msg = Messages.format( "ERROR_CONVERTING_GEOMETRY_PROPERTY", propertyName, 517 "-", e.getMessage() ); 518 throw new XMLParsingException( msg ); 519 } 520 521 FeatureProperty property = FeatureFactory.createFeatureProperty( propertyName, 522 propertyValue ); 523 return property; 524 } 525 526 /** 527 * Determines and retrieves the GML schemas that the document refers to. 528 * 529 * @return the GML schemas that are attached to the document, keys are URIs (namespaces), values 530 * are GMLSchemas. 531 * @throws XMLParsingException 532 * @throws UnknownCRSException 533 */ 534 protected Map<URI, GMLSchema> getGMLSchemas() 535 throws XMLParsingException, UnknownCRSException { 536 537 if ( this.gmlSchemaMap == null ) { 538 gmlSchemaMap = new HashMap<URI, GMLSchema>(); 539 Map schemaMap = getAttachedSchemas(); 540 Iterator it = schemaMap.keySet().iterator(); 541 while ( it.hasNext() ) { 542 URI nsURI = (URI) it.next(); 543 URL schemaURL = (URL) schemaMap.get( nsURI ); 544 GMLSchemaDocument schemaDocument = new GMLSchemaDocument(); 545 LOG.logDebug( "Retrieving schema document for namespace '" + nsURI + "' from URL '" 546 + 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, 553 e.getMessage() ); 554 throw new XMLParsingException( msg ); 555 } catch ( SAXException e ) { 556 String msg = Messages.format( "ERROR_SCHEMA_NOT_XML", schemaURL, e.getMessage() ); 557 throw new XMLParsingException( msg ); 558 } catch ( XMLParsingException e ) { 559 String msg = Messages.format( "ERROR_SCHEMA_PARSING1", schemaURL, 560 e.getMessage() ); 561 throw new XMLParsingException( msg ); 562 } 563 } 564 } 565 566 return this.gmlSchemaMap; 567 } 568 569 /** 570 * Returns the GML schema for the given namespace. 571 * 572 * @param ns 573 * @return the GML schema for the given namespace if it is declared, null otherwise. 574 * @throws XMLParsingException 575 * @throws UnknownCRSException 576 */ 577 protected GMLSchema getSchemaForNamespace( URI ns ) 578 throws XMLParsingException, UnknownCRSException { 579 Map<URI, GMLSchema> gmlSchemaMap = getGMLSchemas(); 580 GMLSchema schema = gmlSchemaMap.get( ns ); 581 return schema; 582 } 583 584 /** 585 * Returns the feature type with the given name. 586 * <p> 587 * If schema information is available and a feature type with the given name is not defined, an 588 * XMLParsingException is thrown. 589 * 590 * @param ftName 591 * feature type to look up 592 * @return the feature type with the given name if it is declared, null otherwise. 593 * @throws XMLParsingException 594 * @throws UnknownCRSException 595 */ 596 protected FeatureType getFeatureType( QualifiedName ftName ) 597 throws XMLParsingException, UnknownCRSException { 598 FeatureType featureType = null; 599 if ( this.gmlSchemaMap != null ) { 600 GMLSchema schema = getSchemaForNamespace( ftName.getNamespace() ); 601 if ( schema == null ) { 602 String msg = Messages.format( "ERROR_SCHEMA_NO_SCHEMA_FOR_NS", 603 ftName.getNamespace() ); 604 throw new XMLParsingException( msg ); 605 } 606 featureType = schema.getFeatureType( ftName ); 607 if ( featureType == null ) { 608 String msg = Messages.format( "ERROR_SCHEMA_FEATURE_TYPE_UNKNOWN", ftName ); 609 throw new XMLParsingException( msg ); 610 } 611 } 612 return featureType; 613 } 614 615 /** 616 * Parses the feature id attribute from the given feature element. 617 * <p> 618 * Looks after 'gml:id' (GML 3 style) first, if no such attribute is present, the 'fid' (GML 2 619 * style) attribute is used. 620 * 621 * @param featureElement 622 * @return the feature id, this is "" if neither a 'gml:id' nor a 'fid' attribute is present 623 */ 624 protected String parseFeatureId( Element featureElement ) { 625 String fid = featureElement.getAttributeNS( GMLID_NS, GMLID ); 626 if ( fid.length() == 0 ) { 627 fid = featureElement.getAttribute( FID ); 628 } 629 630 // TODO 631 // evaluate the regular expression. The pattern given at 632 // http://www.w3.org/TR/xmlschema11-2/ is not a regular expression 633 // even if it should be according to http://www.w3.org/TR/xmlschema11-2/#rf-pattern 634 if ( fid != null && fid.length() > 0 && !fid.matches( "[^\\d][^:]+" ) ) { 635 String msg = Messages.format( "ERROR_INVALID_FEATUREID", fid ); 636 throw new InvalidParameterValueException( msg, "gml:id", fid ); 637 } 638 639 return fid; 640 } 641 642 /** 643 * Returns the feature type for the given feature element. 644 * <p> 645 * If a schema defines a feature type with the element's name, it is returned. Otherwise, a 646 * feature type is generated that matches the child elements (properties) of the feature. 647 * 648 * @param element 649 * feature element 650 * @return the feature type. 651 * @throws XMLParsingException 652 * @throws UnknownCRSException 653 */ 654 private FeatureType getFeatureType( Element element ) 655 throws XMLParsingException, UnknownCRSException { 656 QualifiedName ftName = getQualifiedName( element ); 657 FeatureType featureType = getFeatureType( ftName ); 658 if ( featureType == null ) { 659 LOG.logDebug( "Feature type '" + ftName 660 + "' is not defined in schema. Generating feature type dynamically." ); 661 featureType = generateFeatureType( element ); 662 } 663 return featureType; 664 } 665 666 /** 667 * Method to create a <code>FeatureType</code> from the child elements (properties) of the 668 * given feature element. Used if no schema (=FeatureType definition) is available. 669 * 670 * @param element 671 * feature element 672 * @return the generated feature type. 673 * @throws XMLParsingException 674 */ 675 private FeatureType generateFeatureType( Element element ) 676 throws XMLParsingException { 677 ElementList el = XMLTools.getChildElements( element ); 678 ArrayList<PropertyType> propertyList = new ArrayList<PropertyType>( el.getLength() ); 679 680 for ( int i = 0; i < el.getLength(); i++ ) { 681 Element propertyElement = el.item( i ); 682 QualifiedName propertyName = getQualifiedName( propertyElement ); 683 684 if ( !propertyName.equals( PROP_NAME_BOUNDED_BY ) 685 && !propertyName.equals( PROP_NAME_NAME ) 686 && !propertyName.equals( PROP_NAME_DESCRIPTION ) ) { 687 PropertyType propertyType = determinePropertyType( propertyElement, propertyName ); 688 if ( !propertyList.contains( propertyType ) ) { 689 propertyList.add( propertyType ); 690 } 691 } 692 } 693 694 PropertyType[] properties = new PropertyType[propertyList.size()]; 695 properties = propertyList.toArray( properties ); 696 QualifiedName ftName = getQualifiedName( element ); 697 FeatureType featureType = FeatureFactory.createFeatureType( ftName, false, properties ); 698 699 return featureType; 700 } 701 702 /** 703 * Determines the property type for the given property element heuristically. 704 * 705 * @param propertyElement 706 * property element 707 * @param propertyName 708 * qualified name of the property element 709 * @return the property type. 710 * @throws XMLParsingException 711 */ 712 private PropertyType determinePropertyType( Element propertyElement, QualifiedName propertyName ) 713 throws XMLParsingException { 714 715 PropertyType propertyType = null; 716 ElementList childList = XMLTools.getChildElements( propertyElement ); 717 718 // xlink attr present -> feature property 719 Attr xlink = (Attr) XMLTools.getNode( propertyElement, "@xlink:href", nsContext ); 720 721 if ( childList.getLength() == 0 && xlink == null ) { 722 // no child elements -> simple property 723 String value = XMLTools.getStringValue( propertyElement ); 724 if ( value != null ) { 725 value = value.trim(); 726 } 727 propertyType = guessSimplePropertyType( value, propertyName ); 728 } else { 729 // geometry or feature property 730 if ( xlink != null ) { 731 // TODO could be xlinked geometry as well 732 propertyType = FeatureFactory.createFeaturePropertyType( propertyName, 0, -1 ); 733 } else { 734 QualifiedName elementName = getQualifiedName( childList.item( 0 ) ); 735 if ( isGeometry( elementName ) ) { 736 propertyType = FeatureFactory.createGeometryPropertyType( propertyName, 737 elementName, 0, -1 ); 738 } else { 739 // feature property 740 propertyType = FeatureFactory.createFeaturePropertyType( propertyName, 0, -1 ); 741 } 742 } 743 } 744 return propertyType; 745 } 746 747 /** 748 * Heuristically determines the simple property type from the given property value. 749 * <p> 750 * NOTE: This method may produce unwanted results, for example if an "xsd:string" property 751 * contains a value that can be parsed as an integer, it is always determined as a numeric 752 * property. 753 * 754 * @param value 755 * string value to be used to determine property type 756 * @param propertyName 757 * name of the property 758 * @return the simple property type. 759 */ 760 private SimplePropertyType guessSimplePropertyType( String value, QualifiedName propertyName ) { 761 762 int typeCode = Types.VARCHAR; 763 764 if ( this.guessSimpleTypes ) { 765 // parseable as integer? 766 try { 767 Integer.parseInt( value ); 768 typeCode = Types.INTEGER; 769 } catch ( NumberFormatException e ) { 770 // so it's not an integer 771 } 772 773 // parseable as double? 774 if ( typeCode == Types.VARCHAR ) { 775 try { 776 Double.parseDouble( value ); 777 typeCode = Types.NUMERIC; 778 } catch ( NumberFormatException e ) { 779 // so it's not a double 780 } 781 } 782 783 // parseable as ISO date? 784 /* 785 * if (typeCode == Types.VARCHAR) { try { TimeTools.createCalendar( value ); typeCode = 786 * Types.DATE; } catch (Exception e) {} } 787 */ 788 } 789 790 SimplePropertyType propertyType = FeatureFactory.createSimplePropertyType( propertyName, 791 typeCode, 0, -1 ); 792 return propertyType; 793 } 794 795 /** 796 * Returns true if the given element name is a known GML geometry. 797 * 798 * @param elementName 799 * @return true if the given element name is a known GML geometry, false otherwise. 800 */ 801 private boolean isGeometry( QualifiedName elementName ) { 802 boolean isGeometry = false; 803 if ( TYPE_NAME_BOX.equals( elementName ) || TYPE_NAME_LINESTRING.equals( elementName ) 804 || TYPE_NAME_MULTIGEOMETRY.equals( elementName ) 805 || TYPE_NAME_MULTILINESTRING.equals( elementName ) 806 || TYPE_NAME_MULTIPOINT.equals( elementName ) 807 || TYPE_NAME_MULTIPOLYGON.equals( elementName ) 808 || TYPE_NAME_POINT.equals( elementName ) || TYPE_NAME_POLYGON.equals( elementName ) 809 || TYPE_NAME_SURFACE.equals( elementName ) 810 || TYPE_NAME_MULTISURFACE.equals( elementName ) 811 || TYPE_NAME_CURVE.equals( elementName ) || TYPE_NAME_MULTICURVE.equals( elementName ) ) { 812 isGeometry = true; 813 } 814 return isGeometry; 815 } 816 } 817 818 /*************************************************************************************************** 819 * <code> 820 Changes to this class. What the people have been up to: 821 $Log$ 822 Revision 1.38 2007/03/16 13:10:58 poth 823 *** empty log message *** 824 825 Revision 1.37 2007/03/01 17:00:35 poth 826 evaluation for gml:id added 827 828 Revision 1.36 2007/02/22 19:25:00 poth 829 throwing exception if a propety contains two complex values commented 830 831 Revision 1.35 2007/02/21 13:47:24 mschneider 832 Added check and error message when parsing feature properties that contain more than one element. 833 834 Revision 1.34 2007/01/23 16:20:33 mschneider 835 Improved handling of inherited srsName attributes. 836 837 Revision 1.33 2006/12/07 18:23:18 mschneider 838 Empty properties have now the text value "" (never null). 839 840 Revision 1.32 2006/11/27 09:07:52 poth 841 JNI integration of proj4 has been removed. The CRS functionality now will be done by native deegree code. 842 843 Revision 1.31 2006/08/31 15:21:41 mschneider 844 Javadoc improvements. 845 846 Revision 1.30 2006/08/31 15:00:26 mschneider 847 Added second constructor that allows to disable the guessing of simple types. Javadoc fixes. 848 849 Revision 1.29 2006/08/21 15:47:59 mschneider 850 Refactored due to cleanup (and splitting) of org.deegree.io.datastore.schema package. 851 852 Revision 1.28 2006/08/01 10:41:42 mschneider 853 Added handling of empty fids (""). 854 855 Revision 1.27 2006/07/25 15:54:43 mschneider 856 Activated check for multiple features with same id in the document. 857 858 Revision 1.26 2006/07/25 14:46:21 mschneider 859 Javadoc corrections. 860 861 Revision 1.25 2006/05/23 22:39:46 mschneider 862 Fixed error message. 863 864 Revision 1.24 2006/05/23 16:07:28 mschneider 865 Changed visibility of parseProperty( Element propertyElement, FeatureType featureType ) to public. 866 867 Revision 1.23 2006/05/03 17:26:40 poth 868 *** empty log message *** 869 870 Revision 1.22 2006/05/02 17:32:04 poth 871 *** empty log message *** 872 873 Revision 1.21 2006/04/27 09:44:59 poth 874 *** empty log message *** 875 876 Revision 1.20 2006/04/26 16:19:37 mschneider 877 Synchronized createSimpleProperty() with AbstractSQLDatastore.convertToSQLType(). 878 879 Revision 1.19 2006/04/06 20:25:27 poth 880 *** empty log message *** 881 882 Revision 1.18 2006/04/04 20:39:42 poth 883 *** empty log message *** 884 885 Revision 1.17 2006/04/04 17:51:55 mschneider 886 Fixed imports. 887 888 Revision 1.16 2006/04/04 10:32:11 mschneider 889 Adapted to XMLSchemaException changes. 890 891 Revision 1.15 2006/03/31 08:13:13 poth 892 *** empty log message *** 893 894 Revision 1.14 2006/03/30 21:20:26 poth 895 *** empty log message *** 896 897 Revision 1.13 2006/03/21 13:23:56 poth 898 *** empty log message *** 899 900 Revision 1.12 2006/03/03 13:36:50 mschneider 901 Added handling of Floats. Improved error messages. 902 903 Revision 1.11 2006/03/02 18:03:51 poth 904 *** empty log message *** 905 906 Revision 1.10 2006/02/23 15:30:41 mschneider 907 Added hack to work around empty properties. 908 909 Revision 1.9 2006/02/21 19:47:49 poth 910 *** empty log message *** 911 912 Revision 1.8 2006/02/05 18:52:49 mschneider 913 Cleanup. 914 915 Revision 1.7 2006/02/04 22:50:48 mschneider 916 Added setSchemas() to explicitly specify the application schemas for this document. 917 918 Revision 1.6 2006/01/31 16:24:43 mschneider 919 Changes due to refactoring of org.deegree.model.feature package. 920 921 Revision 1.5 2006/01/30 16:21:03 mschneider 922 Moved resolveXLinkReferences() here. 923 924 Revision 1.4 2006/01/24 16:13:17 poth 925 *** empty log message *** 926 927 Revision 1.3 2006/01/23 10:25:40 mschneider 928 Added heuristic for determining Integer / Double / Date property types. 929 Fixed handling of String property values. 930 931 Revision 1.2 2006/01/20 18:13:47 mschneider 932 Moved parsing functionality from GMLFeatureAdapter here. 933 934 Revision 1.1 2006/01/19 16:18:14 mschneider 935 Initial version. 936 * </code> 937 **************************************************************************************************/