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