/*
* Copyright (C) 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 library 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 library 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 library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
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.READ_ATTRIBUTE_GROUP_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_ATTRIBUTE_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.NAME;
import java.util.Collections;
import java.util.HashMap;
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.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleOperationDefinitionBuilder;
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.global.GlobalOperationHandlers.AbstractMultiTargetHandler;
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.dmr.ModelNode;
import org.jboss.dmr.ModelType;
/**
* {@link org.jboss.as.controller.OperationStepHandler} returning the attributes of a resource for a given attribute-group.
*
* @author <a href="mailto:ehugonne@redhat.com">Emmanuel Hugonnet</a> (c) 2015 Red Hat, inc.
*/
public class ReadAttributeGroupHandler extends AbstractMultiTargetHandler {
static final OperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(READ_ATTRIBUTE_GROUP_OPERATION, ControllerResolver.getResolver("global"))
.setParameters(NAME, INCLUDE_RUNTIME, INCLUDE_DEFAULTS, INCLUDE_ALIASES)
.setReadOnly()
.setRuntimeOnly()
.setReplyType(ModelType.LIST)
.setReplyValueType(ModelType.PROPERTY)
.build();
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_ATTRIBUTE_GROUP_OPERATION, ControllerResolver.getResolver("global"))
.setParameters(NAME, RESOLVE, INCLUDE_RUNTIME, INCLUDE_DEFAULTS, INCLUDE_ALIASES)
.setReadOnly()
.setRuntimeOnly()
.setReplyType(ModelType.LIST)
.setReplyValueType(ModelType.PROPERTY)
.build();
static OperationStepHandler INSTANCE = new ReadAttributeGroupHandler();
public static OperationStepHandler RESOLVE_INSTANCE = new ReadAttributeGroupHandler(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.RESOLVE_EXPRESSIONS)){
if(operation.get(ModelDescriptionConstants.RESOLVE_EXPRESSIONS).asBoolean(false) && !resolvable){
throw ControllerLogger.ROOT_LOGGER.unableToResolveExpressions();
}
}
}
};
private final boolean resolvable;
public ReadAttributeGroupHandler() {
this(false);
}
public ReadAttributeGroupHandler(boolean resolvable) {
this.resolvable = resolvable;
}
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);
}
@Override
void doExecute(OperationContext context, ModelNode operation, FilteredData filteredData, boolean ignoreMissingResource) throws OperationFailedException {
validator.validate(operation);
final PathAddress address = context.getCurrentAddress();
final boolean includeRutime = operation.get(ModelDescriptionConstants.INCLUDE_RUNTIME).asBoolean(false);
final boolean aliases = operation.get(ModelDescriptionConstants.INCLUDE_ALIASES).asBoolean(false);
final boolean defaults = operation.get(ModelDescriptionConstants.INCLUDE_DEFAULTS).asBoolean(true);
final boolean resolve = RESOLVE.resolveModelAttribute(context, operation).asBoolean();
final ModelNode groupNameNode = NAME.resolveModelAttribute(context, operation);
final String groupName = groupNameNode.isDefined() ? groupNameNode.asString() : null;
final FilteredData localFilteredData = new FilteredData(address);
final Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> metrics = includeRutime
? new HashMap<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse>()
: Collections.<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse>emptyMap();
final Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> otherAttributes = new HashMap<>();
final ReadAttributeGroupAssemblyHandler assemblyHandler = new ReadAttributeGroupAssemblyHandler(metrics, otherAttributes, filteredData, ignoreMissingResource);
context.addStep(assemblyHandler, includeRutime ? OperationContext.Stage.VERIFY : OperationContext.Stage.MODEL, true);
final ImmutableManagementResourceRegistration registry = context.getResourceRegistration();
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);
final AttributeDefinition ad = access.getAttributeDefinition();
if ((aliases || !access.getFlags().contains(AttributeAccess.Flag.ALIAS))
&& (includeRutime || access.getStorageType() == AttributeAccess.Storage.CONFIGURATION)
&& (groupName == null || groupName.equals(ad.getAttributeGroup()))) {
Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> responseMap = access.getAccessType() == AttributeAccess.AccessType.METRIC ? metrics : otherAttributes;
AttributeDefinition.NameAndGroup nag = ad == null ? new AttributeDefinition.NameAndGroup(attributeName) : new AttributeDefinition.NameAndGroup(ad);
addReadAttributeStep(context, address, defaults, resolve, localFilteredData, registry, nag, responseMap);
}
}
}
/**
* Assembles the response to a read-attribute request from the components gathered by earlier steps.
*/
private static class ReadAttributeGroupAssemblyHandler implements OperationStepHandler {
private final Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> metrics;
private final Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> otherAttributes;
private final FilteredData filteredData;
private final boolean ignoreMissingResource;
/**
* Creates a ReadAttributeGroupAssemblyHandler that will assemble the response using the contents of the given
* maps.
* @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 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 ReadAttributeGroupAssemblyHandler(final Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> metrics,
final Map<AttributeDefinition.NameAndGroup, GlobalOperationHandlers.AvailableResponse> otherAttributes,
final FilteredData filteredData, final boolean ignoreMissingResource) {
this.metrics = metrics;
this.otherAttributes = otherAttributes;
this.filteredData = filteredData;
this.ignoreMissingResource = ignoreMissingResource;
}
@Override
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
Map<AttributeDefinition.NameAndGroup, ModelNode> sortedAttributes = new TreeMap<>();
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<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));
}
}
final ModelNode result = context.getResult();
result.setEmptyObject();
for (Map.Entry<AttributeDefinition.NameAndGroup, ModelNode> entry : sortedAttributes.entrySet()) {
result.get(entry.getKey().getName()).set(entry.getValue());
}
if (filteredData!= null && 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(context.getCurrentAddress());
}
}
}
}