001    /*----------------------------------------------------------------------------
002     This file is part of deegree, http://deegree.org/
003     Copyright (C) 2001-2009 by:
004       Department of Geography, University of Bonn
005     and
006       lat/lon GmbH
007    
008     This library is free software; you can redistribute it and/or modify it under
009     the terms of the GNU Lesser General Public License as published by the Free
010     Software Foundation; either version 2.1 of the License, or (at your option)
011     any later version.
012     This library is distributed in the hope that it will be useful, but WITHOUT
013     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
014     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
015     details.
016     You should have received a copy of the GNU Lesser General Public License
017     along with this library; if not, write to the Free Software Foundation, Inc.,
018     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019    
020     Contact information:
021    
022     lat/lon GmbH
023     Aennchenstr. 19, 53177 Bonn
024     Germany
025     http://lat-lon.de/
026    
027     Department of Geography, University of Bonn
028     Prof. Dr. Klaus Greve
029     Postfach 1147, 53001 Bonn
030     Germany
031     http://www.geographie.uni-bonn.de/deegree/
032    
033     e-mail: info@deegree.org
034    ----------------------------------------------------------------------------*/
035    
036    package org.deegree.ogcwebservices.wcts.operation;
037    
038    import static org.deegree.framework.xml.XMLTools.getElement;
039    import static org.deegree.framework.xml.XMLTools.getElements;
040    import static org.deegree.framework.xml.XMLTools.getNodeAsBoolean;
041    import static org.deegree.framework.xml.XMLTools.getNodeAsString;
042    import static org.deegree.framework.xml.XMLTools.getStringValue;
043    import static org.deegree.ogcbase.CommonNamespaces.DEEGREEWCTS;
044    import static org.deegree.ogcbase.CommonNamespaces.DEEGREEWCTS_PREFIX;
045    import static org.deegree.ogcbase.CommonNamespaces.GML_PREFIX;
046    import static org.deegree.ogcbase.CommonNamespaces.OWS_1_1_0PREFIX;
047    import static org.deegree.ogcbase.CommonNamespaces.WCS_1_2_0_PREFIX;
048    
049    import java.util.ArrayList;
050    import java.util.List;
051    
052    import javax.vecmath.Point3d;
053    
054    import org.deegree.crs.transformations.Transformation;
055    import org.deegree.framework.log.ILogger;
056    import org.deegree.framework.log.LoggerFactory;
057    import org.deegree.framework.xml.XMLParsingException;
058    import org.deegree.framework.xml.XMLTools;
059    import org.deegree.i18n.Messages;
060    import org.deegree.model.crs.CRSFactory;
061    import org.deegree.model.crs.CoordinateSystem;
062    import org.deegree.model.crs.UnknownCRSException;
063    import org.deegree.model.feature.FeatureCollection;
064    import org.deegree.model.feature.GMLFeatureCollectionDocument;
065    import org.deegree.model.spatialschema.GMLGeometryAdapter;
066    import org.deegree.model.spatialschema.Geometry;
067    import org.deegree.model.spatialschema.GeometryException;
068    import org.deegree.ogcbase.CommonNamespaces;
069    import org.deegree.ogcbase.ExceptionCode;
070    import org.deegree.ogcwebservices.OGCWebServiceException;
071    import org.deegree.ogcwebservices.wcts.WCTSExceptionCode;
072    import org.deegree.ogcwebservices.wcts.WCTService;
073    import org.deegree.ogcwebservices.wcts.WCTServiceFactory;
074    import org.deegree.ogcwebservices.wcts.capabilities.Content;
075    import org.deegree.ogcwebservices.wcts.capabilities.FeatureAbilities;
076    import org.deegree.ogcwebservices.wcts.capabilities.InputOutputFormat;
077    import org.deegree.ogcwebservices.wcts.configuration.WCTSConfiguration;
078    import org.deegree.ogcwebservices.wcts.data.FeatureCollectionData;
079    import org.deegree.ogcwebservices.wcts.data.GeometryData;
080    import org.deegree.ogcwebservices.wcts.data.SimpleData;
081    import org.deegree.ogcwebservices.wcts.data.TransformableData;
082    import org.deegree.owscommon_1_1_0.Manifest;
083    import org.deegree.owscommon_1_1_0.ManifestDocument;
084    import org.w3c.dom.Element;
085    import org.w3c.dom.Node;
086    
087    /**
088     * <code>WCTSTransformDocument</code> is a helper class which supplies a constructor to parse wcts Transform requests
089     * version 0.4.0.
090     * <p>
091     * Following elements are currently not supported:
092     * <ul>
093     * <li>wcts:transformation</li>
094     * <li>wcs:GridCRS</li>
095     * <li>wcts:InterpolationType, which is of type wcs:InterpolationMethodBaseType</li>
096     * </ul>
097     * </p>
098     *
099     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
100     *
101     * @author last edited by: $Author:$
102     *
103     * @version $Revision:$, $Date:$
104     *
105     */
106    public class TransformDocument extends WCTSRequestBaseDocument {
107        private static ILogger LOG = LoggerFactory.getLogger( TransformDocument.class );
108    
109        private static final long serialVersionUID = 1343985893563449983L;
110    
111        private final Transform transformRequest;
112    
113        /**
114         * @param requestId
115         * @param rootElement
116         *            should not be <code>null</code>
117         * @throws OGCWebServiceException
118         *             if an {@link XMLParsingException} occurred or a mandatory element/attribute is missing.
119         */
120        public TransformDocument( String requestId, Element rootElement ) throws OGCWebServiceException {
121            super( rootElement );
122            String version = parseVersion();
123    
124            // check for valid request.
125            parseService();
126    
127            try {
128                String sCRS = getNodeAsString( getRootElement(), PRE + "SourceCRS", nsContext, null );
129                String tCRS = getNodeAsString( getRootElement(), PRE + "TargetCRS", nsContext, null );
130                CoordinateSystem sourceCRS = null;
131                CoordinateSystem targetCRS = null;
132                TransformationReference transformationReference = null;
133    
134                /**
135                 * Try to parse the xml choice.
136                 */
137                if ( ( sCRS != null && tCRS == null ) || ( sCRS == null && tCRS != null ) ) {
138                    throw new OGCWebServiceException(
139                                                      Messages.getMessage(
140                                                                           "WCTS_ISTRANSFORMABLE_MISSING_CRS",
141                                                                           ( ( sCRS == null ) ? "TargetCRS" : "SourceCRS" ),
142                                                                           ( ( sCRS == null ) ? "SourceCRS" : "TargetCRS" ) ),
143                                                      ExceptionCode.INVALIDPARAMETERVALUE );
144                }
145                if ( sCRS != null && tCRS != null ) {
146                    sourceCRS = CRSFactory.create( WCTService.CRS_PROVIDER, sCRS );
147                    targetCRS = CRSFactory.create( WCTService.CRS_PROVIDER, tCRS );
148                } else {
149                    // Check for supported transformation element.
150                    Element transformation = getElement( getRootElement(), PRE + "Transformation", nsContext );
151                    if ( transformation != null ) {
152                        transformationReference = handleTransformation( transformation );
153                    }
154                    if ( transformationReference == null ) {
155                        throw new OGCWebServiceException(
156                                                          Messages.getMessage( "WCTS_NOT_VALID_XML_CHOICE",
157                                                                               PRE + "SourceCRS/TargetCRS and " + PRE
158                                                                                                       + "Transformation" ),
159                                                          ExceptionCode.MISSINGPARAMETERVALUE );
160                    }
161                }
162                // Check for not supported gridcrs.
163                Element gridCRS = getElement( getRootElement(), WCS_1_2_0_PREFIX + ":GridCRS", nsContext );
164                if ( gridCRS != null ) {
165                    throw new OGCWebServiceException( Messages.getMessage( "WCTS_OPERATION_NOT_SUPPORTED",
166                                                                           "Definition of output GridCRS ("
167                                                                                                   + WCS_1_2_0_PREFIX
168                                                                                                   + ":GridCRS element)" ),
169                                                      ExceptionCode.OPERATIONNOTSUPPORTED );
170                }
171    
172                Element inputDataElement = getElement( getRootElement(), OWS_1_1_0PREFIX + ":InputData", nsContext );
173                Manifest inputData = null;
174                TransformableData<?> transformableData = null;
175                int dataPresentation = Transform.MULTIPART;
176                if ( inputDataElement != null ) {
177                    ManifestDocument doc = new ManifestDocument();
178                    inputData = doc.parseManifestType( inputDataElement );
179                    /**
180                     * get deegree specific elements. The featurecollections provided by the mime/multiparts were put
181                     * beneath the d_wcts:InsertedMultiparts.
182                     */
183                    Element multiParts = getElement( inputDataElement, DEEGREEWCTS_PREFIX + ":MultiParts", nsContext );
184                    if ( multiParts != null ) {
185                        List<FeatureCollection> allData = parseFeatureCollectionData( multiParts );
186                        transformableData = new FeatureCollectionData( allData );
187                    }
188                } else {
189                    LOG.logDebug( "Found no " + OWS_1_1_0PREFIX
190                                  + ":InputData element, now checking for the deegree element" );
191                    inputDataElement = getElement( getRootElement(), DEEGREEWCTS_PREFIX + ":InputData", nsContext );
192                    if ( inputDataElement != null ) {
193                        ManifestDocument doc = new ManifestDocument();
194                        inputData = doc.parseManifestType( inputDataElement );
195                        Element inlineData = getElement( inputDataElement, DEEGREEWCTS_PREFIX + ":InlineData", nsContext );
196                        if ( inlineData != null ) {
197                            if ( sourceCRS == null ) {
198                                WCTSConfiguration config = WCTServiceFactory.getConfiguration();
199                                Content cont = config.getContents();
200                                if ( transformationReference == null ) {
201                                    throw new OGCWebServiceException(
202                                                                      Messages.getMessage( "WCTS_OPERATION_NOT_SUPPORTED",
203                                                                                           " transforming of simple data without a transformation or source CRS " ),
204                                                                      ExceptionCode.OPERATIONNOTSUPPORTED );
205                                }
206                                Transformation trans = cont.getTransformations().get(
207                                                                                      transformationReference.gettransformationId() );
208                                if ( trans == null || trans.getSourceCRS() == null ) {
209                                    throw new OGCWebServiceException(
210                                                                      Messages.getMessage( "WCTS_OPERATION_NOT_SUPPORTED",
211                                                                                           " transforming of simple data without a transformation or source CRS " ),
212                                                                      ExceptionCode.OPERATIONNOTSUPPORTED );
213                                }
214                                transformableData = parseInlineData( CRSFactory.create( trans.getSourceCRS() ), inlineData );
215                            } else {
216                                transformableData = parseInlineData( sourceCRS, inlineData );
217                            }
218                            dataPresentation = Transform.INLINE;
219                        } else {
220                            // Handle the xlink:href attributes of the inputdata/referencegroup/reference@xlink:href.
221                        }
222                    }
223                }
224                if ( inputData == null || transformableData == null ) {
225                    throw new OGCWebServiceException( Messages.getMessage( "WCTS_TRANSFORM_MISSING_DATA" ),
226                                                      WCTSExceptionCode.NO_INPUT_DATA );
227                }
228    
229                // Check for not supported interpolationType.
230                Element interpolationType = getElement( getRootElement(), PRE + "InterpolationType", nsContext );
231                if ( interpolationType != null ) {
232                    throw new OGCWebServiceException(
233                                                      Messages.getMessage(
234                                                                           "WCTS_OPERATION_NOT_SUPPORTED",
235                                                                           "Defining an InterpolationType ("
236                                                                                                   + PRE
237                                                                                                   + "InterpolationType element)" ),
238                                                      ExceptionCode.OPERATIONNOTSUPPORTED );
239                }
240    
241                String outputFormat = getNodeAsString( getRootElement(), PRE + "OutputFormat", nsContext, null );
242                if ( outputFormat != null && !"".equals( outputFormat.trim() )
243                     && !"text/xml; gmlVersion=3.1.1".equalsIgnoreCase( outputFormat.trim() ) ) {
244                    WCTSConfiguration config = WCTServiceFactory.getConfiguration();
245                    boolean outputFormatDefined = false;
246                    if ( config != null ) {
247                        Content content = config.getContents();
248                        if ( content != null ) {
249                            FeatureAbilities fa = content.getFeatureAbilities();
250                            if ( fa != null ) {
251                                List<InputOutputFormat> formats = fa.getFeatureFormats();
252                                if ( formats != null ) {
253                                    for ( InputOutputFormat format : formats ) {
254                                        if ( outputFormatDefined && format != null && format.canOutput() ) {
255                                            outputFormat.equals( format.getValue() );
256                                            outputFormatDefined = true;
257                                        }
258                                    }
259                                }
260                            }
261                        }
262                    }
263                    if ( !outputFormatDefined ) {
264                        throw new OGCWebServiceException( Messages.getMessage( "WCTS_REQUESTED_OUTPUTFORMAT_NOT_KNOWN",
265                                                                               outputFormat, "Transform" ),
266                                                          ExceptionCode.INVALIDPARAMETERVALUE );
267                    }
268    
269                } else {
270                    outputFormat = "text/xml";
271                }
272                boolean store = getNodeAsBoolean( getRootElement(), "@store", nsContext, true );
273                this.transformRequest = new Transform( version, requestId, store, sourceCRS, targetCRS,
274                                                       transformationReference, inputData, transformableData, outputFormat,
275                                                       dataPresentation );
276    
277            } catch ( XMLParsingException e ) {
278                LOG.logError( e.getMessage(), e );
279                throw new OGCWebServiceException( e.getMessage(), ExceptionCode.NOAPPLICABLECODE );
280            } catch ( UnknownCRSException e ) {
281                LOG.logError( e.getMessage(), e );
282                throw new OGCWebServiceException( e.getMessage(), ExceptionCode.NOAPPLICABLECODE );
283            }
284        }
285    
286        /**
287         * @param rootElement
288         * @return the transform reference.
289         *
290         * @throws XMLParsingException
291         *             if the xlink:href was not given.
292         */
293        private TransformationReference handleTransformation( Element rootElement )
294                                throws XMLParsingException {
295            if ( rootElement == null ) {
296                LOG.logDebug( "No transformation element given, using standard transformation type." );
297                return null;
298            }
299            String id = rootElement.getAttributeNS( CommonNamespaces.XLNNS.toASCIIString(), "href" );
300            String sourceID = null;
301            String targetID = null;
302            if ( id == null || "".equals( id.trim() ) ) {
303                Element sCRSNode = XMLTools.getElement( rootElement, PRE + "sourceCRS", nsContext );
304                Element tCRSNode = XMLTools.getElement( rootElement, PRE + "targetCRS", nsContext );
305                if ( ( sCRSNode == null && tCRSNode != null ) || ( sCRSNode != null && tCRSNode == null ) ) {
306                    throw new XMLParsingException(
307                                                   Messages.getMessage(
308                                                                        "WCTS_ISTRANSFORMABLE_MISSING_CRS",
309                                                                        ( ( sCRSNode == null ) ? "TargetCRS" : "SourceCRS" ),
310                                                                        ( ( tCRSNode == null ) ? "SourceCRS" : "TargetCRS" ) ) );
311                }
312                if ( sCRSNode != null ) {
313                    sourceID = sCRSNode.getAttributeNS( CommonNamespaces.XLNNS.toASCIIString(), "href" );
314                }
315                if ( tCRSNode != null ) {
316                    targetID = tCRSNode.getAttributeNS( CommonNamespaces.XLNNS.toASCIIString(), "href" );
317                }
318                LOG.logDebug( "The evaluation os supplied sourceID: ", sourceID, " and/or targetID: ", targetID,
319                              " are currently not supported." );
320                throw new XMLParsingException( "Currently only referencing of transformations is supported." );
321            }
322    
323            return new TransformationReference( id );
324    
325        }
326    
327        /**
328         * Parses the deegree inlinedata element.
329         *
330         * @param sourceCRS
331         *            of the data.
332         * @param targetCRS
333         *            in which the data is to be transformed.
334         * @param inlineData
335         *            element to extract the data from.
336         * @return a {@link TransformableData} element instantiated with the right type.
337         * @throws OGCWebServiceException
338         *             if for any reason the data could not be parsed or processed.
339         */
340        private TransformableData<?> parseInlineData( CoordinateSystem sourceCRS, Node inlineData )
341                                throws OGCWebServiceException {
342            Node firstChild = null;
343            try {
344                firstChild = getElement( inlineData, "*[1]", nsContext );
345            } catch ( XMLParsingException e ) {
346                LOG.logError( e.getMessage(), e );
347            }
348            if ( firstChild != null ) {
349                LOG.logDebug( "Incoming inlineData has localname: " + inlineData.getLocalName()
350                              + " has a firstchild with localname: " + firstChild.getLocalName() );
351                String prefix = firstChild.getPrefix();
352                String nameSpace = firstChild.getNamespaceURI();
353                if ( prefix != null ) {
354                    String tmp = firstChild.lookupNamespaceURI( prefix );
355                    if ( tmp != null && !"".equals( tmp ) ) {
356                        nameSpace = tmp;
357                    }
358                }
359                if ( nameSpace == null ) {
360                    nameSpace = "";
361                }
362                LOG.logDebug( "Firstchild is bound to namespace: " + nameSpace );
363                if ( !DEEGREEWCTS.toASCIIString().equalsIgnoreCase( nameSpace.trim() ) ) {
364                    LOG.logError( "The node beneath an " + DEEGREEWCTS_PREFIX
365                                  + ":inlineData element must be bound to the deegree-wcts (" + DEEGREEWCTS.toASCIIString()
366                                  + ") name space, found following namespace: " + nameSpace );
367                } else {
368                    String localName = firstChild.getLocalName();
369                    if ( localName != null ) {
370                        localName = localName.trim();
371                        if ( "SimpleData".equals( localName ) ) {
372                            SimpleData result = parseSimpleData( sourceCRS, (Element) firstChild );
373                            if ( result == null ) {
374                                result = new SimpleData();
375                            }
376                            return result;
377                        } else if ( "GeometryData".equals( localName ) ) {
378                            return new GeometryData( parseGeometryData( sourceCRS.getIdentifier(), (Element) firstChild ) );
379                        } else if ( "FeatureCollectionData".equals( localName ) ) {
380                            return new FeatureCollectionData( parseFeatureCollectionData( (Element) firstChild ) );
381                        } else {
382                            throw new OGCWebServiceException(
383                                                              Messages.getMessage( "WCTS_TRANSFORM_UNKNOWN_INLINE_DATA",
384                                                                                   localName,
385                                                                                   "SimpleData, GeometryData or FeatureCollectionData" ),
386                                                              ExceptionCode.INVALIDPARAMETERVALUE );
387                        }
388    
389                    }
390                }
391            }
392            throw new OGCWebServiceException( Messages.getMessage( "WCTS_TRANSFORM_MISSING_DATA" ),
393                                              WCTSExceptionCode.NO_INPUT_DATA );
394        }
395    
396        /**
397         * @param simpleData
398         *            the dom-xml element to be parsed.
399         * @return a list of point3d's or <code>null</code> if the given parameter <code>null</code>.
400         * @throws OGCWebServiceException
401         *             if the number of points is not congruent with the dimension.
402         */
403        private SimpleData parseSimpleData( CoordinateSystem sourceCRS, Element simpleData )
404                                throws OGCWebServiceException {
405            if ( simpleData == null ) {
406                return null;
407            }
408    
409            String cs = simpleData.getAttribute( "cs" );
410            if ( cs == null || "".equals( cs ) ) {
411                cs = ",";
412            }
413            String ts = simpleData.getAttribute( "ts" );
414            if ( ts == null || "".equals( ts ) ) {
415                ts = " ";
416            }
417            String values = getStringValue( simpleData );
418            List<Point3d> points = SimpleData.parseData( values, sourceCRS.getDimension(), cs, ts, "." );
419            if ( points.size() == 0 ) {
420                throw new OGCWebServiceException( Messages.getMessage( "WCTS_TRANSFORM_MISSING_DATA" ),
421                                                  WCTSExceptionCode.NO_INPUT_DATA );
422            }
423            return new SimpleData( points, cs, ts );
424        }
425    
426        /**
427         * Parse the featurecollections from the given featureCollectionsData element.
428         *
429         * @param featureCollectionData
430         *            (a deegreewcts:inlineElement/deegreewcts:FeatureCollectionData or a deegreewcts:mulipart element).
431         * @return the list of feature collections.
432         * @throws OGCWebServiceException
433         *             if no feature collections were found or an xml parsing exception occurred.
434         */
435        private List<FeatureCollection> parseFeatureCollectionData( Element featureCollectionData )
436                                throws OGCWebServiceException {
437            if ( featureCollectionData == null ) {
438                return null;
439            }
440            List<FeatureCollection> transformableData = new ArrayList<FeatureCollection>();
441            try {
442                List<Element> fcElements = getElements( featureCollectionData, GML_PREFIX + ":FeatureCollection", nsContext );
443                if ( fcElements == null || fcElements.size() == 0 ) {
444                    LOG.logError( "Could not find any feature collections, this is strange!" );
445                    throw new OGCWebServiceException( Messages.getMessage( "WCTS_TRANSFORM_NO_DATA_FOUND",
446                                                                           "gml:FeatureCollection" ),
447                                                      WCTSExceptionCode.NO_INPUT_DATA );
448                }
449                GMLFeatureCollectionDocument fd = new GMLFeatureCollectionDocument( true, true );
450                for ( Element fc : fcElements ) {
451                    fd.setRootElement( fc );
452                    FeatureCollection data = fd.parse();
453                    if ( data != null ) {
454                        transformableData.add( data );
455                    }
456                }
457            } catch ( XMLParsingException e ) {
458                LOG.logError( e.getMessage(), e );
459                throw new OGCWebServiceException( e.getMessage(), ExceptionCode.NOAPPLICABLECODE );
460            }
461            return transformableData;
462        }
463    
464        /**
465         * Parse the geometries from the given geometries data element.
466         *
467         * @param sourceCRSID
468         *            needed for the wrap function of the GMLGeometrieAdapter.
469         * @param geometryData
470         *            (an deegreewcts:inlineElement/deegreewcts:GeometryData or a deegreewcts:mulipart element).
471         * @return the list of geometries.
472         * @throws OGCWebServiceException
473         *             if no feature collections were found or an xml parsing exception occurred.
474         */
475        private List<Geometry> parseGeometryData( String sourceCRSID, Element geometryData )
476                                throws OGCWebServiceException {
477            if ( geometryData == null ) {
478                return null;
479            }
480            List<Geometry> transformableData = new ArrayList<Geometry>();
481            try {
482                List<Element> geomElements = getElements( geometryData, "*", nsContext );
483                if ( geomElements == null || geomElements.size() == 0 ) {
484                    LOG.logError( "Could not find any geometries, this is strange!" );
485                    throw new OGCWebServiceException(
486                                                      Messages.getMessage( "WCTS_TRANSFORM_NO_DATA_FOUND", "gml:Geometries" ),
487                                                      WCTSExceptionCode.NO_INPUT_DATA );
488                }
489                for ( Element fc : geomElements ) {
490                    try {
491                        Geometry data = GMLGeometryAdapter.wrap( fc, sourceCRSID );
492                        if ( data != null ) {
493                            transformableData.add( data );
494                        }
495                    } catch ( GeometryException e ) {
496                        LOG.logError( e.getMessage(), e );
497                    }
498                }
499            } catch ( XMLParsingException e ) {
500                LOG.logError( e.getMessage(), e );
501                throw new OGCWebServiceException( e.getMessage(), ExceptionCode.NOAPPLICABLECODE );
502            }
503            return transformableData;
504        }
505    
506        /**
507         * @return the transformRequest may be <code>null</code>.
508         */
509        public final Transform getTransformRequest() {
510            return transformRequest;
511        }
512    
513    }