Skip to content

Commit

Permalink
Add support for specifying a custom mapper implementation for a Deplo…
Browse files Browse the repository at this point in the history
…yableContainer. (#553)

Fixes #552
  • Loading branch information
starksm64 authored Jun 25, 2024
1 parent 2bc6e1c commit 9e80313
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -47,25 +48,25 @@
* @author <a href="mailto:aslak@redhat.com">Aslak Knutsen</a>
* @version $Revision: $
*/
public class ContainerImpl implements Container {
public class ContainerImpl<T extends ContainerConfiguration> implements Container<T> {
@Inject
private Event<ContainerEvent> event;

@Inject
@ContainerScoped
private InstanceProducer<Container> containerProducer;
private InstanceProducer<Container<T>> containerProducer;

@Inject
private Instance<ServiceLoader> serviceLoader;

private DeployableContainer<?> deployableContainer;
private DeployableContainer<T> 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<T> deployableContainer, ContainerDef containerConfiguration) {
Validate.notNull(name, "Name must be specified");
Validate.notNull(deployableContainer, "DeployableContainer must be specified");
Validate.notNull(containerConfiguration, "ConfigurationConfiguration must be specified");
Expand All @@ -87,7 +88,7 @@ public String getName() {
* @see org.jboss.arquillian.container.impl.ContainerT#getDeployableContainer()
*/
@Override
public DeployableContainer<?> getDeployableContainer() {
public DeployableContainer<T> getDeployableContainer() {
return deployableContainer;
}

Expand All @@ -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<T> configClass = (Class<T>) deployableContainer.getConfigurationClass();
T config = SecurityActions.newInstance(configClass, new Class<?>[0], new Object[0]);
ConfigurationMapper<T> mapper = deployableContainer.getConfigurationMapper();
if(mapper != null) {
mapper.populateConfiguration(config, containerConfiguration);
} else {
MapObject.populate(config, containerConfiguration.getContainerProperties());
}
config.validate();
return config;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<CustomContainerConfiguration> 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<CustomContainerConfiguration> 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";
Expand Down Expand Up @@ -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<CustomContainerConfiguration> {
@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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
* @author <a href="mailto:aslak@redhat.com">Aslak Knutsen</a>
* @version $Revision: $
*/
public interface Container {
public interface Container<T extends ContainerConfiguration> {

/**
* @return the name
Expand All @@ -40,7 +40,7 @@ public interface Container {
/**
* @return the deployableContainer
*/
DeployableContainer<?> getDeployableContainer();
DeployableContainer<T> getDeployableContainer();

/**
* @return the containerConfiguration
Expand All @@ -50,7 +50,7 @@ public interface Container {
/**
* @return the configuration
*/
ContainerConfiguration createDeployableConfiguration() throws Exception;
T createDeployableConfiguration() throws Exception;

boolean hasProtocolConfiguration(ProtocolDescription description);

Expand All @@ -73,4 +73,4 @@ public interface Container {
enum State {
SETUP, SETUP_FAILED, STARTED, STARTED_FAILED, STOPPED, STOPPED_FAILED, KILLED, KILLED_FAILED;
}
}
}
Original file line number Diff line number Diff line change
@@ -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<T>} 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 <T> the type of ContainerConfiguration
*/
public interface ConfigurationMapper<T extends ContainerConfiguration> {
/**
*
* @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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,17 @@ public interface DeployableContainer<T extends ContainerConfiguration> {
// ControllableContainer
Class<T> 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) {
}

Expand Down

0 comments on commit 9e80313

Please sign in to comment.