org.jboss.as.arquillian.container.CommonDeployableContainer Maven / Gradle / Ivy
/*
* Copyright The WildFly Authors
* SPDX-License-Identifier: Apache-2.0
*/
package org.jboss.as.arquillian.container;
import static org.jboss.as.arquillian.container.Authentication.getCallbackHandler;
import java.io.IOException;
import java.net.URI;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicReference;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
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.ProtocolMetaData;
import org.jboss.arquillian.container.spi.context.annotation.ContainerScoped;
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.as.controller.client.ModelControllerClient;
import org.jboss.as.controller.client.ModelControllerClientConfiguration;
import org.jboss.as.controller.client.helpers.ClientConstants;
import org.jboss.as.controller.client.helpers.DelegatingModelControllerClient;
import org.jboss.as.controller.client.helpers.Operations;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.jboss.logging.Logger;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.descriptor.api.Descriptor;
import org.wildfly.plugin.tools.ContainerDescription;
import org.wildfly.plugin.tools.server.ServerManager;
import org.wildfly.plugin.tools.server.StandaloneManager;
/**
* A JBossAS deployable container
*
* @author [email protected]
* @since 17-Nov-2010
*/
public abstract class CommonDeployableContainer implements DeployableContainer {
private static final String JBOSS_URL_PKG_PREFIX = "org.jboss.ejb.client.naming";
private static final String READ_OPERATION_DESCRIPTION_OPERATION = "read-operation-description";
private T containerConfig;
@Inject
@ContainerScoped
private InstanceProducer managementClientProducer;
@Inject
@ContainerScoped
private InstanceProducer archiveDeployer;
@Inject
@ApplicationScoped
private InstanceProducer jndiContext;
@Inject
@ContainerScoped
// Protected scope so the CommonManagedDeployableContainer can set it as well
protected InstanceProducer serverManagerProducer;
private final StandaloneDelegateProvider mccProvider = new StandaloneDelegateProvider();
private ManagementClient managementClient = null;
private ContainerDescription containerDescription = null;
private URI authenticationConfig = null;
@Override
public ProtocolDescription getDefaultProtocol() {
return new ProtocolDescription("Servlet 5.0");
}
@Override
public void setup(T config) {
containerConfig = config;
final String authenticationConfig = containerConfig.getAuthenticationConfig();
// Check for an Elytron configuration
if (authenticationConfig != null) {
this.authenticationConfig = URI.create(authenticationConfig);
}
final ManagementClient client = new ManagementClient(new DelegatingModelControllerClient(mccProvider), containerConfig);
managementClient = client;
managementClientProducer.set(client);
archiveDeployer.set(new ArchiveDeployer(client));
}
@Override
public final void start() throws LifecycleException {
// Create a client configuration builder from the container configuration
final ModelControllerClientConfiguration.Builder clientConfigBuilder = new ModelControllerClientConfiguration.Builder()
.setProtocol(containerConfig.getManagementProtocol())
.setHostName(containerConfig.getManagementAddress())
.setPort(containerConfig.getManagementPort())
.setAuthenticationConfigUri(authenticationConfig);
// only "copy" the timeout if one was set.
final int connectionTimeout = containerConfig.getConnectionTimeout();
if (connectionTimeout > 0) {
clientConfigBuilder.setConnectionTimeout(connectionTimeout);
}
// Check for username and password authentication
if (containerConfig.getUsername() != null) {
Authentication.username = containerConfig.getUsername();
Authentication.password = containerConfig.getPassword();
clientConfigBuilder.setHandler(getCallbackHandler());
}
mccProvider.setDelegate(ModelControllerClient.Factory.create(clientConfigBuilder.build()));
// If we are not a CommonManagedDeployableContainer we still need the ServerManager
if (!(this instanceof CommonManagedDeployableContainer)) {
// Set up the server manager attempting to discover the process for monitoring purposes. We need the
// server manager regardless of whether we are in charge of the lifecycle or not.
final StandaloneManager serverManager = ServerManager.builder()
.client(getManagementClient().getControllerClient())
// Note this won't work on Windows, but should work on other platforms
.process(ServerManager.findProcess().orElse(null))
.standalone();
serverManagerProducer.set(new ArquillianServerManager(serverManager));
}
try {
final Properties jndiProps = new Properties();
jndiProps.setProperty(Context.URL_PKG_PREFIXES, JBOSS_URL_PKG_PREFIX);
jndiContext.set(new InitialContext(jndiProps));
} catch (final NamingException ne) {
throw new LifecycleException("Could not set JNDI Naming Context", ne);
}
try {
startInternal();
} catch (LifecycleException e) {
safeCloseClient();
throw e;
}
}
protected abstract void startInternal() throws LifecycleException;
@Override
public final void stop() throws LifecycleException {
try {
stopInternal(null);
} finally {
safeCloseClient();
}
}
public final void stop(Integer timeout) throws LifecycleException {
try {
stopInternal(timeout);
} finally {
safeCloseClient();
}
}
protected abstract void stopInternal(Integer timeout) throws LifecycleException;
/**
* Returns a description for the running container. If the container has not been started {@code null} will be
* returned.
*
* @return the description for the running container or {@code null} if the container has not yet been started
*/
public ContainerDescription getContainerDescription() {
if (containerDescription == null) {
try {
final ManagementClient client = getManagementClient();
// The management client should be set when the container is started
if (client == null)
return null;
containerDescription = ContainerDescription.lookup(client.getControllerClient());
} catch (IOException e) {
Logger.getLogger(getClass()).warn("Failed to lookup the container description.", e);
containerDescription = new ContainerDescription() {
@Override
public String getProductName() {
return "WildFly";
}
@Override
public String getProductVersion() {
return "";
}
@Override
public String getReleaseVersion() {
return "";
}
@Override
public String getLaunchType() {
return "UNKNOWN";
}
@Override
public boolean isDomain() {
return false;
}
};
}
}
return containerDescription;
}
protected T getContainerConfiguration() {
return containerConfig;
}
protected ManagementClient getManagementClient() {
return managementClient;
}
protected ModelControllerClient getModelControllerClient() {
if (managementClient == null) {
throw new IllegalStateException("The container has not been setup. The client is not usable.");
}
return managementClient.getControllerClient();
}
/**
* Checks to see if the attribute is a valid attribute for the operation. This is useful to determine if the running
* container supports an attribute for the version running.
*
*
* This is the same as executing {@link #isOperationAttributeSupported(ModelNode, String, String)
* isOperationAttriubuteSupported(null, operationName, attributeName)}
*
*
* @param operationName the operation name
* @param attributeName the attribute name
*
* @return {@code true} if the attribute is supported or {@code false} if the attribute was not found on the
* operation description
*
* @throws IOException if an error occurs while attempting to execute the operation
* @throws IllegalStateException if the operation fails
*/
protected boolean isOperationAttributeSupported(final String operationName, final String attributeName) throws IOException {
return isOperationAttributeSupported(null, operationName, attributeName);
}
/**
* Checks to see if the attribute is a valid attribute for the operation. This is useful to determine if the running
* container supports an attribute for the version running.
*
* @param address the address or {@code null} for the root resource
* @param operationName the operation name
* @param attributeName the attribute name
*
* @return {@code true} if the attribute is supported or {@code false} if the attribute was not found on the
* operation description
*
* @throws IOException if an error occurs while attempting to execute the operation
* @throws IllegalStateException if the operation fails
*/
protected boolean isOperationAttributeSupported(final ModelNode address, final String operationName,
final String attributeName) throws IOException {
final ModelControllerClient client = getModelControllerClient();
final ModelNode op;
if (address == null) {
op = Operations.createOperation(READ_OPERATION_DESCRIPTION_OPERATION);
} else {
op = Operations.createOperation(READ_OPERATION_DESCRIPTION_OPERATION, address);
}
op.get(ClientConstants.NAME).set(operationName);
final ModelNode result = client.execute(op);
if (Operations.isSuccessfulOutcome(result)) {
final ModelNode params = Operations.readResult(result).get("request-properties");
return params.keys().contains(attributeName);
}
final String msg;
if (address == null) {
msg = String.format("Failed to determine if attribute %s is supported for operation %s. %s", attributeName,
operationName, Operations.getFailureDescription(result));
} else {
msg = String.format("Failed to determine if attribute %s is supported for operation %s:%s. %s", attributeName,
addressToCliString(address), operationName, Operations.getFailureDescription(result));
}
throw new IllegalStateException(msg);
}
@Override
public ProtocolMetaData deploy(Archive> archive) throws DeploymentException {
String runtimeName = archiveDeployer.get().deploy(archive);
return getManagementClient().getProtocolMetaData(runtimeName);
}
@Override
public void undeploy(Archive> archive) throws DeploymentException {
archiveDeployer.get().undeploy(archive.getName());
}
@Override
public void deploy(Descriptor descriptor) throws DeploymentException {
throw new UnsupportedOperationException("not implemented");
}
@Override
public void undeploy(Descriptor descriptor) throws DeploymentException {
throw new UnsupportedOperationException("not implemented");
}
private void safeCloseClient() {
try {
// Reset the client, this should close the internal resources and setup reinitialization
ManagementClient client = managementClient;
if (client != null) {
client.reset();
}
} catch (final Exception e) {
Logger.getLogger(getClass()).warn("Caught exception closing ManagementClient", e);
} finally {
mccProvider.setDelegate(null);
}
}
private static String addressToCliString(final ModelNode address) {
if (address == null) {
return "";
}
final StringBuilder result = new StringBuilder(32);
for (Property property : address.asPropertyList()) {
result.append('/').append(property.getName()).append('=').append(property.getValue().asString());
}
return result.toString();
}
private static class StandaloneDelegateProvider implements DelegatingModelControllerClient.DelegateProvider {
private final AtomicReference delegate;
private StandaloneDelegateProvider() {
this.delegate = new AtomicReference<>();
}
void setDelegate(final ModelControllerClient client) {
delegate.set(client);
}
@Override
public ModelControllerClient getDelegate() {
final ModelControllerClient result = delegate.get();
if (result == null) {
throw new IllegalStateException("The container has not been started. The client is not usable.");
}
return result;
}
}
}