Skip to content

Commit

Permalink
[Backport 2.25.x] [GEOS-11266] csw-iso: support multiple paths mapped…
Browse files Browse the repository at this point in the history
… to queryable (#7467)

* [GEOS-11266] csw-iso: support multiple paths mapped to queryable

* [GEOS-11266] csw-iso: support multiple paths mapped to queryable (docs)

* [GEOS-11266] csw-iso: support multiple paths mapped to queryable: fix checkstyle

* [GEOS-11266] csw-iso: support multiple paths mapped to queryable: review fixed

---------

Co-authored-by: NielsCharlier <niels@scitus.be>
  • Loading branch information
aaime and NielsCharlier authored Mar 11, 2024
1 parent ed38692 commit 2615e7e
Show file tree
Hide file tree
Showing 19 changed files with 988 additions and 90 deletions.
1 change: 1 addition & 0 deletions doc/en/user/source/extensions/csw-iso/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ This section discusses the Catalog Services for Web (CSW) ISO Metadata Profile e

installing
mapping
queryables
tutorial
14 changes: 14 additions & 0 deletions doc/en/user/source/extensions/csw-iso/queryables.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
CSW ISO Metadata Profile Queryables
===================================

Mapping
~~~~~~~
The ISO Metadata standard (see `OGC Implementation Specification 07-045 <http://www.opengeospatial.org/standards/specifications/catalog>`_) specifies a mapping of XPaths to CSW queryables. These are simple property names such as 'Title' or 'Abstract' that can be used in filters and will automatically be translated to a corresponding XPath.

In some case it might be required to have an alternative mapping of queryables (to a different, non-default XPaths). For instance when records represent services rather than data, the corresponding XPaths are different.

The ISO Metadata queryables mapping can be found in the file ``csw/MD_Metadata.queryables.properties`` inside the data directory. It follows the format of a properties file, where each value can be a comma-separated list of XPaths. When a queryable is linked to multiple XPaths, filters will include records that have a match for any of them. For the `GetDomain` request, the domains of all the XPaths will be merged. Although the intended use case is that each record only has a value for either one of the properties, the user is responsible for configuring valid mappings.

Bounding Box
~~~~~~~~~~~~
The 'BoundingBox' queryable has an additional functionality. Changing it will not only alter the queryable as such, but also how the layers are mapped to ISO metadata records. The first XPath will be the place where the bounding box of the layers are encoded in each of the records.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import net.opengis.cat.csw20.ElementSetType;
import org.geoserver.csw.util.NamespaceQualifier;
Expand Down Expand Up @@ -306,9 +307,10 @@ public String getQueryablesDescription() {
}

@Override
public PropertyName translateProperty(Name name) {
return new CSWPropertyPathExtender()
.extendProperty(buildPropertyName(NAMESPACES, name), FF, NAMESPACES);
public List<PropertyName> translateProperty(Name name) {
return Collections.singletonList(
new CSWPropertyPathExtender()
.extendProperty(buildPropertyName(NAMESPACES, name), FF, NAMESPACES));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public interface RecordDescriptor {
*
* @return the property name
*/
PropertyName translateProperty(Name name);
List<PropertyName> translateProperty(Name name);

/**
* Checks that the spatial filters are actually referring to a spatial property. The {@link
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -60,30 +59,32 @@ public CloseableIterator<String> getDomain(Name typeName, final Name attributeNa
if (rd == null) {
throw new IOException(typeName + " is not a supported type");
}
final List<PropertyName> properties = rd.translateProperty(attributeName);

// do we have such attribute?
final PropertyName property = rd.translateProperty(attributeName);
AttributeDescriptor ad = (AttributeDescriptor) property.evaluate(rd.getFeatureType());
if (ad == null) {
// do we have these properties?
if (!hasProperties(rd.getFeatureType(), properties)) {
return new CloseableIteratorAdapter<>(new ArrayList<String>().iterator());
}

// build the query against csw:record
Query q = new Query(typeName.getLocalPart());

q.setProperties(Arrays.asList(translateProperty(rd, attributeName)));
q.setProperties(translateToPropertyNames(rd, attributeName));

// collect the values without duplicates
final Set<String> values = new HashSet<>();
getRecords(q, Transaction.AUTO_COMMIT, rd)
.accepts(
feature -> {
Property prop = (Property) property.evaluate(feature);
if (prop != null) {
values.add(
new String(
((String) prop.getValue()).getBytes(ISO_8859_1),
UTF_8));
for (PropertyName property : properties) {
Property prop = (Property) property.evaluate(feature);
if (prop != null) {
values.add(
new String(
((String) prop.getValue()).getBytes(ISO_8859_1),
UTF_8));
break;
}
}
},
null);
Expand All @@ -94,6 +95,15 @@ public CloseableIterator<String> getDomain(Name typeName, final Name attributeNa
return new CloseableIteratorAdapter<>(result.iterator());
}

protected boolean hasProperties(FeatureType featureType, List<PropertyName> properties) {
boolean hasProperty = false;
for (PropertyName property : properties) {
AttributeDescriptor ad = (AttributeDescriptor) property.evaluate(featureType);
hasProperty |= ad != null;
}
return hasProperty;
}

@Override
public FeatureCollection<FeatureType, Feature> getRecords(
Query q, Transaction t, RecordDescriptor rdOutput) throws IOException {
Expand Down Expand Up @@ -160,7 +170,8 @@ public CatalogStoreCapabilities getCapabilities() {
}

@Override
public PropertyName translateProperty(RecordDescriptor rd, Name name) {
return AbstractRecordDescriptor.buildPropertyName(rd.getNamespaceSupport(), name);
public List<PropertyName> translateToPropertyNames(RecordDescriptor rd, Name name) {
return Collections.singletonList(
AbstractRecordDescriptor.buildPropertyName(rd.getNamespaceSupport(), name));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ void updateRecord(
/** Returns the store capabilities */
CatalogStoreCapabilities getCapabilities();

/** Maps a qualified name to it's equivalent property name for the backend store. */
PropertyName translateProperty(RecordDescriptor rd, Name name);
/**
* Maps a qualified name to it's equivalent property names for the backend store. @Deprecated
* Use translateToPropertyNames(RecordDescriptor, Name)
*/
@Deprecated
default PropertyName translateProperty(RecordDescriptor rd, Name name) {
List<PropertyName> propNames = translateToPropertyNames(rd, name);
return propNames != null && !propNames.isEmpty() ? propNames.get(0) : null;
}

/** Maps a qualified name to its equivalent property names for the backend store. */
List<PropertyName> translateToPropertyNames(RecordDescriptor rd, Name name);
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public String getQueryablesDescription() {
}

@Override
public PropertyName translateProperty(Name name) {
public List<PropertyName> translateProperty(Name name) {
return delegate.translateProperty(name);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ private List<PropertyName> getPropertyNames(RecordDescriptor rd, QueryType query
// of the elements in the feature's schema
List<PropertyName> result = new ArrayList<>();
for (QName qn : query.getElementName()) {
result.add(store.translateProperty(rd, Types.toTypeName(qn)));
result.addAll(store.translateToPropertyNames(rd, Types.toTypeName(qn)));
}
return result;
} else {
Expand All @@ -263,7 +263,7 @@ private List<PropertyName> getPropertyNames(RecordDescriptor rd, QueryType query
if (properties != null) {
List<PropertyName> result = new ArrayList<>();
for (Name pn : properties) {
result.add(store.translateProperty(rd, pn));
result.addAll(store.translateToPropertyNames(rd, pn));
}
return result;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ public FeatureCollection<FeatureType, Feature> getRecordsInternal(
}

@Override
public PropertyName translateProperty(RecordDescriptor rd, Name name) {
public List<PropertyName> translateToPropertyNames(RecordDescriptor rd, Name name) {
return rd.translateProperty(name);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public String getQueryablesDescription() {
}

@Override
public PropertyName translateProperty(Name name) {
public List<PropertyName> translateProperty(Name name) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public String getBoundingBoxPropertyName() {
XPathUtil.StepList steps =
XPathUtil.steps(
getFeatureDescriptor(),
queryableMapping.get(QUERYABLE_BBOX).getPropertyName(),
queryableMapping.get(QUERYABLE_BBOX).get(0).getPropertyName(),
getNamespaceSupport());

return CatalogStoreMapping.toDotPath(steps);
Expand Down
Loading

0 comments on commit 2615e7e

Please sign in to comment.