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 }