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