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 }