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