diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 000000000..9dab07da5
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,93 @@
+name: Java CI with Maven
+
+on:
+ push:
+ branches: [ "master" ]
+ pull_request:
+ branches: [ "master" ]
+
+jobs:
+
+ arquillian-build-jdk8:
+ name: Integration - JDK 8
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ outputs:
+ SNAPSHOT_VERSION: ${{ steps.arq-version.outputs.SNAPSHOT_VERSION }}
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-java@v4
+ name: JDK 8 setup
+ with:
+ java-version: 8
+ distribution: 'temurin'
+ cache: maven
+
+ - name: Build with Maven
+ run: mvn clean -B install
+
+ - name: Version save
+ id: arq-version
+ run: |
+ VERSION=$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec)
+ echo "SNAPSHOT_VERSION=$VERSION" >> $GITHUB_OUTPUT
+ echo "Arquillian version: $VERSION"
+
+ - name: Artifact upload
+ uses: actions/upload-artifact@v4
+ with:
+ name: arquillian
+ path: ~/.m2/repository/org/jboss/
+
+ arquillian-build-jdk11:
+ name: Integration - JDK 11
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-java@v4
+ name: JDK 11 setup
+ with:
+ java-version: 11
+ distribution: 'temurin'
+ cache: maven
+
+ - name: Build with Maven
+ run: mvn clean -B install
+
+ integration-wildfly-job:
+ runs-on: ubuntu-latest
+ name: Integration verification for WildFly
+ needs: arquillian-build-jdk8
+ timeout-minutes: 300
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/download-artifact@v4
+ with:
+ name: arquillian
+ path: ~/.m2/repository/org/jboss
+
+ - uses: actions/setup-java@v4
+ name: JDK 17 setup
+ with:
+ java-version: 17
+ distribution: 'temurin'
+ cache: maven
+
+ - uses: actions/checkout@v4
+ name: Checkout WildFly
+ with:
+ repository: wildfly/wildfly
+
+ - name: WildFly integration
+ env:
+ SNAPSHOT_VERSION: ${{ needs.arquillian-build-jdk8.outputs.SNAPSHOT_VERSION }}
+ run: |
+ mvn clean install -Dquickly -Dversion.org.jboss.arquillian.core="$SNAPSHOT_VERSION"
+ mvn clean verify -f testsuite/integration -DallTests -Dversion.org.jboss.arquillian.core="$SNAPSHOT_VERSION"
diff --git a/container/impl-base/src/main/java/org/jboss/arquillian/container/impl/ContainerImpl.java b/container/impl-base/src/main/java/org/jboss/arquillian/container/impl/ContainerImpl.java
index dbe3fe3e4..f1cf57978 100644
--- a/container/impl-base/src/main/java/org/jboss/arquillian/container/impl/ContainerImpl.java
+++ b/container/impl-base/src/main/java/org/jboss/arquillian/container/impl/ContainerImpl.java
@@ -20,6 +20,7 @@
import org.jboss.arquillian.config.descriptor.api.ProtocolDef;
import org.jboss.arquillian.container.spi.Container;
import org.jboss.arquillian.container.spi.ServerKillProcessor;
+import org.jboss.arquillian.container.spi.client.container.ConfigurationMapper;
import org.jboss.arquillian.container.spi.client.container.ContainerConfiguration;
import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
import org.jboss.arquillian.container.spi.client.container.LifecycleException;
@@ -47,25 +48,25 @@
* @author Aslak Knutsen
* @version $Revision: $
*/
-public class ContainerImpl implements Container {
+public class ContainerImpl implements Container {
@Inject
private Event event;
@Inject
@ContainerScoped
- private InstanceProducer containerProducer;
+ private InstanceProducer> containerProducer;
@Inject
private Instance serviceLoader;
- private DeployableContainer> deployableContainer;
+ private DeployableContainer deployableContainer;
private String name;
private State state = State.STOPPED;
private Throwable failureCause;
private ContainerDef containerConfiguration;
- public ContainerImpl(String name, DeployableContainer> deployableContainer, ContainerDef containerConfiguration) {
+ public ContainerImpl(String name, DeployableContainer deployableContainer, ContainerDef containerConfiguration) {
Validate.notNull(name, "Name must be specified");
Validate.notNull(deployableContainer, "DeployableContainer must be specified");
Validate.notNull(containerConfiguration, "ConfigurationConfiguration must be specified");
@@ -87,7 +88,7 @@ public String getName() {
* @see org.jboss.arquillian.container.impl.ContainerT#getDeployableContainer()
*/
@Override
- public DeployableContainer> getDeployableContainer() {
+ public DeployableContainer getDeployableContainer() {
return deployableContainer;
}
@@ -103,10 +104,15 @@ public ContainerDef getContainerConfiguration() {
* @see org.jboss.arquillian.container.impl.ContainerT#createDeployableConfiguration()
*/
@Override
- public ContainerConfiguration createDeployableConfiguration() throws Exception {
- ContainerConfiguration config = SecurityActions.newInstance(
- deployableContainer.getConfigurationClass(), new Class>[0], new Object[0]);
- MapObject.populate(config, containerConfiguration.getContainerProperties());
+ public T createDeployableConfiguration() throws Exception {
+ Class configClass = (Class) deployableContainer.getConfigurationClass();
+ T config = SecurityActions.newInstance(configClass, new Class>[0], new Object[0]);
+ ConfigurationMapper mapper = deployableContainer.getConfigurationMapper();
+ if(mapper != null) {
+ mapper.populateConfiguration(config, containerConfiguration);
+ } else {
+ MapObject.populate(config, containerConfiguration.getContainerProperties());
+ }
config.validate();
return config;
}
diff --git a/container/impl-base/src/test/java/org/jboss/arquillian/container/impl/ContainerRegistryTestCase.java b/container/impl-base/src/test/java/org/jboss/arquillian/container/impl/ContainerRegistryTestCase.java
index 8ab81eac2..6e5f85cf0 100644
--- a/container/impl-base/src/test/java/org/jboss/arquillian/container/impl/ContainerRegistryTestCase.java
+++ b/container/impl-base/src/test/java/org/jboss/arquillian/container/impl/ContainerRegistryTestCase.java
@@ -16,10 +16,12 @@
*/
package org.jboss.arquillian.container.impl;
+import org.jboss.arquillian.config.descriptor.api.ContainerDef;
import org.jboss.arquillian.config.descriptor.impl.ContainerDefImpl;
import org.jboss.arquillian.container.spi.ConfigurationException;
import org.jboss.arquillian.container.spi.Container;
import org.jboss.arquillian.container.spi.ContainerRegistry;
+import org.jboss.arquillian.container.spi.client.container.ConfigurationMapper;
import org.jboss.arquillian.container.spi.client.container.ContainerConfiguration;
import org.jboss.arquillian.container.spi.client.container.DeployableContainer;
import org.jboss.arquillian.container.spi.client.deployment.TargetDescription;
@@ -140,6 +142,46 @@ public void shouldBeAbleToCreatePrivateContainerConfiguration() throws Exception
((PrivateDummyContainerConfiguration) container.createDeployableConfiguration()).getProperty());
}
+ @Test
+ public void shouldBeAbleToCreateContainerConfigurationCustomMapper() throws Exception {
+ ServiceLoader serviceLoader = Mockito.mock(ServiceLoader.class);
+ DeployableContainer deployableContainer =
+ Mockito.mock(DeployableContainer.class);
+
+ Mockito.when(serviceLoader.onlyOne(Mockito.same(DeployableContainer.class))).thenReturn(deployableContainer);
+ Mockito.when(deployableContainer.getConfigurationClass()).thenReturn(CustomContainerConfiguration.class);
+ Mockito.when(deployableContainer.getConfigurationMapper()).thenReturn(new CustomMapper());
+
+ String name = "custom-container";
+ String prop = "prop-value";
+ String[] hosts = {"host1", "host2", "host3"};
+
+ ContainerRegistry registry = new LocalContainerRegistry(injector.get());
+ ContainerDefImpl containerDef = new ContainerDefImpl(ARQUILLIAN_XML);
+ containerDef.setContainerName(name);
+ containerDef.property("property", prop);
+ containerDef.property("hosts", "host1,host2,host3");
+
+ registry.create(containerDef, serviceLoader);
+
+ Container container = registry.getContainer(new TargetDescription(name));
+
+ Assert.assertEquals(
+ "Verify that the only registered container is returned as default",
+ name, container.getName());
+
+ CustomContainerConfiguration config = container.createDeployableConfiguration();
+ Assert.assertEquals(
+ "Verify that the custom configuration 'property' was populated",
+ prop,
+ config.getProperty());
+
+ Assert.assertArrayEquals(
+ "Verify that the custom configuration 'hosts' was populated",
+ hosts,
+ config.getHosts());
+ }
+
@Test
public void shouldBeAbleToSpecifyTarget() throws Exception {
String name = "some-name";
@@ -197,4 +239,23 @@ private static class PrivateDummyContainerConfiguration extends DummyContainerCo
private PrivateDummyContainerConfiguration() {
}
}
+ private static class CustomContainerConfiguration extends DummyContainerConfiguration {
+ private String[] hosts;
+ public String[] getHosts() {
+ return hosts;
+ }
+ public void setHosts(String[] hosts) {
+ this.hosts = hosts;
+ }
+ }
+ private static class CustomMapper implements ConfigurationMapper {
+ @Override
+ public void populateConfiguration(CustomContainerConfiguration containerConfiguration, ContainerDef definition) {
+ String property = definition.getContainerProperty("property");
+ containerConfiguration.setProperty(property);
+ String hostsString = definition.getContainerProperty("hosts");
+ String[] hosts = hostsString.split(",");
+ containerConfiguration.setHosts(hosts);
+ }
+ }
}
diff --git a/container/spi/src/main/java/org/jboss/arquillian/container/spi/Container.java b/container/spi/src/main/java/org/jboss/arquillian/container/spi/Container.java
index 59f2b79ab..8ef4c6d37 100644
--- a/container/spi/src/main/java/org/jboss/arquillian/container/spi/Container.java
+++ b/container/spi/src/main/java/org/jboss/arquillian/container/spi/Container.java
@@ -30,7 +30,7 @@
* @author Aslak Knutsen
* @version $Revision: $
*/
-public interface Container {
+public interface Container {
/**
* @return the name
@@ -40,7 +40,7 @@ public interface Container {
/**
* @return the deployableContainer
*/
- DeployableContainer> getDeployableContainer();
+ DeployableContainer getDeployableContainer();
/**
* @return the containerConfiguration
@@ -50,7 +50,7 @@ public interface Container {
/**
* @return the configuration
*/
- ContainerConfiguration createDeployableConfiguration() throws Exception;
+ T createDeployableConfiguration() throws Exception;
boolean hasProtocolConfiguration(ProtocolDescription description);
@@ -73,4 +73,4 @@ public interface Container {
enum State {
SETUP, SETUP_FAILED, STARTED, STARTED_FAILED, STOPPED, STOPPED_FAILED, KILLED, KILLED_FAILED;
}
-}
\ No newline at end of file
+}
diff --git a/container/spi/src/main/java/org/jboss/arquillian/container/spi/client/container/ConfigurationMapper.java b/container/spi/src/main/java/org/jboss/arquillian/container/spi/client/container/ConfigurationMapper.java
new file mode 100644
index 000000000..d0d715e85
--- /dev/null
+++ b/container/spi/src/main/java/org/jboss/arquillian/container/spi/client/container/ConfigurationMapper.java
@@ -0,0 +1,36 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2024 Red Hat Inc. and/or its affiliates and other 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.spi.client.container;
+
+import org.jboss.arquillian.config.descriptor.api.ContainerDef;
+
+/**
+ * An interface that {@link DeployableContainer} can use to control how the mapping
+ * from the externalized container definition, as found in an arquillian.xml file
+ * for example, is applied to the {@link ContainerConfiguration} instances
+ *
+ * @param the type of ContainerConfiguration
+ */
+public interface ConfigurationMapper {
+ /**
+ *
+ * @param containerConfiguration - the deployable container configuration instance to populate
+ * @param definition - the container definition from the ArquillianDescriptor that
+ * was parsed
+ */
+ void populateConfiguration(T containerConfiguration, ContainerDef definition);
+}
diff --git a/container/spi/src/main/java/org/jboss/arquillian/container/spi/client/container/DeployableContainer.java b/container/spi/src/main/java/org/jboss/arquillian/container/spi/client/container/DeployableContainer.java
index 9e4425a91..513356a7b 100644
--- a/container/spi/src/main/java/org/jboss/arquillian/container/spi/client/container/DeployableContainer.java
+++ b/container/spi/src/main/java/org/jboss/arquillian/container/spi/client/container/DeployableContainer.java
@@ -37,6 +37,17 @@ public interface DeployableContainer {
// ControllableContainer
Class getConfigurationClass();
+ /**
+ * Provide a mapping instance that takes the {@link org.jboss.arquillian.config.descriptor.api.ContainerDef}
+ * for the arquillian.xml or other configured descriptor and populates the container configuration
+ * instance from the descriptor values.
+ *
+ * @return A possibly null ConfigurationMapper. If null, the default logic to map from string based
+ * properties as implemented in org.jboss.arquillian.container.impl.MapObject will be used.
+ */
+ default ConfigurationMapper getConfigurationMapper() {
+ return null;
+ }
default void setup(T configuration) {
}
diff --git a/protocols/servlet/src/main/java/org/jboss/arquillian/protocol/servlet/runner/ServletTestRunner.java b/protocols/servlet/src/main/java/org/jboss/arquillian/protocol/servlet/runner/ServletTestRunner.java
index 01fd0141b..1bb1d4624 100644
--- a/protocols/servlet/src/main/java/org/jboss/arquillian/protocol/servlet/runner/ServletTestRunner.java
+++ b/protocols/servlet/src/main/java/org/jboss/arquillian/protocol/servlet/runner/ServletTestRunner.java
@@ -201,7 +201,9 @@ private void writeObject(Object object, HttpServletResponse response) {
try {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
} catch (Exception e2) {
- throw new RuntimeException("Could not write to output", e2);
+ RuntimeException runtimeException = new RuntimeException("Could not write to output", e2);
+ runtimeException.addSuppressed(e);
+ throw runtimeException;
}
}
}