diff --git a/launcher-impl/microprofile/openapi/pom.xml b/launcher-impl/microprofile/openapi/pom.xml index 4a4e243..10dfbb3 100644 --- a/launcher-impl/microprofile/openapi/pom.xml +++ b/launcher-impl/microprofile/openapi/pom.xml @@ -1,6 +1,6 @@ + + org.junit.jupiter + junit-jupiter-api + 5.8.1 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.8.1 + test + + + org.junit.jupiter + junit-jupiter-params + 5.8.1 + test + + + org.mockito + mockito-core + 4.0.0 + test @@ -47,6 +76,11 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + diff --git a/launcher-impl/microprofile/openapi/src/main/java/com/fujitsu/launcher/microprofile/openapi/OpenApiService.java b/launcher-impl/microprofile/openapi/src/main/java/com/fujitsu/launcher/microprofile/openapi/OpenApiService.java index 59f4fdb..61b3fa5 100644 --- a/launcher-impl/microprofile/openapi/src/main/java/com/fujitsu/launcher/microprofile/openapi/OpenApiService.java +++ b/launcher-impl/microprofile/openapi/src/main/java/com/fujitsu/launcher/microprofile/openapi/OpenApiService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Fujitsu Limited and/or its affiliates. All rights reserved. + * Copyright (c) 2019-2021 Fujitsu Limited and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -17,6 +17,7 @@ import java.util.Collections; import java.util.List; import java.util.Set; +import java.util.regex.Pattern; import javax.inject.Inject; @@ -39,7 +40,7 @@ import io.smallrye.openapi.api.OpenApiDocument; import io.smallrye.openapi.runtime.OpenApiProcessor; import io.smallrye.openapi.runtime.OpenApiStaticFile; -import io.smallrye.openapi.runtime.io.OpenApiSerializer.Format; +import io.smallrye.openapi.runtime.io.Format; /** * * @author Katsuhiro Kunisada @@ -166,10 +167,10 @@ private boolean isJarToBeScanned(OpenApiConfig config, String entry) { } private boolean isClassToBeScanned(OpenApiConfig config, String entry) { - Set scanClasses = config.scanClasses(); - Set scanPackages = config.scanPackages(); - Set scanExcludeClasses = config.scanExcludeClasses(); - Set scanExcludePackages = config.scanExcludePackages(); + Pattern scanClasses = config.scanClasses(); + Pattern scanPackages = config.scanPackages(); + Pattern scanExcludeClasses = config.scanExcludeClasses(); + Pattern scanExcludePackages = config.scanExcludePackages(); if (entry == null) { return false; @@ -179,16 +180,16 @@ private boolean isClassToBeScanned(OpenApiConfig config, String entry) { String packageName = getPackageName(fqcn); boolean ret; - if (scanClasses.isEmpty() && scanPackages.isEmpty()) { + if (scanClasses.pattern().isEmpty() && scanPackages.pattern().isEmpty()) { ret = true; - } else if (!scanClasses.isEmpty() && scanPackages.isEmpty()) { - ret = scanClasses.contains(fqcn); - } else if (scanClasses.isEmpty() && !scanPackages.isEmpty()) { - ret = scanPackages.contains(packageName); + } else if (!scanClasses.pattern().isEmpty() && scanPackages.pattern().isEmpty()) { + ret = scanClasses.matcher(fqcn).matches(); + } else if (scanClasses.pattern().isEmpty() && !scanPackages.pattern().isEmpty()) { + ret = scanPackages.matcher(packageName).matches(); } else { - ret = scanClasses.contains(fqcn) || scanPackages.contains(packageName); + ret = scanClasses.matcher(fqcn).matches() || scanPackages.matcher(packageName).matches(); } - if (scanExcludeClasses.contains(fqcn) || scanExcludePackages.contains(packageName)) { + if (scanExcludeClasses.matcher(fqcn).matches() || scanExcludePackages.matcher(packageName).matches()) { ret = false; } return ret; diff --git a/launcher-impl/microprofile/openapi/src/main/java/com/fujitsu/launcher/microprofile/openapi/OpenApiServlet.java b/launcher-impl/microprofile/openapi/src/main/java/com/fujitsu/launcher/microprofile/openapi/OpenApiServlet.java index fafa20c..7d24f99 100644 --- a/launcher-impl/microprofile/openapi/src/main/java/com/fujitsu/launcher/microprofile/openapi/OpenApiServlet.java +++ b/launcher-impl/microprofile/openapi/src/main/java/com/fujitsu/launcher/microprofile/openapi/OpenApiServlet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Fujitsu Limited and/or its affiliates. All rights reserved. + * Copyright (c) 2019-2021 Fujitsu Limited and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -10,6 +10,9 @@ package com.fujitsu.launcher.microprofile.openapi; import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; @@ -17,45 +20,81 @@ import javax.servlet.http.HttpServletResponse; import io.smallrye.openapi.api.OpenApiDocument; +import io.smallrye.openapi.runtime.io.Format; import io.smallrye.openapi.runtime.io.OpenApiSerializer; -import io.smallrye.openapi.runtime.io.OpenApiSerializer.Format; + +import org.glassfish.jersey.message.internal.AcceptableMediaType; +import org.glassfish.jersey.message.internal.HttpHeaderReader; + +import java.text.ParseException; + +import javax.ws.rs.core.MediaType; /** * * @author Takahiro Nagao * @author Katsuhiro Kunisada + * @author Koki Kosaka */ @SuppressWarnings("serial") @WebServlet public class OpenApiServlet extends HttpServlet { - public static final String MEDIA_TYPE_YAML = "text/plain"; - public static final String MEDIA_TYPE_JSON = "application/json"; + public static final Map ACCEPTED_TYPES = new HashMap<>(); + + static { + ACCEPTED_TYPES.put(Format.YAML, MediaType.TEXT_PLAIN); + ACCEPTED_TYPES.put(Format.JSON, MediaType.APPLICATION_JSON); + } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { - Format format = Format.YAML; - String type = MEDIA_TYPE_YAML; + Format format = getResponseFormat(request); + if (format != null) { + String oai = OpenApiSerializer.serialize(OpenApiDocument.INSTANCE.get(), format); + response.setContentType(ACCEPTED_TYPES.get(format)); + response.getWriter().write(oai); + response.setStatus(HttpServletResponse.SC_OK); + } else { + response.sendError(HttpServletResponse.SC_NOT_ACCEPTABLE); + } + } - if (isJson(request)) { - format = Format.JSON; - type = MEDIA_TYPE_JSON; + protected Format getResponseFormat(HttpServletRequest request) { + try { + Format format = parseFormatQueryParameter(request); + if (format != null) return format; + return parseAcceptHeader(request); + } catch (ParseException e) { + throw new RuntimeException(e); } + } - String oai = OpenApiSerializer.serialize(OpenApiDocument.INSTANCE.get(), format); - response.getWriter().write(oai); - response.setContentType(type); - response.setStatus(200); + private Format parseFormatQueryParameter(HttpServletRequest request) { + String formatParam = request.getParameter("format"); + if (formatParam != null) { + for (Format f : Format.values()) { + if (f.name().equalsIgnoreCase(formatParam)) return f; + } + } + return null; } - private boolean isJson(HttpServletRequest request) { - String accept = request.getHeader("Accept"); - if (accept != null && accept.equals(MEDIA_TYPE_JSON)) { - return true; + private Format parseAcceptHeader(HttpServletRequest request) throws ParseException { + String acceptHeader = request.getHeader("Accept"); + // sorted by quality value + List mediaTypes = HttpHeaderReader.readAcceptMediaType(acceptHeader); + if (mediaTypes.isEmpty()) { + mediaTypes.add(AcceptableMediaType.valueOf(MediaType.WILDCARD_TYPE)); } - String formatParam = request.getParameter("format"); - if (formatParam != null && formatParam.equals("json")) { - return true; + + for (AcceptableMediaType mediaType : mediaTypes) { + if (mediaType.isCompatible(MediaType.TEXT_PLAIN_TYPE)) { + return Format.YAML; + } + if (mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE)) { + return Format.JSON; + } } - return false; + return null; } } diff --git a/launcher-impl/microprofile/openapi/src/test/java/com/fujitsu/launcher/microprofile/openapi/OpenApiServletTest.java b/launcher-impl/microprofile/openapi/src/test/java/com/fujitsu/launcher/microprofile/openapi/OpenApiServletTest.java new file mode 100644 index 0000000..d7e73dc --- /dev/null +++ b/launcher-impl/microprofile/openapi/src/test/java/com/fujitsu/launcher/microprofile/openapi/OpenApiServletTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2021 Fujitsu Limited and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + */ + +package com.fujitsu.launcher.microprofile.openapi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.util.stream.Stream; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import io.smallrye.openapi.runtime.io.Format; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class OpenApiServletTest { + + HttpServletRequest mockRequest = mock(HttpServletRequest.class); + HttpServletResponse mockResponse = mock(HttpServletResponse.class); + + OpenApiServlet servlet = spy(new OpenApiServlet()); + + @ParameterizedTest + @MethodSource({"provideOnlyAcceptHeader", "provideOnlyFormatQuery", "provideFormatQueryAndAcceptHeader"}) + public void testGetResponseFormat(String format, String acceptHeader, Format expected) { + when(mockRequest.getParameter("format")).thenReturn(format); + when(mockRequest.getHeader("Accept")).thenReturn(acceptHeader); + Format result = servlet.getResponseFormat(mockRequest); + assertEquals(result, expected); + } + + private static Stream provideOnlyAcceptHeader() { + return Stream.of( + // one value + Arguments.of(null, "", Format.YAML), + Arguments.of(null, "text/plain", Format.YAML), + Arguments.of(null, "application/json", Format.JSON), + Arguments.of(null, "*/*", Format.YAML), + Arguments.of(null, "text/*", Format.YAML), + Arguments.of(null, "application/*", Format.JSON), + Arguments.of(null, "hoge/hoge", null), + + // two value + Arguments.of(null, "text/plain, application/json", Format.YAML), + Arguments.of(null, "application/json, text/plain", Format.JSON), + Arguments.of(null, "text/plain;q=0.9, application/json", Format.JSON), + Arguments.of(null, "application/json;q=0.9, text/plain", Format.YAML), + + Arguments.of(null, "*/*, application/json", Format.JSON), + Arguments.of(null, "text/*, application/json", Format.JSON), + Arguments.of(null, "application/*, text/plain", Format.YAML), + Arguments.of(null, "text/*, application/json;q=0.9", Format.YAML), + Arguments.of(null, "application/*, text/plain;q=0.9", Format.JSON), + + // three value + Arguments.of(null, "*/*, application/*, text/plain", Format.YAML), + Arguments.of(null, "*/*, application/*, text/plain;q=0.9", Format.JSON), + Arguments.of(null, "*/*, application/*;q=0.9, text/plain;q=0.8", Format.YAML) + ); + } + + private static Stream provideOnlyFormatQuery() { + return Stream.of( + // one value + Arguments.of("YAML", null, Format.YAML), + Arguments.of("JSON", null, Format.JSON), + Arguments.of("yaml", null, Format.YAML), + Arguments.of("json", null, Format.JSON), + Arguments.of("hoge", null, Format.YAML), + Arguments.of(null, null, Format.YAML) + ); + } + + private static Stream provideFormatQueryAndAcceptHeader() { + return Stream.of( + Arguments.of("YAML", "application/json", Format.YAML), + Arguments.of("JSON", "text/plain", Format.JSON), + Arguments.of("hoge", "application/json", Format.JSON) + ); + } + + @Test + public void testSendError406() throws IOException { + doReturn(null).when(servlet).getResponseFormat(mockRequest); + servlet.doGet(mockRequest, mockResponse); + verify(mockResponse, times(1)).sendError(406); + } + + @Test + public void testThrowRuntimeException() { + when(mockRequest.getParameter("format")).thenReturn(null); + when(mockRequest.getHeader("Accept")).thenReturn("application/json:q=0.4"); //colon + assertThrows(RuntimeException.class, () -> servlet.getResponseFormat(mockRequest)); + } + +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 17bf88d..6526fbe 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ 3.1 1.2.2 3.0 - 1.1.2 + 2.0 1.3.1 1.3.4 5.1.0 @@ -44,7 +44,7 @@ 3.1.1 3.3.0 3.0.3 - 1.0.2 + 2.1.15 1.3.2 2.33 2.9.4 @@ -285,7 +285,12 @@ io.smallrye - smallrye-open-api-1.1 + smallrye-open-api-core + ${smallrye.openapi.version} + + + io.smallrye + smallrye-open-api-jaxrs ${smallrye.openapi.version}