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]>'].

Reply via email to