001    //$HeadURL: $
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    
037    package org.deegree.ogcwebservices.wcts;
038    
039    import static org.deegree.framework.xml.XMLTools.appendElement;
040    import static org.deegree.ogcbase.CommonNamespaces.DEEGREEWCTS;
041    import static org.deegree.ogcbase.CommonNamespaces.DEEGREEWCTS_PREFIX;
042    import static org.deegree.ogcbase.CommonNamespaces.OWSNS_1_1_0;
043    import static org.deegree.ogcbase.CommonNamespaces.WCTSNS;
044    import static org.deegree.ogcbase.CommonNamespaces.WCTS_PREFIX;
045    import static org.deegree.ogcbase.CommonNamespaces.XLINK_PREFIX;
046    import static org.deegree.ogcbase.CommonNamespaces.XLNNS;
047    import static org.deegree.ogcwebservices.wcts.operation.Transform.INLINE;
048    import static org.deegree.ogcwebservices.wcts.operation.Transform.MULTIPART;
049    
050    import java.io.IOException;
051    import java.util.List;
052    import java.util.Map;
053    
054    import javax.vecmath.Point3d;
055    
056    import org.deegree.crs.transformations.Transformation;
057    import org.deegree.framework.log.ILogger;
058    import org.deegree.framework.log.LoggerFactory;
059    import org.deegree.framework.util.Pair;
060    import org.deegree.framework.xml.XMLFragment;
061    import org.deegree.framework.xml.XMLTools;
062    import org.deegree.model.crs.CoordinateSystem;
063    import org.deegree.model.feature.FeatureCollection;
064    import org.deegree.model.feature.FeatureException;
065    import org.deegree.model.feature.GMLFeatureAdapter;
066    import org.deegree.model.spatialschema.GMLGeometryAdapter;
067    import org.deegree.model.spatialschema.Geometry;
068    import org.deegree.model.spatialschema.GeometryException;
069    import org.deegree.ogcwebservices.wcts.capabilities.Content;
070    import org.deegree.ogcwebservices.wcts.capabilities.CoverageAbilities;
071    import org.deegree.ogcwebservices.wcts.capabilities.FeatureAbilities;
072    import org.deegree.ogcwebservices.wcts.capabilities.InputOutputFormat;
073    import org.deegree.ogcwebservices.wcts.capabilities.WCTSCapabilities;
074    import org.deegree.ogcwebservices.wcts.capabilities.mdprofiles.MetadataProfile;
075    import org.deegree.ogcwebservices.wcts.capabilities.mdprofiles.TransformationMetadata;
076    import org.deegree.ogcwebservices.wcts.data.FeatureCollectionData;
077    import org.deegree.ogcwebservices.wcts.data.GeometryData;
078    import org.deegree.ogcwebservices.wcts.data.SimpleData;
079    import org.deegree.ogcwebservices.wcts.data.TransformableData;
080    import org.deegree.ogcwebservices.wcts.operation.GetResourceByID;
081    import org.deegree.ogcwebservices.wcts.operation.TransformResponse;
082    import org.deegree.owscommon_1_1_0.Manifest;
083    import org.deegree.owscommon_1_1_0.Metadata;
084    import org.w3c.dom.Document;
085    import org.w3c.dom.Element;
086    import org.w3c.dom.Node;
087    import org.xml.sax.SAXException;
088    
089    /**
090     * The <code>XMLFactory</code> provides helper methods to create xml-doc representations of bean encapsulations.
091     *
092     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
093     *
094     * @author last edited by: $Author:$
095     *
096     * @version $Revision:$, $Date:$
097     *
098     */
099    public class XMLFactory {
100    
101        private static ILogger LOG = LoggerFactory.getLogger( XMLFactory.class );
102    
103        private static final String PRE = WCTS_PREFIX + ":";
104    
105        private static XMLFragment capabilitiesElement = null;
106    
107        private static final String MUTEX = "EMPTY";
108    
109        /**
110         * Exports an GetResourceById bean to xml.
111         *
112         * @param resourceByID
113         * @return a dom-representation.
114         */
115        public static XMLFragment create( GetResourceByID resourceByID ) {
116            return null;
117        }
118    
119        /**
120         * Exports an WCTSCapabilies bean to xml.
121         *
122         * @param capabilities
123         *            to be exported.
124         * @return an xml-dom-representation of the given bean or <code>null</code> if the given parameter is
125         *         <code>null</code>.
126         *
127         */
128        public static XMLFragment create( WCTSCapabilities capabilities ) {
129            if ( capabilities == null ) {
130                return null;
131            }
132            if ( capabilitiesElement == null ) {
133                synchronized ( MUTEX ) {
134                    if ( capabilitiesElement == null ) {
135                        org.deegree.owscommon_1_1_0.XMLFactory fac = new org.deegree.owscommon_1_1_0.XMLFactory();
136                        Document doc = XMLTools.create();
137                        Element root = doc.createElementNS( WCTSNS.toASCIIString(), PRE + "Capabilities" );
138                        capabilitiesElement = new XMLFragment( root );
139                        fac.exportCapabilities( root, capabilities );
140                        Content content = capabilities.getContents();
141                        if ( content != null ) {
142                            appendCapabilitiesContent( root, content );
143                        }
144                    }
145                    try {
146                        MUTEX.notifyAll();
147                    } catch ( IllegalMonitorStateException e ) {
148                        // nottin
149                    }
150                }
151            }
152            return capabilitiesElement;
153        }
154    
155        /**
156         * Creates a response to a Transform request. The wcts spec defines it to be a ows_1_1_0:OperationResponse, this
157         * method appends the deegreewcts:MultiParts or the deegreewcts:InlineData element(s) to the root node.
158         *
159         * @param transformResponse
160         *            to create.
161         * @param useDeegreeModel
162         *            true if the transform response element should be embedded into inline/multipart elements.
163         * @return the ows_1_1_0:OperationResponse with deegreewcts:MultiPart element added or <code>null</code> if the
164         *         given param is <code>null</code>.
165         */
166        public static XMLFragment createResponse( TransformResponse transformResponse, boolean useDeegreeModel ) {
167            if ( transformResponse == null ) {
168                return null;
169            }
170            org.deegree.owscommon_1_1_0.XMLFactory owsFac = new org.deegree.owscommon_1_1_0.XMLFactory();
171            Element dataElement = null;
172            LOG.logDebug( "Creating tranform operation response." );
173            Document doc = XMLTools.create();
174            Element root = doc.createElementNS( DEEGREEWCTS.toASCIIString(), PRE + "OperationResponse" );
175            XMLFragment result = new XMLFragment( root );
176            createOperationResponse( owsFac, result.getRootElement(), transformResponse.getInputData() );
177            if ( useDeegreeModel ) {
178                switch ( transformResponse.getDataPresentation() ) {
179                case INLINE:
180                    LOG.logDebug( "Creating tranform operation inline data response." );
181                    dataElement = appendElement( result.getRootElement(), DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":InlineData" );
182                    break;
183                case MULTIPART: // fall through
184                default:
185                    LOG.logDebug( "Creating tranform operation multipart data response." );
186                    // result = owsFac.createOperationResponse( transformResponse.getInputData() );
187                    dataElement = appendElement( result.getRootElement(), DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":MultiParts" );
188                    break;
189                }
190            } else {
191                dataElement = root;
192            }
193            appendTransformableData( dataElement, transformResponse, useDeegreeModel );
194            return result;
195        }
196    
197        /**
198         * Will create an XMLFragment which holds the d_wcts:OperationResponse as the root element, values from the given
199         * manifest will be appended by the given ows_1_1 XMLFactory.
200         *
201         * @param owsFactory
202         *            an instance of the ows_1-1 XMLFactory
203         * @param root
204         *            to append the manifest to.
205         *
206         * @param operationResponse
207         *            to create the dom-xml representation from.
208         */
209        public static void createOperationResponse( org.deegree.owscommon_1_1_0.XMLFactory owsFactory, Element root,
210                                                    Manifest operationResponse ) {
211            if ( operationResponse == null ) {
212                return;
213            }
214            // Document doc = XMLTools.create();
215            // Element root = doc.createElementNS( DEEGREEWCTS.toASCIIString(), PRE + "OperationResponse" );
216            owsFactory.appendManifest( root, operationResponse );
217            // return new XMLFragment( root );
218        }
219    
220        /**
221         * Appends the TransformableData bean, as an xml-dom element to the given root. If either one of the parameters is
222         * <code>null</code>, this method just returns.
223         *
224         * @param root
225         *            to append to.
226         * @param response
227         *            to get the transformable data from.
228         * @param useDeegreeModel
229         *            true if the transform response element should be embedded into inline/multipart elements.
230         */
231        protected static void appendTransformableData( Element root, TransformResponse response, boolean useDeegreeModel ) {
232            if ( root == null || response == null ) {
233                return;
234            }
235    
236            TransformableData<?> transformableData = response.getTransformableData();
237            LOG.logDebug( "Appending transformable data. " );
238            if ( transformableData instanceof SimpleData ) {
239                appendSimpleData( root, response.getTargetCRS().getDimension(), (SimpleData) transformableData,
240                                  useDeegreeModel );
241            } else if ( transformableData instanceof GeometryData ) {
242                appendGeometryData( root, (GeometryData) transformableData, useDeegreeModel );
243            } else if ( transformableData instanceof FeatureCollectionData ) {
244                appendFeatureCollectionData( root, (FeatureCollectionData) transformableData, useDeegreeModel );
245            }
246        }
247    
248        /**
249         * Appends a dom-xml document element with the name {http://www.deegree.org/wcts}:FeatureCollectionData. It will
250         * contain all transformed FeaturCollection as it's children. Or if no FeatureCollections were transformed this
251         * element will have no children at all.
252         * <p>
253         * If either one of the parameters is <code>null</code>, this method just returns.
254         * </p>
255         *
256         * @param root
257         *            to append to.
258         * @param transformableData
259         *            to append.
260         * @param useDeegreeModel
261         *            true if the transform response element should be embedded into inline/multipart elements.
262         *
263         */
264        protected static void appendFeatureCollectionData( Element root, FeatureCollectionData transformableData,
265                                                           boolean useDeegreeModel ) {
266            if ( root == null || transformableData == null ) {
267                return;
268            }
269            LOG.logDebug( "Adding tranformed feature collection data." );
270            Element featureCollectionElement = null;
271            if ( useDeegreeModel ) {
272                featureCollectionElement = appendElement( root, DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":FeatureCollectionData" );
273            } else {
274                featureCollectionElement = root;
275            }
276            GMLFeatureAdapter ad = new GMLFeatureAdapter();
277            List<FeatureCollection> transformedData = transformableData.getTransformedData();
278            if ( transformedData != null && transformedData.size() >= 0 ) {
279                for ( FeatureCollection featureCollection : transformedData ) {
280                    if ( featureCollection != null ) {
281                        try {
282                            ad.append( featureCollectionElement, featureCollection );
283                        } catch ( FeatureException e ) {
284                            LOG.logError( e.getMessage(), e );
285                        } catch ( IOException e ) {
286                            LOG.logError( e.getMessage(), e );
287                        } catch ( SAXException e ) {
288                            LOG.logError( e.getMessage(), e );
289                        }
290                    }
291                }
292            }
293        }
294    
295        /**
296         * Appends a dom-xml document element with the name {http://www.deegree.org/wcts}:GeometryData. It it will contain
297         * all transformed Geometries as it's children. Or if no Geometries were transformed this element will have no
298         * children at all.
299         * <p>
300         * If either one of the parameters is <code>null</code>, this method just returns.
301         * </p>
302         *
303         * @param root
304         *            to append to.
305         * @param transformableData
306         *            to append.
307         * @param useDeegreeModel
308         *            true if the transform response element should be embedded into inline/multipart elements.
309         *
310         */
311        protected static void appendGeometryData( Element root, GeometryData transformableData, boolean useDeegreeModel ) {
312            if ( root == null || transformableData == null ) {
313                return;
314            }
315            LOG.logDebug( "Adding tranformed geometry data." );
316            Element geometryElement = null;
317            if ( useDeegreeModel ) {
318                geometryElement = appendElement( root, DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":GeometryData" );
319            } else {
320                geometryElement = root;
321            }
322            Document doc = geometryElement.getOwnerDocument();
323            List<Geometry> transformedGeometries = transformableData.getTransformedData();
324            if ( transformedGeometries.size() >= 0 ) {
325                for ( Geometry geom : transformedGeometries ) {
326                    if ( geom != null ) {
327                        try {
328                            StringBuffer sb = GMLGeometryAdapter.export( geom );
329                            Element tmp = XMLTools.getStringFragmentAsElement( sb.toString() );
330                            if ( tmp != null ) {
331                                tmp = (Element) doc.importNode( tmp, true );
332                                geometryElement.appendChild( tmp );
333                            }
334                        } catch ( GeometryException e ) {
335                            LOG.logError( e.getMessage(), e );
336                        } catch ( SAXException e ) {
337                            LOG.logError( e.getMessage(), e );
338                        } catch ( IOException e ) {
339                            LOG.logError( e.getMessage(), e );
340                        }
341                    }
342                }
343            }
344        }
345    
346        /**
347         * Appends a dom-xml document element with the name is {http://www.deegree.org/wcts}:SimpleData. It will contain the
348         * points as a separated list as defined by the 'cs' separator. The element has the attribute 'srsDimension'. The
349         * list elements can therefore be interpreted as a tuple of the value of 'srsDimension'. If no points were
350         * transformed the list will be empty.
351         * <p>
352         * If either one of the parameters is <code>null</code>, this method just returns.
353         * </p>
354         *
355         * @param root
356         *            to append to.
357         * @param targetDimension
358         *            of the target CRS
359         * @param transformableData
360         *            to append.
361         * @param useDeegreeModel
362         *            true if the transform response element should be embedded into inline/multipart elements.
363         *
364         */
365        protected static void appendSimpleData( Element root, int targetDimension, SimpleData transformableData,
366                                                boolean useDeegreeModel ) {
367            if ( root == null || transformableData == null ) {
368                return;
369            }
370            LOG.logDebug( "Adding tranformed simple data." );
371            Element simpleDataElement = null;
372            if ( useDeegreeModel ) {
373                simpleDataElement = appendElement( root, DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":SimpleData" );
374            } else {
375                simpleDataElement = root;
376            }
377            int dim = targetDimension;
378            final String ts = transformableData.getTupleSeparator();
379            simpleDataElement.setAttribute( "ts", ts );
380            final String cs = transformableData.getCoordinateSeparator();
381            simpleDataElement.setAttribute( "cs", cs );
382            List<Point3d> transformedPoints = transformableData.getTransformedData();
383            StringBuilder sb = new StringBuilder( transformedPoints.size() * dim );
384            for ( int i = 0; i < transformedPoints.size(); ++i ) {
385                Point3d point = transformedPoints.get( i );
386                if ( point != null ) {
387                    sb.append( point.x );
388                    sb.append( cs );
389                    sb.append( point.y );
390                    if ( dim == 3 ) {
391                        sb.append( cs );
392                        sb.append( point.z );
393                    }
394                    if ( ( i + 1 ) < transformedPoints.size() ) {
395                        sb.append( ts );
396                    }
397                }
398            }
399            XMLTools.setNodeValue( simpleDataElement, sb.toString() );
400        }
401    
402        /**
403         * Appends the WCTSContent bean, as an xml-dom element to the given root. If either one of the parameters is
404         * <code>null</code>, this method just returns.
405         *
406         * @param root
407         *            to append the values to.
408         * @param content
409         *            to be appended.
410         */
411        protected static void appendCapabilitiesContent( Element root, Content content ) {
412            if ( content == null || root == null ) {
413                return;
414            }
415            Element contentElement = appendElement( root, WCTSNS, PRE + "Contents" );
416            Map<String, Transformation> transformations = content.getTransformations();
417            if ( transformations != null && transformations.size() > 0 ) {
418                for ( String transform : transformations.keySet() ) {
419                    if ( transform != null ) {
420                        appendElement( contentElement, WCTSNS, PRE + "Transformation", transform );
421                    }
422                }
423            }
424    
425            List<String> methods = content.getMethods();
426            if ( methods != null && methods.size() > 0 ) {
427                for ( String s : methods ) {
428                    if ( s != null ) {
429                        appendElement( contentElement, WCTSNS, PRE + "Method", s );
430                    }
431                }
432            }
433    
434            List<CoordinateSystem> sourceCRSs = content.getSourceCRSs();
435            if ( sourceCRSs != null && sourceCRSs.size() > 0 ) {
436                for ( CoordinateSystem s : sourceCRSs ) {
437                    if ( s != null ) {
438                        appendElement( contentElement, WCTSNS, PRE + "SourceCRS", s.getIdentifier() );
439                    }
440                }
441            }
442    
443            List<CoordinateSystem> targetCRSs = content.getTargetCRSs();
444            if ( targetCRSs != null && targetCRSs.size() > 0 ) {
445                for ( CoordinateSystem s : targetCRSs ) {
446                    if ( s != null ) {
447                        appendElement( contentElement, WCTSNS, PRE + "TargetCRS", s.getIdentifier() );
448                    }
449                }
450            }
451    
452            CoverageAbilities cAbilities = content.getCoverageAbilities();
453            if ( cAbilities != null ) {
454                Element caElement = appendElement( contentElement, WCTSNS, PRE + "CoverageAbilities" );
455                List<Pair<String, String>> coverageTypes = cAbilities.getCoverageTypes();
456                if ( coverageTypes != null && coverageTypes.size() > 0 ) {
457                    for ( Pair<String, String> values : coverageTypes ) {
458                        if ( values != null ) {
459                            Element ctElement = appendElement( caElement, WCTSNS, PRE + "CoverageType", values.first );
460                            ctElement.setAttribute( "codeSpace", values.second );
461                        }
462                    }
463                }
464                List<InputOutputFormat> cFormats = cAbilities.getCoverageFormats();
465                if ( cFormats != null && cFormats.size() > 0 ) {
466                    for ( InputOutputFormat iof : cFormats ) {
467                        appendInputOutput( caElement, iof, "CoverageFormat" );
468                    }
469                }
470                List<Element> interpolationMethods = cAbilities.getInterPolationMethods();
471                if ( interpolationMethods != null && interpolationMethods.size() > 0 ) {
472                    for ( Element element : interpolationMethods ) {
473                        if ( element != null ) {
474                            Element copyOf = (Element) caElement.getOwnerDocument().importNode( element, true );
475                            caElement.appendChild( copyOf );
476                        }
477                    }
478                }
479    
480            }
481            FeatureAbilities fAbilities = content.getFeatureAbilities();
482            if ( fAbilities != null ) {
483                Element faElement = appendElement( contentElement, WCTSNS, PRE + "FeatureAbilities" );
484                List<Pair<String, String>> geometryTypes = fAbilities.getGeometryTypes();
485                if ( geometryTypes != null && geometryTypes.size() > 0 ) {
486                    for ( Pair<String, String> values : geometryTypes ) {
487                        if ( values != null ) {
488                            Element ctElement = appendElement( faElement, WCTSNS, PRE + "GeometryType", values.first );
489                            ctElement.setAttribute( "codeSpace", values.second );
490                        }
491                    }
492                }
493                List<InputOutputFormat> fFormats = fAbilities.getFeatureFormats();
494                if ( fFormats != null && fFormats.size() > 0 ) {
495                    for ( InputOutputFormat iof : fFormats ) {
496                        appendInputOutput( faElement, iof, "FeatureFormat" );
497                    }
498                }
499                faElement.setAttribute( "remoteProperties", fAbilities.getRemoteProperties() ? "true" : "false" );
500            }
501    
502            List<Metadata> metadataList = content.getMetadata();
503            if ( metadataList != null && metadataList.size() > 0 ) {
504                for ( Metadata attrib : metadataList ) {
505                    if ( attrib != null ) {
506                        Element mdElement = createMetadataRoot( contentElement, attrib );
507                        if ( mdElement != null ) {
508                            Element abst = attrib.getAbstractElement();
509                            if ( abst != null ) {
510                                Node n = mdElement.getOwnerDocument().importNode( abst, true );
511                                mdElement.appendChild( n );
512                            }
513                        }
514                    }
515                }
516            }
517            if ( content.getTransformMetadata() != null ) {
518                for ( MetadataProfile<?> mp : content.getTransformMetadata() ) {
519                    if ( mp != null && ( mp instanceof TransformationMetadata ) ) {
520                        Element metadata = appendElement( contentElement, OWSNS_1_1_0, PRE + "Metadata" );
521                        TransformationMetadata tmd = (TransformationMetadata) mp;
522                        Element tmElem = appendElement( metadata, DEEGREEWCTS, DEEGREEWCTS_PREFIX
523                                                                               + ":transformationMetadata" );
524                        tmElem.setAttribute( "sourceCRS", tmd.getSourceCRS().getIdentifier() );
525                        tmElem.setAttribute( "targetCRS", tmd.getTargetCRS().getIdentifier() );
526                        tmElem.setAttribute( "transformationID", tmd.getTransformID() );
527                        String desc = tmd.getDescription();
528                        if ( desc == null || "".equals( desc ) ) {
529                            desc = "No description";
530                        }
531                        Element description = appendElement( tmElem, DEEGREEWCTS, DEEGREEWCTS_PREFIX + ":description" );
532                        XMLTools.setNodeValue( description, desc );
533                    }
534                }
535            }
536    
537            contentElement.setAttribute( "userDefinedCRSs", content.supportsUserDefinedCRS() ? "true" : "false" );
538        }
539    
540        private static Element createMetadataRoot( Element root, Metadata md ) {
541            if ( root == null || md == null ) {
542                return null;
543            }
544            Element metadata = appendElement( root, OWSNS_1_1_0, PRE + "Metadata" );
545            if ( md.getMetadataHref() != null ) {
546                metadata.setAttributeNS( XLNNS.toASCIIString(), XLINK_PREFIX + ":href", md.getMetadataHref() );
547            }
548            if ( md.getMetadataAbout() != null ) {
549                metadata.setAttribute( "about", md.getMetadataAbout() );
550            }
551            return metadata;
552        }
553    
554        /**
555         * Appends the input output element
556         *
557         * @param root
558         *            to append to
559         * @param inputOutput
560         *            to append
561         * @param nodeName
562         *            the element name, i.e. CoverageFormat or FeatureFormat
563         */
564        private static void appendInputOutput( Element root, InputOutputFormat inputOutput, String nodeName ) {
565            if ( inputOutput != null && root != null ) {
566                Element cfElement = appendElement( root, WCTSNS, PRE + nodeName, inputOutput.getValue() );
567                cfElement.setAttribute( "input", inputOutput.canInput() ? "true" : "false" );
568                cfElement.setAttribute( "output", inputOutput.canOutput() ? "true" : "false" );
569    
570            }
571        }
572    }