/* * JBoss, Home of Professional Open Source. * Copyright 2012, 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.domain.controller.plan; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.Future; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.client.MessageSeverity; import org.jboss.as.controller.client.OperationAttachments; import org.jboss.as.controller.client.OperationMessageHandler; import org.jboss.as.controller.client.OperationResponse; import org.jboss.as.controller.remote.BlockingQueueOperationListener; import org.jboss.as.controller.remote.TransactionalOperationImpl; import org.jboss.as.controller.remote.TransactionalProtocolClient; import org.jboss.as.controller.transform.OperationResultTransformer; import org.jboss.as.domain.controller.ServerIdentity; import org.jboss.as.domain.controller.logging.DomainControllerLogger; import org.jboss.dmr.ModelNode; import org.jboss.threads.AsyncFuture; /** * @author Emanuel Muckenhuber */ public abstract class ServerTaskExecutor { private final OperationContext context; private final Map<ServerIdentity, ExecutedServerRequest> submittedTasks; private final List<ServerTaskExecutor.ServerPreparedResponse> preparedResults; protected ServerTaskExecutor(OperationContext context, Map<ServerIdentity, ExecutedServerRequest> submittedTasks, List<ServerPreparedResponse> preparedResults) { this.context = context; this.submittedTasks = submittedTasks; this.preparedResults = preparedResults; } /** * Execute * * @param listener the transactional operation listener * @param identity the server identity * @param operation the operation * @return time to wait in ms for a response from the server, or {@code -1} if the task execution failed locally * @throws OperationFailedException */ protected abstract int execute(final TransactionalProtocolClient.TransactionalOperationListener<ServerOperation> listener, final ServerIdentity identity, final ModelNode operation) throws OperationFailedException; /** * Execute a server task. * * @param listener the transactional server listener * @param task the server task * @return time to wait in ms for a response from the server, or {@code -1} if the task execution failed locally */ public int executeTask(final TransactionalProtocolClient.TransactionalOperationListener<ServerOperation> listener, final ServerUpdateTask task) { try { return execute(listener, task.getServerIdentity(), task.getOperation()); } catch (OperationFailedException e) { // Handle failures operation transformation failures final ServerIdentity identity = task.getServerIdentity(); final ServerOperation serverOperation = new ServerOperation(identity, task.getOperation(), null, null, OperationResultTransformer.ORIGINAL_RESULT); final TransactionalProtocolClient.PreparedOperation<ServerOperation> result = BlockingQueueOperationListener.FailedOperation.create(serverOperation, e); listener.operationPrepared(result); recordExecutedRequest(new ExecutedServerRequest(identity, result.getFinalResult(), OperationResultTransformer.ORIGINAL_RESULT)); return 1; // 1 ms timeout since there is no reason to wait for the locally stored result } } void cancelTask(ServerIdentity toCancel) { ExecutedServerRequest task = submittedTasks.get(toCancel); if (task != null) { task.asyncCancel(); } } /** * Execute the operation. * * @param listener the transactional operation listener * @param client the transactional protocol client * @param identity the server identity * @param operation the operation * @param transformer the operation result transformer * @return whether the operation was executed */ protected boolean executeOperation(final TransactionalProtocolClient.TransactionalOperationListener<ServerOperation> listener, TransactionalProtocolClient client, final ServerIdentity identity, final ModelNode operation, final OperationResultTransformer transformer) { if(client == null) { return false; } final OperationMessageHandler messageHandler = new DelegatingMessageHandler(context); final OperationAttachments operationAttachments = new DelegatingOperationAttachments(context); final ServerOperation serverOperation = new ServerOperation(identity, operation, messageHandler, operationAttachments, transformer); try { DomainControllerLogger.HOST_CONTROLLER_LOGGER.tracef("Sending %s to %s", operation, identity); final Future<OperationResponse> result = client.execute(listener, serverOperation); recordExecutedRequest(new ExecutedServerRequest(identity, result, transformer)); } catch (IOException e) { final TransactionalProtocolClient.PreparedOperation<ServerOperation> result = BlockingQueueOperationListener.FailedOperation.create(serverOperation, e); listener.operationPrepared(result); recordExecutedRequest(new ExecutedServerRequest(identity, result.getFinalResult(), transformer)); } return true; } /** * Record an executed request. * * @param task the executed task */ void recordExecutedRequest(final ExecutedServerRequest task) { synchronized (submittedTasks) { submittedTasks.put(task.getIdentity(), task); } } /** * Record a prepare operation. * * @param preparedOperation the prepared operation */ void recordPreparedOperation(final TransactionalProtocolClient.PreparedOperation<ServerTaskExecutor.ServerOperation> preparedOperation) { recordPreparedTask(new ServerTaskExecutor.ServerPreparedResponse(preparedOperation)); } /** * Record a prepare operation timeout. * * @param failedOperation the prepared operation */ void recordOperationPrepareTimeout(final BlockingQueueOperationListener.FailedOperation<ServerOperation> failedOperation) { recordPreparedTask(new ServerTaskExecutor.ServerPreparedResponse(failedOperation)); // Swap out the submitted task so we don't wait for the final result. Use a future the returns // prepared response ServerIdentity identity = failedOperation.getOperation().getIdentity(); AsyncFuture<OperationResponse> finalResult = failedOperation.getFinalResult(); synchronized (submittedTasks) { submittedTasks.put(identity, new ServerTaskExecutor.ExecutedServerRequest(identity, finalResult)); } } /** * Record a prepared operation. * * @param task the prepared operation */ void recordPreparedTask(ServerTaskExecutor.ServerPreparedResponse task) { synchronized (preparedResults) { preparedResults.add(task); } } static class ServerOperationListener extends BlockingQueueOperationListener<ServerOperation> { @Override public void operationPrepared(TransactionalProtocolClient.PreparedOperation<ServerOperation> prepared) { super.operationPrepared(prepared); } @Override public void operationComplete(ServerOperation operation, OperationResponse result) { super.operationComplete(operation, result); } @Override protected void drainTo(Collection<TransactionalProtocolClient.PreparedOperation<ServerOperation>> preparedOperations) { super.drainTo(preparedOperations); } } public static class ServerOperation extends TransactionalOperationImpl { private final ServerIdentity identity; private final OperationResultTransformer transformer; ServerOperation(ServerIdentity identity, ModelNode operation, OperationMessageHandler messageHandler, OperationAttachments attachments, OperationResultTransformer transformer) { super(operation, messageHandler, attachments); this.identity = identity; this.transformer = transformer; } public ServerIdentity getIdentity() { return identity; } ModelNode transformResult(ModelNode result) { return transformer.transformResult(result); } } /** * The prepared response. */ public static class ServerPreparedResponse { private TransactionalProtocolClient.PreparedOperation<ServerOperation> preparedOperation; ServerPreparedResponse(TransactionalProtocolClient.PreparedOperation<ServerOperation> preparedOperation) { this.preparedOperation = preparedOperation; } public TransactionalProtocolClient.PreparedOperation<ServerOperation> getPreparedOperation() { return preparedOperation; } public ServerIdentity getServerIdentity() { return preparedOperation.getOperation().getIdentity(); } public String getServerGroupName() { return getServerIdentity().getServerGroupName(); } /** Gets whether the response represents a timeout */ public boolean isTimedOut() { return preparedOperation.isTimedOut(); } /** * Finalize the transaction. This will return {@code false} in case the local operation failed, * but the overall state of the operation is commit=true. * * @param commit {@code true} to commit, {@code false} to rollback * @return whether the local proxy operation result is in sync with the overall operation */ public boolean finalizeTransaction(boolean commit) { final boolean failed = preparedOperation.isFailed(); if(commit && failed) { return false; } if(commit) { preparedOperation.commit(); } else { if(!failed) { preparedOperation.rollback(); } } return true; } } /** * The executed request. */ public static class ExecutedServerRequest implements OperationResultTransformer { private final ServerIdentity identity; private final Future<OperationResponse> finalResult; private final OperationResultTransformer transformer; public ExecutedServerRequest(ServerIdentity identity, Future<OperationResponse> finalResult) { this(identity, finalResult, OperationResultTransformer.ORIGINAL_RESULT); } public ExecutedServerRequest(ServerIdentity identity, Future<OperationResponse> finalResult, OperationResultTransformer transformer) { this.identity = identity; this.finalResult = finalResult; this.transformer = transformer; } public ServerIdentity getIdentity() { return identity; } public Future<OperationResponse> getFinalResult() { return finalResult; } @Override public ModelNode transformResult(final ModelNode result) { return transformer.transformResult(result); } private void asyncCancel() { if (finalResult instanceof AsyncFuture) { ((AsyncFuture) finalResult).asyncCancel(true); } } } private static class DelegatingMessageHandler implements OperationMessageHandler { private final OperationContext context; DelegatingMessageHandler(final OperationContext context) { this.context = context; } @Override public void handleReport(MessageSeverity severity, String message) { context.report(severity, message); } } private static class DelegatingOperationAttachments implements OperationAttachments { private final OperationContext context; private DelegatingOperationAttachments(final OperationContext context) { this.context = context; } @Override public boolean isAutoCloseStreams() { return false; } @Override public List<InputStream> getInputStreams() { int count = context.getAttachmentStreamCount(); List<InputStream> result = new ArrayList<InputStream>(count); for (int i = 0; i < count; i++) { result.add(context.getAttachmentStream(i)); } return result; } @Override public void close() throws IOException { // } } }