001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/io/datastore/schema/MappedGMLSchemaDocument.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.io.datastore.schema; 037 038 import static org.deegree.framework.xml.XMLTools.getElements; 039 import static org.deegree.framework.xml.XMLTools.getNodeAsBoolean; 040 import static org.deegree.framework.xml.XMLTools.getNodeAsQualifiedName; 041 import static org.deegree.framework.xml.XMLTools.getRequiredElement; 042 import static org.deegree.framework.xml.XMLTools.getRequiredNodeAsQualifiedName; 043 import static org.deegree.ogcbase.CommonNamespaces.DEEGREEWFS_PREFIX; 044 import static org.deegree.ogcbase.CommonNamespaces.XS_PREFIX; 045 046 import java.net.URI; 047 import java.util.ArrayList; 048 import java.util.Iterator; 049 import java.util.LinkedList; 050 import java.util.List; 051 import java.util.Properties; 052 053 import org.deegree.datatypes.QualifiedName; 054 import org.deegree.datatypes.Types; 055 import org.deegree.datatypes.UnknownTypeException; 056 import org.deegree.framework.log.ILogger; 057 import org.deegree.framework.log.LoggerFactory; 058 import org.deegree.framework.util.Pair; 059 import org.deegree.framework.xml.XMLParsingException; 060 import org.deegree.framework.xml.XMLTools; 061 import org.deegree.framework.xml.schema.ComplexTypeDeclaration; 062 import org.deegree.framework.xml.schema.ElementDeclaration; 063 import org.deegree.framework.xml.schema.SimpleTypeDeclaration; 064 import org.deegree.framework.xml.schema.TypeDeclaration; 065 import org.deegree.framework.xml.schema.TypeReference; 066 import org.deegree.framework.xml.schema.XMLSchemaException; 067 import org.deegree.i18n.Messages; 068 import org.deegree.io.datastore.AnnotationDocument; 069 import org.deegree.io.datastore.Datastore; 070 import org.deegree.io.datastore.DatastoreConfiguration; 071 import org.deegree.io.datastore.DatastoreRegistry; 072 import org.deegree.io.datastore.idgenerator.IdGenerator; 073 import org.deegree.io.datastore.schema.MappedGMLId.IDPART_INFO; 074 import org.deegree.io.datastore.schema.TableRelation.FK_INFO; 075 import org.deegree.io.datastore.schema.content.ConstantContent; 076 import org.deegree.io.datastore.schema.content.FieldContent; 077 import org.deegree.io.datastore.schema.content.FunctionParam; 078 import org.deegree.io.datastore.schema.content.MappingField; 079 import org.deegree.io.datastore.schema.content.MappingGeometryField; 080 import org.deegree.io.datastore.schema.content.SQLFunctionCall; 081 import org.deegree.io.datastore.schema.content.SimpleContent; 082 import org.deegree.io.datastore.schema.content.SpecialContent; 083 import org.deegree.model.crs.UnknownCRSException; 084 import org.deegree.model.feature.schema.GMLSchemaDocument; 085 import org.deegree.model.feature.schema.PropertyType; 086 import org.deegree.ogcbase.CommonNamespaces; 087 import org.w3c.dom.Element; 088 import org.w3c.dom.Node; 089 090 /** 091 * Parser for GML schema documents which are annotated with mapping (persistence) information. 092 * 093 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a> 094 * @author last edited by: $Author: mschneider $ 095 * 096 * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18 Jun 2009) $ 097 */ 098 public class MappedGMLSchemaDocument extends GMLSchemaDocument { 099 100 private static final long serialVersionUID = 8293629056821438839L; 101 102 private static final ILogger LOG = LoggerFactory.getLogger( MappedGMLSchemaDocument.class ); 103 104 private String namespacePrefix; 105 106 private URI defaultSRS; 107 108 private boolean suppressXLinkOutput; 109 110 private DatastoreConfiguration dsConfiguration; 111 112 /** 113 * Returns the class representation of the underlying mapped GML schema document. 114 * 115 * @return the class representation of the underlying mapped GML schema document 116 * @throws XMLParsingException 117 * @throws XMLSchemaException 118 * @throws UnknownCRSException 119 */ 120 public MappedGMLSchema parseMappedGMLSchema() 121 throws XMLParsingException, XMLSchemaException, UnknownCRSException { 122 parseGlobalAnnotations(); 123 SimpleTypeDeclaration[] simpleTypes = extractSimpleTypeDeclarations(); 124 ComplexTypeDeclaration[] complexTypes = extractComplexTypeDeclarations(); 125 ElementDeclaration[] elementDeclarations = extractElementDeclarations(); 126 return new MappedGMLSchema( getTargetNamespace(), simpleTypes, complexTypes, elementDeclarations, 127 this.namespacePrefix, this.defaultSRS, this.dsConfiguration, 128 this.suppressXLinkOutput, this ); 129 } 130 131 /** 132 * Parses the global "xs:annotation/xs:appinfo" block. 133 * <p> 134 * Delegates the datastore specific configuration options to the responsible {@link AnnotationDocument} parser. 135 * 136 * @throws XMLParsingException 137 */ 138 @SuppressWarnings("unchecked") 139 private void parseGlobalAnnotations() 140 throws XMLParsingException { 141 142 Element appinfoElement = (Element) XMLTools.getRequiredNode( getRootElement(), "xs:annotation/xs:appinfo", 143 nsContext ); 144 this.namespacePrefix = XMLTools.getRequiredNodeAsString( appinfoElement, "deegreewfs:Prefix", nsContext ); 145 String backend = XMLTools.getRequiredNodeAsString( appinfoElement, "deegreewfs:Backend", nsContext ); 146 this.suppressXLinkOutput = XMLTools.getNodeAsBoolean( appinfoElement, "deegreewfs:SuppressXLinkOutput/text()", 147 nsContext, false ); 148 this.defaultSRS = XMLTools.getRequiredNodeAsURI( appinfoElement, "deegreewfs:DefaultSRS", nsContext ); 149 150 Class<Datastore> datastoreClass = null; 151 try { 152 datastoreClass = DatastoreRegistry.getDatastoreClass( backend ); 153 } catch ( IllegalArgumentException e ) { 154 String msg = Messages.getMessage( "DATASTORE_UNKNOWN_TYPE_CODE", backend ); 155 LOG.logInfo( msg ); 156 try { 157 datastoreClass = (Class<Datastore>) Class.forName( backend ); 158 } catch ( ClassNotFoundException e1 ) { 159 msg = Messages.getMessage( "DATASTORE_UNKNOWN_TYPE_AND_CLASS", backend ); 160 throw new XMLParsingException( msg ); 161 } 162 } 163 164 AnnotationDocument annotationParser = null; 165 try { 166 Datastore ds = datastoreClass.newInstance(); 167 annotationParser = ds.getAnnotationParser(); 168 } catch ( Exception e ) { 169 String msg = Messages.getMessage( "DATASTORE_CLASS_INSTANTIATION_ERROR", datastoreClass.getName(), 170 e.getMessage() ); 171 throw new XMLParsingException( msg ); 172 } 173 174 annotationParser.setRootElement( this.getRootElement() ); 175 annotationParser.setSystemId( this.getSystemId() ); 176 try { 177 this.dsConfiguration = annotationParser.parseDatastoreConfiguration(); 178 } catch ( XMLParsingException e ) { 179 LOG.logError( e.getMessage(), e ); 180 String msg = Messages.getMessage( "DATASTORE_CONFIGURATION_BLOCK_FAULTY", e.getMessage() ); 181 throw new XMLParsingException( msg ); 182 } 183 } 184 185 /** 186 * Parses the given <code>Element</code> as an annotated 'xs:element' declaration (with mapping information). 187 * 188 * @param element 189 * 'xs:element' declaration to be parsed 190 * @return object representation of the declaration 191 * @throws XMLParsingException 192 * if the document is not a valid XML Schema document or does not match the limitations of this class 193 */ 194 @Override 195 protected MappedElementDeclaration parseElementDeclaration( Element element ) 196 throws XMLParsingException { 197 198 QualifiedName name = new QualifiedName( XMLTools.getRequiredNodeAsString( element, "@name", nsContext ), 199 getTargetNamespace() ); 200 if ( name.getLocalName().length() == 0 ) { 201 String msg = "Error in schema document. Empty name (\"\") in element declaration found."; 202 throw new XMLSchemaException( msg ); 203 } 204 205 boolean isAbstract = XMLTools.getNodeAsBoolean( element, "@abstract", nsContext, false ); 206 207 TypeReference typeReference = null; 208 Node typeNode = XMLTools.getNode( element, "@type", nsContext ); 209 if ( typeNode != null ) { 210 typeReference = new TypeReference( parseQualifiedName( typeNode ) ); 211 } else { 212 if ( LOG.isDebug() ) { 213 LOG.logDebug( "Trying to find a complexType below the element named " + element.getAttribute( "name" ) 214 + "." ); 215 } 216 // inline type declaration 217 Element elem = (Element) XMLTools.getRequiredNode( element, getFullName( "complexType" ), nsContext ); 218 TypeDeclaration type = parseComplexTypeDeclaration( elem ); 219 typeReference = new TypeReference( type ); 220 } 221 222 int minOccurs = XMLTools.getNodeAsInt( element, "@minOccurs", nsContext, 1 ); 223 int maxOccurs = -1; 224 String maxOccursString = XMLTools.getNodeAsString( element, "@maxOccurs", nsContext, "1" ); 225 if ( !"unbounded".equals( maxOccursString ) ) { 226 try { 227 maxOccurs = Integer.parseInt( maxOccursString ); 228 } catch ( NumberFormatException e ) { 229 throw new XMLParsingException( "Invalid value ('" + maxOccursString + "') in 'maxOccurs' attribute. " 230 + "Must be a valid integer value or 'unbounded'." ); 231 } 232 } 233 234 QualifiedName substitutionGroup = null; 235 Node substitutionGroupNode = XMLTools.getNode( element, "@substitutionGroup", nsContext ); 236 if ( substitutionGroupNode != null ) { 237 substitutionGroup = parseQualifiedName( substitutionGroupNode ); 238 } 239 240 Element annotationElement = (Element) XMLTools.getNode( element, getFullName( "annotation" ), nsContext ); 241 242 return new MappedElementDeclaration( name, isAbstract, typeReference, minOccurs, maxOccurs, substitutionGroup, 243 annotationElement ); 244 } 245 246 /** 247 * Parses the given <code>Element</code> as an annotated 'xs:complexType' declaration (with mapping information). 248 * 249 * @param element 250 * 'xs:complexType' declaration to be parsed 251 * @return object representation of the declaration 252 * @throws XMLParsingException 253 * if the document is not a valid XML Schema document or does not match the limitations of this class 254 */ 255 @Override 256 protected MappedComplexTypeDeclaration parseComplexTypeDeclaration( Element element ) 257 throws XMLParsingException { 258 259 QualifiedName name = null; 260 String localName = XMLTools.getNodeAsString( element, "@name", nsContext, null ); 261 if ( localName != null ) { 262 name = new QualifiedName( localName, getTargetNamespace() ); 263 if ( localName.length() == 0 ) { 264 String msg = "Error in schema document. Empty name (\"\") for complexType " + "declaration found."; 265 throw new XMLSchemaException( msg ); 266 } 267 } 268 269 List<Node> subElementList = null; 270 TypeReference extensionBase = null; 271 Node extensionBaseNode = XMLTools.getNode( element, getFullName( "complexContent/" ) 272 + getFullName( "extension/@base" ), nsContext ); 273 if ( extensionBaseNode != null ) { 274 extensionBase = new TypeReference( parseQualifiedName( extensionBaseNode ) ); 275 subElementList = XMLTools.getNodes( element, getFullName( "complexContent/" ) + getFullName( "extension/" ) 276 + getFullName( "sequence/" ) + getFullName( "element" ), 277 nsContext ); 278 } else { 279 subElementList = XMLTools.getRequiredNodes( element, getFullName( "sequence/" ) + getFullName( "element" ), 280 nsContext ); 281 } 282 283 ElementDeclaration[] subElements = new ElementDeclaration[subElementList.size()]; 284 for ( int i = 0; i < subElements.length; i++ ) { 285 Element subElement = (Element) subElementList.get( i ); 286 subElements[i] = parseElementDeclaration( subElement ); 287 } 288 Element annotationElement = (Element) XMLTools.getNode( element, getFullName( "annotation" ), nsContext ); 289 290 return new MappedComplexTypeDeclaration( name, extensionBase, subElements, annotationElement ); 291 } 292 293 /** 294 * Extracts the "gml:id" information from the given "xs:annotation" element. 295 * 296 * @param annotationElement 297 * "xs:annotation" element 298 * @param defaultTableName 299 * name for table if "deegreewfs:table"-element is missing 300 * @return "gml:id" information as MappedGMLId 301 * @throws XMLSchemaException 302 * if a syntactic or semantic error is found 303 */ 304 MappedGMLId extractGMLId( Element annotationElement, String defaultTableName ) 305 throws XMLSchemaException { 306 MappedGMLId gmlId = null; 307 try { 308 String table = XMLTools.getNodeAsString( annotationElement, "xs:appinfo/deegreewfs:table/text()", 309 nsContext, defaultTableName ); 310 Element gmlIdElement = (Element) XMLTools.getNode( annotationElement, "xs:appinfo/deegreewfs:gmlId", 311 nsContext ); 312 if ( gmlIdElement != null ) { 313 gmlId = parseGMLIdDefinition( gmlIdElement, table ); 314 } else { 315 MappingField[] idFields = new MappingField[] { new MappingField( table, "fid", Types.VARCHAR ) }; 316 IdGenerator idGenerator = IdGenerator.getInstance( IdGenerator.TYPE_UUID, new Properties() ); 317 gmlId = new MappedGMLId( defaultTableName.toUpperCase(), "_", idFields, idGenerator, 318 IDPART_INFO.notIDPart ); 319 } 320 } catch ( XMLParsingException e ) { 321 throw new XMLSchemaException( e.getMessage(), e ); 322 } 323 return gmlId; 324 } 325 326 /** 327 * Extracts the mapping information for a simple property type from the given "xs:annotation" element. 328 * 329 * @param element 330 * "xs:annotation" element 331 * @param propertyName 332 * name of the property (local part is used as default field name if it is not specified explicitly in 333 * the "MappingField" element) 334 * @param type 335 * @param minOccurs 336 * @param maxOccurs 337 * @param isIdentityPart 338 * @param table 339 * name of the table that is associated with the feature type that this property belongs to 340 * @return simple property type with persistence information 341 * @throws XMLSchemaException 342 * if a syntactic or semantic error is found 343 */ 344 MappedSimplePropertyType parseMappedSimplePropertyType( Element element, QualifiedName propertyName, int type, 345 int minOccurs, int maxOccurs, boolean isIdentityPart, 346 String table ) 347 throws XMLSchemaException { 348 349 MappedSimplePropertyType pt = null; 350 try { 351 Element contentElement = (Element) XMLTools.getRequiredNode( element, "xs:appinfo/deegreewfs:Content", 352 nsContext ); 353 // sanity check (common user error) 354 Node typeNode = XMLTools.getNode( contentElement, "@type", nsContext ); 355 if ( typeNode != null ) { 356 String msg = "Content element for simple property type '" + propertyName 357 + "' must not contain a type attribute."; 358 throw new XMLParsingException( msg ); 359 } 360 361 // table relations 362 List<Node> relationElements = XMLTools.getNodes( contentElement, "deegreewfs:Relation", nsContext ); 363 TableRelation[] tableRelations = new TableRelation[relationElements.size()]; 364 String fromTable = table; 365 for ( int i = 0; i < tableRelations.length; i++ ) { 366 tableRelations[i] = parseTableRelation( (Element) relationElements.get( i ), fromTable ); 367 fromTable = tableRelations[i].getToTable(); 368 } 369 370 SimpleContent content = null; 371 372 // check type of content 373 Node node = XMLTools.getNode( contentElement, "deegreewfs:MappingField", nsContext ); 374 if ( node != null ) { 375 String defaultField = propertyName.getLocalName(); 376 content = parseMappingField( (Element) node, defaultField, table ); 377 } else { 378 node = XMLTools.getNode( contentElement, "deegreewfs:Constant", nsContext ); 379 if ( node != null ) { 380 content = parseConstantContent( (Element) node ); 381 } else { 382 node = XMLTools.getNode( contentElement, "deegreewfs:SQLFunctionCall", nsContext ); 383 if ( node != null ) { 384 content = parseSQLFunctionCall( (Element) node, table ); 385 } else { 386 String msg = Messages.getMessage( "DATASTORE_ANNOTATION_SIMPLE_CONTENT_ERROR", propertyName ); 387 throw new XMLParsingException( msg ); 388 } 389 } 390 } 391 392 pt = new MappedSimplePropertyType( propertyName, type, minOccurs, maxOccurs, isIdentityPart, 393 tableRelations, content ); 394 } catch ( XMLParsingException e ) { 395 throw new XMLSchemaException( "Error in definition of simple property '" + propertyName + "': " 396 + e.getMessage() ); 397 } 398 return pt; 399 } 400 401 /** 402 * Parses the given element as a "deegreewfs:Constant" element. 403 * 404 * @param element 405 * "deegreewfs:Constant" element 406 * @return java representation of element 407 * @throws XMLParsingException 408 */ 409 ConstantContent parseConstantContent( Element element ) 410 throws XMLParsingException { 411 String constant = XMLTools.getRequiredNodeAsString( element, "text()", nsContext ); 412 return new ConstantContent( constant ); 413 } 414 415 /** 416 * Parses the given "deegreewfs:SpecialContent" element. 417 * 418 * @param element 419 * "deegreewfs:SpecialContent" element 420 * @return java representation of element 421 * @throws XMLParsingException 422 */ 423 SpecialContent parseSpecialContent( Element element ) 424 throws XMLParsingException { 425 426 SpecialContent content = null; 427 String variable = XMLTools.getRequiredNodeAsString( element, "text()", nsContext ); 428 429 try { 430 content = new SpecialContent( variable ); 431 } catch ( Exception e ) { 432 String msg = Messages.getMessage( "DATASTORE_PARSING_SPECIAL_CONTENT", e.getMessage() ); 433 throw new XMLParsingException( msg ); 434 } 435 return content; 436 } 437 438 /** 439 * Parses the given element as a "deegreewfs:SQLFunctionCall" element. 440 * 441 * @param element 442 * "deegreewfs:SQLFunctionCall" element 443 * @param table 444 * @return java representation of element 445 * @throws XMLParsingException 446 */ 447 SQLFunctionCall parseSQLFunctionCall( Element element, String table ) 448 throws XMLParsingException { 449 450 String callString = XMLTools.getRequiredNodeAsString( element, "@call", nsContext ); 451 452 String typeName = XMLTools.getRequiredNodeAsString( element, "@type", nsContext ); 453 int typeCode = -1; 454 try { 455 typeCode = Types.getTypeCodeForSQLType( typeName ); 456 } catch ( UnknownTypeException e ) { 457 throw new XMLParsingException( "Invalid field type: " + e.getMessage(), e ); 458 } 459 460 List<Node> nl = XMLTools.getNodes( element, "deegreewfs:FunctionParam", nsContext ); 461 List<FunctionParam> functionParams = new ArrayList<FunctionParam>(); 462 Iterator<Node> iter = nl.iterator(); 463 while ( iter.hasNext() ) { 464 Element paramElement = (Element) iter.next(); 465 functionParams.add( parseFunctionParam( paramElement, table ) ); 466 } 467 468 // validate variable references 469 int maxVar = extractMaxVariableNumber( callString ); 470 if ( maxVar > functionParams.size() ) { 471 String msg = "Error in FunctionCall definition ('" + callString + "') - call string uses variable $" 472 + maxVar + ", but only has " + functionParams.size() + " FunctionParam elements."; 473 throw new XMLParsingException( msg ); 474 } 475 476 // check SRS for all function params (mapped geometry columns) 477 int internalSRS = -1; 478 for ( FunctionParam param : functionParams ) { 479 if ( param instanceof FieldContent ) { 480 MappingField mf = ( (FieldContent) param ).getField(); 481 if ( mf instanceof MappingGeometryField ) { 482 int thisSRS = ( (MappingGeometryField) mf ).getSRS(); 483 if ( internalSRS == -1 ) { 484 internalSRS = thisSRS; 485 } else { 486 if ( internalSRS != thisSRS ) { 487 String msg = Messages.getMessage( "DATASTORE_SQL_FUNCTION_CALL_INVALID_SRS", internalSRS, 488 thisSRS ); 489 throw new XMLParsingException( msg ); 490 } 491 } 492 } 493 } 494 } 495 496 // set SRS for all 'SpecialContent' function params 497 for ( FunctionParam param : functionParams ) { 498 if ( param instanceof SpecialContent ) { 499 ( (SpecialContent) param ).setSRS( internalSRS ); 500 } 501 } 502 503 return new SQLFunctionCall( callString, typeCode, functionParams ); 504 } 505 506 /** 507 * Extracts maximum variable numbers ('$i') used in the given call string. 508 * 509 * TODO: handle leading zeros 510 * 511 * @param callString 512 * @return maximum variable numbers used in the given call string 513 */ 514 private int extractMaxVariableNumber( String callString ) 515 throws XMLParsingException { 516 517 int maxVar = 0; 518 519 int foundAt = callString.indexOf( '$' ); 520 while ( foundAt != -1 ) { 521 foundAt++; 522 String varNumberString = ""; 523 while ( foundAt < callString.length() ) { 524 char numberChar = callString.charAt( foundAt++ ); 525 if ( numberChar >= '0' && numberChar <= '9' ) { 526 varNumberString += numberChar; 527 } else { 528 break; 529 } 530 } 531 532 if ( varNumberString.length() == 0 ) { 533 String msg = "Error in call attribute ('" + callString 534 + "') of FunctionCall definition - parameters must be specified by " 535 + "the '$' symbol, followed by a positive integer."; 536 throw new XMLParsingException( msg ); 537 } 538 539 try { 540 int varNo = Integer.parseInt( varNumberString ); 541 if ( varNo > maxVar ) { 542 maxVar = varNo; 543 } 544 if ( varNo == 0 ) { 545 String msg = "Error in call attribute ('" + callString 546 + "') of FunctionCall definition - $0 is not a valid variable " 547 + "identifier. Counting of variables starts with 1."; 548 throw new XMLParsingException( msg ); 549 } 550 } catch ( NumberFormatException e ) { 551 assert ( false ); 552 } 553 554 // find next '$' symbol 555 foundAt = callString.indexOf( '$', foundAt ); 556 } 557 return maxVar; 558 } 559 560 /** 561 * Parses the given "deegreewfs:FunctionParam" element. 562 * <p> 563 * Valid child elements: 564 * <ul> 565 * <li>deegreewfs:MappingField</li> 566 * <li>deegreewfs:ConstantContent</li> 567 * <li>deegreewfs:SpecialContent</li> 568 * </ul> 569 * 570 * @param element 571 * "deegreewfs:FunctionParam" element 572 * @param table 573 * @return java representation of element 574 * @throws XMLParsingException 575 */ 576 FunctionParam parseFunctionParam( Element element, String table ) 577 throws XMLParsingException { 578 579 FunctionParam param = null; 580 581 Element childElement = XMLTools.getFirstChildElement( element ); 582 if ( childElement == null || !childElement.getNamespaceURI().equals( CommonNamespaces.DEEGREEWFS.toString() ) ) { 583 String msg = Messages.getMessage( "DATASTORE_PARSING_SQL_FUNCTION_CALL" ); 584 throw new XMLParsingException( msg ); 585 } 586 587 if ( "MappingField".equals( childElement.getLocalName() ) ) { 588 // table relations 589 List<Node> relationElements = XMLTools.getNodes( element, "deegreewfs:Relation", nsContext ); 590 TableRelation[] tablePath = new TableRelation[relationElements.size()]; 591 String fromTable = table; 592 for ( int i = 0; i < tablePath.length; i++ ) { 593 tablePath[i] = parseTableRelation( (Element) relationElements.get( i ), fromTable ); 594 fromTable = tablePath[i].getToTable(); 595 } 596 // TODO do this a better way 597 String type = XMLTools.getRequiredNodeAsString( childElement, "@type", nsContext ); 598 MappingField field; 599 if ( "GEOMETRY".equals( type ) ) { 600 field = parseGeometryMappingField( childElement, null, fromTable ); 601 } else { 602 field = parseMappingField( childElement, null, fromTable ); 603 } 604 param = new FieldContent( field, tablePath ); 605 } else if ( "ConstantContent".equals( childElement.getLocalName() ) ) { 606 param = parseConstantContent( childElement ); 607 } else if ( "SpecialContent".equals( childElement.getLocalName() ) ) { 608 param = parseSpecialContent( childElement ); 609 } else { 610 String msg = Messages.getMessage( "DATASTORE_PARSING_SQL_FUNCTION_CALL" ); 611 throw new XMLParsingException( msg ); 612 } 613 614 return param; 615 } 616 617 /** 618 * Extracts the mapping information for a geometry property type from the given "xs:annotation" element. 619 * 620 * @param element 621 * "xs:annotation" element 622 * @param propertyName 623 * name of the property (local part is used as default field name if it is not specified explicitly in 624 * the "MappingField" element) 625 * @param typeName 626 * @param type 627 * @param minOccurs 628 * @param maxOccurs 629 * @param isIdentityPart 630 * @param table 631 * name of the table that is associated with the feature type that this property belongs to 632 * @return geometry property type with persistence information 633 * @throws XMLSchemaException 634 * if a syntactic or semantic error is found 635 * @throws UnknownCRSException 636 */ 637 MappedGeometryPropertyType parseMappedGeometryPropertyType( Element element, QualifiedName propertyName, 638 QualifiedName typeName, int type, int minOccurs, 639 int maxOccurs, boolean isIdentityPart, String table ) 640 throws XMLSchemaException, UnknownCRSException { 641 642 MappedGeometryPropertyType pt = null; 643 try { 644 Element contentElement = (Element) XMLTools.getRequiredNode( element, "xs:appinfo/deegreewfs:Content", 645 nsContext ); 646 // sanity check (common error) 647 Node typeNode = XMLTools.getNode( contentElement, "@type", nsContext ); 648 if ( typeNode != null ) { 649 throw new XMLParsingException( "Content element must not contain a type attribute." ); 650 } 651 652 URI srs = XMLTools.getNodeAsURI( element, "deegreewfs:SRS/text()", nsContext, this.defaultSRS ); 653 654 Element mfElement = (Element) XMLTools.getRequiredNode( contentElement, "deegreewfs:MappingField", 655 nsContext ); 656 String defaultField = propertyName.getLocalName(); 657 MappingGeometryField mappingField = parseGeometryMappingField( mfElement, defaultField, table ); 658 List<Node> relationElements = XMLTools.getNodes( contentElement, "deegreewfs:Relation", nsContext ); 659 TableRelation[] tableRelations = new TableRelation[relationElements.size()]; 660 String fromTable = table; 661 for ( int i = 0; i < tableRelations.length; i++ ) { 662 tableRelations[i] = parseTableRelation( (Element) relationElements.get( i ), fromTable ); 663 fromTable = tableRelations[i].getToTable(); 664 } 665 666 pt = new MappedGeometryPropertyType( propertyName, typeName, type, minOccurs, maxOccurs, isIdentityPart, 667 srs, tableRelations, mappingField ); 668 } catch ( XMLParsingException e ) { 669 throw new XMLSchemaException( "Error in definition of geometry property '" + propertyName + "': " 670 + e.getMessage() ); 671 } 672 return pt; 673 } 674 675 /** 676 * Extracts the mapping information for a feature property type from the given "xs:annotation" element. 677 * 678 * @param element 679 * "xs:annotation" element 680 * @param propertyName 681 * name of the property (local part is used as default field name if it is not specified explicitly in 682 * the "MappingField" element) 683 * @param minOccurs 684 * @param maxOccurs 685 * @param isIdentityPart 686 * @param table 687 * name of the table that is associated with the feature type that this property belongs to 688 * @param isReferenceType 689 * true, if this property is of type "gml:ReferenceType", false otherwise 690 * @return feature property type with persistence information 691 * @throws XMLSchemaException 692 * if a syntactic or semantic error is found 693 */ 694 MappedFeaturePropertyType parseMappedFeaturePropertyType( Element element, QualifiedName propertyName, 695 int minOccurs, int maxOccurs, boolean isIdentityPart, 696 String table, boolean isReferenceType ) 697 throws XMLSchemaException { 698 699 MappedFeaturePropertyType pt = null; 700 try { 701 Element contentElement = (Element) XMLTools.getRequiredNode( element, "xs:appinfo/deegreewfs:Content", 702 nsContext ); 703 704 // sanity check (common error) 705 Node mfNode = XMLTools.getNode( element, "deegreewfs:MappingField", nsContext ); 706 if ( mfNode != null ) { 707 throw new XMLParsingException( "Content element must not contain a MappingField element." ); 708 } 709 710 QualifiedName containedFT = parseQualifiedName( XMLTools.getRequiredNode( contentElement, "@type", 711 nsContext ) ); 712 MappedFeatureTypeReference containedFTRef = new MappedFeatureTypeReference( containedFT ); 713 714 List<Node> relationElements = XMLTools.getNodes( contentElement, "deegreewfs:Relation", nsContext ); 715 TableRelation[] tableRelations = new TableRelation[relationElements.size()]; 716 String fromTable = table; 717 for ( int i = 0; i < tableRelations.length; i++ ) { 718 tableRelations[i] = parseTableRelation( (Element) relationElements.get( i ), fromTable ); 719 fromTable = tableRelations[i].getToTable(); 720 } 721 722 boolean allowExternalLinks = getNodeAsBoolean( contentElement, "@allowExternalLinks", nsContext, false ); 723 724 pt = new MappedFeaturePropertyType( propertyName, Types.FEATURE_PROPERTY_NAME, Types.FEATURE, minOccurs, 725 maxOccurs, isIdentityPart, tableRelations, containedFTRef, 726 isReferenceType, allowExternalLinks ); 727 } catch ( XMLParsingException e ) { 728 throw new XMLSchemaException( "Error in definition of feature property '" + propertyName + "': " 729 + e.getMessage() ); 730 } 731 return pt; 732 } 733 734 /** 735 * Parses the given 'MappingField' element. 736 * 737 * @param element 738 * 'MappingField' element 739 * @param defaultField 740 * if null, the element must have a 'field' attribute, otherwise the given value is used, if the element 741 * misses a 'field' attribute 742 * @param defaultTable 743 * if null, the element must have a 'table' attribute, otherwise the 'table' attribute must be left out 744 * or match the given value 745 * @return class representation of 'MappingField' element 746 * @throws XMLParsingException 747 * if a syntactic or semantic error is found 748 */ 749 private MappingField parseMappingField( Element element, String defaultField, String defaultTable ) 750 throws XMLParsingException { 751 752 MappingField mappingField = null; 753 754 String field = null; 755 if ( defaultField == null ) { 756 field = XMLTools.getRequiredNodeAsString( element, "@field", nsContext ); 757 } else { 758 field = XMLTools.getNodeAsString( element, "@field", nsContext, defaultField ); 759 } 760 761 String typeName = XMLTools.getRequiredNodeAsString( element, "@type", nsContext ); 762 int typeCode = -1; 763 try { 764 typeCode = Types.getTypeCodeForSQLType( typeName ); 765 } catch ( UnknownTypeException e ) { 766 throw new XMLParsingException( "Invalid field type: " + e.getMessage(), e ); 767 } 768 769 String table = null; 770 if ( defaultTable == null ) { 771 // if table is unspecified, this is resolved later (in MappedGMLSchema) 772 // TODO clean this up 773 table = XMLTools.getNodeAsString( element, "@table", nsContext, null ); 774 } else { 775 table = XMLTools.getNodeAsString( element, "@table", nsContext, defaultTable ); 776 if ( !table.equals( defaultTable ) ) { 777 throw new XMLParsingException( "Specified 'table' attribute ('" + table 778 + "') in 'MappingField' element is inconsistent; leave out or use '" 779 + defaultTable + "' instead." ); 780 } 781 } 782 783 boolean auto = XMLTools.getNodeAsBoolean( element, "@auto", nsContext, false ); 784 mappingField = new MappingField( table, field, typeCode, auto ); 785 786 return mappingField; 787 } 788 789 /** 790 * Parses the given 'MappingField' element. 791 * 792 * @param element 793 * 'MappingField' element 794 * @param defaultField 795 * if null, the element must have a 'field' attribute, otherwise the given value is used, if the element 796 * misses a 'field' attribute 797 * @param defaultTable 798 * if null, the element must have a 'table' attribute, otherwise the 'table' attribute must be left out 799 * or match the given value 800 * @return class representation of 'MappingField' element 801 * @throws XMLParsingException 802 * if a syntactic or semantic error is found 803 */ 804 private MappingGeometryField parseGeometryMappingField( Element element, String defaultField, String defaultTable ) 805 throws XMLParsingException { 806 807 MappingGeometryField mappingField = null; 808 809 String field = null; 810 if ( defaultField == null ) { 811 field = XMLTools.getRequiredNodeAsString( element, "@field", nsContext ); 812 } else { 813 field = XMLTools.getNodeAsString( element, "@field", nsContext, defaultField ); 814 } 815 816 String typeName = XMLTools.getRequiredNodeAsString( element, "@type", nsContext ); 817 int typeCode = Types.OTHER; 818 if ( !( "GEOMETRY".equals( typeName ) ) ) { 819 try { 820 typeCode = Types.getTypeCodeForSQLType( typeName ); 821 } catch ( UnknownTypeException e ) { 822 throw new XMLParsingException( "Invalid field type: " + e.getMessage(), e ); 823 } 824 } 825 826 String table = null; 827 if ( defaultTable == null ) { 828 // if table is unspecified, this is resolved later (in MappedGMLSchema) 829 // TODO clean this up 830 table = XMLTools.getNodeAsString( element, "@table", nsContext, null ); 831 } else { 832 table = XMLTools.getNodeAsString( element, "@table", nsContext, defaultTable ); 833 if ( !table.equals( defaultTable ) ) { 834 String msg = "Specified 'table' attribute ('" + table 835 + "') in 'MappingField' element is inconsistent; leave out or use '" + defaultTable 836 + "' instead."; 837 throw new XMLParsingException( msg ); 838 } 839 } 840 841 int internalSrs = XMLTools.getNodeAsInt( element, "@srs", nsContext, -1 ); 842 mappingField = new MappingGeometryField( table, field, typeCode, internalSrs ); 843 return mappingField; 844 } 845 846 /** 847 * Parses the given 'gmlId' element. 848 * 849 * @param element 850 * 'gmlId' element 851 * @param table 852 * the associated table of the FeatureType 853 * @return class representation of 'gmlId' element 854 * @throws XMLParsingException 855 * if a syntactic or semantic error is found 856 */ 857 private MappedGMLId parseGMLIdDefinition( Element element, String table ) 858 throws XMLParsingException { 859 String prefix = XMLTools.getNodeAsString( element, "@prefix", nsContext, "" ); 860 String separator = XMLTools.getNodeAsString( element, "@separator", nsContext, "" ); 861 862 List<Node> mappingFieldElementList = XMLTools.getRequiredNodes( element, "deegreewfs:MappingField", nsContext ); 863 MappingField[] mappingFields = new MappingField[mappingFieldElementList.size()]; 864 for ( int i = 0; i < mappingFields.length; i++ ) { 865 Element mappingFieldElement = (Element) mappingFieldElementList.get( i ); 866 mappingFields[i] = parseMappingField( mappingFieldElement, 867 XMLTools.getRequiredNodeAsString( mappingFieldElement, "@field", 868 nsContext ), table ); 869 } 870 871 IDPART_INFO idpart_info = IDPART_INFO.noIDInfo; 872 String identityPart = XMLTools.getNodeAsString( element, "deegreewfs:IdentityPart/text()", nsContext, null ); 873 if ( identityPart != null ) { 874 if ( "false".equals( identityPart ) ) { 875 idpart_info = IDPART_INFO.notIDPart; 876 } else { 877 idpart_info = IDPART_INFO.isIDPart; 878 } 879 } 880 881 IdGenerator idGenerator = null; 882 Element idGeneratorElement = (Element) XMLTools.getNode( element, "deegreewfs:IdGenerator", nsContext ); 883 if ( idGeneratorElement != null ) { 884 idGenerator = parseGMLIdGenerator( idGeneratorElement ); 885 } else { 886 idGenerator = IdGenerator.getInstance( IdGenerator.TYPE_UUID, new Properties() ); 887 } 888 return new MappedGMLId( prefix, separator, mappingFields, idGenerator, idpart_info ); 889 } 890 891 /** 892 * Parses the given 'IdGenerator' element. 893 * 894 * @param element 895 * 'IdGenerator' element 896 * @return object representation of 'IdGenerator' element 897 * @throws XMLParsingException 898 * if a syntactic or semantic error is found 899 */ 900 private IdGenerator parseGMLIdGenerator( Element element ) 901 throws XMLParsingException { 902 String type = XMLTools.getRequiredNodeAsString( element, "@type", nsContext ); 903 Properties params = new Properties(); 904 List<Node> paramElementList = XMLTools.getNodes( element, "deegreewfs:param", nsContext ); 905 Iterator<Node> iter = paramElementList.iterator(); 906 while ( iter.hasNext() ) { 907 Element paramElement = (Element) iter.next(); 908 String name = XMLTools.getRequiredNodeAsString( paramElement, "@name", nsContext ); 909 String value = XMLTools.getRequiredNodeAsString( paramElement, "text()", nsContext ); 910 params.setProperty( name, value ); 911 } 912 IdGenerator idGenerator = IdGenerator.getInstance( type, params ); 913 return idGenerator; 914 } 915 916 private TableRelation parseTableRelation( Element element, String fromTable ) 917 throws XMLParsingException { 918 List<Node> fromMappingElements = XMLTools.getRequiredNodes( element, "deegreewfs:From/deegreewfs:MappingField", 919 nsContext ); 920 List<Node> toMappingElements = XMLTools.getRequiredNodes( element, "deegreewfs:To/deegreewfs:MappingField", 921 nsContext ); 922 if ( fromMappingElements.size() != toMappingElements.size() ) { 923 throw new XMLParsingException( "Error in 'Relation' element: number of 'MappingField' elements " 924 + "below 'From' and 'To' elements do not match." ); 925 } 926 FK_INFO fkInfo = FK_INFO.noFKInfo; 927 boolean fromIsFK = XMLTools.getNodeAsBoolean( element, "deegreewfs:From/@fk", nsContext, false ); 928 boolean toIsFK = XMLTools.getNodeAsBoolean( element, "deegreewfs:To/@fk", nsContext, false ); 929 if ( fromIsFK && toIsFK ) { 930 throw new XMLParsingException( "Error in 'Relation' element: either 'To' or 'From' can " 931 + "have a 'fk' attribute with value 'true', but not both." ); 932 } 933 if ( fromIsFK ) { 934 fkInfo = FK_INFO.fkIsFromField; 935 } 936 if ( toIsFK ) { 937 fkInfo = FK_INFO.fkIsToField; 938 } 939 MappingField[] fromMappingFields = new MappingField[fromMappingElements.size()]; 940 MappingField[] toMappingFields = new MappingField[fromMappingFields.length]; 941 for ( int i = 0; i < fromMappingFields.length; i++ ) { 942 fromMappingFields[i] = parseMappingField( (Element) fromMappingElements.get( i ), null, fromTable ); 943 toMappingFields[i] = parseMappingField( (Element) toMappingElements.get( i ), null, null ); 944 } 945 946 // parse id generator 947 // TODO sanity checks 948 IdGenerator idGenerator = null; 949 if ( fromIsFK ) { 950 Element idGeneratorElement = (Element) XMLTools.getNode( element, "deegreewfs:To/deegreewfs:IdGenerator", 951 nsContext ); 952 if ( idGeneratorElement != null ) { 953 idGenerator = parseGMLIdGenerator( idGeneratorElement ); 954 } else { 955 idGenerator = IdGenerator.getInstance( IdGenerator.TYPE_UUID, new Properties() ); 956 } 957 } else { 958 Element idGeneratorElement = (Element) XMLTools.getNode( element, "deegreewfs:From/deegreewfs:IdGenerator", 959 nsContext ); 960 if ( idGeneratorElement != null ) { 961 idGenerator = parseGMLIdGenerator( idGeneratorElement ); 962 } else { 963 idGenerator = IdGenerator.getInstance( IdGenerator.TYPE_UUID, new Properties() ); 964 } 965 } 966 967 return new TableRelation( fromMappingFields, toMappingFields, fkInfo, idGenerator ); 968 } 969 970 /** 971 * Returns the value of the "deegreewfs:visible" element. 972 * 973 * @param annotationElement 974 * @return -1 if it is not present, 0 if it is "false", 1 if it is "true" 975 * @throws XMLParsingException 976 */ 977 public int parseVisible( Element annotationElement ) 978 throws XMLParsingException { 979 int visibleCode = -1; 980 String visible = XMLTools.getNodeAsString( annotationElement, "xs:appinfo/deegreewfs:visible/text()", 981 nsContext, null ); 982 if ( visible != null ) { 983 if ( "false".equals( visible ) ) { 984 visibleCode = 0; 985 } else { 986 visibleCode = 1; 987 } 988 } 989 return visibleCode; 990 } 991 992 /** 993 * Parses the 'updatable' status of the given feature type annotation element. 994 * 995 * @param annotationElement 996 * @return true, if update transactions may be performed on the feature type, false otherwise 997 * @throws XMLParsingException 998 */ 999 public boolean parseIsUpdatable( Element annotationElement ) 1000 throws XMLParsingException { 1001 return XMLTools.getNodeAsBoolean( annotationElement, "xs:appinfo/deegreewfs:transaction/@update", nsContext, 1002 false ); 1003 } 1004 1005 /** 1006 * Parses the 'deletable' status of the given feature type annotation element. 1007 * 1008 * @param annotationElement 1009 * @return true, if delete transactions may be performed on the feature type, false otherwise 1010 * @throws XMLParsingException 1011 */ 1012 public boolean parseIsDeletable( Element annotationElement ) 1013 throws XMLParsingException { 1014 return XMLTools.getNodeAsBoolean( annotationElement, "xs:appinfo/deegreewfs:transaction/@delete", nsContext, 1015 false ); 1016 } 1017 1018 /** 1019 * Parses the 'insertable' status of the given feature type annotation element. 1020 * 1021 * @param annotationElement 1022 * @return true, if insert transactions may be performed on the feature type, false otherwise 1023 * @throws XMLParsingException 1024 */ 1025 public boolean parseIsInsertable( Element annotationElement ) 1026 throws XMLParsingException { 1027 return XMLTools.getNodeAsBoolean( annotationElement, "xs:appinfo/deegreewfs:transaction/@insert", nsContext, 1028 false ); 1029 } 1030 1031 /** 1032 * Parses the 'isPseudoFeatureType' element of the given feature type annotation element. 1033 * 1034 * @param annotationElement 1035 * @return true, if feature type is a pseudo feature type, false otherwise 1036 * @throws XMLParsingException 1037 */ 1038 public boolean parseIsPseudoFeatureType( Element annotationElement ) 1039 throws XMLParsingException { 1040 return XMLTools.getNodeAsBoolean( annotationElement, "xs:appinfo/deegreewfs:isPseudoFeatureType/text()", 1041 nsContext, false ); 1042 } 1043 1044 /** 1045 * Returns the value of the "deegreewfs:IdentityPart" element. 1046 * 1047 * @param annotationElement 1048 * @return -1 if it is not present, 0 if it is "false", 1 if it is "true" 1049 * @throws XMLParsingException 1050 */ 1051 public int parseIdentityPart( Element annotationElement ) 1052 throws XMLParsingException { 1053 int identityCode = -1; 1054 String identityPart = XMLTools.getNodeAsString( annotationElement, "xs:appinfo/deegreewfs:IdentityPart/text()", 1055 nsContext, null ); 1056 if ( identityPart != null ) { 1057 if ( "false".equals( identityPart ) ) { 1058 identityCode = 0; 1059 } else { 1060 identityCode = 1; 1061 } 1062 } 1063 return identityCode; 1064 } 1065 1066 /** 1067 * Returns the value of the "deegreewfs:DefaultSRS" element. 1068 * 1069 * @param annotationElement 1070 * @param defaultValue 1071 * @return default SRS of the feature type 1072 * @throws XMLParsingException 1073 */ 1074 public URI parseDefaultSRS( Element annotationElement, URI defaultValue ) 1075 throws XMLParsingException { 1076 URI defaultSRS = XMLTools.getNodeAsURI( annotationElement, "xs:appinfo/deegreewfs:DefaultSRS/text()", 1077 nsContext, defaultValue ); 1078 return defaultSRS; 1079 } 1080 1081 /** 1082 * Returns the values of the "deegreewfs:OtherSRS" elements. 1083 * 1084 * @param annotationElement 1085 * @return alternative SRS that may be used to query the feature type 1086 * @throws XMLParsingException 1087 */ 1088 public URI[] parseOtherSRS( Element annotationElement ) 1089 throws XMLParsingException { 1090 URI[] otherSRS = XMLTools.getNodesAsURIs( annotationElement, "xs:appinfo/deegreewfs:OtherSRS/text()", nsContext ); 1091 return otherSRS; 1092 } 1093 1094 /** 1095 * @param annotationElement 1096 * @param table 1097 * @param schema 1098 * @return a list of default GML properties to include. The qualified name specified (if not null), after which 1099 * property this one should be included. 1100 * @throws XMLParsingException 1101 */ 1102 public LinkedList<Pair<PropertyType, QualifiedName>> parseGMLDefaultProps( Element annotationElement, String table, 1103 MappedGMLSchema schema ) 1104 throws XMLParsingException { 1105 Element appinfo = getRequiredElement( annotationElement, XS_PREFIX + ":appinfo", nsContext ); 1106 1107 LinkedList<Pair<PropertyType, QualifiedName>> list = new LinkedList<Pair<PropertyType, QualifiedName>>(); 1108 1109 if ( appinfo == null ) { 1110 return list; 1111 } 1112 1113 for ( Element e : getElements( appinfo, DEEGREEWFS_PREFIX + ":StandardGMLProps/" + XS_PREFIX + ":element", 1114 nsContext ) ) { 1115 try { 1116 QualifiedName realName = getRequiredNodeAsQualifiedName( e, "@name", nsContext ); 1117 Pair<PropertyType, QualifiedName> p = new Pair<PropertyType, QualifiedName>(); 1118 p.first = schema.buildPropertyTypeRealName( parseElementDeclaration( e ), table, realName ); 1119 p.second = getNodeAsQualifiedName( e, "@insertAfter", nsContext, null ); 1120 list.add( p ); 1121 } catch ( UnknownCRSException e1 ) { 1122 throw new XMLParsingException( "Wrapped exception", e1 ); 1123 } 1124 } 1125 1126 return list; 1127 } 1128 }