/* * JBoss, Home of Professional Open Source. * Copyright 2015, 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.operations; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationDefinition; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.OperationStepHandler; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.logging.ControllerLogger; import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration; import org.jboss.as.controller.registry.OperationEntry; import org.jboss.dmr.ModelNode; /** * Utility code related to multistep operations. * * @author Brian Stansberry */ public final class MultistepUtil { /** Prevent instantiation */ private MultistepUtil() {} /** * Adds a step to the given {@link OperationContext} for each operation included in the given {@code operations} list, either * using for each step a response node provided in the {@code responses} list, or if the {@code responses} list is empty, * creating them and storing them in the {@code responses} list. The response objects are not tied into the overall response * to the operation associated with {@code context}. It is the responsibility of the caller to do that. * * @param context the {@code OperationContext}. Cannot be {@code null} * @param operations the list of operations, each element of which must be a proper OBJECT type model node with a structure describing an operation * @param responses a list of response nodes to use for each operation, each element of which corresponds to the operation in the equivalent position * in the {@code operations} list. Cannot be {@code null} but may be empty in which case this method will * create the response nodes and add them to this list. * @throws OperationFailedException if there is a problem registering a step for any of the operations */ public static void recordOperationSteps(final OperationContext context, final List<ModelNode> operations, final List<ModelNode> responses) throws OperationFailedException { assert responses.isEmpty() || operations.size() == responses.size(); boolean responsesProvided = !responses.isEmpty(); LinkedHashMap<Integer, ModelNode> operationMap = new LinkedHashMap<>(); Map<Integer, ModelNode> responseMap = new LinkedHashMap<>(); int i = 0; for (ModelNode op : operations) { operationMap.put(i, op); if (responsesProvided) { ModelNode response = responses.get(i); assert response != null : "No response provided for " + i; responseMap.put(i, response); } i++; } recordOperationSteps(context, operationMap, responseMap, OperationHandlerResolver.DEFAULT, false); if (!responsesProvided) { for (ModelNode response : responseMap.values()) { responses.add(response); } } } /** * Adds a step to the given {@link OperationContext} for each operation included in the given map, either * using for each step a response node provided in the {@code responses} map, or if the {@code responses} map is empty, * creating them and storing them in the {@code responses} map. The response objects are not tied into the overall response * to the operation associated with {@code context}. It is the responsibility of the caller to do that. * <p> * <strong>NOTE:</strong> The given {@code operations} map must provide an iterator over its entry set that provides the entries in the * order in which the operations should execute. A {@link LinkedHashMap} is the typical choice. * * @param context the {@code OperationContext}. Cannot be {@code null} * @param operations the operations, each value of which must be a proper OBJECT type model node with a structure describing an operation. * @param responses a map of the response nodes, the keys for which match the keys in the {@code operations} param. * Cannot be {@code null} but may be empty in which case this method will * create the response nodes and store them in this map. * @param <T> the type of the keys in the maps * @throws OperationFailedException if there is a problem registering a step for any of the operations */ public static <T> void recordOperationSteps(final OperationContext context, final Map<T, ModelNode> operations, final Map<T, ModelNode> responses) throws OperationFailedException { recordOperationSteps(context, operations, responses, OperationHandlerResolver.DEFAULT, false); } /** * This is a specialized version of the other variant of this method that allows a pluggable strategy * for resolving the {@link OperationStepHandler} to use for the added steps. It is not expected to * be needed by users outside the WildFly Core kernel. * * @param <T> the type of the keys in the maps * @param context the {@code OperationContext}. Cannot be {@code null} * @param operations the operations, each value of which must be a proper OBJECT type model node with a structure describing an operation. * @param responses a map of the response nodes, the keys for which match the keys in the {@code operations} param. * Cannot be {@code null} but may be empty in which case this method will * create the response nodes and store them in this map. * @param handlerResolver an object that can provide the {@code OperationStepHandler} to use for the operation * @param adjustAddresses {@code true} if the address of each operation should be adjusted to become a child of the context's * {@link OperationContext#getCurrentAddress() current address} * * @throws OperationFailedException if there is a problem registering a step for any of the operations * */ public static <T> void recordOperationSteps(final OperationContext context, final Map<T, ModelNode> operations, final Map<T, ModelNode> responses, final OperationHandlerResolver handlerResolver, final boolean adjustAddresses) throws OperationFailedException { assert responses != null; assert responses.isEmpty() || operations.size() == responses.size(); boolean responsesProvided = !responses.isEmpty(); PathAddress currentAddress = adjustAddresses ? context.getCurrentAddress() : null; List<OpData> opdatas = new ArrayList<>(); for (Map.Entry<T, ModelNode> entry : operations.entrySet()) { ModelNode response = responsesProvided ? responses.get(entry.getKey()) : new ModelNode(); assert response != null : "No response provided for " + entry.getValue(); ModelNode op = entry.getValue(); PathAddress stepAddress = PathAddress.pathAddress(op.get(OP_ADDR)); if (adjustAddresses) { stepAddress = currentAddress.append(stepAddress); op.get(OP_ADDR).set(stepAddress.toModelNode()); } OpData opData = getOpData(context, op, response, stepAddress, handlerResolver); // Reverse the order for addition to the context opdatas.add(0, opData); if (!responsesProvided) { responses.put(entry.getKey(), response); } } for (OpData opData : opdatas) { context.addModelStep(opData.response, opData.operation, opData.definition, opData.handler, true); } } private static OpData getOpData(OperationContext context, ModelNode subOperation, ModelNode response, PathAddress stepAddress, OperationHandlerResolver handlerResolver) throws OperationFailedException { ImmutableManagementResourceRegistration registry = context.getRootResourceRegistration(); String stepOpName = subOperation.require(OP).asString(); OperationEntry operationEntry = registry.getOperationEntry(stepAddress, stepOpName); if (operationEntry == null) { ImmutableManagementResourceRegistration child = registry.getSubModel(stepAddress); if (child == null) { throw new OperationFailedException(ControllerLogger.ROOT_LOGGER.noSuchResourceType(stepAddress)); } else { throw new OperationFailedException(ControllerLogger.ROOT_LOGGER.noHandlerForOperation(stepOpName, stepAddress)); } } OperationStepHandler osh = handlerResolver.getOperationStepHandler(stepOpName, stepAddress, subOperation, operationEntry); return new OpData(subOperation, operationEntry.getOperationDefinition(), osh, response); } private static class OpData { private final ModelNode operation; private final OperationDefinition definition; private final OperationStepHandler handler; private final ModelNode response; private OpData(ModelNode operation, OperationDefinition definition, OperationStepHandler handler, ModelNode response) { this.definition = definition; this.operation = operation; this.handler = handler; this.response = response; } } /** * Resolves an {@link OperationStepHandler} to use given a set of inputs. */ public interface OperationHandlerResolver { OperationHandlerResolver DEFAULT = new OperationHandlerResolver() {}; default OperationStepHandler getOperationStepHandler(String operationName, PathAddress address, ModelNode operation, OperationEntry operationEntry) { return operationEntry.getOperationHandler(); } } }