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.capabilities;
038    
039    import static org.deegree.framework.xml.XMLTools.getElement;
040    import static org.deegree.framework.xml.XMLTools.getElements;
041    import static org.deegree.framework.xml.XMLTools.getNodeAsBoolean;
042    import static org.deegree.framework.xml.XMLTools.getNodesAsStringList;
043    import static org.deegree.framework.xml.XMLTools.getRequiredElements;
044    import static org.deegree.framework.xml.XMLTools.getRequiredNodeAsBoolean;
045    import static org.deegree.framework.xml.XMLTools.getStringValue;
046    import static org.deegree.ogcbase.CommonNamespaces.DEEGREEWCTS_PREFIX;
047    import static org.deegree.ogcbase.CommonNamespaces.WCS_1_2_0_PREFIX;
048    import static org.deegree.ogcbase.CommonNamespaces.WCTS_PREFIX;
049    
050    import java.util.ArrayList;
051    import java.util.HashMap;
052    import java.util.List;
053    import java.util.Map;
054    
055    import org.deegree.crs.transformations.Transformation;
056    import org.deegree.crs.transformations.helmert.Helmert;
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.XMLParsingException;
062    import org.deegree.framework.xml.XMLTools;
063    import org.deegree.i18n.Messages;
064    import org.deegree.model.crs.CRSFactory;
065    import org.deegree.model.crs.CoordinateSystem;
066    import org.deegree.model.crs.UnknownCRSException;
067    import org.deegree.ogcbase.CommonNamespaces;
068    import org.deegree.ogcbase.ExceptionCode;
069    import org.deegree.ogcwebservices.getcapabilities.InvalidCapabilitiesException;
070    import org.deegree.ogcwebservices.getcapabilities.OGCCapabilities;
071    import org.deegree.ogcwebservices.wcts.WCTService;
072    import org.deegree.ogcwebservices.wcts.capabilities.mdprofiles.MetadataProfile;
073    import org.deegree.ogcwebservices.wcts.capabilities.mdprofiles.TransformationMetadata;
074    import org.deegree.owscommon_1_1_0.Metadata;
075    import org.deegree.owscommon_1_1_0.OWSCommonCapabilitiesDocument;
076    import org.w3c.dom.Element;
077    
078    /**
079     * <code>WCTSCapabilitiesDocument</code> parses a given wcts:Capabilities document version 0.4.0, with ows:Common
080     * 1.1.0 and csw 1.2.0.
081     *
082     * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
083     *
084     * @author last edited by: $Author:$
085     *
086     * @version $Revision:$, $Date:$
087     *
088     */
089    public class WCTSCapabilitiesDocument extends OWSCommonCapabilitiesDocument {
090    
091        /**
092         *
093         */
094        private static final long serialVersionUID = -2378224055294207801L;
095    
096        private static ILogger LOG = LoggerFactory.getLogger( WCTSCapabilitiesDocument.class );
097    
098        private static String PRE = WCTS_PREFIX + ":";
099    
100        // Default value of codeType attribute, from wctsCommon.xsd.
101        private static String DEFAULT_COV_URL = "http://schemas.opengis.net/wcts/0.0.0/coverageType.xml";
102    
103        private static String DEFAULT_GEOM_URL = "http://schemas.opengis.net/wcts/0.0.0/geometryType.xml";
104    
105        /**
106         * Creates a wcts 0.4.0 capabilities document with the rootnode set to wcts:Capabilities.
107         */
108        public void createEmptyDocument() {
109            setRootElement( XMLTools.create().createElementNS( CommonNamespaces.WCTSNS.toASCIIString(),
110                                                               PRE + "Capabilities" ) );
111        }
112    
113        /**
114         * @param configuredProvider
115         *            the crs provider to be used for creation of CRS's (found in the deegreeparams section of the
116         *            configuration). If <code>null</code> the default configured provider will be used.
117         * @return the OGCCapabilities parsed from the root node.
118         * @throws InvalidCapabilitiesException
119         */
120        public OGCCapabilities parseCapabilities( String configuredProvider )
121                                throws InvalidCapabilitiesException {
122            WCTSCapabilities caps = null;
123            try {
124                caps = new WCTSCapabilities( parseVersion(), parseUpdateSequence(), parseServiceIdentification(),
125                                             parseServiceProvider(), parseOperationsMetadata(),
126                                             parseContents( configuredProvider ) );
127            } catch ( XMLParsingException e ) {
128                LOG.logError( e.getMessage(), e );
129                throw new InvalidCapabilitiesException( e.getMessage(), ExceptionCode.INVALID_FORMAT );
130            } catch ( UnknownCRSException ucrse ) {
131                LOG.logError( ucrse.getMessage(), ucrse );
132                throw new InvalidCapabilitiesException( ucrse.getMessage(), ExceptionCode.INVALID_FORMAT );
133            }
134            return caps;
135        }
136    
137        // /**
138        // * @return the OGCCapabilities parsed from the root node.
139        // * @throws InvalidCapabilitiesException
140        // */
141        // public OGCCapabilities parseCapabilities()
142        // throws InvalidCapabilitiesException {
143        // return this.parseCapabilities( null );
144        // }
145    
146        /**
147         * @return the mandatory version string.
148         * @throws InvalidCapabilitiesException
149         *             with code INVALIDPARAMETERVALUE, if the attribute was not given.
150         */
151        public String parseVersion()
152                                throws InvalidCapabilitiesException {
153            Element root = getRootElement();
154            String result = new String();
155            if ( root != null ) {
156                result = root.getAttribute( "version" );
157                if ( result == null || "".equals( result.trim() ) ) {
158                    throw new InvalidCapabilitiesException(
159                                                            Messages.getMessage( "WCTS_ILLEGAL_VERSION", WCTService.version ),
160                                                            ExceptionCode.INVALIDPARAMETERVALUE );
161                }
162            }
163    
164            if ( !WCTService.version.equalsIgnoreCase( result ) ) {
165                throw new InvalidCapabilitiesException( Messages.getMessage( "WCTS_ILLEGAL_VERSION", WCTService.version ),
166                                                        ExceptionCode.INVALIDPARAMETERVALUE );
167            }
168            return result;
169    
170        }
171    
172        /**
173         * @return the optional updateSeqequence String.
174         */
175        public String parseUpdateSequence() {
176            Element root = getRootElement();
177            String result = new String();
178            if ( root != null ) {
179                result = root.getAttribute( "updateSequence" );
180            }
181            return result;
182        }
183    
184        /**
185         * Parses the optional wcts:Content element of the wcts:Capabilities element.
186         *
187         * @param configuredProvider
188         *            the crs provider to be used for creation of CRS's (found in the deegreeparams section of the
189         *            configuration). If <code>null</code> the default configured provider will be used.
190         * @return the content bean representation
191         * @throws XMLParsingException
192         * @throws UnknownCRSException
193         */
194        protected Content parseContents( String configuredProvider )
195                                throws XMLParsingException, UnknownCRSException {
196            Element contents = getElement( getRootElement(), PRE + "Contents", nsContext );
197            if ( contents == null ) {
198                return null;
199            }
200    
201            List<String> transformations = getNodesAsStringList( contents, PRE + "Transformation", nsContext );
202            Map<String, Transformation> configuredTransforms = new HashMap<String, Transformation>( transformations.size() );
203            for ( String trans : transformations ) {
204                Transformation t = CRSFactory.getTransformation( configuredProvider, trans );
205                if ( t == null ) {
206                    LOG.logWarning( "The transformation with id : " + trans
207                                    + " could not be loaded from the crs configuration ignoring it. " );
208                } else {
209                    configuredTransforms.put( trans, t );
210                }
211    
212            }
213    
214            List<String> methods = getNodesAsStringList( contents, PRE + "Method", nsContext );
215            List<String> sCRSs = getNodesAsStringList( contents, PRE + "SourceCRS", nsContext );
216            if ( sCRSs == null || sCRSs.size() == 0 ) {
217                throw new XMLParsingException( "The " + PRE + "Content node of the " + PRE
218                                               + "Capabilites must have at least one SourceCRS element." );
219            }
220            List<CoordinateSystem> sourceCRSs = new ArrayList<CoordinateSystem>( sCRSs.size() );
221            for ( String crs : sCRSs ) {
222                sourceCRSs.add( CRSFactory.create( configuredProvider, crs ) );
223            }
224    
225            List<String> tCRSs = getNodesAsStringList( contents, PRE + "TargetCRS", nsContext );
226            if ( tCRSs == null || tCRSs.size() == 0 ) {
227                throw new XMLParsingException( "The " + PRE + "Content node of the " + PRE
228                                               + "Capabilites must have at least one TargetCRS element." );
229            }
230    
231            List<CoordinateSystem> targetCRSs = new ArrayList<CoordinateSystem>( tCRSs.size() );
232            for ( String crs : tCRSs ) {
233                targetCRSs.add( CRSFactory.create( configuredProvider, crs ) );
234            }
235            CoverageAbilities coverageAbilities = parseCoverageAbilities( getElement( contents, PRE + "CoverageAbilities",
236                                                                                      nsContext ) );
237            FeatureAbilities featureAbilities = parseFeatureAbilities( getElement( contents, PRE + "FeatureAbilities",
238                                                                                   nsContext ) );
239            if ( coverageAbilities == null && featureAbilities == null ) {
240                throw new XMLParsingException( "Both the " + PRE + "CoverageAbilities and " + PRE
241                                               + "FeatureAbilities elements are null, at least one must be present in the "
242                                               + PRE + "Content element of the WCTS-capabilities document." );
243            }
244    
245            List<Metadata> metadata = parseMetadatas( getElements( contents, PRE_OWS + "Metadata", nsContext ) );
246    
247            // the parseAbstractMetadata will remove the metadatas from the metadata list if it could be parsed.
248            List<MetadataProfile<?>> metadataProfiles = parseAbstractMetadata( metadata, sourceCRSs, targetCRSs,
249                                                                               configuredTransforms );
250    
251            boolean userDefinedCRS = getRequiredNodeAsBoolean( contents, "@userDefinedCRSs", nsContext );
252    
253            return new Content( configuredTransforms, methods, sourceCRSs, targetCRSs, coverageAbilities, featureAbilities,
254                                metadata, userDefinedCRS, metadataProfiles );
255        }
256    
257        /**
258         * @param metadata
259         * @param targetCRSs
260         * @param sourceCRSs
261         * @param configuredTransforms
262         * @return the list of metadata profiles, may be emtpy but never <code>null</code>
263         */
264        private List<MetadataProfile<?>> parseAbstractMetadata( List<Metadata> metadata, List<CoordinateSystem> sourceCRSs,
265                                                                List<CoordinateSystem> targetCRSs,
266                                                                Map<String, Transformation> configuredTransforms ) {
267            if ( metadata == null ) {
268                return new ArrayList<MetadataProfile<?>>();
269            }
270            List<MetadataProfile<?>> result = new ArrayList<MetadataProfile<?>>();
271            List<Metadata> toBeRemoved = new ArrayList<Metadata>();
272            for ( Metadata md : metadata ) {
273                if ( md != null ) {
274                    Element abst = md.getAbstractElement();
275                    if ( abst != null ) {
276                        LOG.logDebug( "Found following abstract element: " + abst.getLocalName() );
277                        if ( "transformationMetadata".equals( abst.getLocalName() ) ) {
278                            try {
279                                result.add( parseTransformationMetadata( abst, sourceCRSs, targetCRSs, configuredTransforms ) );
280                            } catch ( XMLParsingException e ) {
281                                LOG.logError( e.getMessage() );
282                            }
283                            // remove all transformation metadatas, they will be added to capabilities in their transform
284                            // form.
285                            toBeRemoved.add( md );
286                        } else {
287                            LOG.logError( "The type: "
288                                          + abst.getLocalName()
289                                          + " is not recognized by the wcts, currently just TransformationMetadata elements are supported." );
290                        }
291    
292                    }
293                }
294            }
295            metadata.removeAll( toBeRemoved );
296            return result;
297        }
298    
299        /**
300         * Parse the transformation metadata elements from the abstractMetadata element of the the content element.
301         *
302         * @param transformationMD
303         * @param sourceCRSs
304         * @param targetCRSs
305         * @param configuredTransforms
306         * @return the metadata bean.
307         * @throws XMLParsingException
308         */
309        private TransformationMetadata parseTransformationMetadata( Element transformationMD,
310                                                                    List<CoordinateSystem> sourceCRSs,
311                                                                    List<CoordinateSystem> targetCRSs,
312                                                                    Map<String, Transformation> configuredTransforms )
313                                throws XMLParsingException {
314            if ( transformationMD == null || !"transformationMetadata".equals( transformationMD.getLocalName() ) ) {
315                return null;
316            }
317            if ( LOG.isDebug() ) {
318                XMLFragment doc = new XMLFragment( transformationMD );
319                LOG.logDebug( "Parsing transformationMD from following xml fragment." );
320                LOG.logDebug( doc.getAsPrettyString() );
321            }
322            String sCRS = transformationMD.getAttribute( "sourceCRS" );
323            CoordinateSystem sourceCRS = null;
324            if ( "".equals( sCRS.trim() ) ) {
325                throw new XMLParsingException( "The sourceCRS attribute may not be empty." );
326            }
327            for ( int i = 0; i < sourceCRSs.size() && sourceCRS == null; ++i ) {
328                if ( sourceCRSs.get( i ) != null ) {
329                    if ( sourceCRSs.get( i ).getCRS().hasID( sCRS ) ) {
330                        sourceCRS = sourceCRSs.get( i );
331                    }
332                }
333            }
334            if ( sourceCRS == null ) {
335                throw new XMLParsingException( "The sourceCRS attribute:" + sCRS
336                                               + " denotes a CRS which is not defined as a sourceCRS." );
337            }
338    
339            String tCRS = transformationMD.getAttribute( "targetCRS" );
340            CoordinateSystem targetCRS = null;
341            if ( "".equals( tCRS.trim() ) ) {
342                throw new XMLParsingException( "The targetCRS attribute may not be empty." );
343            }
344            for ( int i = 0; i < targetCRSs.size() && targetCRS == null; ++i ) {
345                if ( targetCRSs.get( i ) != null ) {
346                    if ( targetCRSs.get( i ).getCRS().hasID( tCRS ) ) {
347                        targetCRS = targetCRSs.get( i );
348                    }
349                }
350            }
351            if ( targetCRS == null ) {
352                throw new XMLParsingException( "The targetCRS attribute: " + tCRS
353                                               + " denotes a CRS which is not defined as a targetCRS." );
354            }
355    
356            String tID = transformationMD.getAttribute( "transformationID" );
357            if ( "".equals( tID.trim() ) ) {
358                throw new XMLParsingException( "The transformationID attribute may not be empty." );
359            }
360            if ( !configuredTransforms.containsKey( tID ) || configuredTransforms.get( tID ) == null ) {
361                configuredTransforms.remove( tID );
362                throw new XMLParsingException( "The transformationID: " + tID
363                                               + " was not found in the configured transformations, discarding this id." );
364            }
365            Transformation transform = configuredTransforms.get( tID );
366            if ( transform == null ) {
367                throw new XMLParsingException(
368                                               "The transformationID: "
369                                                                       + tID
370                                                                       + " does not reference a known Transformation, discarding this metadata element." );
371            }
372            String description = XMLTools.getRequiredNodeAsString( transformationMD, DEEGREEWCTS_PREFIX + ":"
373                                                                                     + "description", nsContext );
374    
375            // if a helmert transformation, just use the default transformation chain.
376            return new TransformationMetadata( ( transform instanceof Helmert ) ? null : transform, tID, sourceCRS,
377                                               targetCRS, description );
378    
379        }
380    
381        /**
382         * @param element
383         * @return the FeatureAbilities
384         * @throws XMLParsingException
385         */
386        private FeatureAbilities parseFeatureAbilities( Element element )
387                                throws XMLParsingException {
388            if ( element == null ) {
389                return null;
390            }
391            List<Element> requiredElements = getRequiredElements( element, PRE + "GeometryType", nsContext );
392            List<Pair<String, String>> geometryTypes = new ArrayList<Pair<String, String>>( requiredElements.size() );
393            for ( Element geomElement : requiredElements ) {
394                Pair<String, String> t = parseCodeType( geomElement, DEFAULT_GEOM_URL );
395                if ( t != null ) {
396                    geometryTypes.add( t );
397                }
398            }
399    
400            requiredElements = getRequiredElements( element, PRE + "FeatureFormat", nsContext );
401            List<InputOutputFormat> featureFormats = new ArrayList<InputOutputFormat>( requiredElements.size() );
402            for ( Element featureFormElement : requiredElements ) {
403                InputOutputFormat t = parseInputOutputFormatType( featureFormElement );
404                if ( t != null ) {
405                    featureFormats.add( t );
406                }
407            }
408    
409            boolean remoteProperties = getRequiredNodeAsBoolean( element, "@remoteProperties", nsContext );
410    
411            return new FeatureAbilities( geometryTypes, featureFormats, remoteProperties );
412        }
413    
414        /**
415         * Parses given element for coverageType, CoverageFormat and InterpolatinMethods, the latter is not evaluated yet
416         * though.
417         *
418         * @param caElement
419         * @return the CoverageAbilities
420         * @throws XMLParsingException
421         */
422        private CoverageAbilities parseCoverageAbilities( Element caElement )
423                                throws XMLParsingException {
424            if ( caElement == null ) {
425                return null;
426            }
427            List<Element> ctElements = getRequiredElements( caElement, PRE + "CoverageType", nsContext );
428            List<Pair<String, String>> coverageTypes = new ArrayList<Pair<String, String>>( ctElements.size() );
429            for ( Element ct : ctElements ) {
430                Pair<String, String> t = parseCodeType( ct, DEFAULT_COV_URL );
431                if ( t != null ) {
432                    coverageTypes.add( t );
433                }
434            }
435            ctElements = getRequiredElements( caElement, PRE + "CoverageFormat", nsContext );
436            List<InputOutputFormat> coverageFormats = new ArrayList<InputOutputFormat>( ctElements.size() );
437            for ( Element ct : ctElements ) {
438                InputOutputFormat t = parseInputOutputFormatType( ct );
439                if ( t != null ) {
440                    coverageFormats.add( t );
441                }
442            }
443            List<Element> interPolationMethods = getRequiredElements( caElement,
444                                                                      WCS_1_2_0_PREFIX + ":InterpolationMethods", nsContext );
445            LOG.logWarning( "The " + WCS_1_2_0_PREFIX + ":InterpolationMethods are not evaluated yet." );
446            return new CoverageAbilities( coverageTypes, coverageFormats, interPolationMethods );
447        }
448    
449        /**
450         * return the &lt;value,codetype &gt; pair of a CodeType element.
451         *
452         * @param elem
453         *            to parse from
454         * @param defaultString
455         *            the String to use as a default value.
456         * @return the &lt;text(), codeType-attribute&gt; pair or <code>null</code> if the element is <code>null</code>
457         */
458        private Pair<String, String> parseCodeType( Element elem, String defaultString ) {
459            String value = getStringValue( elem );
460            String attrib = elem.getAttribute( "codeSpace" );
461            if ( attrib == null || "".equals( attrib ) ) {
462                attrib = defaultString;
463            }
464            return new Pair<String, String>( value, attrib );
465        }
466    
467        /**
468         * return the <mimetype,<input,output>> pair of a CodeType element.
469         *
470         * @param elem
471         *            to parse from
472         * @return the <text(), <input,output>> pair or <code>null</code> if the element is <code>null</code>
473         * @throws XMLParsingException
474         */
475        private InputOutputFormat parseInputOutputFormatType( Element elem )
476                                throws XMLParsingException {
477            String value = elem.getTextContent();
478            boolean input = getNodeAsBoolean( elem, "@input", nsContext, true );
479            boolean output = getNodeAsBoolean( elem, "@output", nsContext, true );
480            return new InputOutputFormat( value, input, output );
481        }
482    
483    }