/*
* Copyright (C) 2014 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;
import java.util.Set;
import org.jboss.as.controller.access.Action;
import org.jboss.as.controller.access.AuthorizationResult;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILED_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILED_SERVICES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILURE_DESCRIPTION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MISSING_TRANSITIVE_DEPENDENCY_PROBLEMS;
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.POSSIBLE_CAUSES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_RESOURCE_OPERATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVICES_MISSING_DEPENDENCIES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SERVICES_MISSING_TRANSITIVE_DEPENDENCIES;
import java.util.HashSet;
import org.jboss.as.controller.access.management.AuthorizedAddress;
import org.jboss.as.controller.descriptions.common.ControllerResolver;
import org.jboss.as.controller.registry.OperationEntry;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
/**
* Collects boot errors.
* @author <a href="mailto:ehugonne@redhat.com">Emmanuel Hugonnet</a> (c) 2014 Red Hat, inc.
*/
public class BootErrorCollector {
private static final String COMPLETE_OP = "real-operation";
private final ModelNode errors;
private final OperationStepHandler listBootErrorsHandler;
public BootErrorCollector() {
errors = new ModelNode();
errors.setEmptyList();
listBootErrorsHandler = new ListBootErrorsHandler(this);
}
void addFailureDescription(final ModelNode operation, final ModelNode failureDescription) {
assert operation != null;
assert failureDescription != null;
ModelNode error = new ModelNode();
// for security reasons failure.get(FAILED).set(operation.clone());
ModelNode failedOperation = error.get(FAILED_OPERATION);
failedOperation.get(OP).set(operation.get(OP));
ModelNode opAddr = operation.get(OP_ADDR);
if (!opAddr.isDefined()) {
opAddr.setEmptyList();
}
failedOperation.get(OP_ADDR).set(opAddr);
error.get(FAILURE_DESCRIPTION).set(failureDescription.asString());
ModelNode report = ServiceVerificationHelper.extractFailedServicesDescription(failureDescription);
if (report != null) {
error.get(FAILED_SERVICES).set(report);
}
report = ServiceVerificationHelper.extractMissingServicesDescription(failureDescription);
if (report != null) {
error.get(SERVICES_MISSING_DEPENDENCIES).set(report);
}
report = ServiceVerificationHelper.extractTransitiveDependencyProblemDescription(failureDescription);
if (report != null) {
error.get(MISSING_TRANSITIVE_DEPENDENCY_PROBLEMS).set(report);
}
error.get(COMPLETE_OP).set(operation);
synchronized (errors) {
errors.add(error);
}
}
private ModelNode getErrors() {
synchronized (errors) {
return errors.clone();
}
}
public OperationStepHandler getReadBootErrorsHandler() {
return this.listBootErrorsHandler;
}
public static class ListBootErrorsHandler implements OperationStepHandler {
private static final String OPERATION_NAME = "read-boot-errors";
private final BootErrorCollector errors;
private static final AttributeDefinition OP_DEFINITION = ObjectTypeAttributeDefinition.Builder.of(FAILED_OPERATION,
SimpleAttributeDefinitionBuilder.create(OP, ModelType.STRING, false).build(),
SimpleListAttributeDefinition.Builder.of(OP_ADDR,
SimpleAttributeDefinitionBuilder.create("element", ModelType.PROPERTY, false).build())
.build())
.setAllowNull(false)
.build();
private static final AttributeDefinition FAILURE_MESSAGE = SimpleAttributeDefinitionBuilder.create(FAILURE_DESCRIPTION, ModelType.STRING, false).build();
private static final AttributeDefinition FAILED_SVC_AD = SimpleListAttributeDefinition.Builder.of(FAILED_SERVICES,
SimpleAttributeDefinitionBuilder.create("element", ModelType.STRING, false).build())
.setAllowNull(true)
.build();
private static final AttributeDefinition MISSING_DEPS_AD = SimpleListAttributeDefinition.Builder.of(SERVICES_MISSING_DEPENDENCIES,
SimpleAttributeDefinitionBuilder.create("element", ModelType.STRING, false).build())
.setAllowNull(true)
.build();
private static final AttributeDefinition AFFECTED_AD = SimpleListAttributeDefinition.Builder.of(SERVICES_MISSING_TRANSITIVE_DEPENDENCIES,
SimpleAttributeDefinitionBuilder.create("element", ModelType.STRING, false).build())
.build();
private static final AttributeDefinition CAUSE_AD = SimpleListAttributeDefinition.Builder.of(POSSIBLE_CAUSES,
SimpleAttributeDefinitionBuilder.create("element", ModelType.STRING, false).build())
.build();
private static final AttributeDefinition TRANSITIVE_AD = ObjectTypeAttributeDefinition.Builder.of(MISSING_TRANSITIVE_DEPENDENCY_PROBLEMS,
AFFECTED_AD, CAUSE_AD)
.setAllowNull(true)
.build();
public static final SimpleOperationDefinition DEFINITION = new SimpleOperationDefinitionBuilder(OPERATION_NAME,
ControllerResolver.getResolver("errors"))
.setReadOnly()
.setRuntimeOnly()
.setReplyType(ModelType.LIST)
.setReplyParameters(OP_DEFINITION, FAILURE_MESSAGE, FAILED_SVC_AD, MISSING_DEPS_AD, TRANSITIVE_AD).build();
ListBootErrorsHandler(final BootErrorCollector errors) {
this.errors = errors;
}
@Override
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
context.addStep(new OperationStepHandler() {
@Override
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
ModelNode bootErrors = new ModelNode().setEmptyList();
ModelNode errorsNode = errors.getErrors();
for (ModelNode bootError : errorsNode.asList()) {
secureOperationAddress(context, bootError);
bootErrors.add(bootError);
}
context.getResult().set(bootErrors);
}
}, OperationContext.Stage.RUNTIME);
}
private void secureOperationAddress(OperationContext context, ModelNode bootError) throws OperationFailedException {
if (bootError.hasDefined(FAILED_OPERATION)) {
ModelNode failedOperation = bootError.get(FAILED_OPERATION);
ModelNode address = failedOperation.get(OP_ADDR);
ModelNode fakeOperation = new ModelNode();
fakeOperation.get(OP).set(READ_RESOURCE_OPERATION);
fakeOperation.get(OP_ADDR).set(address);
AuthorizedAddress authorizedAddress = AuthorizedAddress.authorizeAddress(context, fakeOperation);
if(authorizedAddress.isElided()) {
failedOperation.get(OP_ADDR).set(authorizedAddress.getAddress());
}
if(bootError.has(FAILURE_DESCRIPTION) && !canReadFailureDescription(context, bootError)) {
bootError.get(FAILURE_DESCRIPTION).set(new ModelNode());
}
}
bootError.remove(COMPLETE_OP);
}
private boolean canReadFailureDescription(OperationContext context, ModelNode bootError) {
ModelNode completeOPeration = bootError.get(COMPLETE_OP);
OperationEntry operationEntry = context.getRootResourceRegistration().getOperationEntry(
PathAddress.pathAddress(completeOPeration.get(OP_ADDR)), completeOPeration.get(OP).asString());
Set<Action.ActionEffect> effects = getEffects(operationEntry);
return context.authorize(bootError.get(COMPLETE_OP), effects).getDecision() == AuthorizationResult.Decision.PERMIT;
}
Set<Action.ActionEffect> getEffects(OperationEntry operationEntry) {
Set<Action.ActionEffect> effects = new HashSet<>(5);
effects.add(Action.ActionEffect.ADDRESS);
if(operationEntry != null) {
effects.add(Action.ActionEffect.READ_RUNTIME);
if (!operationEntry.getFlags().contains(OperationEntry.Flag.RUNTIME_ONLY)) {
effects.add(Action.ActionEffect.READ_CONFIG);
}
if(!operationEntry.getFlags().contains(OperationEntry.Flag.READ_ONLY)) {
effects.add(Action.ActionEffect.WRITE_RUNTIME);
if(!operationEntry.getFlags().contains(OperationEntry.Flag.RUNTIME_ONLY)) {
effects.add(Action.ActionEffect.WRITE_CONFIG);
}
}
}
return effects;
}
}
}