/*
* 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.operations.coordination;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.COMPOSITE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DOMAIN_RESULTS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.EXTENSION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.IGNORED;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATION_HEADERS;
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.STEPS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS;
import static org.jboss.as.domain.controller.logging.DomainControllerLogger.HOST_CONTROLLER_LOGGER;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.TransformingProxyController;
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.CompletedFuture;
import org.jboss.as.controller.remote.TransactionalOperationImpl;
import org.jboss.as.controller.remote.TransactionalProtocolClient;
import org.jboss.as.controller.transform.OperationRejectionPolicy;
import org.jboss.as.controller.transform.OperationResultTransformer;
import org.jboss.as.controller.transform.OperationTransformer;
import org.jboss.as.controller.transform.TransformationTarget;
import org.jboss.as.controller.transform.Transformers;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.jboss.dmr.Property;
import org.jboss.threads.AsyncFuture;
/**
* @author Emanuel Muckenhuber
*/
class HostControllerUpdateTask {
private final String name;
private final ModelNode operation;
private final OperationContext context;
private final TransformingProxyController proxyController;
private final Transformers.TransformationInputs transformationInputs;
public HostControllerUpdateTask(final String name, final ModelNode operation, final OperationContext context,
final TransformingProxyController proxyController,
final Transformers.TransformationInputs transformationInputs) {
this.name = name;
this.context = context;
this.operation = operation;
this.proxyController = proxyController;
this.transformationInputs = transformationInputs;
}
public ExecutedHostRequest execute(final ProxyOperationListener listener) {
final TransactionalProtocolClient client = proxyController.getProtocolClient();
final OperationMessageHandler messageHandler = new DelegatingMessageHandler(context);
final OperationAttachments operationAttachments = new DelegatingOperationAttachments(context);
final SubsystemInfoOperationListener subsystemListener = new SubsystemInfoOperationListener(listener, proxyController.getTransformers());
try {
final OperationTransformer.TransformedOperation transformationResult = proxyController.transformOperation(transformationInputs, operation);
final ModelNode transformedOperation = transformationResult.getTransformedOperation();
final ProxyOperation proxyOperation = new ProxyOperation(name, transformedOperation, messageHandler, operationAttachments);
try {
// Make sure we preserve the operation headers like PrepareStepHandler.EXECUTE_FOR_COORDINATOR
if(transformedOperation != null) {
transformedOperation.get(OPERATION_HEADERS).set(operation.get(OPERATION_HEADERS));
// If the operation was transformed
if (!operation.equals(transformedOperation)) {
// push all operations (incl. read-only) to the servers
transformedOperation.get(OPERATION_HEADERS, ServerOperationsResolverHandler.DOMAIN_PUSH_TO_SERVERS).set(true);
HOST_CONTROLLER_LOGGER.tracef("Sending %s (transformed to %s) to %s", operation, transformedOperation, name);
} else {
HOST_CONTROLLER_LOGGER.tracef("Sending %s (untransformed) to %s", transformedOperation, name);
}
} else {
HOST_CONTROLLER_LOGGER.tracef("Sending %s (transformed to null) to %s", operation, name);
}
final AsyncFuture<OperationResponse> result = client.execute(subsystemListener, proxyOperation);
return new ExecutedHostRequest(result, transformationResult);
} catch (IOException e) {
// Handle protocol failures
final TransactionalProtocolClient.PreparedOperation<ProxyOperation> result = BlockingQueueOperationListener.FailedOperation.create(proxyOperation, e);
subsystemListener.operationPrepared(result);
return new ExecutedHostRequest(result.getFinalResult(), transformationResult);
}
} catch (OperationFailedException e) {
// Handle transformation failures
final ProxyOperation proxyOperation = new ProxyOperation(name, operation, messageHandler, operationAttachments);
final TransactionalProtocolClient.PreparedOperation<ProxyOperation> result = BlockingQueueOperationListener.FailedOperation.create(proxyOperation, e);
subsystemListener.operationPrepared(result);
return new ExecutedHostRequest(result.getFinalResult(), OperationResultTransformer.ORIGINAL_RESULT, OperationTransformer.DEFAULT_REJECTION_POLICY);
}
}
static class ProxyOperation extends TransactionalOperationImpl {
private final String name;
protected ProxyOperation(final String name, final ModelNode operation, final OperationMessageHandler messageHandler, final OperationAttachments attachments) {
super(operation, messageHandler, attachments);
this.name = name;
}
public String getName() {
return name;
}
}
static class ExecutedHostRequest implements OperationResultTransformer, OperationRejectionPolicy {
private final AsyncFuture<OperationResponse> futureResult;
private final OperationResultTransformer resultTransformer;
private final OperationRejectionPolicy rejectPolicy;
ExecutedHostRequest(AsyncFuture<OperationResponse> futureResult, OperationResultTransformer resultTransformer, OperationRejectionPolicy rejectPolicy) {
this.futureResult = futureResult;
this.resultTransformer = resultTransformer;
this.rejectPolicy = rejectPolicy;
}
ExecutedHostRequest(AsyncFuture<OperationResponse> futureResult, OperationTransformer.TransformedOperation transformedOperation) {
this(futureResult, transformedOperation, transformedOperation);
}
@Override
public boolean rejectOperation(ModelNode result) {
// Check the host result for successful operations and see if we have to reject it
if(result.has(RESULT, DOMAIN_RESULTS)) {
final ModelNode domainResults = result.get(RESULT, DOMAIN_RESULTS);
// Don't reject ignored operations
if(domainResults.getType() == ModelType.STRING && IGNORED.equals(domainResults.asString())) {
return false;
}
// The format of the prepared operation of the domain coordination step1 is different from a normal operation
// a user would need to handle, therefore try to fix it up as good as possible
final ModelNode userOp = new ModelNode();
userOp.get(OUTCOME).set(SUCCESS);
userOp.get(RESULT).set(domainResults);
return rejectPolicy.rejectOperation(userOp);
} else {
// This should only handle failed host operations
return rejectPolicy.rejectOperation(result);
}
}
@Override
public String getFailureDescription() {
return rejectPolicy.getFailureDescription();
}
@Override
public ModelNode transformResult(ModelNode result) {
final boolean reject = rejectOperation(result);
if(reject) {
result.get(FAILURE_DESCRIPTION).set(getFailureDescription());
}
if(result.has(RESULT, DOMAIN_RESULTS)) {
final ModelNode domainResults = result.get(RESULT, DOMAIN_RESULTS);
if(domainResults.getType() == ModelType.STRING && IGNORED.equals(domainResults.asString())) {
// Untransformed
return result;
}
final ModelNode userResult = new ModelNode();
userResult.get(OUTCOME).set(result.get(OUTCOME));
userResult.get(RESULT).set(domainResults);
if(result.hasDefined(FAILURE_DESCRIPTION)) {
userResult.get(FAILURE_DESCRIPTION).set(result.get(FAILURE_DESCRIPTION));
}
// Transform the result
final ModelNode transformed = resultTransformer.transformResult(userResult);
result.get(RESULT, DOMAIN_RESULTS).set(transformed.get(RESULT));
return result;
} else {
return resultTransformer.transformResult(result);
}
}
public void asyncCancel() {
futureResult.asyncCancel(true);
}
ExecutedHostRequest toFailedRequest(ModelNode finalResponse) {
OperationResponse simpleResponse = OperationResponse.Factory.createSimple(finalResponse);
return new ExecutedHostRequest(new CompletedFuture<>(simpleResponse), resultTransformer, rejectPolicy);
}
}
/**
* The transactional operation listener.
*/
static class ProxyOperationListener extends BlockingQueueOperationListener<ProxyOperation> {
final boolean trace = HOST_CONTROLLER_LOGGER.isTraceEnabled();
@Override
public void operationPrepared(final TransactionalProtocolClient.PreparedOperation<ProxyOperation> prepared) {
try {
super.operationPrepared(prepared);
} finally {
if (trace) {
final ModelNode result = prepared.getPreparedResult();
final String hostName = prepared.getOperation().getName();
HOST_CONTROLLER_LOGGER.tracef("Received prepared result %s from %s", result, hostName);
}
}
}
@Override
public void operationComplete(final ProxyOperation operation, final OperationResponse result) {
try {
super.operationComplete(operation, result);
} finally {
if (trace) {
final String hostName = operation.getName();
HOST_CONTROLLER_LOGGER.tracef("Received final result %s from %s", result, hostName);
}
}
}
}
/** Checks responses from slaves for subsystem version information. TODO this is pretty hacky */
private static class SubsystemInfoOperationListener implements TransactionalProtocolClient.TransactionalOperationListener<ProxyOperation> {
private final ProxyOperationListener delegate;
private final Transformers transformers;
private SubsystemInfoOperationListener(ProxyOperationListener delegate, Transformers transformers) {
this.delegate = delegate;
this.transformers = transformers;
}
@Override
public void operationPrepared(TransactionalProtocolClient.PreparedOperation<ProxyOperation> prepared) {
delegate.operationPrepared(prepared);
}
@Override
public void operationFailed(ProxyOperation operation, ModelNode result) {
delegate.operationFailed(operation, result);
}
@Override
public void operationComplete(ProxyOperation operation, OperationResponse result) {
try {
ModelNode responseNode = result.getResponseNode();
if (responseNode.hasDefined(RESULT, DOMAIN_RESULTS)) {
storeSubsystemVersions(operation.getOperation(), responseNode.get(RESULT, DOMAIN_RESULTS));
}
} finally {
delegate.operationComplete(operation, result);
}
}
private void storeSubsystemVersions(ModelNode operation, ModelNode resultNode) {
PathAddress address = operation.hasDefined(OP_ADDR) ? PathAddress.pathAddress(operation.get(OP_ADDR)) : PathAddress.EMPTY_ADDRESS;
if (address.size() == 0 && COMPOSITE.equals(operation.get(OP).asString())) {
// recurse
List<ModelNode> steps = operation.hasDefined(STEPS) ? operation.get(STEPS).asList() : Collections.<ModelNode>emptyList();
for (int i = 0; i < steps.size(); i++) {
ModelNode stepOp = steps.get(i);
String resultID = "step-" + (i+1);
if (resultNode.hasDefined(resultID, RESULT)) {
storeSubsystemVersions(stepOp, resultNode.get(resultID, RESULT));
}
}
} else if (address.size() == 1 && ADD.equals(operation.get(OP).asString())
&& EXTENSION.equals(address.getElement(0).getKey())) {
// Extract the subsystem info and store it
TransformationTarget target = transformers.getTarget();
for (Property p : resultNode.asPropertyList()) {
String[] version = p.getValue().asString().split("\\.");
int major = Integer.parseInt(version[0]);
int minor = Integer.parseInt(version[1]);
target.addSubsystemVersion(p.getName(), major, minor);
HOST_CONTROLLER_LOGGER.debugf("Registering subsystem %s for host %s with major version [%d] and minor version [%d]",
p.getName(), address, major, minor);
}
// purge the subsystem version data from the response
resultNode.set(new ModelNode());
}
}
}
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 {
//
}
}
}