You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aries.apache.org by cs...@apache.org on 2021/01/26 21:52:10 UTC

[aries-jax-rs-whiteboard] branch master updated: [ARIES-2032] ModelConverters global registry

This is an automated email from the ASF dual-hosted git repository.

csierra pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/aries-jax-rs-whiteboard.git


The following commit(s) were added to refs/heads/master by this push:
     new d219a96  [ARIES-2032] ModelConverters global registry
d219a96 is described below

commit d219a9670cba99c217c309328d02ac0fd203da76
Author: Carlos Sierra Andrés <ca...@liferay.com>
AuthorDate: Tue Jan 26 12:06:14 2021 +0100

    [ARIES-2032] ModelConverters global registry
    
    ModelConverters per OpenAPI instance it work because Swagger
    registers the ModelConverters globally, not per OpenAPI context, so
    they are used internally regardless the context that registered
    them. Therefore we are just adding every registered model converter to
    the global registry.
---
 .../src/main/java/test/OpenApiTest.java            |  88 ++++++++++++++-
 .../main/java/test/types/TestOpenApiResource.java  |   7 +-
 .../jax/rs/openapi/OpenAPIWithModelResolvers.java  |  45 ++++++++
 .../jax/rs/openapi/OpenApiBundleActivator.java     | 119 ++++++++++++++++++---
 .../rs/openapi/OpenApiPrototypeServiceFactory.java |   8 +-
 .../aries/jax/rs/openapi/OpenApiResource.java      |  39 ++++---
 6 files changed, 270 insertions(+), 36 deletions(-)

diff --git a/integrations/openapi/openapi-itest/src/main/java/test/OpenApiTest.java b/integrations/openapi/openapi-itest/src/main/java/test/OpenApiTest.java
index 3c4f4d3..77dab3e 100644
--- a/integrations/openapi/openapi-itest/src/main/java/test/OpenApiTest.java
+++ b/integrations/openapi/openapi-itest/src/main/java/test/OpenApiTest.java
@@ -20,9 +20,14 @@ package test;
 import static org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants.JAX_RS_APPLICATION_BASE;
 import static org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants.JAX_RS_APPLICATION_SELECT;
 
+import io.swagger.v3.core.converter.ModelConverter;
+import io.swagger.v3.core.jackson.ModelResolver;
+import io.swagger.v3.core.jackson.TypeNameResolver;
+import io.swagger.v3.core.util.Json;
 import io.swagger.v3.oas.models.info.Contact;
 import io.swagger.v3.oas.models.info.Info;
 import io.swagger.v3.oas.models.OpenAPI;
+import org.junit.Ignore;
 import org.junit.Test;
 
 import org.osgi.framework.ServiceRegistration;
@@ -34,6 +39,7 @@ import test.types.TestOpenApiResource;
 import javax.ws.rs.client.WebTarget;
 
 import java.util.Hashtable;
+import java.util.Set;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -66,9 +72,84 @@ public class OpenApiTest extends TestHelper {
             String response = webTarget.request().get(String.class);
 
             assertTrue(response.contains("operation"));
+        } finally {
+            serviceRegistration.unregister();
         }
-        finally {
+    }
+
+    @Test
+    public void testOpenApiEndpointWithModelResolver() {
+        OpenAPI openAPI = new OpenAPI();
+
+        openAPI.info(
+            new Info()
+                .title("My Service")
+                .description("Service REST API")
+                .contact(
+                    new Contact()
+                        .email("oschweitzer@me.com"))
+        );
+
+        Hashtable<String, Object> properties = new Hashtable<>();
+
+        properties.put("name", "test");
+
+        ServiceRegistration<OpenAPI> serviceRegistration =
+            bundleContext.registerService(
+                OpenAPI.class, openAPI, properties);
+
+        try {
+            WebTarget webTarget = createDefaultTarget().
+                path("openapi.json");
+
+            registerAddon(new TestOpenApiResource());
+
+            String response = webTarget.request().get(String.class);
+
+            assertFalse(response.contains("MyOwnClassName"));
+        } catch (Exception e) {
             serviceRegistration.unregister();
+
+            throw e;
+        }
+
+        ServiceRegistration<ModelConverter> serviceRegistration2 =
+            bundleContext.registerService(
+                ModelConverter.class, new ModelResolver(Json.mapper(), new TypeNameResolver() {
+                    @Override
+                    protected String nameForClass(Class<?> cls, Set<Options> options) {
+                        return "MyOwnClassName";
+                    }
+                }), new Hashtable<>());
+
+        try {
+            WebTarget webTarget = createDefaultTarget().
+                path("openapi.json");
+
+            registerAddon(new TestOpenApiResource());
+
+            String response = webTarget.request().get(String.class);
+
+            assertTrue(response.contains("MyOwnClassName"));
+        } catch (Exception e) {
+            serviceRegistration.unregister();
+        } finally {
+            serviceRegistration2.unregister();
+        }
+
+        try {
+            WebTarget webTarget = createDefaultTarget().
+                path("openapi.json");
+
+            registerAddon(new TestOpenApiResource());
+
+            String response = webTarget.request().get(String.class);
+
+            assertFalse(response.contains("MyOwnClassName"));
+        } catch (Exception e) {
+            serviceRegistration.unregister();
+
+            throw e;
         }
     }
 
@@ -92,7 +173,7 @@ public class OpenApiTest extends TestHelper {
                         .email("oschweitzer@me.com"))
         );
 
-        @SuppressWarnings({ "unchecked", "rawtypes", "serial" })
+        @SuppressWarnings({"unchecked", "rawtypes", "serial"})
         ServiceRegistration<OpenAPI> serviceRegistration =
             bundleContext.registerService(
                 OpenAPI.class, openAPI, new Hashtable() {{
@@ -123,8 +204,7 @@ public class OpenApiTest extends TestHelper {
             response = webTarget.request().get(String.class);
 
             assertFalse(response.contains("\"/operation\":"));
-        }
-        finally {
+        } finally {
             serviceRegistration.unregister();
         }
     }
diff --git a/integrations/openapi/openapi-itest/src/main/java/test/types/TestOpenApiResource.java b/integrations/openapi/openapi-itest/src/main/java/test/types/TestOpenApiResource.java
index 6c23e86..9d0550d 100644
--- a/integrations/openapi/openapi-itest/src/main/java/test/types/TestOpenApiResource.java
+++ b/integrations/openapi/openapi-itest/src/main/java/test/types/TestOpenApiResource.java
@@ -30,8 +30,11 @@ public class TestOpenApiResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Path("/operation")
     @Operation(description = "operation")
-    public String getJsonObject() {
-        return "operation";
+    public MyReturnType getJsonObject() {
+        return null;
     }
 
+    public static class MyReturnType {
+
+    }
 }
diff --git a/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenAPIWithModelResolvers.java b/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenAPIWithModelResolvers.java
new file mode 100644
index 0000000..63f5b89
--- /dev/null
+++ b/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenAPIWithModelResolvers.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
+ * <p>
+ * The contents of this file are subject to the terms of the Liferay Enterprise
+ * Subscription License ("License"). You may not use this file except in
+ * compliance with the License. You can obtain a copy of the License by
+ * contacting Liferay, Inc. See the License for the specific language governing
+ * permissions and limitations under the License, including but not limited to
+ * distribution rights of the Software.
+ */
+
+package org.apache.aries.jax.rs.openapi;
+
+import io.swagger.v3.core.converter.ModelConverter;
+import io.swagger.v3.core.jackson.ModelResolver;
+import io.swagger.v3.oas.models.OpenAPI;
+import org.apache.aries.component.dsl.CachingServiceReference;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Carlos Sierra Andrés
+ */
+class OpenAPIWithModelResolvers {
+    private final CachingServiceReference<OpenAPI> openAPI;
+    private final Set<CachingServiceReference<ModelConverter>> modelConverters;
+
+    OpenAPIWithModelResolvers(
+        CachingServiceReference<OpenAPI> openAPI,
+        Set<CachingServiceReference<ModelConverter>> modelConverters) {
+
+        this.openAPI = openAPI;
+        this.modelConverters = modelConverters;
+    }
+
+    public Set<CachingServiceReference<ModelConverter>> getModelConverters() {
+        return modelConverters;
+    }
+
+    public CachingServiceReference<OpenAPI> getOpenAPIServiceReference() {
+        return openAPI;
+    }
+}
diff --git a/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenApiBundleActivator.java b/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenApiBundleActivator.java
index 56f1866..df46287 100644
--- a/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenApiBundleActivator.java
+++ b/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenApiBundleActivator.java
@@ -17,6 +17,8 @@
 
 package org.apache.aries.jax.rs.openapi;
 
+import io.swagger.v3.core.converter.ModelConverter;
+import io.swagger.v3.core.converter.ModelConverters;
 import io.swagger.v3.oas.models.OpenAPI;
 import org.apache.aries.component.dsl.CachingServiceReference;
 import org.apache.aries.component.dsl.OSGi;
@@ -25,8 +27,10 @@ import org.osgi.annotation.bundle.Header;
 import org.osgi.framework.*;
 
 import java.util.*;
+import java.util.stream.Collectors;
 
 import static org.apache.aries.component.dsl.OSGi.*;
+import static org.apache.aries.component.dsl.Utils.accumulate;
 
 /**
  * @author Carlos Sierra Andrés
@@ -36,24 +40,115 @@ public class OpenApiBundleActivator implements BundleActivator {
 
     private OSGiResult result;
 
+    private static <T> OSGi<Set<T>> sequence(Set<OSGi<T>> list) {
+        Set<T> objects = new HashSet<>();
+
+        OSGi<Set<T>> result = just(() -> objects);
+
+        for (OSGi<T> osgi : list) {
+            result = osgi.effects(objects::add, objects::remove).then(result);
+        }
+
+        return result;
+    }
+
+    private static OpenAPIWithModelResolvers pairModelConverterWithOpenAPI(
+        CachingServiceReference<OpenAPI> oasr, List<CachingServiceReference<ModelConverter>> mcsrs) {
+
+        return new OpenAPIWithModelResolvers(
+            oasr,
+            mcsrs.stream().filter(
+                mcsr -> filterModelConverter(oasr, mcsr)
+            ).collect(
+                Collectors.toSet()
+            )
+        );
+    }
+
+    private static boolean filterModelConverter(
+        CachingServiceReference<OpenAPI> oasr,
+        CachingServiceReference<ModelConverter> mcsr) {
+
+        final String[] propertyValues = canonicalize(
+            mcsr.getProperty("osgi.jaxrs.openapi.select"));
+
+        for (String propertyValue : propertyValues) {
+            if (propertyValue == null || propertyValue.isEmpty()) {
+                continue;
+            }
+            try {
+                Filter filter = FrameworkUtil.createFilter(propertyValue);
+
+                if (filter.match(oasr.getServiceReference())) {
+                    return true;
+                }
+            } catch (InvalidSyntaxException ise) {
+                //log
+                continue;
+            }
+        }
+
+        return false;
+    }
+
+    private static String[] canonicalize(Object propertyValue) {
+        if (propertyValue == null) {
+            return new String[0];
+        }
+        if (propertyValue instanceof String[]) {
+            return (String[]) propertyValue;
+        }
+        if (propertyValue instanceof Collection) {
+            return
+                ((Collection<?>) propertyValue).stream().
+                    map(
+                        Object::toString
+                    ).toArray(
+                    String[]::new
+                );
+        }
+
+        return new String[]{propertyValue.toString()};
+    }
+
     @Override
     public void start(BundleContext bundleContext) throws Exception {
-        OSGi<?> program =
-            serviceReferences(OpenAPI.class).flatMap(sr ->
-            service(sr).flatMap(openAPI ->
-            just(
-                new OpenApiPrototypeServiceFactory(
-                    new PropertyWrapper(sr),
-                    openAPI))).flatMap(factory ->
-            register(
-                Object.class,
-                factory,
-                () -> getProperties(sr))
-            ));
+        final OSGi<?> modelConverters = services(
+            ModelConverter.class
+        ).effects(
+            ModelConverters.getInstance()::addConverter,
+            ModelConverters.getInstance()::removeConverter
+        );
+
+        OSGi<?> program = combine(
+            (openAPI, __) -> openAPI,
+            serviceReferences(OpenAPI.class),
+            accumulate(modelConverters)
+        ).
+            flatMap(openAPICachingServiceReference ->
+                service(openAPICachingServiceReference).flatMap(openAPI ->
+                    register(
+                        Object.class,
+                        new OpenApiPrototypeServiceFactory(
+                            new PropertyWrapper(openAPICachingServiceReference), openAPI),
+                        () -> getProperties(openAPICachingServiceReference)
+                    )
+                )
+            );
 
         result = program.run(bundleContext);
     }
 
+    private Set<OSGi<ModelConverter>> lazilyGetModelConverters(
+        OpenAPIWithModelResolvers openAPIWithModelConverters) {
+
+        return openAPIWithModelConverters.
+            getModelConverters().
+            stream().
+            map(OSGi::service).
+            collect(Collectors.toSet());
+    }
+
     @Override
     public void stop(BundleContext bundleContext) throws Exception {
         result.close();
diff --git a/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenApiPrototypeServiceFactory.java b/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenApiPrototypeServiceFactory.java
index 1083d26..67e70fe 100644
--- a/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenApiPrototypeServiceFactory.java
+++ b/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenApiPrototypeServiceFactory.java
@@ -45,9 +45,8 @@ public class OpenApiPrototypeServiceFactory
         Bundle bundle,
         ServiceRegistration<Object> serviceRegistration) {
 
-        SwaggerConfiguration
-                swaggerConfiguration = new SwaggerConfiguration().
-                openAPI(openAPI);
+        SwaggerConfiguration swaggerConfiguration = new SwaggerConfiguration().
+            openAPI(openAPI);
 
         propertyWrapper.applyLong("cache.ttl", swaggerConfiguration::setCacheTTL);
         propertyWrapper.applyString("id", swaggerConfiguration::id);
@@ -55,7 +54,8 @@ public class OpenApiPrototypeServiceFactory
         propertyWrapper.applyBoolean("pretty.print", swaggerConfiguration::setPrettyPrint);
         propertyWrapper.applyBoolean("read.all.resources", swaggerConfiguration::setReadAllResources);
 
-        OpenApiResource openApiResource = new OpenApiResource();
+        OpenApiResource openApiResource = new OpenApiResource(
+            (long) serviceRegistration.getReference().getProperty("service.id"));
 
         openApiResource.setOpenApiConfiguration(swaggerConfiguration);
 
diff --git a/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenApiResource.java b/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenApiResource.java
index 662cf5f..a4cc6c3 100644
--- a/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenApiResource.java
+++ b/integrations/openapi/openapi-resource/src/main/java/org/apache/aries/jax/rs/openapi/OpenApiResource.java
@@ -7,12 +7,8 @@ import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
-import javax.ws.rs.core.Application;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.core.*;
+import javax.ws.rs.ext.Providers;
 
 import org.apache.aries.jax.rs.whiteboard.ApplicationClasses;
 
@@ -36,6 +32,17 @@ public class OpenApiResource extends BaseOpenApiResource {
 
     @Context
     ServletConfig config;
+    private long serviceId;
+
+    @Context
+    Providers providers;
+
+    @Context
+    Configuration configuration;
+
+    public OpenApiResource(long serviceId) {
+        this.serviceId = serviceId;
+    }
 
     @GET
     @Produces({MediaType.APPLICATION_JSON, "application/yaml"})
@@ -45,7 +52,9 @@ public class OpenApiResource extends BaseOpenApiResource {
                                @PathParam("type") String type) throws Exception {
 
         String ctxId = app.getClass().getCanonicalName()
-            .concat("#").concat(String.valueOf(System.identityHashCode(app)));
+            .concat("#").
+                concat(String.valueOf(System.identityHashCode(app))).
+                concat(String.valueOf(this.serviceId));
 
         OpenApiContext ctx = new JaxrsOpenApiContextBuilder<>()
             .servletConfig(config)
@@ -53,10 +62,12 @@ public class OpenApiResource extends BaseOpenApiResource {
             .configLocation(configLocation)
             .openApiConfiguration(openApiConfiguration)
             .ctxId(ctxId)
-            .buildContext(true);
+            .buildContext(false);
 
         ctx.setOpenApiScanner(new JaxrsWhiteboardScanner(applicationClasses));
 
+        ctx.init();
+
         OpenAPI oas = ctx.read();
 
         if (oas == null) {
@@ -67,14 +78,14 @@ public class OpenApiResource extends BaseOpenApiResource {
 
         if (Optional.ofNullable(type).map(String::trim).map("yaml"::equalsIgnoreCase).orElse(Boolean.FALSE)) {
             return Response.status(Response.Status.OK)
-                  .entity(pretty ? Yaml.pretty(oas) : Yaml.mapper().writeValueAsString(oas))
-                  .type("application/yaml")
-                  .build();
+                .entity(pretty ? Yaml.pretty(oas) : Yaml.mapper().writeValueAsString(oas))
+                .type("application/yaml")
+                .build();
         } else {
             return Response.status(Response.Status.OK)
-                  .entity(pretty ? Json.pretty(oas) : Json.mapper().writeValueAsString(oas))
-                  .type(MediaType.APPLICATION_JSON_TYPE)
-                  .build();
+                .entity(pretty ? Json.pretty(oas) : Json.mapper().writeValueAsString(oas))
+                .type(MediaType.APPLICATION_JSON_TYPE)
+                .build();
         }
     }