    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    }