Package org.jboss.as.controller

Source Code of org.jboss.as.controller.BasicModelController$MultiStepOperationController

/*
* 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.controller;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CANCELLED;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.COMPENSATING_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.COMPOSITE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILED;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT;
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.STEPS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.CancellationException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import org.jboss.as.controller.client.Operation;
import org.jboss.as.controller.client.OperationBuilder;
import org.jboss.as.controller.descriptions.DescriptionProvider;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.descriptions.common.CommonDescriptions;
import org.jboss.as.controller.operations.global.GlobalOperationHandlers;
import org.jboss.as.controller.operations.validation.ModelTypeValidator;
import org.jboss.as.controller.operations.validation.ParameterValidator;
import org.jboss.as.controller.persistence.ConfigurationPersistenceException;
import org.jboss.as.controller.persistence.ConfigurationPersister;
import org.jboss.as.controller.persistence.ConfigurationPersisterProvider;
import org.jboss.as.controller.registry.ModelNodeRegistration;
import org.jboss.as.controller.registry.OperationEntry;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.jboss.dmr.Property;
import org.jboss.logging.Logger;

/**
* A basic model controller.
*
* @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
*/
public class BasicModelController extends AbstractModelController<OperationControllerContext> implements ModelController {

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

    private final ModelNodeRegistration registry;
    private final ModelNode model;
    private final ConfigurationPersister configurationPersister;
    private final ModelProvider modelSource = new ModelProvider() {
        @Override
        public ModelNode getModel() {
            return BasicModelController.this.model;
        }
    };
    private final OperationContextFactory contextFactory = new OperationContextFactory() {
        @Override
        public OperationContext getOperationContext(final ModelProvider modelSource, final PathAddress address,
                final OperationHandler operationHandler, final Operation operation) {
            final ModelNode subModel = getOperationSubModel(modelSource, operationHandler, address);
            return BasicModelController.this.getOperationContext(subModel, operationHandler, operation, modelSource);
        }
    };
    private final ConfigurationPersisterProvider configPersisterProvider = new ConfigurationPersisterProvider() {
        @Override
        public ConfigurationPersister getConfigurationPersister() {
            return configurationPersister;
        }
    };

    /**
     * Construct a new instance.
     *
     * @param configurationPersister the configuration persister to use to store changes
     */
    protected BasicModelController(final ConfigurationPersister configurationPersister) {
        this(new ModelNode().setEmptyObject(), configurationPersister, (DescriptionProvider) null);
    }

    /**
     * Construct a new instance.
     *
     * @param configurationPersister the configuration persister to use to store changes
     * @param rootDescriptionProvider the description provider of the root element
     */
    protected BasicModelController(final ConfigurationPersister configurationPersister, final DescriptionProvider rootDescriptionProvider) {
        this(new ModelNode().setEmptyObject(), configurationPersister, rootDescriptionProvider);
    }

    /**
     * Construct a new instance.
     *
     * @param model the model
     * @param configurationPersister the configuration persister to use to store changes
     * @param rootDescriptionProvider the description provider of the root element
     */
    protected BasicModelController(final ModelNode model, final ConfigurationPersister configurationPersister, DescriptionProvider rootDescriptionProvider) {
        this(model, configurationPersister, createRootRegistry(rootDescriptionProvider));
    }

    /**
     * Construct a new instance.
     *
     * @param model the model
     * @param configurationPersister the configuration persister to use to store changes
     * @param rootRegistry the ModelNodeRegistration for the root resource
     */
    protected BasicModelController(final ModelNode model, final ConfigurationPersister configurationPersister, ModelNodeRegistration rootRegistry) {
        this.model = model;
        this.configurationPersister = configurationPersister;
        this.registry = rootRegistry;
    }

    private static ModelNodeRegistration createRootRegistry(DescriptionProvider rootDescriptionProvider) {

        // TODO - remove this and require unit test subclasses to pass in an equivalent mock
        if (rootDescriptionProvider == null) {
            rootDescriptionProvider = new DescriptionProvider() {
                @Override
                public ModelNode getModelDescription(final Locale locale) {
                    return new ModelNode();
                }
            };
        }
        return ModelNodeRegistration.Factory.create(rootDescriptionProvider);
    }

    /**
     * Get the operation handler for an address and name.
     *
     * @param address the address
     * @param name the name
     * @return the operation handler
     */
    protected OperationHandler getHandler(final PathAddress address, final String name) {
        return registry.getOperationHandler(address, name);
    }

    protected ModelProvider getModelProvider() {
        return modelSource;
    }

    protected OperationContextFactory getOperationContextFactory() {
        return contextFactory;
    }

    protected ConfigurationPersisterProvider getConfigurationPersisterProvider() {
        return configPersisterProvider;
    }

    @Override
    protected OperationControllerContext getOperationControllerContext(Operation operation) {

        return new OperationControllerContext() {

            @Override
            public ModelProvider getModelProvider() {
                return modelSource;
            }

            @Override
            public OperationContextFactory getOperationContextFactory() {
                return contextFactory;
            }

            @Override
            public ConfigurationPersisterProvider getConfigurationPersisterProvider() {
                return configPersisterProvider;
            }

            @Override
            public ControllerTransactionContext getControllerTransactionContext() {
                return null;
            }

        };
    }

    @Override
    public OperationResult execute(final Operation operation, final ResultHandler handler,
            final OperationControllerContext operationExecutionContext) {
        return execute(operation, handler, operationExecutionContext, true);
    }

    /**
     * Execute an operation using the given resources.
     *
     * @param operation the operation to execute
     * @param handler the result handler
     * @param operationExecutionContext the context of the invocation
     * @param resolve {@code true if multi-target operations should be resolved
     * @return
     */
    protected OperationResult execute(final Operation operation, final ResultHandler handler,
            final OperationControllerContext operationExecutionContext, boolean resolve) {
        try {
            final PathAddress address = PathAddress.pathAddress(operation.getOperation().get(ModelDescriptionConstants.OP_ADDR));
            final boolean multiTarget = address.isMultiTarget();
            if(multiTarget && resolve) {
                final MultiTargetAction action = new MultiTargetAction(address);
                return action.execute(operation, handler, operationExecutionContext);
            }

            final ProxyController proxyExecutor = registry.getProxyController(address);
            if (proxyExecutor != null) {
                Operation newContext = operation.clone();
                newContext.getOperation().get(OP_ADDR).set(address.subAddress(proxyExecutor.getProxyNodeAddress().size()).toModelNode());
                return proxyExecutor.execute(newContext, handler);
            }

            try {
                if (isMultiStepOperation(operation, address)) {
                    MultiStepOperationController multistepController = getMultiStepOperationController(operation, handler, operationExecutionContext);
                    return multistepController.execute(handler);
                }

                final String operationName = operation.getOperation().require(ModelDescriptionConstants.OP).asString();
                final OperationHandler operationHandler = registry.getOperationHandler(address, operationName);
                if (operationHandler == null) {
                    throw new IllegalStateException("No handler for " + operationName + " at address " + address);
                }

                final OperationContext context = operationExecutionContext.getOperationContextFactory().getOperationContext(operationExecutionContext.getModelProvider(), address, operationHandler, operation);

                return doExecute(context, operation, operationHandler, handler, address, operationExecutionContext);
            } catch (OperationFailedException e) {
                log.debugf(e, "operation (%s) failed - address: (%s)", operation.getOperation().get(OP), operation.getOperation().get(OP_ADDR));
                handler.handleFailed(e.getFailureDescription());
                return new BasicOperationResult();
            }
        } catch (final Throwable t) {
            log.errorf(t, "operation (%s) failed - address: (%s)", operation.getOperation().get(OP), operation.getOperation().get(OP_ADDR));
            handler.handleFailed(getFailureResult(t));
            return new BasicOperationResult();
        }
    }

    protected MultiStepOperationController getMultiStepOperationController(final Operation operation, final ResultHandler handler,
            final OperationControllerContext operationExecutionContext) throws OperationFailedException {
        return new MultiStepOperationController(operation, handler, operationExecutionContext);
    }

    protected ModelNode getOperationSubModel(ModelProvider modelSource, OperationHandler operationHandler, PathAddress address) {
        final ModelNode subModel;
        if (operationHandler instanceof ModelAddOperationHandler) {
            validateNewAddress(address);
            subModel = new ModelNode();
        } else if (operationHandler instanceof ModelQueryOperationHandler) {
            // or model update operation handler...
            final ModelNode model = modelSource.getModel();
            synchronized (model) {
                subModel = address.navigate(model, false).clone();
            }
        } else {
            subModel = null;
        }
        return subModel;
    }

    protected boolean isMultiStepOperation(Operation operation, PathAddress address) {
        return address.size() == 0 && COMPOSITE.equals(operation.getOperation().require(OP).asString());
    }

    /**
     * Persist the configuration after an update was executed.
     *
     * @param model the new model
     * @param configurationPersisterFactory factory for the configuration persister
     */
    protected void persistConfiguration(final ModelNode model, final ConfigurationPersisterProvider configurationPersisterFactory) {
        ConfigurationPersister configurationPersister =  configurationPersisterFactory.getConfigurationPersister();
        if (configurationPersister != null) {
            try {
                configurationPersister.store(model);
            } catch (final ConfigurationPersistenceException e) {
                log.warnf(e, "Failed to persist configuration change: %s", e);
            }
        }
    }

    /**
     * Registers {@link OperationHandler}s for operations that require
     * access to controller internals not meant to be exposed via an
     * {@link OperationContext}.
     * <p>
     * This default implementation registers a handler for the
     * {@link CommonDescriptions#getReadConfigAsXmlOperation(Locale) read-config-as-xml}
     * operation.
     * </p>
     */
    protected void registerInternalOperations() {
        if (configurationPersister != null) {
            // Ugly. We register a handler for reading the config as xml to avoid leaking internals
            // via the ModelController or OperationContext interfaces.
            XmlMarshallingHandler handler = new XmlMarshallingHandler(configurationPersister, model);
            this.registry.registerOperationHandler(CommonDescriptions.READ_CONFIG_AS_XML, handler, handler, false, OperationEntry.EntryType.PRIVATE);
        }
    }

    /**
     * Get the operation context for the operation.  By default, this method creates a basic implementation of
     * {@link OperationContext}.
     *
     * @param subModel the submodel affected by the operation
     * @param operationHandler the operation handler which will run the operation
     * @param executionContext the exectution context
     * @param modelProvider source for the overall model
     * @param operation the operation itself
     * @return the operation context
     */
    protected OperationContext getOperationContext(final ModelNode subModel, final OperationHandler operationHandler, final Operation executionContext, ModelProvider modelProvider) {
        return new OperationContextImpl(this, getRegistry(), subModel, modelProvider, executionContext);
    }

    /**
     * Actually perform this operation.  By default, this method simply calls the appropriate {@code execute()}
     * method, applying the operation to the relevant submodel.  If this method throws an exception, the result handler
     * will automatically be notified.  If the operation completes successfully, any configuration change will be persisted.
     *
     *
     * @param operationHandlerContext the context to provide to the operationHandler
     * @param operation the operation itself
     * @param operationHandler the operation handler which will run the operation
     * @param resultHandler the result handler for this operation
     * @param address the address the operation targets
     * @param operationControllerContext context to be used by the controller
     * @param subModel @return a handle which can be used to asynchronously cancel the operation
     */
    protected OperationResult doExecute(final OperationContext operationHandlerContext, final Operation operation,
            final OperationHandler operationHandler, final ResultHandler resultHandler,
            final PathAddress address, final OperationControllerContext operationControllerContext) throws OperationFailedException {
        final OperationResult result = operationHandler.execute(operationHandlerContext, operation.getOperation(), resultHandler);
        if (operationHandler instanceof ModelUpdateOperationHandler) {
            final ModelNode model = operationControllerContext.getModelProvider().getModel();
            synchronized (model) {
                if (operationHandler instanceof ModelRemoveOperationHandler) {
                    address.remove(model);
                } else {
                    address.navigate(model, true).set(operationHandlerContext.getSubModel());
                }
                persistConfiguration(model, operationControllerContext.getConfigurationPersisterProvider());
            }
        }
        return result;
    }

    protected ModelNodeRegistration getRegistry() {
        return registry;
    }

    protected ModelNode getModel() {
        return model;
    }

    /**
     * Validates that it is valid to add a resource to the model at the given
     * address. Confirms that:
     *
     * <ol>
     * <li>No resource already exists at that address</li>
     * <li>All ancestor resources do exist.</li>
     * </ol>
     *
     * @param address the address. Cannot be {@code null}
     *
     * @throws IllegalStateException if the resource already exists or ancestor resources are missing
     */
    protected void validateNewAddress(PathAddress address) {
        if (address.size() == 0) {
            throw new IllegalStateException("Resource at address " + address + " already exists");
        }
        ModelNode node = this.model;
        List<PathElement> elements = new ArrayList<PathElement>();
        for (PathElement element : address.subAddress(0, address.size() - 1)) {
            try {
                elements.add(element);
                node = node.require(element.getKey()).require(element.getValue());
            }
            catch (NoSuchElementException nsee) {
                PathAddress ancestor = PathAddress.pathAddress(elements);
                throw new IllegalStateException("Cannot add resource at address " + address + " because ancestor resource " + ancestor + " does not exist");
            }
        }
        PathElement last = address.getLastElement();
        if (!node.has(last.getKey())) {
            throw new IllegalStateException("Cannot add resource at address " + address + " because parent resource does not have child " + last.getKey());
        }
        else if (node.get(last.getKey()).has(last.getValue()) && node.get(last.getKey()).get(last.getValue()).isDefined()) {
            throw new IllegalStateException("Resource at address " + address + " already exists");
        }
    }

    /** An {@link OperationHandler} that can output a model in XML form */
    public static final class XmlMarshallingHandler implements ModelQueryOperationHandler, DescriptionProvider {

        private final String[] EMPTY = new String[0];
        private final ConfigurationPersister configPersister;
        private final ModelNode model;

        public XmlMarshallingHandler(final ConfigurationPersister configPersister, final ModelNode model) {
            this.configPersister  = configPersister;
            this.model = model;
        }

        @Override
        public ModelNode getModelDescription(Locale locale) {
            return CommonDescriptions.getReadConfigAsXmlOperation(locale);
        }

        @Override
        public OperationResult execute(OperationContext context, ModelNode operation, ResultHandler resultHandler) {
            try {
                final ByteArrayOutputStream baos = new ByteArrayOutputStream();
                try {
                    BufferedOutputStream output = new BufferedOutputStream(baos);
                    configPersister.marshallAsXml(model, output);
                    output.close();
                    baos.close();
                } finally {
                    safeClose(baos);
                }
                String xml = new String(baos.toByteArray());
                ModelNode result = new ModelNode().set(xml);
                resultHandler.handleResultFragment(EMPTY, result);
            } catch (Exception e) {
                e.printStackTrace();
                resultHandler.handleFailed(new ModelNode().set(e.getLocalizedMessage()));
            }
            resultHandler.handleResultComplete();
            return new BasicOperationResult();
        }

        private void safeClose(final Closeable closeable) {
            if (closeable != null) try {
                closeable.close();
            } catch (Throwable t) {
                log.errorf(t, "Failed to close resource %s", closeable);
            }
        }
    }

    protected class MultiStepOperationController implements ModelProvider, OperationContextFactory, ConfigurationPersisterProvider {

        private final ParameterValidator stepsValidator = new ModelTypeValidator(ModelType.LIST);

        /** The original operation for the composite operation */
        protected final Operation operation;

        protected final boolean rollbackOnRuntimeFailure;
        /** The handler passed in by the user */
        protected final ResultHandler resultHandler;
        /** The individual steps in the multi-step op */
        protected final List<ModelNode> steps;
        /** # of steps that have not yet reached their terminal state */
        protected final AtomicInteger unfinishedCount = new AtomicInteger();
        /** Node representing the overall op response's "result" field */
        protected final ModelNode resultsNode = new ModelNode();
        /** Compensating operations keyed by step # */
        protected final Map<Integer, ModelNode> rollbackOps = new HashMap<Integer, ModelNode>();
        /** The ResultHandler for each step */
        protected final Map<Integer, ResultHandler> stepResultHandlers = new HashMap<Integer, ResultHandler>();
        /** The "step-X" string expected in the results for a compensating op, keyed by the step # of the step being rolled back */
        protected final Map<Integer, String> rollbackStepNames = new HashMap<Integer, String>();
        /** Flag set when all steps have been executed and only runtime tasks remain */
        protected final AtomicBoolean modelComplete = new AtomicBoolean(false);
        /** Flag set if any step has had it's handler's handleFailed method invoked */
        protected boolean hasFailures = false;
        /** Provides the model the overall operation should read and/or update */
        protected final ModelProvider modelSource;
        /** Our clone of the model provided by modelSource -- steps read or modify this */
        protected final ModelNode localModel;
        /** Flag indicating a step has modified the model */
        protected boolean modelUpdated;
        /** Index of the operation currently being executed */
        protected int currentOperation;
        /** Runtime tasks registered by individual steps */
        protected final Map<Integer, RuntimeTask> runtimeTasks = new HashMap<Integer, RuntimeTask>();
        /** The config persister provider we were provided */
        protected final ConfigurationPersisterProvider injectedConfigPersisterProvider;
        /** Instead of persisting, this persister records that model was modified and needs to be persisted when all steps are done. */
        protected final ConfigurationPersister localConfigPersister = new ConfigurationPersister() {
            @Override
            public void store(ModelNode model) throws ConfigurationPersistenceException {
                modelUpdated = true;
            }

            @Override
            public void marshallAsXml(ModelNode model, OutputStream output) throws ConfigurationPersistenceException {
                // an UnsupportedOperationException is also fine if this delegation needs to be removed
                // in some refactor someday
                BasicModelController.this.configurationPersister.marshallAsXml(model, output);
            }

            @Override
            public List<ModelNode> load() throws ConfigurationPersistenceException {
                throw new UnsupportedOperationException("load() should not be called as part of operation handling");
            }
        };
        protected final OperationControllerContext localOperationExecutionContext = new OperationControllerContext() {

            @Override
            public ModelProvider getModelProvider() {
                return MultiStepOperationController.this;
            }

            @Override
            public OperationContextFactory getOperationContextFactory() {
                return MultiStepOperationController.this;
            }

            @Override
            public ConfigurationPersisterProvider getConfigurationPersisterProvider() {
                return MultiStepOperationController.this;
            }

            @Override
            public ControllerTransactionContext getControllerTransactionContext() {
                return null;
            }

        };
        /** Flag indicating whether wildcards should be resolved or not. */
        protected boolean resolve = true;

        protected MultiStepOperationController(final Operation operation, final ResultHandler resultHandler,
                final OperationControllerContext operationControllerContext) throws OperationFailedException {
            this(operation, resultHandler, operationControllerContext.getModelProvider(), operationControllerContext.getConfigurationPersisterProvider());
        }

        protected MultiStepOperationController(final Operation operation, final ResultHandler resultHandler,
                final ModelProvider modelProvider, final ConfigurationPersisterProvider injectedConfigPersisterProvider) throws OperationFailedException {
            this.operation = operation;
            final ModelNode operationNode = operation.getOperation();
            stepsValidator.validateParameter(STEPS, operationNode.get(STEPS));
            this.resultHandler = resultHandler;
            this.steps = operationNode.require(STEPS).asList();
            this.unfinishedCount.set(steps.size());
            this.rollbackOnRuntimeFailure = (!operationNode.hasDefined(ROLLBACK_ON_RUNTIME_FAILURE) || operationNode.get(ROLLBACK_ON_RUNTIME_FAILURE).asBoolean());
            this.modelSource = modelProvider;
            this.localModel = this.modelSource.getModel().clone();
            this.injectedConfigPersisterProvider = injectedConfigPersisterProvider;
            // Ensure the outcome and result fields come first for each result
            for (int i = 0; i < unfinishedCount.get(); i++) {
                ModelNode stepResult = getStepResultNode(i);
                stepResult.get(OUTCOME);
                stepResult.get(OP_ADDR).set(steps.get(i).get(OP_ADDR));
                stepResult.get(RESULT);
            }
        }

        // ---------------------- Methods called by or overridden by subclasses

        protected void handleFailures() {

            for (final Property prop : resultsNode.asPropertyList()) {
                ModelNode result = prop.getValue();
                // Invoking resultHandler.handleFailed is going to result in discarding
                // any changes we made, so record that as a rollback
                if (!result.hasDefined(OUTCOME) || !CANCELLED.equals(result.get(OUTCOME).asString())) {
                    if (!modelComplete.get()) {
                        // We haven't gotten the "model complete" signal yet, so this is
                        // being called from execute() and no runtime tasks wiil be run
                        // and any model changes will be discarded.
                        // So, record that as a 'rollback'
                        result.get(ROLLED_BACK).set(true);
                    }
                    result.get(OUTCOME).set(FAILED);
                    resultsNode.get(prop.getName()).set(result);
                }
            }

            // Inform handler of the details
            resultHandler.handleResultFragment(ResultHandler.EMPTY_LOCATION, resultsNode);

            // We're being called due to runtime task execution. Notify the
            // handler of the failure
            final ModelNode failureMsg = getOverallFailureDescription();

            resultHandler.handleFailed(failureMsg);
        }

        /** Returns the compensating operation, or an undefined node if no meaningful compensating operation is possible */
        protected final ModelNode getOverallCompensatingOperation() {

            final ModelNode compensatingOp = new ModelNode();
            compensatingOp.get(OP).set(COMPOSITE);
            compensatingOp.get(OP_ADDR).setEmptyList();
            final ModelNode compSteps = compensatingOp.get(STEPS);
            compSteps.setEmptyList();

            int rollbackIndex = 0;
            for (int i = steps.size() - 1; i >= 0 ; i--) {
                Integer id = Integer.valueOf(i);
                final ModelNode compStep = rollbackOps.get(id);
                if (compStep != null && compStep.isDefined()) {
                    compSteps.add(compStep);
                    // Record the key under which we expect to find the result for this rollback step
                    rollbackStepNames.put(id, getStepKey(rollbackIndex));
                    rollbackIndex++;
                }
            }

            if (rollbackIndex > 0) {
                // Don't let the compensating op rollback; if it fails it needs a manual fix
                compensatingOp.get(ROLLBACK_ON_RUNTIME_FAILURE).set(false);

                return compensatingOp;
            }
            else {
                // No steps were added, so no meaningful compensating op exists
                return new ModelNode();
            }
        }

        protected void recordModelComplete() {
            modelComplete.set(true);
            if (isModelUpdated()) {
                updateModelAndPersist();
            }
            if (unfinishedCount.get() == 0) {
                handleSuccess();
            }
        }

        protected boolean isModelUpdated() {
            return modelUpdated;
        }

        protected void updateModelAndPersist() {
            final ModelNode model = modelSource.getModel();
            synchronized (model) {
                model.set(localModel);
            }
            BasicModelController.this.persistConfiguration(model, injectedConfigPersisterProvider);
        }

        protected final String getStepKey(int id) {
            return "step-" + (id + 1);
        }

        protected OperationResult executeStep(final ModelNode step, final ResultHandler stepResultHandler) {
            return BasicModelController.this.execute(operation.clone(step), stepResultHandler, localOperationExecutionContext, resolve);
        }

        // --------- Methods called by other classes in this file

        /** Executes the multi-step op. The call in point from the ModelController */
        OperationResult execute(ResultHandler handler) {

            for (int i = 0; i < steps.size(); i++) {
                currentOperation = i;
                final ModelNode step = steps.get(i).clone();
                // Do not auto-rollback individual steps
                step.get(ROLLBACK_ON_RUNTIME_FAILURE).set(false);
                if (hasFailures()) {
                    recordCancellation(Integer.valueOf(i));
                }
                else {
                    final Integer id = Integer.valueOf(i);
                    final ResultHandler stepResultHandler = getStepResultHandler(id);
                    final OperationResult result = executeStep(step, stepResultHandler);
                    recordRollbackOp(id, result.getCompensatingOperation());
                }
            }

            if (hasFailures()) {
                handleFailures();
                return new BasicOperationResult();
            }
            else {
                ModelNode compensatingOp = getOverallCompensatingOperation();

                recordModelComplete();

                return new BasicOperationResult(compensatingOp);
            }
        }

        /** Notification from a step's ResultHandler of step completion */
        void recordResult(final Integer id, final ModelNode result) {

            ModelNode rollback = rollbackOps.get(id);

            synchronized (resultsNode) {
                ModelNode stepResult = getStepResultNode(id);
                stepResult.get(OUTCOME).set(SUCCESS);
                stepResult.get(RESULT).set(result);
                stepResult.get(COMPENSATING_OPERATION).set(rollback == null ? new ModelNode() : rollback);
            }
            if(unfinishedCount.decrementAndGet() == 0 && modelComplete.get()) {
                processComplete();
            }
        }

        /** Notification from a step's ResultHandler of step failure */
        void recordFailure(final Integer id, final ModelNode failureDescription) {
            synchronized (resultsNode) {
                ModelNode stepResult = getStepResultNode(id);
                stepResult.get(OUTCOME).set(FAILED);
                if (stepResult.has(RESULT) && !stepResult.hasDefined(RESULT)) {
                    // Remove the undefined node
                    stepResult.remove(RESULT);
                }
                stepResult.get(FAILURE_DESCRIPTION).set(failureDescription);
            }
            hasFailures = true;

            if(unfinishedCount.decrementAndGet() == 0 && modelComplete.get()) {
                processComplete();
            }
        }

        /** Notification from a step's ResultHandler of step cancellation */
        void recordCancellation(final Integer id) {
            synchronized (resultsNode) {
                ModelNode stepResult = getStepResultNode(id);
                stepResult.get(OUTCOME).set(CANCELLED);
                if (stepResult.has(RESULT)) {
                    // Remove the undefined node
                    stepResult.remove(RESULT);
                }
            }
            if(unfinishedCount.decrementAndGet() == 0 && modelComplete.get()) {
                processComplete();
            }
        }

        // ----------------------------------------------------------- Private to this class

        private ResultHandler getStepResultHandler(Integer id) {
            StepResultHandler handler = new StepResultHandler(id, this);
            stepResultHandlers.put(id, handler);
            return handler;
        }

        private void recordRollbackOp(final Integer id, final ModelNode compensatingOperation) {
            rollbackOps.put(id, compensatingOperation);
            synchronized (resultsNode) {
                ModelNode stepResult = getStepResultNode(id);
                stepResult.get(COMPENSATING_OPERATION).set(compensatingOperation == null ? new ModelNode() : compensatingOperation);
            }
        }

        private void handleSuccess() {
            resultHandler.handleResultFragment(ResultHandler.EMPTY_LOCATION, resultsNode);
            resultHandler.handleResultComplete();
        }

        private ModelNode getOverallFailureDescription() {
            final ModelNode failureMsg = new ModelNode();
            // TODO i18n
            final String baseMsg = "Composite operation failed and was rolled back. Steps that failed:";
            for (int i = 0; i < steps.size(); i++) {
                final ModelNode stepResult = getStepResultNode(i);
                if (stepResult.hasDefined(FAILURE_DESCRIPTION)) {
                    failureMsg.get(baseMsg, "Operation " + getStepKey(i)).set(stepResult.get(FAILURE_DESCRIPTION));
                }
            }
            return failureMsg;
        }

        private boolean hasFailures() {
            synchronized (resultsNode) {
                return hasFailures;
            }
        }

        private void processComplete() {
            if (hasFailures()) {
                handleFailures();
            } else {
                handleSuccess();
            }
        }

        private ModelNode getStepResultNode(final Integer id) {
            ModelNode stepResult = resultsNode.get(getStepKey(id));
            return stepResult;
        }

//        private String[] getStepLocation(final Integer id, final String[] location, String... suffixes) {
//
//            String[] fullLoc = new String[location.length + 1 + suffixes.length];
//            fullLoc[0] = getStepKey(id);
//            if (location.length > 0) {
//                System.arraycopy(location, 0, fullLoc, 1, location.length);
//            }
//            if (suffixes.length > 0) {
//                System.arraycopy(suffixes, 0, fullLoc, location.length + 1, suffixes.length);
//            }
//            return fullLoc;
//        }

        // --------------------- ConfigurationPersisterProvider

        @Override
        public ConfigurationPersister getConfigurationPersister() {
            return localConfigPersister;
        }

        // --------------------- OperationContextFactory

        @Override
        public OperationContext getOperationContext(ModelProvider modelSource, PathAddress address,
                OperationHandler operationHandler, Operation executionContext) {
            return BasicModelController.this.contextFactory.getOperationContext(modelSource, address, operationHandler, executionContext);
        }

        // ------------------ ModelProvider

        @Override
        public ModelNode getModel() {
            return localModel;
        }
    }

    protected static class StepResultHandler implements ResultHandler {

        private final Integer id;
        private final ModelNode stepResult = new ModelNode();
        private final MultiStepOperationController compositeContext;

        public StepResultHandler(final Integer id, final MultiStepOperationController stepContext) {
            this.id = id;
            this.compositeContext = stepContext;
        }

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

        @Override
        public void handleResultComplete() {
            compositeContext.recordResult(id, stepResult);
        }

        @Override
        public void handleFailed(final ModelNode failureDescription) {
            compositeContext.recordFailure(id, failureDescription);
        }

        @Override
        public void handleCancellation() {
            compositeContext.recordCancellation(id);
        }
    }

    protected final class MultiTargetAction {

        private final PathAddress address;
        protected MultiTargetAction(PathAddress address) {
            this.address = address;
        }

        protected OperationResult execute(final Operation operation, final ResultHandler handler,
                final OperationControllerContext operationExecutionContext) throws OperationFailedException {
            // Resolve the address first
            final ModelNode resolveOperation = new ModelNode();
            resolveOperation.get(ModelDescriptionConstants.OP).set(GlobalOperationHandlers.ResolveAddressOperationHandler.OPERATION_NAME);
            resolveOperation.get(ModelDescriptionConstants.OP_ADDR).setEmptyList();
            resolveOperation.get(GlobalOperationHandlers.ResolveAddressOperationHandler.ADDRESS_PARAM).set(address.toModelNode());
            resolveOperation.get(GlobalOperationHandlers.ResolveAddressOperationHandler.ORIGINAL_OPERATION).set(operation.getOperation().require(ModelDescriptionConstants.OP));
            // Aggregate the result as collection using a hacked resolve operation and resultHandler
            final Collection<ModelNode> resolved = new ArrayList<ModelNode>();
            final AtomicInteger status = new AtomicInteger();
            final ModelNode failureResult = new ModelNode();
            final Operation resolveContext = OperationBuilder.Factory.create(resolveOperation).build();
            final ResultHandler resolveHandler = new ResultHandler() {
                @Override
                public void handleResultFragment(String[] location, ModelNode result) {
                    synchronized(failureResult) {
                        if (status.get() == 0) {
                            resolved.add(result);
                        }
                    }
                }
                @Override
                public void handleResultComplete() {
                    synchronized(failureResult) {
                        status.compareAndSet(0, 1);
                        failureResult.notify();
                    }
                }
                @Override
                public void handleFailed(ModelNode failureDescription) {
                    synchronized(failureResult) {
                        failureResult.set(failureDescription);
                        status.compareAndSet(0, 2);
                        failureResult.notify();
                    }
                }
                @Override
                public void handleCancellation() {
                    synchronized(failureResult) {
                        status.compareAndSet(0, 3);
                        failureResult.notify();
                    }
                }
            };
            final OperationResult result = BasicModelController.this.execute(resolveContext, resolveHandler, operationExecutionContext, false);
            boolean intr = false;
            try {
                synchronized (failureResult) {
                    for(;;) {
                        try {
                            final int s = status.get();
                            switch (s) {
                                case 1: return executeMultiOperation(resolved, operation, handler, operationExecutionContext);
                                case 2: handler.handleFailed(failureResult); return new BasicOperationResult();
                                case 3: throw new CancellationException();
                            }
                            failureResult.wait();
                        } catch(InterruptedException e) {
                            intr = true;
                            result.getCancellable().cancel();
                        }
                    }
                }
            } finally {
                if(intr) {
                    Thread.currentThread().interrupt();
                }
            }
        }

        protected OperationResult executeMultiOperation(final Collection<ModelNode> resolved, final Operation operation, final ResultHandler handler,
                final OperationControllerContext operationExecutionContext) throws OperationFailedException {
            // unresolved might be a failure?
            if(resolved.isEmpty()) {
                handler.handleResultComplete();
                return new BasicOperationResult();
            }
            // Create multi step operation
            final ModelNode multiStep = new ModelNode();
            for(final ModelNode a : resolved) {
                final ModelNode newOperation = operation.getOperation().clone();
                newOperation.get(ModelDescriptionConstants.OP_ADDR).set(a);
                multiStep.get(STEPS).add(newOperation);
            }
            final Operation multiContext = operation.clone(multiStep);
            final MultiStepOperationController multistepController = BasicModelController.this.getMultiStepOperationController(multiContext, handler, operationExecutionContext);
            multistepController.resolve = false; // tell the multi step controller not to resolve again
            // Execute multi step operation
            return multistepController.execute(handler);
        }


    }
}
TOP

Related Classes of org.jboss.as.controller.BasicModelController$MultiStepOperationController

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.