001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/wfs/configuration/WFSConfiguration.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2006 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: 6588 $, $Date: 2007-04-11 17:31:29 +0200 (Mi, 11 Apr 2007) $
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        /**
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,
118                                ServiceIdentification serviceIdentification,
119                                ServiceProvider serviceProvider, OperationsMetadata operationsMetadata,
120                                FeatureTypeList featureTypeList, GMLObject[] servesGMLObjectTypeList,
121                                GMLObject[] supportsGMLObjectTypeList, Contents contents,
122                                FilterCapabilities filterCapabilities, WFSDeegreeParams deegreeParams )
123                                throws InvalidConfigurationException {
124            super( version, updateSequence, serviceIdentification, serviceProvider, operationsMetadata,
125                   featureTypeList, servesGMLObjectTypeList, supportsGMLObjectTypeList, contents,
126                   filterCapabilities );
127            this.deegreeParams = deegreeParams;
128            try {
129                validateFeatureTypeDefinitions();
130            } catch ( InvalidConfigurationException e ) {
131                LOG.logError( e.getMessage() );
132                throw e;
133            }
134        }
135    
136        /**
137         * Returns the deegreeParams.
138         * 
139         * @return the deegreeParams
140         */
141        public WFSDeegreeParams getDeegreeParams() {
142            return deegreeParams;
143        }
144    
145        /**
146         * The deegreeParams to set.
147         * 
148         * @param deegreeParams
149         */
150        public void setDeegreeParams( WFSDeegreeParams deegreeParams ) {
151            this.deegreeParams = deegreeParams;
152        }
153    
154        /**
155         * Returns a <code>Map</code> of the feature types that this configuration defines.
156         * 
157         * @return keys: feature type names, values: mapped feature types
158         */
159        public Map<QualifiedName, MappedFeatureType> getMappedFeatureTypes() {
160            return this.ftMap;
161        }
162    
163        /**
164         * The <code>WFSConfiguration</code> is processed as follows:
165         * <ul>
166         * <li>The data directories (as specified in the configuration) are scanned for
167         * {@link MappedGMLSchemaDocument}s</li>
168         * <li>All {@link MappedFeatureType}s defined in any of the found
169         * {@link MappedGMLSchemaDocument}s are extracted, if duplicate feature type definitions occur,
170         * an <code>InvalidConfigurationException</code> is thrown.</li>
171         * <li>All feature types defined in the FeatureTypeList section of the WFS configuration are
172         * checked to have a corresponding {@link MappedFeatureType} definition. If this is not the case
173         * (or if the DefaultSRS is not equal to the CRS in the respective datastore definition), an
174         * <code>InvalidConfigurationException</code> is thrown.</li>
175         * <li>NOTE: the above is not necessary for FeatureTypes that are processed by XSLT-scripts
176         * (because they are usually mapped to a different FeatureTypes by XSLT)</li>
177         * <li>All feature types that are not defined in the FeatureTypeList section, but have been
178         * defined in one of the {@link MappedGMLSchemaDocument}s are added as feature types to the WFS
179         * configuration.</li>
180         * </ul>
181         * </p>
182         * 
183         * @throws InvalidConfigurationException
184         */
185        private void validateFeatureTypeDefinitions()
186                                throws InvalidConfigurationException {
187    
188            // extract the mapped feature types from the given data directory
189            this.ftMap = scanForMappedFeatureTypes();
190            Map tempFeatureTypeMap = (Map) ( (HashMap) this.ftMap ).clone();
191    
192            // check that for each configuration feature type a mapped feature type exists and that
193            // their DefaultSRS are identical
194            WFSFeatureType[] wfsFTs = getFeatureTypeList().getFeatureTypes();
195            for ( int i = 0; i < wfsFTs.length; i++ ) {
196    
197                if ( !wfsFTs[i].isVirtual() ) {
198                    MappedFeatureType mappedFT = this.ftMap.get( wfsFTs[i].getName() );
199                    if ( mappedFT == null ) {
200                        String msg = Messages.getMessage( "WFS_CONF_FT_APP_SCHEMA_MISSING",
201                                                          wfsFTs[i].getName() );
202                        throw new InvalidConfigurationException( msg );
203                    }
204                    if ( !mappedFT.isVisible() ) {
205                        String msg = Messages.getMessage( "WFS_CONF_FT_APP_SCHEMA_INVISIBLE",
206                                                          wfsFTs[i].getName() );
207                        throw new InvalidConfigurationException( msg );
208                    }
209                    URI defaultSRS = mappedFT.getGMLSchema().getDefaultSRS();
210                    if ( !defaultSRS.equals( wfsFTs[i].getDefaultSRS() ) ) {
211                        String msg = Messages.getMessage( "WFS_CONF_FT_APP_SCHEMA_WRONG_SRS",
212                                                          wfsFTs[i].getName(),
213                                                          wfsFTs[i].getDefaultSRS(), defaultSRS );
214                        throw new InvalidConfigurationException( msg );
215                    }
216                }
217    
218                // remove datastore feature type
219                tempFeatureTypeMap.remove( wfsFTs[i].getName() );
220            }
221    
222            // add all remaining mapped feature types to the WFS configuration
223            Iterator it = tempFeatureTypeMap.keySet().iterator();
224            while ( it.hasNext() ) {
225                QualifiedName featureTypeName = (QualifiedName) it.next();
226                MappedFeatureType mappedFT = (MappedFeatureType) tempFeatureTypeMap.get( featureTypeName );
227                if ( mappedFT.isVisible() ) {
228                    try {
229                        getFeatureTypeList().addFeatureType( createWFSFeatureType( mappedFT ) );
230                    } catch ( UnknownCRSException e ) {
231                        throw new InvalidConfigurationException( e );
232                    }
233                }
234            }
235        }
236    
237        /**
238         * Scans for and extracts the <code>MappedFeatureType</code> s that are located in the data
239         * directories of the current WFS configuration.
240         * 
241         * @return keys: featureTypeNames, values: mapped feature types
242         * @throws InvalidConfigurationException
243         */
244        private Map<QualifiedName, MappedFeatureType> scanForMappedFeatureTypes()
245                                throws InvalidConfigurationException {
246            List<String> fileNameList = new ArrayList<String>();
247            String[] dataDirectories = getDeegreeParams().getDataDirectories();
248    
249            for ( int i = 0; i < dataDirectories.length; i++ ) {
250                File file = new File( dataDirectories[i] );
251                String[] list = file.list( new XSDFileFilter() );
252                if ( list != null ) {
253                    if ( list.length == 0 ) {
254                        LOG.logInfo( "Specified datastore directory '" + dataDirectories[i]
255                                     + "' does not contain any '.xsd' files." );
256                    }
257                    for ( int j = 0; j < list.length; j++ ) {
258                        fileNameList.add( dataDirectories[i] + '/' + list[j] );
259                    }
260                } else {
261                    LOG.logInfo( "Specified datastore directory '" + dataDirectories[i]
262                                 + "' does not denote a directory." );
263                }
264            }
265            String[] fileNames = fileNameList.toArray( new String[fileNameList.size()] );
266            return extractMappedFeatureTypes( fileNames );
267        }
268    
269        /**
270         * Extracts the {@link MappedFeatureType} s which are defined in the given array of mapped
271         * application schema filenames. Files that do not conform to mapped GML Application Schemas
272         * definition are omitted, so are multiple definitions of the same feature types.
273         * 
274         * @param fileNames
275         *            fileNames to be scanned
276         * @return keys: feature type names, values: mapped feature types
277         * @throws InvalidConfigurationException
278         */
279        private Map<QualifiedName, MappedFeatureType> extractMappedFeatureTypes( String[] fileNames )
280                                throws InvalidConfigurationException {
281    
282            Map<QualifiedName, MappedFeatureType> featureTypeMap = new HashMap<QualifiedName, MappedFeatureType>(
283                                                                                                                  fileNames.length );
284    
285            for ( int i = 0; i < fileNames.length; i++ ) {
286                try {
287                    URL fileURL = new File( fileNames[i] ).toURL();
288                    LOG.logInfo( "Reading annotated GML application schema from URL '" + fileURL + "'." );
289                    MappedGMLSchemaDocument doc = new MappedGMLSchemaDocument();
290                    doc.load( fileURL );
291                    MappedGMLSchema gmlSchema = doc.parseMappedGMLSchema();
292    
293                    FeatureType[] featureTypes = gmlSchema.getFeatureTypes();
294                    for ( int j = 0; j < featureTypes.length; j++ ) {
295                        MappedFeatureType ft = featureTypeMap.get( featureTypes[j].getName() );
296                        if ( ft != null ) {
297                            String msg = Messages.getMessage( "WFS_CONF_MULTIPLE_FEATURE_TYPE_DEF",
298                                                              featureTypes[j].getName(), fileNames[i] );
299                            throw new InvalidConfigurationException( msg );
300                        }
301                        featureTypeMap.put( featureTypes[j].getName(),
302                                            (MappedFeatureType) featureTypes[j] );
303                    }
304                } catch ( IOException e ) {
305                    LOG.logError( e.getMessage(), e );
306                    String msg = "Error loading '" + fileNames[i] + "': " + e.getMessage();
307                    throw new InvalidConfigurationException( msg, e );
308                } catch ( Exception e ) {
309                    LOG.logError( e.getMessage(), e );
310                    String msg = "Error parsing '" + fileNames[i] + "': " + e.getMessage();
311                    throw new InvalidConfigurationException( msg, e );
312                }
313            }
314            return featureTypeMap;
315        }
316    
317        /**
318         * Creates a (minimal) <code>WFSFeatureType</code> from the given
319         * <code>MappedFeatureType</code>.
320         * 
321         * @param rootfeatureType
322         * @throws UnknownCRSException
323         */
324        private WFSFeatureType createWFSFeatureType( MappedFeatureType mappedFeatureType )
325                                throws UnknownCRSException {
326            Operation[] operations = new Operation[1];
327            operations[0] = new Operation( Operation.QUERY );
328            FormatType[] outputFormats = new FormatType[1];
329            // according to WFS 1.1.0 spec text/xml; subtype=gml/3.1.1
330            // is the only mandatory format
331            outputFormats[0] = new FormatType( null, null, null, "text/xml; subtype=gml/3.1.1" );
332            CoordinateSystem crs = CRSFactory.create( "EPSG:4326" );
333            Envelope[] wgs84BoundingBoxes = new Envelope[1];
334            wgs84BoundingBoxes[0] = GeometryFactory.createEnvelope( -180, -90, 180, 90, crs );
335            URI defaultSRS = mappedFeatureType.getGMLSchema().getDefaultSRS();
336            WFSFeatureType featureType = new WFSFeatureType( mappedFeatureType.getName(), null, null,
337                                                             null, defaultSRS, null, operations,
338                                                             outputFormats, wgs84BoundingBoxes, null );
339            return featureType;
340        }
341    
342        static class XSDFileFilter implements FilenameFilter {
343    
344            /**
345             * Tests if a specified file should be included in a file list.
346             * 
347             * @param dir
348             *            the directory in which the file was found
349             * @param name
350             *            the name of the file
351             * @return <code>true</code> if and only if the name should be included in the file list;
352             *         <code>false</code> otherwise
353             */
354            public boolean accept( File dir, String name ) {
355                int pos = name.lastIndexOf( "." );
356                String ext = name.substring( pos + 1 );
357                return ext.toUpperCase().equals( "XSD" );
358            }
359        }
360    }