Skip to content

Commit

Permalink
[GEOS-10892] Allow configuring custom links for OGC API collections a…
Browse files Browse the repository at this point in the history
…nd single collection resources
  • Loading branch information
aaime committed Mar 16, 2023
1 parent 1b13e2d commit a3d2f9b
Show file tree
Hide file tree
Showing 40 changed files with 1,302 additions and 43 deletions.
49 changes: 49 additions & 0 deletions doc/en/user/source/community/ogc-api/configure.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Configuring the GeoServer OGC API module
========================================

The OGC API module is mostly using the same configurations as the equivalent OWS services.
So, for example, setting the WFS limited SRS list will also limit the SRS list for OGC API - Features.

The OGC API module is also using the same security configuration as the equivalent OWS services.

In addition, the OGC API module has some unique configuration options, explained below.

Custom links for the "collections" resource
-------------------------------------------

By specification, the ``collections`` resource can have a number of additional links, beyond
the basic ones that the service code already includes.

The administrator can add links for this resource either in the Global pages, or in the
workspace specific settings:

.. figure:: img/links.png

Links editor

Link editor column description:

* **rel**: the link relation type, as per the OGC API - Features specification
* **Mime type**: the mime type for the resource found following the link
* **URL**: the link URL
* **Title**: the link title (optional)
* **Service**: the service for which the link is valid (optional, defaults to all)


Common links relationships that could be added for the ``collections`` resource are:

* ``enclosure``, in case there is a package delivering all the collections (e.g. a GeoPackage, a ZIP full of shapefiles).
* ``describedBy``, in case there is a document describing all the collections (e.g. a JSON or XML schema).
* ``license``, if all collection data is under the same license.

Custom links for single collections
-----------------------------------

By specification, the ``collection`` resource can have a number of additional links, beyond
the basics ones that the service code already includes.

The editor is the same, but is found in the "Publishing" tab of the layer in question.
The relationships are the same as for the ``collections`` resource, but used in case
there is anything that is specific to the collection (e.g., the schema for the single collection).
In addition, other relations can be specified, like the ``tag`` relation, to link to the eventual
INSPIRE feature concept dictionary entry.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doc/en/user/source/community/ogc-api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This plugin includes implementation of a set of OGC API service implementations
:maxdepth: 1

install
configure
features/index
tiles/index
maps/index
Expand Down
5 changes: 0 additions & 5 deletions src/community/ogcapi/ogcapi-changeset/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@
<artifactId>gs-gwc</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.geoserver.web</groupId>
<artifactId>gs-web-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.geoserver</groupId>
<artifactId>gs-ows</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ private void dispatchService(Request dr, HandlerMethod handler) {
* @return The first {@link APIService} found walking up the inheritance hierarchy, or null if
* not found
*/
static APIService getApiServiceAnnotation(Class<?> clazz) {
public static APIService getApiServiceAnnotation(Class<?> clazz) {
APIService annotation = null;
while (annotation == null && clazz != null) {
annotation = (APIService) clazz.getAnnotation(APIService.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/* (c) 2023 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.ogcapi;

/**
* Configurable links for OGC API services, they can be provided at the {@link
* org.geoserver.catalog.ResourceInfo} and {@link org.geoserver.config.GeoServerInfo} level. More
* places to be added in the future (e.g., {@link org.geoserver.catalog.LayerGroupInfo}, {@link
* org.geoserver.config.ServiceInfo}
*/
import java.io.Serializable;
import org.geoserver.catalog.MetadataMap;

public interface LinkInfo extends Serializable {

String LINKS_METADATA_KEY = "ogcApiLinks";

/** Returns the relation type, e.g., "self", "alternate", "service-desc", ... */
String getRel();

/**
* Sets the relation type for this link (mandatory). e.g., "self", "alternate", "service-desc",
*/
void setRel(String rel);

/** Returns the MIME type, e.g., "application/json", "text/html", "application/atom+xml", ... */
String getType();

/** Sets the MIME type for this link (mandatory) */
void setType(String type);

/** Returns the title, if any */
String getTitle();

/** Sets the title for this link (optional) */
void setTitle(String title);

/** Returns the href for this link */
String getHref();

/** Returns the href */
void setHref(String href);

/** Returns the service type, if any, e.g.,"Features", "Maps", ... */
String getService();

/** Sets the service type for this link (optional) */
void setService(String service);

/**
* Returns the metadata map, which can be used to store additional information about the link
*/
MetadataMap getMetadata();

/** Returns a clone of this link info */
LinkInfo clone();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* (c) 2023 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.ogcapi;

import org.geoserver.catalog.PublishedInfo;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.config.SettingsInfo;
import org.geoserver.ows.Request;
import org.geoserver.platform.Service;
import org.springframework.stereotype.Component;

/**
* Adds links to the OGC API documents, based on the {@link LinkInfo} objects attached to either the
* {@link ResourceInfo}, the {@link PublishedInfo} object, or the {@link SettingsInfo} object.
* Filters out links that are not applicable to the current service.
*/
@Component
public class LinkInfoCallback implements DocumentCallback {

GeoServer geoServer;

public LinkInfoCallback(GeoServer geoServer) {
this.geoServer = geoServer;
}

@Override
public void apply(Request dr, AbstractDocument document) {
if (document instanceof AbstractCollectionDocument) {
decorateCollection(dr, (AbstractCollectionDocument) document);
} else if (isCollectionsDocument(dr)) {
decorateCollections(dr, document);
}
}

private void decorateCollections(Request dr, AbstractDocument document) {
Class<?> serviceClass = getServiceClass(dr);
SettingsInfo settings = geoServer.getSettings();
LinkInfoConverter.addLinksToDocument(document, settings, serviceClass);
}

private boolean isCollectionsDocument(Request dr) {
return dr.getPath().equals("collections");
}

private void decorateCollection(Request dr, AbstractCollectionDocument document) {
Object subject = document.getSubject();
Class<?> serviceClass = getServiceClass(dr);
if (subject instanceof ResourceInfo) {
ResourceInfo resource = (ResourceInfo) subject;
LinkInfoConverter.addLinksToDocument(document, resource, serviceClass);
} else if (subject instanceof PublishedInfo) {
PublishedInfo layer = (PublishedInfo) subject;
LinkInfoConverter.addLinksToDocument(document, layer, serviceClass);
}
}

private static Class<?> getServiceClass(Request dr) {
Service service = dr.getOperation().getService();
return service != null ? service.getService().getClass() : null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/* (c) 2023 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.ogcapi;

import static org.geoserver.ogcapi.LinkInfo.LINKS_METADATA_KEY;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import org.geoserver.catalog.LayerGroupInfo;
import org.geoserver.catalog.LayerInfo;
import org.geoserver.catalog.PublishedInfo;
import org.geoserver.catalog.ResourceInfo;
import org.geoserver.config.SettingsInfo;

/** Helper class to convert {@link LinkInfo} to {@link Link} and add them to a document */
public class LinkInfoConverter {

/**
* Adds links found under the {@link LinkInfo#LINKS_METADATA_KEY} metadata key to the document,
* eventually filtering them by service class
*/
public static void addLinksToDocument(
AbstractDocument document, PublishedInfo publishedInfo, Class serviceClass) {
if (publishedInfo instanceof LayerInfo) {
addLinksToDocument(document, ((LayerInfo) publishedInfo).getResource(), serviceClass);
}
}

/**
* Adds links found under the {@link LinkInfo#LINKS_METADATA_KEY} metadata key to the document,
* eventually filtering them by service class
*/
@SuppressWarnings("unchecked")
public static void addLinksToDocument(
AbstractDocument document, LayerGroupInfo group, Class serviceClass) {
List<LinkInfo> links = group.getMetadata().get(LINKS_METADATA_KEY, List.class);
addLinksToDocument(document, serviceClass, links);
}

/**
* Adds links found under the {@link LinkInfo#LINKS_METADATA_KEY} metadata key to the document,
* eventually filtering them by service class
*/
@SuppressWarnings("unchecked")
public static void addLinksToDocument(
AbstractDocument document, ResourceInfo resource, Class serviceClass) {
List<LinkInfo> links = resource.getMetadata().get(LINKS_METADATA_KEY, List.class);
addLinksToDocument(document, serviceClass, links);
}

@SuppressWarnings("unchecked")
public static void addLinksToDocument(
AbstractDocument document, SettingsInfo settings, Class<?> serviceClass) {
List<LinkInfo> links = settings.getMetadata().get(LINKS_METADATA_KEY, List.class);
addLinksToDocument(document, serviceClass, links);
}

private static void addLinksToDocument(
AbstractDocument document, Class serviceClass, List<LinkInfo> links) {
if (links != null) {
APIService annotation = APIDispatcher.getApiServiceAnnotation(serviceClass);
String service = Optional.ofNullable(annotation).map(s -> s.service()).orElse(null);
links.stream()
.filter(l -> serviceMatch(l, service))
.forEach(l -> addLinkToDocument(l, document));
}
}

private static void addLinkToDocument(LinkInfo l, AbstractDocument document) {
document.addLink(toLink(l));
}

/** Converts a LinkInfo to a Link */
public static Link toLink(LinkInfo l) {
return new Link(l.getHref(), l.getRel(), l.getType(), l.getTitle());
}

private static boolean serviceMatch(LinkInfo l, String service) {
return l.getService() == null || Objects.equals(l.getService(), service);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* (c) 2023 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.ogcapi;

import com.thoughtworks.xstream.XStream;
import java.util.List;
import org.geoserver.config.util.XStreamPersister;
import org.geoserver.config.util.XStreamPersisterInitializer;
import org.geoserver.ogcapi.impl.LinkInfoImpl;
import org.springframework.stereotype.Component;

/** Configures XStream for OGC API configuration objects that will end up in the metadata maps. */
@Component
public class OGCAPIXStreamPersisterInitializer implements XStreamPersisterInitializer {
@Override
public void init(XStreamPersister persister) {
XStream xs = persister.getXStream();
xs.alias("link", LinkInfo.class);
xs.addDefaultImplementation(LinkInfoImpl.class, LinkInfo.class);
persister.registerBreifMapComplexType("list", List.class);
xs.allowTypes(new Class[] {LinkInfo.class});
}
}
Loading

0 comments on commit a3d2f9b

Please sign in to comment.