001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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 }