001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/csw/discovery/XMLFactory.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2006 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstr. 19
030     53115 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041     
042     ---------------------------------------------------------------------------*/
043    package org.deegree.ogcwebservices.csw.discovery;
044    
045    import java.io.IOException;
046    import java.net.URI;
047    import java.net.URISyntaxException;
048    import java.util.Date;
049    import java.util.HashMap;
050    import java.util.List;
051    import java.util.Map;
052    import java.util.Set;
053    
054    import org.deegree.datatypes.QualifiedName;
055    import org.deegree.framework.log.ILogger;
056    import org.deegree.framework.log.LoggerFactory;
057    import org.deegree.framework.util.StringTools;
058    import org.deegree.framework.util.TimeTools;
059    import org.deegree.framework.xml.XMLException;
060    import org.deegree.framework.xml.XMLParsingException;
061    import org.deegree.framework.xml.XMLTools;
062    import org.deegree.ogcbase.CommonNamespaces;
063    import org.deegree.ogcbase.PropertyPath;
064    import org.deegree.ogcbase.SortProperty;
065    import org.deegree.ogcwebservices.OGCWebServiceException;
066    import org.w3c.dom.Attr;
067    import org.w3c.dom.Document;
068    import org.w3c.dom.Element;
069    import org.w3c.dom.NamedNodeMap;
070    import org.w3c.dom.Node;
071    import org.w3c.dom.NodeList;
072    
073    /**
074     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
075     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a>
076     * @author last edited by: $Author: mschneider $
077     * 
078     * @version $Revision: 7457 $, $Date: 2007-06-05 11:59:46 +0200 (Di, 05 Jun 2007) $
079     */
080    public class XMLFactory extends org.deegree.ogcbase.XMLFactory {
081    
082        private static final ILogger LOG = LoggerFactory.getLogger( XMLFactory.class );
083    
084        private static final URI CSWNS = CommonNamespaces.CSWNS;
085    
086        private static final URI OGCNS = CommonNamespaces.OGCNS;
087    
088        /**
089         * Exports a <code>GetRecordsResponse</code> instance to a <code>GetRecordsResponseDocument</code>.
090         * 
091         * @param response
092         * @return DOM representation of the <code>GetRecordsResponse</code>
093         * @throws IOException
094         *             if XML template could not be loaded
095         */
096        public static GetRecordsResultDocument export( GetRecordsResult response ) throws XMLException {
097    
098            GetRecordsResultDocument responseDocument = new GetRecordsResultDocument();
099    
100            try {
101                responseDocument.createEmptyDocument();
102                Element rootElement = responseDocument.getRootElement();
103                Document doc = rootElement.getOwnerDocument();
104    
105                // set required namespaces
106                Element recordRespRoot = response.getSearchResults().getRecords().getOwnerDocument().getDocumentElement();
107                NamedNodeMap nnm = recordRespRoot.getAttributes();
108                for ( int i = 0; i < nnm.getLength(); i++ ) {
109                    Node node = nnm.item( i );
110                    if ( node instanceof Attr ) {
111                        rootElement.setAttribute( node.getNodeName(), node.getNodeValue() );
112                    }
113                }
114                // 'version'-attribute
115                String version = response.getVersion();
116                if ( version == null || "".equals( version.trim() ) ) {
117                    version = "2.0.0";
118                }
119                rootElement.setAttribute( "version", version );
120    
121                // 'RequestId'-element (optional)
122                if ( response.getRequest().getId() != null ) {
123                    Element requestIdElement = doc.createElementNS( CSWNS.toString(), "csw:RequestId" );
124                    requestIdElement.appendChild( doc.createTextNode( response.getRequest().getId() ) );
125                    rootElement.appendChild( requestIdElement );
126                }
127    
128                // 'SearchStatus'-element (required)
129                Element searchStatusElement = doc.createElementNS( CSWNS.toString(), "csw:SearchStatus" );
130                // 'status'-attribute (required)
131                searchStatusElement.setAttribute( "status", response.getSearchStatus().getStatus() );
132                // 'timestamp'-attribute (optional)
133                if ( response.getSearchStatus().getTimestamp() != null ) {
134                    Date date = response.getSearchStatus().getTimestamp();
135                    String time = TimeTools.getISOFormattedTime( date );
136                    searchStatusElement.setAttribute( "timestamp", time );
137                }
138                rootElement.appendChild( searchStatusElement );
139    
140                // 'SeachResults'-element (required)
141                Element searchResultsElement = doc.createElementNS( CSWNS.toString(), "csw:SearchResults" );
142                SearchResults results = response.getSearchResults();
143    
144                // 'resultSetId'-attribute (optional)
145                if ( results.getResultSetId() != null ) {
146                    searchResultsElement.setAttribute( "resultSetId", results.getResultSetId().toString() );
147                }
148                // 'elementSet'-attribute (optional)
149                if ( results.getElementSet() != null ) {
150                    searchResultsElement.setAttribute( "elementSet", results.getElementSet().toString() );
151                }
152                // 'recordSchema'-attribute (optional)
153                if ( results.getRecordSchema() != null ) {
154                    searchResultsElement.setAttribute( "recordSchema", results.getRecordSchema().toString() );
155                }
156                // 'numberOfRecordsMatched'-attribute (required)
157                searchResultsElement.setAttribute( "numberOfRecordsMatched", "" + results.getNumberOfRecordsMatched() );
158                // 'numberOfRecordsReturned'-attribute (required)
159                searchResultsElement.setAttribute( "numberOfRecordsReturned", "" + results.getNumberOfRecordsReturned() );
160                // 'nextRecord'-attribute (required)
161                searchResultsElement.setAttribute( "nextRecord", "" + results.getNextRecord() );
162                // 'expires'-attribute (optional)
163                if ( results.getExpires() != null ) {
164                    Date date = results.getExpires();
165                    String time = TimeTools.getISOFormattedTime( date );
166                    searchResultsElement.setAttribute( "expires", time );
167                }
168                // append all children of the records container node
169                NodeList nl = results.getRecords().getChildNodes();
170                for ( int i = 0; i < nl.getLength(); i++ ) {
171                    Node copy = doc.importNode( nl.item( i ), true );
172                    searchResultsElement.appendChild( copy );
173                }
174                rootElement.appendChild( searchResultsElement );
175            } catch ( Exception e ) {
176                LOG.logError( e.getMessage(), e );
177                throw new XMLException( e.getMessage() );
178            }
179            return responseDocument;
180        }
181    
182        /**
183         * Exports a instance of {@link GetRecordByIdResult} to a {@link GetRecordByIdResultDocument}.
184         * 
185         * @param response
186         * @return
187         * @throws XMLParsingException
188         */
189        public static GetRecordByIdResultDocument export( GetRecordByIdResult response ) throws XMLException {
190    
191            GetRecordByIdResultDocument doc = new GetRecordByIdResultDocument();
192            try {
193                doc.createEmptyDocument();
194                Document owner = doc.getRootElement().getOwnerDocument();
195                if ( response != null && response.getRecord() != null ) {
196                    Node copy = owner.importNode( response.getRecord(), true );
197                    doc.getRootElement().appendChild( copy );
198                }
199            } catch ( Exception e ) {
200                LOG.logError( e.getMessage(), e );
201                throw new XMLException( e.getMessage() );
202            }
203    
204            return doc;
205        }
206    
207        /**
208         * Exports a <code>DescribeRecordResponse</code> instance to a <code>DescribeRecordResponseDocument</code>.
209         * 
210         * @param response
211         * @return DOM representation of the <code>DescribeRecordResponse</code>
212         * @throws IOException
213         *             if XML template could not be loaded
214         */
215        public static DescribeRecordResultDocument export( DescribeRecordResult response ) throws XMLException {
216    
217            DescribeRecordResultDocument responseDocument = new DescribeRecordResultDocument();
218    
219            try {
220                responseDocument.createEmptyDocument();
221                Element rootElement = responseDocument.getRootElement();
222                Document doc = rootElement.getOwnerDocument();
223    
224                // 'SchemaComponent'-elements (required)
225                SchemaComponent[] components = response.getSchemaComponents();
226                for ( int i = 0; i < components.length; i++ ) {
227                    Element schemaComponentElement = doc.createElementNS( CSWNS.toString(), "csw:SchemaComponent" );
228    
229                    // 'targetNamespace'-attribute (required)
230                    schemaComponentElement.setAttribute( "targetNamespace", components[i].getTargetNamespace().toString() );
231    
232                    // 'parentSchema'-attribute (optional)
233                    if ( components[i].getParentSchema() != null ) {
234                        schemaComponentElement.setAttribute( "parentSchema", components[i].getParentSchema().toString() );
235                    }
236    
237                    // 'schemaLanguage'-attribute (required)
238                    schemaComponentElement.setAttribute( "schemaLanguage", components[i].getSchemaLanguage().toString() );
239    
240                    XMLTools.insertNodeInto( components[i].getSchema().getRootElement(), schemaComponentElement );
241                    rootElement.appendChild( schemaComponentElement );
242                }
243            } catch ( Exception e ) {
244                LOG.logError( e.getMessage(), e );
245                throw new XMLException( e.getMessage() );
246            }
247            return responseDocument;
248        }
249    
250        /**
251         * Exports a <code>GetRecords</code> instance to a <code>GetRecordsDocument</code>.
252         * 
253         * @param request
254         * @return DOM representation of the <code>GetRecords</code>
255         * @throws XMLException
256         *             if some elements could not be appended
257         * @throws OGCWebServiceException
258         *             if an error occurred while creating the xml-representation of the GetRecords bean.
259         */
260        public static GetRecordsDocument export( GetRecords request ) throws XMLException,
261                                                                     OGCWebServiceException {
262    
263            GetRecordsDocument getRecordsDocument = new GetRecordsDocument();
264            try {
265                getRecordsDocument.createEmptyDocument();
266            } catch ( Exception e ) {
267                throw new XMLException( e.getMessage() );
268            }
269            Element rootElement = getRecordsDocument.getRootElement();
270            Document doc = rootElement.getOwnerDocument();
271    
272            // 'version'-attribute
273            rootElement.setAttribute( "version", request.getVersion() );
274    
275            // 'resultType'-attribute
276            rootElement.setAttribute( "resultType", request.getResultTypeAsString() );
277    
278            // 'outputFormat'-attribute
279            rootElement.setAttribute( "outputFormat", request.getOutputFormat() );
280    
281            // 'outputSchema'-attribute
282            rootElement.setAttribute( "outputSchema", request.getOutputSchema() );
283    
284            // 'startPosition'-attribute
285            rootElement.setAttribute( "startPosition", "" + request.getStartPosition() );
286    
287            // 'maxRecords'-attribute
288            rootElement.setAttribute( "maxRecords", "" + request.getMaxRecords() );
289    
290            // '<csw:DistributedSearch>'-element
291            if ( request.getHopCount() != -1 ) {
292                Element distributedSearchElement = doc.createElementNS( CSWNS.toString(), "csw:DistributedSearch" );
293    
294                // 'hopCount'-attribute
295                distributedSearchElement.setAttribute( "hopCount", "" + request.getHopCount() );
296                rootElement.appendChild( distributedSearchElement );
297            }
298    
299            // '<csw:ResponseHandler>'-elements (optional)
300            URI responseHandler = request.getResponseHandler();
301            if ( responseHandler != null ) {
302                Element responseHandlerElement = doc.createElementNS( CSWNS.toString(), "csw:ResponseHandler" );
303                responseHandlerElement.appendChild( doc.createTextNode( responseHandler.toASCIIString() ) );
304                rootElement.appendChild( responseHandlerElement );
305    
306            }
307    
308            // '<csw:Query>'-elements (required)
309            Query query = request.getQuery();
310            if ( query != null ) {
311                LOG.logDebug( "Adding the csw:Query element to the csw:GetRecords document" );
312                Element queryElement = doc.createElementNS( CSWNS.toString(), "csw:Query" );
313    
314                // 'typeName'-attribute
315                // Testing for the list of typenames.
316                List<QualifiedName> typeNames = query.getTypeNamesAsList();
317                Map<String, QualifiedName> aliases = new HashMap<String, QualifiedName>( query.getDeclaredTypeNameVariables() );
318                if ( typeNames.size() > 0 ) {
319                    appendTypeNamesAttribute( rootElement, queryElement, typeNames, aliases );
320                } else {
321    
322                    String s = StringTools.arrayToString( query.getTypeNames(), ',' );
323                    queryElement.setAttribute( "typeNames", s );
324                }
325    
326                // '<csw:ElementSetName>'-element (optional)
327                if ( query.getElementSetName() != null ) {
328                    Element elementSetNameElement = doc.createElementNS( CSWNS.toString(), "csw:ElementSetName" );
329                    List<QualifiedName> elementSetNameTypeNamesList = query.getElementSetNameTypeNamesList();
330                    if ( query.getElementSetNameVariables() != null && query.getElementSetNameVariables().size() > 0 ) {
331                        throw new OGCWebServiceException( "The elementSetName element in a csw:GetRecords request may not refrerence variables (aka. aliases), aborting request" );
332                    }
333                    if ( elementSetNameTypeNamesList.size() > 0 ) {
334                        appendTypeNamesAttribute( rootElement, elementSetNameElement, elementSetNameTypeNamesList, null );
335                    }
336                    elementSetNameElement.appendChild( doc.createTextNode( query.getElementSetName() ) );
337                    queryElement.appendChild( elementSetNameElement );
338                }
339    
340                // '<csw:ElementName>'-elements (optional)
341                if ( query.getElementNamesAsPropertyPaths() != null ) {
342                    List<PropertyPath> elementNames = query.getElementNamesAsPropertyPaths();
343                    for ( int j = 0; j < elementNames.size(); j++ ) {
344                        Element elementNameElement = doc.createElementNS( CSWNS.toString(), "csw:ElementName" );
345                        elementNameElement.appendChild( doc.createTextNode( elementNames.get( j ).getAsString() ) );
346                        queryElement.appendChild( elementNameElement );
347                    }
348                }
349    
350                // '<csw:Constraint>'-element (optional)
351                if ( query.getContraint() != null ) {
352                    Element constraintElement = doc.createElementNS( CSWNS.toString(), "csw:Constraint" );
353                    constraintElement.setAttribute( "version", "1.0.0" );
354                    org.deegree.model.filterencoding.XMLFactory.appendFilter( constraintElement, query.getContraint() );
355                    queryElement.appendChild( constraintElement );
356                }
357    
358                // '<ogc:SortBy>'-element (optional)
359                SortProperty[] sortProperties = query.getSortProperties();
360                if ( sortProperties != null && sortProperties.length != 0 ) {
361                    Element sortByElement = doc.createElementNS( OGCNS.toString(), "ogc:SortBy" );
362    
363                    // '<ogc:SortProperty>'-elements
364                    for ( int j = 0; j < sortProperties.length; j++ ) {
365                        Element sortPropertiesElement = doc.createElementNS( OGCNS.toString(), "ogc:SortProperty" );
366    
367                        // '<ogc:PropertyName>'-element (required)
368                        Element propertyNameElement = doc.createElementNS( OGCNS.toString(), "ogc:PropertyName" );
369                        appendPropertyPath( propertyNameElement, sortProperties[j].getSortProperty() );
370    
371                        // '<ogc:SortOrder>'-element (optional)
372                        Element sortOrderElement = doc.createElementNS( OGCNS.toString(), "ogc:SortOrder" );
373                        Node tn = doc.createTextNode( sortProperties[j].getSortOrder() ? "ASC" : "DESC" );
374                        sortOrderElement.appendChild( tn );
375    
376                        sortPropertiesElement.appendChild( propertyNameElement );
377                        sortPropertiesElement.appendChild( sortOrderElement );
378                        sortByElement.appendChild( sortPropertiesElement );
379                    }
380                    queryElement.appendChild( sortByElement );
381                }
382                rootElement.appendChild( queryElement );
383            }
384            return getRecordsDocument;
385        }
386    
387        /**
388         * 
389         * @param rootElement
390         *            the first node of the Docuement
391         * @param toBeInserted
392         *            to which the typeNames attribute will be appended
393         * @param typeNames
394         *            to be inserted into the toBeInserted element
395         * @param aliases
396         *            may be <code>null</code>, if not each typeName must have exactly one alias defined, which will be
397         *            inserted after the typename (e.g. typename=$o). If map must contain a mapping from variable to
398         *            qualifiedName (e.g. [o, typeName]);
399         */
400        protected static void appendTypeNamesAttribute( Element rootElement,
401                                                        Element toBeInserted,
402                                                        List<QualifiedName> typeNames,
403                                                        Map<String, QualifiedName> aliases ) {
404            if ( !typeNames.isEmpty() ) {
405                for ( QualifiedName qName : typeNames ) {
406                    LOG.logDebug ( "found typeName: " + qName );
407                }
408                LOG.logDebug( "for the element: " + toBeInserted.getNodeName()
409                                    + " we are trying to set the typeNames attribute." );
410                StringBuffer sb = new StringBuffer( );
411                int count = 0;
412                for ( QualifiedName qName : typeNames ) {
413                    if ( qName.getLocalName() != null ) {
414                        URI ns = qName.getNamespace();
415                        String prefix = qName.getPrefix();
416                        if ( ns != null && prefix != null ) {
417                            URI boundNS = null;
418                            try {
419                                boundNS = XMLTools.getNamespaceForPrefix( prefix, toBeInserted );
420                            } catch ( URISyntaxException e ) {
421                                // why for crying out loud an UriSyntax exception while lookin up stuff (without giving
422                                // an
423                                // uri).
424                            }
425                            LOG.logDebug( "ElementSetName/@typeNames: Found the namespace " + boundNS
426                                          + " for the prefix: "
427                                          + prefix
428                                          + " from typename (localname) : "
429                                          + qName.getLocalName() );
430                            if ( boundNS == null ) {
431                                if ( CommonNamespaces.OASIS_EBRIMNS.equals( ns ) ) {
432                                    XMLTools.appendNSBinding( rootElement, "rim", ns );
433                                    LOG.logDebug( toBeInserted.getLocalName() + "/@typeName: While no namespace was bound to the prefix: "
434                                                  + prefix
435                                                  + " the namespace: "
436                                                  + ns
437                                                  + " has been bound to 'rim' in the the root element." );
438                                } else {
439                                    XMLTools.appendNSBinding( rootElement, prefix, ns );
440                                    LOG.logDebug( toBeInserted.getLocalName() + "/@typeName: While no namespace was bound to the prefix: "
441                                                  + prefix
442                                                  + " the namespace: "
443                                                  + ns
444                                                  + " has been bound to '"
445                                                  + prefix
446                                                  + "' in the the root element." );
447                                }
448    
449                            }
450                        }
451                        String typeName = prefix;
452                        if ( typeName != null ) {
453                            typeName += ":" + qName.getLocalName();
454                        }
455                        sb.append( typeName );
456                        if ( aliases != null ) {
457                            if ( aliases.containsValue( qName ) ) {
458                                Set<String> keys = aliases.keySet();
459                                for ( String key : keys ) {
460                                    if ( aliases.get( key ).equals( qName ) ) {
461                                        sb.append( "=" ).append( key );
462                                        aliases.remove( key );
463                                        break;
464                                    }
465                                }
466                            } else if ( aliases.size() > 0 ) {
467                                LOG.logError( "No variable mapping found for typename: " + typeName
468                                              + " this may not be, because every single typename must or no typename may have a variable!" );
469                            }
470                        }
471                        if ( ++count < typeNames.size() ) {
472                            sb.append( " " );
473                        }
474                    }
475                }
476                if ( !"null".equals( sb.toString().trim() ) && !"".equals( sb.toString().trim() ) ) {
477                    LOG.logDebug( "for the element: " + toBeInserted.getNodeName()
478                                        + " we are settin the typeNames attribute to: "
479                                        + sb.toString() );
480                    toBeInserted.setAttribute( "typeNames", sb.toString() );
481                }
482            }
483        }
484    
485        /**
486         * Exports a <code>GetRecordById</code> instance to a <code>GetRecordByIdDocument</code>.
487         * 
488         * @param request
489         * @return DOM representation of the <code>GetRecordById</code>
490         * @throws IOException
491         *             if XML template could not be loaded
492         */
493        public static GetRecordByIdDocument export( GetRecordById request ) throws XMLException {
494    
495            GetRecordByIdDocument getRecordByIdDoc = new GetRecordByIdDocument();
496            try {
497                getRecordByIdDoc.createEmptyDocument();
498            } catch ( Exception e ) {
499                throw new XMLException( e.getMessage() );
500            }
501            Element rootElement = getRecordByIdDoc.getRootElement();
502            Document doc = rootElement.getOwnerDocument();
503    
504            // 'version'-attribute
505            rootElement.setAttribute( "version", request.getVersion() );
506    
507            String[] ids = request.getIds();
508            for ( int i = 0; i < ids.length; i++ ) {
509                Element idElement = doc.createElementNS( CSWNS.toString(), "csw:Id" );
510                idElement.appendChild( doc.createTextNode( ids[i] ) );
511                rootElement.appendChild( idElement );
512            }
513    
514            String elementSetName = request.getElementSetName();
515            if ( elementSetName != null ) {
516                Element esnElement = doc.createElementNS( CSWNS.toString(), "csw:ElementSetName" );
517                esnElement.appendChild( doc.createTextNode( elementSetName ) );
518                rootElement.appendChild( esnElement );
519            }
520    
521            return getRecordByIdDoc;
522        }
523    }