Skip to content

Commit

Permalink
Updating OpenAPI output to the latest version of the spec
Browse files Browse the repository at this point in the history
  • Loading branch information
aaime committed May 11, 2018
1 parent 02dd3c3 commit e5f53d1
Show file tree
Hide file tree
Showing 8 changed files with 750 additions and 8 deletions.
20 changes: 20 additions & 0 deletions src/community/wfs3/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,26 @@
<artifactId>woodstox-core-asl</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-core</artifactId>
<version>2.0.1</version>
<exclusions>
<!-- Just need the module to read/write swagger definitions, keep deps to a minimum -->
<exclusion>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</exclusion>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
<exclusion>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.geoserver</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,29 @@
*/
package org.geoserver.wfs3;

import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.parameters.Parameter;
import net.opengis.wfs20.GetFeatureType;
import org.geoserver.ManifestLoader;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.NamespaceInfo;
import org.geoserver.config.ContactInfo;
import org.geoserver.config.GeoServer;
import org.geoserver.ows.Response;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.ServiceException;
import org.geoserver.wfs.WFSInfo;
import org.geoserver.wfs.WebFeatureService20;
import org.geoserver.wfs.request.FeatureCollectionResponse;
import org.geoserver.wfs3.response.APIDocument;
import org.geoserver.wfs3.response.CollectionsDocument;
import org.geoserver.wfs3.response.ConformanceDocument;
import org.geoserver.wfs3.response.LandingPageDocument;
import org.geotools.util.logging.Logging;
import org.opengis.filter.FilterFactory2;

import javax.xml.namespace.QName;
Expand All @@ -29,6 +37,10 @@
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.geoserver.wfs3.response.ConformanceDocument.CORE;
import static org.geoserver.wfs3.response.ConformanceDocument.GEOJSON;
Expand All @@ -38,6 +50,7 @@
/** WFS 3.0 implementation */
public class DefaultWebFeatureService30 implements WebFeatureService30 {

private static final Logger LOGGER = Logging.getLogger(DefaultWebFeatureService30.class);
private FilterFactory2 filterFactory;
private final GeoServer geoServer;
private WebFeatureService20 wfs20;
Expand Down Expand Up @@ -83,11 +96,6 @@ public Object collections(CollectionsRequest request) {
}
}

@Override
public APIDocument api(APIRequest request) {
return new APIDocument(getService(), getCatalog());
}

@Override
public ConformanceDocument conformance(ConformanceRequest request) {
List<String> classes = Arrays.asList(CORE, OAS30, GEOJSON, GMLSF0);
Expand Down Expand Up @@ -150,4 +158,10 @@ public static List<String> getAvailableFormats(Class responseType) {
}
return new ArrayList<>(formatNames);
}

@Override
public OpenAPI api(APIRequest request) {
return new OpenAPIBuilder().build(request, getService());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package org.geoserver.wfs3;

import io.swagger.v3.core.util.Yaml;
import io.swagger.v3.oas.models.ExternalDocumentation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.media.BinarySchema;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.StringSchema;
import io.swagger.v3.oas.models.servers.Server;
import org.geoserver.ManifestLoader;
import org.geoserver.config.ContactInfo;
import org.geoserver.ows.URLMangler;
import org.geoserver.ows.util.ResponseUtils;
import org.geoserver.platform.ServiceException;
import org.geoserver.util.IOUtils;
import org.geoserver.wfs.WFSInfo;
import org.geoserver.wfs.request.FeatureCollectionResponse;
import org.geoserver.wfs3.response.CollectionsDocument;
import org.geoserver.wfs3.response.ConformanceDocument;

import java.io.InputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class OpenAPIBuilder {

static final String OPENAPI_SPECIFICATION;

static {
try (InputStream is = OpenAPIBuilder.class.getResourceAsStream("openapi.yaml")) {
OPENAPI_SPECIFICATION = IOUtils.toString(is);
} catch (Exception e) {
throw new RuntimeException("Failed to read the openapi.yaml template", e);
}
}

public OpenAPI build(BaseRequest request, WFSInfo wfs) {
OpenAPI api = readTemplate();

// build "info"
ContactInfo contactInfo = wfs.getGeoServer().getGlobal().getSettings().getContact();
Contact contact =
new Contact()
.email(contactInfo.getContactEmail())
.name(
Stream.of(
contactInfo.getContactPerson(),
contactInfo.getContactOrganization())
.filter(s -> s != null)
.collect(Collectors.joining(" - ")))
.url(contactInfo.getOnlineResource());
String title = wfs.getTitle() == null ? "WFS 3.0 server" : wfs.getTitle();
String version = getGeoServerVersion();
Info info =
new Info()
.contact(contact)
.title(title)
.description(wfs.getAbstract())
.version(version);
api.info(info);

// the external documentation
api.externalDocs(
new ExternalDocumentation()
.description("WFS specification")
.url("https://github.com/opengeospatial/WFS_FES"));

// the servers
String wfsUrl =
ResponseUtils.buildURL(
request.getBaseUrl(), "wfs3", null, URLMangler.URLType.SERVICE);
api.servers(Arrays.asList(new Server().description("This server").url(wfsUrl)));

// adjust path output formats
declareGetResponseFormats(api, "/", OpenAPI.class);
declareGetResponseFormats(api, "/conformance", ConformanceDocument.class);
declareGetResponseFormats(api, "/collections", CollectionsDocument.class);
declareGetResponseFormats(api, "/collections/{collectionId}", CollectionsDocument.class);
declareGetResponseFormats(
api, "/collections/{collectionId}/items", FeatureCollectionResponse.class);
declareGetResponseFormats(
api,
"/collections/{collectionId}/items/{featureId}",
FeatureCollectionResponse.class);

return api;
}

private void declareGetResponseFormats(OpenAPI api, String path, Class<?> binding) {
PathItem pi = api.getPaths().get(path);
Operation get = pi.getGet();
Content content = get.getResponses().get("200").getContent();
List<String> formats = DefaultWebFeatureService30.getAvailableFormats(binding);
// first remove the ones missing
Set<String> missingFormats = new HashSet<>(content.keySet());
missingFormats.removeAll(formats);
missingFormats.forEach(f -> content.remove(f));
// then add the ones not already declared
Set<String> extraFormats = new HashSet<>(formats);
extraFormats.removeAll(content.keySet());
for (String extraFormat : extraFormats) {
MediaType mediaType = new MediaType();
if (extraFormat.contains("yaml") && content.get("application/json") != null) {
// same schema as JSON
mediaType.schema(content.get("application/json").getSchema());
} else if (extraFormat.contains("text")) {
mediaType.schema(new StringSchema());
} else {
mediaType.schema(new BinarySchema());
}
content.addMediaType(extraFormat, mediaType);
}
}

/**
* Reads the template to customize (each time, as the object tree is not thread safe nor
* cloneable not serializable)
*
* @return
*/
private OpenAPI readTemplate() {
try {
return Yaml.mapper().readValue(OPENAPI_SPECIFICATION, OpenAPI.class);
} catch (Exception e) {
throw new ServiceException(e);
}
}

private String getGeoServerVersion() {
ManifestLoader.AboutModel versions = ManifestLoader.getVersions();
TreeSet<ManifestLoader.AboutModel.ManifestModel> manifests = versions.getManifests();
return manifests
.stream()
.filter(m -> m.getName().equalsIgnoreCase("GeoServer"))
.map(m -> m.getEntries().get("Version"))
.findFirst()
.orElse("1.0.0");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
*/
package org.geoserver.wfs3;

import io.swagger.v3.oas.models.OpenAPI;
import net.opengis.wfs20.GetFeatureType;
import org.geoserver.wfs3.response.APIDocument;
import org.geoserver.wfs3.response.CollectionDocument;
import org.geoserver.wfs3.response.CollectionsDocument;
import org.geoserver.wfs3.response.ConformanceDocument;
Expand All @@ -32,7 +32,7 @@ public interface WebFeatureService30 {
* @param request
* @return
*/
APIDocument api(APIRequest request);
OpenAPI api(APIRequest request);

/**
* The conformance declaration for this service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.introspect.Annotated;
import com.fasterxml.jackson.dataformat.xml.JacksonXmlAnnotationIntrospector;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
Expand Down Expand Up @@ -75,6 +76,7 @@ public void write(Object value, OutputStream output, Operation operation) throws
YAMLFactory factory = new YAMLFactory();
mapper = new ObjectMapper(factory);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
mapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
} else if (isXMLFormat(operation)) {
mapper = new XmlMapper();
// using a custom annotation introspector to set the desired namespace
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* (c) 2018 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.wfs3.response;

import io.swagger.v3.oas.models.OpenAPI;
import org.geoserver.config.GeoServer;
import org.geoserver.platform.Operation;

/**
* JSON/YAML encoding for the API document
*/
public class OpenAPIResponse extends JacksonResponse {

public OpenAPIResponse(GeoServer gs) {
super(gs, OpenAPI.class);
}

protected String getFileName(Object value, Operation operation) {
return "api";
}
}
3 changes: 3 additions & 0 deletions src/community/wfs3/src/main/resources/applicationContext.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@
<bean id="apiResponse" class="org.geoserver.wfs3.response.APIDocumentResponse">
<constructor-arg ref="geoServer"/>
</bean>
<bean id="openAPIResponse" class="org.geoserver.wfs3.response.OpenAPIResponse">
<constructor-arg ref="geoServer"/>
</bean>
<bean id="landingPageResponse" class="org.geoserver.wfs3.response.LandingPageResponse">
<constructor-arg ref="geoServer"/>
</bean>
Expand Down

0 comments on commit e5f53d1

Please sign in to comment.