001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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 }