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 }