Package org.jboss.as.server

Source Code of org.jboss.as.server.ServerControllerImpl$ServerStateMonitorListener

/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package org.jboss.as.server;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CANCELLED;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATION_HEADERS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ROLLBACK_FAILURE_DESCRIPTION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ROLLBACK_ON_RUNTIME_FAILURE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ROLLED_BACK;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS;

import java.io.InputStream;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

import org.jboss.as.controller.BasicModelController;
import org.jboss.as.controller.ModelController;
import org.jboss.as.controller.ModelProvider;
import org.jboss.as.controller.ModelUpdateOperationHandler;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationContextImpl;
import org.jboss.as.controller.OperationControllerContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.OperationHandler;
import org.jboss.as.controller.OperationResult;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.ResultHandler;
import org.jboss.as.controller.RuntimeOperationContext;
import org.jboss.as.controller.RuntimeTask;
import org.jboss.as.controller.RuntimeTaskContext;
import org.jboss.as.controller.client.Operation;
import org.jboss.as.controller.client.OperationAttachments;
import org.jboss.as.controller.client.OperationBuilder;
import org.jboss.as.controller.persistence.ConfigurationPersisterProvider;
import org.jboss.as.controller.persistence.ExtensibleConfigurationPersister;
import org.jboss.as.controller.registry.ModelNodeRegistration;
import org.jboss.as.server.controller.descriptions.ServerDescriptionProviders;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.server.deployment.Phase;
import org.jboss.as.server.deployment.api.DeploymentRepository;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.jboss.logging.Logger;
import org.jboss.msc.service.AbstractServiceListener;
import org.jboss.msc.service.DelegatingServiceRegistry;
import org.jboss.msc.service.ServiceContainer;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceListener;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceRegistry;
import org.jboss.msc.service.ServiceTarget;
import org.jboss.msc.service.StartException;

/**
* @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
*/
class ServerControllerImpl extends BasicModelController implements ServerController {

    private static final Logger log = Logger.getLogger("org.jboss.as.server");

    private final ExecutorService executorService;
    private final ServiceTarget serviceTarget;
    private final ServiceRegistry serviceRegistry;
    private final ServerEnvironment serverEnvironment;
    private final AtomicInteger stamp = new AtomicInteger(0);
    private final AtomicStampedReference<State> state = new AtomicStampedReference<State>(null, 0);
    private final ExtensibleConfigurationPersister extensibleConfigurationPersister;
    private final DeploymentRepository deploymentRepository;
    private final EnumMap<Phase, SortedSet<RegisteredProcessor>> deployers = new EnumMap<Phase, SortedSet<RegisteredProcessor>>(Phase.class);
    private final ServerStateMonitorListener serverStateMonitorListener;

    ServerControllerImpl(final ServiceContainer container, final ServiceTarget serviceTarget, final ServerEnvironment serverEnvironment,
            final ExtensibleConfigurationPersister configurationPersister, final DeploymentRepository deploymentRepository,
            final ExecutorService executorService) {
        super(ServerControllerModelUtil.createCoreModel(), configurationPersister, ServerDescriptionProviders.ROOT_PROVIDER);
        this.serviceTarget = serviceTarget;
        extensibleConfigurationPersister = configurationPersister;
        this.serverEnvironment = serverEnvironment;
        this.deploymentRepository = deploymentRepository;
        serviceRegistry = new DelegatingServiceRegistry(container);
        this.executorService = executorService;
        serverStateMonitorListener = new ServerStateMonitorListener(container);
    }

    void init() {
        state.set(State.STARTING, stamp.incrementAndGet());

        registerInternalOperations();

        // Build up the core model registry
        ServerControllerModelUtil.initOperations(getRegistry(), deploymentRepository, extensibleConfigurationPersister, serverEnvironment);

        deployers.clear();
        for (Phase phase : Phase.values()) {
            deployers.put(phase, new ConcurrentSkipListSet<RegisteredProcessor>());
        }
    }

    EnumMap<Phase, SortedSet<RegisteredProcessor>> finishBoot() {
        state.set(State.RUNNING, stamp.incrementAndGet());
        EnumMap<Phase, SortedSet<RegisteredProcessor>> copy = new EnumMap<Phase, SortedSet<RegisteredProcessor>>(Phase.class);
        for (Map.Entry<Phase, SortedSet<RegisteredProcessor>> entry : deployers.entrySet()) {
            copy.put(entry.getKey(), new ConcurrentSkipListSet<RegisteredProcessor>(entry.getValue()));
        }
        return copy;
    }

    /** {@inheritDoc} */
    @Override
    public ServerEnvironment getServerEnvironment() {
        return serverEnvironment;
    }

    /**
     * Get this server's service container registry.
     *
     * @return the container registry
     */
    @Override
    public ServiceRegistry getServiceRegistry() {
        return serviceRegistry;
    }

    /**
     * Get the server controller state.
     *
     * @return the state
     */
    @Override
    public State getState() {
        return state.getReference();
    }

    ServiceListener<Object> getServerStateMonitorListener() {
        return serverStateMonitorListener;
    }

    /** {@inheritDoc} */
    @Override
    protected OperationContext getOperationContext(final ModelNode subModel, final OperationHandler operationHandler, final Operation operation, final ModelProvider modelProvider) {
        if (operationHandler instanceof BootOperationHandler) {
            if (getState() == State.STARTING) {
                return new BootContextImpl(subModel, getRegistry(), deployers, modelProvider, operation);
            } else {
                state.set(State.RESTART_REQUIRED, stamp.incrementAndGet());
                return super.getOperationContext(subModel, operationHandler, operation, modelProvider);
            }
        } else if (!(getState() == State.RESTART_REQUIRED && operationHandler instanceof ModelUpdateOperationHandler)) {
            return new ServerOperationContextImpl(this, getRegistry(), subModel, modelProvider, operation);
        } else {
            return super.getOperationContext(subModel, operationHandler, operation, modelProvider);
        }
    }

    @Override
    protected OperationResult doExecute(OperationContext context, Operation operation, OperationHandler operationHandler, ResultHandler resultHandler, PathAddress address,
            final OperationControllerContext operationControllerContext) throws OperationFailedException {
        boolean rollback = isRollbackOnRuntimeFailure(context, operation.getOperation());
        RollbackAwareResultHandler rollbackAwareHandler = new RollbackAwareResultHandler(resultHandler);
        final OperationResult result = super.doExecute(context, operation, operationHandler, rollbackAwareHandler, address, operationControllerContext);
        if(context instanceof ServerOperationContextImpl) {
            if (rollback) {
                rollbackAwareHandler.setRollbackOperation(result.getCompensatingOperation());
                // TODO deal with Cancellable as well
            }
            final ServerOperationContextImpl serverOperationContext = ServerOperationContextImpl.class.cast(context);
            if(serverOperationContext.getRuntimeTask() != null) {

                // Make sure we've settled and generated a report post-boot so boot issues don't show up as op issues
                if (!serverStateMonitorListener.isFirstReportComplete() && state.getReference() != State.STARTING) {
                    serverStateMonitorListener.awaitUninterruptibly();
                }

                try {
                    serverOperationContext.getRuntimeTask().execute(new RuntimeTaskContext() {
                        @Override
                        public ServiceTarget getServiceTarget() {
                            return serviceTarget;
                        }

                        @Override
                        public ServiceRegistry getServiceRegistry() {
                            return serviceRegistry;
                        }
                    });
                } catch (OperationFailedException e) {
                    rollbackAwareHandler.handleFailed(e.getFailureDescription());
                } catch (Exception e) {
                    rollbackAwareHandler.handleFailed(new ModelNode().set(e.toString()));
                }

                ModelNode serverStateChangeReport = null;
                if (state.getReference() != State.STARTING) {
                    serverStateChangeReport = serverStateMonitorListener.awaitUninterruptibly();
                }
                if (serverStateChangeReport != null && !rollbackAwareHandler.isTerminalState()) {
                    rollbackAwareHandler.handleFailed(serverStateChangeReport);
                }
            }

            if (!rollbackAwareHandler.isTerminalState()) {
                rollbackAwareHandler.notifySuccess();
            }
        }
        // else this is a step in a composite op and the ServerMultiStepOperationController will handle it

        return result;
    }

    /** {@inheritDoc} */
    @Override
    protected void persistConfiguration(final ModelNode model, final ConfigurationPersisterProvider configurationPersisterFactory) {
        // do not persist during startup
        if (getState() != State.STARTING) {
            super.persistConfiguration(model, configurationPersisterFactory);
        }
    }

    @Override
    protected boolean isReadOnly(OperationHandler operationHandler) {
        // Minor optimization: Assume nothing is RO during boot
        if (getState() == State.STARTING) {
            return false;
        }
        return super.isReadOnly(operationHandler);
    }

    @Override
    protected MultiStepOperationController getMultiStepOperationController(final Operation operation, final ResultHandler handler,
            final OperationControllerContext operationControllerContext) throws OperationFailedException {
        return new ServerMultiStepOperationController(operation, handler, operationControllerContext);
    }

    private boolean isRollbackOnRuntimeFailure(OperationContext context, ModelNode operation) {
        return context instanceof ServerOperationContextImpl &&
            (!operation.hasDefined(OPERATION_HEADERS) || !operation.get(OPERATION_HEADERS).hasDefined(ROLLBACK_ON_RUNTIME_FAILURE)
                    || operation.get(OPERATION_HEADERS, ROLLBACK_ON_RUNTIME_FAILURE).asBoolean());
    }

    private static <T> Set<T> identitySet() {
        return Collections.newSetFromMap(new IdentityHashMap<T, Boolean>());
    }

    /**
     * A service listener to track container status.  Must be present when the service is created, or results will
     * be unpredictable.
     */
    private class ServerStateMonitorListener extends AbstractServiceListener<Object> {
        private final ServiceRegistry serviceRegistry;
        private final AtomicInteger busyServiceCount = new AtomicInteger();

        // protected by "this"
        /** Failed controllers pending tick reaching zero */
        private final Map<ServiceController<?>, String> failedControllers = new IdentityHashMap<ServiceController<?>, String>();
        /** Failed controllers as of the last time tick reached zero */
        private final Map<ServiceController<?>, String> latestSettledFailedControllers = new IdentityHashMap<ServiceController<?>, String>();
        /** Failed controllers as of the last time getServerStateChangeReport() was called */
        private final Map<ServiceController<?>, String> lastReportFailedControllers = new IdentityHashMap<ServiceController<?>, String>();
        /** Services with missing deps */
        private final Set<ServiceController<?>> servicesWithMissingDeps = identitySet();
        /** Services with missing deps as of the last time tick reached zero */
        private Set<ServiceName> previousMissingDepSet = new HashSet<ServiceName>();
        /** Services with missing deps as of the last time getServerStateChangeReport() was called */
        private final Set<ServiceName> lastReportMissingDepSet = new TreeSet<ServiceName>();
        /** Flag indicating we've created our first post-boot report */
        private volatile boolean firstReportDone;

        ServerStateMonitorListener(final ServiceRegistry registry) {
            serviceRegistry = registry;
        }

        @Override
        public void listenerAdded(final ServiceController<?> controller) {
            if (controller.getName().equals(Services.JBOSS_SERVER_CONTROLLER)) {
                controller.removeListener(this);
            } else {
                untick();
            }
        }

        @Override
        public void serviceWaiting(final ServiceController<?> controller) {
            tick();
        }

        @Override
        public void serviceWaitingCleared(final ServiceController<?> controller) {
            untick();
        }

        @Override
        public void serviceWontStart(final ServiceController<?> controller) {
            tick();
        }

        @Override
        public void serviceWontStartCleared(final ServiceController<?> controller) {
            untick();
        }

        @Override
        public void dependencyProblem(final ServiceController<?> controller) {
            tick();
        }

        @Override
        public void dependencyProblemCleared(final ServiceController<?> controller) {
            untick();
        }

        @Override
        public void serviceStarting(final ServiceController<?> controller) {
            // no tick
        }

        @Override
        public void serviceStarted(final ServiceController<?> controller) {
            tick();
        }

        @Override
        public void serviceFailed(final ServiceController<?> controller, final StartException reason) {
            synchronized (this) {
                failedControllers.put(controller, reason.toString());
            }
            tick();
        }

        @Override
        public void serviceRemoved(final ServiceController<?> controller) {
            synchronized (this) {
                failedControllers.remove(controller);
                servicesWithMissingDeps.remove(controller);
            }
            tick();
        }

        @Override
        public void serviceStopRequested(final ServiceController<?> controller) {
            untick();
        }

        @Override
        public void serviceStopRequestCleared(final ServiceController<?> controller) {
            tick();
        }

        @Override
        public void serviceStopping(final ServiceController<?> controller) {
            // no tick
        }

        @Override
        public void failedServiceStarting(final ServiceController<?> controller) {
            synchronized (this) {
                failedControllers.remove(controller);
            }
            untick();
        }

        @Override
        public void failedServiceStopped(final ServiceController<?> controller) {
            synchronized (this) {
                failedControllers.remove(controller);
            }
            untick();
        }

        @Override
        public void immediateDependencyAvailable(final ServiceController<?> controller) {
            synchronized (this) {
                servicesWithMissingDeps.remove(controller);
            }
        }

        @Override
        public void immediateDependencyUnavailable(final ServiceController<?> controller) {
            synchronized (this) {
                servicesWithMissingDeps.add(controller);
            }
        }

        ModelNode awaitUninterruptibly() {
            boolean intr = false;
            // todo - atomically return the last status summary, or something
            try {
                synchronized (this) {
                    while (busyServiceCount.get() > 0) {
                        try {
                            wait();
                        } catch (InterruptedException e) {
                            intr = true;
                        }
                    }

                    return getServerStateChangeReport();
                }
            } finally {
                if (intr) {
                    Thread.currentThread().interrupt();
                }
            }
        }

        /**
         * Tick down the count, triggering a deployment status report when the count is zero.
         */
        private void tick() {
            int tick = busyServiceCount.decrementAndGet();
//            System.out.println("TICK -> " + tick + " (" + Thread.currentThread().getStackTrace()[2].getMethodName() + ") -> " + tickController);
            if (tick == 0) {
                synchronized (this) {
                    notifyAll();
                    final Set<ServiceName> missingDeps = new HashSet<ServiceName>();
                    for (ServiceController<?> controller : servicesWithMissingDeps) {
                        missingDeps.addAll(controller.getImmediateUnavailableDependencies());
                    }

                    final Set<ServiceName> previousMissing = previousMissingDepSet;

                    // no longer missing deps...
                    final Set<ServiceName> noLongerMissing = new TreeSet<ServiceName>();
                    for (ServiceName name : previousMissing) {
                        if (! missingDeps.contains(name)) {
                            noLongerMissing.add(name);
                        }
                    }

                    // newly missing deps
                    final Set<ServiceName> newlyMissing = new TreeSet<ServiceName>();
                    newlyMissing.clear();
                    for (ServiceName name : missingDeps) {
                        if (! previousMissing.contains(name)) {
                            newlyMissing.add(name);
                        }
                    }

                    previousMissingDepSet = missingDeps;

                    // track failed services for the change report
                    latestSettledFailedControllers.clear();
                    latestSettledFailedControllers.putAll(failedControllers);

                    final StringBuilder msg = new StringBuilder();
                    msg.append("Service status report\n");
                    boolean print = false;
                    if (! newlyMissing.isEmpty()) {
                        print = true;
                        msg.append("   New missing/unsatisfied dependencies:\n");
                        for (ServiceName name : newlyMissing) {
                            ServiceController<?> controller = serviceRegistry.getService(name);
                            if (controller == null) {
                                msg.append("      ").append(name).append(" (missing)\n");
                            } else {
                                msg.append("      ").append(name).append(" (unavailable)\n");
                            }
                        }
                    }
                    if (! noLongerMissing.isEmpty()) {
                        print = true;
                        msg.append("   Newly corrected services:\n");
                        for (ServiceName name : noLongerMissing) {
                            ServiceController<?> controller = serviceRegistry.getService(name);
                            if (controller == null) {
                                msg.append("      ").append(name).append(" (no longer required)\n");
                            } else {
                                msg.append("      ").append(name).append(" (now available)\n");
                            }
                        }
                    }
                    if (! failedControllers.isEmpty()) {
                        print = true;
                        msg.append("  Services which failed to start:\n");
                        for (Map.Entry<ServiceController<?>, String> entry : failedControllers.entrySet()) {
                            msg.append("      ").append(entry.getKey().getName()).append(": ").append(entry.getValue()).append('\n');
                        }
                        failedControllers.clear();
                    }
                    if (print) {
                        log.info(msg);
                    }
                }
            }
        }

        private void untick() {
            busyServiceCount.incrementAndGet();
//            System.out.println("UNTICK -> " + tick + " (" + Thread.currentThread().getStackTrace()[2].getMethodName() + ") -> ");
        }

        private synchronized ModelNode getServerStateChangeReport() {

            // Determine the newly failed controllers
            final Map<ServiceController<?>, String> newFailedControllers = new IdentityHashMap<ServiceController<?>, String>(latestSettledFailedControllers);
            newFailedControllers.keySet().removeAll(lastReportFailedControllers.keySet());
            // Back up current state for use in next report
            lastReportFailedControllers.clear();
            lastReportFailedControllers.putAll(latestSettledFailedControllers);
            // Determine the new missing dependencies
            final Set<ServiceName> newReportMissingDepSet = new TreeSet<ServiceName>(previousMissingDepSet);
            newReportMissingDepSet.removeAll(lastReportMissingDepSet);
            // Back up current state for use in next report
            lastReportMissingDepSet.clear();
            lastReportMissingDepSet.addAll(previousMissingDepSet);

            ModelNode report = null;
            if (!newFailedControllers.isEmpty() || !newReportMissingDepSet.isEmpty()) {
                report = new ModelNode();
                if (! newReportMissingDepSet.isEmpty()) {
                    ModelNode missing = report.get("New missing/unsatisfied dependencies");
                    for (ServiceName name : newReportMissingDepSet) {
                        ServiceController<?> controller = serviceRegistry.getService(name);
                        if (controller == null) {
                            missing.add(name + " (missing)");
                        } else {
                            missing.add(name + " (unavailable)\n");
                        }
                    }
                }
                if (! newFailedControllers.isEmpty()) {
                    ModelNode failed = report.get("Services which failed to start:");
                    for (Map.Entry<ServiceController<?>, String> entry : newFailedControllers.entrySet()) {
                        failed.add(entry.getKey().getName().toString());
                    }
                }
            }
            firstReportDone = true;
            return report;
        }

        private boolean isFirstReportComplete() {
            return firstReportDone;
        }
    }

    private class ServerOperationContextImpl extends OperationContextImpl implements ServerOperationContext, RuntimeOperationContext {
        // -1 as initial value ensures the CAS in revertRestartRequired()
        // will never succeed unless restartRequired() is called
        private int ourStamp = -1;
        private RuntimeTask runtimeTask;

        public ServerOperationContextImpl(ModelController controller, ModelNodeRegistration registry, ModelNode subModel, ModelProvider modelProvider, OperationAttachments executionAttachments) {
            super(controller, registry, subModel, modelProvider, executionAttachments);
        }

        @Override
        public ServerController getController() {
            return (ServerController) super.getController();
        }

        @Override
        public synchronized void restartRequired() {
            AtomicStampedReference<State> stateRef = state;
            int newStamp = stamp.incrementAndGet();
            int[] receiver = new int[1];
            // Keep trying until stateRef is RESTART_REQUIRED with our stamp
            for (;;) {
                State was = stateRef.get(receiver);
                if (was == State.STARTING) {
                    break;
                }
                if (stateRef.compareAndSet(was, State.RESTART_REQUIRED, receiver[0], newStamp)) {
                    ourStamp = newStamp;
                    break;
                }
            }
        }

        @Override
        public synchronized void revertRestartRequired() {
            // If 'state' still has the state we last set in restartRequired(), change to RUNNING
            state.compareAndSet(State.RESTART_REQUIRED, State.RUNNING, ourStamp, stamp.incrementAndGet());
        }

        @Override
        public RuntimeOperationContext getRuntimeContext() {
            return this;
        }

        public RuntimeTask getRuntimeTask() {
            return runtimeTask;
        }

        @Override
        public void setRuntimeTask(RuntimeTask runtimeTask) {
            this.runtimeTask = runtimeTask;
        }
    }

    private class BootContextImpl extends ServerOperationContextImpl implements BootOperationContext {

        private final EnumMap<Phase, SortedSet<RegisteredProcessor>> deployers;

        private BootContextImpl(final ModelNode subModel, final ModelNodeRegistration registry, final EnumMap<Phase, SortedSet<RegisteredProcessor>> deployers, ModelProvider modelProvider, OperationAttachments executionAttachments) {
            super(ServerControllerImpl.this, registry, subModel, modelProvider, executionAttachments);
            this.deployers = deployers;
        }

        @Override
        public void addDeploymentProcessor(final Phase phase, final int priority, final DeploymentUnitProcessor processor) {
            if (phase == null) {
                throw new IllegalArgumentException("phase is null");
            }
            if (processor == null) {
                throw new IllegalArgumentException("processor is null");
            }
            if (priority < 0) {
                throw new IllegalArgumentException("priority is invalid (must be >= 0)");
            }
            deployers.get(phase).add(new RegisteredProcessor(priority, processor));
        }
    }

    static final class RegisteredProcessor implements Comparable<RegisteredProcessor> {
        private final int priority;
        private final DeploymentUnitProcessor processor;

        RegisteredProcessor(final int priority, final DeploymentUnitProcessor processor) {
            this.priority = priority;
            this.processor = processor;
        }

        @Override
        public int compareTo(final RegisteredProcessor o) {
            final int rel = Integer.signum(priority - o.priority);
            return rel == 0 ? processor.getClass().getName().compareTo(o.getClass().getName()) : rel;
        }

        int getPriority() {
            return priority;
        }

        DeploymentUnitProcessor getProcessor() {
            return processor;
        }

    }

    class RollbackAwareResultHandler implements ResultHandler {

        private final ResultHandler delegate;
        private volatile ModelNode rollbackOperation;
        private volatile boolean terminalState;

        public RollbackAwareResultHandler(ResultHandler resultHandler) {
            delegate = resultHandler;
        }

        @Override
        public void handleResultFragment(String[] location, ModelNode result) {
            delegate.handleResultFragment(location, result);
        }

        @Override
        public void handleResultComplete() {
            // we ignore these and wait for notifySuccess();
        }

        @Override
        public void handleFailed(final ModelNode failureDescription) {

            if (terminalState) {
                // An async call from a service listener?? Oh well, too late.
                // We're going to eliminate async handling anyway
                return;
            }

            terminalState = true;

            if (rollbackOperation == null || !rollbackOperation.isDefined()) {
                delegate.handleFailed(failureDescription);
                return;
            }

            final ResultHandler rollbackHandler = new ResultHandler() {

                @Override
                public void handleResultFragment(String[] location, ModelNode result) {
                    // ignore fragments from rollback
                }

                @Override
                public void handleResultComplete() {
                    // FIXME this will not appear in the correct location
//                    ModelNode rollbackResult = new ModelNode();
//                    rollbackResult.get("rolled-back").set(true);
//                    delegate.handleResultFragment(new String[0], rollbackResult);
                    delegate.handleFailed(failureDescription);
                }

                @Override
                public void handleFailed(ModelNode rollbackFailureDescription) {
                    // FIXME this will not appear in the correct location
                    ModelNode rollbackResult = new ModelNode();
//                    rollbackResult.get("rolled-back").set(false);
//                    delegate.handleResultFragment(new String[0], rollbackResult);
                    rollbackResult = new ModelNode();
                    rollbackResult.get("rollback-failure-description").set(rollbackFailureDescription);
                    delegate.handleFailed(failureDescription);
                }

                @Override
                public void handleCancellation() {
                    handleFailed(new ModelNode().set("Rollback was cancelled"));
                }
            };

            // Make sure the rollback op doesn't itself try and roll back
            rollbackOperation.get(OPERATION_HEADERS, ROLLBACK_ON_RUNTIME_FAILURE).set(false);

            Runnable r = new Runnable() {

                @Override
                public void run() {
                    execute(OperationBuilder.Factory.create(rollbackOperation).build(), rollbackHandler);
                }

            };
            executorService.execute(r);
        }

        @Override
        public void handleCancellation() {
            terminalState = true;
            delegate.handleCancellation();
        }

        private void setRollbackOperation(ModelNode compensatingOperation) {
            rollbackOperation = compensatingOperation;
        }

        private boolean isTerminalState() {
            return terminalState;
        }

        private void notifySuccess() {
            delegate.handleResultComplete();
        }
    }

    private class ServerMultiStepOperationController extends MultiStepOperationController {

        private ServerMultiStepOperationController(final Operation operation, final ResultHandler resultHandler,
                final OperationControllerContext injectedOperationControllerContext) throws OperationFailedException {
            super(operation, resultHandler, injectedOperationControllerContext);
        }

        @Override
        public OperationContext getOperationContext(ModelProvider modelSource, PathAddress address,
                OperationHandler operationHandler, Operation operation) {
            OperationContext delegate = super.getOperationContext(modelSource, address, operationHandler, operation);
            return delegate.getRuntimeContext() == null ? delegate : new StepRuntimeOperationContext(Integer.valueOf(currentOperation), ServerOperationContext.class.cast(delegate), operation);
        }

        @Override
        protected void handleFailures() {
            if (!modelComplete.get()) {
                super.handleFailures();
            } else if (rollbackOnRuntimeFailure) {
                final ModelNode compensatingOp = getOverallCompensatingOperation();
                if (compensatingOp.isDefined()) {
                    final ResultHandler rollbackResultHandler = new RollbackResultHandler();
                    // Execute the rollback in another thread as this method may be called by an MSC thread
                    // and we don't want to risk blocking it
                    Runnable r = new Runnable() {
                        @Override
                        public void run() {
                            execute(OperationBuilder.Factory.create(compensatingOp).build(), rollbackResultHandler);
                        }
                    };
                    executorService.execute(r);
                } else {
                    super.handleFailures();
                }
            } else if (resultHandler instanceof StepResultHandler) {
                // This is a nested compensating op so it needs to correctly record a failure

                resultHandler.handleResultFragment(ResultHandler.EMPTY_LOCATION, resultsNode);

                if (overallFailure != null) {
                    resultHandler.handleFailed(overallFailure);
                }
                else {
                    final ModelNode failureMsg = new ModelNode();
                    // TODO i18n
                    final String baseMsg = "Composite operation failed. Steps that failed:";
                    for (Property property : resultsNode.asPropertyList()) {
                        final ModelNode stepResult = property.getValue();
                        if (stepResult.hasDefined(FAILURE_DESCRIPTION)) {
                            failureMsg.get(baseMsg, "Operation " + property.getName()).set(stepResult.get(FAILURE_DESCRIPTION));
                        }
                    }
                    resultHandler.handleFailed(failureMsg);
                }
            } else {
                //This is the top level compensating op, which is not going to rollback on runtime failure, so we just return success
                resultHandler.handleResultFragment(ResultHandler.EMPTY_LOCATION, resultsNode);
                resultHandler.handleResultComplete();
            }
        }

        @Override
        protected void recordModelComplete() {

            if (isModelUpdated()) {
                updateModelAndPersist();
            }

            if (runtimeTasks.size() > 0) {
                RuntimeTaskContext rtc = new RuntimeTaskContext() {
                    @Override
                    public ServiceTarget getServiceTarget() {
                        return serviceTarget;
                    }

                    @Override
                    public ServiceRegistry getServiceRegistry() {
                        return serviceRegistry;
                    }
                };
                for(int i = 0; i < steps.size(); i++) {
                    Integer id = Integer.valueOf(i);
                    RuntimeTask runtimeTask = runtimeTasks.get(id);
                    if (runtimeTask == null) {
                        continue;
                    }
                    try {
                        runtimeTask.execute(rtc);
                    } catch (OperationFailedException e) {
                        stepResultHandlers.get(id).handleFailed(e.getFailureDescription());
                    } catch (Throwable t) {
                        stepResultHandlers.get(id).handleFailed(new ModelNode().set(t.toString()));
                    }
                }
            }

            if (state.getReference() != State.STARTING  && !(resultHandler instanceof StepResultHandler)) {
                overallFailure = serverStateMonitorListener.awaitUninterruptibly();
            }

            for(int i = 0; i < steps.size(); i++) {
                Integer id = Integer.valueOf(i);
                StepResultHandler stepHandler = stepResultHandlers.get(id);
                // doExecute will not invoke this
                if (!stepHandler.isTerminalState()) {
                    stepHandler.handleResultComplete();
                }
            }

            // If we haven't already recorded failures and we aren't a nested op
            if (!hasFailures() && overallFailure != null && !(resultHandler instanceof StepResultHandler)) {
                hasFailures = true;
            }

            modelComplete.set(true);
            processComplete();
        }

        private void rollbackComplete(final ModelNode rollbackResult) {

            // Update each of our steps to indicate what happened with the rollback
            synchronized (resultsNode) {
                for (int i = 0; i < steps.size(); i++) {
                    String stepKey = getStepKey(i);
                    ModelNode stepResult = resultsNode.get(stepKey);
                    if (stepResult.hasDefined(OUTCOME) && !CANCELLED.equals(stepResult.get(OUTCOME).asString())) {

                        ModelNode rollbackStepOutcome = null;
                        ModelNode rollbackStepResult = null;
                        String rollbackKey = rollbackStepNames.get(Integer.valueOf(i));
                        if (rollbackKey != null) {
                            rollbackStepResult = rollbackResult.get(rollbackKey);
                            rollbackStepOutcome = rollbackStepResult.isDefined() ? rollbackStepResult.get(OUTCOME) : null;
                        }

                        if (rollbackStepOutcome == null || !rollbackStepOutcome.isDefined()) {
                            stepResult.get(ROLLED_BACK).set(false);
                            stepResult.get(ROLLBACK_FAILURE_DESCRIPTION).set(new ModelNode().set("No compensating operations was available"));
                        } else if (CANCELLED.equals(rollbackStepOutcome.asString())) {
                            stepResult.get(ROLLED_BACK).set(false);
                            stepResult.get(ROLLBACK_FAILURE_DESCRIPTION).set(new ModelNode().set("Execution of the compensating operation was cancelled"));
                        } else if (SUCCESS.equals(rollbackStepOutcome.asString())) {
                            stepResult.get(ROLLED_BACK).set(true);
                        } else {
                            stepResult.get(ROLLED_BACK).set(false);
                            ModelNode rollbackFailureCause = rollbackStepResult.get(FAILURE_DESCRIPTION);
                            if (!rollbackFailureCause.isDefined()) {
                                rollbackFailureCause = new ModelNode().set("Compensating operation was reverted due to failure of other compensating operations");
                            }
                            stepResult.get(ROLLBACK_FAILURE_DESCRIPTION).set(rollbackFailureCause);
                        }
                    }
                }
            }

            // Finally, notify the end user's result handler of completion
            super.handleFailures();
        }

        /** Context that stores any registered RuntimeTask under the step's id */
        private class StepRuntimeOperationContext implements ServerOperationContext, RuntimeOperationContext {

            private final Integer id;
            private final ServerOperationContext delegate;
            private final OperationAttachments executionAttachments;

            private StepRuntimeOperationContext(final Integer id, final ServerOperationContext delegate, OperationAttachments executionAttachments) {
                this.id = id;
                this.delegate = delegate;
                this.executionAttachments = executionAttachments;
            }

            @Override
            public ModelNode getSubModel() throws IllegalArgumentException {
                return delegate.getSubModel();
            }

            @Override
            public ModelNode getSubModel(PathAddress address) throws IllegalArgumentException {
                return delegate.getSubModel(address);
            }

            @Override
            public ModelNodeRegistration getRegistry() {
                return delegate.getRegistry();
            }

            @Override
            public ServerController getController() {
                return delegate.getController();
            }

            @Override
            public void restartRequired() {
                delegate.restartRequired();
            }

            @Override
            public void revertRestartRequired() {
                delegate.revertRestartRequired();
            }

            @Override
            public RuntimeOperationContext getRuntimeContext() {
                return this;
            }

            @Override
            public void setRuntimeTask(RuntimeTask runtimeTask) {
                runtimeTasks.put(id, runtimeTask);
            }

            @Override
            public List<InputStream> getInputStreams() {
                return executionAttachments.getInputStreams();
            }
        }

        /**
         * Captures the result of executing compensating operations and then triggers
         * the final completion of the original operation.
         */
        private class RollbackResultHandler implements ResultHandler {

            private final ModelNode rollbackResult = new ModelNode();

            @Override
            public void handleResultFragment(String[] location, ModelNode result) {
                rollbackResult.get(location).set(result);
            }

            @Override
            public void handleResultComplete() {
                // TODO add an overall rollback message (needs change in ResultHandler API)
                rollbackComplete(rollbackResult);
            }

            @Override
            public void handleFailed(ModelNode failureDescription) {
                // TODO add an overall rollback message (needs change in ResultHandler API)
                rollbackComplete(rollbackResult);
            }

            @Override
            public void handleCancellation() {
                // TODO add an overall rollback message (needs change in ResultHandler API)
                rollbackComplete(rollbackResult);
            }

        }

    }
}
TOP

Related Classes of org.jboss.as.server.ServerControllerImpl$ServerStateMonitorListener

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.