001    /*----------------------------------------------------------------------------
002     This file is part of deegree, http://deegree.org/
003     Copyright (C) 2001-2009 by:
004       Department of Geography, University of Bonn
005     and
006       lat/lon GmbH
007    
008     This library is free software; you can redistribute it and/or modify it under
009     the terms of the GNU Lesser General Public License as published by the Free
010     Software Foundation; either version 2.1 of the License, or (at your option)
011     any later version.
012     This library is distributed in the hope that it will be useful, but WITHOUT
013     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
014     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
015     details.
016     You should have received a copy of the GNU Lesser General Public License
017     along with this library; if not, write to the Free Software Foundation, Inc.,
018     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019    
020     Contact information:
021    
022     lat/lon GmbH
023     Aennchenstr. 19, 53177 Bonn
024     Germany
025     http://lat-lon.de/
026    
027     Department of Geography, University of Bonn
028     Prof. Dr. Klaus Greve
029     Postfach 1147, 53001 Bonn
030     Germany
031     http://www.geographie.uni-bonn.de/deegree/
032    
033     e-mail: info@deegree.org
034    ----------------------------------------------------------------------------*/
035    package org.deegree.ogcwebservices.csw.iso_profile.ebrim;
036    
037    import java.io.BufferedReader;
038    import java.io.IOException;
039    import java.io.InputStream;
040    import java.io.InputStreamReader;
041    import java.net.URI;
042    import java.net.URISyntaxException;
043    import java.util.ArrayList;
044    import java.util.HashMap;
045    import java.util.LinkedList;
046    import java.util.List;
047    import java.util.Map;
048    import java.util.Properties;
049    import java.util.Queue;
050    
051    import org.deegree.datatypes.QualifiedName;
052    import org.deegree.framework.log.ILogger;
053    import org.deegree.framework.log.LoggerFactory;
054    import org.deegree.framework.util.StringTools;
055    import org.deegree.framework.xml.NamespaceContext;
056    import org.deegree.framework.xml.XMLParsingException;
057    import org.deegree.framework.xml.XMLTools;
058    import org.deegree.ogcbase.CommonNamespaces;
059    import org.deegree.ogcwebservices.OGCWebServiceException;
060    import org.deegree.ogcwebservices.csw.discovery.GetRecordsDocument;
061    import org.w3c.dom.Document;
062    import org.w3c.dom.Element;
063    import org.w3c.dom.NamedNodeMap;
064    import org.w3c.dom.Node;
065    import org.w3c.dom.NodeList;
066    
067    /**
068     * Maps valid ebrim propertypaths including variables (aka aliases) e.g. $o1 to the according wfs-propertyPaths.
069     *
070     * @version $Revision: 1.8 $
071     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
072     * @author last edited by: $Author: bezema $
073     *
074     * @version 1.0. $Revision: 1.8 $, $Date: 2007-06-21 13:53:57 $
075     *
076     * @since 2.0
077     */
078    public class EBRIM_Mapping {
079        private final static ILogger LOG = LoggerFactory.getLogger( EBRIM_Mapping.class );
080    
081        private final Properties mapping = new Properties();
082    
083        private final static NamespaceContext nsContext = CommonNamespaces.getNamespaceContext();
084    
085        private final static String rimNS = CommonNamespaces.OASIS_EBRIMNS.toString();
086    
087        private final static String wrsNS = CommonNamespaces.WRS_EBRIMNS.toString();
088    
089        private Map<String, QualifiedName> variables;
090    
091        // Used to do some parsing of QNames and variables.
092        private GetRecordsDocument gd;
093    
094        // the root document node to handle the prefixes.
095        private Node rootNode = null;
096    
097        // /**
098        // * A very important method, it allows the retrieval of the original dom tree from the xslt-processor, which in
099        // it's
100        // * turn can be used to find the prefix mappings used in the document.
101        // *
102        // * @param context
103        // * (org.apache.xalan.extensions.XSLProcessorContext) the xslt Context, which will be given by the xalan
104        // * processor
105        // * @param extElem
106        // * (org.apache.xalan.templates.ElemExtensionCall) a class encapsulating some calling parameters from the
107        // * xslt script, it is not used in this class.
108        // */
109        // public void init( Object context, @SuppressWarnings("unused")
110        // Object extElem ) {
111        // // public void init(org.apache.xalan.extensions.XSLProcessorContext context, @SuppressWarnings("unused")
112        // // org.apache.xalan.templates.ElemExtensionCall extElem ) {
113        // LOG.logWarning( "The EBRIM_Mapping should use the xalan api, but currently not supported" );
114        // // rootNode = context.getSourceTree();
115        // if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
116        // LOG.logDebug( " in init, a test to find the 'rim' prefix" );
117        // URI ns = null;
118        // try {
119        // ns = XMLTools.getNamespaceForPrefix( "rim", rootNode );
120        // LOG.logDebug( " The sourcetree we got from the XSLProcessorContext was the following:" );
121        // LOG.logDebug( "-----------------------------------------------------" );
122        // XMLFragment frag = new XMLFragment( (Element) rootNode );
123        // frag.prettyPrint( System.out );
124        // LOG.logDebug( "-----------------------------------------------------" );
125        // } catch ( URISyntaxException e ) {
126        // LOG.logError( "CSW (ebRIM) EBRIM_Mapping: Couldn't get a namespace for prefix rim because: ", e );
127        // } catch ( TransformerException e ) {
128        // LOG.logError( "CSW (ebRIM) EBRIM_Mapping: Couldn't output the sourcetree because: ", e );
129        // }
130        // if ( ns != null ) {
131        // LOG.logDebug( " For the 'rim:' prefix we found following ns: " + ns.toASCIIString() );
132        // } else {
133        // LOG.logError( "CSW (ebRIM) EBRIM_Mapping: No namespace found for 'rim:' prefix!" );
134        // }
135        // }
136        // }
137    
138        /**
139         * The constructor parses the configuration file 'adv_catalog.properties' containing the propertypath mappings from
140         * ebrim to gml.
141         *
142         */
143        public EBRIM_Mapping() {
144            try {
145                InputStream is = EBRIM_Mapping.class.getResourceAsStream( "ebrim_catalog.properties" );
146                BufferedReader br = new BufferedReader( new InputStreamReader( is ) );
147                String line = null;
148                int lineCount = 1;
149                while ( ( line = br.readLine() ) != null ) {
150                    line = line.trim();
151                    if ( !( line.startsWith( "#" ) || "".equals( line ) ) ) {
152                        String[] tmp = StringTools.toArray( line, "=", false );
153                        if ( tmp == null ) {
154                            LOG.logError( lineCount + ") Found an error, please correct following line: " + line );
155                        } else {
156                            if ( tmp.length < 2 ) {
157                                if ( tmp.length > 0 ) {
158                                    LOG.logError( lineCount + ") No value applied for key: " + tmp[0]
159                                                  + ". Key-value pairs must be seperated by an '='sign." );
160                                } else {
161                                    LOG.logError( lineCount + ") The : " + tmp[0]
162                                                  + ". Key-value pairs must be seperated by an '='sign." );
163                                }
164                            } else if ( tmp.length > 2 ) {
165                                LOG.logError( lineCount
166                                              + ") Only one seperator '=' is allowed in a key-value pair, please correct following line: "
167                                              + line );
168                            } else {
169                                mapping.put( tmp[0], tmp[1] );
170                            }
171                        }
172                    }
173                    lineCount++;
174                }
175    
176            } catch ( IOException e ) {
177                LOG.logError(
178                              "CSW (ebRIM) EBRIM_Mapping: An error occurred while trying to parse the 'adv_catalog.properties' file.",
179                              e );
180                e.printStackTrace();
181            }
182            LOG.logDebug( " The Ebrim-Mapper found following Mappings:\n" + mapping );
183            variables = new HashMap<String, QualifiedName>();
184            gd = new GetRecordsDocument();
185        }
186    
187        /**
188         * maps a property name of GetRecords, Delete and Update request from the catalogue schema to the underlying WFS
189         * schema
190         *
191         * @param node
192         *            containing the propertyPath
193         * @return the mapped propertypath
194         * @throws XMLParsingException
195         */
196        public String mapPropertyPath( Node node )
197                                throws XMLParsingException {
198    
199            String propertyNode = XMLTools.getNodeAsString( node, ".", nsContext, null );
200            propertyNode = stripRoot( propertyNode );
201            LOG.logDebug( "The supplied xml-Node results into following (normalized) propertypath: " + propertyNode );
202            LOG.logDebug( "EBRIMG_Mapping#mapPropertyPath: We have got the variables: " + variables.toString() );
203    
204            // Setting the node which will be used to find the prefixes.
205            Node prefixResolverNode = getPrefixResolverNode( node );
206    
207            String[] props = propertyNode.split( "/" );
208            StringBuffer result = new StringBuffer( "/" );
209            int count = 0;
210            String previousMapping = null;
211            for ( String propertyName : props ) {
212                LOG.logDebug( "Trying to map propertyName: " + propertyName );
213                if ( propertyName != null ) {
214                    // check if the propertyname references a variable.
215                    String tmpVarReference = null;
216                    String mappedName = null;
217                    if ( propertyName.startsWith( "$" ) ) {
218                        tmpVarReference = propertyName;
219                        if ( !variables.containsKey( propertyName.substring( 1 ) )
220                             || variables.get( propertyName.substring( 1 ) ) == null ) {
221    
222                            LOG.logError( "CSW (ebRIM) EBRIM_Mapping: The referenced Variable '" + propertyName
223                                          + "' in the propertyNode: '" + propertyNode
224                                          + "' has not been declared this can't be!" );
225                            LOG.logError( "CSW (ebRIM) EBRIM_Mapping: We have got the variables: " + variables.toString() );
226                        } else {
227                            // get the rim/wrs object to which this variable is bound to.
228                            propertyName = createMapStringForQName( variables.get( propertyName.substring( 1 ) ) );
229                            LOG.logDebug( " trying to find mapping for a property: " + propertyName
230                                          + ", with found variable: " + tmpVarReference );
231                            mappedName = mapping.getProperty( propertyName );
232                        }
233                    } else if ( propertyName.startsWith( "@" ) ) {
234                        if ( previousMapping == null ) {
235                            LOG.logError( "CSW (ebRIM) EBRIM_Mapping: a propertyName may not start with the @ sign, trying to find a mapping without the @ for: "
236                                          + propertyNode );
237                            mappedName = mapping.getProperty( propertyName.substring( 1 ) );
238                        } else {
239                            // first check the RegistryObject attributes
240                            // @id=app:iduri
241                            // @home=app:home
242                            // @lid=app:liduri
243                            // @objectType=app:objectType
244                            // @status=app:status
245                            LOG.logDebug( " trying to find mapping for attribute: " + propertyName );
246                            mappedName = mapping.getProperty( propertyName );
247                            if ( mappedName == null || "".equals( mappedName ) ) {
248                                LOG.logDebug( " the single attribute had no result, trying to find mapping with the dereferenced previous property @property: "
249                                              + ( previousMapping + "/" + propertyName ) );
250                                mappedName = mapping.getProperty( previousMapping + "/" + propertyName );
251                            }
252                        }
253                    } else {
254                        LOG.logDebug( " Trying to find mapping for simple propertyName: " + propertyName );
255                        QualifiedName qName = null;
256                        try {
257                            qName = gd.parseQNameFromString( propertyName, prefixResolverNode, true );
258                        } catch ( URISyntaxException e ) {
259                            LOG.logError( "CSW (ebRIM) EBRIM_Mapping: Could not create QualifiedName from propertyName: "
260                                          + propertyName + ", creating simple QualifiedName.", e );
261                            qName = new QualifiedName( propertyName );
262                        }
263                        propertyName = createMapStringForQName( qName );
264                        LOG.logDebug( " trying to find mapping for a property: " + propertyName );
265                        /**
266                         * This hack is needed, because both person and organization use the following features,
267                         * telephoneNumber@*, EmailAddress/@* and a link to Address.
268                         */
269                        if ( propertyName.endsWith( "TelephoneNumber" ) || propertyName.endsWith( "Address" ) ) {
270                            propertyName = previousMapping + "/" + propertyName;
271                        }
272                        mappedName = mapping.getProperty( propertyName );
273                    }
274                    if ( mappedName == null ) {
275                        LOG.logDebug( "The propertyName: " + propertyName
276                                      + " could not be mapped, maybe a Valuelist or AnyValue?" );
277                        // Very dirty trick to map
278                        // app:slots/app:Slot/app:values/app:SlotValues/app:geometry
279                        // and app:slots/app:Slot/app:values/app:SlotValues/stringValue.
280                        // from incoming rim:ValueList/rim:Value
281                        // as well as the wrs:ValueList/wrs:AnyValue (defined in pkg-basic.xsd)
282                        if ( ( "rim:Value".equals( propertyName ) && ( "rim:ValueList".equals( previousMapping ) || "wrs:ValueList".equals( previousMapping ) ) )
283                             || ( ( "wrs:AnyValue".equals( propertyName ) || "rim:AnyValue".equals( propertyName ) ) && ( "wrs:ValueList".equals( previousMapping ) || "rim:ValueList".equals( previousMapping ) ) ) ) {
284                            if ( ( count + 1 ) != props.length ) {
285                                LOG.logError( "CSW (ebRIM) EBRIM_Mapping: More properties will follow this one, this is strange, because a rim:Value can only be located at the end of a propertyPath" );
286                            }
287                            Node parentNode = XMLTools.getNode( node, "..", nsContext );
288                            String parent = parentNode.getLocalName();
289    
290                            LOG.logDebug( " It seems we want to map the rim:Slot/rim:ValueList/rim:Value or rim:Slot/wrs:ValueList/wrs:AnyValue, the value of the parent of this propertyName is: "
291                                          + parent );
292                            if ( parent != null ) {
293                                if ( parent.startsWith( "Property" ) ) {// hurray a property therefore a
294                                    // stringValue
295                                    LOG.logDebug( parent + " starts with 'Property' we therefore map to app:stringValue." );
296                                    mappedName = "app:stringValue";
297                                } else {
298                                    LOG.logDebug( parent
299                                                  + " doesn't start with 'Property' we therefore map to app:geometry." );
300                                    mappedName = "app:geometry";
301                                }
302                            }
303                        } else {
304                            LOG.logInfo( "CSW (ebRIM) EBRIM_Mapping: found no mapping for: " + propertyName );
305                            mappedName = "";
306                        }
307                    }
308                    if ( !"".equals( mappedName ) ) {
309                        // save the found Mapping, it might be needed to map the following propertyName
310                        // if it is a property (starting with @)
311                        previousMapping = propertyName;
312                        result.append( addWFSPropertyPath( mappedName, tmpVarReference, ( count == 0 ) ) );
313                        if ( ( count + 1 ) != props.length ) {
314                            result.append( "/" );
315                        }
316                    }
317                    // next property/type
318                    count++;
319                }
320            }
321    
322            return result.toString();
323        }
324    
325        /**
326         * maps the property names of given typenames in the typeNames attribute of a GetRecords request to the catalogue
327         * schema of the underlying WFS schema
328         *
329         * @param node
330         *            the GetRecords request Node
331         * @return the mapped propertypath
332         * @throws XMLParsingException
333         */
334        public String mapTypeNames( Node node )
335                                throws XMLParsingException {
336            String typeNamesAttribute = XMLTools.getNodeAsString( node, ".", nsContext, null );
337            Map<String, QualifiedName> tmpVariables = new HashMap<String, QualifiedName>();
338            // Setting the node which will be used to find the prefixes.
339            Node prefixResolverNode = getPrefixResolverNode( node );
340    
341            List<QualifiedName> typeNames = parseTypeList( prefixResolverNode, typeNamesAttribute, tmpVariables );
342            LOG.logDebug( " found following qNames of the typeNames: " + typeNames );
343            // adding new aliases to the query/@typenames
344            String newAliasPreFix = "kQhtYHHp_";
345            StringBuffer queryTypeNameAttrSB = new StringBuffer();
346            if ( tmpVariables.size() == 0 ) {
347                for ( int i = 0; i < typeNames.size(); ++i ) {
348                    String aliasPrefix = newAliasPreFix + i;
349                    tmpVariables.put( newAliasPreFix + i, typeNames.get( i ) );
350                    queryTypeNameAttrSB.append( "rim:" + typeNames.get( i ).getLocalName() ).append( "=" ).append(
351                                                                                                                   aliasPrefix );
352                    if ( ( i + 1 ) < typeNames.size() ) {
353                        queryTypeNameAttrSB.append( " " );
354                    }
355                }
356            }
357    
358            StringBuffer resultString = new StringBuffer();
359            int qNameCounter = 0;
360            // Because we need to reuse the member variables map, we've made a local copy of it.
361            variables.putAll( tmpVariables );
362            LOG.logDebug( " EBRIM_Mapping#mapTypeNames: We have got the variables: " + variables.toString() );
363            List<String> varDefs = new ArrayList<String>();
364            for ( QualifiedName qName : typeNames ) {
365                URI ns = qName.getNamespace();
366                String prefix = qName.getPrefix();
367                // for debugging purposes
368                if ( prefix == null ) {
369                    prefix = "";
370                } else {
371                    prefix += ":";
372                }
373                if ( ns == null || rimNS.equals( ns.toString() ) ) {
374                    LOG.logDebug( " We found the following namespace for the ElementSetName/@typeName: " + ns
375                                  + " so we map to the prefix rim." );
376                    prefix = "rim:";
377                }
378                String result = mapping.getProperty( prefix + qName.getLocalName() );
379                LOG.logDebug( " for the FeatureType: " + prefix + qName.getLocalName() + " we found following mapping: "
380                              + result );
381                if ( result != null ) {
382                    resultString.append( result );
383                    // get the mapped variable for this qName
384                    String var = getVariableForQName( qName, tmpVariables );
385                    if ( var != null ) {
386                        // resultString.append( "=" ).append( var );
387                        varDefs.add( var );
388                    }
389                } else {
390                    LOG.logInfo( "CSW (ebRIM) EBRIM_Mapping: Found no mapping for: " + prefix + qName.getLocalName()
391                                 + ", so ignoring it." );
392                }
393                if ( ++qNameCounter < typeNames.size() ) {
394                    resultString.append( " " );
395                }
396            }
397            // append the aliases= variables to the resultstring
398            if ( varDefs.size() != 0 ) {
399                LOG.logDebug( " The defined variables list is not empty, we therefore append the alias keyword to the typeName" );
400                resultString.append( " aliases=" );
401                for ( int i = 0; i < varDefs.size(); ++i ) {
402                    resultString.append( varDefs.get( i ) );
403                    if ( ( i + 1 ) < varDefs.size() ) {
404                        resultString.append( " " );
405                    }
406                }
407            }
408    
409            return resultString.toString();
410        }
411    
412        /**
413         * This method take an elementSetName node and conerts it's content into a list of wfs:PropertyName nodes. Depending
414         * on the (String) value of the elementSetNameNode (brief, summary, full) the propertyNames will have different
415         * values. For this method to work a temporal document is builded from which one element is created, this element
416         * will hold all child <wfs:PropertyName> elements.
417         *
418         * @param elementSetNameNode
419         *            the ElementSetName Node of the incoming GetRecords request.
420         * @return a Nodelist containing <wfs:PropertyName> nodes.
421         */
422        public NodeList mapElementSetName( Node elementSetNameNode ) {
423    
424            // creating an empty document, so we can append nodes to it, which will be returned as a
425            // Nodelist to the xslt script.
426            Document doc = XMLTools.create();
427            Element resultElement = doc.createElement( "wfs:result" );
428            Node newQueryNode = doc.importNode( elementSetNameNode.getParentNode(), true );
429            try {
430                String elementSetName = XMLTools.getNodeAsString( elementSetNameNode, ".", nsContext, null );
431                LOG.logDebug( " Found following elementSetName: " + elementSetName );
432                if ( elementSetName != null ) {
433                    // Setting the node which will be used to find the prefixes.
434                    Node prefixResolverNode = getPrefixResolverNode( newQueryNode );
435                    String typeNamesAttribute = XMLTools.getNodeAsString( elementSetNameNode, "@typeNames", nsContext, null );
436                    // Element queryElement = (Element) newElementSetNameNode.getParentNode();
437                    Element queryElement = (Element) newQueryNode;
438                    String queryTypeNamesAttribute = XMLTools.getNodeAsString( queryElement, "@typeNames", nsContext, null );
439                    if ( queryTypeNamesAttribute == null ) {
440                        LOG.logError( "CSW (ebRIM) EBRIM_Mapping: no typeNames attribute found in the csw:Query element, this may not be!!!" );
441                    }
442    
443                    // First find the variables in the csw:Query/@typeNames.
444                    Map<String, QualifiedName> varsInQuery = new HashMap<String, QualifiedName>();
445                    varsInQuery.putAll( variables );
446                    // List<QualifiedName> queryTypeNames =
447                    // parseTypeList( prefixResolverNode, queryTypeNamesAttribute, varsInQuery );
448                    if ( varsInQuery.size() == 0 ) {
449                        LOG.logError( "CSW (ebRIM) EBRIM_Mapping: We found no variables in the query, something is terribly wrong" );
450                    }
451                    // StringBuffer queryTypeNameAttrSB = new StringBuffer( queryTypeNamesAttribute );
452                    // boolean resetQueryTypeNames = false;
453    
454                    if ( typeNamesAttribute == null ) {
455                        // if ( varsInQuery.size() == 0 ) {
456                        // resetQueryTypeNames = true;
457                        // }
458                        LOG.logDebug( " no typeNames attribute found in the csw:ElementSetName node, therefore taking the typeNames of the query node." );
459                        typeNamesAttribute = queryTypeNamesAttribute;
460                    }
461                    // if ( varsInQuery.size() == 0 && !typeNamesAttribute.equals( queryTypeNamesAttribute ) ) {
462                    // // adding new aliases to the query/@typenames
463                    // String newAliasPreFix = "kQhtYHHp_";
464                    // queryTypeNameAttrSB = new StringBuffer();
465                    // for ( int i = 0; i < queryTypeNames.size(); ++i ) {
466                    // String aliasPrefix = newAliasPreFix + i;
467                    // varsInQuery.put( newAliasPreFix + i, queryTypeNames.get( i ) );
468                    // queryTypeNameAttrSB.append( "rim:" + queryTypeNames.get( i ).getLocalName() ).append( "=" ).append(
469                    // aliasPrefix );
470                    // if ( ( i + 1 ) < queryTypeNames.size() ) {
471                    // queryTypeNameAttrSB.append( " " );
472                    // }
473                    // }
474                    // queryElement.setAttribute( "typeNames", queryTypeNameAttrSB.toString() );
475                    // }
476    
477                    if ( typeNamesAttribute != null ) {
478    
479                        Map<String, QualifiedName> vars = new HashMap<String, QualifiedName>();
480                        List<QualifiedName> typeNames = parseTypeList( prefixResolverNode, typeNamesAttribute, vars );
481                        if ( vars.size() != 0 && !typeNamesAttribute.equals( queryTypeNamesAttribute ) ) {
482                            LOG.logError( "CSW (ebRIM) EBRIM_Mapping: Found variables (aliases) in the ElementSetName/@typeNames attribute this is not allowed, we will not process them." );
483                        }
484                        LOG.logDebug( " Parent of the elementSetName has a local name (should be query): "
485                                      + queryElement.getLocalName() );
486                        Element constraint = XMLTools.getElement( queryElement, "csw:Constraint", nsContext );
487                        if ( constraint == null ) {
488                            LOG.logDebug( " No contraint node found, therefore creating one." );
489                            Element a = queryElement.getOwnerDocument().createElementNS(
490                                                                                         CommonNamespaces.CSWNS.toASCIIString(),
491                                                                                         "csw:Constraint" );
492                            queryElement.getOwnerDocument().importNode( a, false );
493                            constraint = (Element) queryElement.appendChild( a );
494                        }
495                        Element filter = XMLTools.getElement( constraint, "ogc:Filter", nsContext );
496                        if ( filter == null ) {
497                            LOG.logDebug( " No filter node found, therefore creating one." );
498                            // Element a = queryElement.getOwnerDocument().createElementNS(
499                            // CommonNamespaces.OGCNS.toASCIIString(),
500                            // "ogc:Filter" );
501                            Element a = doc.createElementNS( CommonNamespaces.OGCNS.toASCIIString(), "ogc:Filter" );
502                            // queryElement.getOwnerDocument().importNode( a, false );
503                            doc.importNode( a, false );
504                            filter = (Element) constraint.appendChild( a );
505                        }
506                        Node firstOriginalFilterNode = filter.getFirstChild();
507                        Element andNode = null;
508                        if ( firstOriginalFilterNode != null ) {
509                            LOG.logDebug( " The ogc:Filter has a firstChild node, therefore creating an extra ogc:And." );
510                            Element a = doc.createElementNS( CommonNamespaces.OGCNS.toASCIIString(), "ogc:And" );
511                            // queryElement.getOwnerDocument().importNode( a, false );
512                            doc.importNode( a, false );
513                            andNode = (Element) filter.appendChild( a );
514                            andNode.appendChild( firstOriginalFilterNode );
515                        }
516                        // Element tmpNode = queryElement.getOwnerDocument().createElementNS(
517                        // CommonNamespaces.OGCNS.toASCIIString(),
518                        // "ogc:And" );
519                        Element tmpNode = doc.createElementNS( CommonNamespaces.OGCNS.toASCIIString(), "ogc:And" );
520                        // queryElement.getOwnerDocument().importNode( tmpNode, false );
521                        doc.importNode( tmpNode, false );
522                        Element topNode = null;
523                        if ( andNode != null ) {
524                            if ( typeNames.size() > 1 ) {
525                                topNode = (Element) andNode.appendChild( tmpNode );
526                            } else {
527                                topNode = andNode;
528                            }
529                        } else {
530                            if ( typeNames.size() > 1 ) {
531                                topNode = (Element) filter.appendChild( tmpNode );
532                            } else {
533                                topNode = filter;
534                            }
535    
536                        }
537                        // a random string to be used for the extra filter.
538    
539                        for ( int i = 0; i < typeNames.size(); ++i ) {
540                            QualifiedName qName = typeNames.get( i );
541                            URI ns = qName.getNamespace();
542                            String prefix = qName.getPrefix();
543                            // for debugging purposes
544                            if ( prefix == null ) {
545                                prefix = "";
546                            } else {
547                                prefix += ":";
548                            }
549                            if ( ns == null || rimNS.equals( ns.toString() ) ) {
550                                LOG.logDebug( " We found the following namespace for the ElementSetName/@typeName: " + ns
551                                              + " so we map to the prefix rim." );
552                                prefix = "rim:";
553                            }
554                            String result = mapping.getProperty( prefix + qName.getLocalName() );
555                            LOG.logDebug( " for the FeatureType: " + prefix + qName.getLocalName()
556                                          + " we found following mapping: " + result );
557                            if ( result != null ) {
558                                if ( !"app:RegistryObject".equals( result ) ) {
559                                    LOG.logError( "CSW (ebRIM) EBRIM_Mapping: the given typeName is not a RegistryObject, and can therefore not be returned: "
560                                                  + prefix + qName.getLocalName() );
561                                } else {
562                                    String aliasPrefix = getVariableForQName( qName, varsInQuery );
563                                    LOG.logDebug( " in elementSetName found alias: " + aliasPrefix + " for the typename: "
564                                                  + qName.getLocalName() );
565                                    if ( aliasPrefix == null || "".equals( aliasPrefix ) ) {
566                                        LOG.logError( "CSW (ebRIM) EBRIM_Mapping: in elementSetName found no alias for the typeName: "
567                                                      + qName.getLocalName() + " this cannot be!" );
568                                    }
569    
570                                    //
571                                    appendRegistryObjects( resultElement, "/$" + aliasPrefix );
572                                    // appendRegistryObjects( resultElement, elementSetName, "/" + result );
573    
574                                    // now appending an ogc:PropertyIsEqual to the ogc:Or node of the ogc:Filter
575                                    // tmpNode = queryElement.getOwnerDocument().createElementNS(
576                                    // CommonNamespaces.OGCNS.toASCIIString(),
577                                    // "ogc:PropertyIsEqualTo" );
578                                    tmpNode = doc.createElementNS( CommonNamespaces.OGCNS.toASCIIString(),
579                                                                   "ogc:PropertyIsEqualTo" );
580    
581                                    // queryElement.getOwnerDocument().importNode( tmpNode, false );
582                                    doc.importNode( tmpNode, false );
583                                    Element equalTo = (Element) topNode.appendChild( tmpNode );
584                                    equalTo.setAttribute( "matchCase", "true" );
585    
586                                    // create the propertyName node referencing the app:RegistryObject/app:type
587                                    // tmpNode = queryElement.getOwnerDocument().createElementNS(
588                                    // CommonNamespaces.OGCNS.toASCIIString(),
589                                    // "ogc:PropertyName" );
590                                    tmpNode = doc.createElementNS( CommonNamespaces.OGCNS.toASCIIString(),
591                                                                   "ogc:PropertyName" );
592                                    // queryElement.getOwnerDocument().importNode( tmpNode, false );
593                                    doc.importNode( tmpNode, false );
594                                    Element propName = (Element) equalTo.appendChild( tmpNode );
595                                    XMLTools.setNodeValue( propName, "$" + aliasPrefix + "/rim:type" );
596    
597                                    // create the literal (localname) node to which the propertyname
598                                    // app:RegistryObject/app:type should match
599                                    // tmpNode = queryElement.getOwnerDocument().createElementNS(
600                                    // CommonNamespaces.OGCNS.toASCIIString(),
601                                    // "ogc:Literal" );
602                                    tmpNode = doc.createElementNS( CommonNamespaces.OGCNS.toASCIIString(), "ogc:Literal" );
603                                    // queryElement.getOwnerDocument().importNode( tmpNode, false );
604                                    doc.importNode( tmpNode, false );
605                                    Element literal = (Element) equalTo.appendChild( tmpNode );
606                                    XMLTools.setNodeValue( literal, qName.getLocalName() );
607    
608                                    // define a new alias for the typeNames
609                                    // queryTypeNameAttrSB.append( "rim:" + qName.getLocalName() ).append( "=" ).append(
610                                    // aliasPrefix );
611                                    // if ( ( i + 1 ) < typeNames.size() ) {
612                                    // queryTypeNameAttrSB.append( " " );
613                                    // }
614    
615                                }
616                            } else {
617                                LOG.logInfo( "CSW (ebRIM) EBRIM_Mapping: Found no mapping for: " + prefix
618                                             + qName.getLocalName() + ", so ignoring it." );
619                            }
620                        }
621                        // add the typeNames attribute to the QueryElement
622                        // if ( resetQueryTypeNames ) {
623                        // queryElement.setAttribute( "typeNames", queryTypeNameAttrSB.toString() );
624                        // }
625                        // if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
626                        // XMLFragment docTest = new XMLFragment( queryElement );
627                        // LOG.logDebug( " newly created query Element: \n"
628                        // + docTest.getAsPrettyString() );
629                        // }
630                    }
631                }
632            } catch ( XMLParsingException e ) {
633                LOG.logError( "CSW (ebRIM) EBRIM_Mapping: following error occured while trying to map elementSetName node",
634                              e );
635            }
636            NodeList nodeList = resultElement.getChildNodes();
637            for ( int i = 0; i < nodeList.getLength(); ++i ) {
638                LOG.logDebug( " Node " + i + " has a localName: " + nodeList.item( i ).getLocalName() );
639            }
640    
641            return resultElement.getChildNodes();
642        }
643    
644        /**
645         * Maps the typename of the given Element if the targetNamespace attribute of the given Node equals the rimNS.
646         *
647         * @param typeNameElement
648         *            the TypeName Element inside a DescribeRecord request.
649         * @return the mapping of the featureType requested or an empty string (e.g. "").
650         */
651        public String mapTypeNameElement( Node typeNameElement ) {
652            String typeNameValue = null;
653            if ( typeNameElement != null ) {
654                try {
655                    String targetNamespace = XMLTools.getRequiredNodeAsString( typeNameElement, "@targetNamespace",
656                                                                               nsContext );
657                    if ( rimNS.equals( targetNamespace ) ) {
658                        typeNameValue = XMLTools.getNodeAsString( typeNameElement, ".", nsContext, null );
659                        if ( typeNameValue != null ) {
660                            typeNameValue = stripRoot( typeNameValue.trim() );
661                            int index = typeNameValue.lastIndexOf( ":" );
662                            if ( index != -1 ) {
663                                typeNameValue = typeNameValue.substring( index );
664                            }
665                            typeNameValue = mapping.getProperty( "rim:" + typeNameValue );
666                        }
667                    } else {
668                        LOG.logDebug( " The given namespace: " + targetNamespace
669                                      + " can not be mapped to the rim namespace: " + rimNS + " so no mapping is done" );
670                    }
671                } catch ( XMLParsingException e ) {
672                    LOG.logInfo( e.getMessage() );
673                }
674            }
675            if ( typeNameValue == null ) {
676                typeNameValue = "";
677            }
678            return typeNameValue;
679    
680        }
681    
682        /**
683         * Searches for a given qName in the given map and removes the mapping if it is found.
684         *
685         * @param qName
686         *            to search for
687         * @param tmpVariables
688         *            some temporary variables
689         * @return the mapped variable or <code>null</code> if none was found.
690         */
691        private String getVariableForQName( QualifiedName qName, Map<String, QualifiedName> tmpVariables ) {
692            if ( tmpVariables.containsValue( qName ) ) {
693                for ( String variable : tmpVariables.keySet() ) {
694                    if ( qName.equals( tmpVariables.get( variable ) ) ) {
695                        tmpVariables.remove( variable );
696                        return variable;
697                    }
698                }
699            }
700            return null;
701        }
702    
703        /**
704         * A simple method to split the given typeNamesAttribute in to type names and finds the optional variables inside
705         * them.
706         *
707         * @param node
708         *            the context node which will be used to search for the prefixes.
709         * @param typeNamesAttribute
710         *            the String containing the typenames attribute values.
711         * @param vars
712         *            a map in which the found variables will be stored
713         * @return a list with typenames parsed from the typeNamesAttribute String, if no typenames were found the size will
714         *         be 0.
715         */
716        private List<QualifiedName> parseTypeList( Node node, String typeNamesAttribute, Map<String, QualifiedName> vars ) {
717            LOG.logDebug( " Trying to map following typeName: " + typeNamesAttribute );
718            // System.out.println( "einmal: " + root );
719            // DOMPrinter.printNode( root, "");
720            String[] splitter = typeNamesAttribute.split( " " );
721            List<QualifiedName> typeNames = new ArrayList<QualifiedName>();
722    
723            for ( String s : splitter ) {
724                try {
725                    LOG.logDebug( " Trying to parse following typeName (with/without variables): " + s );
726                    if ( !s.startsWith( "$" ) ) {
727                        gd.findVariablesInTypeName( s, node, typeNames, vars, true );
728                    } else {
729                        // Attention, I don't know if this is the wanted behavior, we loose the variable declaration.
730                        LOG.logDebug( " Because the given typeName starts with an '$'-sign, we first dereference the alias: "
731                                      + s.substring( 1 ) );
732                        if ( variables.containsKey( s.substring( 1 ) ) ) {
733                            QualifiedName qName = variables.get( s.substring( 1 ) );
734                            LOG.logDebug( " \t the alias: " + s.substring( 1 )
735                                          + " was therefore replaced with the propertyName: " + qName.getPrefix() + ":"
736                                          + qName.getLocalName() );
737                            typeNames.add( qName );
738                        } else {
739                            LOG.logError( "CSW (ebRIM) EBRIM_Mapping: \t the alias was not declared, this cannot be!" );
740                        }
741                    }
742    
743                } catch ( OGCWebServiceException e ) {
744                    LOG.logError( e.getMessage(), e );
745                } catch ( URISyntaxException e ) {
746                    LOG.logError( e.getMessage(), e );
747                }
748            }
749            return typeNames;
750        }
751    
752        /**
753         *
754         * @param mappedName
755         *            to which the appropriate propertyPath will be added.
756         * @param variable
757         *            starting with the "$" which will replace the mappedName.
758         * @param isFirst
759         *            true if the given mappedName was the the firstelement on the requested xpath.
760         */
761        private String addWFSPropertyPath( String mappedName, String variable, boolean isFirst ) {
762            StringBuffer toBeAdded = new StringBuffer();
763            if ( "app:RegistryObject".equalsIgnoreCase( mappedName ) ) {
764                if ( !isFirst ) {
765                    toBeAdded.append( "app:linkedRegistryObject/app:LINK_RegObj_RegObj/app:registryObject/" );
766                }
767            } else {
768                if ( isFirst ) {
769                    toBeAdded.append( "app:RegistryObject/" );
770                }
771                if ( "app:Name".equals( mappedName ) ) {
772                    toBeAdded.append( "app:name/" );
773                } else if ( "app:Description".equalsIgnoreCase( mappedName ) ) {
774                    toBeAdded.append( "app:description/" );
775                } else if ( "app:Slot".equals( mappedName ) ) {
776                    toBeAdded.append( "app:slots/" );
777                } else if ( "app:VersionInfo".equals( mappedName ) ) {
778                    toBeAdded.append( "app:versionInfo/" );
779                } else if ( "app:ObjectRef".equals( mappedName ) ) {
780                    toBeAdded.append( "app:auditableEvent/app:AuditableEvent/app:affectedObjects/" );
781                } else if ( "app:ExtrinsicObject".equals( mappedName ) ) {
782                    // this is actually the rim:ExtrinsicObject/rim:ContentVersionInfo element
783                    toBeAdded.append( "app:extrinsicObject" );
784                }
785            }
786            if ( variable != null && !"".equals( variable.trim() ) ) {
787                mappedName = variable;
788            }
789            return ( toBeAdded.toString() + mappedName );
790    
791        }
792    
793        /**
794         * Recursively strips all leading '.' and '/' from the given String.
795         *
796         * @param toBeStripped
797         *            the String to be stripped
798         * @return the stripped String.
799         */
800        private String stripRoot( String toBeStripped ) {
801            if ( toBeStripped != null ) {
802                if ( toBeStripped.startsWith( "/" ) || toBeStripped.startsWith( "." ) ) {
803                    LOG.logDebug( " stripping first character of: " + toBeStripped );
804                    return stripRoot( toBeStripped.substring( 1 ) );
805                }
806            }
807            return toBeStripped;
808        }
809    
810        /**
811         * appends the given mapped elementSetName/@typeNames value according to the elementSetNameValue to the given
812         * resultElement.
813         *
814         * @param resultElement
815         *            to append the <wfs:PropertyNames> to
816         * @param resultedMapping
817         *            the mapped TypeNames-Value
818         */
819        private void appendRegistryObjects( Element resultElement, String resultedMapping ) {
820            // if ( "full".equalsIgnoreCase( elementSetNameValue ) ) {
821            XMLTools.appendElement( resultElement, CommonNamespaces.WFSNS, "wfs:PropertyName", resultedMapping );
822            // } else {
823            // XMLTools.appendElement( resultElement, CommonNamespaces.WFSNS, "wfs:PropertyName", resultedMapping
824            // + "/app:iduri" );
825            // XMLTools.appendElement( resultElement, CommonNamespaces.WFSNS, "wfs:PropertyName", resultedMapping
826            // + "/app:liduri" );
827            // XMLTools.appendElement( resultElement, CommonNamespaces.WFSNS, "wfs:PropertyName", resultedMapping
828            // + "/app:objectType" );
829            // XMLTools.appendElement( resultElement, CommonNamespaces.WFSNS, "wfs:PropertyName", resultedMapping
830            // + "/app:status" );
831            // XMLTools.appendElement( resultElement, CommonNamespaces.WFSNS, "wfs:PropertyName", resultedMapping
832            // + "/app:versionInfo" );
833            // if ( "summary".equalsIgnoreCase( elementSetNameValue ) ) {
834            // XMLTools.appendElement( resultElement, CommonNamespaces.WFSNS, "wfs:PropertyName", resultedMapping
835            // + "/app:slots" );
836            // XMLTools.appendElement( resultElement, CommonNamespaces.WFSNS, "wfs:PropertyName",
837            // resultedMapping + "/app:name/app:Name" );
838            // XMLTools.appendElement( resultElement, CommonNamespaces.WFSNS, "wfs:PropertyName",
839            // resultedMapping + "/app:description/app:Description" );
840            // }
841            // }
842        }
843    
844        /**
845         * Sets the prefix to the localname of the given qName to rim: if the NS of the QName equals the rimNS or the qName
846         * prefix == null (or empty) or the namespace of the given QName == null. Else the found prefix of the QName is
847         * taken (happens for example with the wrs prefix)
848         *
849         * @param qName
850         *            to be parsed.
851         * @return the prefix:localname part of the qName with prefix bound to "rim" or the qNames prefix.
852         */
853        private String createMapStringForQName( QualifiedName qName ) {
854            URI ns = qName.getNamespace();
855            String prefix = qName.getPrefix();
856            if ( ns == null || rimNS.equals( ns.toString() ) || qName.getPrefix() == null
857                 || "".equals( qName.getPrefix().trim() ) ) {
858                prefix = "rim:";
859            } else if ( wrsNS.equals( ns.toString() ) ) {
860                prefix = CommonNamespaces.WRS_EBRIM_PREFIX + ":";
861            }
862            return prefix + qName.getLocalName();
863        }
864    
865        /**
866         * A simple method, which will return a node in which a prefix can be search.
867         *
868         * @param xsltNode
869         *            the local node from the calling xslt processor to one of the functions.
870         * @return the original rootNode if it is not null, otherwise the given xsltNode.
871         */
872        private Node getPrefixResolverNode( Node xsltNode ) {
873            Node tmpNode = rootNode;
874            if ( tmpNode == null ) {
875                tmpNode = xsltNode;
876            }
877            Queue<Node> nodes = new LinkedList<Node>();
878            nodes.offer( tmpNode );
879            while ( !nodes.isEmpty() ) {
880                tmpNode = nodes.poll();
881                if ( xsltNode.getNodeType() == Node.ATTRIBUTE_NODE ) {
882                    NamedNodeMap nnm = tmpNode.getAttributes();
883                    for ( int j = 0; j < nnm.getLength(); ++j ) {
884                        if ( xsltNode.isEqualNode( nnm.item( j ) ) ) {
885                            return nnm.item( j );
886                        }
887                    }
888                } else {
889                    if ( xsltNode.isEqualNode( tmpNode ) ) {
890                        return tmpNode;
891                    }
892                }
893                NodeList nl = tmpNode.getChildNodes();
894                for ( int i = 0; i < nl.getLength(); ++i ) {
895                    nodes.offer( nl.item( i ) );
896                }
897            }
898            return xsltNode;
899        }
900    }