This is an automated email from the ASF dual-hosted git repository.
reta pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/master by this push:
new e79f5cb CXF-7525: Add support for Swagger 2.0 (OpenApi Spec 3.0).
Initial integration implementation. Adding test cases, security definitions and
initial version of OpenApiParseUtils
e79f5cb is described below
commit e79f5cbb66d12d1db6e4c6154cfbb736e983f3c3
Author: reta <[email protected]>
AuthorDate: Fri Dec 29 10:34:38 2017 -0500
CXF-7525: Add support for Swagger 2.0 (OpenApi Spec 3.0). Initial
integration implementation. Adding test cases, security definitions and initial
version of OpenApiParseUtils
---
rt/rs/description-openapi-v3/pom.xml | 7 +
.../apache/cxf/jaxrs/openapi/OpenApiFeature.java | 35 ++-
.../cxf/jaxrs/openapi/parse/OpenApiParseUtils.java | 247 +++++++++++++++++++++
.../jaxrs/openapi/parse/ParseConfiguration.java | 25 +++
systests/jaxrs/pom.xml | 6 +
.../AbstractOpenApiServiceDescriptionTest.java | 225 +++++++++++++++++++
.../description/openapi/BookStoreOpenApi.java | 92 ++++++++
.../openapi/BookStoreStylesheetsOpenApi.java | 37 +++
.../openapi/OpenApiCustomPropertiesTest.java | 70 ++++++
.../OpenApiNonAnnotatedServiceDescriptionTest.java | 77 +++++++
.../OpenApiRegularServiceDescriptionTest.java | 51 +++++
.../jaxrs/description/openapi/OpenApiServer.java | 82 +++++++
12 files changed, 953 insertions(+), 1 deletion(-)
diff --git a/rt/rs/description-openapi-v3/pom.xml
b/rt/rs/description-openapi-v3/pom.xml
index 7b6d02a..a22aeb5 100644
--- a/rt/rs/description-openapi-v3/pom.xml
+++ b/rt/rs/description-openapi-v3/pom.xml
@@ -59,6 +59,13 @@
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-rs-json-basic</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-jaxrs2</artifactId>
</dependency>
diff --git
a/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/OpenApiFeature.java
b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/OpenApiFeature.java
index 622a601..ec0709f 100644
---
a/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/OpenApiFeature.java
+++
b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/OpenApiFeature.java
@@ -23,6 +23,7 @@ import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
+import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
@@ -50,10 +51,12 @@ import
io.swagger.v3.oas.integration.OpenApiConfigurationException;
import io.swagger.v3.oas.integration.SwaggerConfiguration;
import io.swagger.v3.oas.integration.api.OpenAPIConfiguration;
import io.swagger.v3.oas.integration.api.OpenApiContext;
+import io.swagger.v3.oas.models.Components;
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.info.License;
+import io.swagger.v3.oas.models.security.SecurityScheme;
@Provider(value = Type.Feature, scope = Scope.Server)
public class OpenApiFeature extends AbstractFeature implements
SwaggerUiSupport, SwaggerProperties {
@@ -85,7 +88,10 @@ public class OpenApiFeature extends AbstractFeature
implements SwaggerUiSupport,
private String swaggerUiVersion;
private String swaggerUiMavenGroupAndArtifact;
private Map<String, String> swaggerUiMediaTypes;
-
+
+ // Additional components
+ private Map<String, SecurityScheme> securityDefinitions;
+
// Allows to pass the configuration location, usually
openapi-configuration.json
// or openapi-configuration.yml file.
private String configLocation;
@@ -129,6 +135,8 @@ public class OpenApiFeature extends AbstractFeature
implements SwaggerUiSupport,
}
final OpenAPI oas = new OpenAPI().info(getInfo(swaggerProps));
+
registerComponents(securityDefinitions).ifPresent(oas::setComponents);
+
final SwaggerConfiguration config = new SwaggerConfiguration()
.openAPI(oas)
.prettyPrint(getOrFallback(isPrettyPrint(), swaggerProps,
PRETTY_PRINT_PROPERTY))
@@ -342,6 +350,18 @@ public class OpenApiFeature extends AbstractFeature
implements SwaggerUiSupport,
this.propertiesLocation = propertiesLocation;
}
+ public void setRunAsFilter(boolean runAsFilter) {
+ this.runAsFilter = runAsFilter;
+ }
+
+ public Map<String, SecurityScheme> getSecurityDefinitions() {
+ return securityDefinitions;
+ }
+
+ public void setSecurityDefinitions(Map<String, SecurityScheme>
securityDefinitions) {
+ this.securityDefinitions = securityDefinitions;
+ }
+
@Override
public String findSwaggerUiRoot() {
return SwaggerUi.findSwaggerUiRoot(swaggerUiMavenGroupAndArtifact,
swaggerUiVersion);
@@ -494,4 +514,17 @@ public class OpenApiFeature extends AbstractFeature
implements SwaggerUiSupport,
destination.setProperty(name, source.getProperty(name));
}
}
+
+ private static Optional<Components> registerComponents(Map<String,
SecurityScheme> securityDefinitions) {
+ final Components components = new Components();
+
+ boolean hasComponents = false;
+ if (securityDefinitions != null && !securityDefinitions.isEmpty()) {
+ securityDefinitions.entrySet().forEach(entry ->
+ components.addSecuritySchemes(entry.getKey(),
entry.getValue()));
+ hasComponents |= true;
+ }
+
+ return hasComponents ? Optional.of(components) : Optional.empty();
+ }
}
diff --git
a/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/parse/OpenApiParseUtils.java
b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/parse/OpenApiParseUtils.java
new file mode 100644
index 0000000..7155c79
--- /dev/null
+++
b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/parse/OpenApiParseUtils.java
@@ -0,0 +1,247 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.jaxrs.openapi.parse;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.jaxrs.json.basic.JsonMapObjectReaderWriter;
+import org.apache.cxf.jaxrs.model.Parameter;
+import org.apache.cxf.jaxrs.model.ParameterType;
+import org.apache.cxf.jaxrs.model.UserApplication;
+import org.apache.cxf.jaxrs.model.UserOperation;
+import org.apache.cxf.jaxrs.model.UserResource;
+import org.apache.cxf.jaxrs.utils.ResourceUtils;
+
+public final class OpenApiParseUtils {
+ private static final Logger LOG =
LogUtils.getL7dLogger(ResourceUtils.class);
+ private static final Map<String, Class<?>> OPENAPI_TYPE_MAP;
+ static {
+ OPENAPI_TYPE_MAP = new HashMap<>();
+ OPENAPI_TYPE_MAP.put("string", String.class);
+ OPENAPI_TYPE_MAP.put("integer", int.class);
+ OPENAPI_TYPE_MAP.put("float", float.class);
+ OPENAPI_TYPE_MAP.put("double", double.class);
+ OPENAPI_TYPE_MAP.put("int", int.class);
+ OPENAPI_TYPE_MAP.put("long", long.class);
+ OPENAPI_TYPE_MAP.put("byte", byte.class);
+ OPENAPI_TYPE_MAP.put("boolean", boolean.class);
+ OPENAPI_TYPE_MAP.put("date", java.util.Date.class);
+ OPENAPI_TYPE_MAP.put("dateTime", java.util.Date.class);
+ OPENAPI_TYPE_MAP.put("password", String.class);
+ OPENAPI_TYPE_MAP.put("binary", java.io.InputStream.class);
+ }
+ private OpenApiParseUtils() {
+
+ }
+ public static UserApplication getUserApplication(String loc) {
+ return getUserApplication(loc, BusFactory.getThreadDefaultBus());
+ }
+ public static UserApplication getUserApplication(String loc, Bus bus) {
+ return getUserApplication(loc, bus, new ParseConfiguration());
+ }
+ public static UserApplication getUserApplication(String loc, Bus bus,
ParseConfiguration cfg) {
+ try {
+ InputStream is = ResourceUtils.getResourceStream(loc, bus);
+ if (is == null) {
+ return null;
+ }
+ return getUserApplicationFromStream(is, cfg);
+ } catch (Exception ex) {
+ LOG.warning("Problem with processing a user model at " + loc);
+ }
+ return null;
+ }
+ public static UserApplication getUserApplicationFromStream(InputStream is)
throws IOException {
+ return getUserApplicationFromStream(is, new ParseConfiguration());
+ }
+ public static UserApplication getUserApplicationFromStream(InputStream is,
+
ParseConfiguration cfg) throws IOException {
+ return getUserApplicationFromJson(IOUtils.readStringFromStream(is),
cfg);
+ }
+ public static UserApplication getUserApplicationFromJson(String json) {
+ return getUserApplicationFromJson(json, new ParseConfiguration());
+ }
+ public static UserApplication getUserApplicationFromJson(String json,
+
ParseConfiguration cfg) {
+ JsonMapObjectReaderWriter reader = new JsonMapObjectReaderWriter();
+ Map<String, Object> map = reader.fromJson(json);
+
+ UserApplication app = new UserApplication();
+ app.setBasePath("/");
+
+ Map<String, List<UserOperation>> userOpsMap = new
LinkedHashMap<String, List<UserOperation>>();
+ Set<String> tags = new HashSet<>();
+ List<Map<String, Object>> tagsProp =
CastUtils.cast((List<?>)map.get("tags"));
+ if (tagsProp != null) {
+ for (Map<String, Object> tagProp : tagsProp) {
+ tags.add((String)tagProp.get("name"));
+ }
+ } else {
+ tags.add("");
+ }
+
+ for (String tag : tags) {
+ userOpsMap.put(tag, new LinkedList<UserOperation>());
+ }
+
+
+ Map<String, Map<String, Object>> paths = CastUtils.cast((Map<?,
?>)map.get("paths"));
+ for (Map.Entry<String, Map<String, Object>> pathEntry :
paths.entrySet()) {
+ String operPath = pathEntry.getKey();
+
+ Map<String, Object> operations = pathEntry.getValue();
+ for (Map.Entry<String, Object> operEntry : operations.entrySet()) {
+
+ UserOperation userOp = new UserOperation();
+ userOp.setVerb(operEntry.getKey().toUpperCase());
+
+ Map<String, Object> oper = CastUtils.cast((Map<?,
?>)operEntry.getValue());
+
+ userOp.setPath(operPath);
+
+ userOp.setName((String)oper.get("operationId"));
+ Map<String, Object> responses = CastUtils.cast((Map<?,
?>)oper.get("responses"));
+ if (responses != null) {
+ userOp.setProduces(listToString(
+ responses
+ .entrySet()
+ .stream()
+ .map(entry -> CastUtils.cast((Map<?,
?>)entry.getValue()))
+ .map(value -> CastUtils.cast((Map<?,
?>)value.get("content")))
+ .filter(Objects::nonNull)
+ .flatMap(content ->
content.keySet().stream().map(type -> (String)type))
+ .collect(Collectors.toList())
+ ));
+ }
+
+ Map<String, Object> payloads = CastUtils.cast((Map<?,
?>)oper.get("requestBody"));
+ if (payloads != null) {
+ userOp.setConsumes(listToString(
+ payloads
+ .entrySet()
+ .stream()
+ .map(entry -> CastUtils.cast((Map<?,
?>)entry.getValue()))
+ .map(value -> CastUtils.cast((Map<?,
?>)value.get("content")))
+ .filter(Objects::nonNull)
+ .flatMap(content ->
content.keySet().stream().map(type -> (String)type))
+ .collect(Collectors.toList())
+ ));
+ }
+
+ List<Parameter> userOpParams = new LinkedList<Parameter>();
+ List<Map<String, Object>> params =
CastUtils.cast((List<?>)oper.get("parameters"));
+ if (params != null) {
+ for (Map<String, Object> param : params) {
+ String name = (String)param.get("name");
+ //"query", "header", "path" or "cookie".
+ String paramType = (String)param.get("in");
+ ParameterType pType = null;
+
+ if ("query".equals(paramType)) {
+ pType = ParameterType.QUERY;
+ } else if ("header".equals(paramType)) {
+ pType = ParameterType.HEADER;
+ } else if ("path".equals(paramType)) {
+ pType = ParameterType.PATH;
+ } else if ("cookie".equals(paramType)) {
+ pType = ParameterType.COOKIE;
+ } else {
+ pType = ParameterType.REQUEST_BODY;
+ }
+
+ Parameter userParam = new Parameter(pType, name);
+ setJavaType(userParam, (String)param.get("type"));
+ userOpParams.add(userParam);
+ }
+ }
+ if (!userOpParams.isEmpty()) {
+ userOp.setParameters(userOpParams);
+ }
+ List<String> opTags =
CastUtils.cast((List<?>)oper.get("tags"));
+ if (opTags == null) {
+ opTags = Collections.singletonList("");
+ }
+ for (String opTag : opTags) {
+ userOpsMap.get(opTag).add(userOp);
+ }
+
+ }
+ }
+
+ List<UserResource> resources = new LinkedList<UserResource>();
+
+ for (Map.Entry<String, List<UserOperation>> entry :
userOpsMap.entrySet()) {
+ UserResource ur = new UserResource();
+ ur.setPath("/");
+ ur.setOperations(entry.getValue());
+ ur.setName(entry.getKey());
+ resources.add(ur);
+ }
+
+ app.setResources(resources);
+ return app;
+ }
+
+ private static void setJavaType(Parameter userParam, String typeName) {
+ Class<?> javaType = OPENAPI_TYPE_MAP.get(typeName);
+ if (javaType == null) {
+ try {
+ // May work if the model has already been compiled
+ // TODO: need to know the package name
+ javaType = ClassLoaderUtils.loadClass(typeName,
OpenApiParseUtils.class);
+ } catch (Throwable t) {
+ // ignore
+ }
+ }
+
+ userParam.setJavaType(javaType);
+ }
+
+ private static String listToString(List<String> list) {
+ if (list != null) {
+ StringBuilder sb = new StringBuilder();
+ for (String s : list) {
+ if (sb.length() > 0) {
+ sb.append(',');
+ }
+ sb.append(s);
+ }
+ return sb.toString();
+ }
+ return null;
+ }
+}
diff --git
a/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/parse/ParseConfiguration.java
b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/parse/ParseConfiguration.java
new file mode 100644
index 0000000..3d72bf9
--- /dev/null
+++
b/rt/rs/description-openapi-v3/src/main/java/org/apache/cxf/jaxrs/openapi/parse/ParseConfiguration.java
@@ -0,0 +1,25 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.jaxrs.openapi.parse;
+
+public class ParseConfiguration {
+
+ public ParseConfiguration() {
+ }
+}
diff --git a/systests/jaxrs/pom.xml b/systests/jaxrs/pom.xml
index 450aed6..0973471 100644
--- a/systests/jaxrs/pom.xml
+++ b/systests/jaxrs/pom.xml
@@ -137,6 +137,12 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.apache.cxf</groupId>
+ <artifactId>cxf-rt-rs-service-description-openapi-v3</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.webjars</groupId>
<artifactId>swagger-ui</artifactId>
<version>${cxf.swagger.ui.version}</version>
diff --git
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/AbstractOpenApiServiceDescriptionTest.java
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/AbstractOpenApiServiceDescriptionTest.java
new file mode 100644
index 0000000..03e8fab
--- /dev/null
+++
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/AbstractOpenApiServiceDescriptionTest.java
@@ -0,0 +1,225 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.systest.jaxrs.description.openapi;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MediaType;
+
+import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+
+import org.apache.cxf.ext.logging.LoggingFeature;
+import org.apache.cxf.feature.Feature;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.cxf.jaxrs.model.AbstractResourceInfo;
+import org.apache.cxf.jaxrs.model.Parameter;
+import org.apache.cxf.jaxrs.model.ParameterType;
+import org.apache.cxf.jaxrs.model.UserApplication;
+import org.apache.cxf.jaxrs.model.UserOperation;
+import org.apache.cxf.jaxrs.model.UserResource;
+import org.apache.cxf.jaxrs.openapi.OpenApiFeature;
+import org.apache.cxf.jaxrs.openapi.parse.OpenApiParseUtils;
+import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
+import org.apache.cxf.testutil.common.AbstractBusTestServerBase;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.equalTo;
+
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.security.SecurityScheme.Type;
+
+public abstract class AbstractOpenApiServiceDescriptionTest extends
AbstractBusClientServerTestBase {
+ static final String SECURITY_DEFINITION_NAME = "basicAuth";
+
+ private static final String CONTACT = "[email protected]";
+ private static final String TITLE = "CXF unittest";
+ private static final String DESCRIPTION = "API Description";
+ private static final String LICENSE = "API License";
+ private static final String LICENSE_URL = "API License URL";
+
+ @Ignore
+ public abstract static class Server extends AbstractBusTestServerBase {
+ protected final String port;
+ protected final boolean runAsFilter;
+
+ Server(final String port, final boolean runAsFilter) {
+ this.port = port;
+ this.runAsFilter = runAsFilter;
+ }
+
+ @Override
+ protected void run() {
+ final JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
+ sf.setResourceClasses(BookStoreOpenApi.class);
+ sf.setResourceClasses(BookStoreStylesheetsOpenApi.class);
+ sf.setResourceProvider(BookStoreOpenApi.class,
+ new SingletonResourceProvider(new BookStoreOpenApi()));
+ sf.setProvider(new JacksonJsonProvider());
+ final OpenApiFeature feature = createOpenApiFeature();
+ sf.setFeatures(Arrays.asList(feature));
+ sf.setAddress("http://localhost:" + port + "/");
+ sf.create();
+ }
+
+ protected OpenApiFeature createOpenApiFeature() {
+ final OpenApiFeature feature = new OpenApiFeature();
+ feature.setRunAsFilter(runAsFilter);
+ feature.setContactName(CONTACT);
+ feature.setTitle(TITLE);
+ feature.setDescription(DESCRIPTION);
+ feature.setLicense(LICENSE);
+ feature.setLicenseUrl(LICENSE_URL);
+
+
feature.setSecurityDefinitions(Collections.singletonMap(SECURITY_DEFINITION_NAME,
+ new SecurityScheme().type(Type.HTTP)));
+
+ return feature;
+ }
+
+ protected static void start(final Server s) {
+ try {
+ s.start();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ System.exit(-1);
+ } finally {
+ System.out.println("done!");
+ }
+ }
+ }
+
+ protected static void startServers(final Class< ? extends Server>
serverClass) throws Exception {
+ AbstractResourceInfo.clearAllMaps();
+ //keep out of process due to stack traces testing failures
+ assertTrue("server did not launch correctly",
launchServer(serverClass, true));
+ createStaticBus();
+ }
+
+ protected abstract String getPort();
+
+ protected void doTestApiListingIsProperlyReturnedJSON() throws Exception {
+ doTestApiListingIsProperlyReturnedJSON(false);
+ }
+ protected void doTestApiListingIsProperlyReturnedJSON(boolean
useXForwarded) throws Exception {
+
doTestApiListingIsProperlyReturnedJSON(createWebClient("/openapi.json"),
+ useXForwarded);
+ checkUiResource();
+ }
+ protected static void doTestApiListingIsProperlyReturnedJSON(final
WebClient client,
+ boolean
useXForwarded) throws Exception {
+ if (useXForwarded) {
+ client.header("USE_XFORWARDED", true);
+ }
+ try {
+ String swaggerJson = client.get(String.class);
+ UserApplication ap =
OpenApiParseUtils.getUserApplicationFromJson(swaggerJson);
+ assertNotNull(ap);
+ assertEquals(useXForwarded ? "/reverse" : "/", ap.getBasePath());
+
+ List<UserResource> urs = ap.getResources();
+ assertNotNull(urs);
+ assertEquals(1, urs.size());
+ UserResource r = urs.get(0);
+ String basePath = "";
+ if (!"/".equals(r.getPath())) {
+ basePath = r.getPath();
+ }
+ Map<String, UserOperation> map = r.getOperationsAsMap();
+ assertEquals(3, map.size());
+ UserOperation getBooksOp = map.get("getBooks");
+ assertEquals(HttpMethod.GET, getBooksOp.getVerb());
+ assertEquals("/bookstore", basePath + getBooksOp.getPath());
+ assertEquals(MediaType.APPLICATION_JSON, getBooksOp.getProduces());
+ List<Parameter> getBooksOpParams = getBooksOp.getParameters();
+ assertEquals(1, getBooksOpParams.size());
+ assertEquals(ParameterType.QUERY,
getBooksOpParams.get(0).getType());
+ UserOperation getBookOp = map.get("getBook");
+ assertEquals(HttpMethod.GET, getBookOp.getVerb());
+ assertEquals("/bookstore/{id}", basePath + getBookOp.getPath());
+ assertEquals(MediaType.APPLICATION_JSON, getBookOp.getProduces());
+ List<Parameter> getBookOpParams = getBookOp.getParameters();
+ assertEquals(1, getBookOpParams.size());
+ assertEquals(ParameterType.PATH, getBookOpParams.get(0).getType());
+ UserOperation deleteOp = map.get("delete");
+ assertEquals(HttpMethod.DELETE, deleteOp.getVerb());
+ assertEquals("/bookstore/{id}", basePath + deleteOp.getPath());
+ List<Parameter> delOpParams = deleteOp.getParameters();
+ assertEquals(1, delOpParams.size());
+ assertEquals(ParameterType.PATH, delOpParams.get(0).getType());
+
+ assertThat(swaggerJson, CoreMatchers.containsString(CONTACT));
+ assertThat(swaggerJson, CoreMatchers.containsString(TITLE));
+ assertThat(swaggerJson, CoreMatchers.containsString(DESCRIPTION));
+ assertThat(swaggerJson, CoreMatchers.containsString(LICENSE));
+ assertThat(swaggerJson, CoreMatchers.containsString(LICENSE_URL));
+ assertThat(swaggerJson,
CoreMatchers.containsString(SECURITY_DEFINITION_NAME));
+ } finally {
+ client.close();
+ }
+ }
+
+ @Test
+ public void testNonUiResource() {
+ // Test that Swagger UI resources do not interfere with
+ // application-specific ones.
+ WebClient uiClient = WebClient
+ .create("http://localhost:" + getPort() + "/css/book.css")
+ .accept("text/css");
+ String css = uiClient.get(String.class);
+ assertThat(css, equalTo("body { background-color: lightblue; }"));
+ }
+
+ @Test
+ public void testUiResource() {
+ // Test that Swagger UI resources do not interfere with
+ // application-specific ones and are accessible.
+ WebClient uiClient = WebClient
+ .create("http://localhost:" + getPort() + "/swagger-ui.css")
+ .accept("text/css");
+ String css = uiClient.get(String.class);
+ assertThat(css, containsString(".swagger-ui{font"));
+ }
+
+
+ protected WebClient createWebClient(final String url) {
+ return WebClient
+ .create("http://localhost:" + getPort() + url,
+ Arrays.< Object >asList(new JacksonJsonProvider()),
+ Arrays.< Feature >asList(new LoggingFeature()),
+ null)
+ .accept(MediaType.APPLICATION_JSON).accept("application/yaml");
+ }
+
+ protected void checkUiResource() {
+ WebClient uiClient = WebClient.create("http://localhost:" + getPort()
+ "/api-docs")
+ .accept(MediaType.WILDCARD);
+ String uiHtml = uiClient.get(String.class);
+ assertTrue(uiHtml.contains("<title>Swagger UI</title>"));
+ }
+}
diff --git
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/BookStoreOpenApi.java
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/BookStoreOpenApi.java
new file mode 100644
index 0000000..455b20e
--- /dev/null
+++
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/BookStoreOpenApi.java
@@ -0,0 +1,92 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cxf.systest.jaxrs.description.openapi;
+
+import java.util.Arrays;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import org.apache.cxf.systest.jaxrs.Book;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.ArraySchema;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+
+@Path("/bookstore")
+public class BookStoreOpenApi {
+ @Produces({ MediaType.APPLICATION_JSON })
+ @GET
+ @Operation(
+ description = "Get books",
+ responses = @ApiResponse(
+ responseCode = "200",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ array = @ArraySchema(schema = @Schema(implementation =
Book.class))
+ )
+ )
+ )
+ public Response getBooks(
+ @Parameter(description = "Page to fetch", required = true)
@QueryParam("page") @DefaultValue("1") int page) {
+ return Response.ok(
+ Arrays.asList(
+ new Book("Book 1", 1),
+ new Book("Book 2", 2)
+ )
+ ).build();
+ }
+
+ @Produces({ MediaType.APPLICATION_JSON })
+ @Path("/{id}")
+ @GET
+ @Operation(
+ description = "Get book by Id",
+ responses = @ApiResponse(
+ responseCode = "200",
+ content = @Content(
+ mediaType = MediaType.APPLICATION_JSON,
+ schema = @Schema(implementation = Book.class)
+ )
+ )
+ )
+ public Book getBook(@Parameter(required = true) @PathParam("id") Long id) {
+ return new Book("Book", id);
+ }
+
+ @Path("/{id}")
+ @DELETE
+ @Operation(
+ description = "Get book by Id",
+ responses = @ApiResponse(responseCode = "200")
+ )
+ public Response delete(@Parameter(required = true) @PathParam("id") String
id) {
+ return Response.ok().build();
+ }
+}
diff --git
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/BookStoreStylesheetsOpenApi.java
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/BookStoreStylesheetsOpenApi.java
new file mode 100644
index 0000000..f989d50
--- /dev/null
+++
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/BookStoreStylesheetsOpenApi.java
@@ -0,0 +1,37 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cxf.systest.jaxrs.description.openapi;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+
+import io.swagger.v3.oas.annotations.Operation;
+
+@Path("/")
+public class BookStoreStylesheetsOpenApi {
+ @Operation(hidden = true)
+ @Produces({ "text/css" })
+ @Path("/css/book.css")
+ @GET
+ public String getCss() {
+ return "body { background-color: lightblue; }";
+ }
+}
diff --git
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiCustomPropertiesTest.java
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiCustomPropertiesTest.java
new file mode 100644
index 0000000..98e9592
--- /dev/null
+++
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiCustomPropertiesTest.java
@@ -0,0 +1,70 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.systest.jaxrs.description.openapi;
+
+import java.util.Collections;
+
+import org.apache.cxf.jaxrs.openapi.OpenApiFeature;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import io.swagger.v3.oas.models.security.SecurityScheme.Type;
+
+public class OpenApiCustomPropertiesTest extends
AbstractOpenApiServiceDescriptionTest {
+ private static final String PORT =
allocatePort(OpenApiCustomPropertiesTest.class);
+
+ public static class OpenApiRegular extends Server {
+ public OpenApiRegular() {
+ super(PORT, false);
+ }
+
+ public static void main(String[] args) {
+ start(new OpenApiRegular());
+ }
+
+ @Override
+ protected OpenApiFeature createOpenApiFeature() {
+ final OpenApiFeature feature = new OpenApiFeature();
+ feature.setRunAsFilter(runAsFilter);
+ feature.setPropertiesLocation("/files/swagger.properties");
+
+
feature.setSecurityDefinitions(Collections.singletonMap(SECURITY_DEFINITION_NAME,
+ new SecurityScheme().type(Type.HTTP)));
+
+ return feature;
+ }
+ }
+
+ @BeforeClass
+ public static void startServers() throws Exception {
+ startServers(OpenApiRegular.class);
+ }
+
+ @Override
+ protected String getPort() {
+ return PORT;
+ }
+
+ @Test
+ public void testApiListingIsProperlyReturnedJSON() throws Exception {
+ doTestApiListingIsProperlyReturnedJSON();
+ }
+}
diff --git
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiNonAnnotatedServiceDescriptionTest.java
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiNonAnnotatedServiceDescriptionTest.java
new file mode 100644
index 0000000..93866fa
--- /dev/null
+++
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiNonAnnotatedServiceDescriptionTest.java
@@ -0,0 +1,77 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.systest.jaxrs.description.openapi;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.cxf.jaxrs.openapi.OpenApiFeature;
+import org.apache.cxf.systest.jaxrs.description.group1.BookStore;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class OpenApiNonAnnotatedServiceDescriptionTest extends
AbstractOpenApiServiceDescriptionTest {
+ private static final String PORT =
allocatePort(OpenApiNonAnnotatedServiceDescriptionTest.class);
+
+ public static class OpenApiRegularNonAnnotated extends Server {
+ public OpenApiRegularNonAnnotated() {
+ super(PORT, false);
+ }
+
+ @Override
+ protected void run() {
+ final JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
+ sf.setResourceClasses(BookStore.class);
+ sf.setResourceClasses(BookStoreStylesheetsOpenApi.class);
+ sf.setResourceProvider(BookStore.class,
+ new SingletonResourceProvider(new BookStore()));
+ sf.setProvider(new JacksonJsonProvider());
+ final OpenApiFeature feature = createOpenApiFeature();
+
feature.setResourcePackages(Collections.singleton("org.apache.cxf.systest.jaxrs.description.group1"));
+ feature.setReadAllResources(true);
+ sf.setFeatures(Arrays.asList(feature));
+ sf.setAddress("http://localhost:" + port + "/");
+ sf.create();
+ }
+
+ public static void main(String[] args) {
+ start(new OpenApiRegularNonAnnotated());
+ }
+ }
+
+ @BeforeClass
+ public static void startServers() throws Exception {
+ startServers(OpenApiRegularNonAnnotated.class);
+ }
+
+ @Override
+ protected String getPort() {
+ return PORT;
+ }
+
+ @Test
+ public void testApiListingIsProperlyReturnedJSON() throws Exception {
+ doTestApiListingIsProperlyReturnedJSON();
+ }
+}
diff --git
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiRegularServiceDescriptionTest.java
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiRegularServiceDescriptionTest.java
new file mode 100644
index 0000000..cfd01dd
--- /dev/null
+++
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiRegularServiceDescriptionTest.java
@@ -0,0 +1,51 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.systest.jaxrs.description.openapi;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class OpenApiRegularServiceDescriptionTest extends
AbstractOpenApiServiceDescriptionTest {
+ private static final String PORT =
allocatePort(OpenApiRegularServiceDescriptionTest.class);
+
+ public static class OpenApiRegular extends Server {
+ public OpenApiRegular() {
+ super(PORT, false);
+ }
+
+ public static void main(String[] args) {
+ start(new OpenApiRegular());
+ }
+ }
+
+ @BeforeClass
+ public static void startServers() throws Exception {
+ startServers(OpenApiRegular.class);
+ }
+
+ @Override
+ protected String getPort() {
+ return PORT;
+ }
+
+ @Test
+ public void testApiListingIsProperlyReturnedJSON() throws Exception {
+ doTestApiListingIsProperlyReturnedJSON();
+ }
+}
diff --git
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiServer.java
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiServer.java
new file mode 100644
index 0000000..ec358d6
--- /dev/null
+++
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/description/openapi/OpenApiServer.java
@@ -0,0 +1,82 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cxf.systest.jaxrs.description.openapi;
+
+import java.net.URISyntaxException;
+
+import org.apache.cxf.testutil.common.AbstractBusTestServerBase;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import org.eclipse.jetty.webapp.WebAppContext;
+
+
+public class OpenApiServer extends AbstractBusTestServerBase {
+ static final String PORT = allocatePort(OpenApiServer.class);
+
+ private org.eclipse.jetty.server.Server server;
+
+ protected void run() {
+ server = new org.eclipse.jetty.server.Server(Integer.parseInt(PORT));
+
+ WebAppContext webappcontext = new WebAppContext();
+ String contextPath = null;
+ try {
+ contextPath =
getClass().getResource("/jaxrs_openapi_v3").toURI().getPath();
+ } catch (URISyntaxException e1) {
+ e1.printStackTrace();
+ }
+ webappcontext.setContextPath("/");
+
+ webappcontext.setWar(contextPath);
+
+ HandlerCollection handlers = new HandlerCollection();
+ handlers.setHandlers(new Handler[] {webappcontext, new
DefaultHandler()});
+
+ server.setHandler(handlers);
+ try {
+ server.start();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ public void tearDown() throws Exception {
+ super.tearDown();
+ if (server != null) {
+ server.stop();
+ server.destroy();
+ server = null;
+ }
+ }
+
+ public static void main(String args[]) {
+ try {
+ OpenApiServer s = new OpenApiServer();
+ s.start();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ System.exit(-1);
+ } finally {
+ System.out.println("done!");
+ }
+ }
+
+}
--
To stop receiving notification emails like this one, please contact
['"[email protected]" <[email protected]>'].