From 0005bce9bca818f84a4a2686b76c5f36036cd483 Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Tue, 17 Sep 2024 09:21:55 -0700 Subject: [PATCH 01/18] adding ogcapi 'catalog.yaml' output for catalogs --- .../controller/CapabilitiesApiController.java | 23 +- .../controller/CollectionApiController.java | 12 +- .../controller/model/CollectionInfo.java | 237 ++++++++++++++- .../ogcapi/records/controller/model/Root.java | 17 ++ .../ogcapi/records/model/OgcApiAddress.java | 126 ++++++++ .../ogcapi/records/model/OgcApiConcept.java | 85 ++++++ .../ogcapi/records/model/OgcApiContact.java | 243 +++++++++++++++ .../ogcapi/records/model/OgcApiEmail.java | 62 ++++ .../ogcapi/records/model/OgcApiExtent.java | 63 ++++ .../ogcapi/records/model/OgcApiLanguage.java | 91 ++++++ .../ogcapi/records/model/OgcApiLink.java | 160 ++++++++++ .../ogcapi/records/model/OgcApiPhone.java | 53 ++++ .../records/model/OgcApiSpatialExtent.java | 151 ++++++++++ .../records/model/OgcApiTemporalExtent.java | 120 ++++++++ .../ogcapi/records/model/OgcApiTheme.java | 109 +++++++ .../ogcapi/records/service/RecordService.java | 178 +++++++++++ .../records/util/CollectionInfoBuilder.java | 284 +++++++++++++++++- 17 files changed, 1982 insertions(+), 32 deletions(-) create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiAddress.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiConcept.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiContact.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiEmail.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiExtent.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiLanguage.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiLink.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiPhone.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSpatialExtent.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiTemporalExtent.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiTheme.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/service/RecordService.java 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..8cf5d882 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,11 +16,13 @@ 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.XsltModel; +import org.fao.geonet.ogcapi.records.service.RecordService; import org.fao.geonet.ogcapi.records.util.CollectionInfoBuilder; import org.fao.geonet.ogcapi.records.util.LinksItemsBuilder; import org.fao.geonet.ogcapi.records.util.MediaTypeUtil; @@ -77,6 +79,12 @@ public class CapabilitiesApiController { @Autowired MediaTypeUtil mediaTypeUtil; + @Autowired + CollectionInfoBuilder collectionInfoBuilder; + + @Autowired + RecordService recordService; + /** * Landing page end-point. * @@ -111,8 +119,18 @@ 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); + root.setSystemInfo(collectionInfo); } + root.addLinksItem(new Link() .href(requestBaseUrl) .rel("self").type(MediaType.APPLICATION_JSON.toString())); @@ -256,8 +274,9 @@ 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( 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..d1355a64 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; @@ -75,6 +80,9 @@ public class CollectionApiController { @Autowired MediaTypeUtil mediaTypeUtil; + @Autowired + CollectionInfoBuilder collectionInfoBuilder; + /** * Describe a collection. */ @@ -126,9 +134,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/model/CollectionInfo.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/CollectionInfo.java index 0d05f71f..9825faf5 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,6 +17,10 @@ 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.OgcApiTheme; /** * CollectionInfo entity. @@ -17,7 +28,20 @@ @JacksonXmlRootElement(localName = "CollectionInfo") @XmlRootElement(name = "CollectionInfo") @XmlAccessorType(XmlAccessType.FIELD) -public class CollectionInfo { +public class CollectionInfo { + + //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"]. + @JsonProperty("itemType") + @JacksonXmlProperty(localName = "itemType") + private String itemType = "record"; + + @JsonProperty("id") @JacksonXmlProperty(localName = "id") private String id; @@ -32,23 +56,211 @@ public class CollectionInfo { @JsonProperty("links") @JacksonXmlProperty(localName = "links") - + @JsonInclude(Include.NON_EMPTY) private List links = new ArrayList<>(); @JsonProperty("extent") @JacksonXmlProperty(localName = "extent") - private Extent extent; + private OgcApiExtent extent; @JsonProperty("crs") @JacksonXmlProperty(localName = "crs") - + @JsonInclude(Include.NON_EMPTY) private List crs = null; + @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; + + + 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,6 +309,7 @@ public void setDescription(String description) { this.description = description; } + public CollectionInfo links(List links) { this.links = links; return this; @@ -119,7 +332,7 @@ 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 +341,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 +368,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 +407,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 +419,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/Root.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Root.java index 521c9a10..2c3d6fb4 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,10 +1,13 @@ 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; import io.swagger.annotations.ApiModelProperty; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Objects; import javax.xml.bind.annotation.XmlAccessType; @@ -18,6 +21,13 @@ @XmlRootElement(name = "Root") @XmlAccessorType(XmlAccessType.FIELD) public class Root { + + @JsonProperty("systemInfo") + @JacksonXmlProperty(localName = "systemInfo") + @JsonInclude(Include.NON_EMPTY) + private CollectionInfo systemInfo; + + @JsonProperty("title") @JacksonXmlProperty(localName = "title") private String title; @@ -87,6 +97,13 @@ 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/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/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..b75ef9bb --- /dev/null +++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiConcept.java @@ -0,0 +1,85 @@ +/** + * (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; + +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..88be2485 --- /dev/null +++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiContact.java @@ -0,0 +1,243 @@ +/** + * (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.ogcapi.records.util.CollectionInfoBuilder; +import org.springframework.util.StringUtils; + +/** + * https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/contact.yaml + */ +@XmlRootElement(name = "contact") +@XmlAccessorType(XmlAccessType.FIELD) +public class OgcApiContact { + + @JsonInclude(Include.NON_EMPTY) + @XmlElementWrapper(name = "identifier") + @XmlElement(name = "identifier") + private String identifier; + + @JsonInclude(Include.NON_EMPTY) + @XmlElementWrapper(name = "name") + @XmlElement(name = "name") + private String name; + + @JsonInclude(Include.NON_EMPTY) + @XmlElementWrapper(name = "position") + @XmlElement(name = "position") + private String position; + + @JsonInclude(Include.NON_EMPTY) + @XmlElementWrapper(name = "organization") + @XmlElement(name = "organization") + private String organization; + + @JsonInclude(Include.NON_EMPTY) + @XmlElementWrapper(name = "logo") + @XmlElement(name = "logo") + private OgcApiLink logo; + + @JsonInclude(Include.NON_EMPTY) + @XmlElementWrapper(name = "") + @XmlElement(name = "") + private List phones = new ArrayList<>(); + + @JsonInclude(Include.NON_EMPTY) + @XmlElementWrapper(name = "") + @XmlElement(name = "") + private List emails = new ArrayList<>(); + + @JsonInclude(Include.NON_EMPTY) + @XmlElementWrapper(name = "addresses") + @XmlElement(name = "addresses") + private List addresses = new ArrayList<>(); + + @JsonInclude(Include.NON_EMPTY) + @XmlElementWrapper(name = "links") + @XmlElement(name = "links") + private List links = new ArrayList<>(); + + @JsonInclude(Include.NON_EMPTY) + @XmlElementWrapper(name = "hoursOfService") + @XmlElement(name = "hoursOfService") + private String hoursOfService; + + @JsonInclude(Include.NON_EMPTY) + @XmlElementWrapper(name = "contactInstructions") + @XmlElement(name = "contactInstructions") + private String contactInstructions; + + @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 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( + CollectionInfoBuilder.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..3fd4b2d3 --- /dev/null +++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiEmail.java @@ -0,0 +1,62 @@ +/** + * (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; + + +/** + * 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/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..75ad2768 --- /dev/null +++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiLanguage.java @@ -0,0 +1,91 @@ +/** + * (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; + +/** + * 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/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..e9bad391 --- /dev/null +++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiLink.java @@ -0,0 +1,160 @@ +/** + * (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.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 + */ +@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. + */ + @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 + */ + @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 + */ + @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(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; + } +} 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..a7e63f51 --- /dev/null +++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiPhone.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.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; + +@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/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..5a83a631 --- /dev/null +++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSpatialExtent.java @@ -0,0 +1,151 @@ +/** + * (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 java.util.Map; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; + +/** + * 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 = (Double) coord.get(0); + var y = (Double) 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/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..bed65779 --- /dev/null +++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiTemporalExtent.java @@ -0,0 +1,120 @@ +/** + * (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 java.util.Map; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; + +/** + * 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(Map map) { + if (map == null) { + return null; + } + + String start = (String) map.get("gte"); + String end = (String) map.get("lte"); + + 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..6486bd9b --- /dev/null +++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiTheme.java @@ -0,0 +1,109 @@ +/** + * (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.CollectionInfoBuilder.getAsString; +import static org.fao.geonet.ogcapi.records.util.CollectionInfoBuilder.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.springframework.util.StringUtils; + +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 thSetObject : map.values()) { + var thSet = (Map) thSetObject; + + var themeSchema = getAsString(thSet.get("link")); + //at least put something here! + if (!StringUtils.hasText(themeSchema)) { + if (StringUtils.hasText(getAsString(thSet.get("multilingualTitle")))) { + themeSchema = getLangString(thSet.get("multilingualTitle").toString()); + } + } + if (!StringUtils.hasText(themeSchema) && thSet.get("title") != null) { + themeSchema = thSet.get("title").toString(); + } + if (!StringUtils.hasText(themeSchema) && thSet.get("theme") != null) { + themeSchema = thSet.get("theme").toString(); + } + if (!StringUtils.hasText(themeSchema) && thSet.get("id") != null) { + themeSchema = thSet.get("id").toString(); + } + var theme = new OgcApiTheme(themeSchema); + + List conceptsList = (List) thSet.get("keywords"); + for (var anConcept : conceptsList) { + var concept = (Map) anConcept; + var link = getAsString(concept.get("link")); + var ogcConcept = new OgcApiConcept(getLangString(anConcept), link); + theme.getConcepts().add(ogcConcept); + } + if (theme.getConcepts().size() != 0) { + result.add(theme); + } + } + 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/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..868dceac --- /dev/null +++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/service/RecordService.java @@ -0,0 +1,178 @@ +/** + * (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 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; +import java.util.Map; + + +@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; + + + public String getLinkedRecord(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 = 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; + } + 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..34bf81d6 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,164 @@ +/** + * (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 com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; +import java.util.Map; 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.Setting; 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.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.fao.geonet.ogcapi.records.service.RecordService; +import org.fao.geonet.repository.SettingRepository; +import org.fao.geonet.repository.SourceRepository; +import org.springframework.beans.factory.annotation.Autowired; +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"); + @Autowired + RecordService recordService; + + @Autowired + private SourceRepository sourceRepository; + + @Autowired + private SettingRepository settingRepository; + + public CollectionInfoBuilder() { + + } + + /** + * 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; + } + + /** + * 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 = 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; + } + try { + var info = recordService.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; } /** * 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 +168,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,17 +179,14 @@ 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); @@ -72,6 +200,130 @@ public static CollectionInfo buildFromSource(Source source, String language, format, collectionUri.toString(), language, configuration); linkList.forEach(collectionInfo::addLinksItem); + var linkedServiceRecord = getLinkedServiceRecord(request, source); + injectLinkedServiceRecordInfo(collectionInfo, linkedServiceRecord); + return collectionInfo; } + + /** + * inject the "extra" info from the LinkedServiceRecord into the CollectionInfo. + * + * @param collectionInfo collection metadata we've gathered so far (usually not much) + * @param linkedServiceRecord JSON of the linked Service record (GN's DB "source" "serviceRecord") + */ + private void injectLinkedServiceRecordInfo(CollectionInfo collectionInfo, + Map linkedServiceRecord) { + if (linkedServiceRecord == null) { + return; + } + + //override title from attached ServiceRecord + var title = getLangString(linkedServiceRecord.get("resourceTitleObject")); + if (title != null) { + collectionInfo.setTitle(title); + } + + //override description (abstract) from attached ServiceRecord + var desc = getLangString(linkedServiceRecord.get("resourceAbstractObject")); + if (desc != null) { + collectionInfo.setDescription(desc); + } + + var contacts = linkedServiceRecord.get("contact"); + if (contacts != null && linkedServiceRecord.get("contact") instanceof List) { + var cs = (List) contacts; + collectionInfo.setContacts(new ArrayList<>()); + for (var contactMap : cs) { + var contact = OgcApiContact.fromIndexMap((Map) contactMap); + collectionInfo.getContacts().add(contact); + } + } + + OgcApiSpatialExtent spatialExtent = null; + var mySpatialExtent = linkedServiceRecord.get("geom"); + if (mySpatialExtent != null + && mySpatialExtent instanceof List) { + mySpatialExtent = ((List) mySpatialExtent).get(0); + spatialExtent = OgcApiSpatialExtent.fromGnIndexRecord((Map) mySpatialExtent); + } + + OgcApiTemporalExtent temporalExtent = null; + var myTemporalExtent = linkedServiceRecord.get("resourceTemporalDateRange"); + if (myTemporalExtent != null && myTemporalExtent instanceof List) { + myTemporalExtent = ((List) myTemporalExtent).get(0); + temporalExtent = OgcApiTemporalExtent.fromGnIndexRecord( + (Map) myTemporalExtent); + } + + OgcApiExtent extent = new OgcApiExtent(spatialExtent, temporalExtent); + if (spatialExtent != null || temporalExtent != null) { + collectionInfo.setExtent(extent); + } + + var myCrss = linkedServiceRecord.get("coordinateSystem"); + if (myCrss != null && myCrss instanceof List) { + var crss = (List) myCrss; + collectionInfo.setCrs(new ArrayList<>()); + for (var crs : crss) { + collectionInfo.getCrs().add(crs.toString()); + } + } + + var createDate = linkedServiceRecord.get("createDate"); + if (createDate != null && StringUtils.hasText(createDate.toString())) { + collectionInfo.setCreated(createDate.toString()); + } + + var updateDate = linkedServiceRecord.get("changeDate"); + if (updateDate != null && StringUtils.hasText(updateDate.toString())) { + collectionInfo.setUpdated(updateDate.toString()); + } + + var myTags = linkedServiceRecord.get("tag"); + if (myTags != null && myTags instanceof List) { + var tags = (List) myTags; + collectionInfo.setKeywords(new ArrayList<>()); + for (var tag : tags) { + collectionInfo.getKeywords().add(getLangString(tag)); + } + } + + //index record doesn't contain "otherlanguages" + var myMainLang = linkedServiceRecord.get("mainLanguage"); + if (myMainLang != null && StringUtils.hasText(myMainLang.toString())) { + collectionInfo.setLanguage(new OgcApiLanguage(myMainLang.toString())); + } + + var myOtherLangs = linkedServiceRecord.get("otherLanguage"); + if (myOtherLangs != null && myOtherLangs instanceof List) { + var langs = (List) myOtherLangs; + collectionInfo.setLanguages(new ArrayList<>()); + for (var lang : langs) { + collectionInfo.getLanguages().add(new OgcApiLanguage(lang.toString())); + } + } + + var myThemeKeywords = linkedServiceRecord.get("allKeywords"); + collectionInfo.setThemes(OgcApiTheme.parseElasticIndex((Map) myThemeKeywords)); + + //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 + var myLicense = linkedServiceRecord.get("MD_LegalConstraintsUseLimitationObject"); + if (myLicense != null && myLicense instanceof List) { + var myLiceseList = (List) myLicense; + if (myLiceseList.size() > 0) { + var license = getLangString(myLiceseList.get(0)); + collectionInfo.setLicense(license); + } + if (myLiceseList.size() > 1) { + var license2 = getLangString(myLiceseList.get(1)); + collectionInfo.setRights(license2); + } + } + + + } + } From 8496c6efce81fb1c08c6a0f6485a3d1c34e99e6f Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Tue, 17 Sep 2024 09:49:53 -0700 Subject: [PATCH 02/18] cleaning up code and documenting the ogc .yaml objects --- .../records/controller/ItemApiController.java | 54 +++++-------------- .../ogcapi/records/model/OgcApiConcept.java | 8 +++ .../ogcapi/records/model/OgcApiContact.java | 32 +++++++++-- .../ogcapi/records/model/OgcApiEmail.java | 4 +- .../ogcapi/records/model/OgcApiLanguage.java | 4 +- .../ogcapi/records/model/OgcApiLink.java | 2 + .../ogcapi/records/model/OgcApiPhone.java | 5 ++ .../records/model/OgcApiSpatialExtent.java | 4 +- .../records/model/OgcApiTemporalExtent.java | 5 +- .../ogcapi/records/model/OgcApiTheme.java | 10 ++++ .../ogcapi/records/service/RecordService.java | 42 ++++++++------- .../records/util/CollectionInfoBuilder.java | 52 ++---------------- 12 files changed, 104 insertions(+), 118 deletions(-) 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..505bf55f 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 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 index b75ef9bb..7b508525 100644 --- 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 @@ -10,6 +10,14 @@ 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 { 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 index 88be2485..c87aeaa8 100644 --- 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 @@ -20,66 +20,90 @@ /** * 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 = "") - @XmlElement(name = "") + @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 = "") - @XmlElement(name = "") + @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") 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 index 3fd4b2d3..6d84cdb4 100644 --- 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 @@ -17,7 +17,9 @@ /** - * Email addresses at which contact can be made. + * 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) 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 index 75ad2768..ba52a986 100644 --- 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 @@ -11,7 +11,9 @@ import javax.xml.bind.annotation.XmlElementWrapper; /** - * The language used for textual values in this record. + * 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 { 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 index e9bad391..0f3b01b8 100644 --- 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 @@ -15,6 +15,8 @@ /** * https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/linkBase.yaml + * + *

Represents a link. */ @XmlRootElement(name = "link") @XmlAccessorType(XmlAccessType.FIELD) 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 index a7e63f51..e92b0f4f 100644 --- 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 @@ -15,6 +15,11 @@ 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 { 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 index 5a83a631..3d9bbaa3 100644 --- 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 @@ -15,7 +15,9 @@ import javax.xml.bind.annotation.XmlElementWrapper; /** - * The spatial extent of the features in the collection. + * 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 { 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 index bed65779..37617423 100644 --- 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 @@ -15,7 +15,10 @@ import javax.xml.bind.annotation.XmlElementWrapper; /** - * The temporal extent of the features in the collection. + * 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 { 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 index 6486bd9b..04c9990a 100644 --- 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 @@ -17,6 +17,16 @@ import javax.xml.bind.annotation.XmlElementWrapper; 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 { /** 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 index 868dceac..4231816e 100644 --- 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 @@ -12,6 +12,7 @@ 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; @@ -27,7 +28,6 @@ import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.server.ResponseStatusException; -import java.util.Map; @Service @@ -54,8 +54,16 @@ public class RecordService { SourceRepository sourceRepository; - public String getLinkedRecord(Source source) { - if (source ==null) { + /** + * 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; } @@ -76,6 +84,7 @@ public String getLinkedRecord(Source source) { /** * Gets the main GN portal (from GN DB table "sources"). + * * @return Gets the main GN portal (from GN DB table "sources") */ public Source getMainPortal() { @@ -87,28 +96,23 @@ public Source getMainPortal() { * 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 + * @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 = 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(); - } - } + + var uuid = getLinkedRecordUuid(source); + if (StringUtils.isEmpty(uuid) || uuid.trim().equals("-1")) { return null; } try { var info = getRecordAsJson(mainPortal.getUuid(), - uuid, request, mainPortal, - "json") + uuid, request, mainPortal, + "json") .get("_source"); ObjectMapper mapper = new ObjectMapper(); Map result = mapper.convertValue(info, @@ -129,11 +133,11 @@ public Map getLinkedServiceRecord(HttpServletRequest request, /** * 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 + * @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 */ 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 34bf81d6..b9e1ee65 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 @@ -92,55 +92,9 @@ public static String getAsString(Object o) { return result; } - /** - * 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 = 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; - } - try { - var info = recordService.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; - } + + /** * Build Collection info from source table. @@ -200,7 +154,7 @@ public CollectionInfo buildFromSource(Source source, format, collectionUri.toString(), language, configuration); linkList.forEach(collectionInfo::addLinksItem); - var linkedServiceRecord = getLinkedServiceRecord(request, source); + var linkedServiceRecord = recordService.getLinkedServiceRecord(request, source); injectLinkedServiceRecordInfo(collectionInfo, linkedServiceRecord); return collectionInfo; From f8e9ce69c14d499cd7b5e0a567d623c3df85cfa5 Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Tue, 17 Sep 2024 11:50:49 -0700 Subject: [PATCH 03/18] refactor to use OgcApi* objects --- .../controller/CapabilitiesApiController.java | 32 ++- .../controller/model/CollectionInfo.java | 23 +- .../records/controller/model/Content.java | 24 +- .../records/controller/model/CrsEnum.java | 42 +++ .../records/controller/model/Extent.java | 268 ------------------ .../ogcapi/records/controller/model/Link.java | 169 ----------- .../ogcapi/records/controller/model/Root.java | 30 +- .../ogcapi/records/model/OgcApiLink.java | 75 +++++ .../records/util/CollectionInfoBuilder.java | 32 +-- .../records/util/LinksItemsBuilder.java | 10 +- 10 files changed, 201 insertions(+), 504 deletions(-) create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/CrsEnum.java delete mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Extent.java delete mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/Link.java 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 8cf5d882..0a4a7099 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 @@ -19,8 +19,8 @@ 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.service.RecordService; import org.fao.geonet.ogcapi.records.util.CollectionInfoBuilder; @@ -130,15 +130,17 @@ public ResponseEntity getLandingPage(@ApiIgnore HttpServletRequest request 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() + configuration.getFormats(Operations.root).forEach(f -> + root.addLinksItem(new OgcApiLink() .href(requestBaseUrl + "collections?f=" + f.getName()) .type("Catalogue collections") - .rel("self").type(f.getMimeType()))); + .rel("self") + .type(f.getMimeType()))); addOpenApiLinks(root, requestBaseUrl); addConformanceLinks(root, requestBaseUrl); @@ -163,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)); @@ -176,15 +178,19 @@ 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)); } @@ -279,7 +285,7 @@ public ResponseEntity describeCollections(@ApiIgnore HttpServletRequest 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/model/CollectionInfo.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/CollectionInfo.java index 9825faf5..a7609f8a 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 @@ -20,10 +20,13 @@ 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.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") @@ -42,32 +45,40 @@ public class CollectionInfo { 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") @JsonInclude(Include.NON_EMPTY) - private List links = new ArrayList<>(); + private List links = new ArrayList<>(); + /**The spatiotemporal coverage of this catalog.*/ @JsonProperty("extent") @JacksonXmlProperty(localName = "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) @@ -310,12 +321,12 @@ public void setDescription(String 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; } @@ -324,11 +335,11 @@ 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; } 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..bf59e2f8 --- /dev/null +++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/CrsEnum.java @@ -0,0 +1,42 @@ +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 2c3d6fb4..518f9148 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 @@ -7,21 +7,26 @@ import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; import io.swagger.annotations.ApiModelProperty; import java.util.ArrayList; -import java.util.Collection; 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; +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. + */ @JsonProperty("systemInfo") @JacksonXmlProperty(localName = "systemInfo") @JsonInclude(Include.NON_EMPTY) @@ -31,6 +36,12 @@ public class Root { @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. @@ -49,10 +60,6 @@ public Root title(String title) { return this; } - @JsonProperty("description") - @JacksonXmlProperty(localName = "description") - private String description; - /** * Get description. */ @@ -70,17 +77,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; } @@ -89,11 +91,11 @@ 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; } 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 index 0f3b01b8..750af456 100644 --- 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 @@ -7,6 +7,7 @@ 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; @@ -90,6 +91,11 @@ public class OgcApiLink { @XmlElement(name = "href") private String href; + + public OgcApiLink() { + + } + public OgcApiLink(String href, String type) { this.href = href; this.type = type; @@ -159,4 +165,73 @@ public String getHref() { 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/util/CollectionInfoBuilder.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/CollectionInfoBuilder.java index b9e1ee65..d961d4cb 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 @@ -9,8 +9,6 @@ import static java.util.Arrays.asList; import static org.fao.geonet.ogcapi.records.util.LinksItemsBuilder.getHref; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; import java.net.URI; import java.util.ArrayList; import java.util.List; @@ -20,15 +18,14 @@ 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.Setting; 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.CrsEnum; -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.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.OgcApiSpatialExtent; import org.fao.geonet.ogcapi.records.model.OgcApiTemporalExtent; import org.fao.geonet.ogcapi.records.model.OgcApiTheme; @@ -80,7 +77,8 @@ public static String getLangString(Object jsonNode) { } /** - * Simple utility class to get a JSON value as a string. + * Simple utility class to get a JSON value as a string. + * * @param o json object * @return null or o.toString() */ @@ -93,18 +91,15 @@ public static String getAsString(Object o) { } - - - /** * 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 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) + * @param request user request (for security) * @return CollectionInfo filled in from GN DB Table "sources" and the Elastic Index JSON */ public CollectionInfo buildFromSource(Source source, @@ -144,13 +139,13 @@ public CollectionInfo buildFromSource(Source source, // 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); @@ -163,8 +158,9 @@ public CollectionInfo buildFromSource(Source source, /** * inject the "extra" info from the LinkedServiceRecord into the CollectionInfo. * - * @param collectionInfo collection metadata we've gathered so far (usually not much) - * @param linkedServiceRecord JSON of the linked Service record (GN's DB "source" "serviceRecord") + * @param collectionInfo collection metadata we've gathered so far (usually not much) + * @param linkedServiceRecord JSON of the linked Service record (GN's DB "source" + * "serviceRecord") */ private void injectLinkedServiceRecordInfo(CollectionInfo collectionInfo, Map linkedServiceRecord) { 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()); From 9909a0e1b9fae8787722029aeca6f19651b72497 Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Tue, 17 Sep 2024 12:09:31 -0700 Subject: [PATCH 04/18] refactor Elastic Index JSON parsing to its own class --- .../ogcapi/records/model/OgcApiContact.java | 3 +- .../ogcapi/records/model/OgcApiTheme.java | 4 +- .../records/util/CollectionInfoBuilder.java | 170 +------------- .../util/ElasticIndexJson2CollectionInfo.java | 216 ++++++++++++++++++ .../geonet/ogcapi/records/util/JsonUtils.java | 42 ++++ 5 files changed, 269 insertions(+), 166 deletions(-) create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/ElasticIndexJson2CollectionInfo.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/JsonUtils.java 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 index c87aeaa8..f52dd7b7 100644 --- 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 @@ -16,6 +16,7 @@ import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import org.fao.geonet.ogcapi.records.util.CollectionInfoBuilder; +import org.fao.geonet.ogcapi.records.util.JsonUtils; import org.springframework.util.StringUtils; /** @@ -128,7 +129,7 @@ public static OgcApiContact fromIndexMap(Map contactMap) { } if (contactMap.get("organisationObject") != null) { result.setOrganization( - CollectionInfoBuilder.getLangString(contactMap.get("organisationObject"))); + JsonUtils.getLangString(contactMap.get("organisationObject"))); } if (StringUtils.hasText((String) contactMap.get("email"))) { result.getEmails().add(new OgcApiEmail(contactMap.get("email").toString())); 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 index 04c9990a..97cb7e08 100644 --- 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 @@ -5,8 +5,8 @@ package org.fao.geonet.ogcapi.records.model; -import static org.fao.geonet.ogcapi.records.util.CollectionInfoBuilder.getAsString; -import static org.fao.geonet.ogcapi.records.util.CollectionInfoBuilder.getLangString; +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; 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 d961d4cb..8c88118c 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 @@ -10,9 +10,7 @@ import static org.fao.geonet.ogcapi.records.util.LinksItemsBuilder.getHref; import java.net.URI; -import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Optional; import javax.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; @@ -22,19 +20,14 @@ import org.fao.geonet.domain.SourceType; import org.fao.geonet.ogcapi.records.controller.model.CollectionInfo; import org.fao.geonet.ogcapi.records.controller.model.CrsEnum; -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.OgcApiSpatialExtent; -import org.fao.geonet.ogcapi.records.model.OgcApiTemporalExtent; -import org.fao.geonet.ogcapi.records.model.OgcApiTheme; import org.fao.geonet.ogcapi.records.service.RecordService; import org.fao.geonet.repository.SettingRepository; import org.fao.geonet.repository.SourceRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; @Service @Slf4j(topic = "org.fao.geonet.ogcapi.records") @@ -49,46 +42,14 @@ public class CollectionInfoBuilder { @Autowired private SettingRepository settingRepository; + @Autowired + ElasticIndexJson2CollectionInfo elasticIndexJson2CollectionInfo; + public CollectionInfoBuilder() { } - /** - * 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; - } /** @@ -149,131 +110,14 @@ public CollectionInfo buildFromSource(Source source, format, collectionUri.toString(), language, configuration); linkList.forEach(collectionInfo::addLinksItem); - var linkedServiceRecord = recordService.getLinkedServiceRecord(request, source); - injectLinkedServiceRecordInfo(collectionInfo, linkedServiceRecord); + var linkedServiceRecord = + recordService.getLinkedServiceRecord(request, source); + elasticIndexJson2CollectionInfo.injectLinkedServiceRecordInfo(collectionInfo, + linkedServiceRecord); return collectionInfo; } - /** - * inject the "extra" info from the LinkedServiceRecord into the CollectionInfo. - * - * @param collectionInfo collection metadata we've gathered so far (usually not much) - * @param linkedServiceRecord JSON of the linked Service record (GN's DB "source" - * "serviceRecord") - */ - private void injectLinkedServiceRecordInfo(CollectionInfo collectionInfo, - Map linkedServiceRecord) { - if (linkedServiceRecord == null) { - return; - } - - //override title from attached ServiceRecord - var title = getLangString(linkedServiceRecord.get("resourceTitleObject")); - if (title != null) { - collectionInfo.setTitle(title); - } - - //override description (abstract) from attached ServiceRecord - var desc = getLangString(linkedServiceRecord.get("resourceAbstractObject")); - if (desc != null) { - collectionInfo.setDescription(desc); - } - - var contacts = linkedServiceRecord.get("contact"); - if (contacts != null && linkedServiceRecord.get("contact") instanceof List) { - var cs = (List) contacts; - collectionInfo.setContacts(new ArrayList<>()); - for (var contactMap : cs) { - var contact = OgcApiContact.fromIndexMap((Map) contactMap); - collectionInfo.getContacts().add(contact); - } - } - OgcApiSpatialExtent spatialExtent = null; - var mySpatialExtent = linkedServiceRecord.get("geom"); - if (mySpatialExtent != null - && mySpatialExtent instanceof List) { - mySpatialExtent = ((List) mySpatialExtent).get(0); - spatialExtent = OgcApiSpatialExtent.fromGnIndexRecord((Map) mySpatialExtent); - } - - OgcApiTemporalExtent temporalExtent = null; - var myTemporalExtent = linkedServiceRecord.get("resourceTemporalDateRange"); - if (myTemporalExtent != null && myTemporalExtent instanceof List) { - myTemporalExtent = ((List) myTemporalExtent).get(0); - temporalExtent = OgcApiTemporalExtent.fromGnIndexRecord( - (Map) myTemporalExtent); - } - - OgcApiExtent extent = new OgcApiExtent(spatialExtent, temporalExtent); - if (spatialExtent != null || temporalExtent != null) { - collectionInfo.setExtent(extent); - } - - var myCrss = linkedServiceRecord.get("coordinateSystem"); - if (myCrss != null && myCrss instanceof List) { - var crss = (List) myCrss; - collectionInfo.setCrs(new ArrayList<>()); - for (var crs : crss) { - collectionInfo.getCrs().add(crs.toString()); - } - } - - var createDate = linkedServiceRecord.get("createDate"); - if (createDate != null && StringUtils.hasText(createDate.toString())) { - collectionInfo.setCreated(createDate.toString()); - } - - var updateDate = linkedServiceRecord.get("changeDate"); - if (updateDate != null && StringUtils.hasText(updateDate.toString())) { - collectionInfo.setUpdated(updateDate.toString()); - } - - var myTags = linkedServiceRecord.get("tag"); - if (myTags != null && myTags instanceof List) { - var tags = (List) myTags; - collectionInfo.setKeywords(new ArrayList<>()); - for (var tag : tags) { - collectionInfo.getKeywords().add(getLangString(tag)); - } - } - - //index record doesn't contain "otherlanguages" - var myMainLang = linkedServiceRecord.get("mainLanguage"); - if (myMainLang != null && StringUtils.hasText(myMainLang.toString())) { - collectionInfo.setLanguage(new OgcApiLanguage(myMainLang.toString())); - } - - var myOtherLangs = linkedServiceRecord.get("otherLanguage"); - if (myOtherLangs != null && myOtherLangs instanceof List) { - var langs = (List) myOtherLangs; - collectionInfo.setLanguages(new ArrayList<>()); - for (var lang : langs) { - collectionInfo.getLanguages().add(new OgcApiLanguage(lang.toString())); - } - } - - var myThemeKeywords = linkedServiceRecord.get("allKeywords"); - collectionInfo.setThemes(OgcApiTheme.parseElasticIndex((Map) myThemeKeywords)); - - //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 - var myLicense = linkedServiceRecord.get("MD_LegalConstraintsUseLimitationObject"); - if (myLicense != null && myLicense instanceof List) { - var myLiceseList = (List) myLicense; - if (myLiceseList.size() > 0) { - var license = getLangString(myLiceseList.get(0)); - collectionInfo.setLicense(license); - } - if (myLiceseList.size() > 1) { - var license2 = getLangString(myLiceseList.get(1)); - collectionInfo.setRights(license2); - } - } - - - } } 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..74895d34 --- /dev/null +++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/ElasticIndexJson2CollectionInfo.java @@ -0,0 +1,216 @@ +package org.fao.geonet.ogcapi.records.util; + +import static org.fao.geonet.ogcapi.records.util.JsonUtils.getLangString; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +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; + + +@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 JSON of the linked Service record (GN's DB "source" + * "serviceRecord") + */ + public void injectLinkedServiceRecordInfo(CollectionInfo collectionInfo, + Map linkedServiceRecord) { + if (linkedServiceRecord == null) { + return; + } + + handleTitle(collectionInfo, linkedServiceRecord); + handleDescription(collectionInfo, linkedServiceRecord); + + handleContacts(collectionInfo, linkedServiceRecord); + + OgcApiSpatialExtent spatialExtent = handleSpatialExtent(linkedServiceRecord); + OgcApiTemporalExtent temporalExtent = handleTemporalExtent(linkedServiceRecord); + handleExtent(collectionInfo, spatialExtent, temporalExtent); + + handleCrs(collectionInfo, linkedServiceRecord); + + handleCreateDate(collectionInfo, linkedServiceRecord); + handleChangeDate(collectionInfo, linkedServiceRecord); + + handleTags(collectionInfo, linkedServiceRecord); + + handleLanguage(collectionInfo, linkedServiceRecord); + handleOtherLangs(collectionInfo, linkedServiceRecord); + + handleThemes(collectionInfo, linkedServiceRecord); + + handleLicenses(collectionInfo, linkedServiceRecord); + } + + + //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 void handleLicenses(CollectionInfo collectionInfo, + Map linkedServiceRecord) { + var myLicense = linkedServiceRecord.get("MD_LegalConstraintsUseLimitationObject"); + if (myLicense != null && myLicense instanceof List) { + var myLiceseList = (List) myLicense; + if (myLiceseList.size() > 0) { + var license = getLangString(myLiceseList.get(0)); + collectionInfo.setLicense(license); + } + if (myLiceseList.size() > 1) { + var license2 = getLangString(myLiceseList.get(1)); + collectionInfo.setRights(license2); + } + } + } + + //process allKeywords to get themes + private void handleThemes(CollectionInfo collectionInfo, + Map linkedServiceRecord) { + var myThemeKeywords = linkedServiceRecord.get("allKeywords"); + collectionInfo.setThemes(OgcApiTheme.parseElasticIndex((Map) myThemeKeywords)); + } + + //process otherLanguage to get the other languages + private void handleOtherLangs(CollectionInfo collectionInfo, + Map linkedServiceRecord) { + var myOtherLangs = linkedServiceRecord.get("otherLanguage"); + if (myOtherLangs != null && myOtherLangs instanceof List) { + var langs = (List) myOtherLangs; + collectionInfo.setLanguages(new ArrayList<>()); + for (var lang : langs) { + collectionInfo.getLanguages().add(new OgcApiLanguage(lang.toString())); + } + } + } + + //process main language + private void handleLanguage(CollectionInfo collectionInfo, + Map linkedServiceRecord) { + var myMainLang = linkedServiceRecord.get("mainLanguage"); + if (myMainLang != null && StringUtils.hasText(myMainLang.toString())) { + collectionInfo.setLanguage(new OgcApiLanguage(myMainLang.toString())); + } + } + + //handle the "tags" to create keywords + private void handleTags(CollectionInfo collectionInfo, Map linkedServiceRecord) { + var myTags = linkedServiceRecord.get("tag"); + if (myTags != null && myTags instanceof List) { + var tags = (List) myTags; + collectionInfo.setKeywords(new ArrayList<>()); + for (var tag : tags) { + collectionInfo.getKeywords().add(getLangString(tag)); + } + } + } + + //process the change date + private void handleChangeDate(CollectionInfo collectionInfo, + Map linkedServiceRecord) { + var updateDate = linkedServiceRecord.get("changeDate"); + if (updateDate != null && StringUtils.hasText(updateDate.toString())) { + collectionInfo.setUpdated(updateDate.toString()); + } + } + + //process the creation date + private void handleCreateDate(CollectionInfo collectionInfo, + Map linkedServiceRecord) { + var createDate = linkedServiceRecord.get("createDate"); + if (createDate != null && StringUtils.hasText(createDate.toString())) { + collectionInfo.setCreated(createDate.toString()); + } + } + + //process CRS + private void handleCrs(CollectionInfo collectionInfo, Map linkedServiceRecord) { + var myCrss = linkedServiceRecord.get("coordinateSystem"); + if (myCrss != null && myCrss instanceof List) { + var crss = (List) myCrss; + collectionInfo.setCrs(new ArrayList<>()); + for (var crs : crss) { + collectionInfo.getCrs().add(crs.toString()); + } + } + } + + //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 handleTemporalExtent(Map linkedServiceRecord) { + OgcApiTemporalExtent temporalExtent = null; + var myTemporalExtent = linkedServiceRecord.get("resourceTemporalDateRange"); + if (myTemporalExtent != null && myTemporalExtent instanceof List) { + myTemporalExtent = ((List) myTemporalExtent).get(0); + temporalExtent = OgcApiTemporalExtent.fromGnIndexRecord( + (Map) myTemporalExtent); + } + return temporalExtent; + } + + //process the spatial extent + private OgcApiSpatialExtent handleSpatialExtent(Map linkedServiceRecord) { + OgcApiSpatialExtent spatialExtent = null; + var mySpatialExtent = linkedServiceRecord.get("geom"); + if (mySpatialExtent != null + && mySpatialExtent instanceof List) { + mySpatialExtent = ((List) mySpatialExtent).get(0); + spatialExtent = OgcApiSpatialExtent.fromGnIndexRecord((Map) mySpatialExtent); + } + return spatialExtent; + } + + //process contracts + private void handleContacts(CollectionInfo collectionInfo, + Map linkedServiceRecord) { + var contacts = linkedServiceRecord.get("contact"); + if (contacts != null && linkedServiceRecord.get("contact") instanceof List) { + var cs = (List) contacts; + collectionInfo.setContacts(new ArrayList<>()); + for (var contactMap : cs) { + var contact = OgcApiContact.fromIndexMap((Map) contactMap); + collectionInfo.getContacts().add(contact); + } + } + } + + //override description (abstract) from attached ServiceRecord + private void handleDescription(CollectionInfo collectionInfo, + Map linkedServiceRecord) { + var desc = getLangString(linkedServiceRecord.get("resourceAbstractObject")); + if (desc != null) { + collectionInfo.setDescription(desc); + } + } + + + //override title from attached ServiceRecord + private void handleTitle(CollectionInfo collectionInfo, Map linkedServiceRecord) { + var title = getLangString(linkedServiceRecord.get("resourceTitleObject")); + if (title != null) { + collectionInfo.setTitle(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..ec6587e9 --- /dev/null +++ b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/JsonUtils.java @@ -0,0 +1,42 @@ +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; + } +} From 6623ea7497babcb07367c12f8eba47baff00d36a Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Tue, 17 Sep 2024 14:27:12 -0700 Subject: [PATCH 05/18] add test case for ElasticIndexJson2CollectionInfo --- .../records/model/OgcApiSpatialExtent.java | 7 +- .../geonet/ogcapi/records/util/JsonUtils.java | 18 + .../ElasticIndexJson2CollectionInfoTest.java | 134 +++ .../util/sampleCollectionServiceRecord.xml | 787 ++++++++++++++++++ ...leCollectionServiceRecordElasticIndex.json | 538 ++++++++++++ 5 files changed, 1482 insertions(+), 2 deletions(-) create mode 100644 modules/services/ogc-api-records/src/test/java/org/fao/geonet/ogcapi/records/util/ElasticIndexJson2CollectionInfoTest.java create mode 100644 modules/services/ogc-api-records/src/test/resources/org/fao/geonet/ogcapi/records/util/sampleCollectionServiceRecord.xml create mode 100644 modules/services/ogc-api-records/src/test/resources/org/fao/geonet/ogcapi/records/util/sampleCollectionServiceRecordElasticIndex.json 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 index 3d9bbaa3..b74afc61 100644 --- 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 @@ -5,6 +5,8 @@ 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; @@ -14,6 +16,7 @@ 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 * @@ -112,8 +115,8 @@ public static OgcApiSpatialExtent fromGnIndexRecord(Map map) { for (var myCoord : coords) { var coord = (List) myCoord; - var x = (Double) coord.get(0); - var y = (Double) coord.get(1); + var x = getAsDouble(coord.get(0)); + var y = getAsDouble(coord.get(1)); if (x < xmin) { xmin = x; } 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 index ec6587e9..b3bd7e58 100644 --- 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 @@ -1,3 +1,8 @@ +/** + * (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; @@ -39,4 +44,17 @@ public static String getAsString(Object o) { 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/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 From dd1db6e7235508cc6eb55c1c86928f002492c8c1 Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Wed, 18 Sep 2024 11:36:02 -0700 Subject: [PATCH 06/18] fixes from joses review --- .../controller/CapabilitiesApiController.java | 31 ++-- .../controller/CollectionApiController.java | 15 +- .../controller/model/CollectionInfo.java | 107 +++++++++++- .../records/controller/model/CrsEnum.java | 5 + .../ogcapi/records/controller/model/Root.java | 8 + .../ogcapi/records/model/OgcApiComponent.java | 18 ++ .../ogcapi/records/model/OgcApiConcept.java | 3 +- .../ogcapi/records/model/OgcApiContact.java | 57 ++++--- .../model/OgcApiExternalDocumentation.java | 58 +++++++ .../ogcapi/records/model/OgcApiInfo.java | 72 ++++++++ .../ogcapi/records/model/OgcApiLicense.java | 58 +++++++ .../ogcapi/records/model/OgcApiLink.java | 15 ++ .../ogcapi/records/model/OgcApiPath.java | 19 +++ .../ogcapi/records/model/OgcApiReference.java | 37 +++++ .../ogcapi/records/model/OgcApiSchema.java | 154 ++++++++++++++++++ .../records/model/OgcApiSchemaContact.java | 76 +++++++++ .../model/OgcApiSecurityRequirement.java | 39 +++++ .../ogcapi/records/model/OgcApiServer.java | 59 +++++++ .../records/model/OgcApiSpatialExtent.java | 31 ++-- .../ogcapi/records/model/OgcApiTag.java | 47 ++++++ .../records/model/OgcApiTemporalExtent.java | 1 - .../ogcapi/records/model/OgcApiTheme.java | 10 +- .../records/util/CollectionInfoBuilder.java | 10 -- .../geonet/ogcapi/records/util/JsonUtils.java | 1 + 24 files changed, 838 insertions(+), 93 deletions(-) create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiComponent.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiExternalDocumentation.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiInfo.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiLicense.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiPath.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiReference.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSchema.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSchemaContact.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiSecurityRequirement.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiServer.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/OgcApiTag.java 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 0a4a7099..02b97305 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 @@ -22,7 +22,6 @@ 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.service.RecordService; import org.fao.geonet.ogcapi.records.util.CollectionInfoBuilder; import org.fao.geonet.ogcapi.records.util.LinksItemsBuilder; import org.fao.geonet.ogcapi.records.util.MediaTypeUtil; @@ -69,25 +68,17 @@ public class CapabilitiesApiController { @Autowired ConcurrentMapCacheManager cacheManager; - - @Autowired - private SourceRepository sourceRepository; - - @Autowired - private SearchConfiguration configuration; - @Autowired MediaTypeUtil mediaTypeUtil; - @Autowired CollectionInfoBuilder collectionInfoBuilder; - @Autowired - RecordService recordService; + private SourceRepository sourceRepository; + @Autowired + private SearchConfiguration configuration; /** * Landing page end-point. - * */ @io.swagger.v3.oas.annotations.Operation( summary = "Landing page.", @@ -126,7 +117,7 @@ public ResponseEntity getLandingPage(@ApiIgnore HttpServletRequest request CollectionInfo collectionInfo = collectionInfoBuilder .buildFromSource(source, language, requestBaseUrl, - configuration.getFormat(mediaType), configuration,request); + configuration.getFormat(mediaType), configuration, request); root.setSystemInfo(collectionInfo); } @@ -137,10 +128,10 @@ public ResponseEntity getLandingPage(@ApiIgnore HttpServletRequest request configuration.getFormats(Operations.root).forEach(f -> root.addLinksItem(new OgcApiLink() - .href(requestBaseUrl + "collections?f=" + f.getName()) - .type("Catalogue collections") - .rel("self") - .type(f.getMimeType()))); + .href(requestBaseUrl + "collections?f=" + f.getName()) + .type("Catalogue collections") + .rel("self") + .type(f.getMimeType()))); addOpenApiLinks(root, requestBaseUrl); addConformanceLinks(root, requestBaseUrl); @@ -185,7 +176,6 @@ private void addConformanceLinks(Root root, String baseUrl) { .rel(CONFORMANCE_REL) .type(MediaType.TEXT_HTML_VALUE)); - root.addLinksItem(new OgcApiLink() .href(baseUrl + CONFORMANCE_REL + "?f=json") .title(title + " as JSON") @@ -226,7 +216,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); @@ -248,7 +238,6 @@ public ResponseEntity conformanceDeclaration(@ApiIgnore HttpServlet /** * Collections information end-point. - * */ @io.swagger.v3.oas.annotations.Operation( summary = "Collections available from this API.", @@ -282,7 +271,7 @@ public ResponseEntity describeCollections(@ApiIgnore HttpServletRequest sources.forEach(s -> content.addCollectionsItem( collectionInfoBuilder.buildFromSource( s, language, requestBaseUrl, configuration.getFormat(mediaType), - configuration,request))); + configuration, request))); // TODO: Accept format parameter. List linkList = LinksItemsBuilder.build( 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 d1355a64..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 @@ -67,21 +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. @@ -136,7 +131,7 @@ public ResponseEntity describeCollection( CollectionInfo collectionInfo = collectionInfoBuilder .buildFromSource(source, language, requestBaseUrl, - configuration.getFormat(mediaType), configuration,request); + 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/model/CollectionInfo.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/model/CollectionInfo.java index a7609f8a..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 @@ -21,6 +21,7 @@ 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; /** @@ -33,52 +34,70 @@ @XmlAccessorType(XmlAccessType.FIELD) 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 + // 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.*/ + /** + * The identifier of the catalog. + */ @JsonProperty("id") @JacksonXmlProperty(localName = "id") private String id; - /**A human-readable name given to the resource.*/ + /** + * A human-readable name given to the resource. + */ @JsonProperty("title") @JacksonXmlProperty(localName = "title") private String title; - /**A free-text account of the resource.*/ + /** + * A free-text account of the resource. + */ @JsonProperty("description") @JacksonXmlProperty(localName = "description") private String description; - /**links for this object.*/ + /** + * links for this object. + */ @JsonProperty("links") @JacksonXmlProperty(localName = "links") @JsonInclude(Include.NON_EMPTY) private List links = new ArrayList<>(); - /**The spatiotemporal coverage of this catalog.*/ + /** + * The spatiotemporal coverage of this catalog. + */ @JsonProperty("extent") @JacksonXmlProperty(localName = "extent") private OgcApiExtent extent; - /**The list of supported coordinate reference systems.*/ + /** + * 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. */ + /** + * 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) @@ -171,6 +190,76 @@ public class CollectionInfo { 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; } 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 index bf59e2f8..cc2dfe11 100644 --- 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 @@ -1,3 +1,8 @@ +/** + * (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; 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 518f9148..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 @@ -26,6 +26,14 @@ 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") 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 index 7b508525..7d8f1918 100644 --- 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 @@ -15,8 +15,7 @@ * 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. + * system. it is recommended that a resolvable URI be used for each entity/concept identifier. */ public class OgcApiConcept { 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 index f52dd7b7..27b1cbeb 100644 --- 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 @@ -15,7 +15,6 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; -import org.fao.geonet.ogcapi.records.util.CollectionInfoBuilder; import org.fao.geonet.ogcapi.records.util.JsonUtils; import org.springframework.util.StringUtils; @@ -23,63 +22,81 @@ * 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. + * for the resource. */ @XmlRootElement(name = "contact") @XmlAccessorType(XmlAccessType.FIELD) public class OgcApiContact { - /**A value uniquely identifying a contact.*/ + /** + * 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.*/ + /** + * 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.*/ + /** + * 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.*/ + /** + * 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.*/ + /** + * 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.*/ + /** + * 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.*/ + /** + * 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.*/ + /** + * 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.*/ + /** + * On-line information about the contact. + */ @JsonInclude(Include.NON_EMPTY) @XmlElementWrapper(name = "links") @XmlElement(name = "links") @@ -89,22 +106,24 @@ public class OgcApiContact { * 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.*/ + /** + * 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.).*/ + /** + * 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") 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/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 index 750af456..69897aac 100644 --- 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 @@ -17,6 +17,15 @@ /** * 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") @@ -60,6 +69,8 @@ public class OgcApiLink { /** * undocumented in spec. + * + *

This probably shouldn't be used because its not defined. */ @JsonInclude(Include.NON_DEFAULT) @XmlElementWrapper(name = "length") @@ -68,6 +79,8 @@ public class OgcApiLink { /** * 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") @@ -77,6 +90,8 @@ public class OgcApiLink { /** * 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") 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/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 index b74afc61..35d62da5 100644 --- 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 @@ -31,10 +31,10 @@ public class OgcApiSpatialExtent { * 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. + *

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. * *

---- * @@ -53,17 +53,17 @@ public class OgcApiSpatialExtent { * 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 + *

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. + * whether only a single spatial geometry property is used to determine the extent or all relevant + * geometries. */ @JsonInclude(Include.NON_EMPTY) @XmlElementWrapper(name = "bbox") @@ -75,7 +75,7 @@ public class OgcApiSpatialExtent { * 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' */ @@ -87,13 +87,12 @@ public class OgcApiSpatialExtent { /** * 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]]]}] - * + * [[[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 + * "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 @@ -116,7 +115,7 @@ public static OgcApiSpatialExtent fromGnIndexRecord(Map map) { for (var myCoord : coords) { var coord = (List) myCoord; var x = getAsDouble(coord.get(0)); - var y = getAsDouble(coord.get(1)); + var y = getAsDouble(coord.get(1)); if (x < xmin) { xmin = x; } 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 index 37617423..48a90f0d 100644 --- 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 @@ -18,7 +18,6 @@ * 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 { 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 index 97cb7e08..d1f3cd38 100644 --- 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 @@ -18,14 +18,14 @@ import org.springframework.util.StringUtils; /** - * cf https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/theme.yaml + * cf https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/theme.yaml * - *

This is the ogcapi "theme" object model. + *

This is the ogcapi "theme" object model. * - *

From ogcapi-records spec: + *

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. + *

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 { 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 8c88118c..31319318 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 @@ -35,13 +35,6 @@ public class CollectionInfoBuilder { @Autowired RecordService recordService; - - @Autowired - private SourceRepository sourceRepository; - - @Autowired - private SettingRepository settingRepository; - @Autowired ElasticIndexJson2CollectionInfo elasticIndexJson2CollectionInfo; @@ -50,8 +43,6 @@ public CollectionInfoBuilder() { } - - /** * Build Collection info from source table. * @@ -119,5 +110,4 @@ public CollectionInfo buildFromSource(Source source, } - } 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 index b3bd7e58..fa4c5481 100644 --- 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 @@ -8,6 +8,7 @@ import java.util.Map; public class JsonUtils { + /** * todo - be language aware (send in desired language). Move to utility class. * From acfc2840cbcff74302984463a9b08677d6016252 Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Wed, 18 Sep 2024 16:13:27 -0700 Subject: [PATCH 07/18] added documentation --- docs/README.md | 0 docs/manual/README.md | 0 docs/manual/docs/index.md | 17 ++ docs/manual/docs/ogcapi/catalog.md | 41 +++++ docs/manual/docs/ogcapi/conformance.md | 4 + docs/manual/docs/ogcapi/index.md | 15 ++ docs/manual/docs/ogcapi/landingpage.md | 26 +++ docs/manual/docs/ogcapi/openapi.md | 4 + docs/manual/docs/ogcapi/record-collection.md | 7 + docs/manual/docs/ogcapi/record-collections.md | 11 ++ docs/manual/docs/ogcapi/record.md | 6 + docs/manual/docs/ogcapi/records.md | 4 + docs/manual/mkdocs.yml | 152 ++++++++++++++++++ .../overrides/.icons/geonetwork/logo.svg | 28 ++++ .../overrides/.icons/geonetwork/logo_bw.svg | 15 ++ .../assets/images/geonetwork-logo.png | Bin 0 -> 6172 bytes .../assets/images/geonetwork-logo.svg | 21 +++ .../overrides/assets/stylesheets/extra.css | 79 +++++++++ docs/manual/overrides/main.html | 6 + docs/manual/overrides/partials/copyright.html | 41 +++++ docs/manual/pom.xml | 108 +++++++++++++ docs/manual/requirements.txt | 5 + docs/manual/src/assembly/guide.xml | 13 ++ docs/pom.xml | 45 ++++++ 24 files changed, 648 insertions(+) create mode 100644 docs/README.md create mode 100644 docs/manual/README.md create mode 100644 docs/manual/docs/index.md create mode 100644 docs/manual/docs/ogcapi/catalog.md create mode 100644 docs/manual/docs/ogcapi/conformance.md create mode 100644 docs/manual/docs/ogcapi/index.md create mode 100644 docs/manual/docs/ogcapi/landingpage.md create mode 100644 docs/manual/docs/ogcapi/openapi.md create mode 100644 docs/manual/docs/ogcapi/record-collection.md create mode 100644 docs/manual/docs/ogcapi/record-collections.md create mode 100644 docs/manual/docs/ogcapi/record.md create mode 100644 docs/manual/docs/ogcapi/records.md create mode 100644 docs/manual/mkdocs.yml create mode 100644 docs/manual/overrides/.icons/geonetwork/logo.svg create mode 100644 docs/manual/overrides/.icons/geonetwork/logo_bw.svg create mode 100644 docs/manual/overrides/assets/images/geonetwork-logo.png create mode 100644 docs/manual/overrides/assets/images/geonetwork-logo.svg create mode 100644 docs/manual/overrides/assets/stylesheets/extra.css create mode 100644 docs/manual/overrides/main.html create mode 100755 docs/manual/overrides/partials/copyright.html create mode 100644 docs/manual/pom.xml create mode 100644 docs/manual/requirements.txt create mode 100644 docs/manual/src/assembly/guide.xml create mode 100644 docs/pom.xml diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/manual/README.md b/docs/manual/README.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/manual/docs/index.md b/docs/manual/docs/index.md new file mode 100644 index 00000000..86de52fd --- /dev/null +++ b/docs/manual/docs/index.md @@ -0,0 +1,17 @@ +--- +hide: + - navigation +--- + +# GeoNetwork MicroServices {#toc} + +Welcome to GeoNetwork MicroServices. + +

+ +- :fontawesome-solid-signs-post: [OGCAPI-Records](ogcapi/index.md) + + --- + + Implementation of OGCAPI-Records. +
\ No newline at end of file diff --git a/docs/manual/docs/ogcapi/catalog.md b/docs/manual/docs/ogcapi/catalog.md new file mode 100644 index 00000000..199a540c --- /dev/null +++ b/docs/manual/docs/ogcapi/catalog.md @@ -0,0 +1,41 @@ +# Catalog.yaml definition {#toc} + + +See [Catalog Definition in the OGCAPI specification](https://docs.ogc.org/DRAFTS/20-004.html#req_records-api_catalog-response). + +The properties come from the source's (portal sub-portal) in the GeoNetwork "sources" table, column "serviceRecord". +This can be configured in the `GN GUI: Admin Console-Settings-sources` for sub-portals. + +There is no GUI to set this for the "main" portal - do this in GN GUI: Admin Console-Settings-Settings-Catalog Service for the Web (CSW)-Record to use for GetCapabilities (`system/csw/capabilityRecordUuid`). + + +|Property| Description | Elastic Index JSON Property | +|--------| ----------- | --------------------------- | +| id | A unique identifier for this catalog. | metadataIdentifier| +| created | The date this collection was created. | createDate| +| updated | The more recent date on which this collection was changed. |changeDate | +| conformsTo | The extensions/conformance classes used in this catalog object. | `not used` | +| type | Fixed value of "Catalog". | always "Catalog" | +| itemType | Fixed value of "record", "catalog" or both. | always "record"| +| title | A human-readable name given to this catalog. | resourceTitleObject| +| description | A free-text description of this catalog. | resourceAbstractObject| +| extent | The spatiotemporal coverage of this catalog. | spatial: geom
temporal: resourceTemporalDateRange | +| crs | A list of coordinate reference systems used for spatiotemporal values. | coordinateSystem| +| keywords | A list of free-form keywords or tags associated with this collection. |tag | +themes | A knowledge organization system used to classify this collection. | allKeywords| +| language | The language used for textual values (i.e. titles, descriptions, etc.) of this collection object. |mainLanguage | +| languages | The list of other languages in which this collection object is available. |otherLanguage | +| recordLanguages | The list of languages in which records from the collection can be represented. |`not used` | +| contacts | A list of contacts qualified by their role(s). |contact | +| license | The legal provisions under which this collection is made available. |MD_LegalConstraintsUseLimitationObject | +| rights | A statement that concerns all rights not addressed by the license such as a copyright statement. | `not used`| +| recordsArrayName | The name of the array property in the catalog used to encode records in-line. The default value is records. | `not used`| +| records | An array of records encoded in-line in the catalog. |`not used` | +| links | A list of links related to this catalog. | `filled with Java code`| +| linkTemplates | A list of link templates related to this catalog. | `not used`| +| schemes | A list of schemes related to this catalog. |`not used`| + +See `ElasticIndexJson2CollectionInfo.java` for more details. + +NOTE: For `Contact.address`, we only fill in the `deliveryPoint` because that's all that's available in the Elastic Index JSON. + diff --git a/docs/manual/docs/ogcapi/conformance.md b/docs/manual/docs/ogcapi/conformance.md new file mode 100644 index 00000000..d377e007 --- /dev/null +++ b/docs/manual/docs/ogcapi/conformance.md @@ -0,0 +1,4 @@ +# Conformance {#toc} + + +See [Conformance in the OGCAPI specification](https://docs.ogc.org/DRAFTS/20-004.html#conformance_class). diff --git a/docs/manual/docs/ogcapi/index.md b/docs/manual/docs/ogcapi/index.md new file mode 100644 index 00000000..0703666f --- /dev/null +++ b/docs/manual/docs/ogcapi/index.md @@ -0,0 +1,15 @@ + +# GeoNetwork MicroServices OGCAPI-Records {#toc} + + +This service module implements the ([OGCAPI-Records](https://ogcapi.ogc.org/records)). + +| Endpoint Name | Endpoint Location | Meaning | +| -------- | ------- | -------- | +| [Landing Page](landingpage) | / | Home Page | +| [Conformance declaration](conformance) | /conformance | Conformance Documents | +| [OpenAPI Documentation](openapi) | /openapi | OpenAPI (Swagger) description document | +| [Record collections](record-collections) | /collections | List of Catalogs
(GN portals) | +| [Record collection](record-collection) | /collections/{collectionId} | Information about a single Catalog
(GN portal) | +| [Records](records) | /collections/{collectionId}/items | Records in a Catalog
Search, etc... | +| [Record](record) | /collections/{collectionId}/items/{recordId} | Single Metadata Record | \ No newline at end of file diff --git a/docs/manual/docs/ogcapi/landingpage.md b/docs/manual/docs/ogcapi/landingpage.md new file mode 100644 index 00000000..b79f840e --- /dev/null +++ b/docs/manual/docs/ogcapi/landingpage.md @@ -0,0 +1,26 @@ +# Landing Page {#toc} + +The Landing Page will show information about the server. It has links to the [Conformance declaration](conformance), the [OpenAPI Documentation](openapi), and the [Record collections](record-collections). + +See [Landing Page in the OGCAPI specification](https://docs.ogc.org/is/17-069r3/17-069r3.html#_api_landing_page). + + +## JSON Model + +The JSON model follows the ["landingPage.yml"](https://schemas.opengis.net/ogcapi/features/part1/1.0/openapi/schemas/landingPage.yaml) definition in the OGCAPI Specification. + +| JSON Tag| Meaning | +| -------- | ------- | +| title | Title for the server | +| description | Description of the server | +| links | Links to other documents | + + +The GeoNetwork OGCAPI-Records implementation also include a non-standard item: + +| JSON Tag| Meaning | +| -------- | ------- | +| systemInfo | Metadata for the Server. See the [catalog.yaml](../catalog) description. | + + +This metadata information is taken from the Service Record linked to the GeoNetwork's main portal. diff --git a/docs/manual/docs/ogcapi/openapi.md b/docs/manual/docs/ogcapi/openapi.md new file mode 100644 index 00000000..3c229492 --- /dev/null +++ b/docs/manual/docs/ogcapi/openapi.md @@ -0,0 +1,4 @@ +# OpenApi Description Document {#toc} + +This is the [OpenApi (Swagger) Description Document](https://swagger.io/). + diff --git a/docs/manual/docs/ogcapi/record-collection.md b/docs/manual/docs/ogcapi/record-collection.md new file mode 100644 index 00000000..7c6d18a2 --- /dev/null +++ b/docs/manual/docs/ogcapi/record-collection.md @@ -0,0 +1,7 @@ +# Record Collection {#toc} + +See [Record Collection in the OGCAPI specification](https://docs.ogc.org/DRAFTS/20-004.html#clause-record-collection). + +##JSON Model + +See the [catalog.yaml](../catalog) description. \ No newline at end of file diff --git a/docs/manual/docs/ogcapi/record-collections.md b/docs/manual/docs/ogcapi/record-collections.md new file mode 100644 index 00000000..7dcfe581 --- /dev/null +++ b/docs/manual/docs/ogcapi/record-collections.md @@ -0,0 +1,11 @@ +# Record Collections {#toc} + +This gives a summary of all the Record Collections (Catalogs) on the Server. In GeoNetwork this is the main portal, plus any sub-portals described. + + +See [Record Collections in the OGCAPI specification](https://docs.ogc.org/DRAFTS/20-004.html#record-collections). + + +## JSON MODEL + +See the [catalog.yaml](../catalog) description. \ No newline at end of file diff --git a/docs/manual/docs/ogcapi/record.md b/docs/manual/docs/ogcapi/record.md new file mode 100644 index 00000000..52985c11 --- /dev/null +++ b/docs/manual/docs/ogcapi/record.md @@ -0,0 +1,6 @@ +# Record {#toc} + +See [Record in the OGCAPI specification](https://docs.ogc.org/DRAFTS/20-004.html#clause-record-core). + + + \ No newline at end of file diff --git a/docs/manual/docs/ogcapi/records.md b/docs/manual/docs/ogcapi/records.md new file mode 100644 index 00000000..0c259aeb --- /dev/null +++ b/docs/manual/docs/ogcapi/records.md @@ -0,0 +1,4 @@ +# Records {#toc} + +See [Records in the OGCAPI specification](https://docs.ogc.org/DRAFTS/20-004.html#clause-record-collection). + diff --git a/docs/manual/mkdocs.yml b/docs/manual/mkdocs.yml new file mode 100644 index 00000000..73c44668 --- /dev/null +++ b/docs/manual/mkdocs.yml @@ -0,0 +1,152 @@ +# Project information +site_name: GeoNetwork Opensource Microservices +site_description: GeoNetwork Opensource Microservices. +site_dir: target/html +site_url: https://docs.geonetwork-opensource.org/ + +# Repository +repo_name: geonetwork-microservices +repo_url: https://github.com/geonetwork/geonetwork-microservices +edit_uri: edit/main/docs/manual/docs + +# Copyright +copyright: Copyright © 2024 FAO-UN and others + +extra_css: + - assets/stylesheets/extra.css + +# Configuration +theme: + name: material + language: en + custom_dir: overrides + logo: assets/images/geonetwork-logo.svg + favicon: assets/images/geonetwork-logo.png + icon: + repo: fontawesome/brands/github + palette: + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: blue + toggle: + icon: material/weather-night + name: Switch to dark mode + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + toggle: + icon: material/weather-sunny + name: Switch to light mode + features: + - toc.follow + - navigation.tracking + - navigation.top + - navigation.tabs + - navigation.prune + - navigation.indexes + - navigation.footer + - header.autohide + - content.tabs.link + - content.code.copy + - content.action.view + - content.action.edit + - announce.dismiss + +# Plugins - install using: pip3 install -r requirements.txt +plugins: + - exclude: + glob: + - annexes/gallery/bin/README.md + - i18n: + docs_structure: suffix + reconfigure_material: true + languages: + - locale: en + name: English + build: true + default: true + site_name: 'GeoNetwork MicroServices OpenSource' + - locale: fr + name: Français + build: !ENV [FRENCH,true] + site_name: 'GeoNetwork MicroServices OpenSource' + site_description: Catalogue GeoNetwork pour répertorier, rechercher et examiner les enregistrements. + nav_translations: + Home: Home + Search: Search + Record: Record + Map: Map + - search + +# Customizations +extra: + version: + provider: mike + default: stable + alias: true + homepage: https://geonetwork-opensource.org/ + social: + - icon: fontawesome/brands/github + link: https://github.com/geonetwork + - icon: fontawesome/brands/docker + link: https://hub.docker.com/_/geonetwork + - icon: geonetwork/logo_bw + link: https://geonetwork-opensource.org/ + name: GeoNetwork Website + +# For use with --strict to produce failures on build warnings +validation: + nav: + omitted_files: warn + not_found: warn + absolute_links: warn + links: + not_found: warn + absolute_links: warn + unrecognized_links: warn + +# Extensions +# - These are carefully chosen to work with pandoc markdown support for whole document translation +markdown_extensions: + - admonition + - attr_list + - def_list + - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + options: + custom_icons: + - overrides/.icons + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.keys + - pymdownx.smartsymbols + - pymdownx.snippets + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - pymdownx.tabbed: + alternate_style: true + - tables + - md_in_html + +# Page tree +nav: + - 'GeoNetwork MicroServices': index.md + - 'OGCAPI-Records': + - ogcapi/index.md + - ogcapi/landingpage.md + - ogcapi/conformance.md + - ogcapi/openapi.md + - ogcapi/record-collections.md + - ogcapi/record-collection.md + - ogcapi/records.md + - ogcapi/record.md + diff --git a/docs/manual/overrides/.icons/geonetwork/logo.svg b/docs/manual/overrides/.icons/geonetwork/logo.svg new file mode 100644 index 00000000..d1ebaec7 --- /dev/null +++ b/docs/manual/overrides/.icons/geonetwork/logo.svg @@ -0,0 +1,28 @@ + + + + gn_logo + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/manual/overrides/.icons/geonetwork/logo_bw.svg b/docs/manual/overrides/.icons/geonetwork/logo_bw.svg new file mode 100644 index 00000000..61c0c2d9 --- /dev/null +++ b/docs/manual/overrides/.icons/geonetwork/logo_bw.svg @@ -0,0 +1,15 @@ + + + + gn_logo + Created with Sketch. + + + + diff --git a/docs/manual/overrides/assets/images/geonetwork-logo.png b/docs/manual/overrides/assets/images/geonetwork-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f8926bf7e122052a8f7cfc57ccd64294efdce3fa GIT binary patch literal 6172 zcmV+%7~|)OP)00001b5ch_0Itp) z=>Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91N1y`$1ONa40RR91MF0Q*0InmkivR!@hDk(0RCodHTzha`#d$w_&bdcd z@24$Wk}WV8Fb+?H9}r#*NoSIwP1><(Nt4=+feCpOCQZkk%mn|@khJ5FY12SS95A#= z2?o-cz%a=`Ob8g;*gOp8VPhLhvTPyiVe9H~?|E#0zi-dEx{@XNB>`!=@}7NtyZe2= z{dV`;bM{(UAFNZe7p<75m357_tn;+unwUyp$YB{q z_d7GLTEQtf>CTz-7UKp>YA@L8rc?>0(|lwVO-(;)N2K&E2RE&MWh&aAvT2nt+pyr4 z5<3-KV@1|A$h*i=nqW1+pX|W&1L!?isCU3`vm^a<%S#XLO!}FMaLU0Mlj&>MxD9=K z8yuCYh@7y@igc;%IHe4+cn8Y1Gb&X5$`1O2(8<^-)!FvqPrJu#>;E%990aCZb2e_g z*3ER<_0WnsTZNUdajKM)tyJ2sw3I4RTBnegvfW%tA=HtI5c*;0`q-|0l=;fCGlB0O zQQGRYb#x>MQoY$C=g_icPal~|*d_&>#~=J$Z7}4{vt0jN*UOy?gY6vG%{3`YH9+Wk z7&zZ-ky1| z>)q?tJR6K?;3N|XpC?uAp&zZ9XGg*H%JmmRICy^!8*rSQ2rEKM+?RF}005Mv0h)wK z8sJGf|A6XwBD~mu%98?TZXD|xAZIQAzBmP3MDt>0!S5D4g zJYjIw-F@5Ht_p93pnndb=91F{3~GZ23K9B_IG4h6I5jg(FZBN4t#E*=`mtVvO5%qGzmXcDtWXbxyinF%!-BtS_X0B%S8 zuA5ii{Uh=-o|_PyfBUC97M2zTPeRx_3Y-_#$;!yP0tB=sPU8?M+9X9{qU5XeUlff9R8a%fJ-Sv0lo{ zu><~D`ULk}}KoP((JyUUeNq(N6 zQ9LC5lP@|>OYtUU`GGa`)&c*{hQ)t-rDTnIj0w)H#Vc-vs@$}g>%Vx+8Qt|U+|*zAg;G@beoUxd*{2r1 z(c-m!;!@pl{U!YudT!1mAU3^#M6F2{UNTC+OvsgzGzx+hx|WVQ7=F|g=3>I~pUqb_ zV~$l}RijnNQ4m@dX!Madt|bH_zh<$3<>F0uoqbNvqt7{eceUz|p~GsDfQ>UOH@6zm zZ!?{^W599K?mwcikpyIy4d--f)>E&fXFc^=^I=!oqFMU9p_B1_A3M!&?Fkf zG&PJP$dMk)b!EycWJ*P$rLpGN8gwv7a3~#%e9SyfH?G?RDRGG~C_$EKD@403oh64~ zc)e=>_P0x``iJbZP`E%yl8OjnR{TBQL9J`w{!mMjowO(E#zm`O)rQ}Y1bXHlY+I)Lx=qaT=w3YIGjkt?= zRAJi+qpqtk-Kt@kPX`taSdE(Ywp2CmZL7$=xu>>&PisZG>xg$I2AOXkkM{<$*mmr@ zK=(H>Gb8b9T(n{>Fpt3jAW(e)hdYXZ8JZyp4S|1-8sv@MJfp>=mQI-0w|HJ4@QGTiy$#~ z7iGYJP6{XjCh8Xhv~xjnE|rxoOc{Q0XcUFfNGePv0hOSDjDA>yq}qr_Q=5gYO}&^5 zrx+LLB(af35TAO&b;9TlRKJ}~=8LwqF7vX;(mt9B#wO zwhXKRsHvOKA2X}!iyH=k;J`S=lbOA$?#h+rY%mWF=H(YS ze1>r_8BZG-T{50{RFWwr5c|rMbH#LJE+HU<65~AvGt~I2w6k z=*1dEu%KyqpyN8p0GLS_cRFUEx`c@_%O)=q82mL`lQk!df@NelFBGR1Jl7lM%X+}_ zWOO;g^a29P@n@z|tcV0s>KSpd2m@8K!Lm~^5hNs`5tlKOlB_Ww;Ex~sEJd>k>(>;z zPtN}MP1OPpq>rVF8KriN0z*_D`2REnpG(Hu@01D?^g z0*#9EN77Rx6CY2vWNIc0b*!lqEy$D@TX&yn8~qc|sdJCEZCSrtz&WsGU0dMlMKH9U zlhMvIAA=wh2a`v@!b60`Zbi^I89*;<5%UYz7{rsqCF?|v*{x)4lBqZf=wSsOTWmWu zm>)42xv|MkBbVm(+R9Ak^K8TDKO!B^t=os-n#SwC_8EkCK;%Dx2I2iTMhsIxb{ko9 zVn8_eL1?rno?fD10F-qAMl!gOMx6ExhXR$%asi?cwhlVL1TzK45m;N%Ao)-sre)ke zDo%#WVAWg~S?>3#C~a|zZqt&b#bQIm83#80@JU3UZ1~i-W`XOaP~*=)XRZPi<_(kV zNW;^_Xc465)Bqq%6C4RCGab_m;xq%v!h-`zWb^A(? zU4l`&0RAihV9P=sgCcRN2PH;cvY#+ZrX4gzz#uyC-0(;w9Y+DOSTMfB$PU^TU&pcr zhD#b7!S;BSiQ+C$Zbg+%j^(_tfAhMxrIGP!Am7EOj2j!TxMgw9bH6;^b&mF5t)XSQ zG4|42!wvV9WGjOC0O10Nc_xIPjm2j+G!17R)dVf_k_A>{8qf(y3<(THkP7Nq?Z3K}fV~v^!Xm`AD!pw(y|TPo2vP0g1dZ==)mDg; zw6bb6KKnudynL}e2;eC#FNLvM%f+QpZ51Q~`+2N8w_|&lcw?+J1swkIL{-z`l{vho#3&})Fc(`auH2bY z9zHWTFTh%U9^ocsd#`l-@|*AfvA_dxRt;#Sie;xsL9+o8fuc`N+{QQ*u&_KhodE9* z0Jbyoyf48y{ty;z-^u#EucDk2 z4f<{r`B^oX^ZW|D*6Yme+;As$7#_+#JO|DB(k=)!SByOD7+iNEIaC_$5#b4h5gs9c za*Ohy8IS;sSjGla+0rQ!Ep0k%;dJumkLM|F=`afI5Zf}(TinLE1JO_na`|c)z{4KY>#?=&LCyD-ids};x8z*ErDM~7cA~q}1{P>UxW<8z zm)6i8n92*qOfpGL;*Oj2$2`VGYnX2Y$L$Is5&MNvd>A5l6r4^DnrxpZba-Q~7)zS~ zv9A#lMIr1kc&ROVGz|cuzUhehj0rDb5N9td6}nc_q7^jgTH$wtVh4y`SCO-0x^~{# zzWv9vc}~c=XnYTtzl`6GXoQhfVLXu#&4`^shM+NI#V~~@HGF*Soa|EUkOGq#&I&%rF+A1X6iN+f1)z-WI93r}8Ht?; zia6yIP}mFwX0h2uNz&IF?GZ*M77=fZSEbU19c#!_&9p1iKm!aRw5Z+NV zIGAf%xbnAJML*Yp_gMS(AJcZrJEy$q65b+TmVc!~hA@qAFApr5>6TE7xlT(q21@}r z(vfRc6G2QM!iY5Bgr`>lhh@|fY8>O#8u1X8NF}tes7Yr4BUq>t_o3qHSEyOi*r`1r zfOR&s@N@X^_~nXOmzEUQeC(zEjvX@fa2HW;L&sPyI3%&UlKDi9MQWCEG(jTF8O9Ul zr7Gz;nxYxdupo>52p{(7x$q|WsGkQ#0KgSVX>&N=@Pf|RH+{^H^evqsZ}MFY1iaHN zcHd}PvK&Vu(=`^b<==%0td{MXyHdP`8~es=hdiD9IGc0~JrVWC739 zQTter?MxjoY`Fs8z#ksb$Fys2D@$dA<;t;d#^_&|w_Ug>QyXHtC9h$z27%jHgOYb= zCOra7YDg@SfIt>xI>FwFf#M>ZXovtJQ*l_k>;Ovv+adi_I&F2fl{7bu0H?6OnOEO7 zPdV-l_%{9N5btsTPP%XkB~wa3qtKjY{6lCokB|W*xFjtg3P{ML*03JKB##=yIM*12 z323;Eg{r1Z%0)}1>8W`r>)+cpKk&72!71!@{?avGZ+B)vWIIc6G`JX?UIZ}kyD2A? zUteNy@?fEXr11nKfr-MSAP)%B^mNHCHA-WWX(x13tuPjSzN zca7&cK~HlQZ`xC^Jry?ol61~mcvic$Jz&Hk+`+jiy%(NbOGz|PJc+-kYpM$HivV6e!OKx;Z znoA12Sy>QAxuam$;9?P7Q7T(9y^0>kw<&O$fuf$aLuK!`v{&`+Z?DYmZ?DyDhssNj z^m(%)t?Ey{OTDkPx&IMstEqVmI8N5NEpWXnQWk%DK*{l9GKkMqw2WiNm{`nlo|Fh! zyntEXY^tX$2D5Ha%-;G_8ckgerI3x{%vp79(jFFd3FCF zetv*EK@&5gzHM#TkZ1ZBaQZUYjb)W;HSX-@Ps7v*E}}eHaZe&(=5J(L8Z)Q~511e$ zrrE@doqVZ|jIn91^Yb3ExroiMJZ;xpJa?ex!ZZ1e>yVY{EAQX7tNhicw^sz6y^3e6 zkwD=GwryJ<_*o$rruBlHUq82XUvJSxRSlKyjFKW=EsGs6;q6eaDViVYh!-9P3rPqe z({#c(%jFUQKEzGpdn;)hH=w93fsaK{R@(ayFTV2}orGU;%-`>kxp0E%LNL;-J8w0tmP;g?01n82>hb2{I;`LOTr7 zi2{X-mO$6|VNmcUlG*|=1PFk}tNjUB!b4m}Z5r09B8d|;<~LNQXYFhY9!qs)zS`OO zW`3<`>{0Fq+)-ebMO454w^qQczdHRr^)0i!t({QwYMqQ>67oce(F3EZ+J9JVh7*BTSrrgMNZH=E3 z=v2zZ_fto^uy*);p6|Yl1|mUWMR;l?z)hwMJ5MyHFh5y`2%-U%3wIGOK|LPo^!IN5 z=GuL%JC>Ue99C#r^2J8X@8>~y##jwZaUPXu5TAiFIGyynb~sq%>3)DaR9zkf)2ef- zxjvU}s`Kk+*5(?qFyoD`m%%t?5cUJeK7eui({VU2E2?IqvE8 zxZqIu*_W@J6WJOXBtLm()<@A{%~1F$wcZAW+M2TUuix9e{=L&-^n}1+2MtS>pJzwv zcZg0uoen?9x)APVY>*G8tIO_t_o*Mk%s)AI!r-u3YEWqFCs2vj<{y%K6+d^pa`X%I z;|YL0^2A-sOZV?hW%U)jp(E7cz1dEftS{AK3OJDYFdqM{ETatr~Ls7Zwu`kun=B{@}|)=A8uMdma^Qh z?Ax-g<5c)KEpSG6IP;1xHM?PSG4@~lhaUK0h_>dypN06e(X>;XK7{wtUq7&Q{f5)& zXHvl#j}aP^JtbxJIEBrC$ybN35d3c&%JK8T3Vf0%#%p!H0Z75(b+HXQJenZHHz8c# z!0iASef(CP#yhq&=qk_;55zO!8{rGzEa2x%dTp+_ + + + + + + + + + + + + + + + diff --git a/docs/manual/overrides/assets/stylesheets/extra.css b/docs/manual/overrides/assets/stylesheets/extra.css new file mode 100644 index 00000000..07b39e34 --- /dev/null +++ b/docs/manual/overrides/assets/stylesheets/extra.css @@ -0,0 +1,79 @@ + +img + em, .browser-border + em, .browser-mockup + em { + display: block; + text-align: left; + font-size: 0.7rem; + font-style: normal; +} +.md-typeset .admonition, .md-typeset details { + font-size: 0.75rem; +} + +/* definition list used to display general inputs */ +.md-typeset dl dd { + margin: 10px 0; +} +[dir=ltr] .md-typeset dd { + margin-left: 0; +} +.md-typeset dl dd code { + border: 1px solid rgba(230, 230, 230, 0.7); + padding: 8px 12px; +} + +.browser-border { + border: 1px solid rgba(230, 230, 230, 0.7); + border-radius: 3px; + background: #fff; + margin: 10px 0 5px 0; +} +.browser-mockup { + border: 1px solid rgba(230, 230, 230, 0.7); + border-top: 2em solid rgba(230, 230, 230, 0.7); + /*box-shadow: 0 0.1em 1em 0 rgba(0, 0, 0, 0.4);*/ + position: relative; + border-radius: 3px; + margin: 10px 0 5px 0; +} + +.browser-mockup:before { + display: block; + position: absolute; + content: ''; + top: -1.25em; + left: 1em; + width: 0.5em; + height: 0.5em; + border-radius: 50%; + background-color: #f44; + box-shadow: 0 0 0 2px #f44, 1.5em 0 0 2px #9b3, 3em 0 0 2px #fb5; +} + +.browser-mockup.with-tab:after { + display: block; + position: absolute; + content: ''; + top: -2em; + left: 5.5em; + width: 20%; + height: 0; + border-bottom: 2em solid white; + border-left: 0.8em solid transparent; + border-right: 0.8em solid transparent; +} + +.browser-mockup.with-url:after { + display: block; + position: absolute; + content: ''; + top: -1.6em; + left: 5.5em; + width: calc(100% - 6em); + height: 1.2em; + border-radius: 2px; + background-color: white; +} + +.browser-mockup > * { + display: block; +} diff --git a/docs/manual/overrides/main.html b/docs/manual/overrides/main.html new file mode 100644 index 00000000..f2432066 --- /dev/null +++ b/docs/manual/overrides/main.html @@ -0,0 +1,6 @@ +{% extends "base.html" %} + +{% block outdated %} + GeoNetwork 4.4 is latest. + Click here to go to stable. +{% endblock %} diff --git a/docs/manual/overrides/partials/copyright.html b/docs/manual/overrides/partials/copyright.html new file mode 100755 index 00000000..40f52ada --- /dev/null +++ b/docs/manual/overrides/partials/copyright.html @@ -0,0 +1,41 @@ + + + + diff --git a/docs/manual/pom.xml b/docs/manual/pom.xml new file mode 100644 index 00000000..e74e55a0 --- /dev/null +++ b/docs/manual/pom.xml @@ -0,0 +1,108 @@ + + + + + gn-ms-docs + org.geonetwork-opensource.cloud + 4.4.5-0 + + 4.0.0 + gn-ms-guide + GeoNetwork MicroServices Guide + pom + + + Creative Commons Attribution 3.0 License + http://creativecommons.org/licenses/by/3.0/ + repo + + + + + + + maven-antrun-plugin + 3.0.0 + + + + docs + compile + + run + + + + + + + + + + + + + + + + maven-assembly-plugin + 3.3.0 + + + make-assembly + package + + single + + + + + src/assembly/guide.xml + + + + + + + + maven-deploy-plugin + + true + + + + + maven-install-plugin + + true + + + + + + + + \ No newline at end of file diff --git a/docs/manual/requirements.txt b/docs/manual/requirements.txt new file mode 100644 index 00000000..f9aaca20 --- /dev/null +++ b/docs/manual/requirements.txt @@ -0,0 +1,5 @@ +mkdocs-material>=9.5.3 +mkdocs-static-i18n>=1.0.5 +mkdocs-include-markdown-plugin +mkdocs-exclude +mike==2.0.0 \ No newline at end of file diff --git a/docs/manual/src/assembly/guide.xml b/docs/manual/src/assembly/guide.xml new file mode 100644 index 00000000..58394d44 --- /dev/null +++ b/docs/manual/src/assembly/guide.xml @@ -0,0 +1,13 @@ + + manual + + zip + + + + target/html + + + + + diff --git a/docs/pom.xml b/docs/pom.xml new file mode 100644 index 00000000..1d41005e --- /dev/null +++ b/docs/pom.xml @@ -0,0 +1,45 @@ + + + + + gn-cloud-microservices + org.geonetwork-opensource.cloud + 4.4.5-0 + + 4.0.0 + gn-ms-docs + Root Documentation module + pom + + + General Public License (GPL) + http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt + + + + manual + + \ No newline at end of file From 1014da7010db8dfe6ccd808879d5c2850815b25a Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Wed, 25 Sep 2024 10:11:47 -0700 Subject: [PATCH 08/18] CORS - allow all connections --- .../fao/geonet/ogcapi/records/OgcApiRecordApp.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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..d877689e 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,14 @@ public static void main(String[] args) { SpringApplication.run(OgcApiRecordApp.class, args); } + + @Bean + public WebMvcConfigurer corsConfigurer() { + return new WebMvcConfigurer() { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**").allowedOrigins("*"); + } + }; + } } From 734a97d808c665e11a72f05ec40b7bacec2de213 Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Wed, 25 Sep 2024 10:12:55 -0700 Subject: [PATCH 09/18] add link to collection (catalog or sub-portal) for its icon --- .../controller/CapabilitiesApiController.java | 9 ++++++++ .../records/util/CollectionInfoBuilder.java | 22 +++++++++++++++++++ 2 files changed, 31 insertions(+) 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 02b97305..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 @@ -118,6 +118,15 @@ public ResponseEntity getLandingPage(@ApiIgnore HttpServletRequest request 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); } 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 31319318..563f132c 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 @@ -26,18 +26,25 @@ 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 { + @Value("${gn.legacy.url}") + String geonetworkUrl; + @Autowired RecordService recordService; @Autowired ElasticIndexJson2CollectionInfo elasticIndexJson2CollectionInfo; + public CollectionInfoBuilder() { } @@ -101,6 +108,21 @@ public CollectionInfo buildFromSource(Source source, 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 imgType = source.getLogo().substring(source.getLogo().lastIndexOf(".")+1).toLowerCase(); + 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, From dbf1eb13fb4020dbccebb359201ed07841757ff7 Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Wed, 25 Sep 2024 10:25:55 -0700 Subject: [PATCH 10/18] minor formating changes --- .../ogcapi/records/OgcApiRecordApp.java | 4 ++++ .../records/util/CollectionInfoBuilder.java | 23 +++++++++---------- 2 files changed, 15 insertions(+), 12 deletions(-) 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 d877689e..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 @@ -31,6 +31,10 @@ public static void main(String[] args) { } + /** + * Configure CORS to allow all connections. + * @return CORS configuration. + */ @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { 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 563f132c..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 @@ -109,18 +109,17 @@ public CollectionInfo buildFromSource(Source source, 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 imgType = source.getLogo().substring(source.getLogo().lastIndexOf(".")+1).toLowerCase(); - var link = new OgcApiLink(); - link.setHref(url.toString()); - link.setRel("icon"); - link.setType("image/png"); - collectionInfo.addLinksItem(link); + 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 = From dad79a6fbbb109c04d488ee37b68366875ed6133 Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Thu, 26 Sep 2024 12:44:22 -0700 Subject: [PATCH 11/18] better elastic query --- .../ogcapi/records/controller/ItemApiController.java | 1 + .../ogcapi/records/util/RecordsEsQueryBuilder.java | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) 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 505bf55f..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 @@ -575,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/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()); From 51fbde4f860e214002d5585b477c5047fff933b1 Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Tue, 1 Oct 2024 10:36:07 -0700 Subject: [PATCH 12/18] initial queryables implementation --- .../controller/QueryableApiController.java | 63 +++++++ .../geonet/ogcapi/records/model/JsonItem.java | 30 ++++ .../ogcapi/records/model/JsonProperty.java | 155 ++++++++++++++++++ .../ogcapi/records/model/JsonSchema.java | 116 +++++++++++++ .../records/service/QueryablesService.java | 124 ++++++++++++++ 5 files changed, 488 insertions(+) create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/controller/QueryableApiController.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/JsonItem.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/JsonProperty.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/model/JsonSchema.java create mode 100644 modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/service/QueryablesService.java 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/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/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 + + } + +} From 39136d8e8075560f4cc9ea8873faeb33911a1a66 Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Mon, 7 Oct 2024 08:35:47 -0700 Subject: [PATCH 13/18] add hash/idx to Link (as per jose) --- .../src/main/java/org/fao/geonet/index/model/gn/Link.java | 3 +++ 1 file changed, 3 insertions(+) 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; } From 4f4017ebb3b7d613a33b3227a5b32d26c09c5fcd Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Thu, 10 Oct 2024 13:15:25 -0700 Subject: [PATCH 14/18] refactor to use the parsed elastic index model --- .../geonet/index/model/gn/IndexRecord.java | 5 + .../org/fao/geonet/index/model/gn/Theme.java | 52 ++++++ .../ogcapi/records/model/OgcApiContact.java | 58 ++++++ .../records/model/OgcApiTemporalExtent.java | 8 +- .../ogcapi/records/model/OgcApiTheme.java | 44 +++-- .../util/ElasticIndexJson2CollectionInfo.java | 173 +++++++++++------- 6 files changed, 248 insertions(+), 92 deletions(-) create mode 100644 modules/library/common-index-model/src/main/java/org/fao/geonet/index/model/gn/Theme.java diff --git a/modules/library/common-index-model/src/main/java/org/fao/geonet/index/model/gn/IndexRecord.java b/modules/library/common-index-model/src/main/java/org/fao/geonet/index/model/gn/IndexRecord.java index 0fe1caa4..1f09d24d 100644 --- a/modules/library/common-index-model/src/main/java/org/fao/geonet/index/model/gn/IndexRecord.java +++ b/modules/library/common-index-model/src/main/java/org/fao/geonet/index/model/gn/IndexRecord.java @@ -52,6 +52,11 @@ public class IndexRecord extends IndexDocument { List> resourceCredit = new ArrayList<>(); @JsonProperty(IndexRecordFieldNames.tag) ArrayList> tag = new ArrayList<>(); + + Map allKeywords = new HashMap<>(); + + @JsonProperty("MD_LegalConstraintsUseLimitationObject") + ArrayList> 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/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..97d4cb5e --- /dev/null +++ b/modules/library/common-index-model/src/main/java/org/fao/geonet/index/model/gn/Theme.java @@ -0,0 +1,52 @@ +/** + * (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.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 Map multilingualTitle; + +} 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 index 27b1cbeb..02cafb8f 100644 --- 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 @@ -15,6 +15,7 @@ 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; @@ -132,6 +133,63 @@ public class OgcApiContact { 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. * 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 index 48a90f0d..83357956 100644 --- 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 @@ -10,9 +10,9 @@ 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; +import org.fao.geonet.index.model.gn.DateRange; /** * cf. https://github.com/opengeospatial/ogcapi-features/blob/master/core/openapi/schemas/extent.yaml @@ -82,13 +82,13 @@ public class OgcApiTemporalExtent { * @param map from the Elastic Index Record * @return parsed OgcApiTemporalExtent */ - public static OgcApiTemporalExtent fromGnIndexRecord(Map map) { + public static OgcApiTemporalExtent fromGnIndexRecord(DateRange map) { if (map == null) { return null; } - String start = (String) map.get("gte"); - String end = (String) map.get("lte"); + String start = map.getGte(); + String end = map.getLte(); var result = new OgcApiTemporalExtent(); result.trs = DefaultTRS; 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 index d1f3cd38..ded41a77 100644 --- 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 @@ -15,6 +15,7 @@ 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; /** @@ -61,41 +62,46 @@ public OgcApiTheme(String schema) { * @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) { + public static List parseElasticIndex(Map map) { if (map == null) { return null; } List result = new ArrayList<>(); - for (var thSetObject : map.values()) { - var thSet = (Map) thSetObject; - - var themeSchema = getAsString(thSet.get("link")); + for (var theme : map.values()) { + var themeSchema = getAsString(theme.getLink()); //at least put something here! - if (!StringUtils.hasText(themeSchema)) { - if (StringUtils.hasText(getAsString(thSet.get("multilingualTitle")))) { - themeSchema = getLangString(thSet.get("multilingualTitle").toString()); + 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) && thSet.get("title") != null) { - themeSchema = thSet.get("title").toString(); + if (!StringUtils.hasText(themeSchema) && theme.getTitle() != null) { + themeSchema = theme.getTitle(); } - if (!StringUtils.hasText(themeSchema) && thSet.get("theme") != null) { - themeSchema = thSet.get("theme").toString(); + if (!StringUtils.hasText(themeSchema) && theme.getTheme() != null) { + themeSchema = theme.getTheme(); } - if (!StringUtils.hasText(themeSchema) && thSet.get("id") != null) { - themeSchema = thSet.get("id").toString(); + if (!StringUtils.hasText(themeSchema) && theme.getId() != null) { + themeSchema = theme.getId(); } - var theme = new OgcApiTheme(themeSchema); + var theme2 = new OgcApiTheme(themeSchema); - List conceptsList = (List) thSet.get("keywords"); + 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); - theme.getConcepts().add(ogcConcept); + theme2.getConcepts().add(ogcConcept); } - if (theme.getConcepts().size() != 0) { - result.add(theme); + if (theme2.getConcepts().size() != 0) { + result.add(theme2); } } return result; 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 index 74895d34..dc9485b6 100644 --- 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 @@ -2,10 +2,15 @@ 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; @@ -17,45 +22,70 @@ 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 JSON of the linked Service record (GN's DB "source" - * "serviceRecord") + * @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; + } - handleTitle(collectionInfo, linkedServiceRecord); - handleDescription(collectionInfo, linkedServiceRecord); + handleTitle(collectionInfo, indexRecord); + handleDescription(collectionInfo, indexRecord); - handleContacts(collectionInfo, linkedServiceRecord); + handleContacts(collectionInfo, indexRecord); - OgcApiSpatialExtent spatialExtent = handleSpatialExtent(linkedServiceRecord); - OgcApiTemporalExtent temporalExtent = handleTemporalExtent(linkedServiceRecord); + OgcApiSpatialExtent spatialExtent = handleSpatialExtent(indexRecord); + OgcApiTemporalExtent temporalExtent = handleTemporalExtent(indexRecord); handleExtent(collectionInfo, spatialExtent, temporalExtent); - handleCrs(collectionInfo, linkedServiceRecord); + handleCrs(collectionInfo, indexRecord); - handleCreateDate(collectionInfo, linkedServiceRecord); - handleChangeDate(collectionInfo, linkedServiceRecord); + handleCreateDate(collectionInfo, indexRecord); + handleChangeDate(collectionInfo, indexRecord); - handleTags(collectionInfo, linkedServiceRecord); + handleTags(collectionInfo, indexRecord); - handleLanguage(collectionInfo, linkedServiceRecord); - handleOtherLangs(collectionInfo, linkedServiceRecord); + handleLanguage(collectionInfo, indexRecord); + handleOtherLangs(collectionInfo, indexRecord); - handleThemes(collectionInfo, linkedServiceRecord); + handleThemes(collectionInfo, indexRecord); - handleLicenses(collectionInfo, linkedServiceRecord); + handleLicenses(collectionInfo, indexRecord); } @@ -63,8 +93,8 @@ public void injectLinkedServiceRecordInfo(CollectionInfo collectionInfo, // Elastic Index JSON versus what's expected in the ogcapi license field. We do the simple // action private void handleLicenses(CollectionInfo collectionInfo, - Map linkedServiceRecord) { - var myLicense = linkedServiceRecord.get("MD_LegalConstraintsUseLimitationObject"); + IndexRecord indexRecord) { + var myLicense = indexRecord.getMdLegalConstraintsUseLimitationObject(); if (myLicense != null && myLicense instanceof List) { var myLiceseList = (List) myLicense; if (myLiceseList.size() > 0) { @@ -80,38 +110,38 @@ private void handleLicenses(CollectionInfo collectionInfo, //process allKeywords to get themes private void handleThemes(CollectionInfo collectionInfo, - Map linkedServiceRecord) { - var myThemeKeywords = linkedServiceRecord.get("allKeywords"); - collectionInfo.setThemes(OgcApiTheme.parseElasticIndex((Map) myThemeKeywords)); + IndexRecord indexRecord) { + var myThemeKeywords = indexRecord.getAllKeywords(); + collectionInfo.setThemes(OgcApiTheme.parseElasticIndex(myThemeKeywords)); } //process otherLanguage to get the other languages private void handleOtherLangs(CollectionInfo collectionInfo, - Map linkedServiceRecord) { - var myOtherLangs = linkedServiceRecord.get("otherLanguage"); - if (myOtherLangs != null && myOtherLangs instanceof List) { - var langs = (List) myOtherLangs; + IndexRecord indexRecord) { + var myOtherLangs = indexRecord.getOtherLanguage(); + if (myOtherLangs != null) { + var langs = myOtherLangs; collectionInfo.setLanguages(new ArrayList<>()); for (var lang : langs) { - collectionInfo.getLanguages().add(new OgcApiLanguage(lang.toString())); + collectionInfo.getLanguages().add(new OgcApiLanguage(lang)); } } } //process main language private void handleLanguage(CollectionInfo collectionInfo, - Map linkedServiceRecord) { - var myMainLang = linkedServiceRecord.get("mainLanguage"); - if (myMainLang != null && StringUtils.hasText(myMainLang.toString())) { - collectionInfo.setLanguage(new OgcApiLanguage(myMainLang.toString())); + IndexRecord indexRecord) { + var myMainLang = indexRecord.getMainLanguage(); + if (myMainLang != null && StringUtils.hasText(myMainLang)) { + collectionInfo.setLanguage(new OgcApiLanguage(myMainLang)); } } //handle the "tags" to create keywords - private void handleTags(CollectionInfo collectionInfo, Map linkedServiceRecord) { - var myTags = linkedServiceRecord.get("tag"); - if (myTags != null && myTags instanceof List) { - var tags = (List) myTags; + private void handleTags(CollectionInfo collectionInfo, IndexRecord indexRecord) { + var myTags = indexRecord.getTag(); + if (myTags != null) { + var tags = myTags; collectionInfo.setKeywords(new ArrayList<>()); for (var tag : tags) { collectionInfo.getKeywords().add(getLangString(tag)); @@ -121,30 +151,30 @@ private void handleTags(CollectionInfo collectionInfo, Map linke //process the change date private void handleChangeDate(CollectionInfo collectionInfo, - Map linkedServiceRecord) { - var updateDate = linkedServiceRecord.get("changeDate"); - if (updateDate != null && StringUtils.hasText(updateDate.toString())) { - collectionInfo.setUpdated(updateDate.toString()); + IndexRecord indexRecord) { + var updateDate = indexRecord.getChangeDate(); + if (updateDate != null && StringUtils.hasText(updateDate)) { + collectionInfo.setUpdated(updateDate); } } //process the creation date private void handleCreateDate(CollectionInfo collectionInfo, - Map linkedServiceRecord) { - var createDate = linkedServiceRecord.get("createDate"); - if (createDate != null && StringUtils.hasText(createDate.toString())) { - collectionInfo.setCreated(createDate.toString()); + IndexRecord indexRecord) { + var createDate = indexRecord.getCreateDate(); + if (createDate != null && StringUtils.hasText(createDate)) { + collectionInfo.setCreated(createDate); } } //process CRS - private void handleCrs(CollectionInfo collectionInfo, Map linkedServiceRecord) { - var myCrss = linkedServiceRecord.get("coordinateSystem"); - if (myCrss != null && myCrss instanceof List) { - var crss = (List) myCrss; + private void handleCrs(CollectionInfo collectionInfo, IndexRecord indexRecord) { + var myCrss = indexRecord.getCoordinateSystem(); + if (myCrss != null) { + var crss = myCrss; collectionInfo.setCrs(new ArrayList<>()); for (var crs : crss) { - collectionInfo.getCrs().add(crs.toString()); + collectionInfo.getCrs().add(crs); } } } @@ -159,38 +189,43 @@ private void handleExtent(CollectionInfo collectionInfo, OgcApiSpatialExtent spa } //process Temporal Extent - private OgcApiTemporalExtent handleTemporalExtent(Map linkedServiceRecord) { + private OgcApiTemporalExtent handleTemporalExtent(IndexRecord indexRecord) { OgcApiTemporalExtent temporalExtent = null; - var myTemporalExtent = linkedServiceRecord.get("resourceTemporalDateRange"); - if (myTemporalExtent != null && myTemporalExtent instanceof List) { - myTemporalExtent = ((List) myTemporalExtent).get(0); - temporalExtent = OgcApiTemporalExtent.fromGnIndexRecord( - (Map) myTemporalExtent); + 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 handleSpatialExtent(Map linkedServiceRecord) { + private OgcApiSpatialExtent handleSpatialExtent(IndexRecord indexRecord) { OgcApiSpatialExtent spatialExtent = null; - var mySpatialExtent = linkedServiceRecord.get("geom"); - if (mySpatialExtent != null - && mySpatialExtent instanceof List) { - mySpatialExtent = ((List) mySpatialExtent).get(0); - spatialExtent = OgcApiSpatialExtent.fromGnIndexRecord((Map) mySpatialExtent); + 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 void handleContacts(CollectionInfo collectionInfo, - Map linkedServiceRecord) { - var contacts = linkedServiceRecord.get("contact"); - if (contacts != null && linkedServiceRecord.get("contact") instanceof List) { - var cs = (List) contacts; + IndexRecord indexRecord) { + var contacts = indexRecord.getContact(); + if (contacts != null && !contacts.isEmpty()) { collectionInfo.setContacts(new ArrayList<>()); - for (var contactMap : cs) { - var contact = OgcApiContact.fromIndexMap((Map) contactMap); + for (var contactInfo : contacts) { + var contact = OgcApiContact.fromIndexMap(contactInfo); collectionInfo.getContacts().add(contact); } } @@ -198,8 +233,8 @@ private void handleContacts(CollectionInfo collectionInfo, //override description (abstract) from attached ServiceRecord private void handleDescription(CollectionInfo collectionInfo, - Map linkedServiceRecord) { - var desc = getLangString(linkedServiceRecord.get("resourceAbstractObject")); + IndexRecord indexRecord) { + var desc = getLangString(indexRecord.getResourceAbstract()); if (desc != null) { collectionInfo.setDescription(desc); } @@ -207,8 +242,8 @@ private void handleDescription(CollectionInfo collectionInfo, //override title from attached ServiceRecord - private void handleTitle(CollectionInfo collectionInfo, Map linkedServiceRecord) { - var title = getLangString(linkedServiceRecord.get("resourceTitleObject")); + private void handleTitle(CollectionInfo collectionInfo, IndexRecord indexRecord) { + var title = getLangString(indexRecord.getResourceTitle()); if (title != null) { collectionInfo.setTitle(title); } From 5304f8baefd9f87692cff1b7525762f4e516219f Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Mon, 7 Oct 2024 08:35:47 -0700 Subject: [PATCH 15/18] add hash/idx to Link (as per jose) --- .../src/main/java/org/fao/geonet/index/model/gn/Link.java | 3 +++ 1 file changed, 3 insertions(+) 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; } From 77b988f94825be09404d284dba15b55ef6ef992a Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Thu, 10 Oct 2024 14:12:50 -0700 Subject: [PATCH 16/18] handle more types of objects --- .../util/ElasticIndexJson2CollectionInfo.java | 169 +++++++++++------- 1 file changed, 108 insertions(+), 61 deletions(-) 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 index dc9485b6..5a17adea 100644 --- 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 @@ -33,9 +33,9 @@ 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") + * @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) { @@ -64,119 +64,167 @@ public void injectLinkedServiceRecordInfo(CollectionInfo collectionInfo, return; } - handleTitle(collectionInfo, indexRecord); - handleDescription(collectionInfo, indexRecord); + set(getTitle(collectionInfo, indexRecord), collectionInfo, "title"); + set(getDescription(collectionInfo, indexRecord), collectionInfo, "description"); - handleContacts(collectionInfo, indexRecord); + 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(collectionInfo, extent, "extent"); + } - OgcApiSpatialExtent spatialExtent = handleSpatialExtent(indexRecord); - OgcApiTemporalExtent temporalExtent = handleTemporalExtent(indexRecord); - handleExtent(collectionInfo, spatialExtent, temporalExtent); + set(getCrs(collectionInfo, indexRecord), collectionInfo, "crs"); + set(getCreateDate(collectionInfo, indexRecord), collectionInfo, "created"); + set(getChangeDate(collectionInfo, indexRecord), collectionInfo, "updated"); + set(getTags(collectionInfo, indexRecord), collectionInfo, "keywords"); - handleCrs(collectionInfo, indexRecord); + set(getLanguage(collectionInfo, indexRecord), collectionInfo, "language"); + set(getOtherLangs(collectionInfo, indexRecord), collectionInfo, "languages"); - handleCreateDate(collectionInfo, indexRecord); - handleChangeDate(collectionInfo, indexRecord); + set(getThemes(collectionInfo, indexRecord), collectionInfo, "themes"); - handleTags(collectionInfo, indexRecord); + set(getLicenses(collectionInfo, indexRecord), collectionInfo, "license"); + set(getRights(collectionInfo, indexRecord), collectionInfo, "rights"); + } - handleLanguage(collectionInfo, indexRecord); - handleOtherLangs(collectionInfo, indexRecord); - handleThemes(collectionInfo, indexRecord); + /** + * 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; + } - handleLicenses(collectionInfo, indexRecord); } + //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 void handleLicenses(CollectionInfo collectionInfo, + private String getRights(CollectionInfo collectionInfo, IndexRecord indexRecord) { - var myLicense = indexRecord.getMdLegalConstraintsUseLimitationObject(); - if (myLicense != null && myLicense instanceof List) { - var myLiceseList = (List) myLicense; - if (myLiceseList.size() > 0) { - var license = getLangString(myLiceseList.get(0)); - collectionInfo.setLicense(license); - } + var myLiceseList = indexRecord.getMdLegalConstraintsUseLimitationObject(); + if (myLiceseList != null && !myLiceseList.isEmpty()) { if (myLiceseList.size() > 1) { var license2 = getLangString(myLiceseList.get(1)); - collectionInfo.setRights(license2); + 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 void handleThemes(CollectionInfo collectionInfo, + private List getThemes(CollectionInfo collectionInfo, IndexRecord indexRecord) { var myThemeKeywords = indexRecord.getAllKeywords(); - collectionInfo.setThemes(OgcApiTheme.parseElasticIndex(myThemeKeywords)); + return OgcApiTheme.parseElasticIndex(myThemeKeywords); } //process otherLanguage to get the other languages - private void handleOtherLangs(CollectionInfo collectionInfo, + private ArrayList getOtherLangs(CollectionInfo collectionInfo, IndexRecord indexRecord) { - var myOtherLangs = indexRecord.getOtherLanguage(); - if (myOtherLangs != null) { - var langs = myOtherLangs; - collectionInfo.setLanguages(new ArrayList<>()); + var langs = indexRecord.getOtherLanguage(); + if (langs != null) { + var result = new ArrayList(); for (var lang : langs) { - collectionInfo.getLanguages().add(new OgcApiLanguage(lang)); + result.add(new OgcApiLanguage(lang)); } + return result; } + return null; } //process main language - private void handleLanguage(CollectionInfo collectionInfo, + private OgcApiLanguage getLanguage(CollectionInfo collectionInfo, IndexRecord indexRecord) { var myMainLang = indexRecord.getMainLanguage(); if (myMainLang != null && StringUtils.hasText(myMainLang)) { - collectionInfo.setLanguage(new OgcApiLanguage(myMainLang)); + return new OgcApiLanguage(myMainLang); } + return null; } //handle the "tags" to create keywords - private void handleTags(CollectionInfo collectionInfo, IndexRecord indexRecord) { + 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) { - collectionInfo.getKeywords().add(getLangString(tag)); + result.add(getLangString(tag)); } + return result; } + return null; } //process the change date - private void handleChangeDate(CollectionInfo collectionInfo, + private String getChangeDate(CollectionInfo collectionInfo, IndexRecord indexRecord) { var updateDate = indexRecord.getChangeDate(); if (updateDate != null && StringUtils.hasText(updateDate)) { - collectionInfo.setUpdated(updateDate); + return updateDate; } + return null; } //process the creation date - private void handleCreateDate(CollectionInfo collectionInfo, + private String getCreateDate(CollectionInfo collectionInfo, IndexRecord indexRecord) { var createDate = indexRecord.getCreateDate(); if (createDate != null && StringUtils.hasText(createDate)) { - collectionInfo.setCreated(createDate); + return createDate; } + return null; } //process CRS - private void handleCrs(CollectionInfo collectionInfo, IndexRecord indexRecord) { - var myCrss = indexRecord.getCoordinateSystem(); - if (myCrss != null) { - var crss = myCrss; - collectionInfo.setCrs(new ArrayList<>()); + private ArrayList getCrs(CollectionInfo collectionInfo, IndexRecord indexRecord) { + var crss = indexRecord.getCoordinateSystem(); + var result = new ArrayList(); + if (crss != null) { for (var crs : crss) { - collectionInfo.getCrs().add(crs); + result.add(crs); } + return result; } + return null; } //combine spatial and temporal extent @@ -189,7 +237,7 @@ private void handleExtent(CollectionInfo collectionInfo, OgcApiSpatialExtent spa } //process Temporal Extent - private OgcApiTemporalExtent handleTemporalExtent(IndexRecord indexRecord) { + private OgcApiTemporalExtent getTemporalExtent(IndexRecord indexRecord) { OgcApiTemporalExtent temporalExtent = null; var myTemporalExtent = indexRecord.getResourceTemporalExtentDateRange(); if (myTemporalExtent != null || myTemporalExtent.isEmpty()) { @@ -202,7 +250,7 @@ private OgcApiTemporalExtent handleTemporalExtent(IndexRecord indexRecord) { } //process the spatial extent - private OgcApiSpatialExtent handleSpatialExtent(IndexRecord indexRecord) { + private OgcApiSpatialExtent getSpatialExtent(IndexRecord indexRecord) { OgcApiSpatialExtent spatialExtent = null; var mySpatialExtent = indexRecord.getGeometries(); if (mySpatialExtent != null && !mySpatialExtent.isEmpty()) { @@ -219,33 +267,32 @@ private OgcApiSpatialExtent handleSpatialExtent(IndexRecord indexRecord) { } //process contracts - private void handleContacts(CollectionInfo collectionInfo, + private List getContacts(CollectionInfo collectionInfo, IndexRecord indexRecord) { var contacts = indexRecord.getContact(); if (contacts != null && !contacts.isEmpty()) { - collectionInfo.setContacts(new ArrayList<>()); + var result = new ArrayList(); + for (var contactInfo : contacts) { var contact = OgcApiContact.fromIndexMap(contactInfo); - collectionInfo.getContacts().add(contact); + result.add(contact); } + return result; } + return null; } //override description (abstract) from attached ServiceRecord - private void handleDescription(CollectionInfo collectionInfo, + private String getDescription(CollectionInfo collectionInfo, IndexRecord indexRecord) { var desc = getLangString(indexRecord.getResourceAbstract()); - if (desc != null) { - collectionInfo.setDescription(desc); - } + return desc; } //override title from attached ServiceRecord - private void handleTitle(CollectionInfo collectionInfo, IndexRecord indexRecord) { + private String getTitle(CollectionInfo collectionInfo, IndexRecord indexRecord) { var title = getLangString(indexRecord.getResourceTitle()); - if (title != null) { - collectionInfo.setTitle(title); - } + return title; } } From db02e4f450995eafd8c0019ac705103d793dcb78 Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Thu, 10 Oct 2024 14:56:51 -0700 Subject: [PATCH 17/18] test case fix --- .../java/org/fao/geonet/index/model/gn/IndexRecordTest.java | 2 +- .../ogcapi/records/util/ElasticIndexJson2CollectionInfo.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/util/ElasticIndexJson2CollectionInfo.java b/modules/services/ogc-api-records/src/main/java/org/fao/geonet/ogcapi/records/util/ElasticIndexJson2CollectionInfo.java index 5a17adea..24008f6f 100644 --- 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 @@ -114,7 +114,7 @@ public void set(Object val, Object mainObject, String propertyName) { } - + //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 From 7b9f4df488a8159929eedf54af19e7e5c28bee00 Mon Sep 17 00:00:00 2001 From: "david.blasby" Date: Thu, 10 Oct 2024 16:40:05 -0700 Subject: [PATCH 18/18] fix for test cases --- .../src/main/java/org/fao/geonet/index/model/gn/Theme.java | 5 +++-- .../ogcapi/records/util/ElasticIndexJson2CollectionInfo.java | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) 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 index 97d4cb5e..06c670dc 100644 --- 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 @@ -6,6 +6,7 @@ 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; @@ -46,7 +47,7 @@ public class Theme { public String title; public String theme; public String link; - public List> keywords; - public Map multilingualTitle; + public List> keywords; + public HashMap multilingualTitle; } 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 index 24008f6f..454fb4bc 100644 --- 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 @@ -73,7 +73,7 @@ public void injectLinkedServiceRecordInfo(CollectionInfo collectionInfo, OgcApiTemporalExtent temporalExtent = getTemporalExtent(indexRecord); OgcApiExtent extent = new OgcApiExtent(spatialExtent, temporalExtent); if (spatialExtent != null || temporalExtent != null) { - set(collectionInfo, extent, "extent"); + set(extent,collectionInfo, "extent"); } set(getCrs(collectionInfo, indexRecord), collectionInfo, "crs");