Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

GEOS-5755 Support Local Resolve in App-Schema

  • Loading branch information...
commit e76dd59bbf06439d609a389ce98cf819d5fa14fa 1 parent 98fc74a
NielsCharlier NielsCharlier authored
9 modules/extension/app-schema/app-schema/pom.xml
@@ -125,7 +125,12 @@
125 125 <dependency>
126 126 <groupId>xpp3</groupId>
127 127 <artifactId>xpp3_min</artifactId>
128   - </dependency>
  128 + </dependency>
  129 + <dependency>
  130 + <groupId>org.geotools.ogc</groupId>
  131 + <artifactId>net.opengis.wfs</artifactId>
  132 + <version>${project.version}</version>
  133 + </dependency>
129 134 <dependency>
130 135 <!-- for FilenameUtils -->
131 136 <groupId>commons-io</groupId>
@@ -186,7 +191,7 @@
186 191 <artifactId>geosciml-3.0-seegrid</artifactId>
187 192 <version>3.0.0-1</version>
188 193 <scope>test</scope>
189   - </dependency>
  194 + </dependency>
190 195 </dependencies>
191 196
192 197
187 ...ension/app-schema/app-schema/src/main/java/org/geotools/data/complex/AbstractMappingFeatureIterator.java
@@ -19,25 +19,33 @@
19 19 import java.io.IOException;
20 20 import java.util.ArrayList;
21 21 import java.util.Collection;
  22 +import java.util.Collections;
22 23 import java.util.HashMap;
23 24 import java.util.List;
24 25 import java.util.Map;
  26 +import java.util.concurrent.atomic.AtomicBoolean;
25 27 import java.util.logging.Logger;
  28 +
  29 +import net.opengis.wfs20.ResolveValueType;
  30 +
26 31 import org.geotools.data.DataSourceException;
27 32 import org.geotools.data.Query;
28 33 import org.geotools.data.complex.filter.XPath;
29 34 import org.geotools.data.complex.filter.XPathUtil.StepList;
30 35 import org.geotools.factory.CommonFactoryFinder;
  36 +import org.geotools.factory.Hints;
31 37 import org.geotools.feature.AppSchemaFeatureFactoryImpl;
32 38 import org.geotools.feature.FeatureIterator;
33 39 import org.geotools.feature.Types;
34 40 import org.geotools.feature.type.ComplexFeatureTypeFactoryImpl;
35 41 import org.geotools.filter.FilterFactoryImplNamespaceAware;
  42 +import org.geotools.filter.identity.FeatureIdImpl;
36 43 import org.geotools.xlink.XLINK;
37 44 import org.opengis.feature.Attribute;
38 45 import org.opengis.feature.Feature;
39 46 import org.opengis.feature.FeatureFactory;
40 47 import org.opengis.feature.type.AttributeDescriptor;
  48 +import org.opengis.feature.type.AttributeType;
41 49 import org.opengis.feature.type.FeatureTypeFactory;
42 50 import org.opengis.feature.type.PropertyDescriptor;
43 51 import org.opengis.feature.GeometryAttribute;
@@ -47,9 +55,11 @@
47 55 import org.opengis.filter.FilterFactory2;
48 56 import org.opengis.filter.expression.Expression;
49 57 import org.opengis.filter.expression.PropertyName;
  58 +import org.opengis.filter.identity.FeatureId;
50 59 import org.opengis.referencing.crs.CoordinateReferenceSystem;
51 60 import org.xml.sax.Attributes;
52 61 import org.xml.sax.helpers.NamespaceSupport;
  62 +
53 63 import com.vividsolutions.jts.geom.EmptyGeometry;
54 64 import com.vividsolutions.jts.geom.Geometry;
55 65
@@ -114,6 +124,10 @@
114 124 protected int featureCounter;
115 125
116 126 protected NamespaceSupport namespaces;
  127 +
  128 + protected int resolveDepth;
  129 +
  130 + protected Integer resolveTimeOut;
117 131
118 132 /**
119 133 * True if hasNext has been called prior to calling next()
@@ -160,6 +174,19 @@ public AbstractMappingFeatureIterator(AppSchemaDataAccess store, FeatureTypeMapp
160 174 xpathAttributeBuilder.setFeatureFactory(attf);
161 175 initialiseSourceFeatures(mapping, unrolledQuery, query.getCoordinateSystemReproject());
162 176 xpathAttributeBuilder.setFilterFactory(namespaceAwareFilterFactory);
  177 +
  178 + Hints hints = query.getHints();
  179 + ResolveValueType resolveVal = (ResolveValueType) hints.get( Hints.RESOLVE );
  180 + boolean resolve = ResolveValueType.ALL.equals(resolveVal) || ResolveValueType.LOCAL.equals(resolveVal);
  181 +
  182 + if (!resolve && !ResolveValueType.NONE.equals(resolveVal)) {
  183 + throw new IllegalArgumentException("Resolve:" + resolveVal.getName() + " is not supported in app-schema!");
  184 + }
  185 +
  186 + Integer atd = (Integer) hints.get(Hints.ASSOCIATION_TRAVERSAL_DEPTH);
  187 +
  188 + resolveDepth = resolve ? atd==null? 0 : atd : 0;
  189 + resolveTimeOut = (Integer) hints.get( Hints.RESOLVE_TIMEOUT );
163 190 }
164 191
165 192 //properties can only be set by constructor, before initialising source features
@@ -302,8 +329,120 @@ protected Map getClientProperties(Property attribute) throws DataSourceException
302 329 return clientProperties;
303 330 }
304 331
305   - protected void setClientProperties(final Attribute target, final Object source,
306   - final Map<Name, Expression> clientProperties) {
  332 + private class FeatureFinder implements Runnable {
  333 + private Feature feature = null;
  334 + private String refId;
  335 + private Hints hints;
  336 + public AtomicBoolean stopFlag = new AtomicBoolean(false);
  337 +
  338 + public synchronized Feature getFeature() {
  339 + return feature;
  340 + }
  341 +
  342 + private synchronized void setFeature (Feature feature) {
  343 + this.feature = feature;
  344 + }
  345 +
  346 + public FeatureFinder (String refId, Hints hints) {
  347 + this.refId = refId;
  348 + this.hints = hints;
  349 + }
  350 +
  351 + @Override
  352 + public void run() {
  353 + try {
  354 + Feature feature = DataAccessRegistry.getInstance().findFeature(new FeatureIdImpl (refId), hints, stopFlag);
  355 + if (!stopFlag.get()) {
  356 + setFeature(feature);
  357 + }
  358 + } catch (IOException e) { // ignore, no resolve
  359 + }
  360 + }
  361 + };
  362 +
  363 +
  364 + protected static String referenceToIdentifier(String reference) {
  365 +
  366 + //TODO: support custom rules in mapping file
  367 +
  368 + String[] urn = reference.split(":");
  369 +
  370 + String lastPart = urn[urn.length - 1];
  371 +
  372 + if (lastPart.contains("#")) {
  373 + lastPart = lastPart.substring(lastPart.lastIndexOf("#"));
  374 + }
  375 +
  376 + if ("missing".equals(urn[urn.length - 1]) || "unknown".equals(urn[urn.length - 1])) {
  377 + return null;
  378 + }
  379 +
  380 + return lastPart;
  381 + }
  382 +
  383 + protected Attribute setAttributeContent(Attribute target, StepList xpath, Object value, String id, AttributeType targetNodeType, boolean isXlinkRef, Expression sourceExpression, Object source, final Map<Name, Expression> clientProperties, boolean ignoreXlinkHref){
  384 + Attribute instance = null;
  385 +
  386 + Map<Name, Expression> properties = new HashMap<Name, Expression>(clientProperties);
  387 +
  388 + if (ignoreXlinkHref) {
  389 + properties.remove(XLINK_HREF_NAME);
  390 + }
  391 +
  392 + if (properties.containsKey(XLINK_HREF_NAME) && resolveDepth > 0) {
  393 + // local resolve
  394 +
  395 + String refid = referenceToIdentifier(getValue(properties.get(XLINK_HREF_NAME), source).toString());
  396 +
  397 + if (refid != null) {
  398 +
  399 + final Hints hints = new Hints();
  400 + if (resolveDepth > 1) {
  401 + hints.put(Hints.RESOLVE, ResolveValueType.ALL);
  402 + hints.put(Hints.RESOLVE_TIMEOUT, resolveTimeOut);
  403 + hints.put(Hints.ASSOCIATION_TRAVERSAL_DEPTH, resolveDepth - 1);
  404 + } else {
  405 + hints.put(Hints.RESOLVE, ResolveValueType.NONE);
  406 + }
  407 +
  408 + // let's try finding it
  409 + FeatureFinder finder = new FeatureFinder(refid, hints);
  410 + Thread thread = new Thread(finder);
  411 + long currentTime = System.currentTimeMillis();
  412 + thread.start();
  413 + while (thread.isAlive()
  414 + && (System.currentTimeMillis() - currentTime) / 1000 < resolveTimeOut) {
  415 + try {
  416 + Thread.sleep(500);
  417 + } catch (InterruptedException t) {
  418 + }
  419 + }
  420 + synchronized (finder.stopFlag) {
  421 + finder.stopFlag.set(true);
  422 + }
  423 +
  424 + if (finder.getFeature() != null) {
  425 + // found it
  426 +
  427 + instance = xpathAttributeBuilder.set(target, xpath,
  428 + Collections.singletonList(finder.getFeature()), id, targetNodeType,
  429 + false, sourceExpression);
  430 + properties.remove(XLINK_HREF_NAME);
  431 + }
  432 + }
  433 +
  434 + }
  435 +
  436 + if (instance == null) {
  437 + instance = xpathAttributeBuilder.set(target, xpath, value, id, targetNodeType, false, sourceExpression);
  438 + }
  439 +
  440 + setClientProperties(instance, source, properties);
  441 +
  442 + return instance;
  443 + }
  444 +
  445 + protected void setClientProperties(final Attribute target, final Object source, final Map<Name, Expression> clientProperties) {
307 446 if (target == null) {
308 447 return;
309 448 }
@@ -311,33 +450,33 @@ protected void setClientProperties(final Attribute target, final Object source,
311 450 if (source == null && clientProperties.isEmpty()) {
312 451 return;
313 452 }
314   -
  453 +
315 454 // NC - first calculate target attributes
316 455 final Map<Name, Object> targetAttributes = new HashMap<Name, Object>();
317 456 if (target.getUserData().containsValue(Attributes.class)) {
318 457 targetAttributes.putAll((Map<? extends Name, ? extends Object>) target.getUserData()
319 458 .get(Attributes.class));
320 459 }
321   - for (Map.Entry<Name, Expression> entry : clientProperties.entrySet()) {
322   - Name propName = entry.getKey();
323   - Object propExpr = entry.getValue();
324   - Object propValue;
325   - if (propExpr instanceof Expression) {
326   - propValue = getValue((Expression) propExpr, source);
327   - } else {
328   - propValue = propExpr;
329   - }
330   - if (propValue != null) {
331   - if (propValue instanceof Collection) {
332   - if (!((Collection)propValue).isEmpty()) {
333   - propValue = ((Collection)propValue).iterator().next();
334   - targetAttributes.put(propName, propValue);
335   - }
336   - } else {
337   - targetAttributes.put(propName, propValue);
338   - }
339   - }
340   - }
  460 + for (Map.Entry<Name, Expression> entry : clientProperties.entrySet()) {
  461 + Name propName = entry.getKey();
  462 + Object propExpr = entry.getValue();
  463 + Object propValue;
  464 + if (propExpr instanceof Expression) {
  465 + propValue = getValue((Expression) propExpr, source);
  466 + } else {
  467 + propValue = propExpr;
  468 + }
  469 + if (propValue != null) {
  470 + if (propValue instanceof Collection) {
  471 + if (!((Collection) propValue).isEmpty()) {
  472 + propValue = ((Collection) propValue).iterator().next();
  473 + targetAttributes.put(propName, propValue);
  474 + }
  475 + } else {
  476 + targetAttributes.put(propName, propValue);
  477 + }
  478 + }
  479 + }
341 480 // FIXME should set a child Property.. but be careful for things that
342 481 // are smuggled in there internally and don't exist in the schema, like
343 482 // XSDTypeDefinition, CRS etc.
@@ -345,7 +484,7 @@ protected void setClientProperties(final Attribute target, final Object source,
345 484 target.getUserData().put(Attributes.class, targetAttributes);
346 485 }
347 486
348   - setGeometryUserData(target, targetAttributes);
  487 + setGeometryUserData(target, targetAttributes);
349 488 }
350 489
351 490 protected void setGeometryUserData(Attribute target, Map<Name, Object> targetAttributes) {
29 modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/AppSchemaDataAccess.java
@@ -26,7 +26,9 @@
26 26 import java.util.LinkedList;
27 27 import java.util.List;
28 28 import java.util.Map;
  29 +import java.util.Map.Entry;
29 30 import java.util.Set;
  31 +import java.util.concurrent.atomic.AtomicBoolean;
30 32 import java.util.logging.Logger;
31 33
32 34 import org.geotools.data.DataAccess;
@@ -43,6 +45,9 @@
43 45 import org.geotools.data.complex.filter.XPathUtil.StepList;
44 46 import org.geotools.data.joining.JoiningQuery;
45 47 import org.geotools.factory.CommonFactoryFinder;
  48 +import org.geotools.factory.Hints;
  49 +import org.geotools.feature.FeatureCollection;
  50 +import org.geotools.feature.FeatureIterator;
46 51 import org.geotools.feature.Types;
47 52 import org.geotools.filter.FilterAttributeExtractor;
48 53 import org.geotools.filter.SortByImpl;
@@ -57,6 +62,7 @@
57 62 import org.opengis.filter.FilterFactory2;
58 63 import org.opengis.filter.expression.Expression;
59 64 import org.opengis.filter.expression.PropertyName;
  65 +import org.opengis.filter.identity.FeatureId;
60 66 import org.opengis.filter.sort.SortBy;
61 67 import org.opengis.filter.sort.SortOrder;
62 68
@@ -640,4 +646,27 @@ public void updateSchema(Name typeName, FeatureType featureType) throws IOExcept
640 646 throws IOException {
641 647 return new MappingFeatureSource(this, getMappingByName(typeName));
642 648 }
  649 +
  650 + public Feature findFeature(FeatureId id, Hints hints, AtomicBoolean stopFlag) throws IOException {
  651 + Feature result = null;
  652 + for (Entry<Name, FeatureTypeMapping> mapping: mappings.entrySet()) {
  653 + Filter filter = filterFac.id(id);
  654 + FeatureCollection<FeatureType, Feature> fCollection = new MappingFeatureSource(this, mapping.getValue()).getFeatures(filter, hints);
  655 + FeatureIterator<Feature> iterator = fCollection.features();
  656 + if (iterator.hasNext()) {
  657 + result = iterator.next();
  658 + }
  659 + iterator.close();
  660 + if (result != null){
  661 + return result;
  662 + }
  663 +
  664 + synchronized(stopFlag) {
  665 + if (stopFlag.get()) {
  666 + return null;
  667 + }
  668 + }
  669 + }
  670 + return null;
  671 + }
643 672 }
22 ...sion/app-schema/app-schema/src/main/java/org/geotools/data/complex/DataAccessMappingFeatureIterator.java
@@ -521,6 +521,14 @@ protected Attribute setAttributeValue(Attribute target, String id, final Object
521 521 values = getValues(attMapping.isMultiValued(), sourceExpression, source);
522 522 }
523 523 boolean isHRefLink = isByReference(clientPropsMappings, isNestedFeature);
  524 + int newResolveDepth = resolveDepth;
  525 + //if resolving, no xlink:href for chained feature
  526 + boolean ignoreXlinkHref = false;
  527 + if (isHRefLink && newResolveDepth > 0) {
  528 + isHRefLink = false;
  529 + newResolveDepth--;
  530 + ignoreXlinkHref = true;
  531 + }
524 532 if (isNestedFeature) {
525 533 if (values == null) {
526 534 // polymorphism use case, if the value doesn't match anything, don't encode
@@ -549,7 +557,7 @@ protected Attribute setAttributeValue(Attribute target, String id, final Object
549 557 .getInputFeatures(this, val, getIdValues(source), source, reprojection, selectedProperties, includeMandatory));
550 558 } else {
551 559 nestedFeatures.addAll(((NestedAttributeMapping) attMapping).getFeatures(
552   - this, val, getIdValues(source), reprojection, source, selectedProperties, includeMandatory));
  560 + this, val, getIdValues(source), reprojection, source, selectedProperties, includeMandatory, newResolveDepth, resolveTimeOut));
553 561 }
554 562 }
555 563 values = nestedFeatures;
@@ -561,7 +569,7 @@ protected Attribute setAttributeValue(Attribute target, String id, final Object
561 569 values = ((NestedAttributeMapping) attMapping).getInputFeatures(this, values, getIdValues(source), source, reprojection, selectedProperties, includeMandatory);
562 570 } else {
563 571 values = ((NestedAttributeMapping) attMapping).getFeatures(this, values, getIdValues(source), reprojection,
564   - source, selectedProperties, includeMandatory);
  572 + source, selectedProperties, includeMandatory, newResolveDepth, resolveTimeOut);
565 573 }
566 574 if (isHRefLink) {
567 575 // only need to set the href link value, not the nested feature properties
@@ -597,9 +605,7 @@ protected Attribute setAttributeValue(Attribute target, String id, final Object
597 605 } else {
598 606 valueList.add(singleVal);
599 607 }
600   - instance = xpathAttributeBuilder.set(target, xpath, valueList, id,
601   - targetNodeType, false, sourceExpression);
602   - setClientProperties(instance, source, clientPropsMappings);
  608 + instance = setAttributeContent(target, xpath, valueList, id, targetNodeType, false, sourceExpression, source, clientPropsMappings, ignoreXlinkHref);
603 609 }
604 610 } else {
605 611 if (values instanceof Attribute) {
@@ -612,9 +618,7 @@ protected Attribute setAttributeValue(Attribute target, String id, final Object
612 618 }
613 619 values = ((Attribute) values).getValue();
614 620 }
615   - instance = xpathAttributeBuilder.set(target, xpath, values, id,
616   - targetNodeType, false, sourceExpression);
617   - setClientProperties(instance, source, clientPropsMappings);
  621 + instance = setAttributeContent(target, xpath, values, id, targetNodeType, false, sourceExpression, source, clientPropsMappings, ignoreXlinkHref);
618 622
619 623 }
620 624 if (instance != null && attMapping.encodeIfEmpty()) {
@@ -981,7 +985,7 @@ protected Feature computeNext() throws IOException {
981 985 false, sourceExpr);
982 986 } else {
983 987 // simple attributes
984   - instance.setValue(valueString);
  988 + instance.setValue(valueString);
985 989 }
986 990 }
987 991 } else if (attMapping.isMultiValued()) {
22 modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/DataAccessRegistry.java
@@ -22,16 +22,19 @@
22 22 import java.util.Arrays;
23 23 import java.util.Iterator;
24 24 import java.util.List;
  25 +import java.util.concurrent.atomic.AtomicBoolean;
25 26
26 27 import org.geotools.data.DataAccess;
27 28 import org.geotools.data.DataSourceException;
28 29 import org.geotools.data.DataStore;
29 30 import org.geotools.data.FeatureSource;
30 31 import org.geotools.data.Repository;
  32 +import org.geotools.factory.Hints;
31 33 import org.geotools.util.InterpolationProperties;
32 34 import org.opengis.feature.Feature;
33 35 import org.opengis.feature.type.FeatureType;
34 36 import org.opengis.feature.type.Name;
  37 +import org.opengis.filter.identity.FeatureId;
35 38
36 39 /**
37 40 * A registry that stores data access instances per application. This allows feature sources from
@@ -360,6 +363,25 @@ protected void throwDataSourceException(Name featureTypeName) throws IOException
360 363 + " Has the data access been registered in DataAccessRegistry?" + " Available: "
361 364 + typeNames.toString());
362 365 }
  366 +
  367 +
  368 + public Feature findFeature(FeatureId id, Hints hints, AtomicBoolean stopFlag) throws IOException {
  369 + for (DataAccess<FeatureType, Feature> dataAccess : registry) {
  370 + if (dataAccess instanceof AppSchemaDataAccess) {
  371 + Feature feature = ((AppSchemaDataAccess) dataAccess).findFeature(id, hints, stopFlag);
  372 + if (feature != null) {
  373 + return feature;
  374 + }
  375 + synchronized(stopFlag) {
  376 + if (stopFlag.get()) {
  377 + return null;
  378 + }
  379 + }
  380 + }
  381 + }
  382 + return null;
  383 +
  384 + }
363 385
364 386
365 387 }
18 modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/MappingFeatureSource.java
@@ -31,6 +31,7 @@
31 31 import org.geotools.data.QueryCapabilities;
32 32 import org.geotools.data.ResourceInfo;
33 33 import org.geotools.data.joining.JoiningQuery;
  34 +import org.geotools.factory.Hints;
34 35 import org.geotools.feature.FeatureCollection;
35 36 import org.geotools.feature.FeatureIterator;
36 37 import org.geotools.geometry.jts.ReferencedEnvelope;
@@ -84,8 +85,16 @@ public ReferencedEnvelope getBounds() throws IOException {
84 85 private Query namedQuery(Filter filter, int countLimit) {
85 86 return namedQuery(filter, countLimit, false);
86 87 }
87   -
  88 +
  89 + private Query namedQuery(Filter filter, int countLimit, Hints hints) {
  90 + return namedQuery(filter, countLimit, false, hints);
  91 + }
  92 +
88 93 private Query namedQuery(Filter filter, int countLimit, boolean isJoining) {
  94 + return namedQuery(filter, countLimit, isJoining, null);
  95 + }
  96 +
  97 + private Query namedQuery(Filter filter, int countLimit, boolean isJoining, Hints hints) {
89 98 Query query = isJoining ? new JoiningQuery() : new Query();
90 99 if (getName().getNamespaceURI() != null) {
91 100 try {
@@ -97,8 +106,9 @@ private Query namedQuery(Filter filter, int countLimit, boolean isJoining) {
97 106 query.setTypeName(getName().getLocalPart());
98 107 query.setFilter(filter);
99 108 query.setMaxFeatures(countLimit);
  109 + query.setHints(hints);
100 110 return query;
101   - }
  111 + }
102 112
103 113 private Query namedQuery(Query query) {
104 114 Query namedQuery = namedQuery(query.getFilter(), query.getMaxFeatures(), query instanceof JoiningQuery);
@@ -168,6 +178,10 @@ public FeatureTypeMapping getMapping() {
168 178 public FeatureCollection<FeatureType, Feature> getFeatures(Filter filter) throws IOException {
169 179 return new MappingFeatureCollection(store, mapping, namedQuery(filter, Integer.MAX_VALUE));
170 180 }
  181 +
  182 + public FeatureCollection<FeatureType, Feature> getFeatures(Filter filter, Hints hints) throws IOException {
  183 + return new MappingFeatureCollection(store, mapping, namedQuery(filter, Integer.MAX_VALUE, hints));
  184 + }
171 185
172 186 public FeatureCollection<FeatureType, Feature> getFeatures() throws IOException {
173 187 return new MappingFeatureCollection(store, mapping, namedQuery(Filter.INCLUDE,
63 modules/extension/app-schema/app-schema/src/main/java/org/geotools/data/complex/NestedAttributeMapping.java
@@ -25,6 +25,8 @@
25 25 import java.util.Map;
26 26 import java.util.logging.Logger;
27 27
  28 +import net.opengis.wfs20.ResolveValueType;
  29 +
28 30 import org.geotools.data.FeatureSource;
29 31 import org.geotools.data.Query;
30 32 import org.geotools.data.complex.filter.XPathUtil.StepList;
@@ -106,7 +108,7 @@
106 108 * true if the type is depending on a function value, i.e. could be a Function
107 109 */
108 110 private boolean isConditional;
109   -
  111 +
110 112 /**
111 113 * Sole constructor
112 114 *
@@ -187,21 +189,36 @@ public boolean isNestedAttribute() {
187 189 return Collections.EMPTY_LIST;
188 190 }
189 191
190   - // find source expression on nested features side
191   - List<AttributeMapping> mappings = featureTypeMapping
192   - .getAttributeMappingsIgnoreIndex(this.nestedTargetXPath);
193   - if (mappings.size() < 1) {
194   - throw new IllegalArgumentException("Mapping is missing for: '"
195   - + this.nestedTargetXPath + "'!");
196   - }
197   - AttributeMapping mapping = mappings.get(0);
198   - nestedSourceExpression = mapping.getSourceExpression();
  192 + AttributeMapping mapping = getMapping(featureTypeMapping);
  193 + nestedSourceExpression = mapping.getSourceExpression();
199 194 isMultiple = mapping.isMultiValued();
200 195 }
201 196
202 197 return getFilteredFeatures(foreignKeyValue, isMultiple);
203 198 }
204 199
  200 + public AttributeMapping getMapping(FeatureTypeMapping featureTypeMapping) {
  201 + // find source expression on nested features side
  202 + AttributeMapping mapping;
  203 + if (!ComplexFeatureConstants.FEATURE_CHAINING_LINK_STRING.equals(nestedTargetXPath.get(nestedTargetXPath.size()-1).getName().getLocalPart())) {
  204 + List<AttributeMapping> mappings = featureTypeMapping
  205 + .getAttributeMappingsIgnoreIndex(this.nestedTargetXPath);
  206 + if (mappings.size() < 1) {
  207 + throw new IllegalArgumentException("Mapping is missing for: '"
  208 + + this.nestedTargetXPath + "'!");
  209 + }
  210 + mapping = mappings.get(0);
  211 + }
  212 + else {
  213 + mapping = featureTypeMapping.getAttributeMapping(this.nestedTargetXPath);
  214 + if (mapping == null) {
  215 + throw new IllegalArgumentException("Mapping is missing for: '"
  216 + + this.nestedTargetXPath + "'!");
  217 + }
  218 + }
  219 + return mapping;
  220 + }
  221 +
205 222 /**
206 223 * Run the query to get built features from a table based on a foreign key.
207 224 *
@@ -221,6 +238,7 @@ public boolean isNestedAttribute() {
221 238
222 239 Filter filter = filterFac.equals(this.nestedSourceExpression, filterFac
223 240 .literal(foreignKeyValue));
  241 +
224 242 // get all the nested features based on the link values
225 243 FeatureCollection<FeatureType, Feature> fCollection = source.getFeatures(filter);
226 244 FeatureIterator<Feature> it = fCollection.features();
@@ -308,14 +326,8 @@ public boolean isNestedAttribute() {
308 326
309 327 nestedIdExpression = fMapping.getFeatureIdExpression();
310 328
311   - // find source expression on nested features side
312   - List<AttributeMapping> mappings = fMapping
313   - .getAttributeMappingsIgnoreIndex(this.nestedTargetXPath);
314   - if (mappings.size() < 1) {
315   - throw new IllegalArgumentException("Mapping is missing for: '"
316   - + this.nestedTargetXPath + "'!");
317   - }
318   - AttributeMapping mapping = mappings.get(0);
  329 + // find source expression on nested features side
  330 + AttributeMapping mapping = getMapping(fMapping);
319 331 nestedSourceExpression = mapping.getSourceExpression();
320 332 isMultiple = mapping.isMultiValued();
321 333 }
@@ -338,8 +350,8 @@ public boolean isNestedAttribute() {
338 350 * @throws IOException
339 351 */
340 352 public List<Feature> getFeatures(Object foreignKeyValue,
341   - CoordinateReferenceSystem reprojection, Feature feature) throws IOException{
342   - return getFeatures(null, foreignKeyValue, null, reprojection, feature, null, true);
  353 + CoordinateReferenceSystem reprojection, Feature feature, int resolveDepth, Integer resolveTimeOut) throws IOException{
  354 + return getFeatures(null, foreignKeyValue, null, reprojection, feature, null, true, resolveDepth, resolveTimeOut);
343 355 }
344 356
345 357
@@ -354,7 +366,7 @@ public boolean isNestedAttribute() {
354 366 * @throws IOException
355 367 */
356 368 public List<Feature> getFeatures(Object source, Object foreignKeyValue, List<Object> idValues,
357   - CoordinateReferenceSystem reprojection, Object feature, List<PropertyName> selectedProperties, boolean includeMandatory) throws IOException {
  369 + CoordinateReferenceSystem reprojection, Object feature, List<PropertyName> selectedProperties, boolean includeMandatory, int resolveDepth, Integer resolveTimeOut) throws IOException {
358 370
359 371 if (foreignKeyValue == null) {
360 372 return Collections.<Feature>emptyList();
@@ -387,6 +399,15 @@ public boolean isNestedAttribute() {
387 399
388 400 final Hints hints = new Hints();
389 401 hints.put(Query.INCLUDE_MANDATORY_PROPS, includeMandatory);
  402 +
  403 + if (resolveDepth > 0 ) {
  404 + hints.put(Hints.RESOLVE, ResolveValueType.ALL);
  405 + hints.put(Hints.ASSOCIATION_TRAVERSAL_DEPTH, resolveDepth);
  406 + hints.put(Hints.RESOLVE_TIMEOUT, resolveTimeOut);
  407 + } else {
  408 + hints.put(Hints.RESOLVE, ResolveValueType.NONE);
  409 + }
  410 +
390 411 query.setHints(hints);
391 412
392 413 query.setProperties(selectedProperties);
20 ...tension/app-schema/app-schema/src/main/java/org/geotools/data/joining/JoiningNestedAttributeMapping.java
@@ -22,6 +22,8 @@
22 22 import java.util.List;
23 23 import java.util.Map;
24 24
  25 +import net.opengis.wfs20.ResolveValueType;
  26 +
25 27 import org.geotools.data.FeatureSource;
26 28 import org.geotools.data.Query;
27 29 import org.geotools.data.complex.AppSchemaDataAccessRegistry;
@@ -127,7 +129,7 @@ public JoiningNestedAttributeMapping(Expression idExpression, Expression parentE
127 129 */
128 130 public DataAccessMappingFeatureIterator initSourceFeatures(Instance instance,
129 131 Name featureTypeName, CoordinateReferenceSystem reprojection,
130   - List<PropertyName> selectedProperties, boolean includeMandatory) throws IOException {
  132 + List<PropertyName> selectedProperties, boolean includeMandatory, int resolveDepth, Integer resolveTimeOut) throws IOException {
131 133 JoiningQuery query = new JoiningQuery();
132 134 query.setCoordinateSystemReproject(reprojection);
133 135
@@ -169,6 +171,16 @@ public DataAccessMappingFeatureIterator initSourceFeatures(Instance instance,
169 171
170 172 final Hints hints = new Hints();
171 173 hints.put(Query.INCLUDE_MANDATORY_PROPS, includeMandatory);
  174 +
  175 + if (resolveDepth > 0 ) {
  176 + hints.put(Hints.RESOLVE, ResolveValueType.ALL);
  177 + hints.put(Hints.ASSOCIATION_TRAVERSAL_DEPTH, resolveDepth);
  178 + hints.put(Hints.RESOLVE_TIMEOUT, resolveTimeOut);
  179 + } else {
  180 + hints.put(Hints.RESOLVE, ResolveValueType.NONE);
  181 + }
  182 +
  183 +
172 184 query.setHints(hints);
173 185
174 186 query.setProperties(selectedProperties);
@@ -299,7 +311,7 @@ public void close(Object caller) {
299 311 .get((Name) featureTypeName);
300 312 if (featureIterator == null) {
301 313 featureIterator = initSourceFeatures(instance, (Name) featureTypeName, reprojection,
302   - selectedProperties, includeMandatory);
  314 + selectedProperties, includeMandatory, 0, null);
303 315 }
304 316 Expression nestedSourceExpression = instance.nestedSourceExpressions
305 317 .get((Name) featureTypeName);
@@ -347,7 +359,7 @@ public void close(Object caller) {
347 359 @Override
348 360 public List<Feature> getFeatures(Object caller, Object foreignKeyValue, List<Object> idValues,
349 361 CoordinateReferenceSystem reprojection, Object feature,
350   - List<PropertyName> selectedProperties, boolean includeMandatory) throws IOException {
  362 + List<PropertyName> selectedProperties, boolean includeMandatory, int resolveDepth, Integer resolveTimeOut) throws IOException {
351 363
352 364 if (isSameSource()) {
353 365 // if linkField is null, this method shouldn't be called because the mapping
@@ -370,7 +382,7 @@ public void close(Object caller) {
370 382 .get((Name) featureTypeName);
371 383 if (featureIterator == null) {
372 384 featureIterator = initSourceFeatures(instance, (Name) featureTypeName, reprojection,
373   - selectedProperties, includeMandatory);
  385 + selectedProperties, includeMandatory, resolveDepth, resolveTimeOut);
374 386 }
375 387 Expression nestedSourceExpression = instance.nestedSourceExpressions
376 388 .get((Name) featureTypeName);
2  modules/extension/app-schema/app-schema/src/main/java/org/geotools/filter/NestedAttributeExpression.java
@@ -275,7 +275,7 @@ private Step getLastStep() {
275 275 return nestedMapping.getInputFeatures(val, fMapping);
276 276 } else {
277 277 // app-schema with a complex feature source
278   - return nestedMapping.getFeatures(val, null, root);
  278 + return nestedMapping.getFeatures(val, null, root, 0, null);
279 279 }
280 280 }
281 281
14 modules/library/metadata/src/main/java/org/geotools/factory/Hints.java
@@ -813,6 +813,20 @@
813 813 //////// Data stores ////////
814 814 //////// ////////
815 815 ////////////////////////////////////////////////////////////////////////
  816 +
  817 + /**
  818 + * Resolve setting for resolving resources. ("local", "none", "remote" or "all")
  819 + * <p>
  820 + * This maps directly to the {@code resolve} parameter in a WFS query.
  821 + */
  822 + public static final Key RESOLVE = new Key("net.opengis.wfs20.ResolveValueType");
  823 +
  824 + /**
  825 + * The maximum time-out for resolving resources.
  826 + * <p>
  827 + * This maps directly to the {@code resolveTimeOut} parameter in a WFS query.
  828 + */
  829 + public static final Hints.Key RESOLVE_TIMEOUT = new Key(Integer.class);
816 830
817 831 /**
818 832 * The maximum number of associations traversed in a datastore query.

0 comments on commit e76dd59

Please sign in to comment.
Something went wrong with that request. Please try again.