/*
* 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.ATTRIBUTES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DEFAULT;
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_ATTRIBUTE_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.INCLUDE_ALIASES;
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.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.jboss.as.controller.AttributeDefinition;
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.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
import org.jboss.as.controller.UnauthorizedException;
import org.jboss.as.controller.access.Action;
import org.jboss.as.controller.access.AuthorizationResult;
import org.jboss.as.controller.access.ResourceNotAddressableException;
import org.jboss.as.controller.descriptions.DescriptionProvider;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.descriptions.common.ControllerResolver;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.controller.operations.validation.ParametersValidator;
import org.jboss.as.controller.registry.AttributeAccess;
import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration;
import org.jboss.as.controller.registry.PlaceholderResource;
import org.jboss.as.controller.registry.Resource;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.jboss.dmr.Property;
/**
* {@link org.jboss.as.controller.OperationStepHandler} reading a part of the model. The result will only contain the current attributes of a node by default,
* excluding all addressable children and runtime attributes. Setting the request parameter "recursive" to "true" will recursively include
* all children and configuration attributes. Queries can include runtime attributes by setting the request parameter
* "include-runtime" to "true".
*
* @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
*/
public class ReadResourceHandler extends GlobalOperationHandlers.AbstractMultiTargetHandler {
private static final SimpleAttributeDefinition ATTRIBUTES_ONLY = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.ATTRIBUTES_ONLY, ModelType.BOOLEAN)
.setAllowNull(true)
.setDefaultValue(new ModelNode(false))
.build();
public static final OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(READ_RESOURCE_OPERATION, ControllerResolver.getResolver("global"))
.setParameters(RECURSIVE, RECURSIVE_DEPTH, PROXIES, INCLUDE_RUNTIME, INCLUDE_DEFAULTS, ATTRIBUTES_ONLY, INCLUDE_ALIASES)
.setReadOnly()
.setRuntimeOnly()
.setReplyType(ModelType.OBJECT)
.build();
public static final OperationStepHandler INSTANCE = new ReadResourceHandler();
private static final SimpleAttributeDefinition RESOLVE = new SimpleAttributeDefinitionBuilder(ModelDescriptionConstants.RESOLVE_EXPRESSIONS, ModelType.BOOLEAN)
.setAllowNull(true)
.setDefaultValue(new ModelNode(false))
.build();
public static final OperationDefinition RESOLVE_DEFINITION = new SimpleOperationDefinitionBuilder(READ_RESOURCE_OPERATION, ControllerResolver.getResolver("global"))
.setParameters(RESOLVE, RECURSIVE, RECURSIVE_DEPTH, PROXIES, INCLUDE_RUNTIME, INCLUDE_DEFAULTS, ATTRIBUTES_ONLY, INCLUDE_ALIASES)
.setReadOnly()
.setRuntimeOnly()
.setReplyType(ModelType.OBJECT)
.build();
public static final OperationStepHandler RESOLVE_INSTANCE = new ReadResourceHandler(true);
private final ParametersValidator validator = new ParametersValidator() {
@Override
public void validate(ModelNode operation) throws OperationFailedException {
super.validate(operation);
for (AttributeDefinition def : DEFINITION.getParameters()) {
def.validateOperation(operation);
}
if (operation.hasDefined(ModelDescriptionConstants.ATTRIBUTES_ONLY)) {
if (operation.hasDefined(ModelDescriptionConstants.RECURSIVE)) {
throw ControllerLogger.ROOT_LOGGER.cannotHaveBothParameters(ModelDescriptionConstants.ATTRIBUTES_ONLY, ModelDescriptionConstants.RECURSIVE);
}
if (operation.hasDefined(ModelDescriptionConstants.RECURSIVE_DEPTH)) {
throw ControllerLogger.ROOT_LOGGER.cannotHaveBothParameters(ModelDescriptionConstants.ATTRIBUTES_ONLY, ModelDescriptionConstants.RECURSIVE_DEPTH);
}
}
if( operation.hasDefined(ModelDescriptionConstants.RESOLVE_EXPRESSIONS)){
if(operation.get(ModelDescriptionConstants.RESOLVE_EXPRESSIONS).asBoolean(false) && !resolvable){
throw ControllerLogger.ROOT_LOGGER.unableToResolveExpressions();
}
}
}
};
private final OperationStepHandler overrideHandler;
private final boolean resolvable;
public ReadResourceHandler() {
this(null, null, false, false);
}
public ReadResourceHandler(boolean resolvable){
this(null,null,resolvable, false);
}
ReadResourceHandler(final FilteredData filteredData, OperationStepHandler overrideHandler, boolean resolvable) {
this(filteredData, overrideHandler, resolvable, true);
}
private ReadResourceHandler(final FilteredData filteredData, OperationStepHandler overrideHandler,
boolean resolvable, boolean ignoreMissingResource) {
super(filteredData, ignoreMissingResource);
this.overrideHandler = overrideHandler;
this.resolvable = resolvable;
}
@Override
void doExecute(OperationContext context, ModelNode operation, FilteredData filteredData, boolean ignoreMissingResource) throws OperationFailedException {
if (filteredData == null) {
doExecuteInternal(context, operation, ignoreMissingResource);
} else {
try {
if (overrideHandler == null) {
doExecuteInternal(context, operation, ignoreMissingResource);
} else {
overrideHandler.execute(context, operation);
}
} catch (ResourceNotAddressableException rnae) {
// Just report the failure to the filter and complete normally
reportInaccesible(context, operation, filteredData);
ControllerLogger.MGMT_OP_LOGGER.tracef("Caught ResourceNotAddressableException in %s", this);
} catch (Resource.NoSuchResourceException nsre) {
// It's possible this is a remote failure, in which case we
// don't get ResourceNotAddressableException. So see if
// it was due to any authorization denial
AuthorizationResult.Decision decision = context.authorize(operation, EnumSet.of(Action.ActionEffect.ADDRESS)).getDecision();
ControllerLogger.MGMT_OP_LOGGER.tracef("Caught NoSuchResourceException in %s. Authorization decision is %s", this ,decision);
if (decision == AuthorizationResult.Decision.DENY) {
// Just report the failure to the filter and complete normally
reportInaccesible(context, operation, filteredData);
} else if (!ignoreMissingResource) {
throw nsre;
}
} catch (UnauthorizedException ue) {
// Just report the failure to the filter and complete normally
PathAddress pa = PathAddress.pathAddress(operation.get(OP_ADDR));
filteredData.addReadRestrictedResource(pa);
context.getResult().set(new ModelNode());
ControllerLogger.MGMT_OP_LOGGER.tracef("Caught UnauthorizedException in %s", this);
}
}
}
private void reportInaccesible(OperationContext context, ModelNode operation, FilteredData filteredData) {
PathAddress pa = PathAddress.pathAddress(operation.get(OP_ADDR));
filteredData.addAccessRestrictedResource(pa);
context.getResult().set(new ModelNode());
}
private void doExecuteInternal(OperationContext context, ModelNode operation, boolean ignoreMissingResource) throws OperationFailedException {
validator.validate(operation);
final String opName = operation.require(OP).asString();
final PathAddress address = context.getCurrentAddress();
// WFCORE-76
final boolean recursive = GlobalOperationHandlers.getRecursive(context, operation);
final boolean queryRuntime = operation.get(ModelDescriptionConstants.INCLUDE_RUNTIME).asBoolean(false);
final boolean proxies = operation.get(ModelDescriptionConstants.PROXIES).asBoolean(false);
final boolean aliases = operation.get(ModelDescriptionConstants.INCLUDE_ALIASES).asBoolean(false);
final boolean defaults = operation.get(ModelDescriptionConstants.INCLUDE_DEFAULTS).asBoolean(true);
final boolean attributesOnly = operation.get(ModelDescriptionConstants.ATTRIBUTES_ONLY).asBoolean(false);
final boolean resolve = RESOLVE.resolveModelAttribute(context, operation).asBoolean();
// Child types with no actual children
final Set<String> nonExistentChildTypes = new HashSet<String>();
// Children names read directly from the model where we didn't call read-resource to gather data
// We wouldn't call read-resource if the recursive=false
final Map<String, ModelNode> directChildren = new HashMap<String, ModelNode>();
// Attributes of AccessType.METRIC
final Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> metrics = queryRuntime
? new HashMap<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse>()
: Collections.<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse>emptyMap();
// Non-AccessType.METRIC attributes
final Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> otherAttributes = new HashMap<>();
// Child resources recursively read
final Map<PathElement, ModelNode> childResources = recursive ? new LinkedHashMap<PathElement, ModelNode>() : Collections.<PathElement, ModelNode>emptyMap();
// If we were not configured with a FilteredData, we are handling the top
// resource being read, otherwise we are a child resource
FilteredData fd = getFilteredData();
final FilteredData localFilteredData = fd == null ? new FilteredData(address) : fd;
// 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, as that is the way adding a Stage.IMMEDIATE step works
// Last to execute is the handler that assembles the overall response from the pieces created by all the other steps
final ReadResourceAssemblyHandler assemblyHandler = new ReadResourceAssemblyHandler(address, metrics,
otherAttributes, directChildren, childResources, nonExistentChildTypes, localFilteredData, ignoreMissingResource);
context.addStep(assemblyHandler, queryRuntime ? OperationContext.Stage.VERIFY : OperationContext.Stage.MODEL, true);
final ImmutableManagementResourceRegistration registry = context.getResourceRegistration();
// Get the model for this resource.
final Resource resource = nullSafeReadResource(context, registry);
final Map<String, Set<String>> childrenByType = registry != null ? GlobalOperationHandlers.getChildAddresses(context, address, registry, resource, null) : Collections.<String, Set<String>>emptyMap();
if (!attributesOnly) {
// Next, process child resources
for (Map.Entry<String, Set<String>> entry : childrenByType.entrySet()) {
String childType = entry.getKey();
// child type has no children until we add one
nonExistentChildTypes.add(childType);
if(!aliases && (entry.getValue() == null || entry.getValue().isEmpty())) {
if(isGlobalAlias(registry, childType)) {
nonExistentChildTypes.remove(childType);
}
}
for (String child : entry.getValue()) {
PathElement childPE = PathElement.pathElement(childType, child);
PathAddress absoluteChildAddr = address.append(childPE);
ModelNode rrOp = Util.createEmptyOperation(READ_RESOURCE_OPERATION, absoluteChildAddr);
PathAddress relativeAddr = PathAddress.pathAddress(childPE);
if (recursive) {
ImmutableManagementResourceRegistration childReg = registry.getSubModel(relativeAddr);
if (childReg == null) {
throw new OperationFailedException(ControllerLogger.ROOT_LOGGER.noChildRegistry(childType, child));
}
// Decide if we want to invoke on this child resource
boolean proxy = childReg.isRemote();
boolean runtimeResource = childReg.isRuntimeOnly();
boolean getChild = !runtimeResource || (queryRuntime && !proxy) || (proxies && proxy);
if (!aliases && childReg.isAlias()) {
nonExistentChildTypes.remove(childType);
getChild = false;
}
if (getChild) {
nonExistentChildTypes.remove(childType);
// WFCORE-76
GlobalOperationHandlers.setNextRecursive(context, operation, rrOp);
rrOp.get(ModelDescriptionConstants.PROXIES).set(proxies);
rrOp.get(ModelDescriptionConstants.INCLUDE_RUNTIME).set(queryRuntime);
rrOp.get(ModelDescriptionConstants.INCLUDE_ALIASES).set(aliases);
rrOp.get(ModelDescriptionConstants.INCLUDE_DEFAULTS).set(defaults);
ModelNode rrRsp = new ModelNode();
childResources.put(childPE, rrRsp);
// See if there was an override registered for the standard :read-resource handling (unlikely!!!)
OperationStepHandler overrideHandler = childReg.getOperationHandler(PathAddress.EMPTY_ADDRESS, opName);
if (overrideHandler != null && overrideHandler.getClass() == getClass()) {
// not an override
overrideHandler = null;
}
OperationStepHandler rrHandler = new ReadResourceHandler(localFilteredData, overrideHandler, resolvable);
context.addStep(rrRsp, rrOp, rrHandler, OperationContext.Stage.MODEL, true);
}
} else {
// Non-recursive. Just output the names of the children
// But filter inaccessible children
AuthorizationResult ar = context.authorize(rrOp, EnumSet.of(Action.ActionEffect.ADDRESS));
if (ar.getDecision() == AuthorizationResult.Decision.DENY) {
localFilteredData.addAccessRestrictedResource(absoluteChildAddr);
} else {
ModelNode childMap = directChildren.get(childType);
if (childMap == null) {
nonExistentChildTypes.remove(childType);
childMap = new ModelNode();
childMap.setEmptyObject();
directChildren.put(childType, childMap);
}
// Add a "child" => undefined
childMap.get(child);
}
}
}
}
}
// Handle registered attributes
final Set<String> attributeNames = registry != null ? registry.getAttributeNames(PathAddress.EMPTY_ADDRESS) : Collections.<String>emptySet();
for (final String attributeName : attributeNames) {
final AttributeAccess access = registry.getAttributeAccess(PathAddress.EMPTY_ADDRESS, attributeName);
if ((aliases || !access.getFlags().contains(AttributeAccess.Flag.ALIAS))
&& (queryRuntime || access.getStorageType() == AttributeAccess.Storage.CONFIGURATION)) {
Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> responseMap = access.getAccessType() == AttributeAccess.AccessType.METRIC ? metrics : otherAttributes;
AttributeDefinition ad = access.getAttributeDefinition();
AttributeDefinition.NameAndGroup nag = ad == null ? new AttributeDefinition.NameAndGroup(attributeName) : new AttributeDefinition.NameAndGroup(ad);
addReadAttributeStep(context, address, defaults, resolve, localFilteredData, registry, nag, responseMap);
}
}
// Any attributes stored in the model but without a registry entry
final ModelNode model = resource.getModel();
if (model.isDefined()) {
for (String key : model.keys()) {
AttributeDefinition.NameAndGroup nag = new AttributeDefinition.NameAndGroup(key);
// Skip children and attributes already handled
if (!otherAttributes.containsKey(nag) && !childrenByType.containsKey(key) && !metrics.containsKey(nag)) {
addReadAttributeStep(context, address, defaults, resolve, localFilteredData, registry, nag, otherAttributes);
}
}
}
// Last, if defaults are desired, look for unregistered attributes also not in the model
// by checking the resource description
if (defaults) {
//get the model description
final DescriptionProvider descriptionProvider = registry.getModelDescription(PathAddress.EMPTY_ADDRESS);
final Locale locale = GlobalOperationHandlers.getLocale(context, operation);
final ModelNode nodeDescription = descriptionProvider.getModelDescription(locale);
if (nodeDescription.isDefined() && nodeDescription.hasDefined(ATTRIBUTES)) {
for (String key : nodeDescription.get(ATTRIBUTES).keys()) {
AttributeDefinition.NameAndGroup nag = new AttributeDefinition.NameAndGroup(key);
if ((!childrenByType.containsKey(key)) &&
!otherAttributes.containsKey(nag) &&
!metrics.containsKey(nag) &&
nodeDescription.get(ATTRIBUTES).hasDefined(key) &&
nodeDescription.get(ATTRIBUTES, key).hasDefined(DEFAULT)) {
addReadAttributeStep(context, address, defaults, resolve, localFilteredData, registry, nag, otherAttributes);
}
}
}
}
}
private boolean isSingletonResource(final ImmutableManagementResourceRegistration registry, final String key) {
return registry.getSubModel(PathAddress.pathAddress(PathElement.pathElement(key))) == null;
}
private boolean isGlobalAlias(final ImmutableManagementResourceRegistration registry, final String childName) {
if(isSingletonResource(registry, childName)) {
Set<PathElement> childrenPath = registry.getChildAddresses(PathAddress.EMPTY_ADDRESS);
boolean found = false;
boolean alias = true;
for(PathElement childPath : childrenPath) {
if(childPath.getKey().equals(childName)) {
found = true;
ImmutableManagementResourceRegistration squatterRegistration = registry.getSubModel(PathAddress.pathAddress(childPath));
alias = alias && (squatterRegistration != null && squatterRegistration.isAlias());
}
}
return (found && alias);
}
return registry.getSubModel(PathAddress.pathAddress(PathElement.pathElement(childName))).isAlias();
}
private void addReadAttributeStep(OperationContext context, PathAddress address, boolean defaults, boolean resolve, FilteredData localFilteredData,
ImmutableManagementResourceRegistration registry,
AttributeDefinition.NameAndGroup attributeKey, Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> responseMap) {
// See if there was an override registered for the standard :read-attribute handling (unlikely!!!)
OperationStepHandler overrideHandler = registry.getOperationHandler(PathAddress.EMPTY_ADDRESS, READ_ATTRIBUTE_OPERATION);
if (overrideHandler != null &&
(overrideHandler == ReadAttributeHandler.INSTANCE || overrideHandler == ReadAttributeHandler.RESOLVE_INSTANCE)) {
// not an override
overrideHandler = null;
}
OperationStepHandler readAttributeHandler = new ReadAttributeHandler(localFilteredData, overrideHandler, (resolve && resolvable));
final ModelNode attributeOperation = Util.getReadAttributeOperation(address, attributeKey.getName());
attributeOperation.get(ModelDescriptionConstants.INCLUDE_DEFAULTS).set(defaults);
attributeOperation.get(ModelDescriptionConstants.RESOLVE_EXPRESSIONS).set(resolve);
final ModelNode attrResponse = new ModelNode();
GlobalOperationHandlers.AvailableResponse availableResponse = new GlobalOperationHandlers.AvailableResponse(attrResponse);
responseMap.put(attributeKey, availableResponse);
GlobalOperationHandlers.AvailableResponseWrapper wrapper = new GlobalOperationHandlers.AvailableResponseWrapper(readAttributeHandler, availableResponse);
context.addStep(attrResponse, attributeOperation, wrapper, OperationContext.Stage.MODEL, true);
}
/**
* Provides a resource for the current step, either from the context, if the context doesn't have one
* and {@code registry} is runtime-only, it creates a dummy resource.
*/
private static Resource nullSafeReadResource(final OperationContext context, final ImmutableManagementResourceRegistration registry) {
Resource result;
if (registry != null && registry.isRemote()) {
try {
// BES 2015/02/12 (WFCORE-539) -- following comment and use of 'true' I can't understand,
// as the only use of 'resource' is to get the model or the children names,
// neither of which needs the cloning behavior in OperationContextImpl.readResourceFromRoot
//TODO check that having changed this from false to true does not break anything
//If it does, consider adding a Resource.alwaysClone() method that can be used in
//OperationContextImpl.readResourceFromRoot(final PathAddress address, final boolean recursive)
//instead of the recursive check
//result = context.readResource(PathAddress.EMPTY_ADDRESS, true);
// BES 2015/02/12 -- So, back to 'false'
result = context.readResource(PathAddress.EMPTY_ADDRESS, false);
} catch (RuntimeException e) {
result = PlaceholderResource.INSTANCE;
}
} else {
// BES 2015/02/12 (WFCORE-539) -- following comment and use of 'true' I can't understand,
// as the only use of 'resource' is to get the model or the children names,
// neither of which needs the cloning behavior in OperationContextImpl.readResourceFromRoot
//TODO check that having changed this from false to true does not break anything
//If it does, consider adding a Resource.alwaysClone() method that can be used in
//OperationContextImpl.readResourceFromRoot(final PathAddress address, final boolean recursive)
//instead of the recursive check
// BES 2015/02/12 -- So, back to 'false'
result = context.readResource(PathAddress.EMPTY_ADDRESS, false);
}
return result;
}
/**
* Assembles the response to a read-resource request from the components gathered by earlier steps.
*/
private static class ReadResourceAssemblyHandler implements OperationStepHandler {
private final PathAddress address;
private final Map<String, ModelNode> directChildren;
private final Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> metrics;
private final Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> otherAttributes;
private final Map<PathElement, ModelNode> childResources;
private final Set<String> nonExistentChildTypes;
private final FilteredData filteredData;
private final boolean ignoreMissingResource;
/**
* Creates a ReadResourceAssemblyHandler that will assemble the response using the contents
* of the given maps.
* @param address address of the resource
* @param metrics map of attributes of AccessType.METRIC. Keys are the attribute names, values are the full
* read-attribute response from invoking the attribute's read handler. Will not be {@code null}
* @param otherAttributes map of attributes not of AccessType.METRIC that have a read handler registered. Keys
* are the attribute names, values are the full read-attribute response from invoking the
* attribute's read handler. Will not be {@code null}
* @param directChildren Children names read directly from the parent resource where we didn't call read-resource
* to gather data. We wouldn't call read-resource if the recursive=false
* @param childResources read-resource response from child resources, where the key is the PathAddress
* 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 nonExistentChildTypes names of child types where no data is available
* @param filteredData information about resources and attributes that were filtered
* @param ignoreMissingResource {@code true} if we should ignore occasions when the targeted resource
* does not exist; {@code false} if we should throw
* {@link org.jboss.as.controller.registry.Resource.NoSuchResourceException}
* in such cases
*/
private ReadResourceAssemblyHandler(final PathAddress address,
final Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> metrics,
final Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> otherAttributes, final Map<String, ModelNode> directChildren,
final Map<PathElement, ModelNode> childResources, final Set<String> nonExistentChildTypes,
FilteredData filteredData, boolean ignoreMissingResource) {
this.address = address;
this.metrics = metrics;
this.otherAttributes = otherAttributes;
this.directChildren = directChildren;
this.childResources = childResources;
this.nonExistentChildTypes = nonExistentChildTypes;
this.filteredData = filteredData;
this.ignoreMissingResource = ignoreMissingResource;
}
@Override
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
Map<AttributeDefinition.NameAndGroup, ModelNode> sortedAttributes = new TreeMap<>();
Map<String, ModelNode> sortedChildren = new TreeMap<String, ModelNode>();
boolean failed = false;
for (Map.Entry<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> entry : otherAttributes.entrySet()) {
GlobalOperationHandlers.AvailableResponse ar = entry.getValue();
if (ar.unavailable) {
// Our target resource has disappeared
handleMissingResource(context);
return;
}
ModelNode value = ar.response;
if (!value.has(FAILURE_DESCRIPTION)) {
sortedAttributes.put(entry.getKey(), value.get(RESULT));
} else if (value.hasDefined(FAILURE_DESCRIPTION)) {
context.getFailureDescription().set(value.get(FAILURE_DESCRIPTION));
failed = true;
break;
}
}
if (!failed) {
for (Map.Entry<PathElement, ModelNode> entry : childResources.entrySet()) {
PathElement path = entry.getKey();
ModelNode value = entry.getValue();
if (!value.has(FAILURE_DESCRIPTION)) {
if (value.hasDefined(RESULT)) {
ModelNode childTypeNode = sortedChildren.get(path.getKey());
if (childTypeNode == null) {
childTypeNode = new ModelNode();
sortedChildren.put(path.getKey(), childTypeNode);
}
childTypeNode.get(path.getValue()).set(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
try {
context.readResourceFromRoot(address, false);
} catch (Resource.NoSuchResourceException e) {
handleMissingResource(context);
return;
}
} // 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) {
for (Map.Entry<String, ModelNode> directChild : directChildren.entrySet()) {
sortedChildren.put(directChild.getKey(), directChild.getValue());
}
for (String nonExistentChildType : nonExistentChildTypes) {
sortedChildren.put(nonExistentChildType, new ModelNode());
}
for (Map.Entry<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> metric : metrics.entrySet()) {
GlobalOperationHandlers.AvailableResponse ar = metric.getValue();
if (ar.unavailable) {
// Our target resource has disappeared
handleMissingResource(context);
return;
}
ModelNode value = ar.response;
if (!value.has(FAILURE_DESCRIPTION)) {
sortedAttributes.put(metric.getKey(), value.get(RESULT));
}
// we ignore metric failures
// TODO how to prevent the metric failure screwing up the overall context?
}
final ModelNode result = context.getResult();
result.setEmptyObject();
for (Map.Entry<AttributeDefinition.NameAndGroup, ModelNode> entry : sortedAttributes.entrySet()) {
result.get(entry.getKey().getName()).set(entry.getValue());
}
for (Map.Entry<String, ModelNode> entry : sortedChildren.entrySet()) {
if (!entry.getValue().isDefined()) {
result.get(entry.getKey()).set(entry.getValue());
} else {
ModelNode childTypeNode = new ModelNode();
for (Property property : entry.getValue().asPropertyList()) {
PathElement pe = PathElement.pathElement(entry.getKey(), property.getName());
if (!filteredData.isFilteredResource(address, pe)) {
childTypeNode.get(property.getName()).set(property.getValue());
}
}
result.get(entry.getKey()).set(childTypeNode);
}
}
if (filteredData.hasFilteredData()) {
context.getResponseHeaders().get(ACCESS_CONTROL).set(filteredData.toModelNode());
}
}
}
private void handleMissingResource(OperationContext context) {
// Our target resource has disappeared
if (context.hasResult()) {
context.getResult().set(new ModelNode());
}
if (!ignoreMissingResource) {
throw ControllerLogger.MGMT_OP_LOGGER.managementResourceNotFound(address);
}
}
}
}