001    //$HeadURL: svn+ssh://developername@svn.wald.intevation.org/deegree/base/trunk/src/org/deegree/ogcwebservices/csw/discovery/GetRecordsDocument.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     Aennchenstr. 19
030     53115 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     ---------------------------------------------------------------------------*/
044    
045    package org.deegree.ogcwebservices.csw.discovery;
046    
047    import java.io.IOException;
048    import java.net.URI;
049    import java.net.URISyntaxException;
050    import java.net.URL;
051    import java.util.ArrayList;
052    import java.util.HashMap;
053    import java.util.List;
054    import java.util.Map;
055    
056    import org.deegree.datatypes.QualifiedName;
057    import org.deegree.framework.log.ILogger;
058    import org.deegree.framework.log.LoggerFactory;
059    import org.deegree.framework.util.StringTools;
060    import org.deegree.framework.xml.XMLParsingException;
061    import org.deegree.framework.xml.XMLTools;
062    import org.deegree.i18n.Messages;
063    import org.deegree.model.filterencoding.AbstractFilter;
064    import org.deegree.model.filterencoding.AbstractOperation;
065    import org.deegree.model.filterencoding.ComplexFilter;
066    import org.deegree.model.filterencoding.Expression;
067    import org.deegree.model.filterencoding.Filter;
068    import org.deegree.model.filterencoding.FilterConstructionException;
069    import org.deegree.model.filterencoding.LogicalOperation;
070    import org.deegree.model.filterencoding.Operation;
071    import org.deegree.model.filterencoding.PropertyIsBetweenOperation;
072    import org.deegree.model.filterencoding.PropertyIsCOMPOperation;
073    import org.deegree.model.filterencoding.PropertyIsInstanceOfOperation;
074    import org.deegree.model.filterencoding.PropertyIsLikeOperation;
075    import org.deegree.model.filterencoding.PropertyIsNullOperation;
076    import org.deegree.model.filterencoding.PropertyName;
077    import org.deegree.model.filterencoding.SpatialOperation;
078    import org.deegree.ogcbase.CommonNamespaces;
079    import org.deegree.ogcbase.ExceptionCode;
080    import org.deegree.ogcbase.PropertyPath;
081    import org.deegree.ogcbase.PropertyPathFactory;
082    import org.deegree.ogcbase.SortProperty;
083    import org.deegree.ogcwebservices.InvalidParameterValueException;
084    import org.deegree.ogcwebservices.MissingParameterValueException;
085    import org.deegree.ogcwebservices.OGCWebServiceException;
086    import org.deegree.ogcwebservices.OperationNotSupportedException;
087    import org.deegree.ogcwebservices.csw.AbstractCSWRequestDocument;
088    import org.deegree.ogcwebservices.csw.discovery.GetRecords.RESULT_TYPE;
089    import org.w3c.dom.Element;
090    import org.w3c.dom.Node;
091    import org.w3c.dom.NodeList;
092    import org.xml.sax.SAXException;
093    
094    /**
095     * Represents an XML GetRecords document of an OGC CSW 2.0.0 and 2.0.1 compliant service.
096     * 
097     * @author <a href="mailto:tfr@users.sourceforge.net">Torsten Friebe </a>
098     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a>
099     * @author last edited by: $Author: apoth $
100     * 
101     * @version $Revision: 9307 $, $Date: 2007-12-21 08:37:43 +0100 (Fr, 21 Dez 2007) $
102     */
103    public class GetRecordsDocument extends AbstractCSWRequestDocument {
104    
105        private static final long serialVersionUID = 2796229558893029054L;
106    
107        private static final ILogger LOG = LoggerFactory.getLogger( GetRecordsDocument.class );
108    
109        private static final String XML_TEMPLATE = "GetRecordsTemplate.xml";
110    
111        /**
112         * Extracts a <code>GetRecords</code> representation of this object.
113         * 
114         * @param id
115         *            unique ID of the request
116         * @return GetRecords representation of this object
117         * @throws MissingParameterValueException
118         * @throws InvalidParameterValueException
119         * @throws OperationNotSupportedException
120         *             if an CqlText constrained is requested
121         * @throws OGCWebServiceException
122         *             if something else went wrong
123         */
124        public GetRecords parse( String id )
125                                throws OGCWebServiceException {
126    
127            // '<csw:GetRecords>'-element (required)
128            try {
129                Element contextNode = (Element) XMLTools.getRequiredNode( this.getRootElement(), "self::csw:GetRecords",
130                                                                          nsContext );
131                // 'service'-attribute (optional, must be CSW)
132                String service = XMLTools.getNodeAsString( contextNode, "@service", nsContext, "CSW" );
133                if ( !"CSW".equals( service ) ) {
134                    throw new OGCWebServiceException( "GetRecordsDocument",
135                                                      Messages.getMessage( "CSW_INVALID_SERVICE_PARAM" ),
136                                                      ExceptionCode.INVALIDPARAMETERVALUE );
137                }
138    
139                String defaultVersion = GetRecords.DEFAULT_VERSION;
140                boolean isEBRIM = ( contextNode.getOwnerDocument().lookupPrefix(
141                                                                                 CommonNamespaces.OASIS_EBRIMNS.toASCIIString() ) != null );
142                if ( !isEBRIM ) {
143                    isEBRIM = isEbrimDefined( contextNode );
144                }
145                LOG.logDebug( "GetRecordsDocument: For the namespaceDefinition of the ebrim catalogue, the value is: "
146                              + isEBRIM );
147                if ( isEBRIM ) {
148                    defaultVersion = "2.0.1";
149                }
150    
151                // 'version'-attribute (optional)
152                String version = XMLTools.getNodeAsString( contextNode, "@version", nsContext, defaultVersion );
153                if ( !( GetRecords.DEFAULT_VERSION.equals( version ) || "2.0.1".equals( version ) ) ) {
154                    throw new OGCWebServiceException( "GetRecordsDocument",
155                                                      Messages.getMessage( "CSW_NOT_SUPPORTED_VERSION",
156                                                                           GetRecords.DEFAULT_VERSION, "2.0.1", version ),
157                                                      ExceptionCode.INVALIDPARAMETERVALUE );
158                }
159    
160                // 'requestId'-attribute (optional)
161                String requestId = XMLTools.getNodeAsString( contextNode, "@requestId", nsContext, id );
162    
163                // 'resultType'-attribute
164                // type="csw:ResultType" use="optional" default="hits"
165                String resultTypeString = XMLTools.getNodeAsString( contextNode, "@resultType", nsContext,
166                                                                    GetRecords.RESULT_TYPE_STRING_HITS );
167                RESULT_TYPE resultType = RESULT_TYPE.RESULTS;
168                if ( GetRecords.RESULT_TYPE_STRING_HITS.equalsIgnoreCase( resultTypeString ) ) {
169                    resultType = RESULT_TYPE.HITS;
170                } else if ( GetRecords.RESULT_TYPE_STRING_RESULTS.equalsIgnoreCase( resultTypeString ) ) {
171                    resultType = RESULT_TYPE.RESULTS;
172                } else if ( GetRecords.RESULT_TYPE_STRING_VALIDATE.equalsIgnoreCase( resultTypeString ) ) {
173                    resultType = RESULT_TYPE.VALIDATE;
174                } else {
175                    throw new OGCWebServiceException( Messages.getMessage( "CSW_INVALID_RESULTTYPE", resultTypeString,
176                                                                           GetRecords.RESULT_TYPE_STRING_HITS,
177                                                                           GetRecords.RESULT_TYPE_STRING_RESULTS,
178                                                                           GetRecords.RESULT_TYPE_STRING_VALIDATE ),
179                                                      ExceptionCode.INVALIDPARAMETERVALUE );
180                }
181    
182                // 'outputFormat'-attribute
183                // type="xsd:string" use="optional" default="text/xml"
184                String outputFormat = XMLTools.getNodeAsString( contextNode, "@outputFormat", nsContext,
185                                                                GetRecords.DEFAULT_OUTPUTFORMAT );
186    
187                String defaultOutputSchema = GetRecords.DEFAULT_OUTPUTSCHEMA;
188                if ( isEBRIM ) {
189                    defaultOutputSchema = CommonNamespaces.OASIS_EBRIMNS.toASCIIString();
190                }
191                // 'outputSchema'-attribute
192                // type="xsd:anyURI" use="optional" default="OGCCORE"
193                String outputSchema = XMLTools.getNodeAsString( contextNode, "@outputSchema", nsContext,
194                                                                defaultOutputSchema );
195    
196                // 'startPosition'-attribute
197                // type="xsd:positiveInteger" use="optional" default="1"
198                int startPosition = XMLTools.getNodeAsInt( contextNode, "@startPosition", nsContext,
199                                                           GetRecords.DEFAULT_STARTPOSITION );
200                if ( startPosition < 1 ) {
201                    throw new OGCWebServiceException( Messages.getMessage( "CSW_INVALID_STARTPOSITION",
202                                                                           new Integer( startPosition ) ),
203                                                      ExceptionCode.INVALIDPARAMETERVALUE );
204                }
205    
206                // 'maxRecords'-attribute
207                // type="xsd:nonNegativeInteger" use="optional" default="10"
208                int maxRecords = XMLTools.getNodeAsInt( contextNode, "@maxRecords", nsContext,
209                                                        GetRecords.DEFAULT_MAX_RECORDS );
210    
211                // '<csw:DistributedSearch>'-element (optional)
212                Node distributedSearchElement = XMLTools.getNode( contextNode, "csw:DistributedSearch", nsContext );
213                int hopCount = GetRecords.DEFAULT_HOPCOUNT;
214                if ( distributedSearchElement != null ) {
215                    hopCount = XMLTools.getNodeAsInt( contextNode, "@hopCount", nsContext, GetRecords.DEFAULT_HOPCOUNT );
216                }
217    
218                // '<csw:ResponseHandler>'-elements (optional)
219                String rHandler = XMLTools.getNodeAsString( contextNode, "csw:ResponseHandler", nsContext, null );
220                URI responseHandler = null;
221                if ( rHandler != null ) {
222                    try {
223                        responseHandler = new URI( rHandler );
224                    } catch ( URISyntaxException e ) {
225                        throw new OGCWebServiceException( Messages.getMessage( "CSW_INVALID_RESPONSE_HANDLER", rHandler ),
226                                                          ExceptionCode.INVALIDPARAMETERVALUE );
227                    }
228                    LOG.logWarning( Messages.getMessage( "CSW_NO_REPONSE_HANDLER_IMPLEMENTATION" ) );
229    
230                }
231    
232                // '<csw:Query>'-elements (required)
233                // List nl = XMLTools.getRequiredNodes( contextNode, "csw:Query", nsContext );
234                Element queryNode = (Element) XMLTools.getRequiredNode( contextNode, "csw:Query", nsContext );
235    
236                Map<String, QualifiedName> declaredVariables = new HashMap<String, QualifiedName>();
237                List<QualifiedName> queryTypeNames = new ArrayList<QualifiedName>();
238    
239                // 'typeName'-attribute use="required"
240                String tNames = XMLTools.getRequiredNodeAsString( queryNode, "@typeNames", nsContext );
241                String[] simpleTypeNames = tNames.split( " " );
242                // only bind the prefixes to namespaces if the version is 2.0.0
243                boolean bindTypeNamesToNS = !GetRecords.DEFAULT_VERSION.equals( version );
244                // Find any variables
245                for ( String typeName : simpleTypeNames ) {
246                    findVariablesInTypeName( typeName, queryNode, queryTypeNames, declaredVariables, bindTypeNamesToNS );
247                }
248    
249                // '<csw:ElementSetName>'-element (optional)
250                Element elementSetNameElement = (Element) XMLTools.getNode( queryNode, "csw:ElementSetName", nsContext );
251                String elementSetName = null;
252                List<QualifiedName> elementSetNameTypeNames = null;
253                Map<String, QualifiedName> elementSetNameVariables = null;
254                List<PropertyPath> elementNames = null;
255                // choice construct
256                if ( elementSetNameElement != null ) {
257                    // must contain one of the values 'brief', 'summary' or
258                    // 'full'
259                    elementSetName = XMLTools.getRequiredNodeAsString( elementSetNameElement, "text()", nsContext,
260                                                                       new String[] { "brief", "summary", "full" } );
261                    tNames = elementSetNameElement.getAttribute( "typeNames" );
262                    if ( tNames != null ) {
263                        String[] esnTypeNames = tNames.split( " " );
264                        elementSetNameVariables = new HashMap<String, QualifiedName>();
265                        elementSetNameTypeNames = new ArrayList<QualifiedName>();
266                        for ( String tn : esnTypeNames ) {
267                            if ( tn.trim().startsWith( "$" ) ) {
268                                String tmpVar = tn.trim().substring( 1 );
269                                if ( !declaredVariables.containsKey( tmpVar ) ) {
270                                    throw new OGCWebServiceException(
271                                                                      Messages.getMessage(
272                                                                                           "CSW_ELEMENT_SET_NAME_TYPENAME_ALIAS",
273                                                                                           tmpVar ),
274                                                                      ExceptionCode.INVALIDPARAMETERVALUE );
275                                }
276                                elementSetNameVariables.put( tmpVar, declaredVariables.get( tmpVar ) );
277                            } else {
278                                QualifiedName qName = parseQNameFromString( tn.trim(), elementSetNameElement,
279                                                                            bindTypeNamesToNS );
280                                elementSetNameTypeNames.add( qName );
281                            }
282                        }
283                    }
284    
285                } else {
286                    // '<csw:ElementName>'-element (required, if no
287                    // '<csw:ElementSetName>' is given)
288                    List elementNameList = XMLTools.getNodes( queryNode, "csw:ElementName", nsContext );
289                    if ( elementNameList.size() == 0 ) {
290                        throw new XMLParsingException( Messages.getMessage( "CSW_MISSING_QUERY_ELEMENT(SET)NAME" ) );
291                    }
292                    for ( Object en : elementNameList ) {
293                        Node n = (Node) en;
294                        QualifiedName elementName = XMLTools.getNodeAsQualifiedName( n, "text()", nsContext, null );
295                        if ( elementName != null ) {
296                            elementNames.add( PropertyPathFactory.createPropertyPath( elementName ) );
297                        }
298                    }
299    
300                }
301    
302                // '<csw:Constraint>'-element (optional)
303                Element constraintElement = (Element) XMLTools.getNode( queryNode, "csw:Constraint", nsContext );
304                Filter constraint = null;
305                if ( constraintElement != null ) {
306                    String ver = XMLTools.getRequiredNodeAsString( constraintElement, "@version", nsContext );
307                    if ( !"1.0.0".equals( ver ) && !"1.1.0".equals( ver ) ) {
308                        throw new OGCWebServiceException( Messages.getMessage( "CSW_INVALID_CONSTRAINT_VERSION", ver ),
309                                                          ExceptionCode.INVALIDPARAMETERVALUE );
310                    }
311                    Node filterElement = XMLTools.getNode( constraintElement, "ogc:Filter", nsContext );
312                    if ( filterElement != null ) {
313                        try {
314                            constraint = AbstractFilter.buildFromDOM( (Element) filterElement, false );
315                        } catch ( FilterConstructionException fce ) {
316                            throw new OGCWebServiceException( Messages.getMessage( "CSW_INVALID_CONSTRAINT_CONTENT",
317                                                                                   fce.getMessage() ),
318                                                              ExceptionCode.INVALIDPARAMETERVALUE );
319                        }
320                    } else {
321                        String cqlText = XMLTools.getNodeAsString( constraintElement, "csw:CqlText", nsContext, null );
322                        if ( cqlText == null ) {
323                            throw new OGCWebServiceException( Messages.getMessage( "CSW_CQL_NOR_FILTER" ),
324                                                              ExceptionCode.INVALIDPARAMETERVALUE );
325                        }
326    
327                        throw new OGCWebServiceException( Messages.getMessage( "CSW_NO_CQL_IMPLEMENTATION" ),
328                                                          ExceptionCode.OPERATIONNOTSUPPORTED );
329                    }
330                }
331                // find undeclared referenced variables used in the filter element.
332                if ( constraint instanceof ComplexFilter ) {
333                    checkReferencedVariables( (ComplexFilter) constraint, declaredVariables );
334                }
335    
336                // '<ogc:SortBy>'-element (optional)
337                Node sortByElement = XMLTools.getNode( queryNode, "ogc:SortBy", nsContext );
338                SortProperty[] sortProperties = null;
339                if ( sortByElement != null ) {
340                    List sortPropertyList = XMLTools.getNodes( sortByElement, "ogc:SortProperty", nsContext );
341                    if ( sortPropertyList.size() == 0 ) {
342                        throw new OGCWebServiceException( Messages.getMessage( "CSW_NO_SORTPROPERTY_LIST" ),
343                                                          ExceptionCode.INVALIDPARAMETERVALUE );
344    
345                    }
346                    sortProperties = new SortProperty[sortPropertyList.size()];
347                    for ( int j = 0; j < sortPropertyList.size(); j++ ) {
348                        sortProperties[j] = SortProperty.create( (Element) sortPropertyList.get( j ) );
349                    }
350                }
351    
352                Query query = new Query( elementSetName, elementSetNameTypeNames, elementSetNameVariables, elementNames,
353                                         constraint, sortProperties, queryTypeNames, declaredVariables );
354    
355                // in the future the vendorSpecificParameters
356                Map<String, String> vendorSpecificParameters = parseDRMParams( this.getRootElement() );
357                return new GetRecords( requestId, version, vendorSpecificParameters, null, resultType, outputFormat,
358                                       outputSchema, startPosition, maxRecords, hopCount, responseHandler, query );
359            } catch ( XMLParsingException xmlpe ) {
360                LOG.logError( "CatalogGetRecords", xmlpe );
361                throw new OGCWebServiceException( xmlpe.getMessage(), ExceptionCode.INVALIDPARAMETERVALUE );
362            } catch ( URISyntaxException urise ) {
363                LOG.logError( "CatalogGetRecords", urise );
364                throw new OGCWebServiceException( urise.getMessage(), ExceptionCode.INVALIDPARAMETERVALUE );
365            }
366        }
367    
368        /**
369         * @param contextNode
370         * @return true if the namespace "urn:oasis:names:tc:ebxml- regrep:xsd:rim:3.0" was found in one
371         *         of the nodes of the dom-tree.
372         */
373        protected boolean isEbrimDefined( Node contextNode ) {
374    
375            boolean isEbRim = contextNode.lookupPrefix( CommonNamespaces.OASIS_EBRIMNS.toASCIIString() ) != null;
376            if ( !isEbRim ) {
377                NodeList nl = contextNode.getChildNodes();
378                for ( int i = 0; i < nl.getLength(); ++i ) {
379                    isEbRim = isEbrimDefined( nl.item( i ) );
380                    if ( isEbRim ) {
381                        return true;
382                    }
383                }
384            }
385            return isEbRim;
386        }
387    
388        /**
389         * Helper method to find any declared variables in given Query/@typeNames
390         * 
391         * @param typeName
392         *            the type name to test
393         * @param queryNode
394         *            the querynode (used to find a given prefix)
395         * @param typeNames
396         *            a list to save the typeName (as QualifiedNames) in
397         * @param variables
398         *            a Map containing the vars/QualifiedName mappings
399         * @param bindTypeNameToNS
400         *            if the namespaces should be bounded to the typeNames
401         * @throws URISyntaxException
402         *             if the prefix is not bound to a namespace
403         * @throws OGCWebServiceException
404         *             if a variable name is unambiguous
405         */
406        public void findVariablesInTypeName( String typeName, Node queryNode, List<QualifiedName> typeNames,
407                                             Map<String, QualifiedName> variables, boolean bindTypeNameToNS )
408                                throws OGCWebServiceException, URISyntaxException {
409            LOG.logDebug( "testing for variables in typeName: " + typeName );
410            int variableIndex = typeName.lastIndexOf( '=' );
411            String tmpTypeName = typeName;
412            if ( variableIndex != -1 ) {
413                // find the typeNames
414                tmpTypeName = typeName.substring( 0, variableIndex ).trim();
415                LOG.logDebug( "typeName contains variables" );
416            }
417    
418            // creating the qualified name
419            QualifiedName qName = parseQNameFromString( tmpTypeName, queryNode, bindTypeNameToNS );
420            typeNames.add( qName );
421            if ( variableIndex != -1 ) {
422                if ( ( variableIndex + 1 ) < typeName.length() ) {
423                    // find the variables which should be referenced with the $-sign
424                    String allVars = typeName.substring( variableIndex + 1 );
425                    String[] vars = allVars.split( "," );
426                    for ( String var : vars ) {
427                        LOG.logDebug( "found var: " + var );
428                        if ( variables.put( var.trim(), qName ) != null ) {
429                            String out = Messages.getMessage( "CSW_AMBIGUOUS_VARIABLE_DEF", var.trim() );
430                            throw new OGCWebServiceException( "GetRecords", out, ExceptionCode.INVALIDPARAMETERVALUE );
431                        }
432                    }
433                }
434            }
435        }
436    
437        /**
438         * @param typeName
439         *            to be transformed to a QName
440         * @param queryNode
441         *            needed to get the namespace
442         * @param bindTypeNameToNS
443         *            if true the namespace will be bound to the qualified name
444         * @return a QualifiedName representing the typeName
445         * @throws URISyntaxException
446         */
447        public QualifiedName parseQNameFromString( String typeName, Node queryNode, boolean bindTypeNameToNS )
448                                throws URISyntaxException {
449            int prefixIndex = typeName.indexOf( ':' );
450            String preFix = null;
451            URI nameSpace = null;
452            String localName = typeName;
453            if ( prefixIndex != -1 ) {
454                preFix = typeName.substring( 0, prefixIndex ).trim();
455                if ( bindTypeNameToNS ) {
456                    LOG.logDebug( "Trying to find namespace binding for the prefix: " + preFix + " on node queryNode: "
457                                  + queryNode.getNodeName() );
458                    nameSpace = XMLTools.getNamespaceForPrefix( preFix, queryNode );
459                } else {
460                    LOG.logDebug( "Not binding namespaces for the prefix: " + preFix + " on node queryNode: "
461                                  + queryNode.getNodeName() + " because the version of the GetRecordsRequest is not 2.0.2" );
462                }
463                // for version 2.0.0 no namespace checkin is required following versions should check if
464                // the returned namespace is null.
465                if ( ( prefixIndex + 1 ) < typeName.length() ) {
466                    localName = typeName.substring( prefixIndex + 1 ).trim();
467                } else {
468                    localName = typeName.substring( prefixIndex ).trim();
469                }
470            }
471            LOG.logDebug( "found prefix: " + preFix );
472            LOG.logDebug( "found localName: " + localName );
473            LOG.logDebug( "found namespace: " + nameSpace );
474            return new QualifiedName( preFix, localName, nameSpace );
475        }
476    
477        /**
478         * Iterates over the Operations of a complexfilter to find if non declared variables are used.
479         * 
480         * @param constraint
481         * @param variables
482         * @throws OGCWebServiceException
483         */
484        protected void checkReferencedVariables( ComplexFilter constraint, Map<String, QualifiedName> variables )
485                                throws OGCWebServiceException {
486            AbstractOperation topOperation = (AbstractOperation) constraint.getOperation();
487            if ( topOperation instanceof LogicalOperation ) {
488                List<Operation> operations = ( (LogicalOperation) topOperation ).getArguments();
489                for ( Operation op : operations ) {
490                    findNonDeclaredVariables( (AbstractOperation) op, variables );
491                }
492            } else {
493                findNonDeclaredVariables( topOperation, variables );
494            }
495        }
496    
497        /**
498         * (Recursively) finds a reference to a non declared variable in the propertyname of the given
499         * operation.
500         * 
501         * @param operation
502         *            to be checked
503         * @param variables
504         *            which were declared
505         * @throws OGCWebServiceException
506         *             if such a reference is found
507         */
508        protected void findNonDeclaredVariables( AbstractOperation operation, Map<String, QualifiedName> variables )
509                                throws OGCWebServiceException {
510            if ( operation instanceof LogicalOperation ) {
511                List<Operation> operations = ( (LogicalOperation) operation ).getArguments();
512                for ( Operation op : operations ) {
513                    findNonDeclaredVariables( (AbstractOperation) op, variables );
514                }
515            } else if ( operation instanceof SpatialOperation ) {
516                findNonDeclaredVariables( ( (SpatialOperation) operation ).getPropertyName(), variables );
517            } else {
518                if ( operation instanceof PropertyIsBetweenOperation ) {
519                    findNonDeclaredVariables( ( (PropertyIsBetweenOperation) operation ).getPropertyName(), variables );
520                } else if ( operation instanceof PropertyIsCOMPOperation ) {
521                    Expression expr = ( (PropertyIsCOMPOperation) operation ).getFirstExpression();
522                    if ( expr instanceof PropertyName ) {
523                        findNonDeclaredVariables( ( (PropertyName) expr ), variables );
524                    }
525                    expr = ( (PropertyIsCOMPOperation) operation ).getSecondExpression();
526                    if ( expr instanceof PropertyName ) {
527                        findNonDeclaredVariables( ( (PropertyName) expr ), variables );
528                    }
529                } else if ( operation instanceof PropertyIsInstanceOfOperation ) {
530                    findNonDeclaredVariables( ( (PropertyIsInstanceOfOperation) operation ).getPropertyName(), variables );
531                } else if ( operation instanceof PropertyIsLikeOperation ) {
532                    findNonDeclaredVariables( ( (PropertyIsLikeOperation) operation ).getPropertyName(), variables );
533                } else if ( operation instanceof PropertyIsNullOperation ) {
534                    findNonDeclaredVariables( ( (PropertyIsNullOperation) operation ).getPropertyName(), variables );
535                }
536            }
537        }
538    
539        /**
540         * Parse the string representation of the the propertyname to find a variable reference to a non
541         * declared Variable.
542         * 
543         * @param propName
544         *            to check
545         * @param variables
546         *            which were declared
547         * @throws InvalidParameterValueException
548         *             if such a reference was found.
549         */
550        protected void findNonDeclaredVariables( PropertyName propName, Map<String, QualifiedName> variables )
551                                throws OGCWebServiceException {
552            String propertyPath = propName.toString();
553            String[] foundVariables = StringTools.extractStrings( propertyPath, "$", "/" );
554            if ( foundVariables != null && foundVariables.length > 0 ) {
555                LOG.logDebug( "found following variables in properertyName: " + propName.toString() );
556                for ( String var : foundVariables ) {
557                    LOG.logDebug( "variable: " + var );
558                    if ( !variables.containsKey( var ) ) {
559                        throw new OGCWebServiceException( Messages.getMessage( "CSW_VARIABLE_NOT_DEFINED", var ),
560                                                          ExceptionCode.INVALIDPARAMETERVALUE );
561                    }
562                }
563            }
564        }
565    
566        /*
567         * (non-Javadoc)
568         * 
569         * @see org.deegree.framework.xml.XMLFragment#createEmptyDocument()
570         */
571        void createEmptyDocument()
572                                throws IOException, SAXException {
573            URL url = GetRecordsDocument.class.getResource( XML_TEMPLATE );
574            if ( url == null ) {
575                throw new IOException( "The resource '" + XML_TEMPLATE + " could not be found." );
576            }
577            load( url );
578        }
579    }