From ff60d0d399aec35d8bdfbadd52e105ed008efccf Mon Sep 17 00:00:00 2001 From: Jonathan Gallimore Date: Wed, 22 May 2024 10:25:23 +0100 Subject: [PATCH] TOMEE-4332 Load SpringWebUtils in the context class loader in TomEE when the cxf-rs service is initialized --- .../arquillian-tomee-jaxrs-tests/pom.xml | 22 +++ .../jaxrs/spring/AlternativeGreeter.java | 22 +++ .../tests/jaxrs/spring/DemoApplication.java | 13 ++ .../jaxrs/spring/GreetingController.java | 19 +++ .../jaxrs/spring/ServletInitializer.java | 13 ++ .../tests/jaxrs/spring/SpringWebappTest.java | 158 ++++++++++++++++++ .../src/test/resources/spring-webmvc-pom.xml | 44 +++++ arquillian/arquillian-tomee-tests/pom.xml | 1 + .../openejb/server/cxf/rs/CxfRSService.java | 6 + 9 files changed, 298 insertions(+) create mode 100644 arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/AlternativeGreeter.java create mode 100644 arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/DemoApplication.java create mode 100644 arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/GreetingController.java create mode 100644 arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/ServletInitializer.java create mode 100644 arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/SpringWebappTest.java create mode 100644 arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/resources/spring-webmvc-pom.xml diff --git a/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/pom.xml b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/pom.xml index 61b44787557..271df97969c 100644 --- a/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/pom.xml +++ b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/pom.xml @@ -40,6 +40,28 @@ ${project.version} war + + org.springframework.boot + spring-boot-loader-tools + 3.1.11 + + + org.springframework.boot + spring-boot-starter-web + 3.1.11 + + + org.springframework.boot + spring-boot-starter-tomcat + 3.1.11 + provided + + + org.springframework.boot + spring-boot-starter-test + 3.1.11 + test + diff --git a/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/AlternativeGreeter.java b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/AlternativeGreeter.java new file mode 100644 index 00000000000..3eadadc3688 --- /dev/null +++ b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/AlternativeGreeter.java @@ -0,0 +1,22 @@ +package org.apache.openejb.arquillian.tests.jaxrs.spring; + +import jakarta.ejb.Lock; +import jakarta.ejb.LockType; +import jakarta.ejb.Singleton; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; + +@Singleton +@Lock(LockType.READ) +public class AlternativeGreeter { + + @Path("hello") + @GET + @Produces(MediaType.TEXT_PLAIN) + public String sayHello() { + return "Hello World!"; + } + +} diff --git a/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/DemoApplication.java b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/DemoApplication.java new file mode 100644 index 00000000000..19ccb63f501 --- /dev/null +++ b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/DemoApplication.java @@ -0,0 +1,13 @@ +package org.apache.openejb.arquillian.tests.jaxrs.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } + +} diff --git a/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/GreetingController.java b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/GreetingController.java new file mode 100644 index 00000000000..bada7ed118d --- /dev/null +++ b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/GreetingController.java @@ -0,0 +1,19 @@ +package org.apache.openejb.arquillian.tests.jaxrs.spring; + +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@RequestMapping("/greet") +public class GreetingController { + + @GetMapping(produces = MediaType.TEXT_PLAIN_VALUE) + @ResponseBody + public String sayHello() { + return "Hello World!"; + } + +} \ No newline at end of file diff --git a/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/ServletInitializer.java b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/ServletInitializer.java new file mode 100644 index 00000000000..f692b607af9 --- /dev/null +++ b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/ServletInitializer.java @@ -0,0 +1,13 @@ +package org.apache.openejb.arquillian.tests.jaxrs.spring; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +public class ServletInitializer extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + return application.sources(DemoApplication.class); + } + +} diff --git a/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/SpringWebappTest.java b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/SpringWebappTest.java new file mode 100644 index 00000000000..9599c001d26 --- /dev/null +++ b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/java/org/apache/openejb/arquillian/tests/jaxrs/spring/SpringWebappTest.java @@ -0,0 +1,158 @@ +/** + * 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.openejb.arquillian.tests.jaxrs.spring; + +import org.apache.ziplock.WebModule; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.exporter.ZipExporter; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.shrinkwrap.resolver.api.ResolutionException; +import org.jboss.shrinkwrap.resolver.api.ResolvedArtifact; +import org.jboss.shrinkwrap.resolver.api.maven.Maven; +import org.jboss.shrinkwrap.resolver.api.maven.MavenResolvedArtifact; +import org.jboss.shrinkwrap.resolver.api.maven.ScopeType; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.loader.tools.Libraries; +import org.springframework.boot.loader.tools.Library; +import org.springframework.boot.loader.tools.LibraryCallback; +import org.springframework.boot.loader.tools.LibraryScope; +import org.springframework.boot.loader.tools.Repackager; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Arrays; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@RunWith(Arquillian.class) +public class SpringWebappTest { + + @ArquillianResource + private URL url; + + @Deployment(testable = false) + public static WebArchive getArchive() throws Exception { + MavenResolvedArtifact[] dependencies; + try { // try offline first since it is generally faster + dependencies = Maven.configureResolver() + .workOffline() + .loadPomFromFile("src/test/resources/spring-webmvc-pom.xml") + .importCompileAndRuntimeDependencies().resolve().withTransitivity() + .asResolvedArtifact(); + } catch (ResolutionException re) { // try on central + dependencies = Maven.resolver() + .loadPomFromFile("src/test/resources/spring-webmvc-pom.xml") + .importCompileAndRuntimeDependencies().resolve().withTransitivity() + .asResolvedArtifact(); + } + + + final WebArchive archive = new WebModule(SpringWebappTest.class.getSimpleName()).getArchive(); + archive.addClasses(AlternativeGreeter.class, DemoApplication.class, GreetingController.class, ServletInitializer.class); + archive.addAsLibraries(toFiles(dependencies)); + + // repackage as a Spring Boot WAR file + final File originalWarFile = File.createTempFile("test", ".war"); + archive.as(ZipExporter.class).exportTo(originalWarFile, true); + + final Repackager repackager = new Repackager(originalWarFile); + final File repackagedWarFile = File.createTempFile("repackaged", ".war"); + + repackager.repackage(repackagedWarFile, new MavenResolvedArtifactLibraries(dependencies)); + + final WebArchive repackaged = ShrinkWrap.createFromZipFile(WebArchive.class, repackagedWarFile); + + System.out.println(repackaged.toString(true)); + return repackaged; + } + + private static File[] toFiles(MavenResolvedArtifact[] dependencies) { + return Arrays.stream(dependencies) + .map(ResolvedArtifact::asFile) + .collect(Collectors.toList()) + .toArray(new File[dependencies.length]); + } + + @Test + public void validate() throws Exception { + final String launchProfile = System.getProperty("arquillian.launch"); + if ("tomee-embedded".equals(launchProfile)) { + System.out.println("Skipping this test in TomEE embedded"); + return; + } + + final InputStream is = new URL(url.toExternalForm() + "hello").openStream(); + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + + int bytesRead; + byte[] buffer = new byte[8192]; + while ((bytesRead = is.read(buffer)) > -1) { + os.write(buffer, 0, bytesRead); + } + + is.close(); + os.close(); + + final String output = new String(os.toByteArray(), "UTF-8"); + assertNotNull("Response shouldn't be null", output); + assertTrue("Output should contain: " + "Hello World!" + "\n" + output, output.contains("Hello World!")); + } + + public static class MavenResolvedArtifactLibraries implements Libraries { + + private final MavenResolvedArtifact[] artifacts; + + public MavenResolvedArtifactLibraries(final MavenResolvedArtifact[] artifacts) { + this.artifacts = artifacts; + } + + @Override + public void doWithLibraries(final LibraryCallback callback) throws IOException { + for (final MavenResolvedArtifact artifact : artifacts) { + callback.library(new Library(artifact.asFile(), toScope(artifact))); + } + } + + private LibraryScope toScope(MavenResolvedArtifact artifact) { + final ScopeType scope = artifact.getScope(); + if (ScopeType.COMPILE.equals(scope)) { + return LibraryScope.COMPILE; + } else if (ScopeType.RUNTIME.equals(scope)) { + return LibraryScope.COMPILE; + } else if (ScopeType.TEST.equals(scope)) { + return LibraryScope.PROVIDED; + } else if (ScopeType.PROVIDED.equals(scope)) { + return LibraryScope.PROVIDED; + } else if (ScopeType.SYSTEM.equals(scope)) { + return LibraryScope.CUSTOM; + } else if (ScopeType.IMPORT.equals(scope)) { + return LibraryScope.CUSTOM; + } + + throw new IllegalStateException("Unsupported scope: " + scope); + } + } +} diff --git a/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/resources/spring-webmvc-pom.xml b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/resources/spring-webmvc-pom.xml new file mode 100644 index 00000000000..969d8a3bacb --- /dev/null +++ b/arquillian/arquillian-tomee-tests/arquillian-tomee-jaxrs-tests/src/test/resources/spring-webmvc-pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.1.11 + + + org.apache.openejb.arquillian.tests + spring-webmvc-deps + 0.0.1-SNAPSHOT + war + + + org.springframework.boot + spring-boot-starter-web + + + org.apache.tomcat.embed + * + + + + + + org.springframework.boot + spring-boot-starter-tomcat + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.tomee + jakartaee-api + 9.1.1 + provided + + + diff --git a/arquillian/arquillian-tomee-tests/pom.xml b/arquillian/arquillian-tomee-tests/pom.xml index c8f78fda3be..bc739356e1f 100644 --- a/arquillian/arquillian-tomee-tests/pom.xml +++ b/arquillian/arquillian-tomee-tests/pom.xml @@ -243,6 +243,7 @@ org.codehaus.gmaven groovy-maven-plugin + 2.1.1 configure-adapters diff --git a/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/CxfRSService.java b/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/CxfRSService.java index b2eea0bac9b..6d9b8a6956b 100644 --- a/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/CxfRSService.java +++ b/server/openejb-cxf-rs/src/main/java/org/apache/openejb/server/cxf/rs/CxfRSService.java @@ -167,6 +167,12 @@ public void init(final Properties properties) throws Exception { config = properties; factoryByListener = "true".equalsIgnoreCase(properties.getProperty("openejb.cxf-rs.factoryByListener", "false")); + try { + Class.forName("org.apache.cxf.jaxrs.springmvc.SpringWebUtils"); + } catch (Throwable t) { + // ignore + } + System.setProperty("org.apache.johnzon.max-string-length", SystemInstance.get().getProperty("org.apache.johnzon.max-string-length", properties.getProperty("org.apache.johnzon.max-string-length", "8192")));