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    }