/* * 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.transform; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.UNDEFINE_ATTRIBUTE_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.VALUE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.WRITE_ATTRIBUTE_OPERATION; import static org.jboss.as.controller.transform.OperationResultTransformer.ORIGINAL_RESULT; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import org.jboss.as.controller.AttributeDefinition; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.registry.Resource; import org.jboss.dmr.ModelNode; /** * Discards attributes silently. This class should ONLY be used if you are 100% sure a new attribute can be discarded, even if set. * Provide a {@link DiscardApprover} to the constructor to provide specific logic for making the determination as to * whether the transformation should be done. It is made abstract to make you think about using it. * <p> * Normally, you would want to use {@link DiscardUndefinedAttributesTransformer} instead. * </p> * <p> * A typical use case for this transformer would be in combination with {@link DiscardUndefinedAttributesTransformer}. * First this transformer would run, with a {@link DiscardApprover} checking the state of the model or operation to * decide whether removing attributes is valid. The discard approver would only approve the removal if the value of * the model or operation parameters is such that the servers launched by a slave Host Controller running the legacy * version and unaware of the removed attributes would function consistently with newer version servers who saw the * attributes. This transformer would remove the attributes in that case, and leave them otherwise. Then the * {@link DiscardUndefinedAttributesTransformer} would run and would log a warning or fail operations if any of * the attributes were left. So this transformer cleans if possible, and {@link DiscardUndefinedAttributesTransformer} * deals with any problems left after cleaning. * </p> * * @author <a href="kabir.khan@jboss.com">Kabir Khan</a> */ public abstract class DiscardAttributesTransformer implements OperationTransformer, ResourceTransformer { /** * Approves the transformation of the resource or operation. */ public interface DiscardApprover { /** * Replies with whether resource transformation should be done. * * @param context the context * @param address the address of the resource to transform * @param resource the resource * @return {@code true} if transformation should be done; {@code false} if the resource should be left as is */ boolean isResourceDiscardAllowed(TransformationContext context, PathAddress address, Resource resource); /** * Replies with whether operation transformation should be done. * * @param context the context * @param address the address of the resource to transform * @param operation the operation * @return {@code true} if transformation should be done; {@code false} if the operation should be left as is */ boolean isOperationDiscardAllowed(TransformationContext context, PathAddress address, ModelNode operation); } /** * A {@code DiscardApprover} that always returns {@code true}; <strong>use with extreme caution.</strong> It would * be a very unusual situation for it to be appropriate to always transform a resource or operation regardless * of the values in the model or operation. */ public static final DiscardApprover LENIENT_DISCARD_APPROVER = new DiscardApprover() { @Override public boolean isResourceDiscardAllowed(TransformationContext context, PathAddress address, Resource resource) { return true; } @Override public boolean isOperationDiscardAllowed(TransformationContext context, PathAddress address, ModelNode operation) { return true; } }; /** * A {@code DiscardApprover} that checks the value of a provided attribute in order to decide whether transformation * is allowed. If the attribute has the desired value (or is undefined if allowing that is configured) then the * transformation is allowed. * <p> * <strong>This approver can only be used for {@code add}, {@code write-attribute} and {@code undefine-attribute} * operation transformation</strong> as well as resource transformation. * </p> */ public static class AttributeValueDiscardApprover implements DiscardApprover { private final String attributeName; private final ModelNode approvedValue; private final boolean allowUndefined; /** * Creates a new transformer. * * @param attributeName the name of the attribute to check * @param approvedValue the value the attribute must have in order to allow transformation * @param allowUndefined {@code true} if the attribute can also be undefined */ public AttributeValueDiscardApprover(String attributeName, ModelNode approvedValue, boolean allowUndefined) { this.attributeName = attributeName; this.approvedValue = approvedValue; this.allowUndefined = allowUndefined; } @Override public boolean isResourceDiscardAllowed(TransformationContext context, PathAddress address, Resource resource) { return isDiscardAllowed(resource.getModel()); } /** * Checks if the value of the resource after the operation is applied would meet the criteria. Can only be used * for {@code add}, {@code write-attribute} and {@code undefine-attribute} operation transformation. * * {@inheritDoc} * * @throws IllegalStateException if the operation name isn't {@code add}, {@code write-attribute} or {@code undefine-attribute} */ @Override public boolean isOperationDiscardAllowed(TransformationContext context, PathAddress address, ModelNode operation) { String opName = operation.require(OP).asString(); if (ADD.equals(opName)) { return isDiscardAllowed(operation); } else if (WRITE_ATTRIBUTE_OPERATION.equals(opName)) { String attr = operation.require(NAME).asString(); if (attr.equals(attributeName)) { // A desire to change the relevant attribute ModelNode value = operation.hasDefined(VALUE) ? operation.get(VALUE) : new ModelNode(); ModelNode mockModel = new ModelNode(); mockModel.get(attributeName).set(value); return isDiscardAllowed(mockModel); } else { // A desire to change a different attribute. // See if the target attribute has a legal value to permit the change return isDiscardAllowed(context.readResource(PathAddress.EMPTY_ADDRESS).getModel()); } } else if (UNDEFINE_ATTRIBUTE_OPERATION.equals(opName)) { String attr = operation.require(NAME).asString(); if (attr.equals(attributeName)) { // A desire to change the relevant attribute ModelNode mockModel = new ModelNode(); mockModel.get(attributeName); // leave undefined return isDiscardAllowed(mockModel); } else { // A desire to change a different attribute. // See if the target attribute has a legal value to permit the change return isDiscardAllowed(context.readResource(PathAddress.EMPTY_ADDRESS).getModel()); } } // We aren't valid for use with this operation throw new IllegalStateException(); } private boolean isDiscardAllowed(final ModelNode modelNode) { if (modelNode.hasDefined(attributeName)) { return approvedValue.equals(modelNode.get(attributeName)); } return allowUndefined; } } private final DiscardApprover discardApprover; private final Set<String> attributeNames; private final OperationTransformer writeAttributeTransformer = new WriteAttributeTransformer(); private final OperationTransformer undefineAttributeTransformer = writeAttributeTransformer; /** * Creates a new transformer. * * @param attributes the attributes to discard * * @deprecated use a variant that takes a {@link DiscardApprover} */ @Deprecated protected DiscardAttributesTransformer(AttributeDefinition... attributes) { this(LENIENT_DISCARD_APPROVER, namesFromDefinitions(attributes)); } /** * Creates a new transformer. * @param discardApprover approves whether or not transformation should be done. Cannot be {@code null} * @param attributes the attributes to discard */ protected DiscardAttributesTransformer(DiscardApprover discardApprover, AttributeDefinition... attributes) { this(discardApprover, namesFromDefinitions(attributes)); } private static Set<String> namesFromDefinitions(AttributeDefinition... attributes) { final Set<String> names = new HashSet<String>(); for(final AttributeDefinition def : attributes) { names.add(def.getName()); } return names; } /** * Creates a new transformer. * * @param attributeNames the attributes to discard * * @deprecated use a variant that takes a {@link DiscardApprover} */ @Deprecated protected DiscardAttributesTransformer(String... attributeNames) { this(LENIENT_DISCARD_APPROVER, new HashSet<String>(Arrays.asList(attributeNames))); } /** * Creates a new transformer. * @param discardApprover approves whether or not transformation should be done. Cannot be {@code null} * @param attributeNames the attributes to discard */ protected DiscardAttributesTransformer(DiscardApprover discardApprover, String... attributeNames) { this(discardApprover, new HashSet<String>(Arrays.asList(attributeNames))); } /** * Creates a new transformer. * * @param attributeNames the attributes to discard * * @deprecated use a variant that takes a {@link DiscardApprover} */ @Deprecated public DiscardAttributesTransformer(Set<String> attributeNames) { this(LENIENT_DISCARD_APPROVER, attributeNames); } /** * Creates a new transformer. * @param discardApprover approves whether or not transformation should be done. Cannot be {@code null} * @param attributeNames the attributes to discard */ public DiscardAttributesTransformer(DiscardApprover discardApprover, Set<String> attributeNames) { assert discardApprover != null : "discardApprover is null"; assert attributeNames != null : "attributeNames is null"; this.attributeNames = attributeNames; this.discardApprover = discardApprover; } public OperationTransformer getWriteAttributeTransformer() { return writeAttributeTransformer; } public OperationTransformer getUndefineAttributeTransformer() { return undefineAttributeTransformer; } @Override public TransformedOperation transformOperation(final TransformationContext context, final PathAddress address, final ModelNode operation) throws OperationFailedException { boolean allowDiscard = discardApprover.isOperationDiscardAllowed(context, address, operation); final ModelNode transformedOperation = allowDiscard ? transformInternal(operation.clone()) : operation; return new TransformedOperation(transformedOperation, ORIGINAL_RESULT); } @Override public void transformResource(ResourceTransformationContext context, PathAddress address, Resource resource) throws OperationFailedException { if (discardApprover.isResourceDiscardAllowed(context, address, resource)) { transformInternal(resource.getModel()); } } private ModelNode transformInternal(ModelNode model) { for (String attr : attributeNames) { model.remove(attr); } return model; } private class WriteAttributeTransformer implements OperationTransformer { @Override public TransformedOperation transformOperation(TransformationContext context, PathAddress address, ModelNode operation) throws OperationFailedException { if (attributeNames.contains(operation.get(NAME).asString()) && discardApprover.isOperationDiscardAllowed(context, address, operation)) { return OperationTransformer.DISCARD.transformOperation(context, address, operation); } return OperationTransformer.DEFAULT.transformOperation(context, address, operation); } } }