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