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