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