/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, 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;
import static org.jboss.as.controller.logging.ControllerLogger.MGMT_OP_LOGGER;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.jboss.as.controller.capability.RuntimeCapability;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.controller.registry.AttributeAccess;
import org.jboss.as.controller.registry.ImmutableManagementResourceRegistration;
import org.jboss.as.controller.registry.Resource;
import org.jboss.dmr.ModelNode;
/**
* Base class for handlers that remove resources.
*
* @author John Bailey
*/
public abstract class AbstractRemoveStepHandler implements OperationStepHandler {
private static final OperationContext.AttachmentKey<Set<PathAddress>> RECURSION = OperationContext.AttachmentKey.create(Set.class);
private final Set<RuntimeCapability> capabilities;
protected AbstractRemoveStepHandler() {
this(AbstractAddStepHandler.NULL_CAPABILITIES);
}
protected AbstractRemoveStepHandler(RuntimeCapability... capabilities) {
this(capabilities.length == 0 ? AbstractAddStepHandler.NULL_CAPABILITIES : new HashSet<RuntimeCapability>(Arrays.asList(capabilities)));
}
protected AbstractRemoveStepHandler(Set<RuntimeCapability> capabilities) {
this.capabilities = capabilities == null ? AbstractAddStepHandler.NULL_CAPABILITIES : capabilities;
}
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
Resource resource = context.readResource(PathAddress.EMPTY_ADDRESS);
final ModelNode model = Resource.Tools.readModel(resource);
performRemove(context, operation, model);
// Check for the continued presense of the resource. If it's still there, do nothing further.
// This allows performRemove's recursive removal to work by deferring the runtime
// bits until child resources are removed
if (!hasResource(context)) {
recordCapabilitiesAndRequirements(context, operation, resource);
if (requiresRuntime(context)) {
context.addStep(new OperationStepHandler() {
public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
performRuntime(context, operation, model);
context.completeStep(new OperationContext.RollbackHandler() {
@Override
public void handleRollback(OperationContext context, ModelNode operation) {
try {
recoverServices(context, operation, model);
} catch (Exception e) {
MGMT_OP_LOGGER.errorRevertingOperation(e, getClass().getSimpleName(),
operation.require(ModelDescriptionConstants.OP).asString(),
context.getCurrentAddress());
}
}
});
}
}, OperationContext.Stage.RUNTIME);
}
}
}
protected void performRemove(OperationContext context, final ModelNode operation, final ModelNode model) throws OperationFailedException {
final Resource resource = context.readResource(PathAddress.EMPTY_ADDRESS);
Set<PathAddress> removed = context.getAttachment(RECURSION);
if (removed != null && removed.contains(context.getCurrentAddress())) {
// We have already tried to remove children (see below) and are now back.
// So avoid any chance of continual looping and just remove the resource
context.removeResource(PathAddress.EMPTY_ADDRESS);
return;
}
// We're going to add steps to remove any children before we remove the parent
// First, figure out what child steps we need
PathAddress address = context.getCurrentAddress();
final Map<PathAddress, OperationStepHandler> map = new LinkedHashMap<>();
for (String childType : resource.getChildTypes()) {
for (final Resource.ResourceEntry entry : resource.getChildren(childType)) {
PathElement path = entry.getPathElement();
if (!entry.isRuntime() && removeChildRecursively(path)) {
ImmutableManagementResourceRegistration mrr = context.getResourceRegistration().getSubModel(PathAddress.pathAddress(path));
if (!mrr.isRuntimeOnly() && !mrr.isAlias()) {
OperationStepHandler childHandler = mrr.getOperationHandler(PathAddress.EMPTY_ADDRESS, ModelDescriptionConstants.REMOVE);
if (childHandler != null) {
PathAddress childAddress = address.append(path);
map.put(childAddress, childHandler);
}
}
}
}
}
if (map.isEmpty()) {
// No children we need to remove; just do the normal remove
context.removeResource(PathAddress.EMPTY_ADDRESS);
} else {
// We are going to add steps to the front of the current stage step queue to remove
// the children, and then to call ourself again (said call will do the runtime part).
// We put them on the top of the stack so all work associated with this step is done
// before the next currently registered step executes.
// Since these are going to the front of the queue we add them in reverse order of
// intended execution.
// First, call ourself again
context.addStep(this, OperationContext.Stage.MODEL, true);
// Now for each child
// We add the steps in the opposite order of how the resource provides the children
// This may not be necessary, but produces a kind of FIFO behavior if the
// children's order happens to be meaningful. Generally FIFO is a good thing.
for (Map.Entry<PathAddress, OperationStepHandler> entry : map.entrySet()) {
PathAddress child = entry.getKey();
ControllerLogger.MGMT_OP_LOGGER.debugf("Adding remove step for child at %s", child);
context.addStep(Util.createRemoveOperation(child), entry.getValue(), OperationContext.Stage.MODEL, true);
}
// Record that we have been here so when we come back we avoid looping
if (removed == null) {
removed = new HashSet<>();
context.attach(RECURSION, removed);
}
removed.add(context.getCurrentAddress());
}
}
/**
* Record any new {@link org.jboss.as.controller.capability.RuntimeCapability capabilities} that are no longer available as
* a result of this operation, as well as any requirements for other capabilities that no longer exist. This method is
* invoked during {@link org.jboss.as.controller.OperationContext.Stage#MODEL}.
* <p>
* Any changes made by this method will automatically be discarded if the operation rolls back.
* </p>
* <p>
* This default implementation deregisters any capabilities passed to the constructor.
* </p>
*
* @param context the context. Will not be {@code null}
* @param operation the operation that is executing Will not be {@code null}
* @param resource the resource that will be removed. Will <strong>not</strong> reflect any updates made by
* {@link #performRemove(OperationContext, org.jboss.dmr.ModelNode, org.jboss.dmr.ModelNode)} as this method
* is invoked before that method is. Will not be {@code null}
*/
protected void recordCapabilitiesAndRequirements(OperationContext context, ModelNode operation, Resource resource) throws OperationFailedException {
Set<RuntimeCapability> capabilitySet = capabilities.isEmpty() ? context.getResourceRegistration().getCapabilities() : capabilities;
for (RuntimeCapability capability : capabilitySet) {
if (capability.isDynamicallyNamed()) {
context.deregisterCapability(capability.getDynamicName(context.getCurrentAddressValue()));
} else {
context.deregisterCapability(capability.getName());
}
}
ModelNode model = resource.getModel();
ImmutableManagementResourceRegistration mrr = context.getResourceRegistration();
for (String attr : mrr.getAttributeNames(PathAddress.EMPTY_ADDRESS)) {
AttributeAccess aa = mrr.getAttributeAccess(PathAddress.EMPTY_ADDRESS, attr);
if (aa != null) {
AttributeDefinition ad = aa.getAttributeDefinition();
if (ad != null && (model.hasDefined(ad.getName()) || ad.hasCapabilityRequirements())) {
ad.removeCapabilityRequirements(context, model.get(ad.getName()));
}
}
}
}
/**
* Gets whether the remove operation should fail if there are child resources present.
* @return {@code true} if the operation should fail in the presence of child resources
*
* @deprecated never called; this handler now always removes child resources
*/
@Deprecated
protected boolean requireNoChildResources() {
return false;
}
/**
* Gets whether a child resource should be removed via the addition of a step to invoke
* the "remove" operation for its address. If this method returns {@code false}
* then the child resource will simply be discarded along with their parent.
* <p>
* <stong>NOTE:</stong> If a subclass returns {@code false} from this method, it must ensure that it itself
* will make any necessary changes to the capability registry and the service container associated with
* the child resource. Generally, returning {@code false} would only be appropriate for parent resources whose
* children only represent configuration details of the parent, and are not independent entities.
* <p>
* This default implementation returns {@code true}
*
* @param child the path element pointing to the child resource
*
* @return {@code true} if separate steps should be added to remove children; {@code false}
* if any children can simply be discarded along with the parent
*/
protected boolean removeChildRecursively(PathElement child) {
return true;
}
protected void performRuntime(final OperationContext context, final ModelNode operation, final ModelNode model) throws OperationFailedException {
}
protected void recoverServices(final OperationContext context, final ModelNode operation, final ModelNode model) throws OperationFailedException {
}
protected boolean requiresRuntime(OperationContext context) {
return context.isDefaultRequiresRuntime();
}
private static boolean hasResource (OperationContext context) {
try {
context.readResource(PathAddress.EMPTY_ADDRESS, false);
return true;
} catch (Resource.NoSuchResourceException nsre) {
return false;
}
}
}