001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/io/datastore/schema/MappedGMLSchemaDocument.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.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: mschneider $ 091 * 092 * @version $Revision: 7468 $, $Date: 2007-06-05 16:33:13 +0200 (Di, 05 Jun 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 String msg = Messages.getMessage( "DATASTORE_CONFIGURATION_BLOCK_FAULTY", e.getMessage() ); 175 throw new XMLParsingException( msg ); 176 } 177 } 178 179 /** 180 * Parses the given <code>Element</code> as an annotated 'xs:element' declaration (with mapping information). 181 * 182 * @param element 183 * 'xs:element' declaration to be parsed 184 * @return object representation of the declaration 185 * @throws XMLParsingException 186 * if the document is not a valid XML Schema document or does not match the limitations of this class 187 */ 188 @Override 189 protected MappedElementDeclaration parseElementDeclaration( Element element ) 190 throws XMLParsingException { 191 192 QualifiedName name = new QualifiedName( XMLTools.getRequiredNodeAsString( element, "@name", nsContext ), 193 getTargetNamespace() ); 194 if ( name.getLocalName().length() == 0 ) { 195 String msg = "Error in schema document. Empty name (\"\") in element declaration " + "found."; 196 throw new XMLSchemaException( msg ); 197 } 198 199 boolean isAbstract = XMLTools.getNodeAsBoolean( element, "@abstract", nsContext, false ); 200 201 TypeReference typeReference = null; 202 Node typeNode = XMLTools.getNode( element, "@type", nsContext ); 203 if ( typeNode != null ) { 204 typeReference = new TypeReference( parseQualifiedName( typeNode ) ); 205 } else { 206 // inline type declaration 207 Element elem = (Element) XMLTools.getRequiredNode( element, getFullName( "complexType" ), nsContext ); 208 TypeDeclaration type = parseComplexTypeDeclaration( elem ); 209 typeReference = new TypeReference( type ); 210 } 211 212 int minOccurs = XMLTools.getNodeAsInt( element, "@minOccurs", nsContext, 1 ); 213 int maxOccurs = -1; 214 String maxOccursString = XMLTools.getNodeAsString( element, "@maxOccurs", nsContext, "1" ); 215 if ( !"unbounded".equals( maxOccursString ) ) { 216 try { 217 maxOccurs = Integer.parseInt( maxOccursString ); 218 } catch ( NumberFormatException e ) { 219 throw new XMLParsingException( "Invalid value ('" + maxOccursString + "') in 'maxOccurs' attribute. " 220 + "Must be a valid integer value or 'unbounded'." ); 221 } 222 } 223 224 QualifiedName substitutionGroup = null; 225 Node substitutionGroupNode = XMLTools.getNode( element, "@substitutionGroup", nsContext ); 226 if ( substitutionGroupNode != null ) { 227 substitutionGroup = parseQualifiedName( substitutionGroupNode ); 228 } 229 230 Element annotationElement = (Element) XMLTools.getNode( element, getFullName( "annotation" ), nsContext ); 231 232 return new MappedElementDeclaration( name, isAbstract, typeReference, minOccurs, maxOccurs, substitutionGroup, 233 annotationElement ); 234 } 235 236 /** 237 * Parses the given <code>Element</code> as an annotated 'xs:complexType' declaration (with mapping information). 238 * 239 * @param element 240 * 'xs:complexType' declaration to be parsed 241 * @return object representation of the declaration 242 * @throws XMLParsingException 243 * if the document is not a valid XML Schema document or does not match the limitations of this class 244 */ 245 @Override 246 protected MappedComplexTypeDeclaration parseComplexTypeDeclaration( Element element ) 247 throws XMLParsingException { 248 249 QualifiedName name = null; 250 String localName = XMLTools.getNodeAsString( element, "@name", nsContext, null ); 251 if ( localName != null ) { 252 name = new QualifiedName( localName, getTargetNamespace() ); 253 if ( localName.length() == 0 ) { 254 String msg = "Error in schema document. Empty name (\"\") for complexType " + "declaration found."; 255 throw new XMLSchemaException( msg ); 256 } 257 } 258 259 List subElementList = null; 260 TypeReference extensionBase = null; 261 Node extensionBaseNode = XMLTools.getNode( element, getFullName( "complexContent/" ) 262 + getFullName( "extension/@base" ), nsContext ); 263 if ( extensionBaseNode != null ) { 264 extensionBase = new TypeReference( parseQualifiedName( extensionBaseNode ) ); 265 subElementList = XMLTools.getNodes( element, getFullName( "complexContent/" ) + getFullName( "extension/" ) 266 + getFullName( "sequence/" ) + getFullName( "element" ), 267 nsContext ); 268 } else { 269 subElementList = XMLTools.getRequiredNodes( element, getFullName( "sequence/" ) + getFullName( "element" ), 270 nsContext ); 271 } 272 273 ElementDeclaration[] subElements = new ElementDeclaration[subElementList.size()]; 274 for ( int i = 0; i < subElements.length; i++ ) { 275 Element subElement = (Element) subElementList.get( i ); 276 subElements[i] = parseElementDeclaration( subElement ); 277 } 278 Element annotationElement = (Element) XMLTools.getNode( element, getFullName( "annotation" ), nsContext ); 279 280 return new MappedComplexTypeDeclaration( name, extensionBase, subElements, annotationElement ); 281 } 282 283 /** 284 * Extracts the "gml:id" information from the given "xs:annotation" element. 285 * 286 * @param annotationElement 287 * "xs:annotation" element 288 * @param defaultTableName 289 * name for table if "deegreewfs:table"-element is missing 290 * @return "gml:id" information as MappedGMLId 291 * @throws XMLSchemaException 292 * if a syntactic or semantic error is found 293 */ 294 MappedGMLId extractGMLId( Element annotationElement, String defaultTableName ) 295 throws XMLSchemaException { 296 MappedGMLId gmlId = null; 297 try { 298 String table = XMLTools.getNodeAsString( annotationElement, "xs:appinfo/deegreewfs:table/text()", 299 nsContext, defaultTableName ); 300 Element gmlIdElement = (Element) XMLTools.getNode( annotationElement, "xs:appinfo/deegreewfs:gmlId", 301 nsContext ); 302 if ( gmlIdElement != null ) { 303 gmlId = parseGMLIdDefinition( gmlIdElement, table ); 304 } else { 305 MappingField[] idFields = new MappingField[] { new MappingField( table, "fid", Types.VARCHAR ) }; 306 IdGenerator idGenerator = IdGenerator.getInstance( IdGenerator.TYPE_UUID, new Properties() ); 307 gmlId = new MappedGMLId( defaultTableName.toUpperCase(), "_", idFields, idGenerator, 308 IDPART_INFO.notIDPart ); 309 } 310 } catch ( XMLParsingException e ) { 311 throw new XMLSchemaException( e.getMessage(), e ); 312 } 313 return gmlId; 314 } 315 316 /** 317 * Extracts the mapping information for a simple property type from the given "xs:annotation" element. 318 * 319 * @param element 320 * "xs:annotation" element 321 * @param propertyName 322 * name of the property (local part is used as default field name if it is not specified explicitly in 323 * the "MappingField" element) 324 * @param type 325 * @param minOccurs 326 * @param maxOccurs 327 * @param isIdentityPart 328 * @param table 329 * name of the table that is associated with the feature type that this property belongs to 330 * @return simple property type with persistence information 331 * @throws XMLSchemaException 332 * if a syntactic or semantic error is found 333 */ 334 MappedSimplePropertyType parseMappedSimplePropertyType( Element element, QualifiedName propertyName, int type, 335 int minOccurs, int maxOccurs, boolean isIdentityPart, 336 String table ) 337 throws XMLSchemaException { 338 339 MappedSimplePropertyType pt = null; 340 try { 341 Element contentElement = (Element) XMLTools.getRequiredNode( element, "xs:appinfo/deegreewfs:Content", 342 nsContext ); 343 // sanity check (common user error) 344 Node typeNode = XMLTools.getNode( contentElement, "@type", nsContext ); 345 if ( typeNode != null ) { 346 String msg = "Content element for simple property type '" + propertyName 347 + "' must not contain a type attribute."; 348 throw new XMLParsingException( msg ); 349 } 350 351 // table relations 352 List relationElements = XMLTools.getNodes( contentElement, "deegreewfs:Relation", nsContext ); 353 TableRelation[] tableRelations = new TableRelation[relationElements.size()]; 354 String fromTable = table; 355 for ( int i = 0; i < tableRelations.length; i++ ) { 356 tableRelations[i] = parseTableRelation( (Element) relationElements.get( i ), fromTable ); 357 fromTable = tableRelations[i].getToTable(); 358 } 359 360 SimpleContent content = null; 361 362 // check type of content 363 Node node = XMLTools.getNode( contentElement, "deegreewfs:MappingField", nsContext ); 364 if ( node != null ) { 365 String defaultField = propertyName.getLocalName(); 366 content = parseMappingField( (Element) node, defaultField, table ); 367 } else { 368 node = XMLTools.getNode( contentElement, "deegreewfs:Constant", nsContext ); 369 if ( node != null ) { 370 content = parseConstantContent( (Element) node ); 371 } else { 372 node = XMLTools.getNode( contentElement, "deegreewfs:SQLFunctionCall", nsContext ); 373 if ( node != null ) { 374 content = parseSQLFunctionCall( (Element) node, table ); 375 } 376 } 377 } 378 379 pt = new MappedSimplePropertyType( propertyName, type, minOccurs, maxOccurs, isIdentityPart, 380 tableRelations, content ); 381 } catch ( XMLParsingException e ) { 382 throw new XMLSchemaException( "Error in definition of simple property '" + propertyName + "': " 383 + e.getMessage() ); 384 } 385 return pt; 386 } 387 388 /** 389 * Parses the given element as a "deegreewfs:Constant" element. 390 * 391 * @param element 392 * "deegreewfs:Constant" element 393 * @return java representation of element 394 * @throws XMLParsingException 395 */ 396 ConstantContent parseConstantContent( Element element ) 397 throws XMLParsingException { 398 String constant = XMLTools.getRequiredNodeAsString( element, "text()", nsContext ); 399 return new ConstantContent( constant ); 400 } 401 402 /** 403 * Parses the given "deegreewfs:SpecialContent" element. 404 * 405 * @param element 406 * "deegreewfs:SpecialContent" element 407 * @return java representation of element 408 * @throws XMLParsingException 409 */ 410 SpecialContent parseSpecialContent( Element element ) 411 throws XMLParsingException { 412 413 SpecialContent content = null; 414 String variable = XMLTools.getRequiredNodeAsString( element, "text()", nsContext ); 415 416 try { 417 content = new SpecialContent( variable ); 418 } catch ( Exception e ) { 419 String msg = Messages.getMessage( "DATASTORE_PARSING_SPECIAL_CONTENT", e.getMessage() ); 420 throw new XMLParsingException( msg ); 421 } 422 return content; 423 } 424 425 /** 426 * Parses the given element as a "deegreewfs:SQLFunctionCall" element. 427 * 428 * @param element 429 * "deegreewfs:SQLFunctionCall" element 430 * @param table 431 * @return java representation of element 432 * @throws XMLParsingException 433 */ 434 SQLFunctionCall parseSQLFunctionCall( Element element, String table ) 435 throws XMLParsingException { 436 437 String callString = XMLTools.getRequiredNodeAsString( element, "@call", nsContext ); 438 439 String typeName = XMLTools.getRequiredNodeAsString( element, "@type", nsContext ); 440 int typeCode = -1; 441 try { 442 typeCode = Types.getTypeCodeForSQLType( typeName ); 443 } catch ( UnknownTypeException e ) { 444 throw new XMLParsingException( "Invalid field type: " + e.getMessage(), e ); 445 } 446 447 List nl = XMLTools.getNodes( element, "deegreewfs:FunctionParam", nsContext ); 448 List<FunctionParam> functionParams = new ArrayList<FunctionParam>(); 449 Iterator iter = nl.iterator(); 450 while ( iter.hasNext() ) { 451 Element paramElement = (Element) iter.next(); 452 functionParams.add( parseFunctionParam( paramElement, table ) ); 453 } 454 455 // validate variable references 456 int maxVar = extractMaxVariableNumber( callString ); 457 if ( maxVar > functionParams.size() ) { 458 String msg = "Error in FunctionCall definition ('" + callString + "') - call string uses variable $" 459 + maxVar + ", but only has " + functionParams.size() + " FunctionParam elements."; 460 throw new XMLParsingException( msg ); 461 } 462 463 // check SRS for all function params (mapped geometry columns) 464 int internalSRS = -1; 465 for ( FunctionParam param : functionParams ) { 466 if ( param instanceof FieldContent ) { 467 MappingField mf = ( (FieldContent) param ).getField(); 468 if ( mf instanceof MappingGeometryField ) { 469 int thisSRS = ( (MappingGeometryField) mf ).getSRS(); 470 if ( internalSRS == -1 ) { 471 internalSRS = thisSRS; 472 } else { 473 if ( internalSRS != thisSRS ) { 474 String msg = Messages.getMessage( "DATASTORE_SQL_FUNCTION_CALL_INVALID_SRS", internalSRS, 475 thisSRS ); 476 throw new XMLParsingException( msg ); 477 } 478 } 479 } 480 } 481 } 482 483 // set SRS for all 'SpecialContent' function params 484 for ( FunctionParam param : functionParams ) { 485 if ( param instanceof SpecialContent ) { 486 ( (SpecialContent) param ).setSRS( internalSRS ); 487 } 488 } 489 490 return new SQLFunctionCall( callString, typeCode, functionParams ); 491 } 492 493 /** 494 * Extracts maximum variable numbers ('$i') used in the given call string. 495 * 496 * TODO: handle leading zeros 497 * 498 * @param callString 499 * @return maximum variable numbers used in the given call string 500 */ 501 private int extractMaxVariableNumber( String callString ) 502 throws XMLParsingException { 503 504 int maxVar = 0; 505 506 int foundAt = callString.indexOf( '$' ); 507 while ( foundAt != -1 ) { 508 foundAt++; 509 String varNumberString = ""; 510 while ( foundAt < callString.length() ) { 511 char numberChar = callString.charAt( foundAt++ ); 512 if ( numberChar >= '0' && numberChar <= '9' ) { 513 varNumberString += numberChar; 514 } else { 515 break; 516 } 517 } 518 519 if ( varNumberString.length() == 0 ) { 520 String msg = "Error in call attribute ('" + callString 521 + "') of FunctionCall definition - parameters must be specified by " 522 + "the '$' symbol, followed by a positive integer."; 523 throw new XMLParsingException( msg ); 524 } 525 526 try { 527 int varNo = Integer.parseInt( varNumberString ); 528 if ( varNo > maxVar ) { 529 maxVar = varNo; 530 } 531 if ( varNo == 0 ) { 532 String msg = "Error in call attribute ('" + callString 533 + "') of FunctionCall definition - $0 is not a valid variable " 534 + "identifier. Counting of variables starts with 1."; 535 throw new XMLParsingException( msg ); 536 } 537 } catch ( NumberFormatException e ) { 538 assert ( false ); 539 } 540 541 // find next '$' symbol 542 foundAt = callString.indexOf( '$', foundAt ); 543 } 544 return maxVar; 545 } 546 547 /** 548 * Parses the given "deegreewfs:FunctionParam" element. 549 * <p> 550 * Valid child elements: 551 * <ul> 552 * <li>deegreewfs:MappingField</li> 553 * <li>deegreewfs:ConstantContent</li> 554 * <li>deegreewfs:SpecialContent</li> 555 * </ul> 556 * 557 * @param element 558 * "deegreewfs:FunctionParam" element 559 * @param table 560 * @return java representation of element 561 * @throws XMLParsingException 562 */ 563 FunctionParam parseFunctionParam( Element element, String table ) 564 throws XMLParsingException { 565 566 FunctionParam param = null; 567 568 Element childElement = XMLTools.getFirstChildElement( element ); 569 if ( childElement == null || !childElement.getNamespaceURI().equals( CommonNamespaces.DEEGREEWFS.toString() ) ) { 570 String msg = Messages.getMessage( "DATASTORE_PARSING_SQL_FUNCTION_CALL" ); 571 throw new XMLParsingException( msg ); 572 } 573 574 if ( "MappingField".equals( childElement.getLocalName() ) ) { 575 // table relations 576 List relationElements = XMLTools.getNodes( element, "deegreewfs:Relation", nsContext ); 577 TableRelation[] tablePath = new TableRelation[relationElements.size()]; 578 String fromTable = table; 579 for ( int i = 0; i < tablePath.length; i++ ) { 580 tablePath[i] = parseTableRelation( (Element) relationElements.get( i ), fromTable ); 581 fromTable = tablePath[i].getToTable(); 582 } 583 // TODO do this a better way 584 String type = XMLTools.getRequiredNodeAsString( childElement, "@type", nsContext ); 585 MappingField field; 586 if ( "GEOMETRY".equals( type ) ) { 587 field = parseGeometryMappingField( childElement, null, fromTable ); 588 } else { 589 field = parseMappingField( childElement, null, fromTable ); 590 } 591 param = new FieldContent( field, tablePath ); 592 } else if ( "ConstantContent".equals( childElement.getLocalName() ) ) { 593 param = parseConstantContent( childElement ); 594 } else if ( "SpecialContent".equals( childElement.getLocalName() ) ) { 595 param = parseSpecialContent( childElement ); 596 } else { 597 String msg = Messages.getMessage( "DATASTORE_PARSING_SQL_FUNCTION_CALL" ); 598 throw new XMLParsingException( msg ); 599 } 600 601 return param; 602 } 603 604 /** 605 * Extracts the mapping information for a geometry property type from the given "xs:annotation" element. 606 * 607 * @param element 608 * "xs:annotation" element 609 * @param propertyName 610 * name of the property (local part is used as default field name if it is not specified explicitly in 611 * the "MappingField" element) 612 * @param typeName 613 * @param type 614 * @param minOccurs 615 * @param maxOccurs 616 * @param isIdentityPart 617 * @param table 618 * name of the table that is associated with the feature type that this property belongs to 619 * @return geometry property type with persistence information 620 * @throws XMLSchemaException 621 * if a syntactic or semantic error is found 622 * @throws UnknownCRSException 623 */ 624 MappedGeometryPropertyType parseMappedGeometryPropertyType( Element element, QualifiedName propertyName, 625 QualifiedName typeName, int type, int minOccurs, 626 int maxOccurs, boolean isIdentityPart, String table ) 627 throws XMLSchemaException, UnknownCRSException { 628 629 MappedGeometryPropertyType pt = null; 630 try { 631 Element contentElement = (Element) XMLTools.getRequiredNode( element, "xs:appinfo/deegreewfs:Content", 632 nsContext ); 633 // sanity check (common error) 634 Node typeNode = XMLTools.getNode( contentElement, "@type", nsContext ); 635 if ( typeNode != null ) { 636 throw new XMLParsingException( "Content element must not contain a type attribute." ); 637 } 638 639 URI srs = XMLTools.getNodeAsURI( element, "deegreewfs:SRS/text()", nsContext, this.defaultSRS ); 640 641 Element mfElement = (Element) XMLTools.getRequiredNode( contentElement, "deegreewfs:MappingField", 642 nsContext ); 643 String defaultField = propertyName.getLocalName(); 644 MappingGeometryField mappingField = parseGeometryMappingField( mfElement, defaultField, table ); 645 List relationElements = XMLTools.getNodes( contentElement, "deegreewfs:Relation", nsContext ); 646 TableRelation[] tableRelations = new TableRelation[relationElements.size()]; 647 String fromTable = table; 648 for ( int i = 0; i < tableRelations.length; i++ ) { 649 tableRelations[i] = parseTableRelation( (Element) relationElements.get( i ), fromTable ); 650 fromTable = tableRelations[i].getToTable(); 651 } 652 653 pt = new MappedGeometryPropertyType( propertyName, typeName, type, minOccurs, maxOccurs, isIdentityPart, 654 srs, tableRelations, mappingField ); 655 } catch ( XMLParsingException e ) { 656 throw new XMLSchemaException( "Error in definition of geometry property '" + propertyName + "': " 657 + e.getMessage() ); 658 } 659 return pt; 660 } 661 662 /** 663 * Extracts the mapping information for a feature property type from the given "xs:annotation" element. 664 * 665 * @param element 666 * "xs:annotation" element 667 * @param propertyName 668 * name of the property (local part is used as default field name if it is not specified explicitly in 669 * the "MappingField" element) 670 * @param minOccurs 671 * @param maxOccurs 672 * @param isIdentityPart 673 * @param table 674 * name of the table that is associated with the feature type that this property belongs to 675 * @param isReferenceType 676 * true, if this property is of type "gml:ReferenceType", false otherwise 677 * @return feature property type with persistence information 678 * @throws XMLSchemaException 679 * if a syntactic or semantic error is found 680 */ 681 MappedFeaturePropertyType parseMappedFeaturePropertyType( Element element, QualifiedName propertyName, 682 int minOccurs, int maxOccurs, boolean isIdentityPart, 683 String table, boolean isReferenceType ) 684 throws XMLSchemaException { 685 686 MappedFeaturePropertyType pt = null; 687 try { 688 Element contentElement = (Element) XMLTools.getRequiredNode( element, "xs:appinfo/deegreewfs:Content", 689 nsContext ); 690 691 // sanity check (common error) 692 Node mfNode = XMLTools.getNode( element, "deegreewfs:MappingField", nsContext ); 693 if ( mfNode != null ) { 694 throw new XMLParsingException( "Content element must not contain a MappingField element." ); 695 } 696 697 QualifiedName containedFT = parseQualifiedName( XMLTools.getRequiredNode( contentElement, "@type", 698 nsContext ) ); 699 MappedFeatureTypeReference containedFTRef = new MappedFeatureTypeReference( containedFT ); 700 701 List relationElements = XMLTools.getNodes( contentElement, "deegreewfs:Relation", nsContext ); 702 TableRelation[] tableRelations = new TableRelation[relationElements.size()]; 703 String fromTable = table; 704 for ( int i = 0; i < tableRelations.length; i++ ) { 705 tableRelations[i] = parseTableRelation( (Element) relationElements.get( i ), fromTable ); 706 fromTable = tableRelations[i].getToTable(); 707 } 708 pt = new MappedFeaturePropertyType( propertyName, Types.FEATURE_PROPERTY_NAME, Types.FEATURE, minOccurs, 709 maxOccurs, isIdentityPart, tableRelations, containedFTRef, 710 isReferenceType ); 711 } catch ( XMLParsingException e ) { 712 throw new XMLSchemaException( "Error in definition of feature property '" + propertyName + "': " 713 + e.getMessage() ); 714 } 715 return pt; 716 } 717 718 /** 719 * Parses the given 'MappingField' element. 720 * 721 * @param element 722 * 'MappingField' element 723 * @param defaultField 724 * if null, the element must have a 'field' attribute, otherwise the given value is used, if the element 725 * misses a 'field' attribute 726 * @param defaultTable 727 * if null, the element must have a 'table' attribute, otherwise the 'table' attribute must be left out 728 * or match the given value 729 * @return class representation of 'MappingField' element 730 * @throws XMLParsingException 731 * if a syntactic or semantic error is found 732 */ 733 private MappingField parseMappingField( Element element, String defaultField, String defaultTable ) 734 throws XMLParsingException { 735 736 MappingField mappingField = null; 737 738 String field = null; 739 if ( defaultField == null ) { 740 field = XMLTools.getRequiredNodeAsString( element, "@field", nsContext ); 741 } else { 742 field = XMLTools.getNodeAsString( element, "@field", nsContext, defaultField ); 743 } 744 745 String typeName = XMLTools.getRequiredNodeAsString( element, "@type", nsContext ); 746 int typeCode = -1; 747 try { 748 typeCode = Types.getTypeCodeForSQLType( typeName ); 749 } catch ( UnknownTypeException e ) { 750 throw new XMLParsingException( "Invalid field type: " + e.getMessage(), e ); 751 } 752 753 String table = null; 754 if ( defaultTable == null ) { 755 // if table is unspecified, this is resolved later (in MappedGMLSchema) 756 // TODO clean this up 757 table = XMLTools.getNodeAsString( element, "@table", nsContext, null ); 758 } else { 759 table = XMLTools.getNodeAsString( element, "@table", nsContext, defaultTable ); 760 if ( !table.equals( defaultTable ) ) { 761 throw new XMLParsingException( "Specified 'table' attribute ('" + table 762 + "') in 'MappingField' element is inconsistent; leave out or use '" 763 + defaultTable + "' instead." ); 764 } 765 } 766 767 boolean auto = XMLTools.getNodeAsBoolean( element, "@auto", nsContext, false ); 768 mappingField = new MappingField( table, field.toUpperCase(), typeCode, auto ); 769 770 return mappingField; 771 } 772 773 /** 774 * Parses the given 'MappingField' element. 775 * 776 * @param element 777 * 'MappingField' element 778 * @param defaultField 779 * if null, the element must have a 'field' attribute, otherwise the given value is used, if the element 780 * misses a 'field' attribute 781 * @param defaultTable 782 * if null, the element must have a 'table' attribute, otherwise the 'table' attribute must be left out 783 * or match the given value 784 * @return class representation of 'MappingField' element 785 * @throws XMLParsingException 786 * if a syntactic or semantic error is found 787 */ 788 private MappingGeometryField parseGeometryMappingField( Element element, String defaultField, String defaultTable ) 789 throws XMLParsingException { 790 791 MappingGeometryField mappingField = null; 792 793 String field = null; 794 if ( defaultField == null ) { 795 field = XMLTools.getRequiredNodeAsString( element, "@field", nsContext ); 796 } else { 797 field = XMLTools.getNodeAsString( element, "@field", nsContext, defaultField ); 798 } 799 800 String typeName = XMLTools.getRequiredNodeAsString( element, "@type", nsContext ); 801 int typeCode = Types.OTHER; 802 if ( !( "GEOMETRY".equals( typeName ) ) ) { 803 try { 804 typeCode = Types.getTypeCodeForSQLType( typeName ); 805 } catch ( UnknownTypeException e ) { 806 throw new XMLParsingException( "Invalid field type: " + e.getMessage(), e ); 807 } 808 } 809 810 String table = null; 811 if ( defaultTable == null ) { 812 // if table is unspecified, this is resolved later (in MappedGMLSchema) 813 // TODO clean this up 814 table = XMLTools.getNodeAsString( element, "@table", nsContext, null ); 815 } else { 816 table = XMLTools.getNodeAsString( element, "@table", nsContext, defaultTable ); 817 if ( !table.equals( defaultTable ) ) { 818 String msg = "Specified 'table' attribute ('" + table 819 + "') in 'MappingField' element is inconsistent; leave out or use '" + defaultTable 820 + "' instead."; 821 throw new XMLParsingException( msg ); 822 } 823 } 824 825 int internalSrs = XMLTools.getNodeAsInt( element, "@srs", nsContext, -1 ); 826 mappingField = new MappingGeometryField( table, field, typeCode, internalSrs ); 827 return mappingField; 828 } 829 830 /** 831 * Parses the given 'gmlId' element. 832 * 833 * @param element 834 * 'gmlId' element 835 * @param table 836 * the associated table of the FeatureType 837 * @return class representation of 'gmlId' element 838 * @throws XMLParsingException 839 * if a syntactic or semantic error is found 840 */ 841 private MappedGMLId parseGMLIdDefinition( Element element, String table ) 842 throws XMLParsingException { 843 String prefix = XMLTools.getNodeAsString( element, "@prefix", nsContext, "" ); 844 String separator = XMLTools.getNodeAsString( element, "@separator", nsContext, "" ); 845 846 List mappingFieldElementList = XMLTools.getRequiredNodes( element, "deegreewfs:MappingField", nsContext ); 847 MappingField[] mappingFields = new MappingField[mappingFieldElementList.size()]; 848 for ( int i = 0; i < mappingFields.length; i++ ) { 849 Element mappingFieldElement = (Element) mappingFieldElementList.get( i ); 850 mappingFields[i] = parseMappingField( mappingFieldElement, 851 XMLTools.getRequiredNodeAsString( mappingFieldElement, "@field", 852 nsContext ), table ); 853 } 854 855 IDPART_INFO idpart_info = IDPART_INFO.noIDInfo; 856 String identityPart = XMLTools.getNodeAsString( element, "deegreewfs:IdentityPart/text()", nsContext, null ); 857 if ( identityPart != null ) { 858 if ( "false".equals( identityPart ) ) { 859 idpart_info = IDPART_INFO.notIDPart; 860 } else { 861 idpart_info = IDPART_INFO.isIDPart; 862 } 863 } 864 865 IdGenerator idGenerator = null; 866 Element idGeneratorElement = (Element) XMLTools.getNode( element, "deegreewfs:IdGenerator", nsContext ); 867 if ( idGeneratorElement != null ) { 868 idGenerator = parseGMLIdGenerator( idGeneratorElement ); 869 } else { 870 idGenerator = IdGenerator.getInstance( IdGenerator.TYPE_UUID, new Properties() ); 871 } 872 return new MappedGMLId( prefix, separator, mappingFields, idGenerator, idpart_info ); 873 } 874 875 /** 876 * Parses the given 'IdGenerator' element. 877 * 878 * @param element 879 * 'IdGenerator' element 880 * @return object representation of 'IdGenerator' element 881 * @throws XMLParsingException 882 * if a syntactic or semantic error is found 883 */ 884 private IdGenerator parseGMLIdGenerator( Element element ) 885 throws XMLParsingException { 886 String type = XMLTools.getRequiredNodeAsString( element, "@type", nsContext ); 887 Properties params = new Properties(); 888 List paramElementList = XMLTools.getNodes( element, "deegreewfs:param", nsContext ); 889 Iterator iter = paramElementList.iterator(); 890 while ( iter.hasNext() ) { 891 Element paramElement = (Element) iter.next(); 892 String name = XMLTools.getRequiredNodeAsString( paramElement, "@name", nsContext ); 893 String value = XMLTools.getRequiredNodeAsString( paramElement, "text()", nsContext ); 894 params.setProperty( name, value ); 895 } 896 IdGenerator idGenerator = IdGenerator.getInstance( type, params ); 897 return idGenerator; 898 } 899 900 private TableRelation parseTableRelation( Element element, String fromTable ) 901 throws XMLParsingException { 902 List fromMappingElements = XMLTools.getRequiredNodes( element, "deegreewfs:From/deegreewfs:MappingField", 903 nsContext ); 904 List toMappingElements = XMLTools.getRequiredNodes( element, "deegreewfs:To/deegreewfs:MappingField", nsContext ); 905 if ( fromMappingElements.size() != toMappingElements.size() ) { 906 throw new XMLParsingException( "Error in 'Relation' element: number of 'MappingField' elements " 907 + "below 'From' and 'To' elements do not match." ); 908 } 909 FK_INFO fkInfo = FK_INFO.noFKInfo; 910 boolean fromIsFK = XMLTools.getNodeAsBoolean( element, "deegreewfs:From/@fk", nsContext, false ); 911 boolean toIsFK = XMLTools.getNodeAsBoolean( element, "deegreewfs:To/@fk", nsContext, false ); 912 if ( fromIsFK && toIsFK ) { 913 throw new XMLParsingException( "Error in 'Relation' element: either 'To' or 'From' can " 914 + "have a 'fk' attribute with value 'true', but not both." ); 915 } 916 if ( fromIsFK ) { 917 fkInfo = FK_INFO.fkIsFromField; 918 } 919 if ( toIsFK ) { 920 fkInfo = FK_INFO.fkIsToField; 921 } 922 MappingField[] fromMappingFields = new MappingField[fromMappingElements.size()]; 923 MappingField[] toMappingFields = new MappingField[fromMappingFields.length]; 924 for ( int i = 0; i < fromMappingFields.length; i++ ) { 925 fromMappingFields[i] = parseMappingField( (Element) fromMappingElements.get( i ), null, fromTable ); 926 toMappingFields[i] = parseMappingField( (Element) toMappingElements.get( i ), null, null ); 927 } 928 929 // parse id generator 930 // TODO sanity checks 931 IdGenerator idGenerator = null; 932 if ( fromIsFK ) { 933 Element idGeneratorElement = (Element) XMLTools.getNode( element, "deegreewfs:To/deegreewfs:IdGenerator", 934 nsContext ); 935 if ( idGeneratorElement != null ) { 936 idGenerator = parseGMLIdGenerator( idGeneratorElement ); 937 } else { 938 idGenerator = IdGenerator.getInstance( IdGenerator.TYPE_UUID, new Properties() ); 939 } 940 } else { 941 Element idGeneratorElement = (Element) XMLTools.getNode( element, "deegreewfs:From/deegreewfs:IdGenerator", 942 nsContext ); 943 if ( idGeneratorElement != null ) { 944 idGenerator = parseGMLIdGenerator( idGeneratorElement ); 945 } else { 946 idGenerator = IdGenerator.getInstance( IdGenerator.TYPE_UUID, new Properties() ); 947 } 948 } 949 950 return new TableRelation( fromMappingFields, toMappingFields, fkInfo, idGenerator ); 951 } 952 953 /** 954 * Returns the value of the "deegreewfs:visible" element. 955 * 956 * @param annotationElement 957 * @return -1 if it is not present, 0 if it is "false", 1 if it is "true" 958 * @throws XMLParsingException 959 */ 960 public int parseVisible( Element annotationElement ) 961 throws XMLParsingException { 962 int visibleCode = -1; 963 String visible = XMLTools.getNodeAsString( annotationElement, "xs:appinfo/deegreewfs:visible/text()", 964 nsContext, null ); 965 if ( visible != null ) { 966 if ( "false".equals( visible ) ) { 967 visibleCode = 0; 968 } else { 969 visibleCode = 1; 970 } 971 } 972 return visibleCode; 973 } 974 975 /** 976 * Parses the 'updatable' status of the given feature type annotation element. 977 * 978 * @param annotationElement 979 * @return true, if update transactions may be performed on the feature type, false otherwise 980 * @throws XMLParsingException 981 */ 982 public boolean parseIsUpdatable( Element annotationElement ) 983 throws XMLParsingException { 984 return XMLTools.getNodeAsBoolean( annotationElement, "xs:appinfo/deegreewfs:transaction/@update", nsContext, 985 false ); 986 } 987 988 /** 989 * Parses the 'deletable' status of the given feature type annotation element. 990 * 991 * @param annotationElement 992 * @return true, if delete transactions may be performed on the feature type, false otherwise 993 * @throws XMLParsingException 994 */ 995 public boolean parseIsDeletable( Element annotationElement ) 996 throws XMLParsingException { 997 return XMLTools.getNodeAsBoolean( annotationElement, "xs:appinfo/deegreewfs:transaction/@delete", nsContext, 998 false ); 999 } 1000 1001 /** 1002 * Parses the 'insertable' status of the given feature type annotation element. 1003 * 1004 * @param annotationElement 1005 * @return true, if insert transactions may be performed on the feature type, false otherwise 1006 * @throws XMLParsingException 1007 */ 1008 public boolean parseIsInsertable( Element annotationElement ) 1009 throws XMLParsingException { 1010 return XMLTools.getNodeAsBoolean( annotationElement, "xs:appinfo/deegreewfs:transaction/@insert", nsContext, 1011 false ); 1012 } 1013 1014 /** 1015 * Returns the value of the "deegreewfs:IdentityPart" element. 1016 * 1017 * @param annotationElement 1018 * @return -1 if it is not present, 0 if it is "false", 1 if it is "true" 1019 * @throws XMLParsingException 1020 */ 1021 public int parseIdentityPart( Element annotationElement ) 1022 throws XMLParsingException { 1023 int identityCode = -1; 1024 String identityPart = XMLTools.getNodeAsString( annotationElement, "xs:appinfo/deegreewfs:IdentityPart/text()", 1025 nsContext, null ); 1026 if ( identityPart != null ) { 1027 if ( "false".equals( identityPart ) ) { 1028 identityCode = 0; 1029 } else { 1030 identityCode = 1; 1031 } 1032 } 1033 return identityCode; 1034 } 1035 }