/*
* 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.controller.transform;
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.IGNORED;
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.STEPS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS;
import java.util.ArrayList;
import java.util.List;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.registry.AliasEntry;
import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration;
import org.jboss.dmr.ModelNode;
/**
* Operation transformer responsible for handling a composite operation.
*
* @author Emanuel Muckenhuber
*/
class CompositeOperationTransformer implements OperationTransformer {
private static final ModelNode SUCCESSFUL = new ModelNode();
static {
SUCCESSFUL.get(OUTCOME).set(SUCCESS);
SUCCESSFUL.get(RESULT);
SUCCESSFUL.protect();
}
@Override
public TransformedOperation transformOperation(final TransformationContext context, final PathAddress address, final ModelNode operation) throws OperationFailedException {
return transformOperation(context, address, operation, false);
}
TransformedOperation transformOperation(final TransformationContext context, final PathAddress address, final ModelNode operation, final boolean nested) throws OperationFailedException {
assert address.size() == 0;
final ModelNode composite = operation.clone();
composite.get(STEPS).setEmptyList();
final TransformationTarget target = context.getTarget();
final List<Step> steps = new ArrayList<Step>();
int stepIdx = 0, resultIdx = 0;
for(final ModelNode step : operation.require(STEPS).asList()) {
stepIdx++;
final String operationName = step.require(OP).asString();
final PathAddress stepAddress = step.hasDefined(OP_ADDR) ? PathAddress.pathAddress(step.require(OP_ADDR)) : PathAddress.EMPTY_ADDRESS;
final TransformedOperation result;
if(stepAddress.size() == 0 && COMPOSITE.equals(operationName)) {
// Process nested steps directly
result = transformOperation(context, PathAddress.EMPTY_ADDRESS, step, false);
} else {
//If this is an alias, get the real address before transforming
ImmutableManagementResourceRegistration reg = context.getResourceRegistration(stepAddress);
final PathAddress useAddress;
if (reg != null && reg.isAlias()) {
useAddress = reg.getAliasEntry().convertToTargetAddress(stepAddress, AliasEntry.AliasContext.create(step, context));
} else {
useAddress = stepAddress;
}
final OperationTransformer transformer = target.resolveTransformer(context, useAddress, operationName);
final PathAddress transformed = TransformersImpl.transformAddress(useAddress, target);
// Update the operation using the new path address
step.get(OP_ADDR).set(transformed.toModelNode()); // TODO should this happen by default?
result = transformer.transformOperation(context, transformed, step);
}
final ModelNode transformedOperation = result.getTransformedOperation();
if (transformedOperation != null) {
composite.get(STEPS).add(transformedOperation);
resultIdx++;
}
steps.add(new Step(stepIdx, resultIdx, result));
}
final CompositeResultTransformer resultHandler = new CompositeResultTransformer(steps);
return new TransformedOperation(composite, resultHandler, resultHandler);
}
private static class CompositeResultTransformer implements OperationResultTransformer, OperationRejectionPolicy {
private final List<Step> steps;
private volatile Step failedStep;
private CompositeResultTransformer(final List<Step> steps) {
this.steps = steps;
}
// TODO WFCORE-624
@Override
public boolean rejectOperation(final ModelNode preparedResult) {
for(final Step step : steps) {
if(step.isDiscarded()) {
continue;
}
final String resultIdx = "step-" + step.getResultingIdx();
// WFCORE-622 partial workaround. If there's no step-x node, assume it's a mismatch between
// the result being checked and our list of steps. Don't modify the result.
// This doesn't solve the problem of mismatched checks of steps that do exist,
// but it prevents pollution of the result with spurious child nodes for irrelevant steps.
final ModelNode stepResult = preparedResult.hasDefined(RESULT, resultIdx)
? preparedResult.get(RESULT, resultIdx)
: new ModelNode();
// ignored operations have no effect
if(stepResult.hasDefined(OUTCOME) && IGNORED.equals(stepResult.get(OUTCOME).asString())) {
continue;
}
final TransformedOperation stepPolicy = step.getResult();
if(stepPolicy.rejectOperation(stepResult)) {
// Only report the first failing step
failedStep = step;
return true;
}
}
return false;
}
@Override
public String getFailureDescription() {
if(failedStep != null) {
return failedStep.getResult().getFailureDescription();
}
return "";
}
// TODO WFCORE-624
@Override
public ModelNode transformResult(final ModelNode original) {
final ModelNode response = original.clone();
final ModelNode result = response.get(RESULT).setEmptyObject();
boolean modified = false;
for(final Step step : steps) {
final String stepIdx = "step-" + step.getStepCount();
// Set a successful result for discarded steps
if(step.isDiscarded()) {
result.get(stepIdx).set(SUCCESSFUL);
continue;
}
final String resultIdx = "step-" + step.getResultingIdx();
// WFCORE-622 partial workaround. If there's no step-x node, assume it's a mismatch between
// the result being checked and our list of steps. Don't modify the result.
// This doesn't solve the problem of mismatched checks of steps that do exist,
// but it prevents pollution of the result with spurious child nodes for irrelevant steps.
final ModelNode stepResult = original.hasDefined(RESULT, resultIdx)
? original.get(RESULT, resultIdx) : new ModelNode();
// Mark ignored steps as successful
if(stepResult.hasDefined(OUTCOME) && IGNORED.equals(stepResult.get(OUTCOME).asString())) {
result.get(stepIdx).set(SUCCESSFUL);
modified = true;
} else {
final OperationResultTransformer transformer = step.getResult();
// In case this is the failed step
if(step.getResult().rejectOperation(stepResult)) {
// Replace the response of the failed step
stepResult.get(OUTCOME).set(FAILED);
stepResult.get(FAILURE_DESCRIPTION).set(step.getResult().getFailureDescription());
}
ModelNode transformed = transformer.transformResult(stepResult);
if (transformed.isDefined() || original.has(RESULT, resultIdx)) {
result.get(stepIdx).set(transformer.transformResult(stepResult));
modified = true;
}
}
}
return modified ? response : original;
}
}
private static class Step {
private final int stepCount;
private final int resultingIdx;
private final TransformedOperation result;
private Step(int step, int resultingIdx, TransformedOperation result) {
this.stepCount = step;
this.resultingIdx = resultingIdx;
this.result = result;
}
boolean isDiscarded() {
return result.getTransformedOperation() == null;
}
int getResultingIdx() {
return resultingIdx;
}
int getStepCount() {
return stepCount;
}
TransformedOperation getResult() {
return result;
}
@Override
public String toString() {
return "Step{" +
"step=" + stepCount +
", operation=" + result.getTransformedOperation() +
'}';
}
}
}