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