diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 5bf33c5..ab8794e 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -42,7 +42,7 @@ jobs: java-version: ${{ matrix.java }} cache: 'maven' - name: Set up Maven - run: mvn --errors --batch-mode --show-version wrapper:wrapper "-Dmaven=3.9.5" + run: mvn --errors --batch-mode --show-version wrapper:wrapper "-Dmaven=3.9.9" - name: Build with Maven run: ./mvnw install -e -B -V javadoc:jar diff --git a/jetty-embedded-10/src/main/java/org/jboss/arquillian/container/jetty/embedded_10/ArquillianAppProvider.java b/jetty-embedded-10/src/main/java/org/jboss/arquillian/container/jetty/embedded_10/ArquillianAppProvider.java index 778e78c..174ddac 100644 --- a/jetty-embedded-10/src/main/java/org/jboss/arquillian/container/jetty/embedded_10/ArquillianAppProvider.java +++ b/jetty-embedded-10/src/main/java/org/jboss/arquillian/container/jetty/embedded_10/ArquillianAppProvider.java @@ -97,10 +97,7 @@ protected App createApp(final Archive archive) { final File exported; try { if (this.config.isUseArchiveNameAsContext()) { - Path tmpDirectory = Files.createTempDirectory("arquillian-jetty"); - Path archivePath = tmpDirectory.resolveSibling(archive.getName()); - Files.deleteIfExists(archivePath); - exported = Files.createFile(archivePath).toFile(); + exported = Files.createFile(EXPORT_DIR.toPath().resolve(archive.getName())).toFile(); exported.deleteOnExit(); } else { // If this method returns successfully then it is guaranteed that: diff --git a/jetty-embedded-11/src/main/java/org/jboss/arquillian/container/jetty/embedded_11/ArquillianAppProvider.java b/jetty-embedded-11/src/main/java/org/jboss/arquillian/container/jetty/embedded_11/ArquillianAppProvider.java index c655f07..ed993b9 100644 --- a/jetty-embedded-11/src/main/java/org/jboss/arquillian/container/jetty/embedded_11/ArquillianAppProvider.java +++ b/jetty-embedded-11/src/main/java/org/jboss/arquillian/container/jetty/embedded_11/ArquillianAppProvider.java @@ -100,10 +100,7 @@ protected App createApp(final Archive archive) { final File exported; try { if (this.config.isUseArchiveNameAsContext()) { - Path tmpDirectory = Files.createTempDirectory("arquillian-jetty"); - Path archivePath = tmpDirectory.resolveSibling(archive.getName()); - Files.deleteIfExists(archivePath); - exported = Files.createFile(archivePath).toFile(); + exported = Files.createFile(EXPORT_DIR.toPath().resolve(archive.getName())).toFile(); exported.deleteOnExit(); } else { // If this method returns successfully then it is guaranteed that: diff --git a/jetty-embedded-12-ee10/pom.xml b/jetty-embedded-12-ee10/pom.xml index 2ddf176..1e38830 100644 --- a/jetty-embedded-12-ee10/pom.xml +++ b/jetty-embedded-12-ee10/pom.xml @@ -45,6 +45,11 @@ pom import + + jakarta.inject + jakarta.inject-api + 2.0.1 + @@ -119,13 +124,6 @@ jetty-ee10-plus - - jakarta.inject - jakarta.inject-api - 2.0.1 - provided - - org.eclipse.jetty.http2 jetty-http2-server diff --git a/jetty-embedded-12-ee10/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee10/ArquillianAppProvider.java b/jetty-embedded-12-ee10/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee10/ArquillianAppProvider.java index bca9886..d9a5baa 100644 --- a/jetty-embedded-12-ee10/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee10/ArquillianAppProvider.java +++ b/jetty-embedded-12-ee10/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee10/ArquillianAppProvider.java @@ -103,10 +103,7 @@ protected App createApp(final Archive archive) { final File exported; try { if (this.config.isUseArchiveNameAsContext()) { - Path tmpDirectory = Files.createTempDirectory("arquillian-jetty"); - Path archivePath = tmpDirectory.resolveSibling(archive.getName()); - Files.deleteIfExists(archivePath); - exported = Files.createFile(archivePath).toFile(); + exported = Files.createFile(EXPORT_DIR.toPath().resolve(archive.getName())).toFile(); exported.deleteOnExit(); } else { // If this method returns successfully then it is guaranteed that: diff --git a/jetty-embedded-12-ee11/pom.xml b/jetty-embedded-12-ee11/pom.xml new file mode 100644 index 0000000..c06db1b --- /dev/null +++ b/jetty-embedded-12-ee11/pom.xml @@ -0,0 +1,302 @@ + + + + + org.jboss.arquillian.container + arquillian-parent-jetty + 2.0.1.Final-SNAPSHOT + ../pom.xml + + + 4.0.0 + + arquillian-jetty-embedded-12-ee11 + Arquillian Container Jetty Embedded 12.0.x ee11 + Jetty Embedded 12.0.x container integration for the Arquillian project + + + 5.1.2.Final + 6.0.0 + + + 17 + 17 + 17 + + + + + + org.jboss.weld.servlet + weld-servlet-core + ${version.weld} + + + org.eclipse.jetty + jetty-bom + ${jetty12.1.version} + pom + import + + + org.eclipse.jetty.ee11 + jetty-ee11-bom + ${jetty12.1.version} + pom + import + + + + + + + + org.jboss.arquillian.container + arquillian-jetty-common + ${project.version} + + + + + org.jboss.arquillian.container + arquillian-container-spi + + + org.jboss.arquillian.container + arquillian-container-test-spi + + + org.jboss.arquillian.protocol + arquillian-protocol-servlet-jakarta + + + org.jboss.arquillian.testenricher + arquillian-testenricher-cdi-jakarta + + + org.jboss.arquillian.testenricher + arquillian-testenricher-resource-jakarta + + + org.jboss.arquillian.testenricher + arquillian-testenricher-initialcontext + + + + org.codehaus.plexus + plexus-utils + + + org.codehaus.plexus + plexus-xml + + + + org.eclipse.jetty + jetty-server + provided + + + org.eclipse.jetty + jetty-deploy + provided + + + + org.eclipse.jetty.ee11 + jetty-ee11-webapp + provided + + + + org.eclipse.jetty.ee11 + jetty-ee11-annotations + provided + + + + org.eclipse.jetty.ee11 + jetty-ee11-plus + + + + jakarta.inject + jakarta.inject-api + 2.0.1 + provided + + + + org.eclipse.jetty.http2 + jetty-http2-server + provided + + + org.eclipse.jetty.http2 + jetty-http2-hpack + provided + + + org.eclipse.jetty + jetty-alpn-server + provided + + + org.eclipse.jetty.alpn + alpn-api + 1.1.3.v20160715 + + + + org.eclipse.jetty.ee11 + jetty-ee11-cdi + + + + org.jboss.arquillian.junit5 + arquillian-junit5-container + test + + + + org.junit.jupiter + junit-jupiter + + + + org.hamcrest + hamcrest + test + + + + jakarta.enterprise + jakarta.enterprise.cdi-api + 4.1.0 + provided + + + + jakarta.interceptor + jakarta.interceptor-api + 2.1.0 + provided + + + + io.smallrye + jandex + 3.1.7 + provided + + + + + + com.h2database + h2 + + + + org.jboss.shrinkwrap.descriptors + shrinkwrap-descriptors-impl-javaee + test + + + + + org.jboss.shrinkwrap.resolver + shrinkwrap-resolver-api + test + + + org.jboss.shrinkwrap.resolver + shrinkwrap-resolver-spi + test + + + org.jboss.shrinkwrap.resolver + shrinkwrap-resolver-api-maven + test + + + org.jboss.shrinkwrap.resolver + shrinkwrap-resolver-spi-maven + test + + + org.jboss.shrinkwrap.resolver + shrinkwrap-resolver-impl-maven + test + + + javax.inject + javax.inject + + + javax.annotation + javax.annotation-api + + + + + org.jboss.shrinkwrap.resolver + shrinkwrap-resolver-impl-maven-archive + test + + + + org.jboss.weld.servlet + weld-servlet-core + + + + org.eclipse.jetty + jetty-client + test + + + + org.slf4j + slf4j-simple + test + + + + jakarta.el + jakarta.el-api + 6.0.1 + test + + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + + + ban-javax-api + + enforce + + validate + + + + + javax.*:* + + true + + + + + + + + + + diff --git a/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/ArquillianAppProvider.java b/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/ArquillianAppProvider.java new file mode 100644 index 0000000..7af8d13 --- /dev/null +++ b/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/ArquillianAppProvider.java @@ -0,0 +1,240 @@ +package org.jboss.arquillian.container.jetty.embedded_12_ee11; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.logging.Logger; + +import org.eclipse.jetty.ee11.annotations.AnnotationConfiguration; +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.AppProvider; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.ee11.plus.webapp.EnvConfiguration; +import org.eclipse.jetty.ee11.plus.webapp.PlusConfiguration; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.resource.Resource; +import org.eclipse.jetty.ee11.webapp.FragmentConfiguration; +import org.eclipse.jetty.ee11.webapp.JettyWebXmlConfiguration; +import org.eclipse.jetty.ee11.webapp.WebAppContext; +import org.eclipse.jetty.util.resource.ResourceFactory; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.exporter.ZipExporter; + +public class ArquillianAppProvider extends AbstractLifeCycle implements AppProvider { + private static final Logger LOG = Logger.getLogger(ArquillianAppProvider.class.getName()); + + /** + * The prefix assigned to the temporary file where the archive is exported + */ + private static final String EXPORT_FILE_PREFIX = "export"; + + /** + * Directory into which we'll extract export the war files + */ + private static final File EXPORT_DIR; + + private static final String SLASH = "/"; + + static { + /* + * Use of java.io.tmpdir Should be a last-resort fallback for temp directory. + * + * Use of java.io.tmpdir on CI systems is dangerous (overwrite possibility is extremely high) + * + * Use of java.io.tmpdir on Unix systems is unreliable (due to common /tmp dir cleanup processes) + */ + File systemDefaultTmpDir = new File(System.getProperty("java.io.tmpdir")); + + // If running under maven + surefire, use information provided by surefire. + String baseDirVal = System.getProperty("basedir"); + + File mavenTmpDir = null; + if (baseDirVal != null) { + File baseDir = new File(baseDirVal); + if (baseDir.exists() && baseDir.isDirectory()) { + File targetDir = new File(baseDir, "target"); + if (targetDir.exists() && targetDir.isDirectory()) { + mavenTmpDir = new File(targetDir, "arquillian-jetty-temp"); + mavenTmpDir.mkdirs(); + } + } + } + + if ((mavenTmpDir != null) && mavenTmpDir.exists() && mavenTmpDir.isDirectory()) { + EXPORT_DIR = mavenTmpDir; + } else { + EXPORT_DIR = systemDefaultTmpDir; + } + + // If the temp location doesn't exist or isn't a directory + if (!EXPORT_DIR.exists() || !EXPORT_DIR.isDirectory()) { + throw new IllegalStateException("Could not obtain export directory \"" + EXPORT_DIR.getAbsolutePath() + "\""); + } + } + + private final JettyEmbeddedConfiguration config; + private DeploymentManager deploymentManager; + private final Collection webAppContextProcessors; + + public ArquillianAppProvider(JettyEmbeddedConfiguration config, Collection webAppContextProcessors) { + this.config = config; + this.webAppContextProcessors = webAppContextProcessors; + } + + protected App createApp(final Archive archive) { + String name = archive.getName(); + int extOff = name.lastIndexOf('.'); + if (extOff <= 0) { + throw new RuntimeException("Not a valid Web Archive filename: " + name); + } + String ext = name.substring(extOff).toLowerCase(); + if (!ext.equals(".war")) { + throw new RuntimeException("Not a recognized Web Archive: " + name); + } + name = name.substring(0, extOff); + + final File exported; + try { + if (this.config.isUseArchiveNameAsContext()) { + exported = Files.createFile(EXPORT_DIR.toPath().resolve(archive.getName())).toFile(); + exported.deleteOnExit(); + } else { + // If this method returns successfully then it is guaranteed that: + // 1. The file denoted by the returned abstract pathname did not exist before this method was invoked, and + // 2. Neither this method nor any of its variants will return the same abstract pathname again in the current invocation of the virtual machine. + exported = File.createTempFile(EXPORT_FILE_PREFIX, archive.getName(), EXPORT_DIR); + } + } catch (IOException e) { + throw new RuntimeException("Could not create temporary File in " + EXPORT_DIR + " to write exported archive", + e); + } + // We are overwriting the temporary file placeholder reserved by File#createTemplateFile() + archive.as(ZipExporter.class).exportTo(exported, true); + + // Mark to delete when we come down + // exported.deleteOnExit(); + + // Add the context + URI uri = exported.toURI(); + LOG.info("Webapp archive location: " + uri.toASCIIString()); + + return new ArchiveApp(deploymentManager, this, Path.of(uri), archive); + } + + private static class ArchiveApp extends App { + private final Archive archive; + + public ArchiveApp(DeploymentManager manager, AppProvider provider, Path originId, Archive archive) { + super(manager, provider, originId); + this.archive = archive; + } + } + + private static boolean isWebArchiveFile(Path path) { + if (!path.toFile().isFile()) { + return false; + } else { + String name = path.toFile().getName().toLowerCase(Locale.ENGLISH); + return name.endsWith(".war") || name.endsWith(".jar"); + } + } + + @Override + public ContextHandler createContextHandler(final App app) throws Exception { + Resource resource = ResourceFactory.root().newResource(app.getPath()); + Path file = resource.getPath(); + if (!resource.exists()) + throw new IllegalStateException("App resouce does not exist " + resource); + + String context = file.toFile().getName(); + + if (isWebArchiveFile(file)) { + // Context Path is the same as the archive. + context = context.substring(0, context.length() - 4); + } else { + throw new IllegalStateException("unable to create ContextHandler for " + app); + } + + // Ensure "/" is Not Trailing in context paths. + if (context.endsWith("/") && context.length() > 0) { + context = context.substring(0, context.length() - 1); + } + + // Start building the webapplication + WebAppContext webAppContext = new WebAppContext(); + webAppContext.setDisplayName(context); + webAppContext.setLogUrlOnStart(true); + webAppContext.setCrossContextDispatchSupported(config.isCrossContextDispatchSupported()); + String configuredConfigurationClasses = config.getConfigurationClasses(); + if (configuredConfigurationClasses != null && configuredConfigurationClasses.trim().length() > 0) { + // User provided classlist, use it as-is. + webAppContext.setConfigurationClasses(configuredConfigurationClasses.split(",")); + + } else { + // Arquillian assumption is that all features of Servlet 3.1 are available. + // This means that annotation scanning is enabled by default. + // That means jetty-plus is mandatory. + + // Applying equivalent of etc/jetty-annotations.xml + webAppContext.addConfiguration(new JettyWebXmlConfiguration(), + new AnnotationConfiguration()); + + // Applying equivalent of etc/jetty-plus.xml + webAppContext.addConfiguration(new FragmentConfiguration() + , new EnvConfiguration(), new PlusConfiguration()); + } + + // special case of archive (or dir) named "root" is / context + if (context.equalsIgnoreCase("root")) { + context = SLASH; + } else if (context.toLowerCase(Locale.ENGLISH).startsWith("root-")) { + int dash = context.toLowerCase(Locale.ENGLISH).indexOf('-'); + String virtual = context.substring(dash + 1); + webAppContext.setVirtualHosts(List.of(virtual)); + context = SLASH; + } + + // Ensure "/" is Prepended to all context paths. + if (context.charAt(0) != '/') { + context = "/" + context; + } + + webAppContext.setContextPath(context); + webAppContext.setWar(file.toFile().getAbsolutePath()); + if (config.hasDefaultsDescriptor()) { + webAppContext.setDefaultsDescriptor(config.getDefaultsDescriptor().toASCIIString()); + } + webAppContext.setExtractWAR(true); + + webAppContext.setParentLoaderPriority(config.getClassloaderBehavior() == JettyEmbeddedConfiguration.ClassLoaderBehavior.JAVA_SPEC); + + if (config.getTempDirectory() != null) { + /* + * Since the Temp Dir is really a context base temp directory, Lets set the Temp Directory in a way similar to how WebInfConfiguration does it, + * instead of setting the WebAppContext.setTempDirectory(File). If we used .setTempDirectory(File) all webapps will wind up in the same temp / work + * directory, overwriting each others work. + */ + webAppContext.setAttribute(WebAppContext.TEMP_DIR, config.getTempDirectory()); + } + + webAppContextProcessors.forEach(processor -> processor.process(webAppContext, ((ArchiveApp)app).archive)); + + return webAppContext; + } + + @Override + public String getEnvironmentName() { + return "ee11"; + } + + @Override + public void setDeploymentManager(DeploymentManager deploymentManager) { + this.deploymentManager = deploymentManager; + } +} diff --git a/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyEmbeddedConfiguration.java b/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyEmbeddedConfiguration.java new file mode 100644 index 0000000..ce5beac --- /dev/null +++ b/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyEmbeddedConfiguration.java @@ -0,0 +1,132 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2010, Red Hat Middleware LLC, and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * 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 org.jboss.arquillian.container.jetty.embedded_12_ee11; + +import java.io.File; +import java.net.URI; + +import org.eclipse.jetty.server.HttpConfiguration; +import org.jboss.arquillian.container.jetty.AbstractJettyEmbeddedConfiguration; + +/** + * A {@link org.jboss.arquillian.container.spi.client.container.ContainerConfiguration} implementation for the Jetty Embedded + * containers. + * + * @author Dan Allen + * @author Ales Justin + */ +public class JettyEmbeddedConfiguration extends AbstractJettyEmbeddedConfiguration { + public enum ClassLoaderBehavior + + { + /** + * Default behavior for Java Spec (server classloader, then webapp). + * + * Also the default for Arquillian. + */ + JAVA_SPEC, + /** Default behavior for Servlet Spec (webapp classloader, then server) */ + SERVLET_SPEC + } + + /** + * Classloader Search Order behavior. + *

+ * Default for Arquillian is {@link ClassLoaderBehavior#JAVA_SPEC}. + */ + private ClassLoaderBehavior classloaderBehavior = ClassLoaderBehavior.JAVA_SPEC; + + /** + * Optional override for the default servlet spec descriptor + */ + private URI defaultsDescriptor; + + /** + * Dump, to System.err, the server state tree after the server has successfully started up. + */ + private boolean dumpServerAfterStart = false; + + /** + * Optional HttpConfiguration for the ServerConnector that Arquillian + * creates. + */ + private HttpConfiguration httpConfiguration; + + /** + * Idle Timeout (in milliseconds) for active connections. + *

+ * Default: 30,000ms + */ + private long idleTimeoutMillis = 30000; + + /** + * Base directory for all temp files that Jetty will manage. + */ + private File tempDirectory; + + public ClassLoaderBehavior getClassloaderBehavior() { + return classloaderBehavior; + } + + public URI getDefaultsDescriptor() { + return defaultsDescriptor; + } + + public HttpConfiguration getHttpConfiguration() { + return httpConfiguration; + } + + public long getIdleTimeoutMillis() { + return idleTimeoutMillis; + } + + public File getTempDirectory() { + return tempDirectory; + } + + public boolean hasDefaultsDescriptor() { + return (defaultsDescriptor != null); + } + + public boolean isDumpServerAfterStart() { + return dumpServerAfterStart; + } + + public void setClassloaderBehavior(ClassLoaderBehavior classloaderBehavior) { + this.classloaderBehavior = classloaderBehavior; + } + + public void setDefaultsDescriptor(URI defaultsDescriptor) { + this.defaultsDescriptor = defaultsDescriptor; + } + + public void setDumpServerAfterStart(boolean serverDumpAfterStart) { + this.dumpServerAfterStart = serverDumpAfterStart; + } + + public void setHttpConfiguration(HttpConfiguration httpConfiguration) { + this.httpConfiguration = httpConfiguration; + } + + public void setIdleTimeoutMillis(long milliseconds) { + this.idleTimeoutMillis = milliseconds; + } + + public void setTempDirectory(File tempDirectory) { + this.tempDirectory = tempDirectory; + } +} diff --git a/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyEmbeddedContainer.java b/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyEmbeddedContainer.java new file mode 100644 index 0000000..98db667 --- /dev/null +++ b/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyEmbeddedContainer.java @@ -0,0 +1,369 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2010, Red Hat Middleware LLC, and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * 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 org.jboss.arquillian.container.jetty.embedded_12_ee11; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.Collection; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.codehaus.plexus.util.ReflectionUtils; +import org.eclipse.jetty.deploy.App; +import org.eclipse.jetty.deploy.AppLifeCycle; +import org.eclipse.jetty.deploy.DeploymentManager; +import org.eclipse.jetty.ee11.cdi.CdiDecoratingListener; +import org.eclipse.jetty.ee11.cdi.CdiServletContainerInitializer; +import org.eclipse.jetty.http.CookieCompliance; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory; +import org.eclipse.jetty.security.HashLoginService; +import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.server.handler.ContextHandler; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.server.handler.DefaultHandler; +import org.eclipse.jetty.ee11.servlet.ServletHandler; +import org.eclipse.jetty.ee11.servlet.ServletHolder; +import org.eclipse.jetty.util.TypeUtil; +import org.eclipse.jetty.util.resource.ResourceFactory; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.eclipse.jetty.ee11.webapp.WebAppContext; +import org.jboss.arquillian.container.jetty.EnvUtil; +import org.jboss.arquillian.container.spi.client.container.DeployableContainer; +import org.jboss.arquillian.container.spi.client.container.DeploymentException; +import org.jboss.arquillian.container.spi.client.container.LifecycleException; +import org.jboss.arquillian.container.spi.client.protocol.ProtocolDescription; +import org.jboss.arquillian.container.spi.client.protocol.metadata.HTTPContext; +import org.jboss.arquillian.container.spi.client.protocol.metadata.ProtocolMetaData; +import org.jboss.arquillian.container.spi.client.protocol.metadata.Servlet; +import org.jboss.arquillian.container.spi.context.annotation.DeploymentScoped; +import org.jboss.arquillian.core.api.Instance; +import org.jboss.arquillian.core.api.InstanceProducer; +import org.jboss.arquillian.core.api.annotation.ApplicationScoped; +import org.jboss.arquillian.core.api.annotation.Inject; +import org.jboss.arquillian.core.spi.ServiceLoader; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.descriptor.api.Descriptor; + +import jakarta.servlet.ServletContext; + +/** + *

+ * Jetty Embedded 11.x container for the Arquillian project. + *

+ *

+ *

+ * This container only supports a WebArchive deployment. The context path of the deployed application is always set to + * "/test", which is expected by the + * Arquillian servlet protocol. + *

+ *

+ *

+ * Another known issue is that the container configuration process logs an exception when running in-container. However, + * the container is still configured + * properly during setup. + *

+ * + * @author Dan Allen + * @author Ales Justin + * @author Martin Kouba + */ +public class JettyEmbeddedContainer implements DeployableContainer { + private static final Logger log = Logger.getLogger(JettyEmbeddedContainer.class.getName()); + + private Server server; + private String listeningHost; + private int listeningPort; + private DeploymentManager deployer; + private ArquillianAppProvider appProvider; + + private JettyEmbeddedConfiguration containerConfig; + + @Inject + @DeploymentScoped + private InstanceProducer webAppContextProducer; + + @Inject + @ApplicationScoped + private InstanceProducer servletContextInstanceProducer; + + @Inject + private Instance serviceLoader; + + /* + * (non-Javadoc) + * + * @see org.jboss.arquillian.spi.client.container.DeployableContainer#getConfigurationClass() + */ + public Class getConfigurationClass() { + return JettyEmbeddedConfiguration.class; + } + + /* + * (non-Javadoc) + * + * @see org.jboss.arquillian.spi.client.container.DeployableContainer#getDefaultProtocol() + */ + public ProtocolDescription getDefaultProtocol() { + // Jetty 9 is a Servlet 3.1 container. + // However, Arquillian "Protocol" actual means "Packaging" + return new ProtocolDescription("Servlet 5.0"); + } + + public void setup(JettyEmbeddedConfiguration containerConfig) { + this.containerConfig = containerConfig; + } + + public void start() throws LifecycleException { + EnvUtil.assertMinimumJettyVersion(Server.getVersion(), "12.0"); + + try { + server = new Server(); + + HttpConfiguration httpConfig = getHttpConfiguration(); + + ConnectionFactory connectionFactory = new HttpConnectionFactory(httpConfig); + // Setup Connector + ServerConnector connector; + if (containerConfig.isSsl()) { + SslContextFactory.Server sslContextFactory = getSslContextFactory(); + server.addBean(sslContextFactory); + SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()); + sslConnectionFactory.setEnsureSecureRequestCustomizer(containerConfig.isSniRequired()); + connector = new ServerConnector(server, sslConnectionFactory, connectionFactory); + } else { + connector = new ServerConnector(server, connectionFactory); + } + + if (containerConfig.isH2cEnabled()) { + HTTP2CServerConnectionFactory http2CServerConnectionFactory = new HTTP2CServerConnectionFactory(httpConfig); + connector.addConnectionFactory(http2CServerConnectionFactory); + } + + connector.setHost(containerConfig.getBindAddress()); + connector.setPort(containerConfig.getBindHttpPort()); + connector.setIdleTimeout(containerConfig.getIdleTimeoutMillis()); + server.setConnectors(new Connector[] {connector}); + + // Handler Tree location for all webapps + ContextHandlerCollection contexts = new ContextHandlerCollection(); + + // Deployment Management + deployer = new DeploymentManager(); + deployer.setContexts(contexts); + Collection webAppContextProcessors = serviceLoader.get().all(WebAppContextProcessor.class); + appProvider = new ArquillianAppProvider(containerConfig, webAppContextProcessors); + deployer.addAppProvider(appProvider); + server.addBean(deployer); + + // Handler Collection + Handler.Collection collection = new Handler.Sequence(contexts, new DefaultHandler()); + server.setHandler(collection); + + if (containerConfig.isRealmPropertiesFileSet()) { + String realmName = getRealmName(); + HashLoginService hashUserRealm = + new HashLoginService(realmName, + ResourceFactory.of(server) + .newResource(Path.of(containerConfig.getRealmProperties().getAbsolutePath()))); + server.addBean(hashUserRealm); + } + + if (containerConfig.areInferredEncodings()) { + containerConfig.getInferredEncodings().forEach((s, s2) -> server.getMimeTypes().addInferred(s, s2)); + } + + server.setDumpAfterStart(containerConfig.isDumpServerAfterStart()); + log.info("Starting Jetty Embedded Server " + Server.getVersion() + " [id:" + server.hashCode() + "]"); + server.start(); + + listeningHost = connector.getHost(); + if (listeningHost == null) { + listeningHost = containerConfig.getBindAddress(); + } + listeningPort = connector.getLocalPort(); + } catch (Exception e) { + throw new LifecycleException("Could not start container", e); + } + } + + private String getRealmName() { + File realmProperties = containerConfig.getRealmProperties(); + String fileName = realmProperties.getName(); + int index; + if ((index = fileName.indexOf('.')) > -1) { + fileName = fileName.substring(0, index); + } + return fileName; + } + + public void stop() throws LifecycleException { + try { + log.info("Stopping Jetty Embedded Server [id:" + server.hashCode() + "]"); + server.stop(); + } catch (Exception e) { + throw new LifecycleException("Could not stop container", e); + } + } + + /* + * (non-Javadoc) + * + * @see org.jboss.arquillian.spi.client.container.DeployableContainer#deploy(org.jboss.shrinkwrap.descriptor.api.Descriptor) + */ + public void deploy(Descriptor descriptor) throws DeploymentException { + throw new UnsupportedOperationException("Not implemented"); + } + + /* + * (non-Javadoc) + * + * @see org.jboss.arquillian.spi.client.container.DeployableContainer#undeploy(org.jboss.shrinkwrap.descriptor.api.Descriptor) + */ + public void undeploy(Descriptor descriptor) throws DeploymentException { + throw new UnsupportedOperationException("Not implemented"); + } + + public ProtocolMetaData deploy(final Archive archive) throws DeploymentException { + try { + App app = appProvider.createApp(archive); + deployer.removeApp(app); + WebAppContext webAppContext = getWebAppContext(app); + + // Jetty setup telling Jetty's CdiDecoratingListener how to operate. + webAppContext.setInitParameter(CdiServletContainerInitializer.CDI_INTEGRATION_ATTRIBUTE, CdiDecoratingListener.MODE); + // jetty setup for layer between Weld and Jetty. + webAppContext.addServletContainerInitializer(new CdiServletContainerInitializer()); + // Weld's org.jboss.weld.environment.servlet.EnhancedListener can be discovered automatically + // However, it won't happen if jetty-ee11-annotations JAR isn't present in runtime, hence we add it explicitly + // The listener will start up Weld container so long as there is an archive with any beans in it + webAppContext.addServletContainerInitializer(new org.jboss.weld.environment.servlet.EnhancedListener()); + + if (containerConfig.areMimeTypesSet()) { + containerConfig.getMimeTypes().forEach((s, s2) -> webAppContext.getMimeTypes().addMimeMapping(s, s2)); + } + + deployer.addApp(app); + deployer.requestAppGoal(app, AppLifeCycle.STARTED); + + webAppContextProducer.set(app); + servletContextInstanceProducer.set(webAppContext.getServletContext()); + + HTTPContext httpContext = new HTTPContext(listeningHost, listeningPort); + ServletHandler servletHandler = webAppContext.getServletHandler(); + for (ServletHolder servlet : servletHandler.getServlets()) { + httpContext.add(new Servlet(servlet.getName(), servlet.getServletContext().getContextPath())); + } + return new ProtocolMetaData().addContext(httpContext); + } catch (Exception e) { + throw new DeploymentException("Could not deploy " + archive.getName(), e); + } + } + + private WebAppContext getWebAppContext(App app) throws Exception { + ContextHandler handler = app.getContextHandler(); + WebAppContext webAppContext; + if (handler instanceof WebAppContext) { + webAppContext = (WebAppContext) handler; + } else { + throw new DeploymentException("Deployment of raw ContextHandler's not supported by Arquillian"); + } + return webAppContext; + } + + public void undeploy(Archive archive) throws DeploymentException { + App app = webAppContextProducer.get(); + if (app != null) { + deployer.requestAppGoal(app, AppLifeCycle.UNDEPLOYED); + } + } + + /** + * Setup HTTP Configuration + * @return HttpConfiguration + */ + private HttpConfiguration getHttpConfiguration() { + HttpConfiguration httpConfig = containerConfig.getHttpConfiguration(); + if (httpConfig == null) { + httpConfig = new HttpConfiguration(); + if (this.containerConfig.isHeaderBufferSizeSet()) { + httpConfig.setRequestHeaderSize(containerConfig.getHeaderBufferSize()); + httpConfig.setResponseHeaderSize(containerConfig.getHeaderBufferSize()); + } + if(this.containerConfig.getRequestCookieCompliance()!=null) { + httpConfig.setRequestCookieCompliance(CookieCompliance.from(containerConfig.getRequestCookieCompliance())); + } + if(this.containerConfig.getResponseCookieCompliance()!=null) { + httpConfig.setResponseCookieCompliance(CookieCompliance.from(containerConfig.getResponseCookieCompliance())); + } + + if(containerConfig.getHttpConfigurationProperties()!=null){ + for(Map.Entry propertyEntry:containerConfig.getHttpConfigurationProperties().entrySet()){ + Method setter = ReflectionUtils.getSetter(propertyEntry.getKey(), httpConfig.getClass()); + Class setterClass = ReflectionUtils.getSetterType(setter); + Object value = TypeUtil.valueOf(setterClass, propertyEntry.getValue()); + try { + setter.invoke(httpConfig, value); + } catch (IllegalAccessException | InvocationTargetException e) { + log.log(Level.WARNING, "Ignore error setting field with name " + propertyEntry.getKey() + " with value " + propertyEntry.getValue(), e); + } + } + } + + } + + SecureRequestCustomizer secureRequestCustomizer = httpConfig.getCustomizer(SecureRequestCustomizer.class); + if (secureRequestCustomizer == null) { + secureRequestCustomizer = new SecureRequestCustomizer(); + httpConfig.addCustomizer(secureRequestCustomizer); + } + secureRequestCustomizer.setSniHostCheck(containerConfig.isSniHostCheck()); + secureRequestCustomizer.setSniRequired(containerConfig.isSniRequired()); + return httpConfig; + } + + private SslContextFactory.Server getSslContextFactory() { + SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); + if (containerConfig.getKeystorePath() != null) { + sslContextFactory.setKeyStorePath(new File(containerConfig.getKeystorePath()).getAbsolutePath()); + } + if (containerConfig.getKeystorePassword() != null) { + sslContextFactory.setKeyStorePassword(containerConfig.getKeystorePassword()); + } + if(containerConfig.getTrustStorePath() != null) { + sslContextFactory.setTrustStorePath(new File(containerConfig.getTrustStorePath()).getAbsolutePath()); + } + if(containerConfig.getTrustStorePassword() != null) { + sslContextFactory.setTrustStorePassword(containerConfig.getTrustStorePassword()); + } + sslContextFactory.setNeedClientAuth(containerConfig.isNeedClientAuth()); + sslContextFactory.setSniRequired(containerConfig.isSniRequired()); + + return sslContextFactory; + } +} diff --git a/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyExtension.java b/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyExtension.java new file mode 100644 index 0000000..602c2ef --- /dev/null +++ b/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyExtension.java @@ -0,0 +1,33 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * 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 org.jboss.arquillian.container.jetty.embedded_12_ee11; + +import org.jboss.arquillian.container.spi.client.container.DeployableContainer; +import org.jboss.arquillian.core.spi.LoadableExtension; + +/** + * Jetty Embedded 12.x extension. + * + * @author Aslak Knutsen + */ +public class JettyExtension implements LoadableExtension { + @Override + public void register(ExtensionBuilder builder) { + builder.service(DeployableContainer.class, JettyEmbeddedContainer.class); + } +} diff --git a/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/WebAppContextProcessor.java b/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/WebAppContextProcessor.java new file mode 100644 index 0000000..0d430d3 --- /dev/null +++ b/jetty-embedded-12-ee11/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/WebAppContextProcessor.java @@ -0,0 +1,19 @@ +package org.jboss.arquillian.container.jetty.embedded_12_ee11; + +import org.eclipse.jetty.ee11.webapp.WebAppContext; +import org.jboss.shrinkwrap.api.Archive; + +/** + * used to customise the {@link WebAppContext} created for a given {@link Archive} + * You need to register your implementations using the {@link org.jboss.arquillian.core.spi.LoadableExtension} mechanism + */ +public interface WebAppContextProcessor { + + /** + * + * @param webAppContext the created {@link WebAppContext} for the {@link Archive} + * @param archive The user defined deployment archive + */ + void process(WebAppContext webAppContext, Archive archive); + +} diff --git a/jetty-embedded-12-ee11/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/jetty-embedded-12-ee11/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension new file mode 100644 index 0000000..be80211 --- /dev/null +++ b/jetty-embedded-12-ee11/src/main/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension @@ -0,0 +1 @@ +org.jboss.arquillian.container.jetty.embedded_12_ee11.JettyExtension diff --git a/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyEmbeddedClientTestCase.java b/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyEmbeddedClientTestCase.java new file mode 100644 index 0000000..9adf7a0 --- /dev/null +++ b/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyEmbeddedClientTestCase.java @@ -0,0 +1,158 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2010, Red Hat Middleware LLC, and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * 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 org.jboss.arquillian.container.jetty.embedded_12_ee11; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.URL; + +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.transport.HttpClientTransportOverHTTP; +import org.eclipse.jetty.io.ClientConnector; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.hamcrest.Matchers; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.OperateOnDeployment; +import org.jboss.arquillian.container.test.api.TargetsContainer; +import org.jboss.arquillian.junit5.ArquillianExtension; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.shrinkwrap.descriptor.api.Descriptors; +import org.jboss.shrinkwrap.descriptor.api.webapp30.WebAppDescriptor; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import jakarta.servlet.ServletContext; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; + +/** + * Client test case for the Jetty Embedded 12 container + * + * @author Aslak Knutsen + * @author Dan Allen + */ +@ExtendWith(ArquillianExtension.class) +public class JettyEmbeddedClientTestCase { + /** + * Deployment for the test + */ + @Deployment(testable = false) + public static WebArchive getTestArchive() { + return ShrinkWrap.create(WebArchive.class, "client-http.war") + .addClass(MyOtherServlet.class) + .addClass(MyOtherBean.class) + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml") + .setWebXML(new StringAsset(Descriptors.create(WebAppDescriptor.class) + .version("4.0") + .createServlet() + .servletClass(MyOtherServlet.class.getName()) + .servletName("MyOtherServlet").up() + .createServletMapping() + .servletName("MyOtherServlet") + .urlPattern(MyOtherServlet.URL_PATTERN).up() + .exportAsString())); + } + + /** + * Deployment for the test + */ + @Deployment(testable = false, name = "webapp-https") @TargetsContainer("https") + public static WebArchive getTestArchiveHttps() { + return ShrinkWrap.create(WebArchive.class, "client-https.war") + .addClass(MyServlet.class) + .setWebXML(new StringAsset(Descriptors.create(WebAppDescriptor.class) + .version("4.0") + .createServlet() + .servletClass(MyServlet.class.getName()) + .servletName("MyServlet").up() + .createServletMapping() + .servletName("MyServlet") + .urlPattern(MyServlet.URL_PATTERN).up() + .exportAsString())); + } + + @ArquillianResource + ServletContext servletContext; + + @ArquillianResource URL url; + + @ArquillianResource @OperateOnDeployment("webapp-https") URL urlHttps; + + private HttpClient httpClient; + private final SslContextFactory.Client clientSslContextFactory = new SslContextFactory.Client(); + + @BeforeEach + public void setup() throws Exception { + clientSslContextFactory.setTrustAll(true); + ClientConnector clientConnector = new ClientConnector(); + clientConnector.setSelectors(1); + clientConnector.setSslContextFactory(clientSslContextFactory); + httpClient = new HttpClient(new HttpClientTransportOverHTTP(clientConnector)); + httpClient.start(); + } + + @AfterEach + public void shutdown() throws Exception { + if(httpClient.isRunning()) { + httpClient.stop(); + } + } + + @Test + public void shouldBeAbleToInvokeServletInDeployedWebApp() throws Exception { + + String body = httpClient.GET(new URL(url, MyOtherServlet.URL_PATTERN).toURI()).getContentAsString(); + + assertThat( + "Verify that the servlet was deployed and returns expected result", + body, + Matchers.is(MyOtherServlet.MESSAGE)); + } + + @Test + public void shouldBeAbleToInvokeServletInDeployedWebAppHttps() throws Exception { + URL url = new URL("https", urlHttps.getHost(), urlHttps.getPort(), urlHttps.getPath() + MyServlet.URL_PATTERN); + String body = httpClient.GET(url.toURI()).getContentAsString(); + + assertThat( + "Verify that the servlet was deployed and returns expected result", + body, + Matchers.is(MyServlet.MESSAGE)); + } + + @Test + public void shouldEnrichTestWithServletContext() { + assertThat(servletContext, notNullValue()); + } + + public static String readAllAndClose(InputStream is) throws Exception { + try (is;ByteArrayOutputStream out = new ByteArrayOutputStream()) { + int read; + while ((read = is.read()) != -1) { + out.write(read); + } + return out.toString(); + } + } +} diff --git a/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyEmbeddedInContainerTestCase.java b/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyEmbeddedInContainerTestCase.java new file mode 100644 index 0000000..78ea3d8 --- /dev/null +++ b/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/JettyEmbeddedInContainerTestCase.java @@ -0,0 +1,131 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2010, Red Hat Middleware LLC, and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * 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 org.jboss.arquillian.container.jetty.embedded_12_ee11; + +import java.net.URL; +import java.sql.Connection; + +import jakarta.annotation.Resource; +import jakarta.inject.Inject; +import javax.sql.DataSource; + +import org.hamcrest.core.StringContains; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.OperateOnDeployment; +import org.jboss.arquillian.junit5.ArquillianExtension; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.GenericArchive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.shrinkwrap.descriptor.api.Descriptors; +import org.jboss.shrinkwrap.descriptor.api.webapp30.WebAppDescriptor; +import org.jboss.shrinkwrap.resolver.api.maven.Maven; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +/** + * In-container test case for the Jetty Embedded 12 container + * + * @author Dan Allen + * + */ +@ExtendWith(ArquillianExtension.class) +public class JettyEmbeddedInContainerTestCase { + /** + * Deployment for the test + */ + @Deployment + public static WebArchive getTestArchive() { + return ShrinkWrap.create(WebArchive.class) + .addClass(MyBean.class) + // adding the configuration class silences the logged exception when building the configuration on the server-side, but shouldn't be necessary + //.addClass(JettyEmbeddedConfiguration.class) + .addAsLibraries( + Maven.configureResolver() + .workOffline() + .loadPomFromFile("pom.xml") + .resolve("org.jboss.weld.servlet:weld-servlet-core") + .withTransitivity() + .as(GenericArchive.class)) + .addAsWebInfResource("jetty-env.xml") + .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml") + .setWebXML("in-container-web.xml"); + } + + @Deployment(name="encoding") + public static WebArchive getEncodingTestArchive() { + return ShrinkWrap.create(WebArchive.class) + .addClass(MyEncodingServlet.class) + .setWebXML(new StringAsset(Descriptors.create(WebAppDescriptor.class) + .version("4.0") + .createServlet() + .servletClass(MyEncodingServlet.class.getName()) + .servletName("encoding").up() + .createServletMapping() + .servletName("encoding") + .urlPattern(MyEncodingServlet.URL_PATTERN).up() + .exportAsString())); + } + + @ArquillianResource @OperateOnDeployment("encoding") + private URL encodingUrl; + + // defined in jetty-env.xml, scoped to global + @Resource(mappedName = "version") Integer version; + + // defined in web.xml, scoped to webapp (relative to java:comp/env) + @Resource(name = "name") String name; + + // defined in jetty-env.xml, scoped to webapp (relative to java:comp/env) + @Resource(name = "type") String containerType; + + @Resource(name = "jdbc/test") DataSource ds; + + @Inject MyBean testBean; + + @Test + public void shouldBeAbleToInjectMembersIntoTestClass() throws Exception { + assertThat(version, notNullValue()); + assertThat(version, is(6)); + assertThat(name, notNullValue()); + assertThat(name, is("Jetty")); + assertThat(containerType, notNullValue()); + assertThat(containerType, is("Embedded")); + assertThat(ds, notNullValue()); + try (Connection c = ds.getConnection()) { + assertThat(c.getMetaData().getDatabaseProductName(), is("H2")); + } + assertThat(testBean, notNullValue()); + assertThat(testBean.getName(), is("Jetty")); + } + + @Test + public void shouldBeEncodingDefined() throws Exception { + String body = JettyEmbeddedClientTestCase.readAllAndClose(new URL(encodingUrl, MyEncodingServlet.URL_PATTERN).openStream()); + + assertThat( + "Should contains ISO-8859-1", + body, + StringContains.containsStringIgnoringCase("ISO-8859-1")); + } +} diff --git a/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyBean.java b/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyBean.java new file mode 100644 index 0000000..6a759ac --- /dev/null +++ b/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyBean.java @@ -0,0 +1,30 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2010, Red Hat Middleware LLC, and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * 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 org.jboss.arquillian.container.jetty.embedded_12_ee11; + +import jakarta.annotation.Resource; +import jakarta.enterprise.context.Dependent; + +@Dependent +public class MyBean { + @Resource(name = "name") + private String name; + + public String getName() { + return name; + } +} diff --git a/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyEncodingServlet.java b/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyEncodingServlet.java new file mode 100644 index 0000000..9348009 --- /dev/null +++ b/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyEncodingServlet.java @@ -0,0 +1,43 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2010, Red Hat Middleware LLC, and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * 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 org.jboss.arquillian.container.jetty.embedded_12_ee11; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +/** + * MyEncodingServlet + * + */ +public class MyEncodingServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + public static final String URL_PATTERN = "encoding"; + + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + response.setContentType("text/html"); + String encoding = response.getCharacterEncoding(); + response.getWriter().append(encoding); + } +} diff --git a/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyOtherBean.java b/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyOtherBean.java new file mode 100644 index 0000000..613cd4e --- /dev/null +++ b/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyOtherBean.java @@ -0,0 +1,11 @@ +package org.jboss.arquillian.container.jetty.embedded_12_ee11; + +import jakarta.enterprise.context.Dependent; + +@Dependent +public class MyOtherBean { + + public String ping() { + return MyOtherBean.class.getSimpleName(); + } +} diff --git a/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyOtherServlet.java b/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyOtherServlet.java new file mode 100644 index 0000000..8e794ab --- /dev/null +++ b/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyOtherServlet.java @@ -0,0 +1,30 @@ +package org.jboss.arquillian.container.jetty.embedded_12_ee11; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import jakarta.inject.Inject; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class MyOtherServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + public static final String URL_PATTERN = "Test2"; + + public static final String MESSAGE = "hey there"; + + @Inject + MyOtherBean bean; + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + assertThat(bean.ping(), is(MyOtherBean.class.getSimpleName())); + response.getWriter().append(MESSAGE); + } +} diff --git a/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyServlet.java b/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyServlet.java new file mode 100644 index 0000000..5c63922 --- /dev/null +++ b/jetty-embedded-12-ee11/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee11/MyServlet.java @@ -0,0 +1,43 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2010, Red Hat Middleware LLC, and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * 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 org.jboss.arquillian.container.jetty.embedded_12_ee11; + +import java.io.IOException; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * TestServlet + * + * @author Aslak Knutsen + */ +public class MyServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + public static final String URL_PATTERN = "Test"; + + public static final String MESSAGE = "hello"; + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + response.getWriter().append(MESSAGE); + } +} diff --git a/jetty-embedded-12-ee11/src/test/resources/arquillian.xml b/jetty-embedded-12-ee11/src/test/resources/arquillian.xml new file mode 100644 index 0000000..f2ffd74 --- /dev/null +++ b/jetty-embedded-12-ee11/src/test/resources/arquillian.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + 0 + + js application/js + txt text/plain + + + text/html iso-8859-1 + + true + + + relativeRedirectAllowed false + + + + + + + 0 + true + src/test/resources/keystore.p12 + storepwd + + + + + diff --git a/jetty-embedded-12-ee11/src/test/resources/default.properties b/jetty-embedded-12-ee11/src/test/resources/default.properties new file mode 100644 index 0000000..cc7136f --- /dev/null +++ b/jetty-embedded-12-ee11/src/test/resources/default.properties @@ -0,0 +1,3 @@ +admin: admin,server-administrator,content-administrator,admin +other: other +guest: guest,read-only \ No newline at end of file diff --git a/jetty-embedded-12-ee11/src/test/resources/in-container-web.xml b/jetty-embedded-12-ee11/src/test/resources/in-container-web.xml new file mode 100644 index 0000000..8486902 --- /dev/null +++ b/jetty-embedded-12-ee11/src/test/resources/in-container-web.xml @@ -0,0 +1,20 @@ + + + + + name + java.lang.String + Jetty + + + + + type + java.lang.String + Remote + + + diff --git a/jetty-embedded-12-ee11/src/test/resources/jetty-env.xml b/jetty-embedded-12-ee11/src/test/resources/jetty-env.xml new file mode 100644 index 0000000..7986093 --- /dev/null +++ b/jetty-embedded-12-ee11/src/test/resources/jetty-env.xml @@ -0,0 +1,33 @@ + + + + + + + + + + type + Embedded + true + + + version + 6 + true + + + + + + jdbc/test + + + jdbc:h2:mem:test + sa + sa + + + true + + diff --git a/jetty-embedded-12-ee11/src/test/resources/keystore.p12 b/jetty-embedded-12-ee11/src/test/resources/keystore.p12 new file mode 100644 index 0000000..01a1aea Binary files /dev/null and b/jetty-embedded-12-ee11/src/test/resources/keystore.p12 differ diff --git a/jetty-embedded-12-ee11/src/test/resources/simplelogger.properties b/jetty-embedded-12-ee11/src/test/resources/simplelogger.properties new file mode 100644 index 0000000..6f28e9f --- /dev/null +++ b/jetty-embedded-12-ee11/src/test/resources/simplelogger.properties @@ -0,0 +1,6 @@ +org.slf4j.simpleLogger.defaultLogLevel=info +#org.slf4j.simpleLogger.log.org.eclipse.jetty.server=debug +#org.slf4j.simpleLogger.log.org.eclipse.jetty.annotations=debug +#org.slf4j.simpleLogger.log.org.eclipse.jetty=debug +#org.slf4j.simpleLogger.log.org.eclipse.jetty.ee11=debug + diff --git a/jetty-embedded-12-ee9/pom.xml b/jetty-embedded-12-ee9/pom.xml index fba52a4..c301de9 100644 --- a/jetty-embedded-12-ee9/pom.xml +++ b/jetty-embedded-12-ee9/pom.xml @@ -44,6 +44,11 @@ pom import
+ + jakarta.inject + jakarta.inject-api + 2.0.1 + diff --git a/jetty-embedded-12-ee9/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee9/ArquillianAppProvider.java b/jetty-embedded-12-ee9/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee9/ArquillianAppProvider.java index 2a1b3db..b72ba4c 100644 --- a/jetty-embedded-12-ee9/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee9/ArquillianAppProvider.java +++ b/jetty-embedded-12-ee9/src/main/java/org/jboss/arquillian/container/jetty/embedded_12_ee9/ArquillianAppProvider.java @@ -102,10 +102,7 @@ protected App createApp(final Archive archive) { final File exported; try { if (this.config.isUseArchiveNameAsContext()) { - Path tmpDirectory = Files.createTempDirectory("arquillian-jetty"); - Path archivePath = tmpDirectory.resolveSibling(archive.getName()); - Files.deleteIfExists(archivePath); - exported = Files.createFile(archivePath).toFile(); + exported = Files.createFile(EXPORT_DIR.toPath().resolve(archive.getName())).toFile(); exported.deleteOnExit(); } else { // If this method returns successfully then it is guaranteed that: diff --git a/jetty-embedded-12-ee9/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee9/JettyEmbeddedInContainerTestCase.java b/jetty-embedded-12-ee9/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee9/JettyEmbeddedInContainerTestCase.java index 9802abd..e69d2d3 100644 --- a/jetty-embedded-12-ee9/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee9/JettyEmbeddedInContainerTestCase.java +++ b/jetty-embedded-12-ee9/src/test/java/org/jboss/arquillian/container/jetty/embedded_12_ee9/JettyEmbeddedInContainerTestCase.java @@ -62,7 +62,7 @@ public static WebArchive getTestArchive() { //.addClass(JettyEmbeddedConfiguration.class) .addAsLibraries( Maven.configureResolver() - .workOffline() + //.workOffline() .loadPomFromFile("pom.xml") .resolve("org.jboss.weld.servlet:weld-servlet-core") .withTransitivity() diff --git a/jetty-embedded-9/src/main/java/org/jboss/arquillian/container/jetty/embedded_9/ArquillianAppProvider.java b/jetty-embedded-9/src/main/java/org/jboss/arquillian/container/jetty/embedded_9/ArquillianAppProvider.java index c432709..eebf3c0 100644 --- a/jetty-embedded-9/src/main/java/org/jboss/arquillian/container/jetty/embedded_9/ArquillianAppProvider.java +++ b/jetty-embedded-9/src/main/java/org/jboss/arquillian/container/jetty/embedded_9/ArquillianAppProvider.java @@ -93,10 +93,7 @@ protected App createApp(final Archive archive) { final File exported; try { if (this.config.isUseArchiveNameAsContext()) { - Path tmpDirectory = Files.createTempDirectory("arquillian-jetty"); - Path archivePath = tmpDirectory.resolveSibling(archive.getName()); - Files.deleteIfExists(archivePath); - exported = Files.createFile(archivePath).toFile(); + exported = Files.createFile(EXPORT_DIR.toPath().resolve(archive.getName())).toFile(); exported.deleteOnExit(); } else { // If this method returns successfully then it is guaranteed that: diff --git a/pom.xml b/pom.xml index 670db78..dd1773e 100644 --- a/pom.xml +++ b/pom.xml @@ -50,7 +50,8 @@ 1.8 8 - 12.0.9 + 12.0.15 + 12.1.0.alpha0 @@ -228,6 +229,7 @@ jetty-embedded-9 jetty-embedded-10 jetty-embedded-11 + jetty-embedded-12-ee11 jetty-embedded-12-ee10 jetty-embedded-12-ee9 jetty-common