001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/io/datastore/PropertyPathResolver.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 Germany 032 E-Mail: poth@lat-lon.de 033 034 Prof. Dr. Klaus Greve 035 Department of Geography 036 University of Bonn 037 Meckenheimer Allee 166 038 53115 Bonn 039 Germany 040 E-Mail: greve@giub.uni-bonn.de 041 042 ---------------------------------------------------------------------------*/ 043 package org.deegree.io.datastore; 044 045 import java.util.ArrayList; 046 import java.util.Collection; 047 import java.util.Iterator; 048 import java.util.LinkedHashMap; 049 import java.util.List; 050 import java.util.Map; 051 052 import org.deegree.datatypes.QualifiedName; 053 import org.deegree.framework.log.ILogger; 054 import org.deegree.framework.log.LoggerFactory; 055 import org.deegree.i18n.Messages; 056 import org.deegree.io.datastore.schema.MappedFeatureType; 057 import org.deegree.io.datastore.schema.MappedPropertyType; 058 import org.deegree.model.feature.schema.PropertyType; 059 import org.deegree.ogcbase.ElementStep; 060 import org.deegree.ogcbase.PropertyPath; 061 import org.deegree.ogcbase.PropertyPathFactory; 062 import org.deegree.ogcbase.PropertyPathStep; 063 import org.deegree.ogcwebservices.wfs.operation.GetFeature; 064 065 /** 066 * Helper class that resolves {@link PropertyPath} instances (e.g. PropertyName elements in {@link GetFeature}) against 067 * the property structure of {@link MappedFeatureType}s. 068 * 069 * @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a> 070 * @author last edited by: $Author: apoth $ 071 * 072 * @version $Revision: 9342 $, $Date: 2007-12-27 13:32:57 +0100 (Do, 27 Dez 2007) $ 073 */ 074 public class PropertyPathResolver { 075 076 private static final ILogger LOG = LoggerFactory.getLogger( PropertyPathResolver.class ); 077 078 /** 079 * Ensures that the requested property begins with a "feature type step" (this may also be an alias). 080 * <p> 081 * This is necessary, as section 7.4.2 of the Web Feature Implementation Specification 1.1.0 states: 082 * </p> 083 * <p> 084 * The first step of a relative location path <b>may</b> correspond to the root element of the feature property 085 * being referenced <b>or</b> to the root element of the feature type with the next step corresponding to the root 086 * element of the feature property being referenced. 087 * </p> 088 * 089 * @param ft 090 * featureType that the requested properties refer to 091 * @param alias 092 * alias for the feature type (may be null) 093 * @param path 094 * requested property 095 * @return normalized path, i.e. it begins with a feature type step 096 */ 097 public static PropertyPath normalizePropertyPath( MappedFeatureType ft, String alias, PropertyPath path ) { 098 QualifiedName ftName = ft.getName(); 099 QualifiedName firstStep = path.getStep( 0 ).getPropertyName(); 100 if ( !( firstStep.equals( ftName ) || ( alias != null && firstStep.equals( new QualifiedName( '$' + alias ) ) ) ) ) { 101 if ( alias != null ) { 102 path.prepend( PropertyPathFactory.createPropertyPathStep( new QualifiedName( '$' + alias ) ) ); 103 } else { 104 path.prepend( PropertyPathFactory.createPropertyPathStep( ftName ) ); 105 } 106 } 107 return path; 108 } 109 110 /** 111 * Ensures that all requested properties begin with a feature type step or an alias. 112 * <p> 113 * If no properties are specified at all, a single PropertyPath entry is created that selects the whole feature 114 * type. 115 * </p> 116 * 117 * @param ft 118 * feature type that the requested properties refer to 119 * @param alias 120 * alias for the feature type, may be null 121 * @param paths 122 * requested properties, may not be null 123 * @return normalized paths 124 */ 125 public static PropertyPath[] normalizePropertyPaths( MappedFeatureType ft, String alias, PropertyPath[] paths ) { 126 for ( int i = 0; i < paths.length; i++ ) { 127 paths[i] = normalizePropertyPath( ft, alias, paths[i] ); 128 } 129 if ( paths.length == 0 ) { 130 QualifiedName firstStep = ft.getName(); 131 if ( alias != null ) { 132 firstStep = new QualifiedName( alias ); 133 } 134 paths = new PropertyPath[] { PropertyPathFactory.createPropertyPath( firstStep ) }; 135 } 136 return paths; 137 } 138 139 /** 140 * Ensures that all requested properties begin with a feature type (or alias) step. 141 * <p> 142 * If no properties are specified for a feature type, a single {@link PropertyPath} entry is created that selects 143 * the whole feature type. 144 * </p> 145 * 146 * @param fts 147 * feature types that the requested properties refer to 148 * @param paths 149 * requested properties, may not be null 150 * @return normalized paths 151 * @throws PropertyPathResolvingException 152 */ 153 public static List<PropertyPath>[] normalizePropertyPaths( MappedFeatureType[] fts, String[] ftAliases, 154 PropertyPath[] paths ) 155 throws PropertyPathResolvingException { 156 157 List<PropertyPath>[] propLists = new List[fts.length]; 158 159 if ( fts.length == 1 ) { 160 PropertyPath[] normalizedPaths = normalizePropertyPaths( fts[0], ftAliases != null ? ftAliases[0] : null, 161 paths ); 162 List<PropertyPath> pathList = new ArrayList<PropertyPath>( normalizedPaths.length ); 163 for ( PropertyPath path : normalizedPaths ) { 164 pathList.add( path ); 165 } 166 propLists[0] = pathList; 167 } else { 168 for ( PropertyPath path : paths ) { 169 QualifiedName firstStep = path.getStep( 0 ).getPropertyName(); 170 int i = 0; 171 172 if ( ftAliases == null ) { 173 for ( i = 0; i < fts.length; i++ ) { 174 if ( fts[i].getName().equals( firstStep ) ) { 175 break; 176 } 177 } 178 } else { 179 String localName = firstStep.getLocalName(); 180 for ( i = 0; i < fts.length; i++ ) { 181 String fullAlias = '$' + ftAliases[i]; 182 if ( fullAlias.equals( localName ) ) { 183 break; 184 } 185 } 186 } 187 188 if ( i < fts.length ) { 189 List props = propLists[i]; 190 if ( props == null ) { 191 props = new ArrayList<PropertyPath>(); 192 propLists[i] = props; 193 } 194 props.add( path ); 195 } else { 196 String msg = Messages.getMessage( "DATASTORE_PROPERTY_PATH_RESOLVE5", path, firstStep ); 197 throw new PropertyPathResolvingException( msg ); 198 } 199 } 200 201 for ( int i = 0; i < propLists.length; i++ ) { 202 List<PropertyPath> list = propLists[i]; 203 if ( list == null ) { 204 list = new ArrayList<PropertyPath>( 1 ); 205 propLists[i] = list; 206 if ( paths.length == 0 ) { 207 // only assume selection of every feature type if no feature type is selected explicitly 208 QualifiedName firstStep = fts[i].getName(); 209 if ( ftAliases != null ) { 210 firstStep = new QualifiedName( "$" + ftAliases[i] ); 211 } 212 PropertyPath ftSelect = PropertyPathFactory.createPropertyPath( firstStep ); 213 list.add( ftSelect ); 214 } 215 } 216 } 217 } 218 return propLists; 219 } 220 221 /** 222 * Determines the properties of the given feature type that have to be fetched based on the requested property 223 * paths. 224 * <p> 225 * Returns a helper <code>Map</code> that associates each (requested) property of the feature with the property 226 * paths that request it. Note that the returned helper map may contain more properties than specified. This 227 * behaviour is due to section 9.2 of the Web Feature Implementation Specification 1.1.0: 228 * </p> 229 * <p> 230 * In the event that a WFS encounters a query that does not select all mandatory properties of a feature, the WFS 231 * will internally augment the property name list to include all mandatory property names. 232 * </p> 233 * <p> 234 * Note that every requested property path must begin with a step that selects the given feature type. 235 * </p> 236 * 237 * @param ft 238 * feature type 239 * @param alias 240 * alias for the feature type (may be null) 241 * @param requestedPaths 242 * @return <code>Map</code>, key class: <code>MappedPropertyType</code>, value class: <code>Collection</code> 243 * (of <code>PropertyPath</code> instances) 244 * @throws PropertyPathResolvingException 245 */ 246 public static Map<MappedPropertyType, Collection<PropertyPath>> determineFetchProperties( 247 MappedFeatureType ft, 248 String alias, 249 PropertyPath[] requestedPaths ) 250 throws PropertyPathResolvingException { 251 252 Map<MappedPropertyType, Collection<PropertyPath>> requestedMap = null; 253 requestedMap = determineRequestedProperties( ft, alias, requestedPaths ); 254 if ( requestedPaths.length != 0 ) { 255 // only augment mandatory properties, if any property is selected 256 requestedMap = augmentFetchProperties( ft, alias, requestedMap ); 257 } 258 return requestedMap; 259 } 260 261 /** 262 * Builds a map that associates each requested property of the feature with the property paths that request it. The 263 * property path may well select a property of a subfeature, but the key values in the map are always (direct) 264 * properties of the given feature type. 265 * 266 * @param ft 267 * feature type 268 * @param alias 269 * alias for the feature type (may be null) 270 * @param requestedPaths 271 * @return map that associates each requested property with the property paths that request it 272 * @throws PropertyPathResolvingException 273 */ 274 private static Map<MappedPropertyType, Collection<PropertyPath>> determineRequestedProperties( 275 MappedFeatureType ft, 276 String alias, 277 278 PropertyPath[] requestedPaths ) 279 throws PropertyPathResolvingException { 280 281 Map<MappedPropertyType, Collection<PropertyPath>> propertyMap = new LinkedHashMap<MappedPropertyType, Collection<PropertyPath>>(); 282 283 for ( int i = 0; i < requestedPaths.length; i++ ) { 284 PropertyPath requestedPath = requestedPaths[i]; 285 QualifiedName firstStep = requestedPath.getStep( 0 ).getPropertyName(); 286 if ( firstStep.equals( ft.getName() ) 287 || ( alias != null && firstStep.equals( new QualifiedName( '$' + alias ) ) ) ) { 288 if ( requestedPath.getSteps() == 1 ) { 289 // path requests the whole feature 290 PropertyType[] allProperties = ft.getProperties(); 291 for ( int j = 0; j < allProperties.length; j++ ) { 292 Collection<PropertyPath> paths = propertyMap.get( allProperties[j] ); 293 if ( paths == null ) { 294 paths = new ArrayList<PropertyPath>(); 295 } 296 PropertyPath newPropertyPath = PropertyPathFactory.createPropertyPath( ft.getName() ); 297 newPropertyPath.append( PropertyPathFactory.createPropertyPathStep( allProperties[j].getName() ) ); 298 paths.add( newPropertyPath ); 299 propertyMap.put( (MappedPropertyType) allProperties[j], paths ); 300 } 301 } else { 302 // path requests a certain property 303 QualifiedName propertyName = requestedPath.getStep( 1 ).getPropertyName(); 304 PropertyType property = ft.getProperty( propertyName ); 305 if ( property == null ) { 306 String msg = Messages.getMessage( "DATASTORE_NO_SUCH_PROPERTY2", requestedPath, ft.getName(), 307 propertyName ); 308 throw new PropertyPathResolvingException( msg ); 309 } 310 Collection<PropertyPath> paths = propertyMap.get( property ); 311 if ( paths == null ) { 312 paths = new ArrayList<PropertyPath>(); 313 } 314 paths.add( requestedPath ); 315 propertyMap.put( (MappedPropertyType) property, paths ); 316 } 317 } else { 318 String msg = "Internal error in PropertyPathResolver: no property with name '" + requestedPath 319 + "' in feature type '" + ft.getName() + "'."; 320 throw new PropertyPathResolvingException( msg ); 321 } 322 } 323 return propertyMap; 324 } 325 326 /** 327 * Returns an augmented version of the input map that contains additional entries for all mandatory properties of 328 * the feature type. 329 * 330 * @param ft 331 * feature type 332 * @param alias 333 * alias for the feature type (may be null) 334 * @param requestedMap 335 * @return augmented version of the input map 336 * @throws PropertyPathResolvingException 337 */ 338 private static Map<MappedPropertyType, Collection<PropertyPath>> augmentFetchProperties( 339 MappedFeatureType ft, 340 String alias, 341 Map<MappedPropertyType, Collection<PropertyPath>> requestedMap ) { 342 343 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 344 LOG.logDebug( "Properties to be fetched for feature type '" + ft.getName() + "' (alias=" + alias + "):" ); 345 } 346 347 Map<MappedPropertyType, Collection<PropertyPath>> augmentedMap = new LinkedHashMap<MappedPropertyType, Collection<PropertyPath>>(); 348 PropertyType[] allProperties = ft.getProperties(); 349 for ( int i = 0; i < allProperties.length; i++ ) { 350 MappedPropertyType property = (MappedPropertyType) allProperties[i]; 351 Collection<PropertyPath> requestingPaths = requestedMap.get( property ); 352 if ( requestingPaths != null ) { 353 LOG.logDebug( "- " + property.getName() ); 354 augmentedMap.put( property, requestingPaths ); 355 for ( PropertyPath path : requestingPaths ) { 356 LOG.logDebug( " - Requested by path: '" + path + "'" ); 357 } 358 } else if ( property.getMinOccurs() > 0 ) { 359 LOG.logDebug( "- " + property.getName() + " (augmented)" ); 360 Collection<PropertyPath> mandatoryPaths = new ArrayList<PropertyPath>(); 361 List<PropertyPathStep> stepList = new ArrayList<PropertyPathStep>( 2 ); 362 stepList.add( new ElementStep( ft.getName() ) ); 363 stepList.add( new ElementStep( property.getName() ) ); 364 PropertyPath mandatoryPath = new PropertyPath( stepList ); 365 mandatoryPaths.add( mandatoryPath ); 366 augmentedMap.put( property, mandatoryPaths ); 367 } 368 } 369 return augmentedMap; 370 } 371 372 /** 373 * Determines the sub property paths that are needed to fetch the given property paths for the also given property. 374 * 375 * @param featureType 376 * @param propertyPaths 377 * @return sub property paths that are needed to fetch the given property paths 378 */ 379 public static PropertyPath[] determineSubPropertyPaths( MappedFeatureType featureType, 380 Collection<PropertyPath> propertyPaths ) { 381 Collection<PropertyPath> subPropertyPaths = new ArrayList<PropertyPath>(); 382 383 Iterator iter = propertyPaths.iterator(); 384 while ( iter.hasNext() ) { 385 PropertyPath path = (PropertyPath) iter.next(); 386 if ( path.getSteps() > 2 ) { 387 subPropertyPaths.add( PropertyPathFactory.createPropertyPath( path, 2, path.getSteps() ) ); 388 } else { 389 PropertyType[] subProperties = featureType.getProperties(); 390 for ( int i = 0; i < subProperties.length; i++ ) { 391 PropertyPath subPropertyPath = PropertyPathFactory.createPropertyPath( featureType.getName() ); 392 subPropertyPath.append( PropertyPathFactory.createPropertyPathStep( subProperties[i].getName() ) ); 393 subPropertyPaths.add( subPropertyPath ); 394 } 395 } 396 } 397 398 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 399 LOG.logDebug( "Original property paths:" ); 400 for ( PropertyPath path : propertyPaths ) { 401 LOG.logDebug( "- '" + path + "'" ); 402 } 403 LOG.logDebug( "Sub feature property paths:" ); 404 for ( PropertyPath path : subPropertyPaths ) { 405 LOG.logDebug( "- '" + path + "'" ); 406 } 407 } 408 PropertyPath[] subPaths = subPropertyPaths.toArray( new PropertyPath[subPropertyPaths.size()] ); 409 return subPaths; 410 } 411 }