001: /*
002: * Geotools2 - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, GeoTools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: */
017:
018: package org.geotools.data.complex;
019:
020: import java.io.IOException;
021: import java.util.ArrayList;
022: import java.util.Arrays;
023: import java.util.HashMap;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Map;
027: import java.util.Map.Entry;
028: import java.util.logging.Logger;
029:
030: import javax.xml.namespace.QName;
031:
032: import org.apache.commons.collections.map.LinkedMap;
033: import org.geotools.data.DefaultQuery;
034: import org.geotools.data.Query;
035: import org.geotools.data.complex.filter.XPath;
036: import org.geotools.data.complex.filter.XPath.Step;
037: import org.geotools.data.complex.filter.XPath.StepList;
038: import org.geotools.feature.iso.Types;
039: import org.geotools.filter.FilterFactoryImplNamespaceAware;
040: import org.opengis.feature.Attribute;
041: import org.opengis.feature.ComplexAttribute;
042: import org.opengis.feature.Feature;
043: import org.opengis.feature.type.AttributeDescriptor;
044: import org.opengis.feature.type.AttributeType;
045: import org.opengis.feature.type.Name;
046: import org.opengis.filter.FilterFactory;
047: import org.opengis.filter.expression.Expression;
048: import org.xml.sax.Attributes;
049: import org.xml.sax.helpers.NamespaceSupport;
050:
051: /**
052: * An alternative strategy to fetch grouped multivalued content.
053: *
054: * @author Gabriel Roldan
055: * @version $Id: GroupingFeatureIterator2.java 27863 2007-11-12 20:34:34Z desruisseaux $
056: * @source $URL:
057: * http://svn.geotools.org/geotools/branches/2.4.x/modules/unsupported/community-schemas/community-schema-ds/src/main/java/org/geotools/data/complex/GroupingFeatureIterator.java $
058: * @since 2.4
059: */
060: class GroupingFeatureIterator2 extends AbstractMappingFeatureIterator {
061: private static final Logger LOGGER = org.geotools.util.logging.Logging
062: .getLogger(GroupingFeatureIterator2.class.getPackage()
063: .getName());
064:
065: /**
066: * maxFeatures restriction value as provided by query
067: */
068: private int maxFeatures;
069:
070: /**
071: *
072: */
073: private Feature curSrcFeature;
074:
075: /** counter to ensure maxFeatures is not exceeded */
076: private int featureCounter;
077:
078: /**
079: * List of attribute mappings marked as multivalued, and any attribute which
080: * maps to a child attribute of any multivalued mapping. The keys in the map
081: * are attribute mappings explicitly set as multivalued. The values in the
082: * map are the list of mappings whose target xpath expressions correspond to
083: * child properties of the target xpath of the mapping used as key.
084: */
085: private LinkedMap multivaluedMappings;
086:
087: /**
088: * List of attribute mappings not marked as multivalued and whose target
089: * xpath is not a child of any multivalued attribute.
090: */
091: private List /* AttributeMapping */singleValuedMappings;
092:
093: /**
094: * flag to avoid fetching multiple source feature in repeated calld to
095: * hasNext() without calls to next() in the middle
096: */
097: private boolean hasNextCalled;
098:
099: private XPath xpathAttributeBuilder;
100:
101: public GroupingFeatureIterator2(final ComplexDataStore store,
102: final FeatureTypeMapping mappings, final Query query)
103: throws IOException {
104: super (store, mappings, query);
105: xpathAttributeBuilder = new XPath();
106: xpathAttributeBuilder.setFeatureFactory(super .attf);
107: NamespaceSupport namespaces = mappings.getNamespaces();
108: // FilterFactory namespaceAwareFilterFactory =
109: // CommonFactoryFinder.getFilterFactory(hints);
110: FilterFactory namespaceAwareFilterFactory = new FilterFactoryImplNamespaceAware(
111: namespaces);
112: xpathAttributeBuilder
113: .setFilterFactory(namespaceAwareFilterFactory);
114:
115: splitMappings();
116: }
117:
118: protected Query getUnrolledQuery(Query query) {
119: maxFeatures = query.getMaxFeatures();
120: Query unmappedQuery = store.unrollQuery(query, mapping);
121: ((DefaultQuery) unmappedQuery)
122: .setMaxFeatures(Integer.MAX_VALUE);
123:
124: unmappedQuery = ensureGroupingAttsPresent(unmappedQuery);
125:
126: return unmappedQuery;
127: }
128:
129: public boolean hasNext() {
130: if (hasNextCalled) {
131: return curSrcFeature != null;
132: }
133:
134: boolean exists = false;
135:
136: if (sourceFeatures != null && featureCounter < maxFeatures) {
137:
138: exists = this .sourceFeatures.hasNext();
139:
140: if (exists && this .curSrcFeature == null) {
141: this .curSrcFeature = (Feature) this .sourceFeatures
142: .next();
143: }
144: }
145:
146: if (!exists) {
147: LOGGER.finest("no more features, produced "
148: + featureCounter);
149: }
150:
151: hasNextCalled = true;
152: if (!exists) {
153: close();
154: }
155: return exists;
156: }
157:
158: public Object next() {
159: if (!hasNext()) {
160: throw new IllegalStateException(
161: "there are no more features in this iterator");
162: }
163: Feature next;
164: try {
165: next = computeNext();
166: } catch (IOException e) {
167: close();
168: throw new RuntimeException(e);
169: }
170: hasNextCalled = false;
171: ++featureCounter;
172: return next;
173: }
174:
175: // ///////////////////
176:
177: /**
178: *
179: */
180: private Feature computeNext() throws IOException {
181: assert this .curSrcFeature != null : "hastNext not called?";
182:
183: // get the mapping set of a feature attribute
184: final AttributeDescriptor targetNode = mapping
185: .getTargetFeature();
186:
187: // create the target feature and iterate in the source ones to set its
188: // values.
189: final String fid = extractIdForFeature(curSrcFeature);
190: final Feature targetFeature = attf.createFeature(null,
191: targetNode, fid);
192:
193: setNonMultivaluedAttributes(targetFeature);
194:
195: setMultiValuedAttributes(targetFeature);
196:
197: if (!sourceFeatures.hasNext()) {
198: close();
199: }
200: return targetFeature;
201: }
202:
203: private void setNonMultivaluedAttributes(final Feature targetFeature)
204: throws IOException {
205: AttributeMapping mapping;
206: for (Iterator it = this .singleValuedMappings.iterator(); it
207: .hasNext();) {
208: mapping = (AttributeMapping) it.next();
209: StepList targetXPath = mapping.getTargetXPath();
210: if (targetXPath.size() == 1) {
211: Step rootStep = (Step) targetXPath.get(0);
212: QName stepName = rootStep.getName();
213: if (Types.equals(targetFeature.getDescriptor()
214: .getName(), stepName)) {
215: // ignore the top level mapping for the Feature
216: // itself
217: // as it was already set
218: continue;
219: }
220: }
221: setSingleValuedAttribute(targetFeature, curSrcFeature,
222: mapping);
223: }
224: }
225:
226: private void setMultiValuedAttributes(final Feature targetFeature) {
227: final List /* Name */groupByAttNames = toTypeNames(mapping
228: .getGroupByAttNames());
229: // the grouping values to check for equality
230: final List baseGroupingAttributes = extractGroupingAttributes(
231: curSrcFeature, groupByAttNames);
232:
233: int index = 1;
234: setMultiValuedAttributes(targetFeature, curSrcFeature, index);
235:
236: List currFeatureGroupingAtts;
237: while (sourceFeatures.hasNext()) {
238: curSrcFeature = (Feature) sourceFeatures.next();
239: currFeatureGroupingAtts = extractGroupingAttributes(
240: curSrcFeature, groupByAttNames);
241: if (baseGroupingAttributes.equals(currFeatureGroupingAtts)) {
242: index++;
243: setMultiValuedAttributes(targetFeature, curSrcFeature,
244: index);
245:
246: if (index % 100 == 0) {
247: System.out.print('-');
248: }
249: } else {
250: System.out.print('\n');
251: break;
252: }
253: }
254: System.out.print('\n');
255: }
256:
257: private void setMultiValuedAttributes(final Feature targetFeature,
258: final Feature curSrcFeature, final int index) {
259:
260: AttributeMapping mapping;
261: StepList targetXpathAttr;
262: Expression identifierExpression;
263: Expression sourceExpression;
264: Map clientProperties;
265: AttributeType targetNodeInstance;
266:
267: for (Iterator it = multivaluedMappings.entrySet().iterator(); it
268: .hasNext();) {
269: Map.Entry entry = (Entry) it.next();
270: mapping = (AttributeMapping) entry.getKey();
271: List children = (List) entry.getValue();
272: targetXpathAttr = mapping.getTargetXPath();
273: targetXpathAttr = setIndexOfLastStep(targetFeature,
274: targetXpathAttr, index);
275:
276: identifierExpression = mapping.getIdentifierExpression();
277: sourceExpression = mapping.getSourceExpression();
278: clientProperties = mapping.getClientProperties();
279: targetNodeInstance = mapping.getTargetNodeInstance();
280:
281: setXpathValue(targetFeature, identifierExpression,
282: sourceExpression, targetXpathAttr,
283: targetNodeInstance, clientProperties);
284:
285: final int insertPositon = targetXpathAttr.size();
286: for (Iterator childIterator = children.iterator(); childIterator
287: .hasNext();) {
288: mapping = (AttributeMapping) childIterator.next();
289: targetXpathAttr = mapping.getTargetXPath();
290: targetXpathAttr = insertIndexInXpath(targetFeature,
291: targetXpathAttr, index, insertPositon);
292:
293: identifierExpression = mapping
294: .getIdentifierExpression();
295: sourceExpression = mapping.getSourceExpression();
296: clientProperties = mapping.getClientProperties();
297: targetNodeInstance = mapping.getTargetNodeInstance();
298:
299: setXpathValue(targetFeature, identifierExpression,
300: sourceExpression, targetXpathAttr,
301: targetNodeInstance, clientProperties);
302: }
303: }
304: }
305:
306: private void setXpathValue(final Attribute targetFeature,
307: final Expression idExpression,
308: final Expression sourceExpression,
309: final StepList targetXpathAttr,
310: final AttributeType targetNodeInstance,
311: final Map clientProperties) {
312: Object value;
313:
314: try {
315: value = getValue(sourceExpression, curSrcFeature);
316: } catch (Exception e) {
317: // HACK: what we actually need to resolve is dealing
318: // with queries that restricts the attributes returned
319: // by the source featurestore
320: return;
321: }
322: String id = extractIdForAttribute(idExpression, curSrcFeature);
323:
324: Attribute instance = xpathAttributeBuilder.set(targetFeature,
325: targetXpathAttr, value, id, targetNodeInstance);
326: Map clientPropsMappings = clientProperties;
327: setClientProperties(instance, curSrcFeature,
328: clientPropsMappings);
329: }
330:
331: private final StepList setIndexOfLastStep(final Attribute root,
332: final StepList xpathAttrDefinition, int index) {
333:
334: int insertPosition = xpathAttrDefinition.size();
335:
336: StepList indexXpath = insertIndexInXpath(root,
337: xpathAttrDefinition, index, insertPosition);
338:
339: return indexXpath;
340: }
341:
342: /**
343: * Insert index into step of xpath; Position indicate the step.
344: *
345: * @param featureType
346: * @param attrXpath
347: * @param index
348: * @param insertPositon
349: *
350: * @return String
351: */
352: private final StepList insertIndexInXpath(final Attribute root,
353: final StepList attrXpath, final int index,
354: final int insertPositon) {
355:
356: // Constructs an Xpath adding index in the step corresponding to complex
357: // attribute
358: StepList stepList = (StepList) attrXpath.clone();
359:
360: Step step = (Step) stepList.get(insertPositon - 1);
361: Step newStep = new XPath.Step(step.getName(), index);
362:
363: stepList.set(insertPositon - 1, newStep);
364:
365: return stepList;
366: }
367:
368: private List toTypeNames(final List groupByAttNames) {
369: List typeNames = new ArrayList(groupByAttNames.size());
370: String sourceAttName;
371: Name attributeName;
372: for (Iterator it = groupByAttNames.iterator(); it.hasNext();) {
373: sourceAttName = (String) it.next();
374: attributeName = Types.typeName(sourceAttName);
375: typeNames.add(attributeName);
376: }
377: return typeNames;
378: }
379:
380: /**
381: * Extract the attributes from grouping attributes.
382: *
383: * @param groupByAttNames
384: *
385: * @param Feature
386: * a source feature
387: * @return List<List<Attribute>> the the contened list has the attributes
388: * required
389: */
390: private final List/* <List<Attribute>> */extractGroupingAttributes(
391: final ComplexAttribute srcFeature,
392: final List /* Name */groupByAttNames) {
393:
394: List/* <List<Attribute>> */attrGroup = new ArrayList/* <List<Attribute>> */(
395: groupByAttNames.size());
396:
397: for (Iterator it = groupByAttNames.iterator(); it.hasNext();) {
398: Name name = (Name) it.next();
399: List/* <Attribute> */listAttrForName = srcFeature
400: .get(name);
401: attrGroup.add(listAttrForName);
402: }
403:
404: return attrGroup;
405: }
406:
407: /**
408: * Sets the values of grouping attributes.
409: *
410: * @param sourceFeature
411: * @param groupingMappings
412: * @param targetFeature
413: *
414: * @return Feature. Target feature sets with simple attributes
415: */
416: private void setSingleValuedAttribute(final Feature target,
417: final Feature source, final AttributeMapping attMapping)
418: throws IOException {
419:
420: final Expression sourceExpression = attMapping
421: .getSourceExpression();
422: final AttributeType targetNodeType = attMapping
423: .getTargetNodeInstance();
424: final StepList xpath = attMapping.getTargetXPath();
425:
426: Object value = getValue(sourceExpression, source);
427:
428: String id = null;
429: if (Expression.NIL != attMapping.getIdentifierExpression()) {
430: id = extractIdForAttribute(attMapping
431: .getIdentifierExpression(), this .curSrcFeature);
432: }
433: Attribute instance = xpathAttributeBuilder.set(target, xpath,
434: value, id, targetNodeType);
435: Map clientPropsMappings = attMapping.getClientProperties();
436: setClientProperties(instance, source, clientPropsMappings);
437: }
438:
439: private void setClientProperties(final Attribute target,
440: final Feature source, final Map clientProperties) {
441: if (clientProperties.size() == 0) {
442: return;
443: }
444: final Map nodeAttributes = new HashMap();
445: final AttributeDescriptor node = target.getDescriptor();
446:
447: for (Iterator it = clientProperties.entrySet().iterator(); it
448: .hasNext();) {
449: Map.Entry entry = (Map.Entry) it.next();
450: org.opengis.feature.type.Name propName = (org.opengis.feature.type.Name) entry
451: .getKey();
452: Expression propExpr = (Expression) entry.getValue();
453:
454: Object propValue = getValue(propExpr, source);
455:
456: nodeAttributes.put(propName, propValue);
457: }
458:
459: node.putUserData(Attributes.class, nodeAttributes);
460: }
461:
462: /**
463: * Split the attribute mappings in two sets, the ones that belong to a
464: * multivalued property {@link #multivaluedMappings} and the ones that not
465: * {@link #singleValuedMappings}.
466: */
467: private void splitMappings() {
468: this .multivaluedMappings = new LinkedMap();
469: this .singleValuedMappings = new ArrayList(mapping
470: .getAttributeMappings());
471:
472: for (Iterator it = mapping.getAttributeMappings().iterator(); it
473: .hasNext();) {
474: AttributeMapping am = (AttributeMapping) it.next();
475: if (am.isMultiValued()) {
476: singleValuedMappings.remove(am);
477: multivaluedMappings.put(am, new ArrayList(2));
478: }
479: }
480:
481: for (Iterator mvaluedPaths = multivaluedMappings.entrySet()
482: .iterator(); mvaluedPaths.hasNext();) {
483: final Map.Entry entry = (Entry) mvaluedPaths.next();
484: final AttributeMapping parentMapping = (AttributeMapping) entry
485: .getKey();
486: final List childMappings = (List) entry.getValue();
487: final StepList mvaluedPath = parentMapping.getTargetXPath();
488:
489: for (Iterator mappings = mapping.getAttributeMappings()
490: .iterator(); mappings.hasNext();) {
491: final AttributeMapping am = (AttributeMapping) mappings
492: .next();
493: if (am == parentMapping) {
494: continue;
495: }
496:
497: final StepList targetXPath = am.getTargetXPath();
498: boolean isChild = true;
499: if (targetXPath.size() >= mvaluedPath.size()) {
500: for (int currStepIdx = 0; currStepIdx < mvaluedPath
501: .size(); currStepIdx++) {
502: XPath.Step parentStep = (XPath.Step) mvaluedPath
503: .get(currStepIdx);
504: XPath.Step childStep = (XPath.Step) targetXPath
505: .get(currStepIdx);
506: QName parentStepName = parentStep.getName();
507: QName childStepName = childStep.getName();
508: if (!parentStepName.equals(childStepName)) {
509: isChild = false;
510: break;
511: }
512: }
513: } else {
514: isChild = false;
515: }
516: if (isChild) {
517: singleValuedMappings.remove(am);
518: childMappings.add(am);
519: }
520: }
521: }
522: }
523:
524: /**
525: * Takes a Query and returns another one ensuring that all the grouping
526: * attributes are requested, in order to be able of producing the correct
527: * number of output features, for example, from a joined set of tables.
528: *
529: * @param query
530: * @return
531: */
532: private Query ensureGroupingAttsPresent(Query query) {
533: if (query.retrieveAllProperties()) {
534: return query;
535: }
536:
537: List groupByAttributeNames = super .mapping.getGroupByAttNames();
538: DefaultQuery neededQuery = new DefaultQuery(query);
539: List requestedAtts = Arrays.asList(query.getPropertyNames());
540: if (!requestedAtts.containsAll(groupByAttributeNames)) {
541: List remaining = new ArrayList(groupByAttributeNames);
542: remaining.removeAll(requestedAtts);
543: LOGGER.fine("Adding missing grouping atts: " + remaining);
544:
545: List queryAtts = new ArrayList(remaining);
546: queryAtts.addAll(requestedAtts);
547:
548: neededQuery.setPropertyNames(queryAtts);
549: }
550: return neededQuery;
551: }
552: }
|