From 7bfe1e78aabe9218e132df30709c350c236fad8a Mon Sep 17 00:00:00 2001 From: Jorge Bescos Gascon Date: Tue, 17 Oct 2023 10:32:33 +0200 Subject: [PATCH] Defend agains 1+ deployments Signed-off-by: Jorge Bescos Gascon --- .../microprofile/server/EntityMatrixTest.java | 149 +++++++++++++++++ .../JaxRsApplicationMatrixEntityTest.java | 155 ++++++++++++++++++ .../HelidonContainerConfiguration.java | 11 ++ .../HelidonDeployableContainer.java | 122 +++++++------- .../src/test/resources/arquillian.xml | 1 + 5 files changed, 378 insertions(+), 60 deletions(-) create mode 100644 microprofile/server/src/test/java/io/helidon/microprofile/server/EntityMatrixTest.java create mode 100644 microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsApplicationMatrixEntityTest.java diff --git a/microprofile/server/src/test/java/io/helidon/microprofile/server/EntityMatrixTest.java b/microprofile/server/src/test/java/io/helidon/microprofile/server/EntityMatrixTest.java new file mode 100644 index 00000000000..cba8922ef2f --- /dev/null +++ b/microprofile/server/src/test/java/io/helidon/microprofile/server/EntityMatrixTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed 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 io.helidon.microprofile.server; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import io.helidon.microprofile.testing.junit5.AddBean; +import io.helidon.microprofile.testing.junit5.HelidonTest; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.ws.rs.BeanParam; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.MatrixParam; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.MediaType; +import org.junit.jupiter.api.Test; + +@HelidonTest +@AddBean(EntityMatrixTest.TestResource.class) +class EntityMatrixTest { + + private static final String FOO = "foo"; + private static final String BAR = "bar"; + + @Test + void defaultValueField(WebTarget target) { + String getResponse = target.path("/field").request().get(String.class); + assertThat(getResponse, is(equalTo(FOO))); + } + + @Test + void customValueField(WebTarget target) { + String getResponse = target.path("/field;matrix=" + BAR).request().get(String.class); + assertThat(getResponse, is(equalTo(BAR))); + } + + @Test + void defaultValueParam(WebTarget target) { + String getResponse = target.path("/param").request().get(String.class); + assertThat(getResponse, is(equalTo(FOO))); + } + + @Test + void customValueParam(WebTarget target) { + String getResponse = target.path("/param;matrix=" + BAR).request().get(String.class); + assertThat(getResponse, is(equalTo(BAR))); + } + + @Test + void entityMatrix(WebTarget target) { + String getResponse = target.path("/entitymatrix;param=" + BAR).request().get(String.class); + assertThat(getResponse, is(equalTo(BAR))); + } + + @Path("/") + @RequestScoped + public static class TestResource { + + @BeanParam + MatrixBeanParamEntity entity; + + @GET + @Path("field") + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public String field() { + return entity.field.value; + } + + @GET + @Path("param") + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public String param(@BeanParam MatrixBeanParamEntity entity) { + return entity.field.value; + } + + @GET + @Path("entitymatrix") + public String entitymatrix( + @MatrixParam("param") ParamEntityWithFromStringAndValueOf param) { + return param.getValue(); + } + } + + public static class MatrixBeanParamEntity { + + @DefaultValue(FOO) + @MatrixParam("matrix") + public FieldStr field; + + } + + public static class FieldStr { + + private final String value; + + public FieldStr(String value) { + this.value = value; + } + + } + + public static class ParamEntityWithFromStringAndValueOf { + + private String value; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public static ParamEntityWithFromStringAndValueOf valueOf(String arg) { + ParamEntityWithFromStringAndValueOf newEntity = new ParamEntityWithFromStringAndValueOf(); + newEntity.value = arg; + return newEntity; + } + + public static ParamEntityWithFromStringAndValueOf fromString(String arg) { + ParamEntityWithFromStringAndValueOf newEntity = new ParamEntityWithFromStringAndValueOf(); + newEntity.value = arg; + return newEntity; + } + } + +} diff --git a/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsApplicationMatrixEntityTest.java b/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsApplicationMatrixEntityTest.java new file mode 100644 index 00000000000..58a4f8cd680 --- /dev/null +++ b/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsApplicationMatrixEntityTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. + * + * Licensed 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 io.helidon.microprofile.server; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import java.util.HashSet; +import java.util.Set; + +import jakarta.ws.rs.ApplicationPath; +import jakarta.ws.rs.BeanParam; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.MatrixParam; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.client.Client; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Application; +import jakarta.ws.rs.core.MediaType; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class JaxRsApplicationMatrixEntityTest { + + private static final String FOO = "foo"; + private static final String BAR = "bar"; + private static Server server; + private static Client client; + private static int port; + + @BeforeAll + static void beforeAll() { + server = Server.builder().addApplication(MyApplication.class).build(); + server.start(); + port = server.port(); + client = ClientBuilder.newClient(); + } + + @AfterAll + static void afterAll() { + server.stop(); + client.close(); + } + + @Test + void defaultValueField() { + String getResponse = client.target("http://localhost:" + port) + .path("/field").request().get(String.class); + assertThat(getResponse, is(equalTo(FOO))); + } + + @Test + void customValueField() { + String getResponse = client.target("http://localhost:" + port) + .path("/field;matrix=" + BAR).request().get(String.class); + assertThat(getResponse, is(equalTo(BAR))); + } + + @Test + void customAndDefaultValueField() { + String getResponse = client.target("http://localhost:" + port) + .path("/field").request().get(String.class); + assertThat(getResponse, is(equalTo(FOO))); + getResponse = client.target("http://localhost:" + port) + .path("/field;matrix=" + BAR).request().get(String.class); + assertThat(getResponse, is(equalTo(BAR))); + } + + @Test + void defaultValueParam() { + String getResponse = client.target("http://localhost:" + port) + .path("/param").request().get(String.class); + assertThat(getResponse, is(equalTo(FOO))); + } + + @Test + void customValueParam() { + String getResponse = client.target("http://localhost:" + port) + .path("/param;matrix=" + BAR).request().get(String.class); + assertThat(getResponse, is(equalTo(BAR))); + } + + @ApplicationPath("/") + static class MyApplication extends Application { + + @Override + public java.util.Set> getClasses() { + Set> resources = new HashSet>(); + resources.add(TestResource.class); + return resources; + } + } + + @Path("/") + public static class TestResource { + + @BeanParam + MatrixBeanParamEntity entity; + + @GET + @Path("field") + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public String field() { + return entity.field.value; + } + + @GET + @Path("param") + @Produces(MediaType.TEXT_PLAIN) + @Consumes(MediaType.APPLICATION_FORM_URLENCODED) + public String param(@BeanParam MatrixBeanParamEntity entity) { + return entity.field.value; + } + + } + + public static class MatrixBeanParamEntity { + + @DefaultValue(FOO) + @MatrixParam("matrix") + public FieldStr field; + + } + + public static class FieldStr { + + private final String value; + + public FieldStr(String value) { + this.value = value; + } + + } +} diff --git a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerConfiguration.java b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerConfiguration.java index 321f09a9fc6..4e8ab201aa8 100644 --- a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerConfiguration.java +++ b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonContainerConfiguration.java @@ -41,6 +41,8 @@ *
  • useBeanXmlTemplate: (Optional) defaults to true: will create the default templates/beans.xml when beans.xml is missing
  • *
  • includeWarContextPath: (Optional) defaults to false: will include the war name as a root context. * For example, if a example.war is deployed, the root context is going to be /example.
  • + *
  • multipleDeployments: (Optional) defaults to true: workaround for tests that unintentionally + * executes 1+ times @org.jboss.arquillian.container.test.api.Deployment
  • * */ public class HelidonContainerConfiguration implements ContainerConfiguration { @@ -52,6 +54,7 @@ public class HelidonContainerConfiguration implements ContainerConfiguration { private boolean useParentClassloader = true; private boolean inWebContainer = false; private boolean useBeanXmlTemplate = true; + private boolean multipleDeployments = true; /* * Restful requires it, but core profile don't (because rest used to be deployed in a * web container together with other apps and in core profile there is only one app) @@ -140,6 +143,14 @@ public void setIncludeWarContextPath(boolean includeWarContextPath) { this.includeWarContextPath = includeWarContextPath; } + public boolean isMultipleDeployments() { + return multipleDeployments; + } + + public void setMultipleDeployments(boolean multipleDeployments) { + this.multipleDeployments = multipleDeployments; + } + @Override public void validate() throws ConfigurationException { if ((port <= 0) || (port > Short.MAX_VALUE)) { diff --git a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java index 9628ccaaa74..ee94075fecf 100644 --- a/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java +++ b/microprofile/tests/arquillian/src/main/java/io/helidon/microprofile/arquillian/HelidonDeployableContainer.java @@ -189,79 +189,81 @@ public ProtocolDescription getDefaultProtocol() { @Override public ProtocolMetaData deploy(Archive archive) throws DeploymentException { - // Because helidon doesn't have a dynamic war deployment model, we need to actually start the server here. - RunContext context = new RunContext(); - contexts.put(archive.getId(), context); + if (containerConfig.isMultipleDeployments() || (!containerConfig.isMultipleDeployments() && contexts.isEmpty())) { + // Because helidon doesn't have a dynamic war deployment model, we need to actually start the server here. + RunContext context = new RunContext(); + contexts.put(archive.getId(), context); - // Is it a JavaArchive? - boolean isJavaArchive = archive instanceof JavaArchive; + // Is it a JavaArchive? + boolean isJavaArchive = archive instanceof JavaArchive; - try { - // Create the temporary deployment directory. - if (containerConfig.getUseRelativePath()) { - context.deployDir = Paths.get("target/helidon-arquillian-test"); - } else { - context.deployDir = Files.createTempDirectory("helidon-arquillian-test"); - } - LOGGER.log(Level.INFO, "Running Arquillian tests in directory: " + context.deployDir.toAbsolutePath()); + try { + // Create the temporary deployment directory. + if (containerConfig.getUseRelativePath()) { + context.deployDir = Paths.get("target/helidon-arquillian-test"); + } else { + context.deployDir = Files.createTempDirectory("helidon-arquillian-test"); + } + LOGGER.log(Level.INFO, "Running Arquillian tests in directory: " + context.deployDir.toAbsolutePath()); - copyArchiveToDeployDir(archive, context.deployDir); + copyArchiveToDeployDir(archive, context.deployDir); - for (Archive additionalArchive : additionalArchives) { - copyArchiveToDeployDir(additionalArchive, context.deployDir); - } + for (Archive additionalArchive : additionalArchives) { + copyArchiveToDeployDir(additionalArchive, context.deployDir); + } - List classPath = new ArrayList<>(); + List classPath = new ArrayList<>(); - Path rootDir = context.deployDir.resolve(""); - if (isJavaArchive) { - ensureBeansXml(rootDir, null); - classPath.add(rootDir); - } else { - // Prepare the launcher files - Path webInfDir = context.deployDir.resolve("WEB-INF"); - Path classesDir = webInfDir.resolve("classes"); - Path libDir = webInfDir.resolve("lib"); - ensureBeansXml(classesDir, webInfDir); - addServerClasspath(classPath, classesDir, libDir, rootDir); - if (containerConfig.isInWebContainer()) { - if (containerConfig.isIncludeWarContextPath()) { - context.rootContext = archive.getName().split("\\.")[0]; - } - if (!loadApplicationFromWebXml(context, webInfDir)) { - // Search Application in classes - loadApplicationFromClasses(context, archive); + Path rootDir = context.deployDir.resolve(""); + if (isJavaArchive) { + ensureBeansXml(rootDir, null); + classPath.add(rootDir); + } else { + // Prepare the launcher files + Path webInfDir = context.deployDir.resolve("WEB-INF"); + Path classesDir = webInfDir.resolve("classes"); + Path libDir = webInfDir.resolve("lib"); + ensureBeansXml(classesDir, webInfDir); + addServerClasspath(classPath, classesDir, libDir, rootDir); + if (containerConfig.isInWebContainer()) { + if (containerConfig.isIncludeWarContextPath()) { + context.rootContext = archive.getName().split("\\.")[0]; + } + if (!loadApplicationFromWebXml(context, webInfDir)) { + // Search Application in classes + loadApplicationFromClasses(context, archive); + } } } - } - startServer(context, classPath.toArray(new Path[0])); - } catch (IOException | SAXException | ParserConfigurationException e) { - LOGGER.log(Level.INFO, "Failed to start container", e); - throw new DeploymentException("Failed to copy the archive assets into the deployment directory", e); - } catch (InvocationTargetException e) { + startServer(context, classPath.toArray(new Path[0])); + } catch (IOException | SAXException | ParserConfigurationException e) { + LOGGER.log(Level.INFO, "Failed to start container", e); + throw new DeploymentException("Failed to copy the archive assets into the deployment directory", e); + } catch (InvocationTargetException e) { - try { - context.runnerClass - .getDeclaredMethod("abortedCleanup") - .invoke(context.runner); - } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException ex) { - ex.printStackTrace(); - } + try { + context.runnerClass + .getDeclaredMethod("abortedCleanup") + .invoke(context.runner); + } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException ex) { + ex.printStackTrace(); + } - throw lookForSupressedDeploymentException(e.getTargetException()) - .map(d -> + throw lookForSupressedDeploymentException(e.getTargetException()) + .map(d -> new org.jboss.arquillian.container.spi.client.container.DeploymentException("Deployment failure!", d)) - .orElseThrow(() -> new DefinitionException(e)); - } catch (ReflectiveOperationException e) { - LOGGER.log(Level.INFO, "Failed to start container", e); - throw new DefinitionException(e); // validation exceptions - } + .orElseThrow(() -> new DefinitionException(e)); + } catch (ReflectiveOperationException e) { + LOGGER.log(Level.INFO, "Failed to start container", e); + throw new DefinitionException(e); // validation exceptions + } - // Server has started, so we're done. - // ProtocolMetaData pm = new ProtocolMetaData(); - // pm.addContext(new HTTPContext("Helidon", "localhost", containerConfig.getPort())); - // return pm; + // Server has started, so we're done. + // ProtocolMetaData pm = new ProtocolMetaData(); + // pm.addContext(new HTTPContext("Helidon", "localhost", containerConfig.getPort())); + // return pm; + } return new ProtocolMetaData(); } diff --git a/microprofile/tests/tck/tck-restful/tck-restful-test/src/test/resources/arquillian.xml b/microprofile/tests/tck/tck-restful/tck-restful-test/src/test/resources/arquillian.xml index a44feb9e4b3..7c1c85c1fba 100644 --- a/microprofile/tests/tck/tck-restful/tck-restful-test/src/test/resources/arquillian.xml +++ b/microprofile/tests/tck/tck-restful/tck-restful-test/src/test/resources/arquillian.xml @@ -31,6 +31,7 @@ false true true + false \ No newline at end of file