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 }