From c980f1aab3cd15c3c12e69192c3a860193362831 Mon Sep 17 00:00:00 2001 From: Andrea Aime Date: Wed, 27 Mar 2024 11:12:13 +0100 Subject: [PATCH] =?UTF-8?q?[Backport=202.25.x]=20[GEOS-11267]=20csw-iso:?= =?UTF-8?q?=20multiple=20mappings=20should=20have=20multiple=20queryabl?= =?UTF-8?q?=E2=80=A6=20(#7518)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [GEOS-11267] csw-iso: multiple mappings should have multiple queryables mappings * [GEOS-11267] csw-iso: multiple mappings should have multiple queryables mappings (fixes) --------- Co-authored-by: NielsCharlier --- .../source/extensions/csw-iso/queryables.rst | 11 +- .../csw/records/CSWRecordDescriptor.java | 10 +- .../csw/records/GenericRecordBuilder.java | 16 +- .../csw/records/QueryablesMapping.java | 41 +++++ .../csw/records/RecordDescriptor.java | 36 ++-- .../csw/store/AbstractCatalogStore.java | 15 ++ .../org/geoserver/csw/store/CatalogStore.java | 9 + .../java/org/geoserver/csw/GetRecordById.java | 13 +- .../java/org/geoserver/csw/GetRecords.java | 36 +--- .../internal/CatalogStoreFeatureIterator.java | 5 +- .../store/internal/CatalogStoreMapping.java | 6 +- .../store/internal/InternalCatalogStore.java | 96 ++++++---- .../csw/records/iso/MetaDataDescriptor.java | 18 +- .../iso/QueryableMappingRecordDescriptor.java | 168 ++++++++++++------ .../internal/iso/MultipleMappingTest.java | 90 +++++++++- .../MD_Metadata-second.properties.ignore | 1 + ...tadata-second.queryables.properties.ignore | 36 ++++ .../csw/store/simple/SimpleCatalogStore.java | 10 +- 18 files changed, 431 insertions(+), 186 deletions(-) create mode 100644 src/extension/csw/api/src/main/java/org/geoserver/csw/records/QueryablesMapping.java create mode 100644 src/extension/csw/csw-iso/src/test/resources/org/geoserver/csw/store/internal/MD_Metadata-second.queryables.properties.ignore diff --git a/doc/en/user/source/extensions/csw-iso/queryables.rst b/doc/en/user/source/extensions/csw-iso/queryables.rst index d675f46173f..80ffbaa451e 100644 --- a/doc/en/user/source/extensions/csw-iso/queryables.rst +++ b/doc/en/user/source/extensions/csw-iso/queryables.rst @@ -11,4 +11,13 @@ The ISO Metadata queryables mapping can be found in the file ``csw/MD_Metadata.q 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. \ No newline at end of file +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. + +Multiple Mappings +~~~~~~~~~~~~~~~~~ +The CSW module supports mapping each layer to multiple records (see :ref:`csw_mapping_file`). In this case one might want to have separate queryables mappings associated with these distinct mappings as well. (One could avoid this and map queryables to multiple XPaths instead as explained above, but that would not make it possible to map the bounding box to separate XPaths). The syntax of the queryables mappings file names is analogue to the regular mappings. For instance, or instance, one could have the following files in the ``csw`` directory: + +* ``csw/Record.properties`` +* ``csw/Record-otherRecord.properties`` +* ``csw/Record.queryables.properties`` +* ``csw/Record-otherRecord.queryables.properties`` \ No newline at end of file diff --git a/src/extension/csw/api/src/main/java/org/geoserver/csw/records/CSWRecordDescriptor.java b/src/extension/csw/api/src/main/java/org/geoserver/csw/records/CSWRecordDescriptor.java index 07237495199..d394451e883 100644 --- a/src/extension/csw/api/src/main/java/org/geoserver/csw/records/CSWRecordDescriptor.java +++ b/src/extension/csw/api/src/main/java/org/geoserver/csw/records/CSWRecordDescriptor.java @@ -262,6 +262,7 @@ public NamespaceSupport getNamespaceSupport() { @Override public Query adaptQuery(Query query) { + query = new Query(query); Filter filter = query.getFilter(); if (filter != null && !Filter.INCLUDE.equals(filter)) { Filter qualified = (Filter) filter.accept(NSS_QUALIFIER, null); @@ -269,11 +270,12 @@ public Query adaptQuery(Query query) { query.setFilter(extended); } - SortBy[] sortBy = query.getSortBy(); - if (sortBy != null && sortBy.length > 0) { - CSWPropertyPathExtender extender = new CSWPropertyPathExtender(); + CSWPropertyPathExtender extender = new CSWPropertyPathExtender(); + + if (query.getSortBy() != null && query.getSortBy().length > 0) { + SortBy[] sortBy = new SortBy[query.getSortBy().length]; for (int i = 0; i < sortBy.length; i++) { - SortBy sb = sortBy[i]; + SortBy sb = query.getSortBy()[i]; if (!SortBy.NATURAL_ORDER.equals(sb) && !SortBy.REVERSE_ORDER.equals(sb)) { PropertyName name = sb.getPropertyName(); PropertyName extended = extender.extendProperty(name, FF, NAMESPACES); diff --git a/src/extension/csw/api/src/main/java/org/geoserver/csw/records/GenericRecordBuilder.java b/src/extension/csw/api/src/main/java/org/geoserver/csw/records/GenericRecordBuilder.java index 96f8d4cf155..5e7a87078c0 100644 --- a/src/extension/csw/api/src/main/java/org/geoserver/csw/records/GenericRecordBuilder.java +++ b/src/extension/csw/api/src/main/java/org/geoserver/csw/records/GenericRecordBuilder.java @@ -50,6 +50,7 @@ public class GenericRecordBuilder implements RecordBuilder { protected ComplexFeatureBuilder fb; protected List boxes = new ArrayList<>(); protected RecordDescriptor recordDescriptor; + protected QueryablesMapping queryables; protected Map substitutionMap = new HashMap<>(); /** @@ -127,7 +128,18 @@ public boolean cleanUp() { * @param recordDescriptor The Record Descriptor */ public GenericRecordBuilder(RecordDescriptor recordDescriptor) { + this(recordDescriptor, recordDescriptor); + } + + /** + * Start Generic Record Builder based on the Record Descriptor + * + * @param recordDescriptor The Record Descriptor + * @param queryables The queryables + */ + public GenericRecordBuilder(RecordDescriptor recordDescriptor, QueryablesMapping queryables) { this.recordDescriptor = recordDescriptor; + this.queryables = queryables; fb = new ComplexFeatureBuilder(recordDescriptor.getFeatureDescriptor()); for (PropertyDescriptor descriptor : recordDescriptor.getFeatureType().getDescriptors()) { @@ -372,11 +384,11 @@ public Feature build(String id) { geom = geom.getFactory().createMultiPolygon(new Polygon[] {(Polygon) geom}); } - if (recordDescriptor.getBoundingBoxPropertyName() != null) { + if (queryables.getBoundingBoxPropertyName() != null) { Map userData = Collections.singletonMap(ORIGINAL_BBOXES, new ArrayList<>(boxes)); addElement( - recordDescriptor.getBoundingBoxPropertyName(), + queryables.getBoundingBoxPropertyName(), Collections.singletonList(geom), userData, new int[0]); diff --git a/src/extension/csw/api/src/main/java/org/geoserver/csw/records/QueryablesMapping.java b/src/extension/csw/api/src/main/java/org/geoserver/csw/records/QueryablesMapping.java new file mode 100644 index 00000000000..3604d6689cc --- /dev/null +++ b/src/extension/csw/api/src/main/java/org/geoserver/csw/records/QueryablesMapping.java @@ -0,0 +1,41 @@ +/* (c) 2024 Open Source Geospatial Foundation - all rights reserved + * This code is licensed under the GPL 2.0 license, available at the root + * application directory. + */ +package org.geoserver.csw.records; + +import java.util.List; +import org.geotools.api.data.Query; +import org.geotools.api.feature.type.Name; +import org.geotools.api.filter.expression.PropertyName; + +/** + * A mapping of CSW queryables to properties of the metadata record schema. Provides functionality + * for translating properties and adapting queries. + */ +public interface QueryablesMapping { + + /** + * Allow the descriptor to adjust the query to the internal representation of records. For + * example, in the case of SimpleLiteral we have a complex type with simple content, something + * that we cannot readily represent in GeoTools + * + *

Must provide a copy, not change the original query. + */ + Query adaptQuery(Query query); + + /** + * Return the property name (with dots) for the bounding box property + * + * @return the bounding box property name + */ + String getBoundingBoxPropertyName(); + + /** + * Translate a property from a queryable name to a propertyname, possibly converting to an + * x-path + * + * @return the property name + */ + List translateProperty(Name name); +} diff --git a/src/extension/csw/api/src/main/java/org/geoserver/csw/records/RecordDescriptor.java b/src/extension/csw/api/src/main/java/org/geoserver/csw/records/RecordDescriptor.java index 0c3fe270b9c..959f33f18d9 100644 --- a/src/extension/csw/api/src/main/java/org/geoserver/csw/records/RecordDescriptor.java +++ b/src/extension/csw/api/src/main/java/org/geoserver/csw/records/RecordDescriptor.java @@ -8,12 +8,10 @@ import java.util.LinkedHashSet; import java.util.List; import net.opengis.cat.csw20.ElementSetType; -import org.geotools.api.data.Query; import org.geotools.api.feature.type.AttributeDescriptor; import org.geotools.api.feature.type.FeatureType; import org.geotools.api.feature.type.Name; import org.geotools.api.filter.Filter; -import org.geotools.api.filter.expression.PropertyName; import org.xml.sax.helpers.NamespaceSupport; /** @@ -22,7 +20,7 @@ * @author Andrea Aime - GeoSolutions * @author Niels Charlier */ -public interface RecordDescriptor { +public interface RecordDescriptor extends QueryablesMapping { /** The GeoTools feature type representing this kind of record */ FeatureType getFeatureType(); @@ -47,18 +45,21 @@ public interface RecordDescriptor { NamespaceSupport getNamespaceSupport(); /** - * Allow the descriptor to adjust the query to the internal representation of records. For - * example, in the case of SimpleLiteral we have a complex type with simple content, something - * that we cannot readily represent in GeoTools + * Checks that the spatial filters are actually referring to a spatial property. The {@link + * SpatialFilterChecker} utility class can be used against simple records (like CSW), but more + * complex record types will need a more sophisticated approach */ - Query adaptQuery(Query query); + void verifySpatialFilters(Filter filter); /** - * Return the property name (with dots) for the bounding box property + * Optional support for multiple queryables mappings * - * @return the bounding box property name + * @param mappingName name of the queryables mapping + * @return the queryables */ - String getBoundingBoxPropertyName(); + default QueryablesMapping getQueryablesMapping(String mappingName) { + return this; + } /** * Return the queryables for this type of record (for getcapabilities) @@ -73,19 +74,4 @@ public interface RecordDescriptor { * @return the description string */ String getQueryablesDescription(); - - /** - * Translate a property from a queryable name to a propertyname, possibly converting to an - * x-path - * - * @return the property name - */ - List translateProperty(Name name); - - /** - * Checks that the spatial filters are actually referring to a spatial property. The {@link - * SpatialFilterChecker} utility class can be used against simple records (like CSW), but more - * complex record types will need a more sophisticated approach - */ - void verifySpatialFilters(Filter filter); } diff --git a/src/extension/csw/api/src/main/java/org/geoserver/csw/store/AbstractCatalogStore.java b/src/extension/csw/api/src/main/java/org/geoserver/csw/store/AbstractCatalogStore.java index ab6751a4707..34ed1d4dfcf 100644 --- a/src/extension/csw/api/src/main/java/org/geoserver/csw/store/AbstractCatalogStore.java +++ b/src/extension/csw/api/src/main/java/org/geoserver/csw/store/AbstractCatalogStore.java @@ -19,6 +19,7 @@ import org.geoserver.catalog.util.CloseableIteratorAdapter; import org.geoserver.csw.records.AbstractRecordDescriptor; import org.geoserver.csw.records.CSWRecordDescriptor; +import org.geoserver.csw.records.QueryablesMapping; import org.geoserver.csw.records.RecordDescriptor; import org.geotools.api.data.Query; import org.geotools.api.data.Transaction; @@ -104,6 +105,20 @@ protected boolean hasProperties(FeatureType featureType, List prop return hasProperty; } + protected Query prepareQuery(Query q, RecordDescriptor rd, QueryablesMapping qm) { + if (Boolean.TRUE.equals(q.getHints().get(KEY_UNPREPARED))) { + q = qm.adaptQuery(q); + + // the specification demands that we throw an error if a spatial operator + // is used against a non spatial property + if (q.getFilter() != null) { + rd.verifySpatialFilters(q.getFilter()); + } + } + + return q; + } + @Override public FeatureCollection getRecords( Query q, Transaction t, RecordDescriptor rdOutput) throws IOException { diff --git a/src/extension/csw/api/src/main/java/org/geoserver/csw/store/CatalogStore.java b/src/extension/csw/api/src/main/java/org/geoserver/csw/store/CatalogStore.java index e149b00ec08..23e814e8e14 100644 --- a/src/extension/csw/api/src/main/java/org/geoserver/csw/store/CatalogStore.java +++ b/src/extension/csw/api/src/main/java/org/geoserver/csw/store/CatalogStore.java @@ -19,6 +19,7 @@ import org.geotools.api.filter.expression.PropertyName; import org.geotools.api.filter.identity.FeatureId; import org.geotools.feature.FeatureCollection; +import org.geotools.util.factory.Hints; /** * Interfaces to a storage for CSW record objects. By default it has to provide support for CSW @@ -29,6 +30,14 @@ */ public interface CatalogStore { + /** + * This query hint specifies that the query has not yet been prepared (adapted and verified). It + * is necessary when we use multiple queryables mappings so that the query must be prepared + * separately for each mapping; but for backwards compatibility of the API this flag was + * created. + */ + public static final Hints.Key KEY_UNPREPARED = new Hints.Key(Boolean.class); + /** Returns the supported record types */ RecordDescriptor[] getRecordDescriptors() throws IOException; diff --git a/src/extension/csw/core/src/main/java/org/geoserver/csw/GetRecordById.java b/src/extension/csw/core/src/main/java/org/geoserver/csw/GetRecordById.java index 1591303fdd4..a94de4fcb2a 100644 --- a/src/extension/csw/core/src/main/java/org/geoserver/csw/GetRecordById.java +++ b/src/extension/csw/core/src/main/java/org/geoserver/csw/GetRecordById.java @@ -141,19 +141,10 @@ private List toGtQueries( Query q = new Query(typeName.getLocalPart()); q.setFilter(filter); - // perform some necessary query adjustments - Query adapted = rd.adaptQuery(q); - - // the specification demands that we throw an error if a spatial operator - // is used against a non spatial property - if (q.getFilter() != null) { - rd.verifySpatialFilters(q.getFilter()); - } - // smuggle base url - adapted.getHints().put(GetRecords.KEY_BASEURL, request.getBaseUrl()); + q.getHints().put(GetRecords.KEY_BASEURL, request.getBaseUrl()); - result.add(new GetRecords.WrappedQuery(adapted, rd)); + result.add(new GetRecords.WrappedQuery(q, rd)); } return result; diff --git a/src/extension/csw/core/src/main/java/org/geoserver/csw/GetRecords.java b/src/extension/csw/core/src/main/java/org/geoserver/csw/GetRecords.java index c79e4159609..cda8f31da3a 100644 --- a/src/extension/csw/core/src/main/java/org/geoserver/csw/GetRecords.java +++ b/src/extension/csw/core/src/main/java/org/geoserver/csw/GetRecords.java @@ -18,7 +18,6 @@ import net.opengis.cat.csw20.GetRecordsType; import net.opengis.cat.csw20.QueryType; import net.opengis.cat.csw20.ResultType; -import org.geoserver.csw.records.CSWRecordDescriptor; import org.geoserver.csw.records.RecordDescriptor; import org.geoserver.csw.response.CSWRecordsResult; import org.geoserver.csw.store.CatalogStore; @@ -217,8 +216,6 @@ private List toGtQueries( "typeNames"); } - RecordDescriptor rd = getRecordDescriptor(typeName); - Query q = new Query(typeName.getLocalPart()); q.setFilter(filter); q.setProperties(getPropertyNames(outputRd, query)); @@ -228,19 +225,13 @@ private List toGtQueries( } catch (URISyntaxException e) { } - // perform some necessary query adjustments - Query adapted = rd.adaptQuery(q); - - // the specification demands that we throw an error if a spatial operator - // is used against a non spatial property - if (q.getFilter() != null) { - rd.verifySpatialFilters(q.getFilter()); - } + // prepare later for multiple queryables mappings support + q.getHints().put(CatalogStore.KEY_UNPREPARED, true); // smuggle base url - adapted.getHints().put(KEY_BASEURL, request.getBaseUrl()); + q.getHints().put(KEY_BASEURL, request.getBaseUrl()); - result.add(new WrappedQuery(adapted, outputRd)); + result.add(new WrappedQuery(q, outputRd)); } } @@ -295,25 +286,6 @@ private Set getSupportedTypes() throws IOException { return result; } - /** - * Search for the record descriptor maching the typename, throws a service exception in case - * none is found - */ - private RecordDescriptor getRecordDescriptor(Name typeName) { - if (typeName == null) { - return CSWRecordDescriptor.getInstance(); - } - - for (RecordDescriptor rd : recordDescriptors) { - if (typeName.equals(rd.getFeatureDescriptor().getName())) { - return rd; - } - } - - throw new ServiceException( - "Unknown type: " + typeName, ServiceException.INVALID_PARAMETER_VALUE, "typeNames"); - } - /** * Search for the record descriptor maching the request, throws a service exception in case none * is found diff --git a/src/extension/csw/core/src/main/java/org/geoserver/csw/store/internal/CatalogStoreFeatureIterator.java b/src/extension/csw/core/src/main/java/org/geoserver/csw/store/internal/CatalogStoreFeatureIterator.java index 0b50f12898e..e2778682ebe 100644 --- a/src/extension/csw/core/src/main/java/org/geoserver/csw/store/internal/CatalogStoreFeatureIterator.java +++ b/src/extension/csw/core/src/main/java/org/geoserver/csw/store/internal/CatalogStoreFeatureIterator.java @@ -89,7 +89,10 @@ public CatalogStoreFeatureIterator( nextInternal(); this.outputRecordDescriptor = outputRecordDescriptor; - builder = new GenericRecordBuilder(outputRecordDescriptor); + builder = + new GenericRecordBuilder( + outputRecordDescriptor, + outputRecordDescriptor.getQueryablesMapping(mapping.getMappingName())); } @Override diff --git a/src/extension/csw/core/src/main/java/org/geoserver/csw/store/internal/CatalogStoreMapping.java b/src/extension/csw/core/src/main/java/org/geoserver/csw/store/internal/CatalogStoreMapping.java index 1359439b0f7..e9bccca3b89 100644 --- a/src/extension/csw/core/src/main/java/org/geoserver/csw/store/internal/CatalogStoreMapping.java +++ b/src/extension/csw/core/src/main/java/org/geoserver/csw/store/internal/CatalogStoreMapping.java @@ -165,7 +165,11 @@ public CatalogStoreMapping subMapping(List properties, RecordDescr mapping.identifier = identifier; mapping.includeEnvelope = - includeEnvelope && paths.contains(rd.getBoundingBoxPropertyName()); + includeEnvelope + && paths.contains( + rd.getQueryablesMapping(mappingName).getBoundingBoxPropertyName()); + + mapping.mappingName = mappingName; return mapping; } diff --git a/src/extension/csw/core/src/main/java/org/geoserver/csw/store/internal/InternalCatalogStore.java b/src/extension/csw/core/src/main/java/org/geoserver/csw/store/internal/InternalCatalogStore.java index 712d4ee5db3..97f6ef9e3d7 100644 --- a/src/extension/csw/core/src/main/java/org/geoserver/csw/store/internal/InternalCatalogStore.java +++ b/src/extension/csw/core/src/main/java/org/geoserver/csw/store/internal/InternalCatalogStore.java @@ -9,8 +9,10 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -93,16 +95,48 @@ public List getMappings(String typeName) { return result; } + private Query unmap(Query q, CSWUnmappingFilterVisitor unmapper) throws IOException { + Filter unmapped = Filter.INCLUDE; + // unmap filter + if (q.getFilter() != null && q.getFilter() != Filter.INCLUDE) { + Filter filter = q.getFilter(); + unmapped = (Filter) filter.accept(unmapper, null); + } + + // unmap sortby + SortBy[] unmappedSortBy = null; + if (q.getSortBy() != null && q.getSortBy().length > 0) { + unmappedSortBy = new SortBy[q.getSortBy().length]; + for (int i = 0; i < q.getSortBy().length; i++) { + SortBy sortby = q.getSortBy()[i]; + Expression expr = (Expression) sortby.getPropertyName().accept(unmapper, null); + + if (!(expr instanceof PropertyName)) { + throw new IOException( + "Sorting on " + sortby.getPropertyName() + " is not supported."); + } + + unmappedSortBy[i] = new SortByImpl((PropertyName) expr, sortby.getSortOrder()); + } + } + + Query result = new Query(q); + result.setFilter(unmapped); + result.setSortBy(unmappedSortBy); + + return result; + } + @Override public FeatureCollection getRecordsInternal( - RecordDescriptor rd, RecordDescriptor rdOutput, Query q, Transaction t) + RecordDescriptor rd, RecordDescriptor rdOutput, Query query, Transaction t) throws IOException { List> results = new ArrayList<>(); Map interpolationProperties = new HashMap<>(); - String baseUrl = (String) q.getHints().get(GetRecords.KEY_BASEURL); + String baseUrl = (String) query.getHints().get(GetRecords.KEY_BASEURL); if (baseUrl != null) { interpolationProperties.put( "url.wfs", ResponseUtils.buildURL(baseUrl, "wfs", null, URLType.SERVICE)); @@ -117,42 +151,22 @@ public FeatureCollection getRecordsInternal( "url.base", ResponseUtils.buildURL(baseUrl, null, null, URLType.SERVICE)); } - Collection mappings = getMappings(q.getTypeName()); + Collection mappings = getMappings(query.getTypeName()); Collection outputMappings = getMappings(rdOutput.getFeatureDescriptor().getName().getLocalPart()); int startIndex = 0; - if (q.getStartIndex() != null) { - startIndex = q.getStartIndex(); + if (query.getStartIndex() != null) { + startIndex = query.getStartIndex(); } for (CatalogStoreMapping mapping : mappings) { - CSWUnmappingFilterVisitor unmapper = new CSWUnmappingFilterVisitor(mapping, rd); - - Filter unmapped = Filter.INCLUDE; - // unmap filter - if (q.getFilter() != null && q.getFilter() != Filter.INCLUDE) { - Filter filter = q.getFilter(); - unmapped = (Filter) filter.accept(unmapper, null); - } - - // unmap sortby - SortBy[] unmappedSortBy = null; - if (q.getSortBy() != null && q.getSortBy().length > 0) { - unmappedSortBy = new SortBy[q.getSortBy().length]; - for (int i = 0; i < q.getSortBy().length; i++) { - SortBy sortby = q.getSortBy()[i]; - Expression expr = (Expression) sortby.getPropertyName().accept(unmapper, null); - - if (!(expr instanceof PropertyName)) { - throw new IOException( - "Sorting on " + sortby.getPropertyName() + " is not supported."); - } - - unmappedSortBy[i] = new SortByImpl((PropertyName) expr, sortby.getSortOrder()); - } - } + Query unmapped = + unmap( + prepareQuery( + query, rd, rd.getQueryablesMapping(mapping.getMappingName())), + new CSWUnmappingFilterVisitor(mapping, rd)); for (CatalogStoreMapping outputMapping : outputMappings) { // we only output mappings with the same name, to avoid duplication of the results @@ -160,16 +174,17 @@ public FeatureCollection getRecordsInternal( if (outputMapping.getMappingName().equals(mapping.getMappingName())) { - if (q.getProperties() != null) { - outputMapping = outputMapping.subMapping(q.getProperties(), rdOutput); + if (unmapped.getProperties() != null) { + outputMapping = + outputMapping.subMapping(unmapped.getProperties(), rdOutput); } results.add( new CatalogStoreFeatureCollection( startIndex, - q.getMaxFeatures(), - unmappedSortBy, - unmapped, + unmapped.getMaxFeatures(), + unmapped.getSortBy(), + unmapped.getFilter(), geoServer.getCatalog(), outputMapping, rdOutput, @@ -187,7 +202,13 @@ public FeatureCollection getRecordsInternal( @Override public List translateToPropertyNames(RecordDescriptor rd, Name name) { - return rd.translateProperty(name); + Set propertyNames = new HashSet<>(); + for (CatalogStoreMapping mapping : + getMappings(rd.getFeatureDescriptor().getName().getLocalPart())) { + propertyNames.addAll( + rd.getQueryablesMapping(mapping.getMappingName()).translateProperty(name)); + } + return new ArrayList<>(propertyNames); } private static boolean isMappingFileForType(String fileName, String typeName) { @@ -195,6 +216,9 @@ private static boolean isMappingFileForType(String fileName, String typeName) { return false; } fileName = FilenameUtils.removeExtension(fileName); + if ("queryables".equals(FilenameUtils.getExtension(fileName))) { + return false; + } return typeName.equals(fileName) || fileName.startsWith(typeName + "-"); } diff --git a/src/extension/csw/csw-iso/src/main/java/org/geoserver/csw/records/iso/MetaDataDescriptor.java b/src/extension/csw/csw-iso/src/main/java/org/geoserver/csw/records/iso/MetaDataDescriptor.java index bf5ceec020a..0da9307c440 100644 --- a/src/extension/csw/csw-iso/src/main/java/org/geoserver/csw/records/iso/MetaDataDescriptor.java +++ b/src/extension/csw/csw-iso/src/main/java/org/geoserver/csw/records/iso/MetaDataDescriptor.java @@ -12,7 +12,6 @@ import org.geoserver.csw.records.CSWRecordDescriptor; import org.geoserver.csw.records.RecordFeatureTypeRegistryConfiguration; import org.geoserver.csw.records.SpatialFilterChecker; -import org.geoserver.csw.store.internal.CatalogStoreMapping; import org.geoserver.platform.GeoServerExtensions; import org.geotools.api.feature.type.AttributeDescriptor; import org.geotools.api.feature.type.AttributeType; @@ -23,7 +22,6 @@ import org.geotools.csw.CSW; import org.geotools.data.complex.feature.type.FeatureTypeRegistry; import org.geotools.data.complex.util.EmfComplexFeatureReader; -import org.geotools.data.complex.util.XPathUtil; import org.geotools.feature.NameImpl; import org.geotools.feature.TypeBuilder; import org.geotools.feature.type.FeatureTypeFactoryImpl; @@ -208,17 +206,6 @@ public AttributeDescriptor getFeatureDescriptor() { return METADATA_DESCRIPTOR; } - @Override - public String getBoundingBoxPropertyName() { - XPathUtil.StepList steps = - XPathUtil.steps( - getFeatureDescriptor(), - queryableMapping.get(QUERYABLE_BBOX).get(0).getPropertyName(), - getNamespaceSupport()); - - return CatalogStoreMapping.toDotPath(steps); - } - @Override public List getQueryables() { return QUERYABLES; @@ -229,6 +216,11 @@ public String getQueryablesDescription() { return "SupportedISOQueryables"; } + @Override + protected String getBoundingBoxQueryable() { + return QUERYABLE_BBOX; + } + @Override public void verifySpatialFilters(Filter filter) { filter.accept(new SpatialFilterChecker(getFeatureType()), null); diff --git a/src/extension/csw/csw-iso/src/main/java/org/geoserver/csw/records/iso/QueryableMappingRecordDescriptor.java b/src/extension/csw/csw-iso/src/main/java/org/geoserver/csw/records/iso/QueryableMappingRecordDescriptor.java index 7c910293237..3c27b8d70e5 100644 --- a/src/extension/csw/csw-iso/src/main/java/org/geoserver/csw/records/iso/QueryableMappingRecordDescriptor.java +++ b/src/extension/csw/csw-iso/src/main/java/org/geoserver/csw/records/iso/QueryableMappingRecordDescriptor.java @@ -4,6 +4,7 @@ */ package org.geoserver.csw.records.iso; +import com.google.common.base.Strings; import com.google.common.collect.Lists; import java.io.IOException; import java.io.InputStream; @@ -17,6 +18,8 @@ import java.util.stream.Collectors; import org.geoserver.config.GeoServer; import org.geoserver.csw.records.AbstractRecordDescriptor; +import org.geoserver.csw.records.QueryablesMapping; +import org.geoserver.csw.store.internal.CatalogStoreMapping; import org.geoserver.platform.GeoServerResourceLoader; import org.geoserver.platform.resource.Resource; import org.geoserver.platform.resource.Resources; @@ -26,6 +29,7 @@ import org.geotools.api.filter.Filter; import org.geotools.api.filter.expression.PropertyName; import org.geotools.api.filter.sort.SortBy; +import org.geotools.data.complex.util.XPathUtil; import org.geotools.filter.SortByImpl; import org.geotools.util.logging.Logging; import org.springframework.beans.FatalBeanException; @@ -37,23 +41,121 @@ */ public abstract class QueryableMappingRecordDescriptor extends AbstractRecordDescriptor { + private class MappedQueryables implements QueryablesMapping { + + protected Map> queryableMapping = new HashMap<>(); + + public MappedQueryables(Properties props) { + queryableMapping.putAll( + props.entrySet().stream() + .collect( + Collectors.toMap( + e -> (String) e.getKey(), + e -> toPropertyNames((String) e.getValue())))); + } + + @Override + public List translateProperty(Name name) { + return queryableMapping.get(name.getLocalPart()); + } + + @SuppressWarnings("unchecked") + @Override + public Query adaptQuery(Query query) { + QueryableMappingFilterVisitor visitor = + new QueryableMappingFilterVisitor(getFeatureDescriptor(), queryableMapping); + query = new Query(query); + Filter filter = query.getFilter(); + if (filter != null && !Filter.INCLUDE.equals(filter)) { + query.setFilter((Filter) filter.accept(visitor, null)); + } + + if (query.getSortBy() != null && query.getSortBy().length > 0) { + List sortBy = Lists.newArrayList(); + for (int i = 0; i < query.getSortBy().length; i++) { + SortBy sb = query.getSortBy()[i]; + if (!SortBy.NATURAL_ORDER.equals(sb) && !SortBy.REVERSE_ORDER.equals(sb)) { + List properties = + (List) sb.getPropertyName().accept(visitor, null); + for (PropertyName property : properties) { + sortBy.add(new SortByImpl(property, sb.getSortOrder())); + } + } else { + sortBy.add(sb); + } + } + query.setSortBy(sortBy.toArray(new SortBy[sortBy.size()])); + } + + return query; + } + + private List toPropertyNames(String strPropNames) { + return Arrays.stream(strPropNames.split(";")) + .map(strPropName -> ff.property(strPropName, getNamespaceSupport())) + .collect(Collectors.toList()); + } + + @Override + public String getBoundingBoxPropertyName() { + XPathUtil.StepList steps = + XPathUtil.steps( + getFeatureDescriptor(), + queryableMapping + .get(getBoundingBoxQueryable()) + .get(0) + .getPropertyName(), + getNamespaceSupport()); + + return CatalogStoreMapping.toDotPath(steps); + } + } + private static final Logger LOGGER = Logging.getLogger(QueryableMappingRecordDescriptor.class); private GeoServer geoServer; - protected Map> queryableMapping = new HashMap<>(); + private Map mappedQueryables = new HashMap<>(); public QueryableMappingRecordDescriptor(GeoServer geoServer) { this.geoServer = geoServer; - readMapping(); } - public QueryableMappingRecordDescriptor() { - readMapping(); + public QueryableMappingRecordDescriptor() {} + + protected abstract String getBoundingBoxQueryable(); + + @Override + public String getBoundingBoxPropertyName() { + return getQueryablesMapping(null).getBoundingBoxPropertyName(); + } + + @Override + public Query adaptQuery(Query query) { + return getQueryablesMapping(null).adaptQuery(query); + } + + @Override + public List translateProperty(Name name) { + return getQueryablesMapping(null).translateProperty(name); + } + + @Override + public QueryablesMapping getQueryablesMapping(String mappingName) { + QueryablesMapping result = + mappedQueryables.computeIfAbsent( + mappingName == null ? "" : mappingName, name -> readMapping(name)); + if (result == null && mappingName != null) { + return getQueryablesMapping(null); // default + } + return result; } - public void readMapping() { - String fileName = getFeatureDescriptor().getLocalName() + ".queryables.properties"; + private MappedQueryables readMapping(String mappingName) { + String fileName = + getFeatureDescriptor().getLocalName() + + (Strings.isNullOrEmpty(mappingName) ? "" : "-" + mappingName) + + ".queryables.properties"; try { Properties props = new Properties(); @@ -63,7 +165,11 @@ public void readMapping() { Resource f = loader.get("csw").get(fileName); if (!Resources.exists(f)) { - IOUtils.copy(getClass().getResourceAsStream(fileName), f.out()); + if (mappingName == null) { + IOUtils.copy(getClass().getResourceAsStream(fileName), f.out()); + } else { + return null; + } } try (InputStream in = f.in()) { @@ -75,57 +181,11 @@ public void readMapping() { } } - queryableMapping.putAll( - props.entrySet().stream() - .collect( - Collectors.toMap( - e -> (String) e.getKey(), - e -> toPropertyNames((String) e.getValue())))); + return new MappedQueryables(props); } catch (IOException e) { LOGGER.log(Level.SEVERE, e.getMessage(), e); throw new FatalBeanException(e.getMessage(), e); } } - - @Override - public List translateProperty(Name name) { - return queryableMapping.get(name.getLocalPart()); - } - - @Override - public Query adaptQuery(Query query) { - QueryableMappingFilterVisitor visitor = - new QueryableMappingFilterVisitor(getFeatureDescriptor(), queryableMapping); - Filter filter = query.getFilter(); - if (filter != null && !Filter.INCLUDE.equals(filter)) { - query.setFilter((Filter) filter.accept(visitor, null)); - } - - if (query.getSortBy() != null && query.getSortBy().length > 0) { - List sortBy = Lists.newArrayList(); - for (int i = 0; i < query.getSortBy().length; i++) { - SortBy sb = query.getSortBy()[i]; - if (!SortBy.NATURAL_ORDER.equals(sb) && !SortBy.REVERSE_ORDER.equals(sb)) { - @SuppressWarnings("unchecked") - List properties = - (List) sb.getPropertyName().accept(visitor, null); - for (PropertyName property : properties) { - sortBy.add(new SortByImpl(property, sb.getSortOrder())); - } - } else { - sortBy.add(sb); - } - } - query.setSortBy(sortBy.toArray(new SortBy[sortBy.size()])); - } - - return query; - } - - private List toPropertyNames(String strPropNames) { - return Arrays.stream(strPropNames.split(";")) - .map(strPropName -> ff.property(strPropName, getNamespaceSupport())) - .collect(Collectors.toList()); - } } diff --git a/src/extension/csw/csw-iso/src/test/java/org/geoserver/csw/store/internal/iso/MultipleMappingTest.java b/src/extension/csw/csw-iso/src/test/java/org/geoserver/csw/store/internal/iso/MultipleMappingTest.java index 6bd9ebaf805..3666c33bc03 100644 --- a/src/extension/csw/csw-iso/src/test/java/org/geoserver/csw/store/internal/iso/MultipleMappingTest.java +++ b/src/extension/csw/csw-iso/src/test/java/org/geoserver/csw/store/internal/iso/MultipleMappingTest.java @@ -7,6 +7,7 @@ import static org.custommonkey.xmlunit.XMLAssert.assertXpathEvaluatesTo; import java.io.File; +import org.geoserver.catalog.ResourceInfo; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -17,10 +18,18 @@ public class MultipleMappingTest extends MDTestSupport { private File secondMappingFileIgnore, secondMappingFile, secondMappingFileIgnore2, - secondMappingFile2; + secondMappingFile2, + secondqMappingFileIgnore, + secondqMappingFile; @Before public void load() { + + // insert extra metadata + ResourceInfo forestInfo = getCatalog().getLayerByName("Forests").getResource(); + forestInfo.getMetadata().put("abstract2", "Forests-abstract2"); + getCatalog().save(forestInfo); + // copy all mappings into the data directory secondMappingFileIgnore = new File( @@ -28,19 +37,31 @@ public void load() { "csw/MD_Metadata-second.properties.ignore"); secondMappingFile = new File(testData.getDataDirectoryRoot(), "csw/MD_Metadata-second.properties"); + secondMappingFileIgnore2 = new File(testData.getDataDirectoryRoot(), "csw/Record-second.properties.ignore"); secondMappingFile2 = new File(testData.getDataDirectoryRoot(), "csw/Record-second.properties"); + secondqMappingFileIgnore = + new File( + testData.getDataDirectoryRoot(), + "csw/MD_Metadata-second.queryables.properties.ignore"); + secondqMappingFile = + new File( + testData.getDataDirectoryRoot(), + "csw/MD_Metadata-second.queryables.properties"); + secondMappingFileIgnore.renameTo(secondMappingFile); secondMappingFileIgnore2.renameTo(secondMappingFile2); + secondqMappingFileIgnore.renameTo(secondqMappingFile); } @After public void restore() { secondMappingFile.renameTo(secondMappingFileIgnore); secondMappingFile2.renameTo(secondMappingFileIgnore2); + secondqMappingFile.renameTo(secondqMappingFileIgnore); } @Test @@ -69,7 +90,7 @@ public void testTitleFilter() throws Exception { String request = "csw?service=CSW&version=2.0.2&request=GetRecords&typeNames=gmd:MD_Metadata&resultType=results&elementSetName=brief&outputSchema=http://www.isotc211.org/2005/gmd&constraint=Title = 'Forests'"; Document d = getAsDOM(request); - print(d); + // print(d); // validateSchema(d.getElementsByTagName("//gmd:MD_MetaData")); assertXpathEvaluatesTo("2", "//csw:SearchResults/@numberOfRecordsMatched", d); @@ -99,6 +120,71 @@ public void testTitleFilter() throws Exception { + forestId + ".second']/gmd:contact/gmd:CI_ResponsibleParty/gmd:individualName/gco:CharacterString", d); + + assertXpathEvaluatesTo( + "-180.0", + "//gmd:MD_Metadata[gmd:fileIdentifier/gco:CharacterString='" + + forestId + + "']/gmd:identificationInfo/gmd:MD_DataIdentification/gmd:extent/gmd:EX_Extent/gmd:geographicElement/gmd:EX_GeographicBoundingBox/gmd:westBoundLongitude/gco:Decimal", + d); + assertXpathEvaluatesTo( + "-90.0", + "//gmd:MD_Metadata[gmd:fileIdentifier/gco:CharacterString='" + + forestId + + "']/gmd:identificationInfo/gmd:MD_DataIdentification/gmd:extent/gmd:EX_Extent/gmd:geographicElement/gmd:EX_GeographicBoundingBox/gmd:southBoundLatitude/gco:Decimal", + d); + assertXpathEvaluatesTo( + "180.0", + "//gmd:MD_Metadata[gmd:fileIdentifier/gco:CharacterString='" + + forestId + + "']/gmd:identificationInfo/gmd:MD_DataIdentification/gmd:extent/gmd:EX_Extent/gmd:geographicElement/gmd:EX_GeographicBoundingBox/gmd:eastBoundLongitude/gco:Decimal", + d); + assertXpathEvaluatesTo( + "90.0", + "//gmd:MD_Metadata[gmd:fileIdentifier/gco:CharacterString='" + + forestId + + "']/gmd:identificationInfo/gmd:MD_DataIdentification/gmd:extent/gmd:EX_Extent/gmd:geographicElement/gmd:EX_GeographicBoundingBox/gmd:northBoundLatitude/gco:Decimal", + d); + + assertXpathEvaluatesTo( + "-180.0", + "//gmd:MD_Metadata[gmd:fileIdentifier/gco:CharacterString='" + + forestId + + ".second']/gmd:identificationInfo/srv:SV_ServiceIdentification/srv:extent/gmd:EX_Extent/gmd:geographicElement/gmd:EX_GeographicBoundingBox/gmd:westBoundLongitude/gco:Decimal", + d); + assertXpathEvaluatesTo( + "-90.0", + "//gmd:MD_Metadata[gmd:fileIdentifier/gco:CharacterString='" + + forestId + + ".second']/gmd:identificationInfo/srv:SV_ServiceIdentification/srv:extent/gmd:EX_Extent/gmd:geographicElement/gmd:EX_GeographicBoundingBox/gmd:southBoundLatitude/gco:Decimal", + d); + assertXpathEvaluatesTo( + "180.0", + "//gmd:MD_Metadata[gmd:fileIdentifier/gco:CharacterString='" + + forestId + + ".second']/gmd:identificationInfo/srv:SV_ServiceIdentification/srv:extent/gmd:EX_Extent/gmd:geographicElement/gmd:EX_GeographicBoundingBox/gmd:eastBoundLongitude/gco:Decimal", + d); + assertXpathEvaluatesTo( + "90.0", + "//gmd:MD_Metadata[gmd:fileIdentifier/gco:CharacterString='" + + forestId + + ".second']/gmd:identificationInfo/srv:SV_ServiceIdentification/srv:extent/gmd:EX_Extent/gmd:geographicElement/gmd:EX_GeographicBoundingBox/gmd:northBoundLatitude/gco:Decimal", + d); + } + + @Test + public void testTitleFilterSecondQueryable() throws Exception { + String request = + "csw?service=CSW&version=2.0.2&request=GetRecords&typeNames=gmd:MD_Metadata&resultType=results&elementSetName=brief&outputSchema=http://www.isotc211.org/2005/gmd&constraint=Abstract = 'Forests-abstract2'"; + Document d = getAsDOM(request); + + assertXpathEvaluatesTo("1", "//csw:SearchResults/@numberOfRecordsMatched", d); + assertXpathEvaluatesTo("1", "//csw:SearchResults/@numberOfRecordsReturned", d); + assertXpathEvaluatesTo("1", "count(//csw:SearchResults/*)", d); + assertXpathEvaluatesTo( + "Forests", + "//gmd:MD_Metadata/gmd:identificationInfo/gmd:MD_DataIdentification/gmd:citation/gmd:CI_Citation/gmd:title/gco:CharacterString", + d); } @Test diff --git a/src/extension/csw/csw-iso/src/test/resources/org/geoserver/csw/store/internal/MD_Metadata-second.properties.ignore b/src/extension/csw/csw-iso/src/test/resources/org/geoserver/csw/store/internal/MD_Metadata-second.properties.ignore index 93351648960..f491c531304 100644 --- a/src/extension/csw/csw-iso/src/test/resources/org/geoserver/csw/store/internal/MD_Metadata-second.properties.ignore +++ b/src/extension/csw/csw-iso/src/test/resources/org/geoserver/csw/store/internal/MD_Metadata-second.properties.ignore @@ -2,3 +2,4 @@ identificationInfo.MD_DataIdentification.citation.CI_Citation.title.CharacterString=title $dateStamp.Date=if_then_else(isNull("metadata.date"), "Expression/NIL", "metadata.date") $contact.CI_ResponsibleParty.individualName.CharacterString='Jeffery Smith' +identificationInfo.SV_ServiceIdentification.abstract.CharacterString="metadata.abstract2" \ No newline at end of file diff --git a/src/extension/csw/csw-iso/src/test/resources/org/geoserver/csw/store/internal/MD_Metadata-second.queryables.properties.ignore b/src/extension/csw/csw-iso/src/test/resources/org/geoserver/csw/store/internal/MD_Metadata-second.queryables.properties.ignore new file mode 100644 index 00000000000..c9a88319bc3 --- /dev/null +++ b/src/extension/csw/csw-iso/src/test/resources/org/geoserver/csw/store/internal/MD_Metadata-second.queryables.properties.ignore @@ -0,0 +1,36 @@ +# brief +Identifier=gmd:fileIdentifier/gco:CharacterString +Title=gmd:identificationInfo/gmd:MD_DataIdentification/gmd:citation/gmd:CI_Citation/gmd:title/gco:CharacterString;gmd:identificationInfo/gmd:MD_DataIdentification/gmd:citation/gmd:CI_Citation/gmd:title/gco:CharacterString +Type=gmd:hierarchyLevel/gmd:MD_ScopeCode/@codeListValue +BoundingBox=gmd:identificationInfo/srv:SV_ServiceIdentification/srv:extent/gmd:EX_Extent/gmd:geographicElement/gmd:EX_GeographicBoundingBox +GraphicOverview=gmd:identificationInfo/gmd:MD_DataIdentification/gmd:graphicOverview/gmd:MD_BrowseGraphic/gmd:fileName/gco:CharacterString +ServiceType=gmd:identificationInfo/gmd:SV_ServiceIdentification/gmd:serviceType/gco:CharacterString +ServiceTypeVersion=gmd:identificationInfo/gmd:SV_ServiceIdentification/gmd:serviceTypeVersion/gco:CharacterString +# summary +Abstract=gmd:identificationInfo/srv:SV_ServiceIdentification/gmd:abstract/gco:CharacterString +CharacterSet=gmd:identificationInfo/gmd:MD_DataIdentification/gmd:characterSet/gmd:MD_CharacterSetCode/@codeListValue +Creator=gmd:identificationInfo/gmd:MD_DataIdentification/gmd:pointOfContact/gmd:CI_ResponsibleParty/gmd:organisationName[role/gmd:CI_RoleCode/gmd:@codeListValue=’originator’]/gco:CharacterString +Contributor=gmd:identificationInfo/gmd:MD_DataIdentification/gmd:pointOfContact/gmd:CI_ResponsibleParty/gmd:organisationName[role/gmd:CI_RoleCode/gmd:@codeListValue=’author’]/gco:CharacterString +CouplingType=gmd:identificationInfo/gmd:SV_ServiceIdentification/gmd:couplingType/gmd:SV_CouplingType/gmd:code/@codeListValue +Format=gmd:distributionInfo/gmd:MD_Distribution/gmd:distributionFormat/gmd:MD_Format/gmd:name/gco:CharacterString +FormatVersion=gmd:distributionInfo/gmd:MD_Distribution/gmd:distributionFormat/gmd:MD_Format/gmd:version/gco:CharacterString +HierarchyLevelName= gmd:hierarchyLevelName/gco:CharacterString +Language=gmd:language/gco:CharacterString +Lineage=gmd:dataQualityInfo/gmd:DQ_DataQuality/gmd:lineage/gmd:LI_Lineage/gmd:statement/gco:CharacterString +MetadataCharacterSet=gmd:characterSet/gmd:MD_ScopeCode/@codeListValue +MetadataStandardName=gmd:metadataStandardName/gco:CharacterString +MetadataStandardVersion=gmd:metadataStandardVersion/gco:CharacterString +Modified=gmd:dateStamp/gmd:Date +OnlineResource=gmd:distributionInfo/gmd:MD_Distribution/gmd:transferOptions/gmd:MD_DigitalTransferOption/gmd:onLine/gmd:CI_OnlineResource/gmd:linkage/gmd:URL/gco:CharacterString +ParentIdentifier=gmd:parentIdentifier/gco:CharacterString +Publisher=gmd:identificationInfo/gmd:MD_DataIdentification/gmd:pointOfContact/gmd:CI_ResponsibleParty/gmd:organisationName[role/gmd:CI_RoleCode/gmd:@codeListValue=’publisher’]/gco:CharacterString +ResourceLanguage=gmd:identificationInfo/gmd:MD_DataIdentification/gmd:language/gco:CharacterString +ReferenceSystem=gmd:referenceSystemInfo/gmd:MD_ReferenceSystem/gmd:referenceSystemIdentifier/gmd:RS_Identifier/gco:CharacterString +RevisionDate=gmd:identificationInfo/gmd:MD_DataIdentification/gmd:citation/gmd:CI_Citation/gmd:date/gmd:CI_Date[dateType/gmd:CI_DateTypeCode/gmd:@codeListValue='revision']/gmd:date/gmd:Date +Rights=gmd:identificationInfo/gmd:MD_DataIdentification/gmd:resourceConstraints/gmd:MD_LegalConstraints/gmd:accessConstraints/@codeListValue +ServiceOperation=gmd:identificationInfo/gmd:SV_ServiceIdentification/gmd:containsOperations/gmd:SV_OperationMetadata/gco:CharacterString +SpatialResolution=gmd:identificationInfo/gmd:MD_DataIdentification/gmd:spatialResolution/gco:CharacterString +SpatialRepresentationType=gmd:identificationInfo/gmd:MD_DataIdentification/gmd:spatialRepresentationType/gmd:MD_SpatialRepresentationTypeCode/@codeListValue +TopicCategory=gmd:identificationInfo/gmd:MD_DataIdentification/gmd:topicCategory/gco:CharacterString +# required +Contact=gmd:contact/gmd:CI_ResponsibleParty/gmd:organisationName/gco:CharacterString;gmd:contact/gmd:CI_ResponsibleParty/gmd:organisationName/gmx:Anchor \ No newline at end of file diff --git a/src/extension/csw/simple-store/src/main/java/org/geoserver/csw/store/simple/SimpleCatalogStore.java b/src/extension/csw/simple-store/src/main/java/org/geoserver/csw/store/simple/SimpleCatalogStore.java index cb002e2c768..12d1d6b4113 100644 --- a/src/extension/csw/simple-store/src/main/java/org/geoserver/csw/store/simple/SimpleCatalogStore.java +++ b/src/extension/csw/simple-store/src/main/java/org/geoserver/csw/store/simple/SimpleCatalogStore.java @@ -65,6 +65,8 @@ public FeatureCollection getRecordsInternal( RecordDescriptor rd, RecordDescriptor outputRd, Query q, Transaction t) throws IOException { + Query pq = prepareQuery(q, rd, rd); + int startIndex = 0; if (q.getStartIndex() != null) { startIndex = q.getStartIndex(); @@ -73,8 +75,8 @@ public FeatureCollection getRecordsInternal( new RecordsFeatureCollection(root, startIndex); // filtering - if (q.getFilter() != null && q.getFilter() != Filter.INCLUDE) { - Filter filter = q.getFilter(); + if (pq.getFilter() != null && pq.getFilter() != Filter.INCLUDE) { + Filter filter = pq.getFilter(); CSWAnyExpander expander = new CSWAnyExpander(); Filter expanded = (Filter) filter.accept(expander, null); @@ -82,10 +84,10 @@ public FeatureCollection getRecordsInternal( } // sorting - if (q.getSortBy() != null && q.getSortBy().length > 0) { + if (pq.getSortBy() != null && pq.getSortBy().length > 0) { Feature[] features = records.toArray(new Feature[records.size()]); Comparator comparator = - ComplexComparatorFactory.buildComparator(q.getSortBy()); + ComplexComparatorFactory.buildComparator(pq.getSortBy()); Arrays.sort(features, comparator); records = new MemoryFeatureCollection(records.getSchema(), Arrays.asList(features));