/* * 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; } } }