/* * JBoss, Home of Professional Open Source. * Copyright 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 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.notification; import static org.jboss.as.controller.PathAddress.pathAddress; import static org.jboss.as.controller.PathElement.pathElement; import static org.jboss.as.controller.SimpleAttributeDefinitionBuilder.create; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.READ_ATTRIBUTE_OPERATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.REMOVE; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESOURCE_ADDED_NOTIFICATION; import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RESOURCE_REMOVED_NOTIFICATION; 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.operations.global.GlobalNotifications.NEW_VALUE; import static org.jboss.as.controller.operations.global.GlobalNotifications.OLD_VALUE; import static org.jboss.dmr.ModelType.BOOLEAN; import static org.jboss.dmr.ModelType.LONG; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.util.UUID; import org.jboss.as.controller.AbstractAddStepHandler; import org.jboss.as.controller.AbstractRemoveStepHandler; import org.jboss.as.controller.AbstractWriteAttributeHandler; import org.jboss.as.controller.ManagementModel; import org.jboss.as.controller.OperationContext; import org.jboss.as.controller.OperationFailedException; import org.jboss.as.controller.OperationStepHandler; import org.jboss.as.controller.PathAddress; import org.jboss.as.controller.ResourceBuilder; import org.jboss.as.controller.ResourceDefinition; import org.jboss.as.controller.SimpleAttributeDefinition; import org.jboss.as.controller.descriptions.NonResolvingResourceDescriptionResolver; import org.jboss.as.controller.operations.global.GlobalNotifications; import org.jboss.as.controller.operations.global.GlobalOperationHandlers; import org.jboss.as.controller.registry.ManagementResourceRegistration; import org.jboss.as.controller.registry.Resource; import org.jboss.as.controller.test.AbstractControllerTestBase; import org.jboss.dmr.ModelNode; import org.junit.Test; /** * @author <a href="http://jmesnil.net/">Jeff Mesnil</a> (c) 2013 Red Hat inc. */ public class GlobalNotificationsTestCase extends AbstractControllerTestBase { public static final SimpleAttributeDefinition MY_ATTRIBUTE = create("my-attribute", LONG, true) .setDefaultValue(new ModelNode(12345)) .build(); public static final SimpleAttributeDefinition MY_RUNTIME_ATTRIBUTE = create("my-runtime-attribute", LONG) .setDefaultValue(new ModelNode(6789)) .setAllowNull(true) .setStorageRuntime() .build(); public static final SimpleAttributeDefinition FAIL_ADD_OPERATION = create("fail-add-operation", BOOLEAN) .setDefaultValue(new ModelNode(false)) .build(); public static final SimpleAttributeDefinition FAIL_REMOVE_OPERATION = create("fail-remove-operation", BOOLEAN) .setDefaultValue(new ModelNode(false)) .build(); public static long runtimeAttributeValue; private static final PathAddress RESOURCE_ADDRESS_PATTERN = pathAddress(pathElement("profile", "*")); private final PathAddress resourceAddress = pathAddress(pathElement("profile", "myprofile")); @Override protected void initModel(ManagementModel managementModel) { ManagementResourceRegistration rootRegistration = managementModel.getRootResourceRegistration(); // register the global operations to be able to call :read-attribute and :write-attribute GlobalOperationHandlers.registerGlobalOperations(rootRegistration, processType); // register the global notifications so there is no warning that emitted notifications are not described by the resource. GlobalNotifications.registerGlobalNotifications(rootRegistration, processType); ResourceDefinition profileDefinition = createDummyProfileResourceDefinition(); rootRegistration.registerSubModel(profileDefinition); } private static ResourceDefinition createDummyProfileResourceDefinition() { return ResourceBuilder.Factory.create(RESOURCE_ADDRESS_PATTERN.getElement(0), new NonResolvingResourceDescriptionResolver()) .setAddOperation(new AbstractAddStepHandler() { @Override protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException { MY_ATTRIBUTE.validateAndSet(operation, model); FAIL_ADD_OPERATION.validateAndSet(operation, model); FAIL_REMOVE_OPERATION.validateAndSet(operation, model); } @Override protected void performRuntime(OperationContext context, ModelNode operation, Resource resource) throws OperationFailedException { runtimeAttributeValue = MY_RUNTIME_ATTRIBUTE.resolveModelAttribute(context, operation).asLong(); boolean fail = FAIL_ADD_OPERATION.resolveModelAttribute(context, resource.getModel()).asBoolean(); if (fail) { throw new OperationFailedException("add operation failed"); } } }) .setRemoveOperation(new AbstractRemoveStepHandler() { @Override protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { boolean fail = FAIL_REMOVE_OPERATION.resolveModelAttribute(context, model).asBoolean(); if (fail) { throw new OperationFailedException("remove operation failed"); } } // no-op }) .addReadWriteAttribute(MY_ATTRIBUTE, null, new AbstractWriteAttributeHandler<Long>(MY_ATTRIBUTE) { @Override protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode resolvedValue, ModelNode currentValue, HandbackHolder<Long> handbackHolder) throws OperationFailedException { return false; } @Override protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode valueToRestore, ModelNode valueToRevert, Long handback) throws OperationFailedException { } }) .addReadWriteAttribute(MY_RUNTIME_ATTRIBUTE, new OperationStepHandler() { @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { context.getResult().set(runtimeAttributeValue); } }, new OperationStepHandler() { @Override public void execute(OperationContext context, ModelNode operation) throws OperationFailedException { runtimeAttributeValue = operation.get(VALUE).asLong(); } }) .build(); } @Test public void test_RESOURCE_ADDED_NOTIFICATION() throws Exception { ListBackedNotificationHandler handler = new ListBackedNotificationHandler(); NotificationFilter filter = new TestNotificationHandler(resourceAddress, RESOURCE_ADDED_NOTIFICATION); getController().getNotificationRegistry().registerNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); executeForResult(createOperation(ADD, resourceAddress)); assertEquals("the notification handler did not receive the " + RESOURCE_ADDED_NOTIFICATION, 1, handler.getNotifications().size()); getController().getNotificationRegistry().unregisterNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); } @Test public void test_RESOURCE_ADDED_NOTIFICATION_isNotSentWhenAddOperationFails() throws Exception { ListBackedNotificationHandler handler = new ListBackedNotificationHandler(); NotificationFilter filter = new TestNotificationHandler(resourceAddress, RESOURCE_ADDED_NOTIFICATION); getController().getNotificationRegistry().registerNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); ModelNode addOperation = createOperation(ADD, resourceAddress); addOperation.get(FAIL_ADD_OPERATION.getName()).set(true); executeForFailure(addOperation); assertTrue("the notification handler unexpectedly receives the " + RESOURCE_ADDED_NOTIFICATION, handler.getNotifications().isEmpty()); getController().getNotificationRegistry().unregisterNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); } @Test public void test_RESOURCE_REMOVED_NOTIFICATION() throws Exception { executeForResult(createOperation(ADD, resourceAddress)); ListBackedNotificationHandler handler = new ListBackedNotificationHandler(); NotificationFilter filter = new TestNotificationHandler(resourceAddress, RESOURCE_REMOVED_NOTIFICATION); getController().getNotificationRegistry().registerNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); executeForResult(createOperation(REMOVE, resourceAddress)); assertEquals("the notification handler did not receive the " + RESOURCE_REMOVED_NOTIFICATION, 1, handler.getNotifications().size()); getController().getNotificationRegistry().unregisterNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); } @Test public void test_RESOURCE_REMOVED_NOTIFICATION_isNotSentWhenRemoveOperationFails() throws Exception { ModelNode addOperation = createOperation(ADD, resourceAddress); addOperation.get(FAIL_REMOVE_OPERATION.getName()).set(true); executeForResult(addOperation); ListBackedNotificationHandler handler = new ListBackedNotificationHandler(); NotificationFilter filter = new TestNotificationHandler(resourceAddress, RESOURCE_REMOVED_NOTIFICATION); getController().getNotificationRegistry().registerNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); executeForFailure(createOperation(REMOVE, resourceAddress)); assertTrue("the notification handler unexpectedly receives the " + RESOURCE_REMOVED_NOTIFICATION, handler.getNotifications().isEmpty()); getController().getNotificationRegistry().unregisterNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); } @Test public void test_ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION() throws Exception { executeForResult(createOperation(ADD, resourceAddress)); ModelNode readAttribute = createOperation(READ_ATTRIBUTE_OPERATION, resourceAddress); readAttribute.get(NAME).set(MY_ATTRIBUTE.getName()); ModelNode result = executeForResult(readAttribute); // read-attribute returns the default value assertEquals(MY_ATTRIBUTE.getDefaultValue().asLong(), result.asLong()); ListBackedNotificationHandler handler = new ListBackedNotificationHandler(); NotificationFilter filter = new TestNotificationHandler(resourceAddress, ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION); getController().getNotificationRegistry().registerNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); long newValue = System.currentTimeMillis(); ModelNode writeAttribute = createOperation(WRITE_ATTRIBUTE_OPERATION, resourceAddress); writeAttribute.get(NAME).set(MY_ATTRIBUTE.getName()); writeAttribute.get(VALUE).set(newValue); executeForResult(writeAttribute); assertEquals("the notification handler did not receive the " + ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION, 1, handler.getNotifications().size()); Notification notification = handler.getNotifications().get(0); assertEquals(MY_ATTRIBUTE.getName(), notification.getData().require(NAME).asString()); // the value was not defined initially: the notification does not return the default value but undefined instead. assertFalse(notification.getData().require(OLD_VALUE).isDefined()); assertEquals(newValue, notification.getData().require(NEW_VALUE).asLong()); getController().getNotificationRegistry().unregisterNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); } @Test public void test_ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION_isNotSentWhenWriteAttributeOperationFails() throws Exception { executeForResult(createOperation(ADD, resourceAddress)); ListBackedNotificationHandler handler = new ListBackedNotificationHandler(); NotificationFilter filter = new TestNotificationHandler(resourceAddress, ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION); getController().getNotificationRegistry().registerNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); String incorrectValue = UUID.randomUUID().toString(); ModelNode writeAttribute = createOperation(WRITE_ATTRIBUTE_OPERATION, resourceAddress); writeAttribute.get(NAME).set(MY_ATTRIBUTE.getName()); writeAttribute.get(VALUE).set(incorrectValue); executeForFailure(writeAttribute); assertTrue("the notification handler unexpectedly receives the " + ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION, handler.getNotifications().isEmpty()); getController().getNotificationRegistry().unregisterNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); } @Test public void test_ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION_isNotSentWhenAttributeValueIsTheSame() throws Exception { long value = System.currentTimeMillis(); ModelNode add = createOperation(ADD, resourceAddress); add.get(MY_ATTRIBUTE.getName()).set(value); executeForResult(add); ListBackedNotificationHandler handler = new ListBackedNotificationHandler(); NotificationFilter filter = new TestNotificationHandler(resourceAddress, ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION); getController().getNotificationRegistry().registerNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); ModelNode writeAttribute = createOperation(WRITE_ATTRIBUTE_OPERATION, resourceAddress); writeAttribute.get(NAME).set(MY_ATTRIBUTE.getName()); writeAttribute.get(VALUE).set(value); executeForResult(writeAttribute); assertEquals("the notification handler unexpectedly receives the " + ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION, 0, handler.getNotifications().size()); getController().getNotificationRegistry().unregisterNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); } @Test public void test_ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION_isSentWhenUndefineAttributeIsCalled() throws Exception { long initialValue = System.currentTimeMillis(); ModelNode add = createOperation(ADD, resourceAddress); add.get(MY_ATTRIBUTE.getName()).set(initialValue); executeForResult(add); ListBackedNotificationHandler handler = new ListBackedNotificationHandler(); NotificationFilter filter = new TestNotificationHandler(resourceAddress, ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION); getController().getNotificationRegistry().registerNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); ModelNode undefineAttribute = createOperation(UNDEFINE_ATTRIBUTE_OPERATION, resourceAddress); undefineAttribute.get(NAME).set(MY_ATTRIBUTE.getName()); executeForResult(undefineAttribute); assertEquals("the notification handler did not receive the " + ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION, 1, handler.getNotifications().size()); Notification notification = handler.getNotifications().get(0); assertEquals(MY_ATTRIBUTE.getName(), notification.getData().require(NAME).asString()); assertEquals(initialValue, notification.getData().require(OLD_VALUE).asLong()); assertFalse(notification.getData().require(NEW_VALUE).isDefined()); getController().getNotificationRegistry().unregisterNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); } @Test public void test_ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION_isSentWhenUndefineAttributeIsCalledOnAnUndefinedAttribute() throws Exception { // MY_ATTRIBUTE is not defined when the resource is created. ModelNode add = createOperation(ADD, resourceAddress); executeForResult(add); ListBackedNotificationHandler handler = new ListBackedNotificationHandler(); NotificationFilter filter = new TestNotificationHandler(resourceAddress, ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION); getController().getNotificationRegistry().registerNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); ModelNode undefineAttribute = createOperation(UNDEFINE_ATTRIBUTE_OPERATION, resourceAddress); undefineAttribute.get(NAME).set(MY_ATTRIBUTE.getName()); executeForResult(undefineAttribute); assertTrue("the notification handler unexpectedly receives the " + ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION, handler.getNotifications().isEmpty()); getController().getNotificationRegistry().unregisterNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); } @Test public void test_ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION_isSentForUndefinedRuntimeAttribute() throws Exception { /// the resource is added without a defined my-runtime-attribute ModelNode add = createOperation(ADD, resourceAddress); executeForResult(add); ListBackedNotificationHandler handler = new ListBackedNotificationHandler(); NotificationFilter filter = new TestNotificationHandler(resourceAddress, ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION); getController().getNotificationRegistry().registerNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); long newValue = System.currentTimeMillis(); ModelNode writeAttribute = createOperation(WRITE_ATTRIBUTE_OPERATION, resourceAddress); writeAttribute.get(NAME).set(MY_RUNTIME_ATTRIBUTE.getName()); writeAttribute.get(VALUE).set(newValue); executeForResult(writeAttribute); assertEquals("the notification handler did not receive the " + ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION, 1, handler.getNotifications().size()); Notification notification = handler.getNotifications().get(0); assertEquals(MY_RUNTIME_ATTRIBUTE.getName(), notification.getData().require(NAME).asString()); // the old-value corresponds to the actual value read from the runtime attribute before its value was changed assertEquals(MY_RUNTIME_ATTRIBUTE.getDefaultValue().asLong(), notification.getData().require(OLD_VALUE).asLong()); assertEquals(newValue, notification.getData().require(NEW_VALUE).asLong()); getController().getNotificationRegistry().unregisterNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); } @Test public void test_ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION_isSentForRuntimeAttribute() throws Exception { long initialValue = 42; ModelNode add = createOperation(ADD, resourceAddress); add.get(MY_RUNTIME_ATTRIBUTE.getName()).set(initialValue); executeForResult(add); ListBackedNotificationHandler handler = new ListBackedNotificationHandler(); NotificationFilter filter = new TestNotificationHandler(resourceAddress, ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION); getController().getNotificationRegistry().registerNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); long newValue = System.currentTimeMillis(); ModelNode writeAttribute = createOperation(WRITE_ATTRIBUTE_OPERATION, resourceAddress); writeAttribute.get(NAME).set(MY_RUNTIME_ATTRIBUTE.getName()); writeAttribute.get(VALUE).set(newValue); executeForResult(writeAttribute); assertEquals("the notification handler did not receive the " + ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION, 1, handler.getNotifications().size()); Notification notification = handler.getNotifications().get(0); assertEquals(MY_RUNTIME_ATTRIBUTE.getName(), notification.getData().require(NAME).asString()); assertEquals(initialValue, notification.getData().require(OLD_VALUE).asLong()); assertEquals(newValue, notification.getData().require(NEW_VALUE).asLong()); getController().getNotificationRegistry().unregisterNotificationHandler(RESOURCE_ADDRESS_PATTERN, handler, filter); } private static class TestNotificationHandler implements NotificationFilter { private final PathAddress expectedAddress; private final String expectedType; /** * Filters out notifications so that the handler will handle only those that are from the {@code expectedType} * and emitted from the {@code expectedAddress}. */ TestNotificationHandler(PathAddress expectedAddress, String expectedType) { this.expectedAddress = expectedAddress; this.expectedType = expectedType; } @Override public boolean isNotificationEnabled(Notification notification) { return notification.getSource().equals(expectedAddress) && notification.getType().equals(expectedType); } } }