SwaggerApiProducer.java
/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2016 ForgeRock AS.
*/
package org.forgerock.http.swagger;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.wrensecurity.guava.common.base.Strings.isNullOrEmpty;
import static org.forgerock.http.util.Paths.addLeadingSlash;
import static org.forgerock.http.util.Paths.removeTrailingSlash;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.wrensecurity.guava.common.base.Function;
import org.forgerock.http.ApiProducer;
import org.forgerock.http.header.AcceptApiVersionHeader;
import org.forgerock.http.routing.Version;
import io.swagger.models.Info;
import io.swagger.models.Model;
import io.swagger.models.Path;
import io.swagger.models.Response;
import io.swagger.models.Scheme;
import io.swagger.models.SecurityRequirement;
import io.swagger.models.Swagger;
import io.swagger.models.Tag;
import io.swagger.models.auth.SecuritySchemeDefinition;
import io.swagger.models.parameters.HeaderParameter;
import io.swagger.models.parameters.Parameter;
/**
* An API Producer for APIs that use the Swagger model implementation of the OpenAPI specification.
*/
public class SwaggerApiProducer implements ApiProducer<Swagger> {
private final List<Scheme> schemes;
private final String basePath;
private final Info info;
private final String host;
/**
* Create a new API Description Producer with {@literal null} as basePath, host and no scheme.
*
* @param info The Swagger {@code Info} instance to add to all OpenAPI descriptors.
*/
public SwaggerApiProducer(Info info) {
this(info, null, null, Collections.<Scheme> emptyList());
}
/**
* Create a new API Description Producer.
*
* @param info The Swagger {@code Info} instance to add to all OpenAPI descriptors.
* @param basePath The base path.
* @param host The host, if known at construction time, otherwise null.
* @param schemes The supported schemes.
*/
public SwaggerApiProducer(Info info, String basePath, String host, Scheme... schemes) {
this(info, basePath, host, asList(schemes));
}
/**
* Create a new API Description Producer.
*
* @param info The Swagger {@code Info} instance to add to all OpenAPI descriptors.
* @param basePath The base path.
* @param host The host, if known at construction time, otherwise null.
* @param schemes The supported schemes.
*/
public SwaggerApiProducer(Info info, String basePath, String host, List<Scheme> schemes) {
this.info = info;
this.basePath = basePath;
this.host = host;
this.schemes = new ArrayList<>(schemes);
}
@Override
public Swagger withPath(Swagger descriptor, String parentPath) {
return transform(descriptor, new PathTransformer(parentPath));
}
private static class PathTransformer implements Function<Map<String, Path>, Map<String, Path>> {
private final String parentPath;
PathTransformer(String parentPath) {
this.parentPath = addLeadingSlash(removeTrailingSlash(parentPath));
}
@Override
public Map<String, Path> apply(Map<String, Path> pathMap) {
Map<String, Path> result = new HashMap<>(pathMap.size());
for (Map.Entry<String, Path> entry : pathMap.entrySet()) {
String key = entry.getKey();
result.put(parentPath + addLeadingSlash(key), entry.getValue());
}
return result;
}
}
@Override
public Swagger withVersion(Swagger descriptor, Version version) {
return transform(descriptor, new VersionTransformer(version));
}
private static class VersionTransformer implements Function<Map<String, Path>, Map<String, Path>> {
public static final String PATH_FRAGMENT_MARKER = "#";
public static final String PATH_FRAGMENT_COMPONENT_SEPARATOR = "_";
private final Version version;
VersionTransformer(Version version) {
this.version = version;
}
@Override
public Map<String, Path> apply(Map<String, Path> pathMap) {
Map<String, Path> result = new HashMap<>(pathMap.size());
for (Map.Entry<String, Path> entry : pathMap.entrySet()) {
String key = entry.getKey();
Path path = entry.getValue();
HeaderParameter acceptVersionHeader = new HeaderParameter()
.name(AcceptApiVersionHeader.NAME)
._enum(singletonList(AcceptApiVersionHeader.RESOURCE + "=" + version));
path.addParameter(acceptVersionHeader);
if (key.contains(PATH_FRAGMENT_MARKER)) {
result.put(key + PATH_FRAGMENT_COMPONENT_SEPARATOR + version, path);
} else {
result.put(key + PATH_FRAGMENT_MARKER + version, path);
}
}
return result;
}
}
private Swagger transform(Swagger descriptor, Function<Map<String, Path>,
Map<String, Path>> transformer) {
Swagger swagger = addApiInfo(SwaggerUtils.clone(descriptor));
swagger.setPaths(transformer.apply(descriptor.getPaths()));
return swagger;
}
@Override
public Swagger merge(List<Swagger> descriptors) {
descriptors = new ArrayList<>(descriptors);
descriptors.removeAll(Collections.<Swagger>singletonList(null));
if (descriptors.isEmpty()) {
return null;
}
Swagger swagger = addApiInfo(new SwaggerExtended());
for (Swagger descriptor : descriptors) {
for (String consumes : ensureNotNull(descriptor.getConsumes())) {
swagger.consumes(consumes);
}
for (String produces : ensureNotNull(descriptor.getProduces())) {
swagger.produces(produces);
}
for (Tag tag : ensureNotNull(descriptor.getTags())) {
swagger.addTag(tag);
}
for (Map.Entry<String, Response> response : ensureNotNull(descriptor.getResponses()).entrySet()) {
if (isUndefinedEntry("response", response, swagger.getResponses())) {
swagger.response(response.getKey(), response.getValue());
}
}
for (Map.Entry<String, Parameter> parameter : ensureNotNull(descriptor.getParameters()).entrySet()) {
if (isUndefinedEntry("parameter", parameter, swagger.getParameters())) {
swagger.addParameter(parameter.getKey(), parameter.getValue());
}
}
for (Map.Entry<String, Object> extension : ensureNotNull(descriptor.getVendorExtensions()).entrySet()) {
if (isUndefinedEntry("extension", extension, swagger.getVendorExtensions())) {
swagger.vendorExtension(extension.getKey(), extension.getValue());
}
}
for (Map.Entry<String, Model> definition : ensureNotNull(descriptor.getDefinitions()).entrySet()) {
if (isUndefinedEntry("definition", definition, swagger.getDefinitions())) {
swagger.addDefinition(definition.getKey(), definition.getValue());
}
}
for (Map.Entry<String, Path> path : ensureNotNull(descriptor.getPaths()).entrySet()) {
validatePathNotDefined(path.getKey(), ensureNotNull(swagger.getPaths()).keySet());
swagger.path(path.getKey(), path.getValue());
}
for (SecurityRequirement security : ensureNotNull(descriptor.getSecurity())) {
swagger.security(security);
}
Map<String, SecuritySchemeDefinition> schemeDefinitionMap = ensureNotNull(descriptor
.getSecurityDefinitions());
for (Map.Entry<String, SecuritySchemeDefinition> secDef : schemeDefinitionMap.entrySet()) {
if (isUndefinedEntry("security definition", secDef, swagger.getSecurityDefinitions())) {
swagger.securityDefinition(secDef.getKey(), secDef.getValue());
}
}
}
return swagger;
}
private <T> Map<String, T> ensureNotNull(Map<String, T> map) {
return map == null ? Collections.<String, T>emptyMap() : map;
}
private <T> List<T> ensureNotNull(List<T> list) {
return list == null ? Collections.<T>emptyList() : list;
}
@Override
public Swagger addApiInfo(Swagger swagger) {
if (info != null) {
swagger.info(info.mergeWith(swagger.getInfo()));
}
return swagger.host(host).basePath(basePath).schemes(schemes);
}
private <V> boolean isUndefinedEntry(String entryType, Map.Entry<String, V> entry, Map<String, V> existing) {
V value = existing == null ? null : existing.get(entry.getKey());
if (value == null) {
return true;
}
if (value.equals(entry.getValue())) {
return false;
}
throw new IllegalArgumentException("Duplicated key for " + entryType + " but different value. Already got "
+ value);
}
private void validatePathNotDefined(String path, Set<String> paths) {
if (paths.contains(path)) {
throw new IllegalArgumentException("Duplicated path");
}
}
@Override
public ApiProducer<Swagger> newChildProducer(String subPath) {
return new SwaggerApiProducer(info, isNullOrEmpty(basePath) ? subPath : basePath + subPath, host, schemes);
}
}