001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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    }