diff --git a/controller-client/src/main/java/org/jboss/as/controller/client/LocalModelControllerClient.java b/controller-client/src/main/java/org/jboss/as/controller/client/LocalModelControllerClient.java new file mode 100644 index 00000000000..75bc498c35c --- /dev/null +++ b/controller-client/src/main/java/org/jboss/as/controller/client/LocalModelControllerClient.java @@ -0,0 +1,54 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.controller.client; + +import java.io.IOException; + +import org.jboss.as.controller.client.logging.ControllerClientLogger; +import org.jboss.dmr.ModelNode; + +/** + * A {@link ModelControllerClient} subinterface that does not throw {@link java.io.IOException}. + * Used for clients that operate in the same VM as the target {@code ModelController} and hence + * are not subject to IO failures associated with remote calls. + * + * @author Brian Stansberry + */ +public interface LocalModelControllerClient extends ModelControllerClient { + + @Override + default ModelNode execute(ModelNode operation) { + return execute(Operation.Factory.create(operation), OperationMessageHandler.DISCARD); + } + + @Override + default ModelNode execute(Operation operation) { + return execute(operation, OperationMessageHandler.DISCARD); + } + + @Override + default ModelNode execute(ModelNode operation, OperationMessageHandler messageHandler) { + return execute(Operation.Factory.create(operation), messageHandler); + } + + @Override + default ModelNode execute(Operation operation, OperationMessageHandler messageHandler) { + OperationResponse or = executeOperation(operation, messageHandler); + ModelNode result = or.getResponseNode(); + try { + or.close(); + } catch (IOException e) { + ControllerClientLogger.ROOT_LOGGER.debugf(e, "Failed closing response to %s", operation); + } + return result; + } + + @Override + OperationResponse executeOperation(Operation operation, OperationMessageHandler messageHandler); + + @Override + void close(); +} diff --git a/controller/pom.xml b/controller/pom.xml index 0abc3995bba..0a399392742 100644 --- a/controller/pom.xml +++ b/controller/pom.xml @@ -46,6 +46,10 @@ org.wildfly.core wildfly-core-security + + org.wildfly.core + wildfly-embedded + org.wildfly.security diff --git a/controller/src/main/java/org/jboss/as/controller/AbstractControllerService.java b/controller/src/main/java/org/jboss/as/controller/AbstractControllerService.java index de5385e5359..0fcbde3f46c 100644 --- a/controller/src/main/java/org/jboss/as/controller/AbstractControllerService.java +++ b/controller/src/main/java/org/jboss/as/controller/AbstractControllerService.java @@ -73,6 +73,7 @@ import org.jboss.msc.service.StartContext; import org.jboss.msc.service.StartException; import org.jboss.msc.service.StopContext; +import org.wildfly.core.embedded.spi.EmbeddedModelControllerClientFactory; import org.wildfly.security.manager.WildFlySecurityManager; /** @@ -336,7 +337,8 @@ bootErrorCollector, createExtraValidationStepHandler(), capabilityRegistry, getP rootResourceRegistration.registerCapability(NOTIFICATION_REGISTRY_CAPABILITY); final ServiceName clientFactorySN = CLIENT_FACTORY_CAPABILITY.getCapabilityServiceName(); final ServiceBuilder clientFactorySB = target.addService(clientFactorySN); - clientFactorySB.setInstance(new SimpleService(clientFactorySB.provides(clientFactorySN), clientFactory)); + clientFactorySB.setInstance(new SimpleService(clientFactorySB.provides(clientFactorySN, + EmbeddedModelControllerClientFactory.SERVICE_NAME), clientFactory)); clientFactorySB.install(); final ServiceName notifyRegistrySN = NOTIFICATION_REGISTRY_CAPABILITY.getCapabilityServiceName(); final ServiceBuilder notifyRegistrySB = target.addService(notifyRegistrySN); diff --git a/controller/src/main/java/org/jboss/as/controller/ControlledProcessState.java b/controller/src/main/java/org/jboss/as/controller/ControlledProcessState.java index 6b69761b481..8464cf90463 100644 --- a/controller/src/main/java/org/jboss/as/controller/ControlledProcessState.java +++ b/controller/src/main/java/org/jboss/as/controller/ControlledProcessState.java @@ -8,6 +8,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicStampedReference; +import org.wildfly.core.embedded.spi.EmbeddedProcessState; + /** * The overall state of a process that is being managed by a {@link ModelController}. * @@ -19,11 +21,11 @@ public enum State { /** * The process is starting and its runtime state is being made consistent with its persistent configuration. */ - STARTING("starting", false), + STARTING(EmbeddedProcessState.STARTING, false), /** * The process is started, is running normally and has a runtime state consistent with its persistent configuration. */ - RUNNING("running", true), + RUNNING(EmbeddedProcessState.RUNNING, true), /** * The process requires a stop and re-start of its root service (but not a full process restart) in order to * ensure stable operation and/or to bring its running state in line with its persistent configuration. A @@ -32,22 +34,22 @@ public enum State { * handle external requests is similar to that of a full process restart. However, a reload can execute more * quickly than a full process restart. */ - RELOAD_REQUIRED("reload-required", true), + RELOAD_REQUIRED(EmbeddedProcessState.RELOAD_REQUIRED, true), /** * The process must be terminated and replaced with a new process in order to ensure stable operation and/or to bring * the running state in line with the persistent configuration. */ - RESTART_REQUIRED("restart-required", true), + RESTART_REQUIRED(EmbeddedProcessState.RESTART_REQUIRED, true), /** The process is stopping. */ - STOPPING("stopping", false), + STOPPING(EmbeddedProcessState.STOPPING, false), /** The process is stopped */ - STOPPED("stopped", false); + STOPPED(EmbeddedProcessState.STOPPED, false); - private final String stringForm; + private final EmbeddedProcessState embeddedForm; private final boolean running; - State(final String stringForm, final boolean running) { - this.stringForm = stringForm; + State(final EmbeddedProcessState embeddedForm, final boolean running) { + this.embeddedForm = embeddedForm; this.running = running; } @@ -65,7 +67,11 @@ public boolean isRunning() { @Override public String toString() { - return stringForm; + return embeddedForm.toString(); + } + + EmbeddedProcessState getEmbeddedProcessState() { + return embeddedForm; } } @@ -81,8 +87,12 @@ public String toString() { private boolean restartRequiredOnStarting = false; public ControlledProcessState(final boolean reloadSupported) { + this(reloadSupported, false); + } + + public ControlledProcessState(final boolean reloadSupported, boolean embedded) { this.reloadSupported = reloadSupported; - service = new ControlledProcessStateService(State.STOPPED); + service = new ControlledProcessStateService(State.STOPPED, embedded); } public State getState() { diff --git a/controller/src/main/java/org/jboss/as/controller/ControlledProcessStateService.java b/controller/src/main/java/org/jboss/as/controller/ControlledProcessStateService.java index 76e9c65f74a..3480f48f1d1 100644 --- a/controller/src/main/java/org/jboss/as/controller/ControlledProcessStateService.java +++ b/controller/src/main/java/org/jboss/as/controller/ControlledProcessStateService.java @@ -13,6 +13,7 @@ import org.jboss.msc.service.ServiceBuilder; import org.jboss.msc.service.ServiceName; import org.jboss.msc.service.ServiceTarget; +import org.wildfly.core.embedded.spi.EmbeddedProcessState; /** * Exposes the current {@link ControlledProcessState.State} and allows services to register a listener for changes @@ -21,7 +22,7 @@ * @author Brian Stansberry (c) 2011 Red Hat Inc. * @author Richard Opalka */ -public class ControlledProcessStateService implements ProcessStateNotifier { +public class ControlledProcessStateService implements ProcessStateNotifier, org.wildfly.core.embedded.spi.ProcessStateNotifier { /** @deprecated use the 'org.wildfly.management.process-state-notifier' capability to obtain a {@link ProcessStateNotifier}*/ @Deprecated(forRemoval = true) @@ -41,7 +42,7 @@ public class ControlledProcessStateService implements ProcessStateNotifier { public static ProcessStateNotifier addService(ServiceTarget target, ControlledProcessState processState) { final ControlledProcessStateService notifier = processState.getService(); final ServiceBuilder sb = target.addService(); - Consumer consumer = sb.provides(INTERNAL_SERVICE_NAME, SERVICE_NAME); + Consumer consumer = sb.provides(INTERNAL_SERVICE_NAME, SERVICE_NAME, org.wildfly.core.embedded.spi.ProcessStateNotifier.SERVICE_NAME); sb.setInstance(Service.newInstance(consumer, notifier)); sb.install(); return notifier; @@ -49,10 +50,12 @@ public static ProcessStateNotifier addService(ServiceTarget target, ControlledPr private ControlledProcessState.State processState; private final PropertyChangeSupport changeSupport; + private final boolean embedded; - ControlledProcessStateService(ControlledProcessState.State initialState) { + ControlledProcessStateService(ControlledProcessState.State initialState, boolean embedded) { this.processState = initialState; - changeSupport = new PropertyChangeSupport(this); + this.changeSupport = new PropertyChangeSupport(this); + this.embedded = embedded; } /** @@ -65,6 +68,17 @@ public ControlledProcessState.State getCurrentState() { return processState; } + /** + * Returns the current process state. + * + * @return the current state + */ + @Override + public EmbeddedProcessState getEmbeddedProcessState() { + checkEmbedded(); + return processState.getEmbeddedProcessState(); + } + /** * Registers a listener for changes to the process state. * @@ -73,7 +87,7 @@ public ControlledProcessState.State getCurrentState() { @Override public void addPropertyChangeListener( PropertyChangeListener listener) { - changeSupport.addPropertyChangeListener(listener); + changeSupport.addPropertyChangeListener("currentState", listener); } /** @@ -84,12 +98,31 @@ public void addPropertyChangeListener( @Override public void removePropertyChangeListener( PropertyChangeListener listener) { - changeSupport.removePropertyChangeListener(listener); + changeSupport.removePropertyChangeListener("currentState", listener); + } + + @Override + public void addProcessStateListener(PropertyChangeListener listener) { + checkEmbedded(); + changeSupport.addPropertyChangeListener("embeddedState", listener); + } + + @Override + public void removeProcessStateListener(PropertyChangeListener listener) { + checkEmbedded(); + changeSupport.removePropertyChangeListener("embeddedState", listener); } synchronized void stateChanged(ControlledProcessState.State newState) { final ControlledProcessState.State oldState = processState; processState = newState; changeSupport.firePropertyChange("currentState", oldState, newState); + changeSupport.firePropertyChange("embeddedState", oldState.getEmbeddedProcessState(), newState.getEmbeddedProcessState()); + } + + private void checkEmbedded() { + if (!embedded) { + throw new IllegalStateException(); + } } } diff --git a/controller/src/main/java/org/jboss/as/controller/LocalModelControllerClient.java b/controller/src/main/java/org/jboss/as/controller/LocalModelControllerClient.java index ce6bbc6beba..9d9f5324c04 100644 --- a/controller/src/main/java/org/jboss/as/controller/LocalModelControllerClient.java +++ b/controller/src/main/java/org/jboss/as/controller/LocalModelControllerClient.java @@ -5,14 +5,7 @@ package org.jboss.as.controller; -import java.io.IOException; - import org.jboss.as.controller.client.ModelControllerClient; -import org.jboss.as.controller.client.Operation; -import org.jboss.as.controller.client.OperationMessageHandler; -import org.jboss.as.controller.client.OperationResponse; -import org.jboss.as.controller.logging.ControllerLogger; -import org.jboss.dmr.ModelNode; /** * A {@link ModelControllerClient} subinterface that does not throw {@link java.io.IOException}. @@ -21,38 +14,5 @@ * * @author Brian Stansberry */ -public interface LocalModelControllerClient extends ModelControllerClient { - - @Override - default ModelNode execute(ModelNode operation) { - return execute(Operation.Factory.create(operation), OperationMessageHandler.DISCARD); - } - - @Override - default ModelNode execute(Operation operation) { - return execute(operation, OperationMessageHandler.DISCARD); - } - - @Override - default ModelNode execute(ModelNode operation, OperationMessageHandler messageHandler) { - return execute(Operation.Factory.create(operation), messageHandler); - } - - @Override - default ModelNode execute(Operation operation, OperationMessageHandler messageHandler) { - OperationResponse or = executeOperation(operation, messageHandler); - ModelNode result = or.getResponseNode(); - try { - or.close(); - } catch (IOException e) { - ControllerLogger.MGMT_OP_LOGGER.debugf(e, "Failed closing response to %s", operation); - } - return result; - } - - @Override - OperationResponse executeOperation(Operation operation, OperationMessageHandler messageHandler); - - @Override - void close(); +public interface LocalModelControllerClient extends org.jboss.as.controller.client.LocalModelControllerClient { } diff --git a/controller/src/main/java/org/jboss/as/controller/ModelControllerClientFactoryImpl.java b/controller/src/main/java/org/jboss/as/controller/ModelControllerClientFactoryImpl.java index 26feed1dd88..6ff4417b7a1 100644 --- a/controller/src/main/java/org/jboss/as/controller/ModelControllerClientFactoryImpl.java +++ b/controller/src/main/java/org/jboss/as/controller/ModelControllerClientFactoryImpl.java @@ -37,6 +37,7 @@ import org.jboss.dmr.ModelNode; import org.jboss.threads.AsyncFuture; import org.jboss.threads.AsyncFutureTask; +import org.wildfly.core.embedded.spi.EmbeddedModelControllerClientFactory; import org.wildfly.security.auth.server.SecurityIdentity; import org.wildfly.security.manager.WildFlySecurityManager; @@ -45,7 +46,7 @@ * * @author Brian Stansberry */ -final class ModelControllerClientFactoryImpl implements ModelControllerClientFactory { +final class ModelControllerClientFactoryImpl implements ModelControllerClientFactory, EmbeddedModelControllerClientFactory { private final ModelControllerImpl modelController; private final Supplier securityIdentitySupplier; @@ -65,6 +66,11 @@ public LocalModelControllerClient createSuperUserClient(Executor executor, boole return createSuperUserClient(executor, forUserCalls, false); } + @Override + public org.jboss.as.controller.client.LocalModelControllerClient createEmbeddedClient(Executor executor) { + return createSuperUserClient(executor, true); + } + /** * Creates a superuser client that can execute calls that are to be regarded as part of process boot. * Package protected as this facility should only be made available to kernel code. diff --git a/controller/src/main/java/org/jboss/as/controller/ProcessStateNotifier.java b/controller/src/main/java/org/jboss/as/controller/ProcessStateNotifier.java index 84b0321f0d0..9408ded02f1 100644 --- a/controller/src/main/java/org/jboss/as/controller/ProcessStateNotifier.java +++ b/controller/src/main/java/org/jboss/as/controller/ProcessStateNotifier.java @@ -16,7 +16,8 @@ * @author Brian Stansberry (c) 2019 Red Hat Inc. */ public interface ProcessStateNotifier { - NullaryServiceDescriptor SERVICE_DESCRIPTOR = NullaryServiceDescriptor.of("org.wildfly.management.process-state-notifier", ProcessStateNotifier.class); + NullaryServiceDescriptor SERVICE_DESCRIPTOR = + NullaryServiceDescriptor.of("org.wildfly.management.process-state-notifier", ProcessStateNotifier.class); /** * Gets the current state of the controlled process. diff --git a/controller/src/main/java/org/jboss/as/controller/ProcessType.java b/controller/src/main/java/org/jboss/as/controller/ProcessType.java index f9951f7a4fc..96766d43e7b 100644 --- a/controller/src/main/java/org/jboss/as/controller/ProcessType.java +++ b/controller/src/main/java/org/jboss/as/controller/ProcessType.java @@ -11,20 +11,22 @@ * need to be present. {@link OperationStepHandler}s can use this to determine how to handle operations. */ public enum ProcessType { - DOMAIN_SERVER(true, true), - EMBEDDED_SERVER(true, false), - STANDALONE_SERVER(true, false), - HOST_CONTROLLER(false, true), - EMBEDDED_HOST_CONTROLLER(false, true), - APPLICATION_CLIENT(true, false), - SELF_CONTAINED(true, false); + DOMAIN_SERVER(true, true, false), + EMBEDDED_SERVER(true, false, true), + STANDALONE_SERVER(true, false, false), + HOST_CONTROLLER(false, true, false), + EMBEDDED_HOST_CONTROLLER(false, true, true), + APPLICATION_CLIENT(true, false, false), + SELF_CONTAINED(true, false, false); private final boolean server; private final boolean domain; + private final boolean embedded; - ProcessType(final boolean server, final boolean domain) { + ProcessType(final boolean server, final boolean domain, final boolean embedded) { this.server = server; this.domain = domain; + this.embedded = embedded; } /** @@ -53,4 +55,13 @@ public boolean isHostController() { public boolean isManagedDomain() { return domain; } + + /** + * Gets whether the process is an embedded process. + * + * @return true if the process is an embedded process; false otherwise. + */ + public boolean isEmbedded() { + return embedded; + } } diff --git a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/controller/main/module.xml b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/controller/main/module.xml index c871dfdbae1..9868d48e761 100644 --- a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/controller/main/module.xml +++ b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/controller/main/module.xml @@ -36,6 +36,7 @@ + diff --git a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/host-controller/main/module.xml b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/host-controller/main/module.xml index 17dd33b9224..12b6d7c01d0 100644 --- a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/host-controller/main/module.xml +++ b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/host-controller/main/module.xml @@ -56,6 +56,7 @@ + diff --git a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/server/main/module.xml b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/server/main/module.xml index bc4bdd1c955..2476af6861e 100644 --- a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/server/main/module.xml +++ b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/jboss/as/server/main/module.xml @@ -67,6 +67,7 @@ + diff --git a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/embedded/main/module.xml b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/embedded/main/module.xml index ce48bf20a54..4e18297e036 100644 --- a/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/embedded/main/module.xml +++ b/core-feature-pack/common/src/main/resources/modules/system/layers/base/org/wildfly/embedded/main/module.xml @@ -21,16 +21,11 @@ - - - - - + - + + - - diff --git a/embedded/pom.xml b/embedded/pom.xml index dee3195cec0..35824f1a971 100644 --- a/embedded/pom.xml +++ b/embedded/pom.xml @@ -98,39 +98,6 @@ by the embedded process' modular classloader from its module path. These do not need to be visible to the embedding app's classloader and are thus treated as 'provided' --> - - org.wildfly.core - wildfly-controller - - - * - * - - - provided - - - org.wildfly.core - wildfly-server - - - * - * - - - provided - - - org.wildfly.core - wildfly-host-controller - - - * - * - - - provided - org.jboss.msc jboss-msc diff --git a/embedded/src/main/java/org/wildfly/core/embedded/AbstractEmbeddedManagedProcess.java b/embedded/src/main/java/org/wildfly/core/embedded/AbstractEmbeddedManagedProcess.java new file mode 100644 index 00000000000..6373db9bf0e --- /dev/null +++ b/embedded/src/main/java/org/wildfly/core/embedded/AbstractEmbeddedManagedProcess.java @@ -0,0 +1,213 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.core.embedded; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ServiceLoader; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.jboss.as.controller.client.ModelControllerClient; +import org.jboss.as.controller.client.helpers.DelegatingModelControllerClient; +import org.wildfly.core.embedded.logging.EmbeddedLogger; +import org.wildfly.core.embedded.spi.BootstrappedEmbeddedProcess; +import org.wildfly.core.embedded.spi.EmbeddedModelControllerClientFactory; +import org.wildfly.core.embedded.spi.EmbeddedProcessBootstrap; +import org.wildfly.core.embedded.spi.EmbeddedProcessBootstrapConfiguration; +import org.wildfly.core.embedded.spi.EmbeddedProcessState; +import org.wildfly.core.embedded.spi.ProcessStateNotifier; + +/** + * Base class for modular-classloader-side implementations of {@link StandaloneServer} and {@link HostController}. + */ +abstract class AbstractEmbeddedManagedProcess implements EmbeddedManagedProcess { + + private final EmbeddedProcessBootstrap.Type type; + private final PropertyChangeListener processStateListener; + private final String[] cmdargs; + private final ClassLoader embeddedModuleCL; + private BootstrappedEmbeddedProcess embeddedProcess; + private ModelControllerClient modelControllerClient; + private ExecutorService executorService; + + AbstractEmbeddedManagedProcess(EmbeddedProcessBootstrap.Type type, String[] cmdargs, ClassLoader embeddedModuleCL) { + this.type = type; + this.cmdargs = cmdargs; + this.embeddedModuleCL = embeddedModuleCL; + + processStateListener = new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + establishModelControllerClient(); + } + }; + } + + @Override + public final synchronized ModelControllerClient getModelControllerClient() { + return modelControllerClient == null ? null : new DelegatingModelControllerClient(this::getActiveModelControllerClient); + } + + @Override + public final void start() throws EmbeddedProcessStartException { + EmbeddedProcessBootstrapConfiguration bootstrapConfiguration = getBootstrapConfiguration(); + + ClassLoader tccl = SecurityActions.getTccl(); + try { + SecurityActions.setTccl(embeddedModuleCL); + try { + + EmbeddedProcessBootstrap bootstrap = loadEmbeddedProcessBootstrap(); + embeddedProcess = bootstrap.startup(bootstrapConfiguration); + if (embeddedProcess == null) { + // TODO before adding embedded-spi, why did we ignore Main.determineEnvironment not providing one? + // This just continues that behavior + return; + } + + executorService = Executors.newCachedThreadPool(); + + ProcessStateNotifier processStateNotifier = embeddedProcess.getProcessStateNotifier(); + processStateNotifier.addProcessStateListener(processStateListener); + establishModelControllerClient(); + + } catch (RuntimeException rte) { + throw rte; + } catch (Exception ex) { + throw EmbeddedLogger.ROOT_LOGGER.cannotStartEmbeddedServer(ex); + } + } finally { + SecurityActions.setTccl(tccl); + } + } + + @Override + public final void stop() { + ClassLoader tccl = SecurityActions.getTccl(); + try { + SecurityActions.setTccl(embeddedModuleCL); + exit(); + } finally { + SecurityActions.setTccl(tccl); + } + } + + @Override + public String getProcessState() { + if (embeddedProcess == null) { + return null; + } + return embeddedProcess.getProcessStateNotifier().getEmbeddedProcessState().toString(); + } + + @Override + public boolean canQueryProcessState() { + return true; + } + + EmbeddedProcessBootstrapConfiguration getBootstrapConfiguration() { + return new EmbeddedProcessBootstrapConfiguration( + cmdargs, + (status) -> AbstractEmbeddedManagedProcess.this.exit() + ); + } + + private EmbeddedProcessBootstrap loadEmbeddedProcessBootstrap() { + ServiceLoader loader = ServiceLoader.load(EmbeddedProcessBootstrap.class, embeddedModuleCL); + for (EmbeddedProcessBootstrap epb : loader) { + if (type == epb.getType()) { + return epb; + } + } + throw new IllegalStateException(); + } + + private synchronized void establishModelControllerClient() { + assert embeddedProcess != null; + EmbeddedProcessState eps = embeddedProcess.getProcessStateNotifier().getEmbeddedProcessState(); + ModelControllerClient newClient = null; + if (eps != EmbeddedProcessState.STOPPING && eps != EmbeddedProcessState.STOPPED) { + EmbeddedModelControllerClientFactory clientFactory = embeddedProcess.getModelControllerClientFactory(); + if (clientFactory != null) { + newClient = clientFactory.createEmbeddedClient(executorService); + } + } + modelControllerClient = newClient; + } + + private synchronized ModelControllerClient getActiveModelControllerClient() { + assert embeddedProcess != null; // We only get called after an initial client is established + switch (embeddedProcess.getProcessStateNotifier().getEmbeddedProcessState()) { + case STOPPING: { + throw EmbeddedLogger.ROOT_LOGGER.processIsStopping(); + } + case STOPPED: { + throw EmbeddedLogger.ROOT_LOGGER.processIsStopped(); + } + case STARTING: + case RUNNING: { + if (modelControllerClient == null) { + // Service wasn't available when we got the ControlledProcessState + // state change notification; try again + establishModelControllerClient(); + if (modelControllerClient == null) { + throw EmbeddedLogger.ROOT_LOGGER.processIsReloading(); + } + } + // fall through + } + default: { + return modelControllerClient; + } + } + + } + + private void exit() { + + if (embeddedProcess != null) { + try { + embeddedProcess.getServiceContainer().shutdown(); + + embeddedProcess.getServiceContainer().awaitTermination(); + } catch (RuntimeException rte) { + throw rte; + } catch (InterruptedException ite) { + EmbeddedLogger.ROOT_LOGGER.error(ite.getLocalizedMessage(), ite); + Thread.currentThread().interrupt(); + } catch (Exception ex) { + EmbeddedLogger.ROOT_LOGGER.error(ex.getLocalizedMessage(), ex); + } + + embeddedProcess.getProcessStateNotifier().removeProcessStateListener(processStateListener); + embeddedProcess = null; + } + if (executorService != null) { + try { + executorService.shutdown(); + + // 10 secs is arbitrary, but if the service container is terminated, + // no good can happen from waiting for ModelControllerClient requests to complete + executorService.awaitTermination(10, TimeUnit.SECONDS); + } catch (RuntimeException rte) { + throw rte; + } catch (InterruptedException ite) { + EmbeddedLogger.ROOT_LOGGER.error(ite.getLocalizedMessage(), ite); + Thread.currentThread().interrupt(); + } catch (Exception ex) { + EmbeddedLogger.ROOT_LOGGER.error(ex.getLocalizedMessage(), ex); + } + } + + + if (embeddedProcess != null) { + embeddedProcess.close(); + embeddedProcess = null; + } + } +} diff --git a/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedHostControllerFactory.java b/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedHostControllerFactory.java index 90b392d2889..6426d06e79e 100644 --- a/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedHostControllerFactory.java +++ b/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedHostControllerFactory.java @@ -5,47 +5,22 @@ package org.wildfly.core.embedded; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.StandardCopyOption; -import java.security.SecureRandom; import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Base64; import java.util.Date; -import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.Random; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import org.jboss.as.controller.ControlledProcessState; -import org.jboss.as.controller.ProcessStateNotifier; -import org.jboss.as.controller.ModelControllerClientFactory; -import org.jboss.as.controller.ProcessType; -import org.jboss.as.controller.client.ModelControllerClient; -import org.jboss.as.controller.client.helpers.DelegatingModelControllerClient; -import org.jboss.as.server.ElapsedTime; -import org.jboss.as.host.controller.DomainModelControllerService; -import org.jboss.as.host.controller.HostControllerEnvironment; -import org.jboss.as.host.controller.Main; -import org.jboss.as.server.FutureServiceContainer; -import org.jboss.as.server.SystemExiter; -import org.jboss.as.server.logging.ServerLogger; + import org.jboss.modules.ModuleLoader; -import org.jboss.msc.service.ServiceContainer; -import org.jboss.msc.service.ServiceController; import org.wildfly.core.embedded.logging.EmbeddedLogger; +import org.wildfly.core.embedded.spi.EmbeddedProcessBootstrap; +import org.wildfly.core.embedded.spi.EmbeddedProcessBootstrapConfiguration; /** - * This is the host controller counterpart to EmbeddedProcessFactory which lives behind a module class loader. + * This is the host controller counterpart to EmbeddedProcessFactory that lives behind a module class loader. *

* Factory that sets up an embedded {@link HostController} using modular classloading. *

@@ -66,13 +41,14 @@ * @author Ken Wills * @see EmbeddedProcessFactory */ + public class EmbeddedHostControllerFactory { public static final String JBOSS_EMBEDDED_ROOT = "jboss.embedded.root"; - private static final String MODULE_PATH = "-mp"; - private static final String PC_ADDRESS = "--pc-address"; - private static final String PC_PORT = "--pc-port"; + private static final String DOMAIN_BASE_DIR = "jboss.domain.base.dir"; + private static final String DOMAIN_CONFIG_DIR = "jboss.domain.config.dir"; + private static final String DOMAIN_DATA_DIR = "jboss.domain.data.dir"; private EmbeddedHostControllerFactory() { } @@ -90,7 +66,7 @@ public static HostController create(final File jbossHomeDir, final ModuleLoader throw EmbeddedLogger.ROOT_LOGGER.nullVar("cmdargs"); setupCleanDirectories(jbossHomeDir, systemProps); - return new HostControllerImpl(jbossHomeDir, cmdargs, systemProps, systemEnv, moduleLoader, embeddedModuleCL); + return new HostControllerImpl(jbossHomeDir, cmdargs, systemProps, systemEnv, embeddedModuleCL); } static void setupCleanDirectories(File jbossHomeDir, Properties props) { @@ -99,8 +75,8 @@ static void setupCleanDirectories(File jbossHomeDir, Properties props) { return; } - File originalConfigDir = getFileUnderAsRoot(jbossHomeDir, props, HostControllerEnvironment.DOMAIN_CONFIG_DIR, "configuration", true); - File originalDataDir = getFileUnderAsRoot(jbossHomeDir, props, HostControllerEnvironment.DOMAIN_DATA_DIR, "data", false); + File originalConfigDir = getFileUnderAsRoot(jbossHomeDir, props, DOMAIN_CONFIG_DIR, "configuration", true); + File originalDataDir = getFileUnderAsRoot(jbossHomeDir, props, DOMAIN_DATA_DIR, "data", false); try { File configDir = new File(tempRoot, "config"); @@ -116,9 +92,9 @@ static void setupCleanDirectories(File jbossHomeDir, Properties props) { copyDirectory(originalDataDir, dataDir); } - props.put(HostControllerEnvironment.DOMAIN_BASE_DIR, tempRoot.getAbsolutePath()); - props.put(HostControllerEnvironment.DOMAIN_CONFIG_DIR, configDir.getAbsolutePath()); - props.put(HostControllerEnvironment.DOMAIN_DATA_DIR, dataDir.getAbsolutePath()); + props.put(DOMAIN_BASE_DIR, tempRoot.getAbsolutePath()); + props.put(DOMAIN_CONFIG_DIR, configDir.getAbsolutePath()); + props.put(DOMAIN_DATA_DIR, dataDir.getAbsolutePath()); } catch (IOException e) { throw EmbeddedLogger.ROOT_LOGGER.cannotSetupEmbeddedServer(e); } @@ -128,21 +104,21 @@ static void setupCleanDirectories(File jbossHomeDir, Properties props) { private static File getFileUnderAsRoot(File jbossHomeDir, Properties props, String propName, String relativeLocation, boolean mustExist) { String prop = props.getProperty(propName, null); if (prop == null) { - prop = props.getProperty(HostControllerEnvironment.DOMAIN_BASE_DIR, null); + prop = props.getProperty(DOMAIN_BASE_DIR, null); if (prop == null) { File dir = new File(jbossHomeDir, "domain" + File.separator + relativeLocation); if (mustExist && (!dir.exists() || !dir.isDirectory())) { - throw ServerLogger.ROOT_LOGGER.embeddedServerDirectoryNotFound("domain" + File.separator + relativeLocation, jbossHomeDir.getAbsolutePath()); + throw EmbeddedLogger.ROOT_LOGGER.embeddedServerDirectoryNotFound("domain" + File.separator + relativeLocation, jbossHomeDir.getAbsolutePath()); } return dir; } else { File server = new File(prop); - validateDirectory(HostControllerEnvironment.DOMAIN_BASE_DIR, server); + validateDirectory(DOMAIN_BASE_DIR, server); return new File(server, relativeLocation); } } else { File dir = new File(prop); - validateDirectory(HostControllerEnvironment.DOMAIN_BASE_DIR, dir); + validateDirectory(DOMAIN_BASE_DIR, dir); return dir; } @@ -174,10 +150,10 @@ private static File getTempRoot(Properties props) { private static void validateDirectory(String property, File file) { if (!file.exists()) { - throw ServerLogger.ROOT_LOGGER.propertySpecifiedFileDoesNotExist(property, file.getAbsolutePath()); + throw EmbeddedLogger.ROOT_LOGGER.propertySpecifiedFileDoesNotExist(property, file.getAbsolutePath()); } if (!file.isDirectory()) { - throw ServerLogger.ROOT_LOGGER.propertySpecifiedFileIsNotADirectory(property, file.getAbsolutePath()); + throw EmbeddedLogger.ROOT_LOGGER.propertySpecifiedFileIsNotADirectory(property, file.getAbsolutePath()); } } @@ -193,247 +169,34 @@ private static void copyDirectory(File src, File dest) { copyDirectory(srcFile, destFile); } } catch (IOException e) { - throw ServerLogger.ROOT_LOGGER.errorCopyingFile(srcFile.getAbsolutePath(), destFile.getAbsolutePath(), e); + throw EmbeddedLogger.ROOT_LOGGER.errorCopyingFile(srcFile.getAbsolutePath(), destFile.getAbsolutePath(), e); } } } } - private static class HostControllerImpl implements HostController { + private static class HostControllerImpl extends AbstractEmbeddedManagedProcess implements HostController { - private final PropertyChangeListener processStateListener; - private final String[] cmdargs; private final File jbossHomeDir; - private final Properties systemProps; - private final Map systemEnv; - private final ModuleLoader moduleLoader; - private final ClassLoader embeddedModuleCL; - private ServiceContainer serviceContainer; - private volatile ControlledProcessState.State currentProcessState; - private ModelControllerClient modelControllerClient; - private ExecutorService executorService; - private ProcessStateNotifier processStateNotifier; - - public HostControllerImpl(final File jbossHomeDir, String[] cmdargs, Properties systemProps, Map systemEnv, ModuleLoader moduleLoader, ClassLoader embeddedModuleCL) { - this.cmdargs = cmdargs; + private final Properties systemProps; // TODO why is this not used? + private final Map systemEnv; // TODO why is this not used? + + public HostControllerImpl(final File jbossHomeDir, String[] cmdargs, Properties systemProps, Map systemEnv, ClassLoader embeddedModuleCL) { + super(EmbeddedProcessBootstrap.Type.HOST_CONTROLLER, cmdargs, embeddedModuleCL); this.jbossHomeDir = jbossHomeDir; this.systemProps = systemProps; this.systemEnv = systemEnv; - this.moduleLoader = moduleLoader; - this.embeddedModuleCL = embeddedModuleCL; - - processStateListener = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if ("currentState".equals(evt.getPropertyName())) { - ControlledProcessState.State newState = (ControlledProcessState.State) evt.getNewValue(); - establishModelControllerClient(newState, true); - } - } - }; - } - - @Override - public void start() throws EmbeddedProcessStartException { - ElapsedTime elapsedTime = ElapsedTime.startingFromNow(); - ClassLoader tccl = SecurityActions.getTccl(); - try { - SecurityActions.setTccl(embeddedModuleCL); - EmbeddedHostControllerBootstrap hostControllerBootstrap = null; - try { - // Take control of server use of System.exit - SystemExiter.initialize(new SystemExiter.Exiter() { - @Override - public void exit(int status) { - HostControllerImpl.this.exit(); - } - }); - - // Determine the HostControllerEnvironment - HostControllerEnvironment environment = createHostControllerEnvironment(jbossHomeDir, cmdargs, elapsedTime); - - FutureServiceContainer futureContainer = new FutureServiceContainer(); - final byte[] authBytes = new byte[16]; - new Random(new SecureRandom().nextLong()).nextBytes(authBytes); - final String pcAuthCode = Base64.getEncoder().encodeToString(authBytes); - final AtomicReference notifierReference = new AtomicReference<>(); - hostControllerBootstrap = new EmbeddedHostControllerBootstrap(futureContainer, environment, pcAuthCode); - hostControllerBootstrap.bootstrap(processStateListener, notifierReference); - serviceContainer = futureContainer.get(); - executorService = Executors.newCachedThreadPool(); - processStateNotifier = notifierReference.get(); - establishModelControllerClient(currentProcessState, false); - } catch (RuntimeException rte) { - if (hostControllerBootstrap != null) { - hostControllerBootstrap.failed(); - } - throw rte; - } catch (Exception ex) { - if (hostControllerBootstrap != null) { - hostControllerBootstrap.failed(); - } - throw EmbeddedLogger.ROOT_LOGGER.cannotStartEmbeddedServer(ex); - } - } finally { - SecurityActions.setTccl(tccl); - } - } - - @Override - public synchronized ModelControllerClient getModelControllerClient() { - return modelControllerClient == null ? null : new DelegatingModelControllerClient(new DelegatingModelControllerClient.DelegateProvider() { - @Override - public ModelControllerClient getDelegate() { - return getActiveModelControllerClient(); - } - }); - } - - private synchronized void establishModelControllerClient(ControlledProcessState.State state, boolean storeState) { - ModelControllerClient newClient = null; - if (state != ControlledProcessState.State.STOPPING && state != ControlledProcessState.State.STOPPED && serviceContainer != null) { - ModelControllerClientFactory clientFactory; - try { - @SuppressWarnings("unchecked") - final ServiceController clientFactorySvc = - serviceContainer.getService(DomainModelControllerService.CLIENT_FACTORY_SERVICE_NAME); - clientFactory = (ModelControllerClientFactory) clientFactorySvc.getValue(); - } catch (RuntimeException e) { - // Either NPE because clientFactorySvc was not installed, or ISE from getValue because not UP - clientFactory = null; - } - if (clientFactory != null) { - newClient = clientFactory.createSuperUserClient(executorService, true); - } - } - modelControllerClient = newClient; - if (storeState || currentProcessState == null) { - currentProcessState = state; - } - } - - private synchronized ModelControllerClient getActiveModelControllerClient() { - switch (currentProcessState) { - case STOPPING: { - throw EmbeddedLogger.ROOT_LOGGER.processIsStopping(); - } - case STOPPED: { - throw EmbeddedLogger.ROOT_LOGGER.processIsStopped(); - } - case STARTING: - case RUNNING: { - if (modelControllerClient == null) { - // Service wasn't available when we got the ControlledProcessState - // state change notification; try again - establishModelControllerClient(currentProcessState, false); - if (modelControllerClient == null) { - throw EmbeddedLogger.ROOT_LOGGER.processIsReloading(); - } - } - // fall through - } - default: { - return modelControllerClient; - } - } - } - - - @Override - public void stop() { - ClassLoader tccl = SecurityActions.getTccl(); - try { - SecurityActions.setTccl(embeddedModuleCL); - exit(); - } finally { - SecurityActions.setTccl(tccl); - } } @Override - public String getProcessState() { - if (currentProcessState == null) { - return null; - } - return currentProcessState.toString(); - } - - @Override - public boolean canQueryProcessState() { - return true; - } - - private void exit() { - - if (serviceContainer != null) { - try { - serviceContainer.shutdown(); - serviceContainer.awaitTermination(); - } catch (RuntimeException rte) { - throw rte; - } catch (InterruptedException ite) { - ServerLogger.ROOT_LOGGER.error(ite.getLocalizedMessage(), ite); - Thread.currentThread().interrupt(); - } catch (Exception ex) { - ServerLogger.ROOT_LOGGER.error(ex.getLocalizedMessage(), ex); - } - } - if (processStateNotifier != null) { - processStateNotifier.removePropertyChangeListener(processStateListener); - processStateNotifier = null; - } - if (executorService != null) { - try { - executorService.shutdown(); - // 10 secs is arbitrary, but if the service container is terminated, - // no good can happen from waiting for ModelControllerClient requests to complete - executorService.awaitTermination(10, TimeUnit.SECONDS); - } catch (RuntimeException rte) { - throw rte; - } catch (InterruptedException ite) { - ServerLogger.ROOT_LOGGER.error(ite.getLocalizedMessage(), ite); - Thread.currentThread().interrupt(); - } catch (Exception ex) { - ServerLogger.ROOT_LOGGER.error(ex.getLocalizedMessage(), ex); - } - } - - SystemExiter.initialize(SystemExiter.Exiter.DEFAULT); + EmbeddedProcessBootstrapConfiguration getBootstrapConfiguration() { + EmbeddedProcessBootstrapConfiguration configuration = super.getBootstrapConfiguration(); + configuration.setJBossHome(jbossHomeDir); + return configuration; } - private static HostControllerEnvironment createHostControllerEnvironment(File jbossHome, String[] cmdargs, - ElapsedTime elapsedTime) { - SecurityActions.setPropertyPrivileged(HostControllerEnvironment.HOME_DIR, jbossHome.getAbsolutePath()); - - List cmds = new ArrayList(Arrays.asList(cmdargs)); - - // these are for compatibility with Main.determineEnvironment / HostControllerEnvironment - // Once WFCORE-938 is resolved, --admin-only will allow a connection back to the DC for slaves, - // and support a method for setting the domain master address outside of -Djboss.domain.primary.address - // so we'll probably need a command line argument for this if its not specified as a system prop - if (SecurityActions.getPropertyPrivileged(HostControllerEnvironment.JBOSS_DOMAIN_PRIMARY_ADDRESS, null) == null) { - SecurityActions.setPropertyPrivileged(HostControllerEnvironment.JBOSS_DOMAIN_PRIMARY_ADDRESS, "127.0.0.1"); - } - cmds.add(MODULE_PATH); - cmds.add(SecurityActions.getPropertyPrivileged("module.path", "")); - cmds.add(PC_ADDRESS); - cmds.add("0"); - cmds.add(PC_PORT); - cmds.add("0"); - // this used to be set in the embedded-hc specific env setup, WFCORE-938 will add support for --admin-only=false - cmds.add("--admin-only"); - - for (final String prop : EmbeddedProcessFactory.DOMAIN_KEYS) { - // if we've started with any jboss.domain.base.dir etc, copy those in here. - String value = SecurityActions.getPropertyPrivileged(prop, null); - if (value != null) - cmds.add("-D" + prop + "=" + value); - } - return Main.determineEnvironment(cmds.toArray(new String[cmds.size()]), elapsedTime, ProcessType.EMBEDDED_HOST_CONTROLLER).getHostControllerEnvironment(); - } - - } + } diff --git a/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedManagedProcess.java b/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedManagedProcess.java index 6e19e9dc122..72c745f463d 100644 --- a/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedManagedProcess.java +++ b/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedManagedProcess.java @@ -37,7 +37,7 @@ public interface EmbeddedManagedProcess { /** * Returns the current process state of this managed process. *

- * The returned value is a String representation of one of the possible {@code ControlledProcessState.State} values. + * The returned value is a String representation of one of the possible {@code EmbeddedProcessState} values. * * @return The current process state, or {@code null} if currently the process state is unknown. * @throws UnsupportedOperationException if the requested operation is not supported by the implementation of this embedded server. diff --git a/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedManagedProcessImpl.java b/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedManagedProcessImpl.java index 1ff976f5d22..11a68ae8ed1 100644 --- a/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedManagedProcessImpl.java +++ b/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedManagedProcessImpl.java @@ -12,10 +12,10 @@ import org.wildfly.core.embedded.logging.EmbeddedLogger; /** - * Indirection to the {@link StandaloneServer} or {@link HostController}; used to encapsulate access to the underlying - * embedded instance in a manner that does not directly link this class. Necessary to avoid {@link ClassCastException} - * when this class is loaded by the application {@link ClassLoader} (or any other hierarchical CL) while the server is - * loaded by a modular environment. + * Embedding-application-side indirection to the {@link StandaloneServer} or {@link HostController}. + * Used to encapsulate access to the underlying embedded instance in a manner that does not directly link this class. + * Necessary to avoid {@link ClassCastException} when this class is loaded by the application {@link ClassLoader} + * (or any other hierarchical CL) while the server is loaded by a modular environment. * * @author Andrew Lee Rubinger * @author Thomas.Diesler@jboss.com. diff --git a/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedProcessFactory.java b/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedProcessFactory.java index 5fd12279a0c..1faaf11283c 100644 --- a/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedProcessFactory.java +++ b/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedProcessFactory.java @@ -18,7 +18,6 @@ import org.jboss.modules.Module; import org.jboss.modules.ModuleClassLoader; -import org.jboss.modules.ModuleIdentifier; import org.jboss.modules.ModuleLoadException; import org.jboss.modules.ModuleLoader; import org.wildfly.core.embedded.logging.EmbeddedLogger; @@ -41,17 +40,6 @@ public class EmbeddedProcessFactory { private static final String MODULE_ID_EMBEDDED = "org.wildfly.embedded"; private static final String MODULE_ID_VFS = "org.jboss.vfs"; - private static final String SYSPROP_KEY_JBOSS_DOMAIN_BASE_DIR = "jboss.domain.base.dir"; - private static final String SYSPROP_KEY_JBOSS_DOMAIN_CONFIG_DIR = "jboss.domain.config.dir"; - private static final String SYSPROP_KEY_JBOSS_DOMAIN_DEPLOYMENT_DIR = "jboss.domain.deployment.dir"; - private static final String SYSPROP_KEY_JBOSS_DOMAIN_TEMP_DIR = "jboss.domain.temp.dir"; - private static final String SYSPROP_KEY_JBOSS_DOMAIN_LOG_DIR = "jboss.domain.log.dir"; - - static final String[] DOMAIN_KEYS = { - SYSPROP_KEY_JBOSS_DOMAIN_BASE_DIR, SYSPROP_KEY_JBOSS_DOMAIN_CONFIG_DIR, SYSPROP_KEY_JBOSS_DOMAIN_DEPLOYMENT_DIR, - SYSPROP_KEY_JBOSS_DOMAIN_TEMP_DIR, SYSPROP_KEY_JBOSS_DOMAIN_LOG_DIR, SYSPROP_KEY_JBOSS_DOMAIN_CONFIG_DIR - }; - private static final String HOST_FACTORY = "org.wildfly.core.embedded.EmbeddedHostControllerFactory"; private static final String SERVER_FACTORY = "org.wildfly.core.embedded.EmbeddedStandaloneServerFactory"; /** @@ -302,14 +290,11 @@ public static HostController createHostController(final Configuration configurat } private static void setupVfsModule(final ModuleLoader moduleLoader) { - final ModuleIdentifier vfsModuleID = ModuleIdentifier.create(MODULE_ID_VFS); - final Module vfsModule; try { - vfsModule = moduleLoader.loadModule(vfsModuleID); + Module.registerURLStreamHandlerFactoryModule(moduleLoader.loadModule(MODULE_ID_VFS)); } catch (final ModuleLoadException mle) { throw EmbeddedLogger.ROOT_LOGGER.moduleLoaderError(mle,MODULE_ID_VFS, moduleLoader); } - Module.registerURLStreamHandlerFactoryModule(vfsModule); } private static Object createManagedProcess(final ProcessType embeddedType, final Method createServerMethod, final Configuration configuration, ModuleClassLoader embeddedModuleCL) { diff --git a/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedStandaloneServerFactory.java b/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedStandaloneServerFactory.java index af60e960c6c..93241ebead4 100644 --- a/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedStandaloneServerFactory.java +++ b/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedStandaloneServerFactory.java @@ -5,48 +5,21 @@ package org.wildfly.core.embedded; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; -import java.util.Collections; import java.util.Map; import java.util.Properties; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; -import org.jboss.as.controller.ControlledProcessState; -import org.jboss.as.controller.ProcessStateNotifier; -import org.jboss.as.controller.ControlledProcessStateService; -import org.jboss.as.controller.ModelControllerClientFactory; -import org.jboss.as.controller.client.ModelControllerClient; -import org.jboss.as.controller.client.helpers.DelegatingModelControllerClient; -import org.jboss.as.server.Bootstrap; -import org.jboss.as.server.ElapsedTime; -import org.jboss.as.server.Main; -import org.jboss.as.server.ServerEnvironment; -import org.jboss.as.server.ServerService; -import org.jboss.as.server.SystemExiter; import org.jboss.modules.ModuleLoader; -import org.jboss.msc.Service; -import org.jboss.msc.service.ServiceActivator; -import org.jboss.msc.service.ServiceActivatorContext; -import org.jboss.msc.service.ServiceBuilder; -import org.jboss.msc.service.ServiceContainer; -import org.jboss.msc.service.ServiceController; -import org.jboss.msc.service.StartContext; -import org.jboss.msc.service.StopContext; import org.wildfly.core.embedded.logging.EmbeddedLogger; +import org.wildfly.core.embedded.spi.EmbeddedProcessBootstrap; +import org.wildfly.core.embedded.spi.EmbeddedProcessBootstrapConfiguration; /** - * This is the standalone server counter-part of EmbeddedProcessFactory which lives behind a module class loader. + * This is the standalone server counter-part of EmbeddedProcessFactory that lives behind a module class loader. *

* Factory that sets up an embedded @{link StandaloneServer} using modular classloading. *

@@ -71,6 +44,9 @@ public class EmbeddedStandaloneServerFactory { public static final String JBOSS_EMBEDDED_ROOT = "jboss.embedded.root"; + private static final String SERVER_BASE_DIR = "jboss.server.base.dir"; + private static final String SERVER_CONFIG_DIR = "jboss.server.config.dir"; + private static final String SERVER_DATA_DIR = "jboss.server.data.dir"; private EmbeddedStandaloneServerFactory() { } @@ -100,8 +76,8 @@ static void setupCleanDirectories(Path jbossHomeDir, Properties props) { return; } - File originalConfigDir = getFileUnderAsRoot(jbossHomeDir.toFile(), props, ServerEnvironment.SERVER_CONFIG_DIR, "configuration", true); - File originalDataDir = getFileUnderAsRoot(jbossHomeDir.toFile(), props, ServerEnvironment.SERVER_DATA_DIR, "data", false); + File originalConfigDir = getFileUnderAsRoot(jbossHomeDir.toFile(), props, SERVER_CONFIG_DIR, "configuration", true); + File originalDataDir = getFileUnderAsRoot(jbossHomeDir.toFile(), props, SERVER_DATA_DIR, "data", false); try { Path configDir = tempRoot.resolve("config"); @@ -117,9 +93,9 @@ static void setupCleanDirectories(Path jbossHomeDir, Properties props) { copyDirectory(originalDataDir, dataDir.toFile()); } - props.put(ServerEnvironment.SERVER_BASE_DIR, tempRoot.toAbsolutePath().toString()); - props.put(ServerEnvironment.SERVER_CONFIG_DIR, configDir.toAbsolutePath().toString()); - props.put(ServerEnvironment.SERVER_DATA_DIR, dataDir.toAbsolutePath().toString()); + props.put(SERVER_BASE_DIR, tempRoot.toAbsolutePath().toString()); + props.put(SERVER_CONFIG_DIR, configDir.toAbsolutePath().toString()); + props.put(SERVER_DATA_DIR, dataDir.toAbsolutePath().toString()); } catch (IOException e) { throw EmbeddedLogger.ROOT_LOGGER.cannotSetupEmbeddedServer(e); } @@ -129,7 +105,7 @@ static void setupCleanDirectories(Path jbossHomeDir, Properties props) { private static File getFileUnderAsRoot(File jbossHomeDir, Properties props, String propName, String relativeLocation, boolean mustExist) { String prop = props.getProperty(propName, null); if (prop == null) { - prop = props.getProperty(ServerEnvironment.SERVER_BASE_DIR, null); + prop = props.getProperty(SERVER_BASE_DIR, null); if (prop == null) { File dir = new File(jbossHomeDir, "standalone" + File.separator + relativeLocation); if (mustExist && (!dir.exists() || !dir.isDirectory())) { @@ -138,12 +114,12 @@ private static File getFileUnderAsRoot(File jbossHomeDir, Properties props, Stri return dir; } else { File server = new File(prop); - validateDirectory(ServerEnvironment.SERVER_BASE_DIR, server); + validateDirectory(SERVER_BASE_DIR, server); return new File(server, relativeLocation); } } else { File dir = new File(prop); - validateDirectory(ServerEnvironment.SERVER_CONFIG_DIR, dir); + validateDirectory(SERVER_CONFIG_DIR, dir); return dir; } @@ -196,135 +172,17 @@ private static void copyDirectory(File src, File dest) { } - private static class StandaloneServerImpl implements StandaloneServer { + private static class StandaloneServerImpl extends AbstractEmbeddedManagedProcess implements StandaloneServer { - private final PropertyChangeListener processStateListener; - private final String[] cmdargs; private final Properties systemProps; private final Map systemEnv; private final ModuleLoader moduleLoader; - private final ClassLoader embeddedModuleCL; - private ServiceContainer serviceContainer; - private ControlledProcessState.State currentProcessState; - private ModelControllerClient modelControllerClient; - private ExecutorService executorService; - private ProcessStateNotifier processStateNotifier; public StandaloneServerImpl(String[] cmdargs, Properties systemProps, Map systemEnv, ModuleLoader moduleLoader, ClassLoader embeddedModuleCL) { - this.cmdargs = cmdargs; + super(EmbeddedProcessBootstrap.Type.STANDALONE_SERVER, cmdargs, embeddedModuleCL); this.systemProps = systemProps; this.systemEnv = systemEnv; this.moduleLoader = moduleLoader; - this.embeddedModuleCL = embeddedModuleCL; - - processStateListener = new PropertyChangeListener() { - @Override - public void propertyChange(PropertyChangeEvent evt) { - if ("currentState".equals(evt.getPropertyName())) { - ControlledProcessState.State newState = (ControlledProcessState.State) evt.getNewValue(); - establishModelControllerClient(newState, false); - } - } - }; - } - - @Override - public synchronized ModelControllerClient getModelControllerClient() { - return modelControllerClient == null ? null : new DelegatingModelControllerClient(new DelegatingModelControllerClient.DelegateProvider() { - @Override - public ModelControllerClient getDelegate() { - return getActiveModelControllerClient(); - } - }); - } - - @Override - public void start() throws EmbeddedProcessStartException { - ElapsedTime elapsedTime = ElapsedTime.startingFromNow(); - ClassLoader tccl = SecurityActions.getTccl(); - try { - SecurityActions.setTccl(embeddedModuleCL); - Bootstrap bootstrap = null; - try { - final long startTime = System.currentTimeMillis(); - - // Take control of server use of System.exit - SystemExiter.initialize(new SystemExiter.Exiter() { - @Override - public void exit(int status) { - StandaloneServerImpl.this.exit(); - } - }); - - // Determine the ServerEnvironment - ServerEnvironment serverEnvironment = Main.determineEnvironment(cmdargs, systemProps, systemEnv, - ServerEnvironment.LaunchType.EMBEDDED, elapsedTime).getServerEnvironment(); - if (serverEnvironment == null) { - // Nothing to do - return; - } - bootstrap = Bootstrap.Factory.newInstance(); - - Bootstrap.Configuration configuration = new Bootstrap.Configuration(serverEnvironment); - - configuration.setModuleLoader(moduleLoader); - - // As part of bootstrap install a service to capture the ProcessStateNotifier - AtomicReference notifierRef = new AtomicReference<>(); - ServiceActivator notifierCapture = ctx -> captureNotifier(ctx, notifierRef); - - Future future = bootstrap.startup(configuration, Collections.singletonList(notifierCapture)); - - serviceContainer = future.get(); - - executorService = Executors.newCachedThreadPool(); - - processStateNotifier = notifierRef.get(); - processStateNotifier.addPropertyChangeListener(processStateListener); - establishModelControllerClient(processStateNotifier.getCurrentState(), true); - - } catch (RuntimeException rte) { - if (bootstrap != null) { - bootstrap.failed(); - } - throw rte; - } catch (Exception ex) { - if (bootstrap != null) { - bootstrap.failed(); - } - throw EmbeddedLogger.ROOT_LOGGER.cannotStartEmbeddedServer(ex); - } - } finally { - SecurityActions.setTccl(tccl); - } - } - - private static void captureNotifier(ServiceActivatorContext ctx, AtomicReference notifierRef) { - ServiceBuilder sb = ctx.getServiceTarget().addService(); - final Supplier result = sb.requires(ControlledProcessStateService.INTERNAL_SERVICE_NAME); - sb.setInstance(new Service() { - @Override - public void start(StartContext context) { - notifierRef.set(result.get()); - context.getController().setMode(ServiceController.Mode.REMOVE); - } - - @Override - public void stop(StopContext context) { - } - }); - sb.install(); - } - - @Override - public void stop() { - ClassLoader tccl = SecurityActions.getTccl(); - try { - SecurityActions.setTccl(embeddedModuleCL); - exit(); - } finally { - SecurityActions.setTccl(tccl); - } } @Override @@ -339,95 +197,14 @@ public boolean canQueryProcessState() { return false; } - private void exit() { - - if (serviceContainer != null) { - try { - serviceContainer.shutdown(); - - serviceContainer.awaitTermination(); - } catch (RuntimeException rte) { - throw rte; - } catch (InterruptedException ite) { - EmbeddedLogger.ROOT_LOGGER.error(ite.getLocalizedMessage(), ite); - Thread.currentThread().interrupt(); - } catch (Exception ex) { - EmbeddedLogger.ROOT_LOGGER.error(ex.getLocalizedMessage(), ex); - } - } - if (processStateNotifier != null) { - processStateNotifier.removePropertyChangeListener(processStateListener); - processStateNotifier = null; - } - if (executorService != null) { - try { - executorService.shutdown(); - - // 10 secs is arbitrary, but if the service container is terminated, - // no good can happen from waiting for ModelControllerClient requests to complete - executorService.awaitTermination(10, TimeUnit.SECONDS); - } catch (RuntimeException rte) { - throw rte; - } catch (InterruptedException ite) { - EmbeddedLogger.ROOT_LOGGER.error(ite.getLocalizedMessage(), ite); - Thread.currentThread().interrupt(); - } catch (Exception ex) { - EmbeddedLogger.ROOT_LOGGER.error(ex.getLocalizedMessage(), ex); - } - } - - - SystemExiter.initialize(SystemExiter.Exiter.DEFAULT); - } - - private synchronized void establishModelControllerClient(ControlledProcessState.State state, boolean storeState) { - ModelControllerClient newClient = null; - if (state != ControlledProcessState.State.STOPPING && state != ControlledProcessState.State.STOPPED && serviceContainer != null) { - ModelControllerClientFactory clientFactory; - try { - // TODO replace this in start() with the ServiceActivator approach we used to capture the ProcessStateNotifier - @SuppressWarnings("unchecked") - final ServiceController clientFactorySvc = - serviceContainer.getService(ServerService.JBOSS_SERVER_CLIENT_FACTORY); - clientFactory = (ModelControllerClientFactory) clientFactorySvc.getValue(); - } catch (RuntimeException e) { - // Either NPE because clientFactorySvc was not installed, or ISE from getValue because not UP - clientFactory = null; - } - if (clientFactory != null) { - newClient = clientFactory.createSuperUserClient(executorService, true); - } - } - modelControllerClient = newClient; - if (storeState || currentProcessState == null) { - currentProcessState = state; - } - } - - private synchronized ModelControllerClient getActiveModelControllerClient() { - switch (currentProcessState) { - case STOPPING: { - throw EmbeddedLogger.ROOT_LOGGER.processIsStopping(); - } - case STOPPED: { - throw EmbeddedLogger.ROOT_LOGGER.processIsStopped(); - } - case STARTING: - case RUNNING: { - if (modelControllerClient == null) { - // Service wasn't available when we got the ControlledProcessState - // state change notification; try again - establishModelControllerClient(currentProcessState, false); - if (modelControllerClient == null) { - throw EmbeddedLogger.ROOT_LOGGER.processIsReloading(); - } - } - // fall through - } - default: { - return modelControllerClient; - } - } + @Override + EmbeddedProcessBootstrapConfiguration getBootstrapConfiguration() { + EmbeddedProcessBootstrapConfiguration configuration = super.getBootstrapConfiguration(); + configuration.setModuleLoader(moduleLoader); + configuration.setSystemProperties(systemProps); + configuration.setSystemEnv(systemEnv); + return configuration; } } + } diff --git a/embedded/src/main/java/org/wildfly/core/embedded/spi/BootstrappedEmbeddedProcess.java b/embedded/src/main/java/org/wildfly/core/embedded/spi/BootstrappedEmbeddedProcess.java new file mode 100644 index 00000000000..624aac3e526 --- /dev/null +++ b/embedded/src/main/java/org/wildfly/core/embedded/spi/BootstrappedEmbeddedProcess.java @@ -0,0 +1,42 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.core.embedded.spi; + +import java.util.concurrent.Executor; + +import org.jboss.msc.service.ServiceContainer; + +/** + * Provides access to objects used to interact with a started embedded process. + */ +public interface BootstrappedEmbeddedProcess { + + /** + * Gets the JBoss MSC {@link ServiceContainer} used by the embedded process. + * @return the service container. Will not return {@code null}. + */ + ServiceContainer getServiceContainer(); + + /** + * Get the object used to track the {@link EmbeddedProcessState state} of the embedded process. + * @return the state notifier. Will not return {@code null}. + */ + ProcessStateNotifier getProcessStateNotifier(); + + /** + * Gets a factory for {@link EmbeddedModelControllerClientFactory#createEmbeddedClient(Executor) creating management clients} + * to manage with the embedded process. + * + * @return the factory. May return {@code null} if the embedded process is not in {@link EmbeddedProcessState#RUNNING} state. + */ + EmbeddedModelControllerClientFactory getModelControllerClientFactory(); + + /** + * Closes out the use of the embedded process. This method must be called when the + * embedding application is done with the embedded process. + */ + void close(); +} diff --git a/embedded/src/main/java/org/wildfly/core/embedded/spi/EmbeddedModelControllerClientFactory.java b/embedded/src/main/java/org/wildfly/core/embedded/spi/EmbeddedModelControllerClientFactory.java new file mode 100644 index 00000000000..4bee0f78251 --- /dev/null +++ b/embedded/src/main/java/org/wildfly/core/embedded/spi/EmbeddedModelControllerClientFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.core.embedded.spi; + +import java.util.concurrent.Executor; + +import org.jboss.as.controller.client.LocalModelControllerClient; +import org.jboss.msc.service.ServiceName; + +/** + * Factory for obtaining a {@link org.jboss.as.controller.client.ModelControllerClient} + * for use by an embedding process. + * + * @author Brian Stansberry + */ +public interface EmbeddedModelControllerClientFactory { + + /** Only for use within the WildFly kernel; may change or be removed at any time */ + ServiceName SERVICE_NAME = ServiceName.parse("org.wildfly.embedded.model-controller-client-factory"); + + /** + * Create an in-VM client whose operations are executed as if they were invoked by a user in the + * RBAC {@code SuperUser} role, regardless of any security identity that is or isn't associated + * with the calling thread when the client is invoked. This client generally should not + * be used to handle requests from external callers, and if it is used great care should be + * taken to ensure such use is not suborning the intended access control scheme. + *

+ * In a VM with a {@link java.lang.SecurityManager SecurityManager} installed, invocations + * against the returned client can only occur from a calling context with the + * {@code org.jboss.as.controller.security.ControllerPermission#PERFORM_IN_VM_CALL PERFORM_IN_VM_CALL} + * permission. Without this permission a {@link SecurityException} will be thrown. + * + * @param executor the executor to use for asynchronous operation execution. Cannot be {@code null} + * @return the client. Will not return {@code null} + * + * @throws SecurityException if the caller does not have the + * {@code org.jboss.as.controller.security.ControllerPermission#CAN_ACCESS_MODEL_CONTROLLER CAN_ACCESS_MODEL_CONTROLLER} + * permission + */ + LocalModelControllerClient createEmbeddedClient(Executor executor); +} diff --git a/embedded/src/main/java/org/wildfly/core/embedded/spi/EmbeddedProcessBootstrap.java b/embedded/src/main/java/org/wildfly/core/embedded/spi/EmbeddedProcessBootstrap.java new file mode 100644 index 00000000000..2d10337ef9b --- /dev/null +++ b/embedded/src/main/java/org/wildfly/core/embedded/spi/EmbeddedProcessBootstrap.java @@ -0,0 +1,35 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.core.embedded.spi; + +/** + * Service interface that standalone server or host controller bootstrap logic can implement + * to allow their type of process to be bootstrapped in an embedded environment. + */ +public interface EmbeddedProcessBootstrap { + + enum Type { + STANDALONE_SERVER, + HOST_CONTROLLER + } + + /** + * Gets the type of managed process this object bootstraps. + * @return the type. Will not be {@code null}. + */ + Type getType(); + + /** + * Bootstraps an embedded process based on the provided configuration. + * + * @param configuration configuration for the bootstrap. Cannot be {@code null}. + * + * @return container object providing the embedding code access to items needed to manage the embedded process + * + * @throws Exception if one occurs while bootstrapping the process. + */ + BootstrappedEmbeddedProcess startup(EmbeddedProcessBootstrapConfiguration configuration) throws Exception; +} diff --git a/embedded/src/main/java/org/wildfly/core/embedded/spi/EmbeddedProcessBootstrapConfiguration.java b/embedded/src/main/java/org/wildfly/core/embedded/spi/EmbeddedProcessBootstrapConfiguration.java new file mode 100644 index 00000000000..b55acfb456d --- /dev/null +++ b/embedded/src/main/java/org/wildfly/core/embedded/spi/EmbeddedProcessBootstrapConfiguration.java @@ -0,0 +1,124 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.core.embedded.spi; + +import java.io.File; +import java.util.Map; +import java.util.Properties; +import java.util.function.Consumer; + +import org.jboss.modules.ModuleLoader; + +/** + * Configuration information for {@link EmbeddedProcessBootstrap#startup(EmbeddedProcessBootstrapConfiguration) starting} + * a {@link BootstrappedEmbeddedProcess}. + */ +public final class EmbeddedProcessBootstrapConfiguration { + + private final String[] cmdArgs; + private volatile ModuleLoader moduleLoader; + private final Consumer systemExitCallback; + + private volatile Properties systemProperties; + + private volatile Map systemEnv; + + private volatile File jbossHome; + + /** + * Create a new @code EmbeddedProcessBootstrapConfiguration}. + * @param cmdArgs arguments to pass to the process, analogous to what would be passed from the command line + * to a {@code main} method. Cannot be {@code null}. + * @param systemExitCallback function to call if a system exit is being handled. The consumer is + * passed the exit code. Cannot be {@code null}. + */ + public EmbeddedProcessBootstrapConfiguration(String[] cmdArgs, Consumer systemExitCallback) { + this.cmdArgs = cmdArgs; + this.systemExitCallback = systemExitCallback; + } + + /** + * Gets the arguments to pass to the process, analogous to what would be passed from the command line + * to a {@code main} method. + * + * @return the arguments. Will not be {@code null}. + */ + public String[] getCmdArgs() { + return cmdArgs; + } + + /** + * Gets the module loader to provide to the process bootstrap. + * @return the module loader. May be {@code null}. + */ + public ModuleLoader getModuleLoader() { + return moduleLoader; + } + + /** + * Gets the consumer to invoke if a system exit is being processed. + * @return the consumer. Will not be {@code null} + */ + public Consumer getSystemExitCallback() { + return systemExitCallback; + } + + /** + * Gets the properties to pass to the embedded process as if they were system properties. + * @return the properties. May be {@code null}. + */ + public Properties getSystemProperties() { + return systemProperties; + } + + /** + * Sets the module loader to provide to the process bootstrap. + * @param moduleLoader the module loader. May be {@code null}. + */ + public void setModuleLoader(ModuleLoader moduleLoader) { + this.moduleLoader = moduleLoader; + } + + /** + * Sets the properties to pass to the embedded process as if they were system properties. + * @param systemProperties the properties. May be {@code null}. + */ + public void setSystemProperties(Properties systemProperties) { + this.systemProperties = systemProperties; + } + + /** + * Gets the environment values to pass to the embedded process. + * @return the properties. May be {@code null}. + */ + public Map getSystemEnv() { + return systemEnv; + } + + /** + * Sets the environment values to pass to the embedded process. + * @param systemEnv the properties. May be {@code null}. + */ + public void setSystemEnv(Map systemEnv) { + this.systemEnv = systemEnv; + } + + /** + * Gets the file to use as the root dir of the managed process. + * @return the root dir. May be {@code null}. + */ + public File getJBossHome() { + return jbossHome; + } + + /** + * Sets the file to use as the root dir of the managed process. + * @param jbossHome the root dir. May be {@code null}. + */ + public void setJBossHome(File jbossHome) { + this.jbossHome = jbossHome; + } +} diff --git a/embedded/src/main/java/org/wildfly/core/embedded/spi/EmbeddedProcessState.java b/embedded/src/main/java/org/wildfly/core/embedded/spi/EmbeddedProcessState.java new file mode 100644 index 00000000000..8678f4f9053 --- /dev/null +++ b/embedded/src/main/java/org/wildfly/core/embedded/spi/EmbeddedProcessState.java @@ -0,0 +1,50 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.core.embedded.spi; + +/** + * Analogue to {@code org.jboss.as.controller.ControlledProcessState.State} for use + * in an embedded process. + */ +public enum EmbeddedProcessState { + /** + * The process is starting and its runtime state is being made consistent with its persistent configuration. + */ + STARTING("starting"), + /** + * The process is started, is running normally and has a runtime state consistent with its persistent configuration. + */ + RUNNING("running"), + /** + * The process requires a stop and re-start of its root service (but not a full process restart) in order to + * ensure stable operation and/or to bring its running state in line with its persistent configuration. A + * stop and restart of the root service (also known as a 'reload') will result in the removal of all other + * services and creation of new services based on the current configuration, so its affect on availability to + * handle external requests is similar to that of a full process restart. However, a reload can execute more + * quickly than a full process restart. + */ + RELOAD_REQUIRED("reload-required"), + /** + * The process must be terminated and replaced with a new process in order to ensure stable operation and/or to bring + * the running state in line with the persistent configuration. + */ + RESTART_REQUIRED("restart-required"), + /** The process is stopping. */ + STOPPING("stopping"), + /** The process is stopped */ + STOPPED("stopped"); + + private final String stringForm; + + EmbeddedProcessState(final String stringForm) { + this.stringForm = stringForm; + } + + @Override + public String toString() { + return stringForm; + } +} diff --git a/embedded/src/main/java/org/wildfly/core/embedded/spi/ProcessStateNotifier.java b/embedded/src/main/java/org/wildfly/core/embedded/spi/ProcessStateNotifier.java new file mode 100644 index 00000000000..848f23f706b --- /dev/null +++ b/embedded/src/main/java/org/wildfly/core/embedded/spi/ProcessStateNotifier.java @@ -0,0 +1,57 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.wildfly.core.embedded.spi; + +import java.beans.PropertyChangeListener; + +import org.jboss.msc.service.ServiceName; + +/** + * Allows callers to check the current {@link EmbeddedProcessState state} of the process + * and to register for notifications of state changes. + * + * @author Brian Stansberry + */ +public interface ProcessStateNotifier { + + /** Only for use within the WildFly kernel; may change or be removed at any time */ + ServiceName SERVICE_NAME = ServiceName.parse("org.wildfly.embedded.process-state-notifier"); + + /** + * Gets the current state of the controlled process. + * @return the current state. Will not be {@code null} + * + * @throws IllegalStateException if the process is not an embedded process + */ + EmbeddedProcessState getEmbeddedProcessState(); + + /** + * Register a listener for changes in the current state. The listener will be notified + * with a {@code PropertyChangeEvent} whose property name will be {@code embeddedState}. + * If listener is null, no exception is thrown and no action + * is taken. + * + * @param listener the listener + * + * @throws IllegalStateException if the process is not an embedded process + */ + void addProcessStateListener(PropertyChangeListener listener); + + + /** + * Remove a listener for changes in the current state. + * If listener was added more than once, + * it will be notified one less time after being removed. + * If listener is null, or was never added, no exception is + * thrown and no action is taken. + * + * @param listener the listener + * + * @throws IllegalStateException if the process is not an embedded process + */ + void removeProcessStateListener(PropertyChangeListener listener); +} + diff --git a/embedded/src/test/java/org/wildfly/core/embedded/EmbeddedServerFactorySetupUnitTestCase.java b/embedded/src/test/java/org/wildfly/core/embedded/EmbeddedServerFactorySetupUnitTestCase.java index 6cf3868148b..3275c72f37d 100644 --- a/embedded/src/test/java/org/wildfly/core/embedded/EmbeddedServerFactorySetupUnitTestCase.java +++ b/embedded/src/test/java/org/wildfly/core/embedded/EmbeddedServerFactorySetupUnitTestCase.java @@ -11,7 +11,6 @@ import java.nio.file.Paths; import java.util.Properties; -import org.jboss.as.server.ServerEnvironment; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -22,6 +21,10 @@ * @version $Revision: 1.1 $ */ public class EmbeddedServerFactorySetupUnitTestCase { + private static final String SERVER_BASE_DIR = "jboss.server.base.dir"; + private static final String SERVER_CONFIG_DIR = "jboss.server.config.dir"; + private static final String SERVER_DATA_DIR = "jboss.server.data.dir"; + final Path standardJBossHome; final Path alternativeServer; final Path alternativeDataDir; @@ -66,65 +69,65 @@ public void testEmbeddedRootNoOverrides() throws Exception { Assert.assertEquals(4, props.size()); Assert.assertEquals(embeddedRoot.toAbsolutePath().toString(), props.getProperty(EmbeddedStandaloneServerFactory.JBOSS_EMBEDDED_ROOT)); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_BASE_DIR, -1); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_DATA_DIR, 1); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_CONFIG_DIR, 1); + assertPropertyAndEmbeddedRootFile(props, SERVER_BASE_DIR, -1); + assertPropertyAndEmbeddedRootFile(props, SERVER_DATA_DIR, 1); + assertPropertyAndEmbeddedRootFile(props, SERVER_CONFIG_DIR, 1); } @Test public void testEmbeddedRootServerOverride() throws Exception { Properties props = new Properties(); props.setProperty(EmbeddedStandaloneServerFactory.JBOSS_EMBEDDED_ROOT, embeddedRoot.toAbsolutePath().toString()); - props.setProperty(ServerEnvironment.SERVER_BASE_DIR, alternativeServer.toAbsolutePath().toString()); + props.setProperty(SERVER_BASE_DIR, alternativeServer.toAbsolutePath().toString()); EmbeddedStandaloneServerFactory.setupCleanDirectories(standardJBossHome, props); Assert.assertEquals(4, props.size()); Assert.assertEquals(embeddedRoot.toAbsolutePath().toString(), props.getProperty(EmbeddedStandaloneServerFactory.JBOSS_EMBEDDED_ROOT)); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_BASE_DIR, -1); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_DATA_DIR, 2); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_CONFIG_DIR, 2); + assertPropertyAndEmbeddedRootFile(props, SERVER_BASE_DIR, -1); + assertPropertyAndEmbeddedRootFile(props, SERVER_DATA_DIR, 2); + assertPropertyAndEmbeddedRootFile(props, SERVER_CONFIG_DIR, 2); } @Test public void testDataAndConfigOverride() throws Exception { Properties props = new Properties(); props.setProperty(EmbeddedStandaloneServerFactory.JBOSS_EMBEDDED_ROOT, embeddedRoot.toAbsolutePath().toString()); - props.setProperty(ServerEnvironment.SERVER_DATA_DIR, alternativeDataDir.toAbsolutePath().toString()); - props.setProperty(ServerEnvironment.SERVER_CONFIG_DIR, alternativeConfigDir.toAbsolutePath().toString()); + props.setProperty(SERVER_DATA_DIR, alternativeDataDir.toAbsolutePath().toString()); + props.setProperty(SERVER_CONFIG_DIR, alternativeConfigDir.toAbsolutePath().toString()); EmbeddedStandaloneServerFactory.setupCleanDirectories(standardJBossHome, props); Assert.assertEquals(4, props.size()); Assert.assertEquals(embeddedRoot.toAbsolutePath().toString(), props.getProperty(EmbeddedStandaloneServerFactory.JBOSS_EMBEDDED_ROOT)); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_BASE_DIR, -1); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_DATA_DIR, 3); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_CONFIG_DIR, 4); + assertPropertyAndEmbeddedRootFile(props, SERVER_BASE_DIR, -1); + assertPropertyAndEmbeddedRootFile(props, SERVER_DATA_DIR, 3); + assertPropertyAndEmbeddedRootFile(props, SERVER_CONFIG_DIR, 4); } @Test public void testServerOverrideAndDataAndConfigOverride() throws Exception { Properties props = new Properties(); props.setProperty(EmbeddedStandaloneServerFactory.JBOSS_EMBEDDED_ROOT, embeddedRoot.toAbsolutePath().toString()); - props.setProperty(ServerEnvironment.SERVER_BASE_DIR, alternativeServer.toAbsolutePath().toString()); - props.setProperty(ServerEnvironment.SERVER_DATA_DIR, alternativeDataDir.toAbsolutePath().toString()); - props.setProperty(ServerEnvironment.SERVER_CONFIG_DIR, alternativeConfigDir.toAbsolutePath().toString()); + props.setProperty(SERVER_BASE_DIR, alternativeServer.toAbsolutePath().toString()); + props.setProperty(SERVER_DATA_DIR, alternativeDataDir.toAbsolutePath().toString()); + props.setProperty(SERVER_CONFIG_DIR, alternativeConfigDir.toAbsolutePath().toString()); EmbeddedStandaloneServerFactory.setupCleanDirectories(standardJBossHome, props); Assert.assertEquals(4, props.size()); Assert.assertEquals(embeddedRoot.toAbsolutePath().toString(), props.getProperty(EmbeddedStandaloneServerFactory.JBOSS_EMBEDDED_ROOT)); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_BASE_DIR, -1); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_DATA_DIR, 3); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_CONFIG_DIR, 4); + assertPropertyAndEmbeddedRootFile(props, SERVER_BASE_DIR, -1); + assertPropertyAndEmbeddedRootFile(props, SERVER_DATA_DIR, 3); + assertPropertyAndEmbeddedRootFile(props, SERVER_CONFIG_DIR, 4); } @Test public void testServerOverrideAndConfigOverride() throws Exception { Properties props = new Properties(); props.setProperty(EmbeddedStandaloneServerFactory.JBOSS_EMBEDDED_ROOT, embeddedRoot.toAbsolutePath().toString()); - props.setProperty(ServerEnvironment.SERVER_BASE_DIR, alternativeServer.toAbsolutePath().toString()); - props.setProperty(ServerEnvironment.SERVER_CONFIG_DIR, alternativeConfigDir.toAbsolutePath().toString()); + props.setProperty(SERVER_BASE_DIR, alternativeServer.toAbsolutePath().toString()); + props.setProperty(SERVER_CONFIG_DIR, alternativeConfigDir.toAbsolutePath().toString()); EmbeddedStandaloneServerFactory.setupCleanDirectories(standardJBossHome, props); Assert.assertEquals(4, props.size()); Assert.assertEquals(embeddedRoot.toAbsolutePath().toString(), props.getProperty(EmbeddedStandaloneServerFactory.JBOSS_EMBEDDED_ROOT)); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_BASE_DIR, -1); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_DATA_DIR, 2); - assertPropertyAndEmbeddedRootFile(props, ServerEnvironment.SERVER_CONFIG_DIR, 4); + assertPropertyAndEmbeddedRootFile(props, SERVER_BASE_DIR, -1); + assertPropertyAndEmbeddedRootFile(props, SERVER_DATA_DIR, 2); + assertPropertyAndEmbeddedRootFile(props, SERVER_CONFIG_DIR, 4); } private void assertPropertyAndEmbeddedRootFile(Properties props, String property, int id) { diff --git a/host-controller/src/main/java/org/jboss/as/host/controller/HostControllerBootstrap.java b/host-controller/src/main/java/org/jboss/as/host/controller/HostControllerBootstrap.java index 76494a2b717..dc23ce74e6e 100644 --- a/host-controller/src/main/java/org/jboss/as/host/controller/HostControllerBootstrap.java +++ b/host-controller/src/main/java/org/jboss/as/host/controller/HostControllerBootstrap.java @@ -43,7 +43,7 @@ public HostControllerBootstrap(final HostControllerEnvironment environment, fina */ public void bootstrap() throws Exception { final HostRunningModeControl runningModeControl = environment.getRunningModeControl(); - final ControlledProcessState processState = new ControlledProcessState(true); + final ControlledProcessState processState = new ControlledProcessState(true, false); shutdownHook.setControlledProcessState(processState); ServiceTarget target = serviceContainer.subTarget(); ProcessStateNotifier processStateNotifier = ControlledProcessStateService.addService(target, processState); diff --git a/host-controller/src/main/java/org/jboss/as/host/controller/HostControllerService.java b/host-controller/src/main/java/org/jboss/as/host/controller/HostControllerService.java index f8deb495d74..23979092afb 100644 --- a/host-controller/src/main/java/org/jboss/as/host/controller/HostControllerService.java +++ b/host-controller/src/main/java/org/jboss/as/host/controller/HostControllerService.java @@ -42,10 +42,13 @@ import org.jboss.as.server.logging.ServerLogger; import org.jboss.as.version.ProductConfig; import org.jboss.msc.service.Service; +import org.jboss.msc.service.ServiceActivator; +import org.jboss.msc.service.ServiceActivatorContext; import org.jboss.msc.service.ServiceBuilder; import org.jboss.msc.service.ServiceContainer; import org.jboss.msc.service.ServiceController; import org.jboss.msc.service.ServiceName; +import org.jboss.msc.service.ServiceRegistry; import org.jboss.msc.service.ServiceTarget; import org.jboss.msc.service.StartContext; import org.jboss.msc.service.StartException; @@ -80,11 +83,13 @@ public JBossThreadFactory run() { private final String authCode; private final CapabilityRegistry capabilityRegistry; private final ElapsedTime elapsedTime; - private volatile FutureServiceContainer futureContainer; + private final ServiceActivator[] extraServices; + private final FutureServiceContainer futureContainer; private volatile boolean everStopped; public HostControllerService(final HostControllerEnvironment environment, final HostRunningModeControl runningModeControl, - final String authCode, final ControlledProcessState processState, FutureServiceContainer futureContainer) { + final String authCode, final ControlledProcessState processState, + final FutureServiceContainer futureContainer, ServiceActivator... extraServices) { this.environment = environment; this.runningModeControl = runningModeControl; this.authCode = authCode; @@ -92,6 +97,7 @@ public HostControllerService(final HostControllerEnvironment environment, final this.elapsedTime = environment.getElapsedTime(); this.futureContainer = futureContainer; this.capabilityRegistry = new CapabilityRegistry(false); + this.extraServices = extraServices; } public HostControllerService(final HostControllerEnvironment environment, final HostRunningModeControl runningModeControl, @@ -197,6 +203,24 @@ public void start(StartContext context) throws StartException { DomainModelControllerService.addService(serviceTarget, environment, runningModeControl, processState, bootstrapListener, hostPathManagerService, capabilityRegistry, THREAD_GROUP); + + if (extraServices != null && extraServices.length > 0) { + final ServiceActivatorContext serviceActivatorContext = new ServiceActivatorContext() { + @Override + public ServiceTarget getServiceTarget() { + return serviceTarget; + } + + @Override + public ServiceRegistry getServiceRegistry() { + return context.getController().getServiceContainer(); + } + }; + + for(ServiceActivator activator : extraServices) { + activator.activate(serviceActivatorContext); + } + } } @Override diff --git a/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedHostControllerBootstrap.java b/host-controller/src/main/java/org/jboss/as/host/controller/embedded/EmbeddedHostControllerBootstrap.java similarity index 78% rename from embedded/src/main/java/org/wildfly/core/embedded/EmbeddedHostControllerBootstrap.java rename to host-controller/src/main/java/org/jboss/as/host/controller/embedded/EmbeddedHostControllerBootstrap.java index dc42b8db520..9af3ee61ddb 100644 --- a/embedded/src/main/java/org/wildfly/core/embedded/EmbeddedHostControllerBootstrap.java +++ b/host-controller/src/main/java/org/jboss/as/host/controller/embedded/EmbeddedHostControllerBootstrap.java @@ -3,25 +3,25 @@ * SPDX-License-Identifier: Apache-2.0 */ -package org.wildfly.core.embedded; +package org.jboss.as.host.controller.embedded; + -import java.beans.PropertyChangeListener; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.Future; import org.jboss.as.controller.ControlledProcessState; -import org.jboss.as.controller.ProcessStateNotifier; import org.jboss.as.controller.ControlledProcessStateService; import org.jboss.as.host.controller.HostControllerEnvironment; import org.jboss.as.host.controller.HostControllerService; import org.jboss.as.host.controller.HostRunningModeControl; import org.jboss.as.server.FutureServiceContainer; import org.jboss.as.server.jmx.RunningStateJmx; +import org.jboss.msc.service.ServiceActivator; import org.jboss.msc.service.ServiceContainer; import org.jboss.msc.service.ServiceTarget; /** - * Bootstrap of the Embedded HostController process. + * Embedded variant of {@link org.jboss.as.host.controller.HostControllerBootstrap}. TODO see if these can be unified. * * @author Ken Wills (c) 2015 Red Hat Inc. */ @@ -31,30 +31,29 @@ public class EmbeddedHostControllerBootstrap { private final ServiceContainer serviceContainer; private final HostControllerEnvironment environment; private final String authCode; - private final FutureServiceContainer futureContainer; - public EmbeddedHostControllerBootstrap(FutureServiceContainer futureContainer, final HostControllerEnvironment environment, final String authCode) { + public EmbeddedHostControllerBootstrap(final HostControllerEnvironment environment, final String authCode) { this.environment = environment; this.authCode = authCode; this.shutdownHook = new ShutdownHook(); this.serviceContainer = shutdownHook.register(); - this.futureContainer = futureContainer; } - public FutureServiceContainer bootstrap(PropertyChangeListener processStateListener, AtomicReference notifierRef) throws Exception { + public Future bootstrap(ServiceActivator... extraServices) throws Exception { try { final HostRunningModeControl runningModeControl = environment.getRunningModeControl(); - final ControlledProcessState processState = new ControlledProcessState(true); + final ControlledProcessState processState = new ControlledProcessState(true, true); shutdownHook.setControlledProcessState(processState); ServiceTarget target = serviceContainer.subTarget(); - final ProcessStateNotifier processStateNotifier = ControlledProcessStateService.addService(target, processState); - processStateNotifier.addPropertyChangeListener(processStateListener); - notifierRef.set(processStateNotifier); + final org.jboss.as.controller.ProcessStateNotifier processStateNotifier = ControlledProcessStateService.addService(target, processState); RunningStateJmx.registerMBean(processStateNotifier, null, runningModeControl, false); - final HostControllerService hcs = new HostControllerService(environment, runningModeControl, authCode, processState, futureContainer); + + final FutureServiceContainer futureServiceContainer = new FutureServiceContainer(); + final HostControllerService hcs = new HostControllerService(environment, runningModeControl, authCode, + processState, futureServiceContainer, extraServices); target.addService(HostControllerService.HC_SERVICE_NAME, hcs).install(); - return futureContainer; + return futureServiceContainer; } catch (RuntimeException | Error e) { shutdownHook.run(); throw e; diff --git a/host-controller/src/main/java/org/jboss/as/host/controller/embedded/HostEmbeddedProcessBootstrap.java b/host-controller/src/main/java/org/jboss/as/host/controller/embedded/HostEmbeddedProcessBootstrap.java new file mode 100644 index 00000000000..2ca0bd3fbb3 --- /dev/null +++ b/host-controller/src/main/java/org/jboss/as/host/controller/embedded/HostEmbeddedProcessBootstrap.java @@ -0,0 +1,99 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.host.controller.embedded; + +import java.io.File; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.Random; +import java.util.concurrent.Future; + +import org.jboss.as.controller.ProcessType; +import org.jboss.as.host.controller.HostControllerEnvironment; +import org.jboss.as.host.controller.Main; +import org.jboss.as.server.ElapsedTime; +import org.jboss.as.server.embedded.AbstractEmbeddedProcessBootstrap; +import org.jboss.msc.service.ServiceActivator; +import org.jboss.msc.service.ServiceContainer; +import org.wildfly.core.embedded.spi.EmbeddedProcessBootstrapConfiguration; + +public final class HostEmbeddedProcessBootstrap extends AbstractEmbeddedProcessBootstrap { + + private static final String MODULE_PATH = "-mp"; + private static final String PC_ADDRESS = "--pc-address"; + private static final String PC_PORT = "--pc-port"; + + private static final String SYSPROP_KEY_JBOSS_DOMAIN_BASE_DIR = "jboss.domain.base.dir"; + private static final String SYSPROP_KEY_JBOSS_DOMAIN_CONFIG_DIR = "jboss.domain.config.dir"; + private static final String SYSPROP_KEY_JBOSS_DOMAIN_DEPLOYMENT_DIR = "jboss.domain.deployment.dir"; + private static final String SYSPROP_KEY_JBOSS_DOMAIN_TEMP_DIR = "jboss.domain.temp.dir"; + private static final String SYSPROP_KEY_JBOSS_DOMAIN_LOG_DIR = "jboss.domain.log.dir"; + + private static final String[] DOMAIN_KEYS = { + SYSPROP_KEY_JBOSS_DOMAIN_BASE_DIR, SYSPROP_KEY_JBOSS_DOMAIN_CONFIG_DIR, SYSPROP_KEY_JBOSS_DOMAIN_DEPLOYMENT_DIR, + SYSPROP_KEY_JBOSS_DOMAIN_TEMP_DIR, SYSPROP_KEY_JBOSS_DOMAIN_LOG_DIR, SYSPROP_KEY_JBOSS_DOMAIN_CONFIG_DIR + }; + + @Override + public Type getType() { + return Type.HOST_CONTROLLER; + } + + @Override + protected Future bootstrapEmbeddedProcess(ElapsedTime elapsedTime, + EmbeddedProcessBootstrapConfiguration configuration, + ServiceActivator... extraServices) throws Exception { + // Determine the HostControllerEnvironment + HostControllerEnvironment environment = createHostControllerEnvironment(configuration.getJBossHome(), configuration.getCmdArgs(), elapsedTime); + + final byte[] authBytes = new byte[16]; + new Random(new SecureRandom().nextLong()).nextBytes(authBytes); + final String pcAuthCode = Base64.getEncoder().encodeToString(authBytes); + + EmbeddedHostControllerBootstrap hostControllerBootstrap = new EmbeddedHostControllerBootstrap(environment, pcAuthCode); + try { + return hostControllerBootstrap.bootstrap(extraServices); + } catch (Exception ex) { + hostControllerBootstrap.failed(); + throw ex; + } + } + + private static HostControllerEnvironment createHostControllerEnvironment(File jbossHome, String[] cmdargs, + ElapsedTime elapsedTime) { + SecurityActions.setPropertyPrivileged(HostControllerEnvironment.HOME_DIR, jbossHome.getAbsolutePath()); + + List cmds = new ArrayList<>(Arrays.asList(cmdargs)); + + // these are for compatibility with Main.determineEnvironment / HostControllerEnvironment + // Once WFCORE-938 is resolved, --admin-only will allow a connection back to the DC for slaves, + // and support a method for setting the domain master address outside of -Djboss.domain.primary.address + // so we'll probably need a command line argument for this if it's not specified as a system prop + if (SecurityActions.getPropertyPrivileged(HostControllerEnvironment.JBOSS_DOMAIN_PRIMARY_ADDRESS, null) == null) { + SecurityActions.setPropertyPrivileged(HostControllerEnvironment.JBOSS_DOMAIN_PRIMARY_ADDRESS, "127.0.0.1"); + } + cmds.add(MODULE_PATH); + cmds.add(SecurityActions.getPropertyPrivileged("module.path", "")); + cmds.add(PC_ADDRESS); + cmds.add("0"); + cmds.add(PC_PORT); + cmds.add("0"); + // this used to be set in the embedded-hc specific env setup, WFCORE-938 will add support for --admin-only=false + cmds.add("--admin-only"); + + for (final String prop : DOMAIN_KEYS) { + // if we've started with any jboss.domain.base.dir etc, copy those in here. + String value = SecurityActions.getPropertyPrivileged(prop, null); + if (value != null) + cmds.add("-D" + prop + "=" + value); + } + return Main.determineEnvironment(cmds.toArray(new String[0]), elapsedTime, ProcessType.EMBEDDED_HOST_CONTROLLER).getHostControllerEnvironment(); + } +} + diff --git a/host-controller/src/main/java/org/jboss/as/host/controller/embedded/SecurityActions.java b/host-controller/src/main/java/org/jboss/as/host/controller/embedded/SecurityActions.java new file mode 100644 index 00000000000..33ccca25896 --- /dev/null +++ b/host-controller/src/main/java/org/jboss/as/host/controller/embedded/SecurityActions.java @@ -0,0 +1,74 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.host.controller.embedded; + +import static java.lang.System.getProperty; +import static java.lang.System.getSecurityManager; +import static java.lang.System.setProperty; +import static java.security.AccessController.doPrivileged; + +import java.security.PrivilegedAction; + +/** + * Privileged actions used by more than one class in this module. + * + * @author Brian Stansberry + * @author James R. Perkins + */ +final class SecurityActions { + static String getPropertyPrivileged(final String name, final String def) { + final SecurityManager sm = getSecurityManager(); + if (sm == null) { + return getProperty(name, def); + } + return doPrivileged(new PrivilegedAction() { + @Override + public String run() { + return getProperty(name, def); + } + }); + } + + static String setPropertyPrivileged(final String name, final String value) { + final SecurityManager sm = getSecurityManager(); + if (sm == null) { + return setProperty(name, value); + } else { + return doPrivileged(new PrivilegedAction() { + @Override + public String run() { + return setProperty(name, value); + } + }); + } + } + + static ClassLoader getTccl() { + if (getSecurityManager() == null) { + return Thread.currentThread().getContextClassLoader(); + } + return doPrivileged(new PrivilegedAction() { + @Override + public ClassLoader run() { + return Thread.currentThread().getContextClassLoader(); + } + }); + } + + static void setTccl(final ClassLoader cl) { + if (getSecurityManager() == null) { + Thread.currentThread().setContextClassLoader(cl); + } else { + doPrivileged(new PrivilegedAction() { + @Override + public Object run() { + Thread.currentThread().setContextClassLoader(cl); + return null; + } + }); + } + } +} diff --git a/host-controller/src/main/resources/META-INF/services/org.wildfly.core.embedded.spi.EmbeddedProcessBootstrap b/host-controller/src/main/resources/META-INF/services/org.wildfly.core.embedded.spi.EmbeddedProcessBootstrap new file mode 100644 index 00000000000..502b10dc4aa --- /dev/null +++ b/host-controller/src/main/resources/META-INF/services/org.wildfly.core.embedded.spi.EmbeddedProcessBootstrap @@ -0,0 +1,6 @@ +# +# Copyright The WildFly Authors +# SPDX-License-Identifier: Apache-2.0 +# + +org.jboss.as.host.controller.embedded.HostEmbeddedProcessBootstrap \ No newline at end of file diff --git a/server/src/main/java/org/jboss/as/server/BootstrapImpl.java b/server/src/main/java/org/jboss/as/server/BootstrapImpl.java index f9b3da13b29..6b68108a51a 100644 --- a/server/src/main/java/org/jboss/as/server/BootstrapImpl.java +++ b/server/src/main/java/org/jboss/as/server/BootstrapImpl.java @@ -95,7 +95,8 @@ private AsyncFuture internalBootstrap(final Configuration conf } final FutureServiceContainer future = new FutureServiceContainer(container); final ServiceTarget tracker = container.subTarget(); - final ControlledProcessState processState = new ControlledProcessState(true); + final boolean embedded = configuration.getServerEnvironment().getLaunchType().getProcessType().isEmbedded(); + final ControlledProcessState processState = new ControlledProcessState(true, embedded); shutdownHook.setControlledProcessState(processState); ProcessStateNotifier processStateNotifier = ControlledProcessStateService.addService(tracker, processState); ServerSuspendController suspendController = new SuspendController(); diff --git a/server/src/main/java/org/jboss/as/server/ServerEnvironmentWrapper.java b/server/src/main/java/org/jboss/as/server/ServerEnvironmentWrapper.java index c31302f527e..189143d1a41 100644 --- a/server/src/main/java/org/jboss/as/server/ServerEnvironmentWrapper.java +++ b/server/src/main/java/org/jboss/as/server/ServerEnvironmentWrapper.java @@ -16,8 +16,8 @@ enum ServerEnvironmentStatus { ERROR // problematic abort } - private ServerEnvironment serverEnvironment; - private ServerEnvironmentStatus serverEnvironmentStatus; + private final ServerEnvironment serverEnvironment; + private final ServerEnvironmentStatus serverEnvironmentStatus; private ServerEnvironmentWrapper(ServerEnvironment serverEnvironment, ServerEnvironmentStatus serverEnvironmentStatus) { this.serverEnvironment = serverEnvironment; diff --git a/server/src/main/java/org/jboss/as/server/embedded/AbstractEmbeddedProcessBootstrap.java b/server/src/main/java/org/jboss/as/server/embedded/AbstractEmbeddedProcessBootstrap.java new file mode 100644 index 00000000000..38ecb3bca58 --- /dev/null +++ b/server/src/main/java/org/jboss/as/server/embedded/AbstractEmbeddedProcessBootstrap.java @@ -0,0 +1,159 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.server.embedded; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.jboss.as.server.ElapsedTime; +import org.jboss.as.server.SystemExiter; +import org.jboss.as.server.logging.ServerLogger; +import org.jboss.msc.Service; +import org.jboss.msc.service.ServiceActivator; +import org.jboss.msc.service.ServiceActivatorContext; +import org.jboss.msc.service.ServiceBuilder; +import org.jboss.msc.service.ServiceContainer; +import org.jboss.msc.service.ServiceController; +import org.jboss.msc.service.ServiceName; +import org.jboss.msc.service.StartContext; +import org.jboss.msc.service.StopContext; +import org.wildfly.core.embedded.spi.BootstrappedEmbeddedProcess; +import org.wildfly.core.embedded.spi.EmbeddedModelControllerClientFactory; +import org.wildfly.core.embedded.spi.EmbeddedProcessBootstrap; +import org.wildfly.core.embedded.spi.EmbeddedProcessBootstrapConfiguration; +import org.wildfly.core.embedded.spi.ProcessStateNotifier; + +/** + * Base {@link EmbeddedProcessBootstrap} implementation that provides the bootstrap + * behavior that is common to an embedded {@code StandaloneServer} and {@code HostController}. + */ +public abstract class AbstractEmbeddedProcessBootstrap implements EmbeddedProcessBootstrap { + + @Override + public BootstrappedEmbeddedProcess startup(EmbeddedProcessBootstrapConfiguration configuration) throws Exception { + ElapsedTime elapsedTime = ElapsedTime.startingFromNow(); + + // Take control of server use of System.exit + SystemExiter.initialize(new SystemExiter.Exiter() { + @Override + public void exit(int status) { + configuration.getSystemExitCallback().accept(status); + } + }); + + // As part of bootstrap, install services to capture the ProcessStateNotifier and EmbeddedModelControllerClientFactory. + // These are started async, so use a latch to await their start. + CountDownLatch serviceAwait = new CountDownLatch(2); + AtomicReference notifierRef = new AtomicReference<>(); + ServiceActivator notifierCapture = ctx -> captureValue(ctx, ProcessStateNotifier.SERVICE_NAME, + new CountdownConsumer<>(serviceAwait, notifierRef)); + AtomicReference clientFactoryRef = new AtomicReference<>(); + ServiceActivator clientFactoryCapture = ctx -> captureValue(ctx, EmbeddedModelControllerClientFactory.SERVICE_NAME, + new CountdownConsumer<>(serviceAwait, clientFactoryRef)); + + Future future = bootstrapEmbeddedProcess(elapsedTime, configuration, notifierCapture, clientFactoryCapture); + if (future == null) { + // TODO why do we ignore this? + return null; + } + + final ServiceContainer serviceContainer = future.get(); + try { + if (!serviceAwait.await(30, TimeUnit.SECONDS)) { + StringBuilder sb = new StringBuilder(); + ProcessStateNotifier psn = notifierRef.get(); + if (psn == null) { + sb.append(ProcessStateNotifier.class.getSimpleName()); + } + if (clientFactoryRef.get() == null) { + if (psn == null) { + sb.append(" "); + } + sb.append(EmbeddedModelControllerClientFactory.class.getSimpleName()); + } + ServerLogger.ROOT_LOGGER.embeddedProcessServicesUnavailable(30, sb.toString()); + } + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + return null; + } + + final ProcessStateNotifier psn = notifierRef.get(); + return new BootstrappedEmbeddedProcess() { + @Override + public ServiceContainer getServiceContainer() { + return serviceContainer; + } + + @Override + public ProcessStateNotifier getProcessStateNotifier() { + return psn; + } + + @Override + public EmbeddedModelControllerClientFactory getModelControllerClientFactory() { + return clientFactoryRef.get(); + } + + @Override + public void close() { + SystemExiter.initialize(SystemExiter.Exiter.DEFAULT); + } + }; + } + + /** + * Subclass-specific bootstrap logic. Implementations should use the provided inputs to perform their bootstrap. + * @param elapsedTime tracker for elapsed time since embedded process 'start'. Cannot be {@code null}. + * @param configuration configuration information for starting. Cannot be {@code null}. + * @param extraServices activators for other services to start + * @return future from which the MSC service container can be obtained + * @throws Exception if one occurs + */ + protected abstract Future bootstrapEmbeddedProcess(ElapsedTime elapsedTime, + EmbeddedProcessBootstrapConfiguration configuration, + ServiceActivator... extraServices) + throws Exception; + + + private static void captureValue(ServiceActivatorContext ctx, ServiceName toCapture, Consumer captor) { + ServiceBuilder sb = ctx.getServiceTarget().addService(); + final Supplier result = sb.requires(toCapture); + sb.setInstance(new Service() { + @Override + public void start(StartContext context) { + captor.accept(result.get()); + context.getController().setMode(ServiceController.Mode.REMOVE); + } + + @Override + public void stop(StopContext context) { + } + }); + sb.install(); + } + + private static class CountdownConsumer implements Consumer { + + private final CountDownLatch latch; + private final AtomicReference reference; + + private CountdownConsumer(CountDownLatch latch, AtomicReference reference) { + this.latch = latch; + this.reference = reference; + } + + @Override + public void accept(T t) { + reference.set(t); + latch.countDown(); + } + } +} \ No newline at end of file diff --git a/server/src/main/java/org/jboss/as/server/embedded/StandaloneEmbeddedProcessBootstrap.java b/server/src/main/java/org/jboss/as/server/embedded/StandaloneEmbeddedProcessBootstrap.java new file mode 100644 index 00000000000..4e72a82ed9b --- /dev/null +++ b/server/src/main/java/org/jboss/as/server/embedded/StandaloneEmbeddedProcessBootstrap.java @@ -0,0 +1,51 @@ +/* + * Copyright The WildFly Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.jboss.as.server.embedded; + +import java.util.List; +import java.util.concurrent.Future; + +import org.jboss.as.server.Bootstrap; +import org.jboss.as.server.ElapsedTime; +import org.jboss.as.server.Main; +import org.jboss.as.server.ServerEnvironment; +import org.jboss.msc.service.ServiceActivator; +import org.jboss.msc.service.ServiceContainer; +import org.wildfly.core.embedded.spi.EmbeddedProcessBootstrapConfiguration; + +public final class StandaloneEmbeddedProcessBootstrap extends AbstractEmbeddedProcessBootstrap { + + + @Override + public Type getType() { + return Type.STANDALONE_SERVER; + } + + @Override + protected Future bootstrapEmbeddedProcess(ElapsedTime elapsedTime, + EmbeddedProcessBootstrapConfiguration configuration, + ServiceActivator... extraServices) { + + + // Determine the ServerEnvironment + ServerEnvironment serverEnvironment = Main.determineEnvironment(configuration.getCmdArgs(), + configuration.getSystemProperties(), configuration.getSystemEnv(), + ServerEnvironment.LaunchType.EMBEDDED, elapsedTime).getServerEnvironment(); + if (serverEnvironment == null) { + // Nothing to do + return null; + } + Bootstrap bootstrap = Bootstrap.Factory.newInstance(); + try { + Bootstrap.Configuration bootstrapConfiguration = new Bootstrap.Configuration(serverEnvironment); + bootstrapConfiguration.setModuleLoader(configuration.getModuleLoader()); + return bootstrap.startup(bootstrapConfiguration, List.of(extraServices)); + } catch (Exception ex) { + bootstrap.failed(); + throw ex; + } + } +} diff --git a/server/src/main/java/org/jboss/as/server/logging/ServerLogger.java b/server/src/main/java/org/jboss/as/server/logging/ServerLogger.java index 8d1394a897d..d72d840976c 100644 --- a/server/src/main/java/org/jboss/as/server/logging/ServerLogger.java +++ b/server/src/main/java/org/jboss/as/server/logging/ServerLogger.java @@ -1474,6 +1474,10 @@ default void suspendingServer(long timeout, TimeUnit unit) { @Message(id = 309, value = "%s stability level is not supported in %s") IllegalArgumentException unsupportedStability(Stability stability, String name); + @Message(id = 310, value = "Embedded process services are not available after %d seconds: %s") + @LogMessage(level = Logger.Level.ERROR) + void embeddedProcessServicesUnavailable(int timeout, String unavailable); + //////////////////////////////////////////////// //Messages without IDs diff --git a/server/src/main/resources/META-INF/services/org.wildfly.core.embedded.spi.EmbeddedProcessBootstrap b/server/src/main/resources/META-INF/services/org.wildfly.core.embedded.spi.EmbeddedProcessBootstrap new file mode 100644 index 00000000000..eac2f1116d0 --- /dev/null +++ b/server/src/main/resources/META-INF/services/org.wildfly.core.embedded.spi.EmbeddedProcessBootstrap @@ -0,0 +1,6 @@ +# +# Copyright The WildFly Authors +# SPDX-License-Identifier: Apache-2.0 +# + +org.jboss.as.server.embedded.StandaloneEmbeddedProcessBootstrap \ No newline at end of file