> mdLegalConstraintsUseLimitationObject = new ArrayList<>();
private Integer internalId;
private String metadataIdentifier;
private IndexDocumentType docType;
diff --git a/modules/library/common-index-model/src/main/java/org/fao/geonet/index/model/gn/Link.java b/modules/library/common-index-model/src/main/java/org/fao/geonet/index/model/gn/Link.java
index 68dfa15e..2a903394 100644
--- a/modules/library/common-index-model/src/main/java/org/fao/geonet/index/model/gn/Link.java
+++ b/modules/library/common-index-model/src/main/java/org/fao/geonet/index/model/gn/Link.java
@@ -28,4 +28,7 @@ public class Link {
private String group;
private String mimeType;
private String nilReason;
+
+ private String hash;
+ private String idx;
}
diff --git a/modules/library/common-index-model/src/main/java/org/fao/geonet/index/model/gn/Theme.java b/modules/library/common-index-model/src/main/java/org/fao/geonet/index/model/gn/Theme.java
new file mode 100644
index 00000000..06c670dc
--- /dev/null
+++ b/modules/library/common-index-model/src/main/java/org/fao/geonet/index/model/gn/Theme.java
@@ -0,0 +1,53 @@
+/**
+ * (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.fao.geonet.index.model.gn;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * represents a theme (inside the elastic json allKeywords).
+ *
+ *"allKeywords": {
+ * "th_inspire-service-taxonomy": {
+ * "id": "geonetwork.thesaurus.external.theme.inspire-service-taxonomy",
+ * "theme": "theme",
+ * "link": "http://localhost:8080/geonetwork/srv/api/registries/vocabularies/external.theme.inspire-service-taxonomy",
+ * "keywords": [
+ * {
+ * "default": "GEONETWORK",
+ * "langeng": "GEONETWORK"
+ * },
+ * {
+ * "default": "OGCAPI",
+ * "langeng": "OGCAPI"
+ * }
+ * ]
+ * }
+ * }
+ *
+ */
+@Data
+@EqualsAndHashCode()
+@XmlRootElement(name = "indexRecord")
+@XmlAccessorType(XmlAccessType.FIELD)
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class Theme {
+ public String id;
+ public String title;
+ public String theme;
+ public String link;
+ public List> keywords;
+ public HashMap multilingualTitle;
+
+}
diff --git a/modules/library/common-index-model/src/test/java/org/fao/geonet/index/model/gn/IndexRecordTest.java b/modules/library/common-index-model/src/test/java/org/fao/geonet/index/model/gn/IndexRecordTest.java
index af9d3f67..f13e14df 100644
--- a/modules/library/common-index-model/src/test/java/org/fao/geonet/index/model/gn/IndexRecordTest.java
+++ b/modules/library/common-index-model/src/test/java/org/fao/geonet/index/model/gn/IndexRecordTest.java
@@ -66,7 +66,7 @@ public void testJsonToPojo() throws IOException {
record.resourceTitle.get(defaultText)
);
- Assert.assertEquals(49, record.getOtherProperties().size());
+ Assert.assertEquals(48, record.getOtherProperties().size());
Assert.assertEquals("gmd:MD_Metadata", record.getRoot());
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/OgcApiRecordApp.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/OgcApiRecordApp.java
index 1723f829..564c1172 100644
--- a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/OgcApiRecordApp.java
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/OgcApiRecordApp.java
@@ -10,9 +10,12 @@
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@SpringBootApplication
@@ -27,4 +30,18 @@ public static void main(String[] args) {
SpringApplication.run(OgcApiRecordApp.class, args);
}
+
+ /**
+ * Configure CORS to allow all connections.
+ * @return CORS configuration.
+ */
+ @Bean
+ public WebMvcConfigurer corsConfigurer() {
+ return new WebMvcConfigurer() {
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ registry.addMapping("/**").allowedOrigins("*");
+ }
+ };
+ }
}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/CapabilitiesApiController.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/CapabilitiesApiController.java
index 12395b73..1fb0324c 100644
--- a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/CapabilitiesApiController.java
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/CapabilitiesApiController.java
@@ -16,10 +16,11 @@
import org.fao.geonet.common.search.SearchConfiguration.Operations;
import org.fao.geonet.domain.Source;
import org.fao.geonet.domain.SourceType;
+import org.fao.geonet.ogcapi.records.controller.model.CollectionInfo;
import org.fao.geonet.ogcapi.records.controller.model.Conformance;
import org.fao.geonet.ogcapi.records.controller.model.Content;
-import org.fao.geonet.ogcapi.records.controller.model.Link;
import org.fao.geonet.ogcapi.records.controller.model.Root;
+import org.fao.geonet.ogcapi.records.model.OgcApiLink;
import org.fao.geonet.ogcapi.records.model.XsltModel;
import org.fao.geonet.ogcapi.records.util.CollectionInfoBuilder;
import org.fao.geonet.ogcapi.records.util.LinksItemsBuilder;
@@ -67,19 +68,17 @@ public class CapabilitiesApiController {
@Autowired
ConcurrentMapCacheManager cacheManager;
-
+ @Autowired
+ MediaTypeUtil mediaTypeUtil;
+ @Autowired
+ CollectionInfoBuilder collectionInfoBuilder;
@Autowired
private SourceRepository sourceRepository;
-
@Autowired
private SearchConfiguration configuration;
- @Autowired
- MediaTypeUtil mediaTypeUtil;
-
/**
* Landing page end-point.
- *
*/
@io.swagger.v3.oas.annotations.Operation(
summary = "Landing page.",
@@ -111,16 +110,37 @@ public ResponseEntity getLandingPage(@ApiIgnore HttpServletRequest request
String label = source.getLabel("eng");
root.title(StringUtils.isEmpty(label) ? source.getName() : label);
root.description("");
+
+ Locale locale = LocaleContextHolder.getLocale();
+
+ var language = locale.getISO3Language();
+
+ CollectionInfo collectionInfo = collectionInfoBuilder
+ .buildFromSource(source, language, requestBaseUrl,
+ configuration.getFormat(mediaType), configuration, request);
+ if (collectionInfo != null) {
+ //put title and description in the main obj
+ if (StringUtils.hasText(collectionInfo.getTitle())) {
+ root.setTitle(collectionInfo.getTitle());
+ }
+ if (StringUtils.hasText(collectionInfo.getDescription())) {
+ root.setDescription(collectionInfo.getDescription());
+ }
+ }
+ root.setSystemInfo(collectionInfo);
}
- root.addLinksItem(new Link()
+ root.addLinksItem(new OgcApiLink()
.href(requestBaseUrl)
- .rel("self").type(MediaType.APPLICATION_JSON.toString()));
+ .rel("self")
+ .type(MediaType.APPLICATION_JSON.toString()));
- configuration.getFormats(Operations.root).forEach(f -> root.addLinksItem(new Link()
- .href(requestBaseUrl + "collections?f=" + f.getName())
- .type("Catalogue collections")
- .rel("self").type(f.getMimeType())));
+ configuration.getFormats(Operations.root).forEach(f ->
+ root.addLinksItem(new OgcApiLink()
+ .href(requestBaseUrl + "collections?f=" + f.getName())
+ .type("Catalogue collections")
+ .rel("self")
+ .type(f.getMimeType())));
addOpenApiLinks(root, requestBaseUrl);
addConformanceLinks(root, requestBaseUrl);
@@ -145,12 +165,12 @@ public ResponseEntity getLandingPage(@ApiIgnore HttpServletRequest request
private void addOpenApiLinks(Root root, String baseUrl) {
String title = "The OpenAPI Documentation";
- root.addLinksItem(new Link()
+ root.addLinksItem(new OgcApiLink()
.href(baseUrl + "openapi")
.title(title)
.rel("service-doc").type(MediaType.TEXT_HTML_VALUE));
- root.addLinksItem(new Link()
+ root.addLinksItem(new OgcApiLink()
.href(baseUrl + "openapi?f=json")
.title(title + " as JSON")
.rel("service-desc").type(MediaType.APPLICATION_JSON_VALUE));
@@ -158,15 +178,18 @@ private void addOpenApiLinks(Root root, String baseUrl) {
private void addConformanceLinks(Root root, String baseUrl) {
String title = "The Conformance classes";
- root.addLinksItem(new Link()
+
+ root.addLinksItem(new OgcApiLink()
.href(baseUrl + CONFORMANCE_REL)
.title(title)
- .rel(CONFORMANCE_REL).type(MediaType.TEXT_HTML_VALUE));
+ .rel(CONFORMANCE_REL)
+ .type(MediaType.TEXT_HTML_VALUE));
- root.addLinksItem(new Link()
+ root.addLinksItem(new OgcApiLink()
.href(baseUrl + CONFORMANCE_REL + "?f=json")
.title(title + " as JSON")
- .rel(CONFORMANCE_REL).type(MediaType.APPLICATION_JSON_VALUE));
+ .rel(CONFORMANCE_REL)
+ .type(MediaType.APPLICATION_JSON_VALUE));
}
@@ -202,7 +225,7 @@ public ResponseEntity conformanceDeclaration(@ApiIgnore HttpServlet
"http://www.opengis.net/spec/ogcapi-records-1/1.0/conf/html",
"http://www.opengis.net/spec/ogcapi-records-1/1.0/conf/json",
"http://www.opengis.net/spec/ogcapi-records-1/1.0/conf/sorting"
- ));
+ ));
if (!mediaType.equals(MediaType.TEXT_HTML)) {
return ResponseEntity.ok(conformance);
@@ -224,7 +247,6 @@ public ResponseEntity conformanceDeclaration(@ApiIgnore HttpServlet
/**
* Collections information end-point.
- *
*/
@io.swagger.v3.oas.annotations.Operation(
summary = "Collections available from this API.",
@@ -256,11 +278,12 @@ public ResponseEntity describeCollections(@ApiIgnore HttpServletRequest
List sources = sourceRepository.findAll();
sources.forEach(s -> content.addCollectionsItem(
- CollectionInfoBuilder.buildFromSource(
- s, language, requestBaseUrl, configuration.getFormat(mediaType), configuration)));
+ collectionInfoBuilder.buildFromSource(
+ s, language, requestBaseUrl, configuration.getFormat(mediaType),
+ configuration, request)));
// TODO: Accept format parameter.
- List linkList = LinksItemsBuilder.build(
+ List linkList = LinksItemsBuilder.build(
configuration.getFormat(mediaType), requestBaseUrl, language, configuration);
linkList.forEach(content::addLinksItem);
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/CollectionApiController.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/CollectionApiController.java
index 56a4c867..096ae997 100644
--- a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/CollectionApiController.java
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/CollectionApiController.java
@@ -1,3 +1,8 @@
+/**
+ * (c) 2020 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.fao.geonet.ogcapi.records.controller;
import io.swagger.annotations.Api;
@@ -62,18 +67,16 @@ public class CollectionApiController {
@Autowired
@Qualifier("xsltViewResolver")
ViewResolver viewResolver;
-
- @Autowired
- private CollectionService collectionService;
-
@Autowired
MessageSource messages;
-
- @Autowired
- private SearchConfiguration configuration;
-
@Autowired
MediaTypeUtil mediaTypeUtil;
+ @Autowired
+ CollectionInfoBuilder collectionInfoBuilder;
+ @Autowired
+ private CollectionService collectionService;
+ @Autowired
+ private SearchConfiguration configuration;
/**
* Describe a collection.
@@ -126,9 +129,9 @@ public ResponseEntity describeCollection(
String requestBaseUrl = request.getRequestURL()
.toString().replace(collectionId, "");
- CollectionInfo collectionInfo = CollectionInfoBuilder
+ CollectionInfo collectionInfo = collectionInfoBuilder
.buildFromSource(source, language, requestBaseUrl,
- configuration.getFormat(mediaType), configuration);
+ configuration.getFormat(mediaType), configuration, request);
return ResponseEntity.ok(collectionInfo);
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/ItemApiController.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/ItemApiController.java
index c5282407..57cea862 100644
--- a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/ItemApiController.java
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/ItemApiController.java
@@ -54,6 +54,7 @@
import org.fao.geonet.ogcapi.records.model.Item;
import org.fao.geonet.ogcapi.records.model.XsltModel;
import org.fao.geonet.ogcapi.records.service.CollectionService;
+import org.fao.geonet.ogcapi.records.service.RecordService;
import org.fao.geonet.ogcapi.records.util.MediaTypeUtil;
import org.fao.geonet.ogcapi.records.util.RecordsEsQueryBuilder;
import org.fao.geonet.ogcapi.records.util.XmlUtil;
@@ -111,6 +112,8 @@ public class ItemApiController {
MediaTypeUtil mediaTypeUtil;
@Autowired
DcatConverter dcatConverter;
+ @Autowired
+ RecordService recordService;
/**
* Describe a collection item.
@@ -167,7 +170,8 @@ public ResponseEntity collectionsCollectionIdItemsRecordIdGet(
|| mediaType.equals(GnMediaType.APPLICATION_GEOJSON)) {
try {
String type = mediaType.equals(MediaType.APPLICATION_JSON) ? "json" : "geojson";
- JsonNode recordAsJson = getRecordAsJson(collectionId, recordId, request, source, type);
+ JsonNode recordAsJson = recordService.getRecordAsJson(collectionId, recordId,
+ request, source, type);
streamResult(response,
recordAsJson.toPrettyString(),
@@ -307,7 +311,7 @@ private ResponseEntity collectionsCollectionIdItemsRecordIdGetAsJsonLd(
|| GnMediaType.APPLICATION_RDF_XML_VALUE.equals(acceptHeader);
boolean isLinkedData = (isTurtle || isRdfXml || isDcat);
- JsonNode recordAsJson = getRecordAsJson(collectionId, recordId, request, source,
+ JsonNode recordAsJson = recordService.getRecordAsJson(collectionId, recordId, request, source,
isLinkedData ? "json" : "schema.org");
if (isLinkedData) {
@@ -372,9 +376,11 @@ private ResponseEntity collectionsCollectionIdItemsRecordIdGetAsXml(
try {
String collectionFilter = collectionService.retrieveCollectionFilter(source, true);
- String query = recordsEsQueryBuilder.buildQuerySingleRecord(recordId, collectionFilter, null);
+ String query = recordsEsQueryBuilder.buildQuerySingleRecord(recordId,
+ collectionFilter, null);
- String queryResponse = proxy.searchAndGetResult(request.getSession(), request, query, null);
+ String queryResponse = proxy.searchAndGetResult(request.getSession(),
+ request, query, null);
Document queryResult = XmlUtil.parseXmlString(queryResponse);
String total = queryResult.getChildNodes().item(0).getAttributes().getNamedItem("total")
@@ -417,7 +423,8 @@ private ResponseEntity collectionsCollectionIdItemsRecordIdGetAsHtml(
}
try {
- JsonNode recordAsJson = getRecordAsJson(collectionId, recordId, request, source, "json");
+ JsonNode recordAsJson = recordService.getRecordAsJson(collectionId, recordId,
+ request, source, "json");
Metadata metadataRecord = metadataRepository.findOneByUuid(recordId);
if (metadataRecord == null) {
@@ -463,43 +470,6 @@ private ResponseEntity collectionsCollectionIdItemsRecordIdGetAsHtml(
}
- private JsonNode getRecordAsJson(
- String collectionId,
- String recordId,
- HttpServletRequest request,
- Source source,
- String type) throws Exception {
- String collectionFilter = collectionService.retrieveCollectionFilter(source, true);
- String query = recordsEsQueryBuilder.buildQuerySingleRecord(recordId, collectionFilter, null);
-
- String queryResponse = proxy.searchAndGetResult(request.getSession(), request, query, null);
-
- ObjectMapper mapper = new ObjectMapper();
- JsonFactory factory = mapper.getFactory();
- JsonParser parser = factory.createParser(queryResponse);
- JsonNode actualObj = mapper.readTree(parser);
-
- JsonNode totalValue =
- "json".equals(type)
- ? actualObj.get("hits").get("total").get("value")
- : actualObj.get("size");
-
- if ((totalValue == null) || (totalValue.intValue() == 0)) {
- throw new ResponseStatusException(HttpStatus.NOT_FOUND,
- messages.getMessage(EXCEPTION_COLLECTION_ITEM_NOT_FOUND,
- new String[]{recordId, collectionId},
- request.getLocale()));
- }
-
- if ("json".equals(type)) {
- return actualObj.get("hits").get("hits").get(0);
- } else {
- String elementName = "schema.org".equals(type) ? "dataFeedElement" : "features";
- return actualObj.get(elementName).get(0);
- }
- }
-
-
private List setDefaultRssSortBy(List sortby, HttpServletRequest request) {
boolean isRss = "rss".equals(request.getParameter("f"))
|| (request.getHeader(HttpHeaders.ACCEPT) != null
@@ -605,6 +575,7 @@ private ResponseEntity collectionsCollectionIdItemsGetAsHtml(
String collectionFilter = collectionService.retrieveCollectionFilter(source, false);
String query = recordsEsQueryBuilder
.buildQuery(q, externalids, bbox, startindex, limit, collectionFilter, sortby, null);
+
EsSearchResults results = new EsSearchResults();
try {
results = proxy
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/QueryableApiController.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/QueryableApiController.java
new file mode 100644
index 00000000..7accc200
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/QueryableApiController.java
@@ -0,0 +1,63 @@
+package org.fao.geonet.ogcapi.records.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiParam;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.responses.ApiResponses;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import lombok.extern.slf4j.Slf4j;
+import org.fao.geonet.ogcapi.records.model.JsonSchema;
+import org.fao.geonet.ogcapi.records.service.QueryablesService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.ResponseStatus;
+import springfox.documentation.annotations.ApiIgnore;
+
+/**
+ * See https://docs.ogc.org/is/19-079r2/19-079r2.html#rc_queryables and
+ * https://docs.ogc.org/DRAFTS/20-004.html#_queryables_link
+ */
+@Api(tags = "OGC API Records")
+@Controller
+@Slf4j(topic = "org.fao.geonet.ogcapi")
+public class QueryableApiController {
+
+ @Autowired
+ QueryablesService queryablesService;
+
+ /**
+ * Describe queryables for a collection.
+ */
+ @io.swagger.v3.oas.annotations.Operation(
+ summary = "Describes queryables for a collection.",
+ description = "Queryables resource for discovering a list of resource properties with their "
+ + "types and constraints that may be used to construct filter expressions"
+ + " on a collection of resources.")
+ @GetMapping(value = "/collections/{collectionId}/queryables",
+ produces = {MediaType.APPLICATION_JSON_VALUE,
+ })
+ @ResponseStatus(HttpStatus.OK)
+ @ApiResponses(value = {
+ @ApiResponse(responseCode = "200", description = "Describe queryables for a collection.")
+ })
+ @ResponseBody
+ public ResponseEntity queryablesForCollection(
+ @ApiParam(value = "Identifier (name) of a specific collection", required = true)
+ @PathVariable("collectionId") String collectionId,
+ @ApiIgnore HttpServletRequest request,
+ @ApiIgnore HttpServletResponse response,
+ @ApiIgnore Model model) throws Exception {
+
+ var jsonSchema = queryablesService.buildQueryables(collectionId);
+
+ return ResponseEntity.ok(jsonSchema);
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/CollectionInfo.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/CollectionInfo.java
index 0d05f71f..35a29957 100644
--- a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/CollectionInfo.java
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/CollectionInfo.java
@@ -1,5 +1,12 @@
+/**
+ * (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.fao.geonet.ogcapi.records.controller.model;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
@@ -10,45 +17,350 @@
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
+import org.fao.geonet.ogcapi.records.model.OgcApiContact;
+import org.fao.geonet.ogcapi.records.model.OgcApiExtent;
+import org.fao.geonet.ogcapi.records.model.OgcApiLanguage;
+import org.fao.geonet.ogcapi.records.model.OgcApiLink;
+import org.fao.geonet.ogcapi.records.model.OgcApiSchema;
+import org.fao.geonet.ogcapi.records.model.OgcApiTheme;
/**
- * CollectionInfo entity.
+ * cf. https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/catalog.yaml
+ *
+ * CollectionInfo entity.
*/
@JacksonXmlRootElement(localName = "CollectionInfo")
@XmlRootElement(name = "CollectionInfo")
@XmlAccessorType(XmlAccessType.FIELD)
-public class CollectionInfo {
+public class CollectionInfo {
+
+ /**
+ * Fixed value of "Catalog".
+ */
+ //required cf. https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/catalog.yaml
+ @JsonProperty("type")
+ @JacksonXmlProperty(localName = "type")
+ private String type = "catalog";
+
+ // cf. https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/catalog.yaml
+ // NOTE: spec says this should be "record" or ["record","catalog"].
+ /**Fixed value of "record", "catalog" or both.*/
+ @JsonProperty("itemType")
+ @JacksonXmlProperty(localName = "itemType")
+ private String itemType = "record";
+
+
+ /**
+ * The identifier of the catalog.
+ */
@JsonProperty("id")
@JacksonXmlProperty(localName = "id")
private String id;
+ /**
+ * A human-readable name given to the resource.
+ */
@JsonProperty("title")
@JacksonXmlProperty(localName = "title")
private String title;
+ /**
+ * A free-text account of the resource.
+ */
@JsonProperty("description")
@JacksonXmlProperty(localName = "description")
private String description;
+ /**
+ * links for this object.
+ */
@JsonProperty("links")
@JacksonXmlProperty(localName = "links")
-
- private List links = new ArrayList<>();
+ @JsonInclude(Include.NON_EMPTY)
+ private List links = new ArrayList<>();
+ /**
+ * The spatiotemporal coverage of this catalog.
+ */
@JsonProperty("extent")
@JacksonXmlProperty(localName = "extent")
- private Extent extent;
+ private OgcApiExtent extent;
+ /**
+ * The list of supported coordinate reference systems.
+ */
@JsonProperty("crs")
@JacksonXmlProperty(localName = "crs")
-
+ @JsonInclude(Include.NON_EMPTY)
private List crs = null;
+ /**
+ * A list of contacts qualified by their role(s) in association to the record or the resource
+ * described by the record.
+ */
+ @JsonProperty("contacts")
+ @JacksonXmlProperty(localName = "contacts")
+ @JsonInclude(Include.NON_EMPTY)
+ private List contacts = null;
+
+ /**
+ * The date this record was created in the server. format: date-time
+ */
+ @JsonProperty("created")
+ @JacksonXmlProperty(localName = "created")
+ @JsonInclude(Include.NON_EMPTY)
+ private String created = null;
+
+ /**
+ * The most recent date on which the record was changed. format: date-time
+ */
+ @JsonProperty("updated")
+ @JacksonXmlProperty(localName = "updated")
+ @JsonInclude(Include.NON_EMPTY)
+ private String updated = null;
+
+ /**
+ * The topic or topics of the resource. Typically represented using free-form keywords, tags, key
+ * phrases, or classification codes.
+ */
+ @JsonProperty("keywords")
+ @JacksonXmlProperty(localName = "keywords")
+ @JsonInclude(Include.NON_EMPTY)
+ private List keywords = null;
+
+ /**
+ * The language used for textual values in this record representation.
+ */
+ @JsonProperty("language")
+ @JacksonXmlProperty(localName = "language")
+ @JsonInclude(Include.NON_EMPTY)
+ private OgcApiLanguage language = null;
+
+ /**
+ * This list of languages in which this record is available.
+ */
+ @JsonProperty("languages")
+ @JacksonXmlProperty(localName = "languages")
+ @JsonInclude(Include.NON_EMPTY)
+ private List languages = null;
+
+ /**
+ * A knowledge organization system used to classify the resource.
+ **/
+ @JsonProperty("themes")
+ @JsonInclude(Include.NON_EMPTY)
+ @JacksonXmlProperty(localName = "themes")
+ private List themes = null;
+
+
+ /**
+ * A legal document under which the resource is made available. If the resource is being made
+ * available under a common license then use an SPDX license id (https://spdx.org/licenses/). If
+ * the resource is being made available under multiple common licenses then use an SPDX license
+ * expression v2.3 string (https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/) If the
+ * resource is being made available under one or more licenses that haven't been assigned an SPDX
+ * identifier or one or more custom licenses then use a string value of 'other' and include one or
+ * more links (rel="license") in the `link` section of the record to the file(s) that contains the
+ * text of the license(s). There is also the case of a resource that is private or unpublished and
+ * is thus unlicensed; in this case do not register such a resource in the catalog in the first
+ * place since there is no point in making such a resource discoverable.
+ */
+ @JsonProperty("license")
+ @JacksonXmlProperty(localName = "license")
+ @JsonInclude(Include.NON_EMPTY)
+ private String license = null;
+
+
+ /**
+ * A statement that concerns all rights not addressed by the license such as a copyright
+ * statement.
+ */
+ @JsonProperty("rights")
+ @JacksonXmlProperty(localName = "rights")
+ @JsonInclude(Include.NON_EMPTY)
+ private String rights = null;
+
+
+ /**
+ * Each string in the array SHALL be the name of a sortable.
+ */
+ @JsonProperty("defaultSortOrder")
+ @JacksonXmlProperty(localName = "defaultSortOrder")
+ @JsonInclude(Include.NON_EMPTY)
+ private List defaultSortOrder = null;
+
+
+ //--------------------------------------------------------------------------
+ // these are properties in the written spec, but not in the .yaml definition
+ //--------------------------------------------------------------------------
+
+ /**
+ * The extensions/conformance classes used in this catalog object.
+ */
+ @JsonProperty("conformsTo")
+ @JacksonXmlProperty(localName = "conformsTo")
+ @JsonInclude(Include.NON_EMPTY)
+ private String conformsTo = null;
+
+ /**The list of languages in which records from the collection can be represented.*/
+ @JsonProperty("recordLanguages")
+ @JacksonXmlProperty(localName = "recordLanguages")
+ @JsonInclude(Include.NON_EMPTY)
+ private List recordLanguages = null;
+
+
+ /**
+ * The name of the array property in the catalog used to encode records in-line.
+ * The default value is records.
+ */
+ @JsonProperty("recordsArrayName")
+ @JacksonXmlProperty(localName = "recordsArrayName")
+ @JsonInclude(Include.NON_EMPTY)
+ private String recordsArrayName = null;
+
+ /**A list of schemes related to this catalog.*/
+ @JsonProperty("schemes")
+ @JacksonXmlProperty(localName = "schemes")
+ @JsonInclude(Include.NON_EMPTY)
+ private List schemes = null;
+
+
+ //--------------------------------------------------------------------------
+
+
+ public String getConformsTo() {
+ return conformsTo;
+ }
+
+ public void setConformsTo(String conformsTo) {
+ this.conformsTo = conformsTo;
+ }
+
+ public List getRecordLanguages() {
+ return recordLanguages;
+ }
+
+ public void setRecordLanguages(List recordLanguages) {
+ this.recordLanguages = recordLanguages;
+ }
+
+ public String getRecordsArrayName() {
+ return recordsArrayName;
+ }
+
+ public void setRecordsArrayName(String recordsArrayName) {
+ this.recordsArrayName = recordsArrayName;
+ }
+
+ public List getSchemes() {
+ return schemes;
+ }
+
+ public void setSchemes(List schemes) {
+ this.schemes = schemes;
+ }
+
+ public List getDefaultSortOrder() {
+ return defaultSortOrder;
+ }
+
+ public void setDefaultSortOrder(List defaultSortOrder) {
+ this.defaultSortOrder = defaultSortOrder;
+ }
+
+ public String getRights() {
+ return rights;
+ }
+
+ public void setRights(String rights) {
+ this.rights = rights;
+ }
+
+ public String getLicense() {
+ return license;
+ }
+
+ public void setLicense(String license) {
+ this.license = license;
+ }
+
public CollectionInfo id(String id) {
this.id = id;
return this;
}
+ public List getThemes() {
+ return themes;
+ }
+
+ public void setThemes(List themes) {
+ this.themes = themes;
+ }
+
+ public OgcApiLanguage getLanguage() {
+ return language;
+ }
+
+ public void setLanguage(OgcApiLanguage language) {
+ this.language = language;
+ }
+
+ public List getLanguages() {
+ return languages;
+ }
+
+ public void setLanguages(List languages) {
+ this.languages = languages;
+ }
+
+ public List getKeywords() {
+ return keywords;
+ }
+
+ public void setKeywords(List keywords) {
+ this.keywords = keywords;
+ }
+
+ public String getCreated() {
+ return created;
+ }
+
+ public void setCreated(String created) {
+ this.created = created;
+ }
+
+ public String getUpdated() {
+ return updated;
+ }
+
+ public void setUpdated(String updated) {
+ this.updated = updated;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getItemType() {
+ return itemType;
+ }
+
+ public void setItemType(String itemType) {
+ this.itemType = itemType;
+ }
+
+ public List getContacts() {
+ return contacts;
+ }
+
+ public void setContacts(List contacts) {
+ this.contacts = contacts;
+ }
+
/**
* Identifier of the collection used, for example, in URIs.
*/
@@ -97,12 +409,13 @@ public void setDescription(String description) {
this.description = description;
}
- public CollectionInfo links(List links) {
+
+ public CollectionInfo links(List links) {
this.links = links;
return this;
}
- public CollectionInfo addLinksItem(Link linksItem) {
+ public CollectionInfo addLinksItem(OgcApiLink linksItem) {
this.links.add(linksItem);
return this;
}
@@ -111,15 +424,15 @@ public CollectionInfo addLinksItem(Link linksItem) {
* Get links.
*/
@ApiModelProperty(example = "[{\"href\":\"http://data.example.org/collections/buildings/items\",\"rel\":\"item\",\"type\":\"application/geo+json\",\"title\":\"Buildings\"},{\"href\":\"http://example.org/concepts/building.html\",\"rel\":\"describedBy\",\"type\":\"text/html\",\"title\":\"Coverage for buildings\"}]", required = true, value = "")
- public List getLinks() {
+ public List getLinks() {
return links;
}
- public void setLinks(List links) {
+ public void setLinks(List links) {
this.links = links;
}
- public CollectionInfo extent(Extent extent) {
+ public CollectionInfo extent(OgcApiExtent extent) {
this.extent = extent;
return this;
}
@@ -128,11 +441,11 @@ public CollectionInfo extent(Extent extent) {
* Get extent.
*/
@ApiModelProperty(value = "")
- public Extent getExtent() {
+ public OgcApiExtent getExtent() {
return extent;
}
- public void setExtent(Extent extent) {
+ public void setExtent(OgcApiExtent extent) {
this.extent = extent;
}
@@ -155,8 +468,8 @@ public CollectionInfo addCrsItem(String crsItem) {
/**
* The coordinate reference systems in which geometries may be retrieved. Coordinate reference
* systems are identified by a URI. The first coordinate reference system is the coordinate
- * reference system that is used by default. This is always
- * \"http://www.opengis.net/def/crs/OGC/1.3/CRS84\", i.e. WGS84 longitude/latitude.
+ * reference system that is used by default. This is always \"http://www.opengis.net/def/crs/OGC/1.3/CRS84\",
+ * i.e. WGS84 longitude/latitude.
*/
@ApiModelProperty(value = "The coordinate reference systems in which geometries may be retrieved. Coordinate reference systems are identified by a URI. The first coordinate reference system is the coordinate reference system that is used by default. This is always \"http://www.opengis.net/def/crs/OGC/1.3/CRS84\", i.e. WGS84 longitude/latitude.")
public List getCrs() {
@@ -194,7 +507,7 @@ public int hashCode() {
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class CollectionInfo {\n");
-
+
sb.append(" name: ").append(toIndentedString(id)).append("\n");
sb.append(" title: ").append(toIndentedString(title)).append("\n");
sb.append(" description: ").append(toIndentedString(description)).append("\n");
@@ -206,8 +519,8 @@ public String toString() {
}
/**
- * Convert the given object to string with each line indented by 4 spaces
- * (except the first line).
+ * Convert the given object to string with each line indented by 4 spaces (except the first
+ * line).
*/
private String toIndentedString(java.lang.Object o) {
if (o == null) {
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Content.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Content.java
index c490f43a..9664f534 100644
--- a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Content.java
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Content.java
@@ -10,6 +10,7 @@
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
+import org.fao.geonet.ogcapi.records.model.OgcApiLink;
/**
* Content entity.
@@ -17,23 +18,24 @@
@JacksonXmlRootElement(localName = "Content")
@XmlRootElement(name = "Content")
@XmlAccessorType(XmlAccessType.FIELD)
-public class Content {
+public class Content {
+
@JsonProperty("links")
@JacksonXmlProperty(localName = "links")
-
- private List links = new ArrayList<>();
+
+ private List links = new ArrayList<>();
@JsonProperty("collections")
@JacksonXmlProperty(localName = "collections")
-
+
private List collections = new ArrayList<>();
- public Content links(List links) {
+ public Content links(List links) {
this.links = links;
return this;
}
- public Content addLinksItem(Link linksItem) {
+ public Content addLinksItem(OgcApiLink linksItem) {
this.links.add(linksItem);
return this;
}
@@ -42,11 +44,11 @@ public Content addLinksItem(Link linksItem) {
* Get links.
*/
@ApiModelProperty(example = "[{\"href\":\"http://data.example.org/collections.json\",\"rel\":\"self\",\"type\":\"application/json\",\"title\":\"this document\"},{\"href\":\"http://data.example.org/collections.html\",\"rel\":\"alternate\",\"type\":\"text/html\",\"title\":\"this document as HTML\"},{\"href\":\"http://schemas.example.org/1.0/foobar.xsd\",\"rel\":\"describedBy\",\"type\":\"application/xml\",\"title\":\"XML schema for Acme Corporation data\"}]", required = true, value = "")
- public List getLinks() {
+ public List getLinks() {
return links;
}
- public void setLinks(List links) {
+ public void setLinks(List links) {
this.links = links;
}
@@ -95,7 +97,7 @@ public int hashCode() {
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class Content {\n");
-
+
sb.append(" links: ").append(toIndentedString(links)).append("\n");
sb.append(" collections: ").append(toIndentedString(collections)).append("\n");
sb.append("}");
@@ -103,8 +105,8 @@ public String toString() {
}
/**
- * Convert the given object to string with each line indented by 4 spaces
- * (except the first line).
+ * Convert the given object to string with each line indented by 4 spaces (except the first
+ * line).
*/
private String toIndentedString(java.lang.Object o) {
if (o == null) {
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/CrsEnum.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/CrsEnum.java
new file mode 100644
index 00000000..cc2dfe11
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/CrsEnum.java
@@ -0,0 +1,47 @@
+/**
+ * (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.fao.geonet.ogcapi.records.controller.model;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonValue;
+
+/**
+ * Coordinate reference system of the coordinates in the spatial extent (property `spatial`).
+ * In the Core, only WGS84 longitude/latitude is supported. Extensions may support additional
+ * coordinate reference systems.
+ */
+public enum CrsEnum {
+ HTTP_WWW_OPENGIS_NET_DEF_CRS_OGC_1_3_CRS84("http://www.opengis.net/def/crs/OGC/1.3/CRS84");
+
+ private String value;
+
+ CrsEnum(String value) {
+ this.value = value;
+ }
+
+ @JsonValue
+ public String getValue() {
+ return value;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+ /**
+ * Get a CrsEnum value from a string representation.
+ */
+ @JsonCreator
+ public static CrsEnum fromValue(String value) {
+ for (CrsEnum b : CrsEnum.values()) {
+ if (b.value.equals(value)) {
+ return b;
+ }
+ }
+ throw new IllegalArgumentException("Unexpected value '" + value + "'");
+ }
+}
\ No newline at end of file
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Extent.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Extent.java
deleted file mode 100644
index ced3f92f..00000000
--- a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Extent.java
+++ /dev/null
@@ -1,268 +0,0 @@
-package org.fao.geonet.ogcapi.records.controller.model;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonValue;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
-import io.swagger.annotations.ApiModelProperty;
-import java.math.BigDecimal;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlRootElement;
-
-/**
- * Extent entity.
- */
-@JacksonXmlRootElement(localName = "Extent")
-@XmlRootElement(name = "Extent")
-@XmlAccessorType(XmlAccessType.FIELD)
-public class Extent {
- /**
- * Coordinate reference system of the coordinates in the spatial extent (property `spatial`).
- * In the Core, only WGS84 longitude/latitude is supported. Extensions may support additional
- * coordinate reference systems.
- */
- public enum CrsEnum {
- HTTP_WWW_OPENGIS_NET_DEF_CRS_OGC_1_3_CRS84("http://www.opengis.net/def/crs/OGC/1.3/CRS84");
-
- private String value;
-
- CrsEnum(String value) {
- this.value = value;
- }
-
- @JsonValue
- public String getValue() {
- return value;
- }
-
- @Override
- public String toString() {
- return String.valueOf(value);
- }
-
- /**
- * Get a CrsEnum value from a string representation.
- */
- @JsonCreator
- public static CrsEnum fromValue(String value) {
- for (CrsEnum b : CrsEnum.values()) {
- if (b.value.equals(value)) {
- return b;
- }
- }
- throw new IllegalArgumentException("Unexpected value '" + value + "'");
- }
- }
-
- @JsonProperty("crs")
- @JacksonXmlProperty(localName = "crs")
- private CrsEnum crs = CrsEnum.HTTP_WWW_OPENGIS_NET_DEF_CRS_OGC_1_3_CRS84;
-
- @JsonProperty("spatial")
- @JacksonXmlProperty(localName = "spatial")
-
- private List spatial = null;
-
- /**
- * Temporal reference system of the coordinates in the temporal extent (property `temporal`).
- * In the Core, only the Gregorian calendar is supported. Extensions may support additional
- * temporal reference systems.
- */
- public enum TrsEnum {
- HTTP_WWW_OPENGIS_NET_DEF_UOM_ISO_8601_0_GREGORIAN("http://www.opengis.net/def/uom/ISO-8601/0/Gregorian");
-
- private String value;
-
- TrsEnum(String value) {
- this.value = value;
- }
-
- @JsonValue
- public String getValue() {
- return value;
- }
-
- @Override
- public String toString() {
- return String.valueOf(value);
- }
-
- /**
- * Get a TrsEnum value from a string representation.
- */
- @JsonCreator
- public static TrsEnum fromValue(String value) {
- for (TrsEnum b : TrsEnum.values()) {
- if (b.value.equals(value)) {
- return b;
- }
- }
- throw new IllegalArgumentException("Unexpected value '" + value + "'");
- }
- }
-
- @JsonProperty("trs")
- @JacksonXmlProperty(localName = "trs")
- private TrsEnum trs = TrsEnum.HTTP_WWW_OPENGIS_NET_DEF_UOM_ISO_8601_0_GREGORIAN;
-
- @JsonProperty("temporal")
- @JacksonXmlProperty(localName = "temporal")
-
- private List temporal = null;
-
- public Extent crs(CrsEnum crs) {
- this.crs = crs;
- return this;
- }
-
- /**
- * Coordinate reference system of the coordinates in the spatial extent (property `spatial`).
- * In the Core, only WGS84 longitude/latitude is supported. Extensions may support additional
- * coordinate reference systems.
- */
- @ApiModelProperty(value = "Coordinate reference system of the coordinates in the spatial extent "
- + "(property `spatial`). In the Core, only WGS84 longitude/latitude is supported. "
- + "Extensions may support additional coordinate reference systems.")
- public CrsEnum getCrs() {
- return crs;
- }
-
- public void setCrs(CrsEnum crs) {
- this.crs = crs;
- }
-
- public Extent spatial(List spatial) {
- this.spatial = spatial;
- return this;
- }
-
- /**
- * Adds a spatial item value.
- */
- public Extent addSpatialItem(BigDecimal spatialItem) {
- if (this.spatial == null) {
- this.spatial = new ArrayList<>();
- }
- this.spatial.add(spatialItem);
- return this;
- }
-
- /**
- * West, north, east, south edges of the spatial extent. The minimum and maximum values apply
- * to the coordinate reference system WGS84 longitude/latitude that is supported in the Core.
- * If, for example, a projected coordinate reference system is used, the minimum and maximum
- * values need to be adjusted.
- */
- @ApiModelProperty(example = "[-180,-90,180,90]", value = "West, north, east, south edges of the "
- + "spatial extent. The minimum and maximum values apply to the coordinate reference system "
- + "WGS84 longitude/latitude that is supported in the Core. If, for example, a projected "
- + "coordinate reference system is used, the minimum and maximum values need to be adjusted.")
- public List getSpatial() {
- return spatial;
- }
-
- public void setSpatial(List spatial) {
- this.spatial = spatial;
- }
-
- public Extent trs(TrsEnum trs) {
- this.trs = trs;
- return this;
- }
-
- /**
- * Temporal reference system of the coordinates in the temporal extent (property `temporal`).
- * In the Core, only the Gregorian calendar is supported. Extensions may support additional
- * temporal reference systems.
- */
- @ApiModelProperty(value = "Temporal reference system of the coordinates in the temporal extent "
- + "(property `temporal`). In the Core, only the Gregorian calendar is supported. "
- + "Extensions may support additional temporal reference systems.")
- public TrsEnum getTrs() {
- return trs;
- }
-
- public void setTrs(TrsEnum trs) {
- this.trs = trs;
- }
-
- public Extent temporal(List temporal) {
- this.temporal = temporal;
- return this;
- }
-
- /**
- * Adds a temporal item.
- */
- public Extent addTemporalItem(String temporalItem) {
- if (this.temporal == null) {
- this.temporal = new ArrayList<>();
- }
- this.temporal.add(temporalItem);
- return this;
- }
-
- /**
- * Begin and end times of the temporal extent.
- */
- @ApiModelProperty(example = "[\"2011-11-11T12:22:11.000Z\",\"2012-11-24T12:32:43.000Z\"]",
- value = "Begin and end times of the temporal extent.")
- public List getTemporal() {
- return temporal;
- }
-
- public void setTemporal(List temporal) {
- this.temporal = temporal;
- }
-
-
- @Override
- public boolean equals(java.lang.Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- Extent extent = (Extent) o;
- return Objects.equals(this.crs, extent.crs)
- && Objects.equals(this.spatial, extent.spatial)
- && Objects.equals(this.trs, extent.trs)
- && Objects.equals(this.temporal, extent.temporal);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(crs, spatial, trs, temporal);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("class Extent {\n");
-
- sb.append(" crs: ").append(toIndentedString(crs)).append("\n");
- sb.append(" spatial: ").append(toIndentedString(spatial)).append("\n");
- sb.append(" trs: ").append(toIndentedString(trs)).append("\n");
- sb.append(" temporal: ").append(toIndentedString(temporal)).append("\n");
- sb.append("}");
- return sb.toString();
- }
-
- /**
- * Convert the given object to string with each line indented by 4 spaces
- * (except the first line).
- */
- private String toIndentedString(java.lang.Object o) {
- if (o == null) {
- return "null";
- }
- return o.toString().replace("\n", "\n ");
- }
-}
-
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Link.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Link.java
deleted file mode 100644
index 76d91c41..00000000
--- a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Link.java
+++ /dev/null
@@ -1,169 +0,0 @@
-package org.fao.geonet.ogcapi.records.controller.model;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
-import io.swagger.annotations.ApiModelProperty;
-import java.util.Objects;
-import javax.xml.bind.annotation.XmlAccessType;
-import javax.xml.bind.annotation.XmlAccessorType;
-import javax.xml.bind.annotation.XmlRootElement;
-
-/**
- * Link entity.
- */
-@JacksonXmlRootElement(localName = "Link")
-@XmlRootElement(name = "Link")
-@XmlAccessorType(XmlAccessType.FIELD)
-public class Link {
- @JsonProperty("href")
- @JacksonXmlProperty(localName = "href")
- private String href;
-
- @JsonProperty("rel")
- @JacksonXmlProperty(localName = "rel")
- private String rel;
-
- @JsonProperty("title")
- @JacksonXmlProperty(localName = "title")
- private String title;
-
- @JsonProperty("type")
- @JacksonXmlProperty(localName = "type")
- private String type;
-
- @JsonProperty("hreflang")
- @JacksonXmlProperty(localName = "hreflang")
- private String hreflang;
-
- public Link href(String href) {
- this.href = href;
- return this;
- }
-
- /**
- * Get href.
- * */
- @ApiModelProperty(required = true, value = "")
- public String getHref() {
- return href;
- }
-
- public void setHref(String href) {
- this.href = href;
- }
-
- public Link rel(String rel) {
- this.rel = rel;
- return this;
- }
-
- /**
- * Get rel.
- */
- @ApiModelProperty(example = "prev", value = "")
- public String getRel() {
- return rel;
- }
-
- public void setRel(String rel) {
- this.rel = rel;
- }
-
- public Link type(String type) {
- this.type = type;
- return this;
- }
-
- /**
- * Get title.
- */
- @ApiModelProperty(example = "The OpenAPI documentation", value = "")
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public Link title(String title) {
- this.title = title;
- return this;
- }
-
- /**
- * Get type.
- */
- @ApiModelProperty(example = "application/geo+json", value = "")
- public String getType() {
- return type;
- }
-
- public void setType(String type) {
- this.type = type;
- }
-
- public Link hreflang(String hreflang) {
- this.hreflang = hreflang;
- return this;
- }
-
- /**
- * Get hreflang.
- */
- @ApiModelProperty(example = "en", value = "")
- public String getHreflang() {
- return hreflang;
- }
-
- public void setHreflang(String hreflang) {
- this.hreflang = hreflang;
- }
-
-
- @Override
- public boolean equals(java.lang.Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- Link link = (Link) o;
- return Objects.equals(this.href, link.href)
- && Objects.equals(this.rel, link.rel)
- && Objects.equals(this.type, link.type)
- && Objects.equals(this.hreflang, link.hreflang);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(href, rel, type, hreflang);
- }
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append("class Link {\n");
-
- sb.append(" href: ").append(toIndentedString(href)).append("\n");
- sb.append(" rel: ").append(toIndentedString(rel)).append("\n");
- sb.append(" type: ").append(toIndentedString(type)).append("\n");
- sb.append(" hreflang: ").append(toIndentedString(hreflang)).append("\n");
- sb.append("}");
- return sb.toString();
- }
-
- /**
- * Convert the given object to string with each line indented by 4 spaces
- * (except the first line).
- */
- private String toIndentedString(java.lang.Object o) {
- if (o == null) {
- return "null";
- }
- return o.toString().replace("\n", "\n ");
- }
-}
-
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Root.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Root.java
index 521c9a10..0c1d4bf5 100644
--- a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Root.java
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Root.java
@@ -1,5 +1,7 @@
package org.fao.geonet.ogcapi.records.controller.model;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
@@ -10,17 +12,44 @@
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
+import org.fao.geonet.ogcapi.records.model.OgcApiLink;
/**
* Root entity.
+ *
+ * Used for the landing page.
*/
@JacksonXmlRootElement(localName = "Root")
@XmlRootElement(name = "Root")
@XmlAccessorType(XmlAccessType.FIELD)
public class Root {
+
+ /**
+ * This is the collection info for the main-portal.
+ *
+ *
THIS IS NON-STANDARD (not in the ogcapi spec)!
+ *
+ *
The landing page JSON also includes a new property systemInfo which contains a catalog.yaml
+ * object in it.
+ * I'm not sure if this is allowed in the spec, but it allows for more metadata about the
+ * entire GN system ("ogcapi-records" server). This is useful for making a nicer landing page
+ * (cf. pygeoapi's landing page).
+ */
+ @JsonProperty("systemInfo")
+ @JacksonXmlProperty(localName = "systemInfo")
+ @JsonInclude(Include.NON_EMPTY)
+ private CollectionInfo systemInfo;
+
+
@JsonProperty("title")
@JacksonXmlProperty(localName = "title")
private String title;
+ @JsonProperty("description")
+ @JacksonXmlProperty(localName = "description")
+ private String description;
+ @JsonProperty("links")
+ @JacksonXmlProperty(localName = "links")
+ private List links = new ArrayList<>();
/**
* Get title.
@@ -39,10 +68,6 @@ public Root title(String title) {
return this;
}
- @JsonProperty("description")
- @JacksonXmlProperty(localName = "description")
- private String description;
-
/**
* Get description.
*/
@@ -60,17 +85,12 @@ public Root description(String description) {
return this;
}
- @JsonProperty("links")
- @JacksonXmlProperty(localName = "links")
-
- private List links = new ArrayList<>();
-
- public Root links(List links) {
+ public Root links(List links) {
this.links = links;
return this;
}
- public Root addLinksItem(Link linksItem) {
+ public Root addLinksItem(OgcApiLink linksItem) {
this.links.add(linksItem);
return this;
}
@@ -79,14 +99,21 @@ public Root addLinksItem(Link linksItem) {
* Get links.
*/
@ApiModelProperty(example = "[{\"href\":\"http://data.example.org/\",\"rel\":\"self\",\"type\":\"application/json\",\"title\":\"this document\"},{\"href\":\"http://data.example.org/api\",\"rel\":\"service\",\"type\":\"application/openapi+json;version=3.0\",\"title\":\"the API definition\"},{\"href\":\"http://data.example.org/conformance\",\"rel\":\"conformance\",\"type\":\"application/json\",\"title\":\"OGC conformance classes implemented by this API\"},{\"href\":\"http://data.example.org/collections\",\"title\":\"Metadata about the resource collections\"}]", required = true, value = "")
- public List getLinks() {
+ public List getLinks() {
return links;
}
- public void setLinks(List links) {
+ public void setLinks(List links) {
this.links = links;
}
+ public CollectionInfo getSystemInfo() {
+ return systemInfo;
+ }
+
+ public void setSystemInfo(CollectionInfo systemInfo) {
+ this.systemInfo = systemInfo;
+ }
@Override
public boolean equals(java.lang.Object o) {
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/JsonItem.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/JsonItem.java
new file mode 100644
index 00000000..4edbdf81
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/JsonItem.java
@@ -0,0 +1,30 @@
+package org.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ * THIS IS SIMPLIFIED - SEE FULL SPECIFICATION.
+ *
+ * See the JSON Schema specification.
+ */
+public class JsonItem {
+
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "type")
+ @XmlElement(name = "type")
+ public String type;
+
+ //---------------------------------------------
+
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/JsonProperty.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/JsonProperty.java
new file mode 100644
index 00000000..0aa2f962
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/JsonProperty.java
@@ -0,0 +1,155 @@
+package org.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ * This is for Queryables. Its a json schema. cf https://json-schema.org/draft/2020-12/schema
+ *
+ *
https://json-schema.org/learn/miscellaneous-examples
+ *
+ *
example: https://demo.pycsw.org/gisdata/collections/metadata:main/queryables?f=json
+ *
+ *
THIS IS SIMPLIFIED - SEE FULL SPECIFICATION and JsonItem
+ */
+public class JsonProperty {
+
+ public static final String TypeString = "string";
+
+ //----------------------
+
+ /**
+ * https://json-schema.org/draft/2020-12/schema.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "title")
+ @XmlElement(name = "title")
+ public String title;
+
+ /**
+ * https://json-schema.org/draft/2020-12/schema.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "type")
+ @XmlElement(name = "type")
+ public String type;
+
+ /**
+ * https://json-schema.org/draft/2020-12/schema.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "description")
+ @XmlElement(name = "description")
+ public String description;
+
+ /**
+ * cf. https://docs.ogc.org/is/19-079r2/19-079r2.html.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "format")
+ @XmlElement(name = "format")
+ public String format;
+
+ /**
+ * cf. https://docs.ogc.org/is/19-079r2/19-079r2.html.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "enum")
+ @XmlElement(name = "enum")
+ @com.fasterxml.jackson.annotation.JsonProperty("enum")
+ public List enumeration;
+
+ /**
+ * cf. https://docs.ogc.org/is/19-079r2/19-079r2.html.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "x-ogc-role")
+ @XmlElement(name = "x-ogc-role")
+ @com.fasterxml.jackson.annotation.JsonProperty("x-ogc-role")
+ public String xxOgcRole;
+
+ /**
+ * cf. https://docs.ogc.org/is/19-079r2/19-079r2.html.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "items")
+ @XmlElement(name = "items")
+ public JsonItem items;
+
+ //----------------------------------
+
+ /**
+ * builds a minimal JsonProperty (part of json schema).
+ *
+ * @param type type of the property
+ * @param title title of the property
+ * @param description description of the property
+ */
+ public JsonProperty(String type, String title, String description) {
+ this.type = type;
+ this.title = title;
+ this.description = description;
+ }
+
+ //----------------------------------
+
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getFormat() {
+ return format;
+ }
+
+ public void setFormat(String format) {
+ this.format = format;
+ }
+
+ public List getEnum() {
+ return enumeration;
+ }
+
+ public void setEnum(List enumeration) {
+ this.enumeration = enumeration;
+ }
+
+ public String getxOgcRole() {
+ return xxOgcRole;
+ }
+
+ public void setxOgcRole(String xxOgcRole) {
+ this.xxOgcRole = xxOgcRole;
+ }
+
+ public JsonItem getItems() {
+ return items;
+ }
+
+ public void setItems(JsonItem items) {
+ this.items = items;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/JsonSchema.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/JsonSchema.java
new file mode 100644
index 00000000..fad8602f
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/JsonSchema.java
@@ -0,0 +1,116 @@
+package org.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import java.util.Map;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ * This is for Queryables. Its a json schema. cf https://json-schema.org/draft/2020-12/schema
+ *
+ * example: https://demo.pycsw.org/gisdata/collections/metadata:main/queryables?f=json
+ */
+public class JsonSchema {
+
+ /**
+ * The property $schema is https://json-schema.org/draft/2020-12/schema.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "$schema")
+ @XmlElement(name = "$schema")
+ @com.fasterxml.jackson.annotation.JsonProperty("$schema")
+ public String schema = "https://json-schema.org/draft/2020-12/schema";
+
+ /**
+ * The type is object and each property is a queryable.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "type")
+ @XmlElement(name = "type")
+ public String type = "object";
+
+ /**
+ * The property $id is the URI of the resource without query parameters.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "id")
+ @XmlElement(name = "id")
+ public String id;
+
+ /**
+ * The property $id is the URI of the resource without query parameters.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "title")
+ @XmlElement(name = "title")
+ public String title = "Queryables for GeoNetwork Collection";
+
+ /**
+ * https://json-schema.org/draft/2020-12/schema.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "description")
+ @XmlElement(name = "description")
+ public String description;
+
+
+ /**
+ * The properties for this schema.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "properties")
+ @XmlElement(name = "properties")
+ public Map properties;
+
+ //----------------------------------------------
+
+
+ public String getSchema() {
+ return schema;
+ }
+
+ public void setSchema(String schema) {
+ this.schema = schema;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Map getProperties() {
+ return properties;
+ }
+
+ public void setProperties(Map properties) {
+ this.properties = properties;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiAddress.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiAddress.java
new file mode 100644
index 00000000..25bdfa0d
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiAddress.java
@@ -0,0 +1,126 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * see "address" in https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/contact.yaml
+ *
+ * Physical location at which contact can be made.
+ */
+@XmlRootElement(name = "address")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class OgcApiAddress {
+
+ /**
+ * Address lines for the location.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "deliveryPoint")
+ @XmlElement(name = "deliveryPoint")
+ public List deliveryPoint = new ArrayList<>();
+
+ /**
+ * City for the location.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "city")
+ @XmlElement(name = "city")
+ public String city;
+
+ /**
+ * State or province of the location.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "administrativeArea")
+ @XmlElement(name = "administrativeArea")
+ public String administrativeArea;
+
+ /**
+ * ZIP or other postal code.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "postalCode")
+ @XmlElement(name = "postalCode")
+ public String postalCode;
+
+ /**
+ * Country of the physical address. ISO 3166-1 is recommended.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "country")
+ @XmlElement(name = "country")
+ public String country;
+
+ /**
+ * The type of address (e.g. office, home, etc.).
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "roles")
+ @XmlElement(name = "roles")
+ public List roles = new ArrayList<>();
+
+ public OgcApiAddress(String address) {
+ deliveryPoint.add(address);
+ }
+
+ public List getDeliveryPoint() {
+ return deliveryPoint;
+ }
+
+ public void setDeliveryPoint(List deliveryPoint) {
+ this.deliveryPoint = deliveryPoint;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getAdministrativeArea() {
+ return administrativeArea;
+ }
+
+ public void setAdministrativeArea(String administrativeArea) {
+ this.administrativeArea = administrativeArea;
+ }
+
+ public String getPostalCode() {
+ return postalCode;
+ }
+
+ public void setPostalCode(String postalCode) {
+ this.postalCode = postalCode;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List roles) {
+ this.roles = roles;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiComponent.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiComponent.java
new file mode 100644
index 00000000..ed0e4be7
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiComponent.java
@@ -0,0 +1,18 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+/**
+ * OgcApiComponent.
+ *
+ * cf. https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/schema.yaml
+ *
+ *
TODO - This is a very complex object with many pattern properties. I've left this empty
+ * for now. When this is needed, please fill in (also, include the x-* properties used elsewhere).
+ */
+public class OgcApiComponent {
+
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiConcept.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiConcept.java
new file mode 100644
index 00000000..7d8f1918
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiConcept.java
@@ -0,0 +1,92 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+
+/**
+ * cf. https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/theme.yaml
+ *
+ *
Entity/concept identifiers from this knowledge
+ * system. it is recommended that a resolvable URI be used for each entity/concept identifier.
+ */
+public class OgcApiConcept {
+
+
+ /**
+ * An identifier for the concept.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "id")
+ @XmlElement(name = "id")
+ public String id;
+
+ /**
+ * A human readable title for the concept.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "title")
+ @XmlElement(name = "title")
+ public String title;
+
+ /**
+ * A human readable description for the concept.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "description")
+ @XmlElement(name = "description")
+ public String description;
+
+ /**
+ * A URI providing further description of the concept. format: uri
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "url")
+ @XmlElement(name = "url")
+ public String url;
+
+ public OgcApiConcept(String id, String url) {
+ this.id = id;
+ this.url = url;
+ }
+
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiContact.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiContact.java
new file mode 100644
index 00000000..02cafb8f
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiContact.java
@@ -0,0 +1,345 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+import org.fao.geonet.index.model.gn.Contact;
+import org.fao.geonet.ogcapi.records.util.JsonUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/contact.yaml
+ *
+ *
Identification of, and means of communication with, person responsible
+ * for the resource.
+ */
+@XmlRootElement(name = "contact")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class OgcApiContact {
+
+ /**
+ * A value uniquely identifying a contact.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "identifier")
+ @XmlElement(name = "identifier")
+ private String identifier;
+
+ /**
+ * The name of the responsible person.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "name")
+ @XmlElement(name = "name")
+ private String name;
+
+ /**
+ * The name of the role or position of the responsible person taken from the organization's formal
+ * organizational hierarchy or chart.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "position")
+ @XmlElement(name = "position")
+ private String position;
+
+ /**
+ * Organization/affiliation of the contact.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "organization")
+ @XmlElement(name = "organization")
+ private String organization;
+
+ /**
+ * Graphic identifying a contact. The link relation should be `icon` and the media type should be
+ * an image media type.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "logo")
+ @XmlElement(name = "logo")
+ private OgcApiLink logo;
+
+ /**
+ * Telephone numbers at which contact can be made.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "phones")
+ @XmlElement(name = "phones")
+ private List phones = new ArrayList<>();
+
+ /**
+ * Email addresses at which contact can be made.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "emails")
+ @XmlElement(name = "emails")
+ private List emails = new ArrayList<>();
+
+ /**
+ * Physical location at which contact can be made.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "addresses")
+ @XmlElement(name = "addresses")
+ private List addresses = new ArrayList<>();
+
+ /**
+ * On-line information about the contact.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "links")
+ @XmlElement(name = "links")
+ private List links = new ArrayList<>();
+
+ /**
+ * Time period when the contact can be contacted.
+ *
+ * example: "Hours: Mo-Fr 10am-7pm Sa 10am-22pm Su 10am-21pm"
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "hoursOfService")
+ @XmlElement(name = "hoursOfService")
+ private String hoursOfService;
+
+ /**
+ * Supplemental instructions on how or when to contact the responsible party.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "contactInstructions")
+ @XmlElement(name = "contactInstructions")
+ private String contactInstructions;
+
+ /**
+ * The set of named duties, job functions and/or permissions associated with this contact. (e.g.
+ * developer, administrator, etc.).
+ */
+ @JsonInclude(JsonInclude.Include.NON_NULL)
+ @XmlElementWrapper(name = "roles")
+ @XmlElement(name = "roles")
+ private List roles = new ArrayList<>();
+
+ public OgcApiContact() {
+ }
+
+
+ /**
+ * create from the Elastic Index JSON node for the contact.
+ *
+ * @param contactInfo from parsed Elastic Index for a single contact
+ * @return parsed contact
+ */
+ public static OgcApiContact fromIndexMap(Contact contactInfo) {
+ if (contactInfo == null) {
+ return null;
+ }
+ OgcApiContact result = new OgcApiContact();
+ if (StringUtils.hasText(contactInfo.getRole())) {
+ result.getRoles().add(contactInfo.getRole());
+ }
+ if (contactInfo.getOrganisation() != null && !contactInfo.getOrganisation().isEmpty()) {
+ result.setOrganization(
+ JsonUtils.getLangString(contactInfo.getOrganisation()));
+ }
+ if (StringUtils.hasText(contactInfo.getEmail())) {
+ result.getEmails().add(new OgcApiEmail(contactInfo.getEmail()));
+ }
+
+ if (StringUtils.hasText(contactInfo.getPosition())) {
+ result.setPosition(contactInfo.getPosition());
+ }
+
+ if (StringUtils.hasText(contactInfo.getIndividual())) {
+ result.setName(contactInfo.getIndividual());
+ }
+
+ if (StringUtils.hasText(contactInfo.getPosition())) {
+ result.setPosition(contactInfo.getPosition());
+ }
+
+ if (StringUtils.hasText(contactInfo.getPhone())) {
+ result.getPhones().add(new OgcApiPhone(contactInfo.getPhone()));
+ }
+
+ if (StringUtils.hasText(contactInfo.getAddress())) {
+ result.getAddresses().add(new OgcApiAddress(contactInfo.getAddress()));
+ }
+
+ if (StringUtils.hasText(contactInfo.getLogo())) {
+ var logo = new OgcApiLink(contactInfo.getLogo(), "image/*");
+ logo.setRel("icon");
+ result.setLogo(logo);
+ }
+
+ if (StringUtils.hasText(contactInfo.getWebsite())) {
+ var website = new OgcApiLink(contactInfo.getWebsite(), "text/html");
+ result.getLinks().add(website);
+ }
+
+ return result;
+ }
+
+ /**
+ * create from the Elastic Index JSON node for the contact.
+ *
+ * @param contactMap from Elastic Index for a single contact
+ * @return parsed contact
+ */
+ public static OgcApiContact fromIndexMap(Map contactMap) {
+ if (contactMap == null) {
+ return null;
+ }
+ OgcApiContact result = new OgcApiContact();
+ if (StringUtils.hasText((String) contactMap.get("role"))) {
+ result.getRoles().add(contactMap.get("role").toString());
+ }
+ if (contactMap.get("organisationObject") != null) {
+ result.setOrganization(
+ JsonUtils.getLangString(contactMap.get("organisationObject")));
+ }
+ if (StringUtils.hasText((String) contactMap.get("email"))) {
+ result.getEmails().add(new OgcApiEmail(contactMap.get("email").toString()));
+ }
+
+ if (StringUtils.hasText((String) contactMap.get("position"))) {
+ result.setPosition(contactMap.get("position").toString());
+ }
+
+ if (StringUtils.hasText((String) contactMap.get("individual"))) {
+ result.setName(contactMap.get("individual").toString());
+ }
+
+ if (StringUtils.hasText((String) contactMap.get("position"))) {
+ result.setPosition(contactMap.get("position").toString());
+ }
+
+ if (StringUtils.hasText((String) contactMap.get("phone"))) {
+ result.getPhones().add(new OgcApiPhone(contactMap.get("phone").toString()));
+ }
+
+ if (StringUtils.hasText((String) contactMap.get("address"))) {
+ result.getAddresses().add(new OgcApiAddress(contactMap.get("address").toString()));
+ }
+
+ if (StringUtils.hasText((String) contactMap.get("logo"))) {
+ var logo = new OgcApiLink(contactMap.get("logo").toString(), "image/*");
+ logo.setRel("icon");
+ result.setLogo(logo);
+ }
+
+ if (StringUtils.hasText((String) contactMap.get("website"))) {
+ var website = new OgcApiLink(contactMap.get("website").toString(), "text/html");
+ result.getLinks().add(website);
+ }
+
+ return result;
+ }
+
+
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ public void setIdentifier(String identifier) {
+ this.identifier = identifier;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getPosition() {
+ return position;
+ }
+
+ public void setPosition(String position) {
+ this.position = position;
+ }
+
+ public String getOrganization() {
+ return organization;
+ }
+
+ public void setOrganization(String organization) {
+ this.organization = organization;
+ }
+
+ public OgcApiLink getLogo() {
+ return logo;
+ }
+
+ public void setLogo(OgcApiLink logo) {
+ this.logo = logo;
+ }
+
+ public List getPhones() {
+ return phones;
+ }
+
+ public void setPhones(List phones) {
+ this.phones = phones;
+ }
+
+ public List getEmails() {
+ return emails;
+ }
+
+ public void setEmails(List emails) {
+ this.emails = emails;
+ }
+
+ public List getAddresses() {
+ return addresses;
+ }
+
+ public void setAddresses(List addresses) {
+ this.addresses = addresses;
+ }
+
+ public List getLinks() {
+ return links;
+ }
+
+ public void setLinks(List links) {
+ this.links = links;
+ }
+
+ public String getHoursOfService() {
+ return hoursOfService;
+ }
+
+ public void setHoursOfService(String hoursOfService) {
+ this.hoursOfService = hoursOfService;
+ }
+
+ public String getContactInstructions() {
+ return contactInstructions;
+ }
+
+ public void setContactInstructions(String contactInstructions) {
+ this.contactInstructions = contactInstructions;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List roles) {
+ this.roles = roles;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiEmail.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiEmail.java
new file mode 100644
index 00000000..6d84cdb4
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiEmail.java
@@ -0,0 +1,64 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+
+
+/**
+ * cf. https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/contact.yaml
+ *
+ * Email addresses at which contact can be made.
+ */
+@XmlRootElement(name = "email")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class OgcApiEmail {
+
+ /**
+ * The value is the email number itself.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "value")
+ @XmlElement(name = "value")
+ private String value;
+
+ /**
+ * The type of email (e.g. home, work, etc.).
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "roles")
+ @XmlElement(name = "roles")
+ private List roles = new ArrayList<>();
+
+ public OgcApiEmail(String email) {
+ value = email;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List roles) {
+ this.roles = roles;
+ }
+}
+
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiExtent.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiExtent.java
new file mode 100644
index 00000000..e92fcdde
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiExtent.java
@@ -0,0 +1,63 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ * https://schemas.opengis.net/ogcapi/features/part1/1.0/openapi/schemas/extent.yaml
+ *
+ * The extent of the features in the collection. In the Core only spatial and temporal extents
+ * are specified. Extensions may add additional members to represent other extents, for example,
+ * thermal or pressure ranges.
+ *
+ *
An array of extents is provided for each extent type (spatial, temporal). The first item in
+ * the array describes the overall extent of the data. All subsequent items describe more precise
+ * extents, e.g., to identify clusters of data. Clients only interested in the overall extent will
+ * only need to access the first extent in the array.
+ */
+public class OgcApiExtent {
+
+ /**
+ * The spatial extent of the features in the collection.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "spatial")
+ @XmlElement(name = "spatial")
+ public OgcApiSpatialExtent spatial;
+
+ /**
+ * The temporal extent of the features in the collection.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "temporal")
+ @XmlElement(name = "temporal")
+ public OgcApiTemporalExtent temporal;
+
+ public OgcApiExtent(OgcApiSpatialExtent spatial, OgcApiTemporalExtent temporal) {
+ this.spatial = spatial;
+ this.temporal = temporal;
+ }
+
+ public OgcApiSpatialExtent getSpatial() {
+ return spatial;
+ }
+
+ public void setSpatial(OgcApiSpatialExtent spatial) {
+ this.spatial = spatial;
+ }
+
+ public OgcApiTemporalExtent getTemporal() {
+ return temporal;
+ }
+
+ public void setTemporal(OgcApiTemporalExtent temporal) {
+ this.temporal = temporal;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiExternalDocumentation.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiExternalDocumentation.java
new file mode 100644
index 00000000..8ce6ae23
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiExternalDocumentation.java
@@ -0,0 +1,58 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ * OgcApiExternalDocumentation.
+ *
+ *
cf. https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/schema.yaml
+ *
+ *
I haven't included the "x-*" pattern properties.
+ */
+public class OgcApiExternalDocumentation {
+
+ /**
+ * undefined in spec.
+ *
+ *
format: uri-reference
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "url")
+ @XmlElement(name = "url")
+ private String url;
+
+ /**
+ * undefined in spec.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "description")
+ @XmlElement(name = "description")
+ private String description;
+
+ //------------------------------------------
+
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiInfo.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiInfo.java
new file mode 100644
index 00000000..66197701
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiInfo.java
@@ -0,0 +1,72 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+
+/**
+ * cf https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/schema.yaml
+ *
+ *
I haven't included the "x-*" pattern properties.
+ */
+public class OgcApiInfo {
+
+ /**
+ * undefined in spec.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "title")
+ @XmlElement(name = "title")
+ private String title;
+
+ /**
+ * undefined in spec.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "description")
+ @XmlElement(name = "description")
+ private String description;
+
+ /**
+ * undefined in spec.
+ *
+ *
format: uri-reference
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "termsOfService")
+ @XmlElement(name = "termsOfService")
+ private String termsOfService;
+
+ /**
+ * undefined in spec.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "contact")
+ @XmlElement(name = "contact")
+ private OgcApiSchemaContact contact;
+
+
+ /**
+ * undefined in spec.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "license")
+ @XmlElement(name = "license")
+ private OgcApiLicense license;
+
+ /**
+ * undefined in spec.
+ *
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "version")
+ @XmlElement(name = "version")
+ private String version;
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiLanguage.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiLanguage.java
new file mode 100644
index 00000000..ba52a986
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiLanguage.java
@@ -0,0 +1,93 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ * cf. https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/language.yaml
+ *
+ *
The language used for textual values in this record.
+ */
+public class OgcApiLanguage {
+
+ /**
+ * The language tag as per RFC-5646. example: el
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "code")
+ @XmlElement(name = "code")
+ public String code;
+
+ /**
+ * The untranslated name of the language. example: Ελληνικά
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "name")
+ @XmlElement(name = "name")
+ public String name;
+
+ /**
+ * The name of the language in another well-understood language, usually English. example: Greek
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "alternate")
+ @XmlElement(name = "alternate")
+ public String alternate;
+
+
+ /**
+ * The direction for text in this language. The default, `ltr` (left-to-right), represents the
+ * most common situation. However, care should be taken to set the value of `dir` appropriately if
+ * the language direction is not `ltr`. Other values supported are `rtl` (right-to-left), `ttb`
+ * (top-to-bottom), and `btt` (bottom-to-top). enum: - ltr - rtl - ttb - btt
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "dir")
+ @XmlElement(name = "dir")
+ public String dir;
+
+
+ public OgcApiLanguage(String code) {
+ this.code = code;
+ }
+
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setCode(String code) {
+ this.code = code;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getAlternate() {
+ return alternate;
+ }
+
+ public void setAlternate(String alternate) {
+ this.alternate = alternate;
+ }
+
+ public String getDir() {
+ return dir;
+ }
+
+ public void setDir(String dir) {
+ this.dir = dir;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiLicense.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiLicense.java
new file mode 100644
index 00000000..8ead1968
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiLicense.java
@@ -0,0 +1,58 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ * OgcApiLicense.
+ *
+ *
cf https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/schema.yaml
+ *
+ *
I haven't included the "x-*" pattern properties.
+ */
+public class OgcApiLicense {
+
+ /**
+ * undefined in spec.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "name")
+ @XmlElement(name = "name")
+ private String name;
+
+
+ /**
+ * undefined in spec.
+ *
+ *
format: uri-reference
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "url")
+ @XmlElement(name = "url")
+ private String url;
+
+ //-----------------------------------------------------
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiLink.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiLink.java
new file mode 100644
index 00000000..69897aac
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiLink.java
@@ -0,0 +1,252 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import java.util.Objects;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/linkBase.yaml
+ *
+ *
also see: https://schemas.opengis.net/ogcapi/features/part1/1.0/openapi/schemas/link.yaml
+ *
+ *
NOTE:
+ * These specs aren't really consistent. The first:
+ * a) doesn't have a href (this is obviously a mistake).
+ * b) has a created/updated field (not in the other spec). Recommend not using this.
+ *
+ *
Note - there is a length field, but its value isn't defined. Recommend not using this.
+ *
+ *
Represents a link.
+ */
+@XmlRootElement(name = "link")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class OgcApiLink {
+
+ /**
+ * The type or semantics of the relation. example: alternate
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "rel")
+ @XmlElement(name = "rel")
+ private String rel;
+
+ /**
+ * A hint indicating what the media type of the result of dereferencing the link should be.
+ * example: application/geo+json
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "type")
+ @XmlElement(name = "type")
+ private String type;
+
+ /**
+ * A hint indicating what the language of the result of dereferencing the link should be. example:
+ * en
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "hreflang")
+ @XmlElement(name = "hreflang")
+ private String hreflang;
+
+ /**
+ * Used to label the destination of a link such that it can be used as a human-readable
+ * identifier. example: "Trierer Strasse 70, 53115 Bonn"
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "title")
+ @XmlElement(name = "title")
+ private String title;
+
+ /**
+ * undocumented in spec.
+ *
+ *
This probably shouldn't be used because its not defined.
+ */
+ @JsonInclude(Include.NON_DEFAULT)
+ @XmlElementWrapper(name = "length")
+ @XmlElement(name = "length")
+ private Integer length;
+
+ /**
+ * Date of creation of the resource pointed to by the link. format: date-time
+ *
+ *
This is only is one of the link specifications. This probably shouldn't be used.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "created")
+ @XmlElement(name = "created")
+ private String created;
+
+ /**
+ * description: Most recent date on which the resource pointed to by the link was changed. format:
+ * date-time
+ *
+ *
This is only is one of the link specifications. This probably shouldn't be used.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "updated")
+ @XmlElement(name = "updated")
+ private String updated;
+
+ /**
+ * example: http://data.example.com/buildings/123
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "href")
+ @XmlElement(name = "href")
+ private String href;
+
+
+ public OgcApiLink() {
+
+ }
+
+ public OgcApiLink(String href, String type) {
+ this.href = href;
+ this.type = type;
+ }
+
+
+ public String getRel() {
+ return rel;
+ }
+
+ public void setRel(String rel) {
+ this.rel = rel;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getHreflang() {
+ return hreflang;
+ }
+
+ public void setHreflang(String hreflang) {
+ this.hreflang = hreflang;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public Integer getLength() {
+ return length;
+ }
+
+ public void setLength(Integer length) {
+ this.length = length;
+ }
+
+ public String getCreated() {
+ return created;
+ }
+
+ public void setCreated(String created) {
+ this.created = created;
+ }
+
+ public String getUpdated() {
+ return updated;
+ }
+
+ public void setUpdated(String updated) {
+ this.updated = updated;
+ }
+
+ public String getHref() {
+ return href;
+ }
+
+ public void setHref(String href) {
+ this.href = href;
+ }
+
+
+ @Override
+ public boolean equals(java.lang.Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ OgcApiLink link = (OgcApiLink) o;
+ return Objects.equals(this.href, link.getHref())
+ && Objects.equals(this.rel, link.getRel())
+ && Objects.equals(this.type, link.getType())
+ && Objects.equals(this.hreflang, link.getHreflang())
+
+ && Objects.equals(this.created, link.getCreated())
+ && Objects.equals(this.updated, link.getUpdated())
+ && Objects.equals(this.length, link.getLength());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(href, rel, type, hreflang);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("class Link {\n");
+
+ sb.append(" href: ").append(toIndentedString(href)).append("\n");
+ sb.append(" rel: ").append(toIndentedString(rel)).append("\n");
+ sb.append(" type: ").append(toIndentedString(type)).append("\n");
+ sb.append(" hreflang: ").append(toIndentedString(hreflang)).append("\n");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ /**
+ * Convert the given object to string with each line indented by 4 spaces (except the first
+ * line).
+ */
+ private String toIndentedString(java.lang.Object o) {
+ if (o == null) {
+ return "null";
+ }
+ return o.toString().replace("\n", "\n ");
+ }
+
+ public OgcApiLink href(String s) {
+ setHref(s);
+ return this;
+ }
+
+ public OgcApiLink title(String s) {
+ setTitle(s);
+ return this;
+ }
+
+ public OgcApiLink rel(String s) {
+ setRel(s);
+ return this;
+ }
+
+ public OgcApiLink type(String s) {
+ setType(s);
+ return this;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiPath.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiPath.java
new file mode 100644
index 00000000..21d50493
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiPath.java
@@ -0,0 +1,19 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+
+/**
+ * OgcApiPath.
+ *
+ *
https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/schema.yaml
+ *
+ *
TODO -This is a very complex object with many pattern properties. I've left this empty for
+ * now. When this is needed, please fill in (also, include the x-* properties used elsewhere).
+ */
+public class OgcApiPath {
+
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiPhone.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiPhone.java
new file mode 100644
index 00000000..e92b0f4f
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiPhone.java
@@ -0,0 +1,58 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import java.util.ArrayList;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * cf. https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/contact.yaml
+ *
+ *
Represents the Contact's phone number.
+ */
+@XmlRootElement(name = "phone")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class OgcApiPhone {
+
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "value")
+ @XmlElement(name = "value")
+ private String value;
+
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "roles")
+ @XmlElement(name = "roles")
+ private List roles = new ArrayList<>();
+
+
+ public OgcApiPhone(String phoneNumber) {
+ value = phoneNumber;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public List getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List roles) {
+ this.roles = roles;
+ }
+
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiReference.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiReference.java
new file mode 100644
index 00000000..95574c6b
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiReference.java
@@ -0,0 +1,37 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ * OgcApiReference.
+ * cf https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/schema.yaml
+ */
+public class OgcApiReference {
+
+ /**
+ * format: uri-reference.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "ref")
+ @XmlElement(name = "ref$")
+ public String ref;
+
+ //------------------------------------------------
+
+
+ public String getRef() {
+ return ref;
+ }
+
+ public void setRef(String ref) {
+ this.ref = ref;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSchema.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSchema.java
new file mode 100644
index 00000000..6d149c5b
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSchema.java
@@ -0,0 +1,154 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import java.util.List;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ * https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/schema.yaml
+ *
+ * A scheme related to this catalog.
+ *
+ *
The description of OpenAPI v3.0.x documents, as defined by
+ * https://spec.openapis.org/oas/v3.0.3
+ *
+ *
NOTE - THIS ISN'T WELL DEFINED IN THE SPECIFICATION (OR .yaml).
+ * I've done my best.
+ *
+ *
I haven't included the "x-*" pattern properties
+ *
+ *
NOTE - THE SCHEMA OBJECT ISN'T FULLY DESCRIBED IN THE SUB-PROPERTIES SINCE THEY CONTAIN
+ * DIFFERENT TYPES OF PATTERN PROPERTIES
+ *
+ *
TODO - when these objects are needed (currently not). Please go through the spec and
+ * .yaml to determine what's needed and that all the objects are fully filled out.
+ */
+@XmlRootElement(name = "schema")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class OgcApiSchema {
+
+ /**
+ * Undefined in the spec. Likely the openapi version number.
+ *
+ *
pattern: ^3\.0\.\d(-.+)?$
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "openapi")
+ @XmlElement(name = "openapi")
+ private String openapi;
+
+
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "info")
+ @XmlElement(name = "info")
+ private OgcApiInfo info;
+
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "externalDocs")
+ @XmlElement(name = "externalDocs")
+ private OgcApiExternalDocumentation externalDocs;
+
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "servers")
+ @XmlElement(name = "servers")
+ private List servers;
+
+
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "security")
+ @XmlElement(name = "security")
+ private List security;
+
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "tags")
+ @XmlElement(name = "tags")
+ private List tags;
+
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "paths")
+ @XmlElement(name = "paths")
+ private OgcApiPath paths;
+
+
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "components")
+ @XmlElement(name = "components")
+ private OgcApiComponent components;
+
+ //---------------------------------------------------------------------
+
+
+ public String getOpenapi() {
+ return openapi;
+ }
+
+ public void setOpenapi(String openapi) {
+ this.openapi = openapi;
+ }
+
+ public OgcApiInfo getInfo() {
+ return info;
+ }
+
+ public void setInfo(OgcApiInfo info) {
+ this.info = info;
+ }
+
+ public OgcApiExternalDocumentation getExternalDocs() {
+ return externalDocs;
+ }
+
+ public void setExternalDocs(OgcApiExternalDocumentation externalDocs) {
+ this.externalDocs = externalDocs;
+ }
+
+ public List getServers() {
+ return servers;
+ }
+
+ public void setServers(List servers) {
+ this.servers = servers;
+ }
+
+ public List getSecurity() {
+ return security;
+ }
+
+ public void setSecurity(List security) {
+ this.security = security;
+ }
+
+ public List getTags() {
+ return tags;
+ }
+
+ public void setTags(List tags) {
+ this.tags = tags;
+ }
+
+ public OgcApiPath getPaths() {
+ return paths;
+ }
+
+ public void setPaths(OgcApiPath paths) {
+ this.paths = paths;
+ }
+
+ public OgcApiComponent getComponents() {
+ return components;
+ }
+
+ public void setComponents(OgcApiComponent components) {
+ this.components = components;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSchemaContact.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSchemaContact.java
new file mode 100644
index 00000000..c2a425e3
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSchemaContact.java
@@ -0,0 +1,76 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ * This is different (simpler) from the "normal" OgcApiContact.
+ *
+ * cf. https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/schema.yaml
+ *
+ *
I haven't included the "x-*" pattern properties.
+ */
+public class OgcApiSchemaContact {
+
+ /**
+ * undefined in spec.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "name")
+ @XmlElement(name = "name")
+ private String name;
+
+ /**
+ * undefined in spec.
+ *
+ *
format: uri-reference
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "url")
+ @XmlElement(name = "url")
+ private String url;
+
+ /**
+ * undefined in spec.
+ *
+ *
format: email
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "email")
+ @XmlElement(name = "email")
+ private String email;
+
+ //-----------------------------------------------------
+
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSecurityRequirement.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSecurityRequirement.java
new file mode 100644
index 00000000..bb8bafdb
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSecurityRequirement.java
@@ -0,0 +1,39 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ * OgcApiSecurityRequirement.
+ *
+ *
https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/schema.yaml
+ */
+public class OgcApiSecurityRequirement {
+
+ /**
+ * undefined in spec.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "items")
+ @XmlElement(name = "items")
+ private List items;
+
+ //------------------------------------------
+
+
+ public List getItems() {
+ return items;
+ }
+
+ public void setItems(List items) {
+ this.items = items;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiServer.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiServer.java
new file mode 100644
index 00000000..f21f24b5
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiServer.java
@@ -0,0 +1,59 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ * OgcApiServer.
+ *
+ * https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/schema.yaml
+ *
+ *
I haven't included the "x-*" pattern properties.
+ *
+ *
I haven't included the variables ("ServerVariable") additional properties.
+ */
+public class OgcApiServer {
+
+ /**
+ * undefined in spec.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "url")
+ @XmlElement(name = "url")
+ private String url;
+
+ /**
+ * undefined in spec.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "description")
+ @XmlElement(name = "description")
+ private String description;
+
+
+ //----------------------------------------------------------
+
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSpatialExtent.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSpatialExtent.java
new file mode 100644
index 00000000..35d62da5
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSpatialExtent.java
@@ -0,0 +1,155 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import static org.fao.geonet.ogcapi.records.util.JsonUtils.getAsDouble;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+
+/**
+ * cf. https://github.com/opengeospatial/ogcapi-features/blob/master/core/openapi/schemas/extent.yaml
+ *
+ *
The spatial extent of the features in the collection.
+ */
+public class OgcApiSpatialExtent {
+
+ public static final String CRS84 = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
+ public static final String CRS84h = "http://www.opengis.net/def/crs/OGC/0/CRS84h";
+
+ /**
+ * One or more bounding boxes that describe the spatial extent of the dataset. In the Core only a
+ * single bounding box is supported.
+ *
+ *
Extensions may support additional areas. The first bounding box describes the overall
+ * spatial extent of the data. All subsequent bounding boxes describe more precise bounding boxes,
+ * e.g.,to identify clusters of data. Clients only interested in the overall spatial extent will
+ * only need to access the first bounding box in the array.
+ *
+ *
----
+ *
+ *
Each bounding box is provided as four or six numbers, depending on whether the coordinate
+ * reference system includes a vertical axis (height or depth):
+ *
+ *
Lower left corner, coordinate axis 1 Lower left corner, coordinate axis 2 Minimum value,
+ * coordinate axis 3 (optional) Upper right corner, coordinate axis 1 Upper right corner,
+ * coordinate axis 2 Maximum value, coordinate axis 3 (optional)
+ *
+ *
If the value consists of four numbers, the coordinate reference system is WGS 84
+ * longitude/latitude (http://www.opengis.net/def/crs/OGC/1.3/CRS84) unless a different coordinate
+ * reference system is specified in `crs`.
+ *
+ *
If the value consists of six numbers, the coordinate reference system is WGS 84
+ * longitude/latitude/ellipsoidal height (http://www.opengis.net/def/crs/OGC/0/CRS84h) unless a
+ * different coordinate reference system is specified in `crs`.
+ *
+ *
For WGS 84 longitude/latitude the values are in most cases the sequence of minimum
+ * longitude, minimum latitude, maximum longitude and maximum latitude. However, in cases where
+ * the box spans the antimeridian the first value (west-most box edge) is larger than the third
+ * value (east-most box edge).
+ *
+ *
If the vertical axis is included, the third and the sixth number are the bottom and the
+ * top of the 3-dimensional bounding box.
+ *
+ *
If a feature has multiple spatial geometry properties, it is the decision of the server
+ * whether only a single spatial geometry property is used to determine the extent or all relevant
+ * geometries.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "bbox")
+ @XmlElement(name = "bbox")
+ public List bbox = new ArrayList<>();
+ /**
+ * Coordinate reference system of the coordinates in the spatial extent (property `bbox`). The
+ * default reference system is WGS 84 longitude/latitude. In the Core the only other supported
+ * coordinate reference system is WGS 84 longitude/latitude/ellipsoidal height for coordinates
+ * with height. Extensions may support additional coordinate reference systems and add additional
+ * enum values.
+ *
+ * enum: - 'http://www.opengis.net/def/crs/OGC/1.3/CRS84' - 'http://www.opengis.net/def/crs/OGC/0/CRS84h'
+ * default: 'http://www.opengis.net/def/crs/OGC/1.3/CRS84'
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "crs")
+ @XmlElement(name = "crs")
+ public String crs = CRS84;
+
+ /**
+ * Create OgcApiSpatialExtent from the "geom" section of the GN Index record. Typically this is a
+ * polygon:
+ *
+ *
"geom":[{"type":"Polygon","coordinates":
+ * [[[37.0,-3.0],[156.0,-3.0],[156.0,83.0],[37.0,83.0],[37.0,-3.0]]]}]
+ *
+ *
you should call this method with the {"type":"Polygon",
+ * "coordinates":[[[37.0,-3.0],[156.0,-3.0],[156.0,83.0],[37.0,83.0],[37.0,-3.0]]]} object
+ *
+ * @param map from the GN Index Record JSON
+ * @return parsed OgcApiSpatialExtent
+ */
+ public static OgcApiSpatialExtent fromGnIndexRecord(Map map) {
+ if (map == null
+ || !map.containsKey("type")
+ || !map.get("type").equals("Polygon")
+ || !map.containsKey("coordinates")
+ || !(map.get("coordinates") instanceof List)) {
+ return null;
+ }
+ var coords = (List) ((List) map.get("coordinates")).get(0);
+
+ double xmin = Double.MAX_VALUE;
+ double ymin = Double.MAX_VALUE;
+ double xmax = Double.MIN_VALUE;
+ double ymax = Double.MIN_VALUE;
+
+ for (var myCoord : coords) {
+ var coord = (List) myCoord;
+ var x = getAsDouble(coord.get(0));
+ var y = getAsDouble(coord.get(1));
+ if (x < xmin) {
+ xmin = x;
+ }
+ if (y < ymin) {
+ ymin = y;
+ }
+ if (x > xmax) {
+ xmax = x;
+ }
+ if (y > ymax) {
+ ymax = y;
+ }
+ }
+
+ var result = new OgcApiSpatialExtent();
+ result.crs = CRS84;
+ result.bbox = Arrays.asList(xmin, ymin, xmax, ymax);
+ return result;
+ }
+
+
+ public List getBbox() {
+ return bbox;
+ }
+
+ public void setBbox(List bbox) {
+ this.bbox = bbox;
+ }
+
+ public String getCrs() {
+ return crs;
+ }
+
+ public void setCrs(String crs) {
+ this.crs = crs;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiTag.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiTag.java
new file mode 100644
index 00000000..ed7eac15
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiTag.java
@@ -0,0 +1,47 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+
+/**
+ * OgcApiTag.
+ *
+ * cf. https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/schema.yaml
+ *
+ *
I haven't included the "x-*" pattern properties.
+ */
+public class OgcApiTag {
+
+ /**
+ * undefined in spec.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "name")
+ @XmlElement(name = "name")
+ private String name;
+
+ /**
+ * undefined in spec.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "description")
+ @XmlElement(name = "description")
+ private String description;
+
+ /**
+ * undefined in spec.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "externalDocs")
+ @XmlElement(name = "externalDocs")
+ private OgcApiExternalDocumentation externalDocs;
+
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiTemporalExtent.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiTemporalExtent.java
new file mode 100644
index 00000000..83357956
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiTemporalExtent.java
@@ -0,0 +1,122 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import org.fao.geonet.index.model.gn.DateRange;
+
+/**
+ * cf. https://github.com/opengeospatial/ogcapi-features/blob/master/core/openapi/schemas/extent.yaml
+ *
+ *
The temporal extent of the features in the collection.
+ */
+public class OgcApiTemporalExtent {
+
+ public static final String DefaultTRS = "http://www.opengis.net/def/uom/ISO-8601/0/Gregorian";
+ /**
+ * One or more time intervals that describe the temporal extent of the dataset. In the Core only a
+ * single time interval is supported.
+ *
+ *
Extensions may support multiple intervals. The first time interval describes the overall
+ * temporal extent of the data. All subsequent time intervals describe more precise time
+ * intervals, e.g., to identify clusters of data. Clients only interested in the overall temporal
+ * extent will only need to access the first time interval in the array (a pair of lower and upper
+ * bound instants).
+ *
+ *
---
+ *
+ *
Begin and end times of the time interval. The timestamps are in the temporal coordinate
+ * reference system specified in `trs`. By default this is the Gregorian calendar.
+ *
+ *
The value `null` at start or end is supported and indicates a half-bounded interval. type:
+ * array minItems: 2 maxItems: 2
+ *
+ *
format: date-time nullable: true example: - '2011-11-11T12:22:11Z' - null
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "interval")
+ @XmlElement(name = "interval")
+ public List interval = new ArrayList<>();
+
+ /**
+ * not yet official.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "resolution")
+ @XmlElement(name = "resolution")
+ public String resolution;
+
+ /**
+ * Coordinate reference system of the coordinates in the temporal extent (property `interval`).
+ * The default reference system is the Gregorian calendar. In the Core this is the only supported
+ * temporal coordinate reference system. Extensions may support additional temporal coordinate
+ * reference systems and add additional enum values.
+ *
+ * enum: - 'http://www.opengis.net/def/uom/ISO-8601/0/Gregorian' default:
+ * 'http://www.opengis.net/def/uom/ISO-8601/0/Gregorian'
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "trs")
+ @XmlElement(name = "trs")
+ public String trs = DefaultTRS;
+
+ /**
+ * Create OgcApiTemporalExtent from the "resourceTemporalExtentDateRange" section of the GN Index
+ * record. Typically this looks like this:
+ *
+ *
"resourceTemporalExtentDateRange": [ { "gte": "2000-01-01T12:29:00.000Z", "lte":
+ * "2008-01-08T12:29:00.000Z" } ],
+ *
+ *
you should call this method with the { "gte": "2000-01-01T12:29:00.000Z", "lte":
+ * "2008-01-08T12:29:00.000Z" } object
+ *
+ * @param map from the Elastic Index Record
+ * @return parsed OgcApiTemporalExtent
+ */
+ public static OgcApiTemporalExtent fromGnIndexRecord(DateRange map) {
+ if (map == null) {
+ return null;
+ }
+
+ String start = map.getGte();
+ String end = map.getLte();
+
+ var result = new OgcApiTemporalExtent();
+ result.trs = DefaultTRS;
+ result.interval = Arrays.asList(start, end);
+ return result;
+ }
+
+ public List getInterval() {
+ return interval;
+ }
+
+ public void setInterval(List interval) {
+ this.interval = interval;
+ }
+
+ public String getResolution() {
+ return resolution;
+ }
+
+ public void setResolution(String resolution) {
+ this.resolution = resolution;
+ }
+
+ public String getTrs() {
+ return trs;
+ }
+
+ public void setTrs(String trs) {
+ this.trs = trs;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiTheme.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiTheme.java
new file mode 100644
index 00000000..ded41a77
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiTheme.java
@@ -0,0 +1,125 @@
+/**
+ * (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.fao.geonet.ogcapi.records.model;
+
+import static org.fao.geonet.ogcapi.records.util.JsonUtils.getAsString;
+import static org.fao.geonet.ogcapi.records.util.JsonUtils.getLangString;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import org.fao.geonet.index.model.gn.Theme;
+import org.springframework.util.StringUtils;
+
+/**
+ * cf https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/theme.yaml
+ *
+ * This is the ogcapi "theme" object model.
+ *
+ *
From ogcapi-records spec:
+ *
+ *
Themes are concepts associated with the resource(s) that a record describes taken from
+ * one or more formal knowledge organization systems or schemes.
+ */
+public class OgcApiTheme {
+
+ /**
+ * One or more entity/concept identifiers from this knowledge system. it is recommended that a
+ * resolvable URI be used for each entity/concept identifier.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "concepts")
+ @XmlElement(name = "concepts")
+ public List concepts;
+
+ /**
+ * An identifier for the knowledge organization system used to classify the resource. It is
+ * recommended that the identifier be a resolvable URI. The list of schemes used in a searchable
+ * catalog can be determined by inspecting the server's OpenAPI document or, if the server
+ * implements CQL2, by exposing a queryable (e.g. named `scheme`) and enumerating the list of
+ * schemes in the queryable's schema definition.
+ */
+ @JsonInclude(Include.NON_EMPTY)
+ @XmlElementWrapper(name = "schema")
+ @XmlElement(name = "schema")
+ public String schema;
+
+ public OgcApiTheme(String schema) {
+ this.schema = schema;
+ this.concepts = new ArrayList<>();
+ }
+
+ /**
+ * Parse the Elastic (JSON) Index document. Call with linkedServiceRecord.get("allKeyWords").
+ *
+ * @param map map from Elastic (JSON) Index document representing "allKeyWords"
+ * @return map will be parsed into a corresponding List of OgcApiTheme
+ */
+ public static List parseElasticIndex(Map map) {
+ if (map == null) {
+ return null;
+ }
+ List result = new ArrayList<>();
+ for (var theme : map.values()) {
+ var themeSchema = getAsString(theme.getLink());
+ //at least put something here!
+ if (!StringUtils.hasText(themeSchema)
+ && theme.getMultilingualTitle() != null
+ && !theme.getMultilingualTitle().isEmpty()) {
+ var multilingualTitle = theme.getMultilingualTitle();
+ var multilingualTitleLink = multilingualTitle.get("link");
+
+ if (StringUtils.hasText(multilingualTitleLink)) {
+ themeSchema = multilingualTitleLink;
+ } else {
+ themeSchema = getLangString(multilingualTitle);
+ }
+ }
+ if (!StringUtils.hasText(themeSchema) && theme.getTitle() != null) {
+ themeSchema = theme.getTitle();
+ }
+ if (!StringUtils.hasText(themeSchema) && theme.getTheme() != null) {
+ themeSchema = theme.getTheme();
+ }
+ if (!StringUtils.hasText(themeSchema) && theme.getId() != null) {
+ themeSchema = theme.getId();
+ }
+ var theme2 = new OgcApiTheme(themeSchema);
+
+ List conceptsList = theme.getKeywords();
+ for (var anConcept : conceptsList) {
+ var concept = (Map) anConcept;
+ var link = getAsString(concept.get("link"));
+ var ogcConcept = new OgcApiConcept(getLangString(anConcept), link);
+ theme2.getConcepts().add(ogcConcept);
+ }
+ if (theme2.getConcepts().size() != 0) {
+ result.add(theme2);
+ }
+ }
+ return result;
+ }
+
+ public List getConcepts() {
+ return concepts;
+ }
+
+ public void setConcepts(List concepts) {
+ this.concepts = concepts;
+ }
+
+ public String getSchema() {
+ return schema;
+ }
+
+ public void setSchema(String schema) {
+ this.schema = schema;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/service/QueryablesService.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/service/QueryablesService.java
new file mode 100644
index 00000000..44e40fb1
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/service/QueryablesService.java
@@ -0,0 +1,124 @@
+package org.fao.geonet.ogcapi.records.service;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+import org.fao.geonet.ogcapi.records.model.JsonProperty;
+import org.fao.geonet.ogcapi.records.model.JsonSchema;
+import org.springframework.stereotype.Service;
+
+/**
+ * Basic Service to handle Queryables according to the OGCAPI spec.
+ */
+@Service
+@Slf4j(topic = "org.fao.geonet.ogcapi.records")
+public class QueryablesService {
+
+ /**
+ * build a schema based on collection. It will be based on the underlying elastic index json.
+ *
+ * NOTE: these are hard coded at the moment.
+ *
+ * @param collectionId which collection
+ * @return schema based on collection
+ */
+ public JsonSchema buildQueryables(String collectionId) {
+ var jsonSchema = new JsonSchema();
+
+ Map properties = new LinkedHashMap<>();
+ jsonSchema.setProperties(properties);
+ addStandardProperties(properties);
+
+ return jsonSchema;
+ }
+
+ /**
+ * cf. https://docs.ogc.org/DRAFTS/20-004.html
+ *
+ * The only mandatory one is "id".
+ *
+ * @param properties existing set of properties to add to.
+ */
+ public void addStandardProperties(Map properties) {
+
+ JsonProperty p;
+ //table 8
+ p = new JsonProperty(JsonProperty.TypeString, "id",
+ "A unique record identifier assigned by the server.");
+ p.setxOgcRole("id");
+ properties.put("id", p);
+
+ p = new JsonProperty(JsonProperty.TypeString, "created",
+ "The date this record was created in the server.");
+ properties.put("created", p);
+
+ p = new JsonProperty(JsonProperty.TypeString, "updated",
+ "The most recent date on which the record was changed.");
+ properties.put("updated", p);
+
+ //conformsTo -- not in Elastic Index JSON
+
+ p = new JsonProperty(JsonProperty.TypeString, "language",
+ "The language used for textual values (i.e. titles, descriptions, etc.)"
+ + " of this record.");
+ properties.put("language", p);
+
+ p = new JsonProperty(JsonProperty.TypeString, "languages",
+ "The list of other languages in which this record is available.");
+ properties.put("languages", p);
+
+ //links -- not in Elastic Index JSON
+ //linkTemplates -- not in Elastic Index JSON
+
+ //table 9
+
+ //unclear what this maps to in elastic
+ p = new JsonProperty(JsonProperty.TypeString, "title",
+ "The nature or genre of the resource described by this record.");
+ properties.put("type", p);
+
+ p = new JsonProperty(JsonProperty.TypeString, "title",
+ "A human-readable name given to the resource described by this record.");
+ properties.put("title", p);
+
+ p = new JsonProperty(JsonProperty.TypeString, "description",
+ "A free-text description of the resource described by this record.");
+ properties.put("description", p);
+
+ p = new JsonProperty(JsonProperty.TypeString, "geometry",
+ "A spatial extent associated with the resource described by this record.");
+ properties.put("geometry", p);
+
+ p = new JsonProperty(JsonProperty.TypeString, "time",
+ "A temporal extent associated with the resource described by this record.");
+ properties.put("time", p);
+
+ p = new JsonProperty(JsonProperty.TypeString, "keywords",
+ "A list of free-form keywords or tags associated with the resource"
+ + " described by this record.");
+ properties.put("keywords", p);
+
+ p = new JsonProperty(JsonProperty.TypeString, "themes",
+ "A knowledge organization system used to classify the resource"
+ + " described by this resource.");
+ properties.put("themes", p);
+
+ p = new JsonProperty(JsonProperty.TypeString, "contacts",
+ "A list of contacts qualified by their role(s) in association to the record"
+ + " or the resource described by this record.");
+ properties.put("contacts", p);
+
+ //resourceLanguages -- not in Elastic Index JSON
+ //externalIds -- not in Elastic Index JSON
+ //formats -- not in Elastic Index JSON
+
+ p = new JsonProperty(JsonProperty.TypeString, "license",
+ "The legal provisions under which the resource described by this record"
+ + " is made available.");
+ properties.put("license", p);
+
+ //rights -- not in Elastic Index JSON
+
+ }
+
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/service/RecordService.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/service/RecordService.java
new file mode 100644
index 00000000..4231816e
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/service/RecordService.java
@@ -0,0 +1,182 @@
+/**
+ * (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.fao.geonet.ogcapi.records.service;
+
+import static org.fao.geonet.ogcapi.records.controller.ItemApiController.EXCEPTION_COLLECTION_ITEM_NOT_FOUND;
+
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
+import org.fao.geonet.common.search.ElasticSearchProxy;
+import org.fao.geonet.domain.Setting;
+import org.fao.geonet.domain.Source;
+import org.fao.geonet.domain.SourceType;
+import org.fao.geonet.ogcapi.records.util.RecordsEsQueryBuilder;
+import org.fao.geonet.repository.SettingRepository;
+import org.fao.geonet.repository.SourceRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.MessageSource;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+import org.springframework.web.server.ResponseStatusException;
+
+
+@Service
+@Slf4j(topic = "org.fao.geonet.ogcapi.records")
+public class RecordService {
+
+ @Autowired
+ CollectionService collectionService;
+
+ @Autowired
+ RecordsEsQueryBuilder recordsEsQueryBuilder;
+
+ @Autowired
+ ElasticSearchProxy proxy;
+
+ @Autowired
+ MessageSource messages;
+
+
+ @Autowired
+ SettingRepository settingRepository;
+
+ @Autowired
+ SourceRepository sourceRepository;
+
+
+ /**
+ * For a source (sub-portal), get the ServiceRecord's uuid.
+ * 1. usually, its part of the source object (source#getServiceRecord())
+ * 2. however, if its the full-portal, there is no GUI to edit this. In this case
+ * we use the CSW setting (system/csw/capabilityRecordUuid).
+ * @param source which sub-portal?
+ * @return uuid of the linked record or null
+ */
+ public String getLinkedRecordUuid(Source source) {
+ if (source == null) {
+ return null;
+ }
+
+ var uuid = source.getServiceRecord();
+ if (StringUtils.isEmpty(uuid) || uuid.trim().equals("-1")) {
+ if (source.getType().equals(SourceType.portal)) {
+ // this is the main portal - we have a 2nd way to get the link (admin->settings->CSW)
+ Setting setting = settingRepository.getOne("system/csw/capabilityRecordUuid");
+ uuid = setting.getStoredValue();
+ }
+ }
+ if (StringUtils.isEmpty(uuid) || uuid.trim().equals("-1")) {
+ return null;
+ }
+ return uuid;
+ }
+
+
+ /**
+ * Gets the main GN portal (from GN DB table "sources").
+ *
+ * @return Gets the main GN portal (from GN DB table "sources")
+ */
+ public Source getMainPortal() {
+ return sourceRepository.findByType(SourceType.portal, null).get(0);
+ }
+
+
+ /**
+ * Given a source, get the parsed Elastic Index JSON.
+ *
+ * @param request user request (for security)
+ * @param source which sub-portal to get the linked serviceRecord
+ * @return parsed Elastic Index JSON for the source's linked serviceRecord
+ */
+ public Map getLinkedServiceRecord(HttpServletRequest request,
+ Source source) {
+
+ var mainPortal = getMainPortal();
+
+ var uuid = getLinkedRecordUuid(source);
+
+ if (StringUtils.isEmpty(uuid) || uuid.trim().equals("-1")) {
+ return null;
+ }
+ try {
+ var info = getRecordAsJson(mainPortal.getUuid(),
+ uuid, request, mainPortal,
+ "json")
+ .get("_source");
+ ObjectMapper mapper = new ObjectMapper();
+ Map result = mapper.convertValue(info,
+ new TypeReference>() {
+ });
+ return result;
+ } catch (Exception ex) {
+ // mislinked record? not published? error processing the record?
+ log.error(String.format(
+ "An error occurred while trying to retrieve the linked ServiceRecord for sub-portal "
+ + "'%s' (link uuid '%s'). Error is: %s",
+ source.getUuid(), uuid, ex.getMessage()));
+ }
+ return null;
+ }
+
+
+ /**
+ * Get the Elastic Index JSON.
+ *
+ * @param collectionId which collection is the record apart of
+ * @param recordId which uuid to get
+ * @param request incomming user request (for security)
+ * @param source from the GN DB "source" table for this collectionId
+ * @param type what type of record
+ * @return parsed as a JSON object
+ * @throws Exception if there was a problem retrieving the record
+ */
+ public JsonNode getRecordAsJson(
+ String collectionId,
+ String recordId,
+ HttpServletRequest request,
+ Source source,
+ String type) throws Exception {
+
+ String collectionFilter = collectionService.retrieveCollectionFilter(source, true);
+ String query = recordsEsQueryBuilder.buildQuerySingleRecord(recordId, collectionFilter, null);
+
+ String queryResponse = proxy.searchAndGetResult(request.getSession(), request, query, null);
+
+ ObjectMapper mapper = new ObjectMapper();
+ JsonFactory factory = mapper.getFactory();
+ JsonParser parser = factory.createParser(queryResponse);
+ JsonNode actualObj = mapper.readTree(parser);
+
+ JsonNode totalValue =
+ "json".equals(type)
+ ? actualObj.get("hits").get("total").get("value")
+ : actualObj.get("size");
+
+ if ((totalValue == null) || (totalValue.intValue() == 0)) {
+ throw new ResponseStatusException(HttpStatus.NOT_FOUND,
+ messages.getMessage(EXCEPTION_COLLECTION_ITEM_NOT_FOUND,
+ new String[]{recordId, collectionId},
+ request.getLocale()));
+ }
+
+ if ("json".equals(type)) {
+ return actualObj.get("hits").get("hits").get(0);
+ } else {
+ String elementName = "schema.org".equals(type) ? "dataFeedElement" : "features";
+ return actualObj.get(elementName).get(0);
+ }
+
+ }
+
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/CollectionInfoBuilder.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/CollectionInfoBuilder.java
index ac253470..caf23cd8 100644
--- a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/CollectionInfoBuilder.java
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/CollectionInfoBuilder.java
@@ -1,34 +1,72 @@
+/**
+ * (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.fao.geonet.ogcapi.records.util;
+import static java.util.Arrays.asList;
import static org.fao.geonet.ogcapi.records.util.LinksItemsBuilder.getHref;
-import java.math.BigDecimal;
import java.net.URI;
-import java.util.Arrays;
import java.util.List;
import java.util.Optional;
+import javax.servlet.http.HttpServletRequest;
+import lombok.extern.slf4j.Slf4j;
import org.fao.geonet.common.search.SearchConfiguration;
import org.fao.geonet.common.search.SearchConfiguration.Format;
import org.fao.geonet.domain.Source;
import org.fao.geonet.domain.SourceType;
import org.fao.geonet.ogcapi.records.controller.model.CollectionInfo;
-import org.fao.geonet.ogcapi.records.controller.model.Extent;
-import org.fao.geonet.ogcapi.records.controller.model.Extent.CrsEnum;
-import org.fao.geonet.ogcapi.records.controller.model.Extent.TrsEnum;
-import org.fao.geonet.ogcapi.records.controller.model.Link;
+import org.fao.geonet.ogcapi.records.controller.model.CrsEnum;
+import org.fao.geonet.ogcapi.records.model.OgcApiExtent;
+import org.fao.geonet.ogcapi.records.model.OgcApiLink;
+import org.fao.geonet.ogcapi.records.model.OgcApiSpatialExtent;
+import org.fao.geonet.ogcapi.records.service.RecordService;
+import org.fao.geonet.repository.SettingRepository;
+import org.fao.geonet.repository.SourceRepository;
+import org.fao.geonet.view.ViewUtility;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+@Service
+@Slf4j(topic = "org.fao.geonet.ogcapi.records")
public class CollectionInfoBuilder {
- private CollectionInfoBuilder() {
- throw new IllegalStateException("Utility class");
+ @Value("${gn.legacy.url}")
+ String geonetworkUrl;
+
+ @Autowired
+ RecordService recordService;
+ @Autowired
+ ElasticIndexJson2CollectionInfo elasticIndexJson2CollectionInfo;
+
+
+ public CollectionInfoBuilder() {
+
}
+
/**
* Build Collection info from source table.
+ *
+ * @param source from the GN DB Table "sources"
+ * @param language which language is the request wanting
+ * @param baseUrl base url for the ogcapi
+ * @param format what format are the results requested in
+ * @param configuration config for searching
+ * @param request user request (for security)
+ * @return CollectionInfo filled in from GN DB Table "sources" and the Elastic Index JSON
*/
- public static CollectionInfo buildFromSource(Source source, String language,
- String baseUrl, Optional format, SearchConfiguration configuration) {
+ public CollectionInfo buildFromSource(Source source,
+ String language,
+ String baseUrl,
+ Optional format,
+ SearchConfiguration configuration,
+ HttpServletRequest request) {
String name;
if (source.getType() == SourceType.portal) {
@@ -38,6 +76,7 @@ public static CollectionInfo buildFromSource(Source source, String language,
}
CollectionInfo collectionInfo = new CollectionInfo();
+
collectionInfo.setId(name);
String label = source.getLabel(language);
// The source label may contain a description
@@ -48,30 +87,48 @@ public static CollectionInfo buildFromSource(Source source, String language,
collectionInfo.setTitle(
titleAndDescription.length > 1 ? titleAndDescription[0] : label);
collectionInfo
- .setCrs(Arrays.asList(CrsEnum.HTTP_WWW_OPENGIS_NET_DEF_CRS_OGC_1_3_CRS84.getValue()));
+ .setCrs(asList(CrsEnum.HTTP_WWW_OPENGIS_NET_DEF_CRS_OGC_1_3_CRS84.getValue()));
- // TODO: Review values
- Extent extent = new Extent();
- extent.crs(CrsEnum.HTTP_WWW_OPENGIS_NET_DEF_CRS_OGC_1_3_CRS84);
- extent.setSpatial(Arrays.asList(new BigDecimal(-180), new BigDecimal(-90),
- new BigDecimal(180), new BigDecimal(90)));
+ var spatialExtent = new OgcApiSpatialExtent();
+ spatialExtent.crs = OgcApiSpatialExtent.CRS84;
+ spatialExtent.setBbox(asList(-180.0, -90.0, 180.0, 90.0));
+ var extent = new OgcApiExtent(spatialExtent, null);
collectionInfo.setExtent(extent);
- extent.setTrs(TrsEnum.HTTP_WWW_OPENGIS_NET_DEF_UOM_ISO_8601_0_GREGORIAN);
-
// TODO: Accept format parameter.
baseUrl = baseUrl + (!baseUrl.endsWith("/") ? "/" : "");
URI collectionUri = URI.create(baseUrl).resolve(name);
- Link currentDoc = new Link();
+ OgcApiLink currentDoc = new OgcApiLink();
currentDoc.setRel("self");
currentDoc.setHref(getHref(collectionUri.toString(), format));
currentDoc.setType(format.get().getMimeType());
currentDoc.setHreflang(language);
- List linkList = LinksItemsBuilder.build(
+ List linkList = LinksItemsBuilder.build(
format, collectionUri.toString(), language, configuration);
linkList.forEach(collectionInfo::addLinksItem);
+
+ var gnbase = geonetworkUrl;
+ if (!gnbase.endsWith("/")) {
+ gnbase += "/";
+ }
+ //assume its a png
+ var url = URI.create(gnbase).resolve("images/logos/" + source.getUuid() + ".png");
+ var link = new OgcApiLink();
+ link.setHref(url.toString());
+ link.setRel("icon");
+ link.setType("image/png");
+ collectionInfo.addLinksItem(link);
+
+
+ var linkedServiceRecord =
+ recordService.getLinkedServiceRecord(request, source);
+ elasticIndexJson2CollectionInfo.injectLinkedServiceRecordInfo(collectionInfo,
+ linkedServiceRecord);
+
return collectionInfo;
}
+
+
}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/ElasticIndexJson2CollectionInfo.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/ElasticIndexJson2CollectionInfo.java
new file mode 100644
index 00000000..454fb4bc
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/ElasticIndexJson2CollectionInfo.java
@@ -0,0 +1,298 @@
+package org.fao.geonet.ogcapi.records.util;
+
+import static org.fao.geonet.ogcapi.records.util.JsonUtils.getLangString;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+import org.codehaus.jackson.map.type.TypeFactory;
+import org.fao.geonet.index.model.gn.IndexRecord;
+import org.fao.geonet.ogcapi.records.controller.model.CollectionInfo;
+import org.fao.geonet.ogcapi.records.model.OgcApiContact;
+import org.fao.geonet.ogcapi.records.model.OgcApiExtent;
+import org.fao.geonet.ogcapi.records.model.OgcApiLanguage;
+import org.fao.geonet.ogcapi.records.model.OgcApiSpatialExtent;
+import org.fao.geonet.ogcapi.records.model.OgcApiTemporalExtent;
+import org.fao.geonet.ogcapi.records.model.OgcApiTheme;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+
+/**
+ * Takes an Elastic Index Json and injects it inside a collectioninfo.
+ */
+@Service
+@Slf4j(topic = "org.fao.geonet.ogcapi.records.util")
+public class ElasticIndexJson2CollectionInfo {
+
+
+ /**
+ * inject the "extra" info from the LinkedServiceRecord into the CollectionInfo.
+ *
+ * @param collectionInfo collection metadata we've gathered so far (usually not much)
+ * @param linkedServiceRecord Parsed JSON map of the linked Service record (GN's DB "source"
+ * "serviceRecord")
+ */
+ public void injectLinkedServiceRecordInfo(CollectionInfo collectionInfo,
+ Map linkedServiceRecord) {
+ if (linkedServiceRecord == null) {
+ return;
+ }
+ ObjectMapper objectMapper = org.fao.geonet.index.JsonUtils.getObjectMapper();
+
+ IndexRecord indexRecord = objectMapper.convertValue(
+ linkedServiceRecord,
+ IndexRecord.class);
+
+ injectLinkedServiceRecordInfo(collectionInfo, indexRecord);
+ }
+
+ /**
+ * inject the "extra" info from the LinkedServiceRecord into the CollectionInfo.
+ *
+ * @param collectionInfo collection metadata we've gathered so far (usually not much)
+ * @param indexRecord Parsed JSON of the linked Service record (GN's DB "source"
+ * "serviceRecord")
+ */
+ public void injectLinkedServiceRecordInfo(CollectionInfo collectionInfo,
+ IndexRecord indexRecord) {
+ if (indexRecord == null) {
+ return;
+ }
+
+ set(getTitle(collectionInfo, indexRecord), collectionInfo, "title");
+ set(getDescription(collectionInfo, indexRecord), collectionInfo, "description");
+
+ set(getContacts(collectionInfo, indexRecord), collectionInfo, "contacts");
+
+ OgcApiSpatialExtent spatialExtent = getSpatialExtent(indexRecord);
+ OgcApiTemporalExtent temporalExtent = getTemporalExtent(indexRecord);
+ OgcApiExtent extent = new OgcApiExtent(spatialExtent, temporalExtent);
+ if (spatialExtent != null || temporalExtent != null) {
+ set(extent,collectionInfo, "extent");
+ }
+
+ set(getCrs(collectionInfo, indexRecord), collectionInfo, "crs");
+ set(getCreateDate(collectionInfo, indexRecord), collectionInfo, "created");
+ set(getChangeDate(collectionInfo, indexRecord), collectionInfo, "updated");
+ set(getTags(collectionInfo, indexRecord), collectionInfo, "keywords");
+
+ set(getLanguage(collectionInfo, indexRecord), collectionInfo, "language");
+ set(getOtherLangs(collectionInfo, indexRecord), collectionInfo, "languages");
+
+ set(getThemes(collectionInfo, indexRecord), collectionInfo, "themes");
+
+ set(getLicenses(collectionInfo, indexRecord), collectionInfo, "license");
+ set(getRights(collectionInfo, indexRecord), collectionInfo, "rights");
+ }
+
+
+ /**
+ * use reflection to set a value. We do this, so we can handle all sorts of different object (i.e.
+ * geojson) and parts of the various ogcapi specifications (not just record).
+ *
+ * @param val object value
+ * @param mainObject object to set the property on
+ * @param propertyName name of the property
+ */
+ public void set(Object val, Object mainObject, String propertyName) {
+ if (val == null || mainObject == null || !StringUtils.hasText(propertyName)) {
+ return; //nothing to do
+ }
+ //see if there is a field of that name (should probably cache)
+ try {
+ var field = mainObject.getClass().getDeclaredField(propertyName);
+ field.setAccessible(true);
+ field.set(mainObject, val);
+ } catch (Exception e) {
+ return;
+ }
+
+ }
+
+
+
+ //its a bit unclear what to do here - there's a big difference between MD record,
+ // Elastic Index JSON versus what's expected in the ogcapi license field. We do the simple
+ // action
+ private String getRights(CollectionInfo collectionInfo,
+ IndexRecord indexRecord) {
+ var myLiceseList = indexRecord.getMdLegalConstraintsUseLimitationObject();
+ if (myLiceseList != null && !myLiceseList.isEmpty()) {
+ if (myLiceseList.size() > 1) {
+ var license2 = getLangString(myLiceseList.get(1));
+ return license2;
+ }
+ }
+ return null;
+ }
+
+
+ //its a bit unclear what to do here - there's a big difference between MD record,
+ // Elastic Index JSON versus what's expected in the ogcapi license field. We do the simple
+ // action
+ private String getLicenses(CollectionInfo collectionInfo,
+ IndexRecord indexRecord) {
+ var myLiceseList = indexRecord.getMdLegalConstraintsUseLimitationObject();
+ if (myLiceseList != null && !myLiceseList.isEmpty()) {
+ if (myLiceseList.size() > 0) {
+ var license = getLangString(myLiceseList.get(0));
+ return license;
+ }
+
+ }
+ return null;
+ }
+
+ //process allKeywords to get themes
+ private List getThemes(CollectionInfo collectionInfo,
+ IndexRecord indexRecord) {
+ var myThemeKeywords = indexRecord.getAllKeywords();
+ return OgcApiTheme.parseElasticIndex(myThemeKeywords);
+ }
+
+ //process otherLanguage to get the other languages
+ private ArrayList getOtherLangs(CollectionInfo collectionInfo,
+ IndexRecord indexRecord) {
+ var langs = indexRecord.getOtherLanguage();
+ if (langs != null) {
+ var result = new ArrayList();
+ for (var lang : langs) {
+ result.add(new OgcApiLanguage(lang));
+ }
+ return result;
+ }
+ return null;
+ }
+
+ //process main language
+ private OgcApiLanguage getLanguage(CollectionInfo collectionInfo,
+ IndexRecord indexRecord) {
+ var myMainLang = indexRecord.getMainLanguage();
+ if (myMainLang != null && StringUtils.hasText(myMainLang)) {
+ return new OgcApiLanguage(myMainLang);
+ }
+ return null;
+ }
+
+ //handle the "tags" to create keywords
+ private ArrayList getTags(CollectionInfo collectionInfo, IndexRecord indexRecord) {
+ var myTags = indexRecord.getTag();
+ if (myTags != null) {
+ var result = new ArrayList();
+ var tags = myTags;
+ collectionInfo.setKeywords(new ArrayList<>());
+ for (var tag : tags) {
+ result.add(getLangString(tag));
+ }
+ return result;
+ }
+ return null;
+ }
+
+ //process the change date
+ private String getChangeDate(CollectionInfo collectionInfo,
+ IndexRecord indexRecord) {
+ var updateDate = indexRecord.getChangeDate();
+ if (updateDate != null && StringUtils.hasText(updateDate)) {
+ return updateDate;
+ }
+ return null;
+ }
+
+ //process the creation date
+ private String getCreateDate(CollectionInfo collectionInfo,
+ IndexRecord indexRecord) {
+ var createDate = indexRecord.getCreateDate();
+ if (createDate != null && StringUtils.hasText(createDate)) {
+ return createDate;
+ }
+ return null;
+ }
+
+ //process CRS
+ private ArrayList getCrs(CollectionInfo collectionInfo, IndexRecord indexRecord) {
+ var crss = indexRecord.getCoordinateSystem();
+ var result = new ArrayList();
+ if (crss != null) {
+ for (var crs : crss) {
+ result.add(crs);
+ }
+ return result;
+ }
+ return null;
+ }
+
+ //combine spatial and temporal extent
+ private void handleExtent(CollectionInfo collectionInfo, OgcApiSpatialExtent spatialExtent,
+ OgcApiTemporalExtent temporalExtent) {
+ OgcApiExtent extent = new OgcApiExtent(spatialExtent, temporalExtent);
+ if (spatialExtent != null || temporalExtent != null) {
+ collectionInfo.setExtent(extent);
+ }
+ }
+
+ //process Temporal Extent
+ private OgcApiTemporalExtent getTemporalExtent(IndexRecord indexRecord) {
+ OgcApiTemporalExtent temporalExtent = null;
+ var myTemporalExtent = indexRecord.getResourceTemporalExtentDateRange();
+ if (myTemporalExtent != null || myTemporalExtent.isEmpty()) {
+ myTemporalExtent = indexRecord.getResourceTemporalDateRange();
+ }
+ if (myTemporalExtent != null || myTemporalExtent.isEmpty()) {
+ temporalExtent = OgcApiTemporalExtent.fromGnIndexRecord(myTemporalExtent.get(0));
+ }
+ return temporalExtent;
+ }
+
+ //process the spatial extent
+ private OgcApiSpatialExtent getSpatialExtent(IndexRecord indexRecord) {
+ OgcApiSpatialExtent spatialExtent = null;
+ var mySpatialExtent = indexRecord.getGeometries();
+ if (mySpatialExtent != null && !mySpatialExtent.isEmpty()) {
+ Map map = null;
+ try {
+ map = new org.codehaus.jackson.map.ObjectMapper().readValue(mySpatialExtent.get(0),
+ TypeFactory.mapType(HashMap.class, String.class, Object.class));
+ } catch (IOException e) {
+ return null;
+ }
+ spatialExtent = OgcApiSpatialExtent.fromGnIndexRecord(map);
+ }
+ return spatialExtent;
+ }
+
+ //process contracts
+ private List getContacts(CollectionInfo collectionInfo,
+ IndexRecord indexRecord) {
+ var contacts = indexRecord.getContact();
+ if (contacts != null && !contacts.isEmpty()) {
+ var result = new ArrayList();
+
+ for (var contactInfo : contacts) {
+ var contact = OgcApiContact.fromIndexMap(contactInfo);
+ result.add(contact);
+ }
+ return result;
+ }
+ return null;
+ }
+
+ //override description (abstract) from attached ServiceRecord
+ private String getDescription(CollectionInfo collectionInfo,
+ IndexRecord indexRecord) {
+ var desc = getLangString(indexRecord.getResourceAbstract());
+ return desc;
+ }
+
+
+ //override title from attached ServiceRecord
+ private String getTitle(CollectionInfo collectionInfo, IndexRecord indexRecord) {
+ var title = getLangString(indexRecord.getResourceTitle());
+ return title;
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/JsonUtils.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/JsonUtils.java
new file mode 100644
index 00000000..fa4c5481
--- /dev/null
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/JsonUtils.java
@@ -0,0 +1,61 @@
+/**
+ * (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.fao.geonet.ogcapi.records.util;
+
+import java.util.Map;
+
+public class JsonUtils {
+
+ /**
+ * todo - be language aware (send in desired language). Move to utility class.
+ *
+ * @param jsonNode json node for the potentially multi-language string
+ * @return "correct" language value.
+ */
+ public static String getLangString(Object jsonNode) {
+ if (jsonNode == null) {
+ return null;
+ }
+ if ((jsonNode instanceof String)) {
+ return (String) jsonNode;
+ }
+ if (jsonNode instanceof Map) {
+ var map = (Map) jsonNode;
+ if (map.containsKey("default")) {
+ return map.get("default").toString();
+ }
+ return null;
+ }
+ return null;
+ }
+
+ /**
+ * Simple utility class to get a JSON value as a string.
+ *
+ * @param o json object
+ * @return null or o.toString()
+ */
+ public static String getAsString(Object o) {
+ if (o == null) {
+ return null;
+ }
+ var result = o.toString();
+ return result;
+ }
+
+ /**
+ * Simple utility class to get a JSON value as a double.
+ *
+ * @param o json object (could be integer or double)
+ * @return null or double value
+ */
+ public static Double getAsDouble(Object o) {
+ if (o == null) {
+ return null;
+ }
+ return ((Number) o).doubleValue();
+ }
+}
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/LinksItemsBuilder.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/LinksItemsBuilder.java
index 18aebc3b..33154fb6 100644
--- a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/LinksItemsBuilder.java
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/LinksItemsBuilder.java
@@ -5,7 +5,7 @@
import java.util.Optional;
import org.fao.geonet.common.search.SearchConfiguration;
import org.fao.geonet.common.search.SearchConfiguration.Format;
-import org.fao.geonet.ogcapi.records.controller.model.Link;
+import org.fao.geonet.ogcapi.records.model.OgcApiLink;
import org.springframework.http.MediaType;
public class LinksItemsBuilder {
@@ -17,23 +17,23 @@ private LinksItemsBuilder() {
/**
* Build items.
*/
- public static List build(
+ public static List build(
Optional format, String url, String language,
SearchConfiguration configuration) {
- Link currentDoc = new Link();
+ OgcApiLink currentDoc = new OgcApiLink();
Format linkFormat = format.get();
currentDoc.setRel("self");
currentDoc.setHref(getHref(url, format));
currentDoc.setType(format.get().getMimeType());
currentDoc.setHreflang(language);
- List links = new ArrayList<>();
+ List links = new ArrayList<>();
links.add(currentDoc);
for (MediaType supportedMediaType : MediaTypeUtil.defaultSupportedMediaTypes) {
if (!supportedMediaType.toString().equals(linkFormat.getMimeType())) {
Optional f = configuration.getFormat(supportedMediaType);
- Link alternateDoc = new Link();
+ OgcApiLink alternateDoc = new OgcApiLink();
alternateDoc.setRel("alternate");
alternateDoc.setHref(getHref(url, f));
alternateDoc.setType(supportedMediaType.toString());
diff --git a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/RecordsEsQueryBuilder.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/RecordsEsQueryBuilder.java
index 81711090..f8a86621 100644
--- a/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/RecordsEsQueryBuilder.java
+++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/RecordsEsQueryBuilder.java
@@ -14,6 +14,7 @@
import org.elasticsearch.geometry.Rectangle;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.GeoShapeQueryBuilder;
+import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
@@ -141,11 +142,15 @@ public String buildQuery(
}
}
- String filterQueryString = configuration.getQueryFilter();
+ String filterQueryString = "(" + configuration.getQueryFilter() + ")";
if (StringUtils.isNotEmpty(collectionFilter)) {
- filterQueryString += " " + collectionFilter;
+ filterQueryString += " (" + collectionFilter + ")";
}
- boolQuery.filter(QueryBuilders.queryStringQuery(filterQueryString));
+
+ var bbQuery = QueryBuilders.queryStringQuery(filterQueryString);
+ bbQuery.defaultOperator(Operator.AND);
+ boolQuery.filter(bbQuery);
+
sourceBuilder.query(boolQuery);
sourceBuilder.trackTotalHits(configuration.getTrackTotalHits());
log.debug("OGC API query: {}", sourceBuilder.toString());
diff --git a/modules/services/ogc-api-records/src/test/java/org/fao/geonet/ogcapi/records/util/ElasticIndexJson2CollectionInfoTest.java b/modules/services/ogc-api-records/src/test/java/org/fao/geonet/ogcapi/records/util/ElasticIndexJson2CollectionInfoTest.java
new file mode 100644
index 00000000..2d5a6c54
--- /dev/null
+++ b/modules/services/ogc-api-records/src/test/java/org/fao/geonet/ogcapi/records/util/ElasticIndexJson2CollectionInfoTest.java
@@ -0,0 +1,134 @@
+/**
+ * (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.fao.geonet.ogcapi.records.util;
+
+import org.apache.commons.io.IOUtils;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.type.TypeFactory;
+import org.fao.geonet.ogcapi.records.controller.model.CollectionInfo;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+public class ElasticIndexJson2CollectionInfoTest {
+
+
+ // sample Elastic Index JSON
+ public static String sample1ElasticIndexJson;
+ public static Map sample1ElasticIndexParsed;
+
+
+
+ ElasticIndexJson2CollectionInfo elasticIndexJson2CollectionInfo = new ElasticIndexJson2CollectionInfo();
+
+
+ @BeforeClass
+ public static void setupClass() throws IOException {
+
+
+
+
+ sample1ElasticIndexJson = IOUtils.toString(
+ ClassLoader.getSystemClassLoader().getResourceAsStream("org/fao/geonet/ogcapi/records/util/sampleCollectionServiceRecordElasticIndex.json"),
+ "UTF-8");
+
+ sample1ElasticIndexParsed = new ObjectMapper().readValue( sample1ElasticIndexJson,
+ TypeFactory.mapType(HashMap.class, String.class, Object.class));
+ sample1ElasticIndexParsed = (Map ) sample1ElasticIndexParsed.get("hits");
+ sample1ElasticIndexParsed = (Map ) ((List)sample1ElasticIndexParsed.get("hits")).get(0);
+ sample1ElasticIndexParsed = (Map )sample1ElasticIndexParsed.get("_source");
+
+
+ }
+
+
+ /**
+ * Simple test case to read in the Elastic Index JSON, and parse it into a CollectionInfo.
+ */
+ @Test
+ public void testFull() {
+ var collectionInfo = new CollectionInfo();
+ elasticIndexJson2CollectionInfo.injectLinkedServiceRecordInfo(collectionInfo, sample1ElasticIndexParsed);
+
+ assertEquals("catalog", collectionInfo.getType());
+ assertEquals("record", collectionInfo.getItemType());
+
+ assertEquals("GeoCat Demo OGCIAPI sub-portal", collectionInfo.getTitle());
+ assertEquals("This is a sub-portal for testing OGCAPI.", collectionInfo.getDescription());
+
+ assertEquals("http://www.opengis.net/def/crs/OGC/1.3/CRS84", collectionInfo.getCrs().get(0));
+ assertEquals("2024-09-10T19:10:58.536961Z", collectionInfo.getCreated());
+ assertEquals("2024-09-16T17:07:51.673194Z", collectionInfo.getUpdated());
+ assertEquals("eng", collectionInfo.getLanguage().code);
+ assertEquals("use limitation", collectionInfo.getLicense());
+
+ //languages
+ assertEquals(3, collectionInfo.getLanguages().size());
+ assertEquals("dut", collectionInfo.getLanguages().get(0).code);
+ assertEquals("spa", collectionInfo.getLanguages().get(1).code);
+ assertEquals("eng", collectionInfo.getLanguages().get(2).code);
+
+ //keywords
+ assertEquals(6, collectionInfo.getKeywords().size());
+ assertEquals("GEONETWORK", collectionInfo.getKeywords().get(0));
+ assertEquals("OSGeo", collectionInfo.getKeywords().get(1));
+ assertEquals("GeoCat", collectionInfo.getKeywords().get(2));
+ assertEquals("OGCAPI", collectionInfo.getKeywords().get(3));
+ assertEquals("Algeria", collectionInfo.getKeywords().get(4));
+ assertEquals("Antarctica", collectionInfo.getKeywords().get(5));
+
+
+ //themes
+ assertEquals(2, collectionInfo.getThemes().size());
+ //first theme
+ assertEquals("otherKeywords-theme", collectionInfo.getThemes().get(0).schema);
+ assertEquals(4, collectionInfo.getThemes().get(0).getConcepts().size());
+ assertEquals("GEONETWORK", collectionInfo.getThemes().get(0).getConcepts().get(0).id);
+ assertEquals("OSGeo", collectionInfo.getThemes().get(0).getConcepts().get(1).id);
+ assertEquals("GeoCat", collectionInfo.getThemes().get(0).getConcepts().get(2).id);
+ assertEquals("OGCAPI", collectionInfo.getThemes().get(0).getConcepts().get(3).id);
+
+ //2nd theme
+ assertEquals("http://localhost:8080/geonetwork/srv/api/registries/vocabularies/external.place.regions", collectionInfo.getThemes().get(1).schema);
+ assertEquals(2, collectionInfo.getThemes().get(1).getConcepts().size());
+ assertEquals("Algeria", collectionInfo.getThemes().get(1).getConcepts().get(0).id);
+ assertEquals("Antarctica", collectionInfo.getThemes().get(1).getConcepts().get(1).id);
+
+ //contacts
+ assertEquals(1, collectionInfo.getContacts().size());
+ assertEquals("Jody Garnett", collectionInfo.getContacts().get(0).getName());
+ assertEquals("Hat Wearer Extraordinaire", collectionInfo.getContacts().get(0).getPosition());
+ assertEquals("GeoCat Canada Ltd", collectionInfo.getContacts().get(0).getOrganization());
+ //phones
+ assertEquals(1, collectionInfo.getContacts().get(0).getPhones().size());
+ assertEquals("+1 (250) 213-1219", collectionInfo.getContacts().get(0).getPhones().get(0).getValue());
+ //email
+ assertEquals(1, collectionInfo.getContacts().get(0).getEmails().size());
+ assertEquals("jody.garnett@geocat.net", collectionInfo.getContacts().get(0).getEmails().get(0).getValue());
+ //address
+ assertEquals(1, collectionInfo.getContacts().get(0).getAddresses().size());
+ assertEquals(1, collectionInfo.getContacts().get(0).getAddresses().get(0).getDeliveryPoint().size());
+ assertEquals("3613 Doncaster Drr, Victoria, BC, V8P 3W5, Canada", collectionInfo.getContacts().get(0).getAddresses().get(0).getDeliveryPoint().get(0));
+
+ //extent - spatial
+ assertEquals("http://www.opengis.net/def/crs/OGC/1.3/CRS84", collectionInfo.getExtent().getSpatial().getCrs());
+ assertEquals(4, collectionInfo.getExtent().getSpatial().getBbox().size());
+ assertEquals(-180.0, collectionInfo.getExtent().getSpatial().getBbox().get(0).doubleValue() ,0);
+ assertEquals(-90.0, collectionInfo.getExtent().getSpatial().getBbox().get(1).doubleValue() ,0);
+ assertEquals(180.0, collectionInfo.getExtent().getSpatial().getBbox().get(2).doubleValue() ,0);
+ assertEquals(90.0, collectionInfo.getExtent().getSpatial().getBbox().get(3).doubleValue() ,0);
+ //extent - temporal
+ assertEquals("http://www.opengis.net/def/uom/ISO-8601/0/Gregorian", collectionInfo.getExtent().getTemporal().getTrs());
+ assertEquals(2, collectionInfo.getExtent().getTemporal().getInterval().size());
+ assertEquals("2016-02-29T20:00:00.000Z", collectionInfo.getExtent().getTemporal().getInterval().get(0));
+ assertEquals("2016-02-29T20:00:00.000Z", collectionInfo.getExtent().getTemporal().getInterval().get(1));
+
+ }
+}
diff --git a/modules/services/ogc-api-records/src/test/resources/org/fao/geonet/ogcapi/records/util/sampleCollectionServiceRecord.xml b/modules/services/ogc-api-records/src/test/resources/org/fao/geonet/ogcapi/records/util/sampleCollectionServiceRecord.xml
new file mode 100644
index 00000000..c4ac0b5d
--- /dev/null
+++ b/modules/services/ogc-api-records/src/test/resources/org/fao/geonet/ogcapi/records/util/sampleCollectionServiceRecord.xml
@@ -0,0 +1,787 @@
+
+
+
+ 9bac358b-11ec-4293-aeef-5a077b778412
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Jody Garnett
+
+
+ Jody Garnett
+
+
+
+
+ GeoCat Canada Ltd
+
+
+ GeoCat Canada Ltd
+
+
+
+
+ Hat Wearer Extraordinaire
+
+
+ Hat Wearer Extraordinaire
+
+
+
+
+
+
+
+
+ +1 (250) 213-1219
+
+
+
+
+
+
+
+
+
+ 3613 Doncaster Drr
+
+
+ 3613 Doncaster Drr
+
+
+
+
+ Victoria
+
+
+ BC
+
+
+ V8P 3W5
+
+
+ Canada
+
+
+ Canada
+
+
+
+
+ jody.garnett@geocat.net
+
+
+ jody.garnett@geocat.net
+
+
+
+
+
+
+
+
+ http://geocat.net
+
+
+
+
+
+
+
+ Please call between 9:00am and 5:00pm
+
+
+ Please call between 9:00am and 5:00pm
+
+
+
+
+
+
+
+
+
+
+
+ 2024-09-16T17:07:51.673224Z
+
+
+ ISO 19115:2003/19139
+
+
+ 1.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ http://www.opengis.net/def/crs/OGC/1.3/CRS84
+
+
+ http://www.opengis.net/def/crs/OGC/1.3/CRS84
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GeoCat Demo OGCIAPI sub-portal
+
+
+ GeoCat Demo OGCIAPI sub-portal
+
+
+
+
+
+
+
+ 2016-02-29T12:00:00
+
+
+
+
+
+
+
+
+
+ This is a sub-portal for testing OGCAPI.
+
+
+ This is a sub-portal for testing OGCAPI.
+
+
+
+
+
+
+
+
+
+ Jody Garnett
+
+
+ Jody Garnett
+
+
+
+
+ GeoCat Canada Ltd
+
+
+ GeoCat Canada Ltd
+
+
+
+
+ position
+
+
+ position
+
+
+
+
+
+
+
+
+ +1 (250) 213-1219
+
+
+
+
+
+
+
+
+
+ 3613 Doncaster Dr
+
+
+ 3613 Doncaster Dr
+
+
+
+
+ Victoria
+
+
+ BC
+
+
+ V8P 3W5
+
+
+ Canada
+
+
+ Canada
+
+
+
+
+ jody.garnett@geocat.net
+
+
+ jody.garnett@geocat.net
+
+
+
+
+
+
+
+
+ http://geocat.net
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ http://localhost:8080/geonetwork/srv/api/records/9bac358b-11ec-4293-aeef-5a077b778412/attachments/jody.jpg
+
+
+
+
+
+
+ GEONETWORK
+
+
+ GEONETWORK
+
+
+
+
+ OSGeo
+
+
+ OSGeo
+
+
+
+
+ GeoCat
+
+
+ GeoCat
+
+
+
+
+ OGCAPI
+
+
+ OGCAPI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ INSPIRE Service taxonomy
+
+
+ INSPIRE Service taxonomy
+
+
+
+
+
+
+ 2010-04-22
+
+
+
+
+
+
+
+
+
+ geonetwork.thesaurus.external.theme.inspire-service-taxonomy
+
+
+
+
+
+
+
+
+
+
+ Algeria
+
+
+ Algeria
+
+
+
+
+
+
+
+
+
+
+ Antarctica
+
+
+ Antarctica
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Continents, countries, sea regions of the world.
+
+
+ Continents, countries, sea regions of the world.
+
+
+
+
+
+
+ 2015-07-17
+
+
+
+
+
+
+
+
+
+ geonetwork.thesaurus.external.place.regions
+
+
+
+
+
+
+
+
+
+
+ use limitation
+
+
+ use limitation
+
+
+
+
+
+
+
+
+
+
+ No Conditions Apply
+
+
+ No Conditions Apply
+
+
+
+
+ blah
+
+
+ blah
+
+
+
+
+
+
+ other
+
+
+ 1.1.1
+
+
+
+
+ NONE
+
+
+
+
+
+
+
+
+ -180.00
+
+
+ 180.00
+
+
+ -90.00
+
+
+ 90.00
+
+
+
+
+
+
+
+
+
+
+
+ GetCapabilities
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ invocable
+
+
+ invocable
+
+
+
+
+
+
+ 2014-12-11
+
+
+
+
+
+
+
+
+
+ Conformant to the INSPIRE SDS specifications.
+
+
+ Conformant to the INSPIRE SDS specifications.
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+ availability
+
+
+ availability
+
+
+
+
+
+
+ INSPIRE_service_availability
+
+
+ INSPIRE_service_availability
+
+
+
+
+
+
+
+
+
+ 90
+
+
+
+
+
+
+
+
+ performance
+
+
+ performance
+
+
+
+
+
+
+ INSPIRE_service_performance
+
+
+ INSPIRE_service_performance
+
+
+
+
+
+
+
+
+
+ 0.5
+
+
+
+
+
+
+
+
+ capacity
+
+
+ capacity
+
+
+
+
+
+
+ INSPIRE_service_capacity
+
+
+ INSPIRE_service_capacity
+
+
+
+
+
+
+
+
+
+ 50
+
+
+
+
+
+
+
+
+
+
+
+
+ Description of technical specification
+
+
+ Description of technical specification
+
+
+
+
+
+
+ 2014-12-11
+
+
+
+
+
+
+
+
+
+ Conformant to the cited specifications.
+
+
+ Conformant to the cited specifications.
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/services/ogc-api-records/src/test/resources/org/fao/geonet/ogcapi/records/util/sampleCollectionServiceRecordElasticIndex.json b/modules/services/ogc-api-records/src/test/resources/org/fao/geonet/ogcapi/records/util/sampleCollectionServiceRecordElasticIndex.json
new file mode 100644
index 00000000..02a60fed
--- /dev/null
+++ b/modules/services/ogc-api-records/src/test/resources/org/fao/geonet/ogcapi/records/util/sampleCollectionServiceRecordElasticIndex.json
@@ -0,0 +1,538 @@
+{
+ "took": 67,
+ "timed_out": false,
+ "_shards": {
+ "total": 1,
+ "successful": 1,
+ "skipped": 0,
+ "failed": 0
+ },
+ "hits": {
+ "total": {
+ "value": 1,
+ "relation": "eq"
+ },
+ "max_score": 1.8064759,
+ "hits": [
+ {
+ "_index": "gn-records",
+ "_id": "9bac358b-11ec-4293-aeef-5a077b778412",
+ "_score": 1.8064759,
+ "_ignored": [
+ "overview.data.keyword"
+ ],
+ "_source": {
+ "docType": "metadata",
+ "document": "",
+ "metadataIdentifier": "9bac358b-11ec-4293-aeef-5a077b778412",
+ "standardNameObject": {
+ "default": "ISO 19115:2003/19139",
+ "langeng": "ISO 19115:2003/19139"
+ },
+ "standardVersionObject": {
+ "default": "1.0",
+ "langeng": "1.0"
+ },
+ "indexingDate": 1726506476530,
+ "dateStamp": "2024-09-16T17:07:51.673224Z",
+ "mainLanguage": "eng",
+ "otherLanguage": [
+ "dut",
+ "spa",
+ "eng"
+ ],
+ "otherLanguageId": [
+ "NL",
+ "ES",
+ "EN"
+ ],
+ "cl_characterSet": [
+ {
+ "key": "utf8",
+ "default": "UTF8",
+ "langeng": "UTF8",
+ "langdut": "utf8",
+ "langspa": "UTF8",
+ "link": "http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_CharacterSetCode"
+ }
+ ],
+ "resourceType": [
+ "service"
+ ],
+ "OrgObject": {
+ "default": "GeoCat Canada Ltd",
+ "langeng": "GeoCat Canada Ltd"
+ },
+ "pointOfContactOrgObject": {
+ "default": "GeoCat Canada Ltd",
+ "langeng": "GeoCat Canada Ltd"
+ },
+ "contact": [
+ {
+ "organisationObject": {
+ "default": "GeoCat Canada Ltd",
+ "langeng": "GeoCat Canada Ltd"
+ },
+ "role": "pointOfContact",
+ "email": "jody.garnett@geocat.net",
+ "website": "http://geocat.net",
+ "logo": "",
+ "individual": "Jody Garnett",
+ "position": "Hat Wearer Extraordinaire",
+ "phone": "+1 (250) 213-1219",
+ "address": "3613 Doncaster Drr, Victoria, BC, V8P 3W5, Canada"
+ }
+ ],
+ "cl_hierarchyLevel": [
+ {
+ "key": "service",
+ "default": "Service",
+ "langeng": "Service",
+ "langdut": "service",
+ "langspa": "Servicio",
+ "link": "http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_ScopeCode"
+ }
+ ],
+ "cl_status": [
+ {
+ "key": "completed",
+ "default": "Completed",
+ "langeng": "Completed",
+ "langdut": "compleet",
+ "langspa": "Terminado",
+ "link": "http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_ProgressCode"
+ }
+ ],
+ "cl_type": [
+ {
+ "key": "theme",
+ "default": "Theme",
+ "langeng": "Theme",
+ "langdut": "theme",
+ "langspa": "Tema",
+ "link": "http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_KeywordTypeCode"
+ },
+ {
+ "key": "place",
+ "default": "Place",
+ "langeng": "Place",
+ "langdut": "place",
+ "langspa": "Lugar",
+ "link": "http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_KeywordTypeCode"
+ }
+ ],
+ "cl_accessConstraints": [
+ {
+ "key": "otherRestrictions",
+ "default": "Other restrictions",
+ "langeng": "Other restrictions",
+ "langdut": "anders",
+ "langspa": "Otras restricciones",
+ "link": "http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_RestrictionCode"
+ }
+ ],
+ "cl_useConstraints": [
+ {
+ "key": "restricted",
+ "default": "Restricted",
+ "langeng": "Restricted",
+ "langdut": "niet\n toegankelijk\n ",
+ "langspa": "Restricted",
+ "link": "http://standards.iso.org/iso/19139/resources/gmxCodelists.xml#MD_RestrictionCode"
+ }
+ ],
+ "cl_couplingType": [
+ {
+ "key": "tight",
+ "default": "Tight",
+ "langeng": "Tight",
+ "langdut": "tight",
+ "langspa": "Apretado",
+ "link": "http://www.isotc211.org/2005/iso19119/resources/Codelist/gmxCodelists.xml#SV_CouplingType"
+ }
+ ],
+ "cl_DCP": [
+ {
+ "key": "XML",
+ "default": "XML",
+ "langeng": "XML",
+ "langdut": "XML",
+ "langspa": "XML",
+ "link": "http://inspire.ec.europa.eu/metadata-codelist/DCPList"
+ }
+ ],
+ "resourceTitleObject": {
+ "default": "GeoCat Demo OGCIAPI sub-portal",
+ "langeng": "GeoCat Demo OGCIAPI sub-portal"
+ },
+ "resourceAltTitleObject": [
+ {
+ "default": "",
+ "lang": ""
+ }
+ ],
+ "revisionDateForResource": [
+ "2016-02-29T20:00:00.000Z"
+ ],
+ "revisionYearForResource": "2016",
+ "revisionMonthForResource": "2016-02",
+ "resourceDate": [
+ {
+ "type": "revision",
+ "date": "2016-02-29T20:00:00.000Z"
+ }
+ ],
+ "resourceTemporalDateRange": [
+ {
+ "gte": "2016-02-29T20:00:00.000Z",
+ "lte": "2016-02-29T20:00:00.000Z"
+ }
+ ],
+ "resourceAbstractObject": {
+ "default": "This is a sub-portal for testing OGCAPI.",
+ "langeng": "This is a sub-portal for testing OGCAPI."
+ },
+ "OrgForResourceObject": {
+ "default": "GeoCat Canada Ltd",
+ "langeng": "GeoCat Canada Ltd"
+ },
+ "pointOfContactOrgForResourceObject": {
+ "default": "GeoCat Canada Ltd",
+ "langeng": "GeoCat Canada Ltd"
+ },
+ "contactForResource": [
+ {
+ "organisationObject": {
+ "default": "GeoCat Canada Ltd",
+ "langeng": "GeoCat Canada Ltd"
+ },
+ "role": "pointOfContact",
+ "email": "jody.garnett@geocat.net",
+ "website": "http://geocat.net",
+ "logo": "",
+ "individual": "Jody Garnett",
+ "position": "position",
+ "phone": "+1 (250) 213-1219",
+ "address": "3613 Doncaster Dr, Victoria, BC, V8P 3W5, Canada"
+ },
+ {
+ "organisationObject": {},
+ "role": "",
+ "email": "",
+ "website": "",
+ "logo": "",
+ "individual": "",
+ "position": "",
+ "phone": "",
+ "address": ""
+ }
+ ],
+ "hasOverview": "true",
+ "tag": [
+ {
+ "default": "GEONETWORK",
+ "langeng": "GEONETWORK"
+ },
+ {
+ "default": "OSGeo",
+ "langeng": "OSGeo"
+ },
+ {
+ "default": "GeoCat",
+ "langeng": "GeoCat"
+ },
+ {
+ "default": "OGCAPI",
+ "langeng": "OGCAPI"
+ },
+ {
+ "default": "Algeria",
+ "langeng": "Algeria",
+ "link": "http://www.naturalearthdata.com/ne_admin#Country/DZA",
+ "key": "http://www.naturalearthdata.com/ne_admin#Country/DZA"
+ },
+ {
+ "default": "Antarctica",
+ "langeng": "Antarctica",
+ "link": "http://www.naturalearthdata.com/ne_admin#Country/Indeterminate/ATA",
+ "key": "http://www.naturalearthdata.com/ne_admin#Country/Indeterminate/ATA"
+ }
+ ],
+ "tagNumber": "6",
+ "isOpenData": "false",
+ "keywordType-theme": [
+ {
+ "default": "GEONETWORK",
+ "langeng": "GEONETWORK"
+ },
+ {
+ "default": "OSGeo",
+ "langeng": "OSGeo"
+ },
+ {
+ "default": "GeoCat",
+ "langeng": "GeoCat"
+ },
+ {
+ "default": "OGCAPI",
+ "langeng": "OGCAPI"
+ }
+ ],
+ "keywordType-place": [
+ {
+ "default": "Algeria",
+ "langeng": "Algeria",
+ "link": "http://www.naturalearthdata.com/ne_admin#Country/DZA"
+ },
+ {
+ "default": "Antarctica",
+ "langeng": "Antarctica",
+ "link": "http://www.naturalearthdata.com/ne_admin#Country/Indeterminate/ATA"
+ }
+ ],
+ "th_otherKeywords-themeNumber": "4",
+ "th_otherKeywords-theme": [
+ {
+ "default": "GEONETWORK",
+ "langeng": "GEONETWORK"
+ },
+ {
+ "default": "OSGeo",
+ "langeng": "OSGeo"
+ },
+ {
+ "default": "GeoCat",
+ "langeng": "GeoCat"
+ },
+ {
+ "default": "OGCAPI",
+ "langeng": "OGCAPI"
+ }
+ ],
+ "th_regionsNumber": "2",
+ "th_regions": [
+ {
+ "default": "Algeria",
+ "langeng": "Algeria",
+ "link": "http://www.naturalearthdata.com/ne_admin#Country/DZA"
+ },
+ {
+ "default": "Antarctica",
+ "langeng": "Antarctica",
+ "link": "http://www.naturalearthdata.com/ne_admin#Country/Indeterminate/ATA"
+ }
+ ],
+ "th_inspire-service-taxonomyNumber": "0",
+ "th_inspire-service-taxonomy": [],
+ "allKeywords": {
+ "th_otherKeywords-theme": {
+ "title": "otherKeywords-theme",
+ "theme": "theme",
+ "keywords": [
+ {
+ "default": "GEONETWORK",
+ "langeng": "GEONETWORK"
+ },
+ {
+ "default": "OSGeo",
+ "langeng": "OSGeo"
+ },
+ {
+ "default": "GeoCat",
+ "langeng": "GeoCat"
+ },
+ {
+ "default": "OGCAPI",
+ "langeng": "OGCAPI"
+ }
+ ]
+ },
+ "th_regions": {
+ "id": "geonetwork.thesaurus.external.place.regions",
+ "multilingualTitle": {
+ "default": "Continents, countries, sea regions of the world.",
+ "langeng": "Continents, countries, sea regions of the world.",
+ "link": "http://geonetwork-opensource.org/thesaurus/naturalearth-and-seavox"
+ },
+ "theme": "place",
+ "link": "http://localhost:8080/geonetwork/srv/api/registries/vocabularies/external.place.regions",
+ "keywords": [
+ {
+ "default": "Algeria",
+ "langeng": "Algeria",
+ "link": "http://www.naturalearthdata.com/ne_admin#Country/DZA"
+ },
+ {
+ "default": "Antarctica",
+ "langeng": "Antarctica",
+ "link": "http://www.naturalearthdata.com/ne_admin#Country/Indeterminate/ATA"
+ }
+ ]
+ },
+ "th_inspire-service-taxonomy": {
+ "id": "geonetwork.thesaurus.external.theme.inspire-service-taxonomy",
+ "multilingualTitle": {
+ "default": "INSPIRE Service taxonomy",
+ "langeng": "INSPIRE Service taxonomy"
+ },
+ "theme": "theme",
+ "link": "http://localhost:8080/geonetwork/srv/api/registries/vocabularies/external.theme.inspire-service-taxonomy",
+ "keywords": []
+ }
+ },
+ "th_regions_tree": {
+ "default": [
+ "Africa",
+ "Africa^Algeria",
+ "Antarctica"
+ ],
+ "key": [
+ "http://www.naturalearthdata.com/ne_admin#Continent/Africa",
+ "http://www.naturalearthdata.com/ne_admin#Continent/Africa^http://www.naturalearthdata.com/ne_admin#Country/DZA",
+ "http://www.naturalearthdata.com/ne_admin#Country/Indeterminate/ATA"
+ ]
+ },
+ "MD_LegalConstraintsOtherConstraintsObject": [
+ {
+ "default": "No Conditions Apply",
+ "langeng": "No Conditions Apply",
+ "link": "http://inspire.ec.europa.eu/registry/metadata-codelist/ConditionsApplyingToAccessAndUse/noConditionsApply"
+ },
+ {
+ "default": "blah",
+ "langeng": "blah"
+ }
+ ],
+ "MD_LegalConstraintsUseLimitationObject": [
+ {
+ "default": "use limitation",
+ "langeng": "use limitation"
+ }
+ ],
+ "licenseObject": [
+ {
+ "default": "No Conditions Apply",
+ "langeng": "No Conditions Apply",
+ "link": "http://inspire.ec.europa.eu/registry/metadata-codelist/ConditionsApplyingToAccessAndUse/noConditionsApply"
+ },
+ {
+ "default": "blah",
+ "langeng": "blah"
+ }
+ ],
+ "geom": [
+ {
+ "type": "Polygon",
+ "coordinates": [
+ [
+ [
+ -180,
+ -90
+ ],
+ [
+ 180,
+ -90
+ ],
+ [
+ 180,
+ 90
+ ],
+ [
+ -180,
+ 90
+ ],
+ [
+ -180,
+ -90
+ ]
+ ]
+ ]
+ }
+ ],
+ "location": "0,0",
+ "serviceType": "other",
+ "serviceTypeVersion": "1.1.1",
+ "coordinateSystem": [
+ "http://www.opengis.net/def/crs/OGC/1.3/CRS84"
+ ],
+ "crsDetails": [
+ {
+ "code": "http://www.opengis.net/def/crs/OGC/1.3/CRS84",
+ "codeSpace": "",
+ "name": "http://www.opengis.net/def/crs/OGC/1.3/CRS84",
+ "url": ""
+ }
+ ],
+ "specificationConformance": [
+ {
+ "title": "invocable",
+ "date": "2014-12-11",
+ "link": "http://inspire.ec.europa.eu/metadata-codelist/Category/invocable",
+ "explanation": "Conformant to the INSPIRE SDS specifications.",
+ "pass": "true"
+ },
+ {
+ "title": "Description of technical specification",
+ "date": "2014-12-11",
+ "link": "http://link,to/the.technical.specification",
+ "explanation": "Conformant to the cited specifications.",
+ "pass": "true"
+ }
+ ],
+ "conformTo_invocable": "true",
+ "conformTo_Descriptionoftechnicalspecification": "true",
+ "recordGroup": "9bac358b-11ec-4293-aeef-5a077b778412",
+ "recordOwner": "admin admin",
+ "uuid": "9bac358b-11ec-4293-aeef-5a077b778412",
+ "displayOrder": "0",
+ "groupPublishedId": [
+ "2",
+ "1",
+ "0"
+ ],
+ "popularity": 27,
+ "userinfo": "admin|admin|admin|Administrator",
+ "groupPublished": [
+ "sample",
+ "all",
+ "intranet"
+ ],
+ "isPublishedToAll": "true",
+ "record": "record",
+ "draft": "n",
+ "changeDate": "2024-09-16T17:07:51.673194Z",
+ "id": "117",
+ "createDate": "2024-09-10T19:10:58.536961Z",
+ "isPublishedToIntranet": "true",
+ "owner": "1",
+ "groupOwner": "2",
+ "logo": "/images/logos/9a8e760d-08cc-4406-b120-bb3f7d02c94b.png",
+ "hasxlinks": "false",
+ "featureOfRecord": "record",
+ "isPublishedToGuest": "false",
+ "extra": "null",
+ "documentStandard": "iso19139",
+ "valid": "-1",
+ "isTemplate": "n",
+ "feedbackCount": "0",
+ "rating": "0",
+ "isHarvested": "false",
+ "userSavedCount": "0",
+ "sourceCatalogue": "9a8e760d-08cc-4406-b120-bb3f7d02c94b",
+ "overview": [
+ {}
+ ]
+ },
+ "edit": false,
+ "owner": false,
+ "isPublishedToAll": true,
+ "view": true,
+ "notify": false,
+ "download": true,
+ "dynamic": true,
+ "featured": false,
+ "selected": false
+ }
+ ]
+ }
+}
\ No newline at end of file