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 }