/*
* 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.operations.global;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ACCESS_CONTROL;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION;
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.READ_CHILDREN_RESOURCES_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESULT;
import static org.jboss.as.controller.operations.global.GlobalOperationAttributes.CHILD_TYPE;
import static org.jboss.as.controller.operations.global.GlobalOperationAttributes.INCLUDE_DEFAULTS;
import static org.jboss.as.controller.operations.global.GlobalOperationAttributes.INCLUDE_RUNTIME;
import static org.jboss.as.controller.operations.global.GlobalOperationAttributes.PROXIES;
import static org.jboss.as.controller.operations.global.GlobalOperationAttributes.RECURSIVE;
import static org.jboss.as.controller.operations.global.GlobalOperationAttributes.RECURSIVE_DEPTH;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
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.PathElement;
import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
import org.jboss.as.controller.descriptions.common.ControllerResolver;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration;
import org.jboss.as.controller.registry.Resource;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
/**
* {@link org.jboss.as.controller.OperationStepHandler} querying the children resources of a given "child-type".
*
* @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
*/
public class ReadChildrenResourcesHandler implements OperationStepHandler {
static final OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(READ_CHILDREN_RESOURCES_OPERATION, ControllerResolver.getResolver("global"))
.setParameters(CHILD_TYPE, RECURSIVE, RECURSIVE_DEPTH, PROXIES, INCLUDE_RUNTIME, INCLUDE_DEFAULTS)
.setReadOnly()
.setRuntimeOnly()
.setReplyType(ModelType.LIST)
.setReplyValueType(ModelType.OBJECT)
.build();
static final OperationStepHandler INSTANCE = new ReadChildrenResourcesHandler();
@Override
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
final PathAddress address = context.getCurrentAddress();
final String childType = CHILD_TYPE.resolveModelAttribute(context, operation).asString();
// Build up the op we're going to repeatedly execute
final ModelNode readOp = new ModelNode();
readOp.get(OP).set(READ_RESOURCE_OPERATION);
INCLUDE_RUNTIME.validateAndSet(operation, readOp);
RECURSIVE.validateAndSet(operation, readOp);
RECURSIVE_DEPTH.validateAndSet(operation, readOp);
PROXIES.validateAndSet(operation, readOp);
INCLUDE_DEFAULTS.validateAndSet(operation, readOp);
final Map<PathElement, ModelNode> resources = new HashMap<PathElement, ModelNode>();
final Resource resource = context.readResource(PathAddress.EMPTY_ADDRESS, false);
final ImmutableManagementResourceRegistration registry = context.getResourceRegistration();
Map<String, Set<String>> childAddresses = GlobalOperationHandlers.getChildAddresses(context, address, registry, resource, childType);
Set<String> childNames = childAddresses.get(childType);
if (childNames == null) {
throw new OperationFailedException(ControllerLogger.ROOT_LOGGER.unknownChildType(childType));
}
// Track any excluded items
FilteredData filteredData = new FilteredData(address);
// We're going to add a bunch of steps that should immediately follow this one. We are going to add them
// in reverse order of how they should execute, building up a stack.
// Last to execute is the handler that assembles the overall response from the pieces created by all the other steps
final ReadChildrenResourcesAssemblyHandler assemblyHandler = new ReadChildrenResourcesAssemblyHandler(resources, filteredData, address, childType);
context.addStep(assemblyHandler, OperationContext.Stage.MODEL, true);
for (final String key : childNames) {
final PathElement childPath = PathElement.pathElement(childType, key);
final PathAddress childAddress = PathAddress.EMPTY_ADDRESS.append(PathElement.pathElement(childType, key));
final ModelNode readResOp = readOp.clone();
readResOp.get(OP_ADDR).set(PathAddress.pathAddress(address, childPath).toModelNode());
// See if there was an override registered for the standard :read-resource handling (unlikely!!!)
OperationStepHandler overrideHandler = context.getResourceRegistration().getOperationHandler(childAddress, READ_RESOURCE_OPERATION);
if (overrideHandler == null) {
throw new OperationFailedException(ControllerLogger.ROOT_LOGGER.noOperationHandler());
} else if (overrideHandler.getClass() == ReadResourceHandler.class) {
// not an override
overrideHandler = null;
}
OperationStepHandler rrHandler = new ReadResourceHandler(filteredData, overrideHandler, false);
final ModelNode rrRsp = new ModelNode();
resources.put(childPath, rrRsp);
context.addStep(rrRsp, readResOp, rrHandler, OperationContext.Stage.MODEL, true);
}
}
/**
* Assembles the response to a read-resource request from the components gathered by earlier steps.
*/
private static class ReadChildrenResourcesAssemblyHandler implements OperationStepHandler {
private final Map<PathElement, ModelNode> resources;
private final FilteredData filteredData;
private final PathAddress address;
private final String childType;
/**
* Creates a ReadResourceAssemblyHandler that will assemble the response using the contents
* of the given maps.
*
* @param resources read-resource response from child resources, where the key is the path of the resource
* relative to the address of the operation this handler is handling and the
* value is the full read-resource response. Will not be {@code null}
* @param filteredData record of any excluded data
* @param address the address of the targeted resource
* @param childType the type of child being read
*/
private ReadChildrenResourcesAssemblyHandler(final Map<PathElement, ModelNode> resources, FilteredData filteredData,
PathAddress address, String childType) {
this.resources = resources;
this.filteredData = filteredData;
this.address = address;
this.childType = childType;
}
@Override
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
context.addStep(new OperationStepHandler() {
@Override
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
Map<String, ModelNode> sortedChildren = new TreeMap<String, ModelNode>();
boolean failed = false;
for (Map.Entry<PathElement, ModelNode> entry : resources.entrySet()) {
PathElement path = entry.getKey();
ModelNode value = entry.getValue();
if (!value.has(FAILURE_DESCRIPTION)) {
if (value.hasDefined(RESULT)) {
sortedChildren.put(path.getValue(), value.get(RESULT));
} else {
// A child did not produce a response. We don't know if the definition
// of our resource indicates the child that has disappeared must be
// present, so we don't want to produce a response for our resource
// without the child if our resource is now gone as well.
// So, see if our resource has disappeared as well.
if (!filteredData.isAddressFiltered(address, path)) {
// Wasn't filtered. Confirm our resource still exists
context.readResourceFromRoot(address, false);
} // else there's no result because it was just filtered
}
} else if (!failed && value.hasDefined(FAILURE_DESCRIPTION)) {
context.getFailureDescription().set(value.get(FAILURE_DESCRIPTION));
failed = true;
}
}
if (!failed) {
boolean hasFilteredData = filteredData.hasFilteredData();
final ModelNode result = context.getResult();
result.setEmptyObject();
for (Map.Entry<String, ModelNode> entry : sortedChildren.entrySet()) {
if (!hasFilteredData || !filteredData.isAddressFiltered(address, PathElement.pathElement(childType, entry.getKey()))) {
result.get(entry.getKey()).set(entry.getValue());
}
}
if (hasFilteredData) {
context.getResponseHeaders().get(ACCESS_CONTROL).set(filteredData.toModelNode());
}
}
}
}, OperationContext.Stage.VERIFY);
}
}
}