    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    }