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 }