Package org.jboss.as.server.mgmt

Source Code of org.jboss.as.server.mgmt.ServerUpdateController$ServerUpdateCommitHandler

/*
* JBoss, Home of Professional Open Source.
* Copyright 2010, 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.mgmt;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;

import org.jboss.as.model.AbstractServerModelUpdate;
import org.jboss.as.model.ServerModel;
import org.jboss.as.model.UpdateContext;
import org.jboss.as.model.UpdateFailedException;
import org.jboss.as.model.UpdateResultHandler;
import org.jboss.logging.Logger;
import org.jboss.msc.service.BatchBuilder;
import org.jboss.msc.service.ServiceContainer;
import org.jboss.msc.service.ServiceRegistryException;

/**
* Coordinates the execution of a set of {@link AbstractServerModelUpdate updates}.
*/
public class ServerUpdateController {

    /** The status of the overall model update */
    public enum Status {
        /**
         * The {@link ServerUpdateController#executeUpdates()} method has not
         * yet been invoked
         */
        PENDING,
        /** The {@link ServerUpdateController#executeUpdates()} method has been
         * invoked and updates are being applied.*/
        ACTIVE,
        /** An error has occurred with one or more updates */
        MARKED_ROLLBACK,
        /** Updates are being rolled back */
        ROLLING_BACK,
        /**
         * All updates have completed or been rolled back; the
         * overall update process is finishing.
         */
        COMMITING,
        /**
         * The overall update process has finished following a rollback of
         * attempted updates.
         */
        ROLLED_BACK,
        COMMITTED
    }

    public static interface ServerUpdateCommitHandler {
        void handleUpdateCommit(ServerUpdateController controller, Status priorStatus);
    }

    private static Logger logger = Logger.getLogger("org.jboss.as.server.deployment");

    private volatile Status status = Status.PENDING;
    private final boolean allowOverallRollback;
    private final boolean allowRuntimeUpdates;
    /** The updates to execute */
    private final List<ServerModelUpdateTuple<?,?>> updates = new ArrayList<ServerModelUpdateTuple<?,?>>();
    /** Compensating updates for updates that that have succeeded and may need rollback */
    private final List<ServerModelUpdateTuple<?,?>> rollbacks = new ArrayList<ServerModelUpdateTuple<?,?>>();
    /** ServerModel against which updates are executed */
    private final ServerModel serverModel;
    /** ServiceContainer to use for runtime changes */
    private final ServiceContainer serviceContainer;
    /** Thread pool for async rollback and commit handling */
    private final Executor executor;
    private final ServerUpdateCommitHandler commitHandler;
    /** Tracks completion of non-rollback updates */
    private final AtomicInteger updatedCount = new AtomicInteger();
    /** Tracks completion of rollback updates */
    private final AtomicInteger rolledBackCount = new AtomicInteger();

    public ServerUpdateController(final ServerModel serverModel,
            final ServiceContainer serviceContainer,
            final Executor executor,
            final ServerUpdateCommitHandler commitHandler,
            final boolean allowOverallRollback,
            final boolean allowRuntimeUpdates) {
        this.serverModel = serverModel;
        this.serviceContainer = serviceContainer;
        this.executor = executor;
        this.commitHandler = commitHandler;
        this.allowOverallRollback = allowOverallRollback;
        this.allowRuntimeUpdates = allowRuntimeUpdates;
    }

    /**
     * Add a new update to the set of updates to be executed.
     *
     * @param <R> the result type provided by the update
     * @param <P> the parameter type expected by the result handler
     * @param update the update. Cannot be {@code null}
     * @param resultHandler a handler for the update result. May be {@code null}
     * @param param parameter to pass to {@code resultHandler}. May be {@code null}
     *               if {@code <P>} is {@code Void} or {@code resultHandler} is
     *               {@code null}.
     *
     * @throws IllegalStateException if {@link #getStatus()} is not {@link Status#PENDING}
     */
    public <R, P> void addServerModelUpdate(AbstractServerModelUpdate<R> update, UpdateResultHandler<? super R, P> resultHandler, P param) {
        synchronized (this) {
            if (status != Status.PENDING)
                throw new IllegalStateException("Cannot add updates after executeUpdates() has been invoked");
            updates.add(new ServerModelUpdateTuple<R, P>(update, resultHandler, param));
        }
    }

    /**
     * Execute the updates
     * {@link #addServerModelUpdate(AbstractServerModelUpdate, UpdateResultHandler, Object) previously added}
     */
    public void executeUpdates() {
        synchronized (this) {
            if (status != Status.PENDING)
                throw new IllegalStateException("Status is " + status + " -- must be " + Status.PENDING);
            status = Status.ACTIVE;
            try {
                applyUpdates();
            } catch (Exception e) {
                handleRollback();
            }
        }
    }

    /**
     * Gets the current {@link Status}.
     *
     * @return the status. Will not return {@code null}
     */
    public Status getStatus() {
        return status;
    }

    /** Only invoke with the object monitor held */
    private void applyUpdates() {

        if (updates.size() == 0) {
            // No other event is going to trigger a transition from ACTIVE,
            // so we do it directly
            transition(false);
            return;
        }

        BatchBuilder batchBuilder = serviceContainer.batchBuilder();
        UpdateContext updateContext = new SimpleUpdateContext(serviceContainer, batchBuilder);
        for (ServerModelUpdateTuple<?,?> update : updates) {

            logger.debugf("Applying update %s", update.getUpdate().toString());

            if (status != Status.ACTIVE) {
                // Don't execute this update; just notify any handler and
                // count it as updated for overall completion tracking
                update.handleCancellation();
                continue;
            }

            boolean appliedToModel = false;
            ServerModelUpdateTuple<Object, ?> rollbackTuple = null;
            try {
                rollbackTuple = update.getRollbackTuple(serverModel);

                serverModel.update(update.getUpdate());
                appliedToModel = true;
                if (allowRuntimeUpdates) {
                    update.applyUpdate(updateContext);
                }
                else {
                    // We won't get a callback from a result handler, so
                    // directly record the completion for overall completion tracking
                    updateComplete();
                }

                // As the last thing in this try block, add the rollbackTuple
                // to the rollback list. Do it last because if this update
                // directly fails, we roll it back in catch block below.
                // The 'rollbacks' list is for updates that succeeeded.
                if (allowOverallRollback && rollbackTuple != null) {
                    // Add this latest update's rollback to the list
                    rollbacks.add(0, rollbackTuple);
                }
            }
            catch (Exception e) {
                update.handleFailure(e);

                if (rollbackTuple != null) {
                    if (appliedToModel) {
                        try {
                            if (allowRuntimeUpdates) {
                                // FIXME this is likely incorrect given we are now
                                // using a batch!!!
                                rollbackTuple.applyUpdate(updateContext);
                            }
                            serverModel.update(rollbackTuple.getUpdate());
                        } catch (UpdateFailedException e1) {
                            rollbackTuple.handleFailure(e1);
                        }
                    }
                }
                // else there was no compensating update or creating the
                // rollbackTuple failed at the beginning of 'try'
                // and there is nothing else needing to be done here
            }
        }

        if (status == Status.ACTIVE) {
            try {
                batchBuilder.install();
            } catch (ServiceRegistryException e) {
                handleRollback();
            }
        }

        if (logger.isDebugEnabled()) {
            logger.debugf("%s update(s) applied", updates.size());
        }
    }

    /** Only invoke with the object monitor held */
    private void applyRollbacks() {

        if (rollbacks.size() == 0) {
            // No other event is going to trigger a transition from ROLLING_BACK,
            // so we do it directly
            transition(false);
            return;
        }

        BatchBuilder batchBuilder = serviceContainer.batchBuilder();
        UpdateContext updateContext = new SimpleUpdateContext(serviceContainer, batchBuilder);
        boolean failed = false;
        try {
            for (ServerModelUpdateTuple<?, ?> update : rollbacks) {

                if (failed) {
                    update.handleCancellation();
                }
                else {
                    try {
                        serverModel.update(update.getUpdate());
                    }
                    catch (Exception e) {
                        update.handleFailure(e);
                    }
                    finally {
                        try {
                            if (allowRuntimeUpdates) {
                                update.applyUpdate(updateContext);
                            }
                            else {
                                rollbackComplete();
                            }
                        }
                        catch (Exception e) {
                            update.handleFailure(e);
                        }
                    }
                }
            }

            batchBuilder.install();

            if (logger.isDebugEnabled()) {
                logger.debugf("%s rollbacks applied", rollbacks.size());
            }
        }
        catch (Exception e) {
            // FIXME what to do
            logger.error("Caught exception applying rollbacks", e);
        }
    }

    private void updateComplete() {
        updatedCount.incrementAndGet();
        transition(false);
    }

    private void updateFailed() {
        updatedCount.incrementAndGet();
        transition(true);
    }

    private void rollbackComplete() {
        rolledBackCount.incrementAndGet();
        transition(false);
    }

    private void rollbackFailed() {
        rolledBackCount.incrementAndGet();
        transition(false);
    }

    private void handleCommit() {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                logger.debug("Committing");
                synchronized (ServerUpdateController.this) {
                    Status priorStatus = status;
                    status = Status.COMMITING;
                    commitHandler.handleUpdateCommit(ServerUpdateController.this, priorStatus);
                    Status newStatus = (priorStatus == Status.ACTIVE || allowOverallRollback) ? Status.COMMITTED : Status.ROLLED_BACK;
                    status = newStatus;
                }
                logger.debug("Committed");
            }
        };
        this.executor.execute(r);
    }

    private void handleRollback() {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                logger.debug("Rolling back");
                synchronized (ServerUpdateController.this) {
                    if (status != Status.MARKED_ROLLBACK) {
                        // Failure has already been handled by executeUpdate()
                        return;
                    }
                    status = Status.ROLLING_BACK;
                    applyRollbacks();
                }
            }
        };
        this.executor.execute(r);

    }

    private void transition(boolean updateFailure) {

        if (logger.isTraceEnabled()) {
            logger.tracef("transition(): status=%s updates.size()=%s updatedCount=%s rollbacks.size()=%s rolledBackCount=%s", status, updates.size(), updatedCount.get(), rollbacks.size(), rolledBackCount.get());
        }

        switch (status) {
            case ACTIVE: {
                if (updateFailure) {
                    status = Status.MARKED_ROLLBACK;
                }

                if (updateFailure && allowOverallRollback) {
                    handleRollback();
                }
                else if (updatedCount.get() == updates.size()) {
                    handleCommit();
                }
                break;
            }
            case MARKED_ROLLBACK: {
                if (rolledBackCount.get() == rollbacks.size()) {
                    // Getting here shouldn't really be possible since we
                    // shouldn't get rollback updates until RollbackTask switches
                    // us to Status.ROLLING_BACK
                    handleCommit();
                }
                break;
            }
            case ROLLING_BACK: {
                if (rolledBackCount.get() == rollbacks.size()) {
                    handleCommit();
                }
                break;
            }
            case ROLLED_BACK:
            case COMMITING:
            case COMMITTED:
                // TODO something spurious came in late. Log a WARN?
                break;
            default :
                throw new IllegalStateException("Unexpected status " + status);
        }

    }

    /** Data object that associates an update with its result handler and param */
    private class ServerModelUpdateTuple<R, P> {

        private final AbstractServerModelUpdate<R> update;
        private final DelegatingUpdateResultHandler<? super R, P> resultHandler;
        private final P param;

        public ServerModelUpdateTuple(final AbstractServerModelUpdate<R> update,
                final UpdateResultHandler<? super R, P> resultHandler,
                final P param) {
            if (update == null) {
                throw new IllegalArgumentException("update is null");
            }
            this.update = update;
            this.resultHandler = new DelegatingUpdateResultHandler<R, P>(resultHandler);
            this.param = param;
        }

        public AbstractServerModelUpdate<R> getUpdate() {
            return update;
        }

        private void handleCancellation() {
            resultHandler.handleCancellation(param);
        }

        private void handleFailure(Throwable cause) {
            resultHandler.handleFailure(cause, param);
        }

        private void applyUpdate(UpdateContext container) {
            update.applyUpdate(container, resultHandler, param);
        }

        private ServerModelUpdateTuple<Object, P> getRollbackTuple(ServerModel serverModel) {
            ServerModelUpdateTuple<Object, P> rollbackTuple = null;
            @SuppressWarnings("unchecked") // Safe because we aren't going to use the result
            AbstractServerModelUpdate<Object> compensating = (AbstractServerModelUpdate<Object>) update.getCompensatingUpdate(serverModel);
            if (compensating != null) {
                RollbackUpdateResultHandler<P> rollbackHandler = RollbackUpdateResultHandler.getRollbackUpdateResultHandler(resultHandler.getDelegate());
                rollbackTuple = new ServerModelUpdateTuple<Object, P>(compensating, rollbackHandler, param);
            }
            return rollbackTuple;
        }
    }

    /**
     * The actual result handler we pass to the applyUpdate method when we execute updates.
     * Uses the callback notifications to track the completion of the updates,
     * then passes the notifications on to any actual end-user handler that was passed in.
     */
    private class DelegatingUpdateResultHandler<R, P> implements UpdateResultHandler<R, P> {

        private final UpdateResultHandler<? super R, P> delegate;

        private DelegatingUpdateResultHandler(final UpdateResultHandler<? super R, P> delegate) {
            this.delegate = delegate;
        }

        @Override
        public void handleCancellation(P param) {
            updateComplete();
            if (delegate != null) {
                delegate.handleCancellation(param);
            }
        }

        @Override
        public void handleFailure(Throwable cause, P param) {
            updateFailed();
            if (delegate != null) {
                delegate.handleFailure(cause, param);
            }
            logger.errorf(cause, "Caught exception handling update (param is %s)", param);
        }

        @Override
        public void handleSuccess(R result, P param) {
            updateComplete();
            if (delegate != null) {
                delegate.handleSuccess(result, param);
            }
        }

        @Override
        public void handleTimeout(P param) {
            updateFailed();
            if (delegate != null) {
                delegate.handleTimeout(param);
            }
        }

        @Override
        public void handleRollbackFailure(Throwable cause, P param) {
            rollbackFailed();
            if (delegate != null) {
                delegate.handleRollbackFailure(cause, param);
            }
            logger.errorf(cause, "Caught exception handling rollback of an update (param is %s)", param);
        }

        @Override
        public void handleRollbackSuccess(P param) {
            rollbackComplete();
            if (delegate != null) {
                delegate.handleRollbackSuccess(param);
            }
        }

        @Override
        public void handleRollbackCancellation(P param) {
            rollbackFailed();
            if (delegate != null) {
                delegate.handleRollbackCancellation(param);
            }
        }

        @Override
        public void handleRollbackTimeout(P param) {
            rollbackFailed();
            if (delegate != null) {
                delegate.handleRollbackTimeout(param);
            }
        }

        private UpdateResultHandler<? super R, P> getDelegate() {
            return this.delegate;
        }
    }


}
TOP

Related Classes of org.jboss.as.server.mgmt.ServerUpdateController$ServerUpdateCommitHandler

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.