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