    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/ogcwebservices/wfs/configuration/WFSConfiguration.java $
    002    /*----------------------------------------------------------------------------
    003     This file is part of deegree, http://deegree.org/
    004     Copyright (C) 2001-2009 by:
    005       Department of Geography, University of Bonn
    006     and
    007       lat/lon GmbH
    008
    009     This library is free software; you can redistribute it and/or modify it under
    010     the terms of the GNU Lesser General Public License as published by the Free
    011     Software Foundation; either version 2.1 of the License, or (at your option)
    012     any later version.
    013     This library is distributed in the hope that it will be useful, but WITHOUT
    014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
    016     details.
    017     You should have received a copy of the GNU Lesser General Public License
    018     along with this library; if not, write to the Free Software Foundation, Inc.,
    019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    020
    021     Contact information:
    022
    023     lat/lon GmbH
    024     Aennchenstr. 19, 53177 Bonn
    025     Germany
    026     http://lat-lon.de/
    027
    028     Department of Geography, University of Bonn
    029     Prof. Dr. Klaus Greve
    030     Postfach 1147, 53001 Bonn
    031     Germany
    032     http://www.geographie.uni-bonn.de/deegree/
    033
    034     e-mail: info@deegree.org
    035    ----------------------------------------------------------------------------*/
    036    package org.deegree.ogcwebservices.wfs.configuration;
    037
    038    import java.io.File;
    039    import java.io.FilenameFilter;
    040    import java.io.IOException;
    041    import java.net.URI;
    042    import java.net.URL;
    043    import java.util.ArrayList;
    044    import java.util.HashMap;
    045    import java.util.HashSet;
    046    import java.util.Iterator;
    047    import java.util.List;
    048    import java.util.Map;
    049    import java.util.Set;
    050
    051    import org.deegree.datatypes.QualifiedName;
    052    import org.deegree.framework.log.ILogger;
    053    import org.deegree.framework.log.LoggerFactory;
    054    import org.deegree.framework.xml.InvalidConfigurationException;
    055    import org.deegree.i18n.Messages;
    056    import org.deegree.io.datastore.schema.MappedFeatureType;
    057    import org.deegree.io.datastore.schema.MappedGMLSchema;
    058    import org.deegree.io.datastore.schema.MappedGMLSchemaDocument;
    059    import org.deegree.model.crs.CRSFactory;
    060    import org.deegree.model.crs.CoordinateSystem;
    061    import org.deegree.model.crs.UnknownCRSException;
    062    import org.deegree.model.feature.schema.FeatureType;
    063    import org.deegree.model.filterencoding.capabilities.FilterCapabilities;
    064    import org.deegree.model.spatialschema.Envelope;
    065    import org.deegree.model.spatialschema.GeometryFactory;
    066    import org.deegree.ogcwebservices.getcapabilities.Contents;
    067    import org.deegree.ogcwebservices.getcapabilities.OperationsMetadata;
    068    import org.deegree.ogcwebservices.getcapabilities.ServiceIdentification;
    069    import org.deegree.ogcwebservices.getcapabilities.ServiceProvider;
    070    import org.deegree.ogcwebservices.wfs.WFService;
    071    import org.deegree.ogcwebservices.wfs.capabilities.FeatureTypeList;
    072    import org.deegree.ogcwebservices.wfs.capabilities.FormatType;
    073    import org.deegree.ogcwebservices.wfs.capabilities.GMLObject;
    074    import org.deegree.ogcwebservices.wfs.capabilities.Operation;
    075    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
    076    import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType;
    077
    078    /**
    079     * Represents the configuration for a deegree {@link WFService} instance.
    080     *
    081     * @author <a href="mailto:mschneider@lat-lon.de">Markus Schneider </a>
    082     * @author last edited by: $Author: mschneider $
    083     *
    084     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
    085     */
    086    public class WFSConfiguration extends WFSCapabilities {
    087
    088        private static final long serialVersionUID = -8929822028461025018L;
    089
    090        protected static final ILogger LOG = LoggerFactory.getLogger( WFSConfiguration.class );
    091
    092        private WFSDeegreeParams deegreeParams;
    093
    094        private Map<QualifiedName, MappedFeatureType> ftMap = new HashMap<QualifiedName, MappedFeatureType>();
    095
    096        private boolean hasUniquePrefixMapping = true;
    097
    098        private Map<String, MappedFeatureType> prefixMap = new HashMap<String, MappedFeatureType>();
    099
    100        /**
    101         * Generates a new <code>WFSConfiguration</code> instance from the given parameters.
    102         *
    103         * @param version
    104         * @param updateSequence
    105         * @param serviceIdentification
    106         * @param serviceProvider
    107         * @param operationsMetadata
    108         * @param featureTypeList
    109         * @param servesGMLObjectTypeList
    110         * @param supportsGMLObjectTypeList
    111         * @param contents
    112         *            TODO field not verified! Check spec.
    113         * @param filterCapabilities
    114         * @param deegreeParams
    115         * @throws InvalidConfigurationException
    116         */
    117        public WFSConfiguration( String version, String updateSequence, ServiceIdentification serviceIdentification,
    118                                 ServiceProvider serviceProvider, OperationsMetadata operationsMetadata,
    119                                 FeatureTypeList featureTypeList, GMLObject[] servesGMLObjectTypeList,
    120                                 GMLObject[] supportsGMLObjectTypeList, Contents contents,
    121                                 FilterCapabilities filterCapabilities, WFSDeegreeParams deegreeParams )
    122                                throws InvalidConfigurationException {
    123            super( version, updateSequence, serviceIdentification, serviceProvider, operationsMetadata, featureTypeList,
    124                   servesGMLObjectTypeList, supportsGMLObjectTypeList, contents, filterCapabilities );
    125            this.deegreeParams = deegreeParams;
    126            try {
    127                validateFeatureTypeDefinitions();
    128                buildAndValidatePrefixMap();
    129            } catch ( InvalidConfigurationException e ) {
    130                LOG.logError( e.getMessage() );
    131                throw e;
    132            }
    133        }
    134
    135        /**
    136         * Returns the deegreeParams.
    137         *
    138         * @return the deegreeParams
    139         */
    140        public WFSDeegreeParams getDeegreeParams() {
    141            return deegreeParams;
    142        }
    143
    144        /**
    145         * The deegreeParams to set.
    146         *
    147         * @param deegreeParams
    148         */
    149        public void setDeegreeParams( WFSDeegreeParams deegreeParams ) {
    150            this.deegreeParams = deegreeParams;
    151        }
    152
    153        /**
    154         * Returns a <code>Map</code> of the feature types that this configuration defines.
    155         *
    156         * @return keys: feature type names, values: mapped feature types
    157         */
    158        public Map<QualifiedName, MappedFeatureType> getMappedFeatureTypes() {
    159            return this.ftMap;
    160        }
    161
    162        /**
    163         * Returns whether this WFS has unique prefixes for it's feature ids, so the type of a feature can always be
    164         * identified by its feature id.
    165         *
    166         * @return true, if it has unique prefixes, false otherwise
    167         */
    168        public boolean hasUniquePrefixMapping() {
    169            return hasUniquePrefixMapping;
    170        }
    171
    172        /**
    173         * Returns the {@link MappedFeatureType} for the given feature id.
    174         *
    175         * @param featureId
    176         *            feature id to look up
    177         * @return the {@link MappedFeatureType} for the given feature id, null if no mapping was found
    178         */
    179        public MappedFeatureType getFeatureType( String featureId ) {
    180
    181            // TODO improve this (but note that there is no ensured delimiter between prefix and the rest of the id)
    182            for ( String prefix : prefixMap.keySet() ) {
    183                if ( featureId.startsWith( prefix ) ) {
    184                    return prefixMap.get( prefix );
    185                }
    186            }
    187            return null;
    188        }
    189
    190        /**
    191         * The <code>WFSConfiguration</code> is processed as follows:
    192         * <ul>
    193         * <li>The data directories (as specified in the configuration) are scanned for {@link MappedGMLSchemaDocument}s</li>
    194         * <li>All {@link MappedFeatureType}s defined in any of the found {@link MappedGMLSchemaDocument}s are extracted, if
    195         * duplicate feature type definitions occur, an <code>InvalidConfigurationException</code> is thrown.</li>
    196         * <li>All feature types defined in the FeatureTypeList section of the WFS configuration are checked to have a
    197         * corresponding {@link MappedFeatureType} definition. If this is not the case (or if the DefaultSRS is not equal to
    198         * the CRS in the respective datastore definition), an <code>InvalidConfigurationException</code> is thrown.</li>
    199         * <li>NOTE: the above is not necessary for FeatureTypes that are processed by XSLT-scripts (because they are
    200         * usually mapped to a different FeatureTypes by XSLT)</li>
    201         * <li>All feature types that are not defined in the FeatureTypeList section, but have been defined in one of the
    202         * {@link MappedGMLSchemaDocument}s are added as feature types to the WFS configuration.</li>
    203         * </ul>
    204         * </p>
    205         *
    206         * @throws InvalidConfigurationException
    207         */
    208        @SuppressWarnings("unchecked")
    209        private void validateFeatureTypeDefinitions()
    210                                throws InvalidConfigurationException {
    211
    212            // extract the mapped feature types from the given data directory
    213            this.ftMap = scanForMappedFeatureTypes();
    214            Map<QualifiedName, MappedFeatureType> tempFeatureTypeMap = (Map<QualifiedName, MappedFeatureType>) ( (HashMap<QualifiedName, MappedFeatureType>) this.ftMap ).clone();
    215
    216            // check that for each configuration feature type a mapped feature type exists and that
    217            // their DefaultSRS are identical
    218            WFSFeatureType[] wfsFTs = getFeatureTypeList().getFeatureTypes();
    219            for ( int i = 0; i < wfsFTs.length; i++ ) {
    220
    221                if ( !wfsFTs[i].isVirtual() ) {
    222                    MappedFeatureType mappedFT = this.ftMap.get( wfsFTs[i].getName() );
    223                    if ( mappedFT == null ) {
    224                        String msg = Messages.getMessage( "WFS_CONF_FT_APP_SCHEMA_MISSING", wfsFTs[i].getName() );
    225                        throw new InvalidConfigurationException( msg );
    226                    }
    227                    if ( !mappedFT.isVisible() ) {
    228                        String msg = Messages.getMessage( "WFS_CONF_FT_APP_SCHEMA_INVISIBLE", wfsFTs[i].getName() );
    229                        throw new InvalidConfigurationException( msg );
    230                    }
    231                    URI defaultSRS = wfsFTs[i].getDefaultSRS();
    232                    if ( defaultSRS == null ) {
    233                        wfsFTs[i].setDefaultSrs( mappedFT.getDefaultSRS() );
    234                    } else {
    235                        try {
    236                            CoordinateSystem crs1 = CRSFactory.create( defaultSRS.toASCIIString() );
    237                            CoordinateSystem crs2 = CRSFactory.create( mappedFT.getDefaultSRS().toASCIIString() );
    238                            if ( !crs1.equals( crs2 ) ) {
    239                                String msg = Messages.getMessage( "WFS_CONF_FT_APP_SCHEMA_WRONG_SRS", wfsFTs[i].getName(),
    240                                                                  wfsFTs[i].getDefaultSRS(), mappedFT.getDefaultSRS() );
    241                                throw new InvalidConfigurationException( msg );
    242                            }
    243                        } catch ( UnknownCRSException e ) {
    244                            // probably not going to happen anyway
    245                            if ( !defaultSRS.equals( mappedFT.getDefaultSRS() ) ) {
    246                                String msg = Messages.getMessage( "WFS_CONF_FT_APP_SCHEMA_WRONG_SRS", wfsFTs[i].getName(),
    247                                                                  wfsFTs[i].getDefaultSRS(), mappedFT.getDefaultSRS() );
    248                                throw new InvalidConfigurationException( msg );
    249                            }
    250                        }
    251                    }
    252
    253                    // merge otherSRS from WFS configuration with otherSRS from schema
    254                    Set<URI> mergedOtherSRS = new HashSet<URI>();
    255                    for ( URI uri : wfsFTs[i].getOtherSrs() ) {
    256                        mergedOtherSRS.add( uri );
    257                    }
    258                    for ( URI uri : mappedFT.getOtherSRS() ) {
    259                        mergedOtherSRS.add( uri );
    260                    }
    261                    wfsFTs[i].setOtherSrs( mergedOtherSRS.toArray( new URI[mergedOtherSRS.size()] ) );
    262                }
    263
    264                // remove datastore feature type
    265                tempFeatureTypeMap.remove( wfsFTs[i].getName() );
    266            }
    267
    268            // add all remaining mapped feature types to the WFS configuration
    269            Iterator<QualifiedName> it = tempFeatureTypeMap.keySet().iterator();
    270            while ( it.hasNext() ) {
    271                QualifiedName featureTypeName = it.next();
    272                MappedFeatureType mappedFT = tempFeatureTypeMap.get( featureTypeName );
    273                if ( mappedFT.isVisible() ) {
    274                    try {
    275                        getFeatureTypeList().addFeatureType( createWFSFeatureType( mappedFT ) );
    276                    } catch ( UnknownCRSException e ) {
    277                        throw new InvalidConfigurationException( e );
    278                    }
    279                }
    280            }
    281        }
    282
    283        /**
    284         * Scans for and extracts the <code>MappedFeatureType</code> s that are located in the data directories of the
    285         * current WFS configuration.
    286         *
    287         * @return keys: featureTypeNames, values: mapped feature types
    288         * @throws InvalidConfigurationException
    289         */
    290        private Map<QualifiedName, MappedFeatureType> scanForMappedFeatureTypes()
    291                                throws InvalidConfigurationException {
    292            List<String> fileNameList = new ArrayList<String>();
    293            String[] dataDirectories = getDeegreeParams().getDataDirectories();
    294
    295            for ( int i = 0; i < dataDirectories.length; i++ ) {
    296                File file = new File( dataDirectories[i] );
    297                String[] list = file.list( new XSDFileFilter() );
    298                if ( list != null ) {
    299                    if ( list.length == 0 ) {
    300                        LOG.logInfo( "Specified datastore directory '" + dataDirectories[i]
    301                                     + "' does not contain any '.xsd' files." );
    302                    }
    303                    for ( int j = 0; j < list.length; j++ ) {
    304                        fileNameList.add( dataDirectories[i] + '/' + list[j] );
    305                    }
    306                } else {
    307                    LOG.logInfo( "Specified datastore directory '" + dataDirectories[i] + "' does not denote a directory." );
    308                }
    309            }
    310            String[] fileNames = fileNameList.toArray( new String[fileNameList.size()] );
    311            return extractMappedFeatureTypes( fileNames );
    312        }
    313
    314        /**
    315         * Extracts the {@link MappedFeatureType} s which are defined in the given array of mapped application schema
    316         * filenames. Files that do not conform to mapped GML Application Schemas definition are omitted, so are multiple
    317         * definitions of the same feature types.
    318         *
    319         * @param fileNames
    320         *            fileNames to be scanned
    321         * @return keys: feature type names, values: mapped feature types
    322         * @throws InvalidConfigurationException
    323         */
    324        private Map<QualifiedName, MappedFeatureType> extractMappedFeatureTypes( String[] fileNames )
    325                                throws InvalidConfigurationException {
    326
    327            Map<QualifiedName, MappedFeatureType> featureTypeMap = new HashMap<QualifiedName, MappedFeatureType>(
    328                                                                                                                  fileNames.length );
    329
    330            for ( int i = 0; i < fileNames.length; i++ ) {
    331                try {
    332                    URL fileURL = new File( fileNames[i] ).toURL();
    333                    LOG.logInfo( "Reading annotated GML application schema from URL '" + fileURL + "'." );
    334                    MappedGMLSchemaDocument doc = new MappedGMLSchemaDocument();
    335                    doc.load( fileURL );
    336                    MappedGMLSchema gmlSchema = doc.parseMappedGMLSchema();
    337
    338                    FeatureType[] featureTypes = gmlSchema.getFeatureTypes();
    339                    for ( int j = 0; j < featureTypes.length; j++ ) {
    340                        MappedFeatureType ft = featureTypeMap.get( featureTypes[j].getName() );
    341                        if ( ft != null ) {
    342                            String msg = Messages.getMessage( "WFS_CONF_MULTIPLE_FEATURE_TYPE_DEF",
    343                                                              featureTypes[j].getName(), fileNames[i] );
    344                            throw new InvalidConfigurationException( msg );
    345                        }
    346                        featureTypeMap.put( featureTypes[j].getName(), (MappedFeatureType) featureTypes[j] );
    347                    }
    348                } catch ( IOException e ) {
    349                    LOG.logError( e.getMessage(), e );
    350                    String msg = "Error loading '" + fileNames[i] + "': " + e.getMessage();
    351                    throw new InvalidConfigurationException( msg, e );
    352                } catch ( Exception e ) {
    353                    LOG.logError( e.getMessage(), e );
    354                    String msg = "Error parsing '" + fileNames[i] + "': " + e.getMessage();
    355                    throw new InvalidConfigurationException( msg, e );
    356                }
    357            }
    358            return featureTypeMap;
    359        }
    360
    361        private void buildAndValidatePrefixMap() {
    362            for ( MappedFeatureType ft : ftMap.values() ) {
    363                String prefix = ft.getGMLId().getPrefix();
    364                if ( prefixMap.containsKey( prefix ) ) {
    365                    String msg = Messages.get( "WFS_CONF_FT_PREFICES_NOT_UNIQUE" );
    366                    LOG.logWarning( msg );
    367                    hasUniquePrefixMapping = false;
    368                    break;
    369                }
    370                prefixMap.put( prefix, ft );
    371            }
    372        }
    373
    374        /**
    375         * Creates a (minimal) <code>WFSFeatureType</code> from the given <code>MappedFeatureType</code>.
    376         *
    377         * @param mappedFeatureType
    378         * @throws UnknownCRSException
    379         */
    380        private WFSFeatureType createWFSFeatureType( MappedFeatureType mappedFeatureType )
    381                                throws UnknownCRSException {
    382            Operation[] operations = new Operation[1];
    383            operations[0] = new Operation( Operation.QUERY );
    384            FormatType[] outputFormats = new FormatType[1];
    385            // according to WFS 1.1.0 spec text/xml; subtype=gml/3.1.1
    386            // is the only mandatory format
    387            outputFormats[0] = new FormatType( null, null, null, "text/xml; subtype=gml/3.1.1" );
    388            CoordinateSystem crs = CRSFactory.create( "EPSG:4326" );
    389            Envelope[] wgs84BoundingBoxes = new Envelope[1];
    390            wgs84BoundingBoxes[0] = GeometryFactory.createEnvelope( -180, -90, 180, 90, crs );
    391            URI defaultSRS = mappedFeatureType.getDefaultSRS();
    392            URI[] otherSRS = mappedFeatureType.getOtherSRS();
    393            WFSFeatureType featureType = new WFSFeatureType( mappedFeatureType.getName(), null, null, null, defaultSRS,
    394                                                             otherSRS, operations, outputFormats, wgs84BoundingBoxes, null );
    395            return featureType;
    396        }
    397
    398        static class XSDFileFilter implements FilenameFilter {
    399
    400            /**
    401             * Tests if a specified file should be included in a file list.
    402             *
    403             * @param dir
    404             *            the directory in which the file was found
    405             * @param name
    406             *            the name of the file
    407             * @return <code>true</code> if and only if the name should be included in the file list; <code>false</code>
    408             *         otherwise
    409             */
    410            public boolean accept( File dir, String name ) {
    411                int pos = name.lastIndexOf( "." );
    412                String ext = name.substring( pos + 1 );
    413                return ext.toUpperCase().equals( "XSD" );
    414            }
    415        }
    416    }