You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aries.apache.org by da...@apache.org on 2012/05/16 17:47:33 UTC
svn commit: r1339237 - in /aries/trunk/sandbox/jmx-next:
jmx-core/src/main/java/org/apache/aries/jmx/framework/
jmx-core/src/test/java/org/apache/aries/jmx/framework/
jmx-itests/src/test/java/org/apache/aries/jmx/framework/
Author: davidb
Date: Wed May 16 15:47:33 2012
New Revision: 1339237
URL: http://svn.apache.org/viewvc?rev=1339237&view=rev
Log:
JMX-Next: Support JMX attribute change notifications on ServiceStateMBean.getServiceIds()
Modified:
aries/trunk/sandbox/jmx-next/jmx-core/src/main/java/org/apache/aries/jmx/framework/ServiceState.java
aries/trunk/sandbox/jmx-next/jmx-core/src/test/java/org/apache/aries/jmx/framework/ServiceStateTest.java
aries/trunk/sandbox/jmx-next/jmx-itests/src/test/java/org/apache/aries/jmx/framework/ServiceStateMBeanTest.java
Modified: aries/trunk/sandbox/jmx-next/jmx-core/src/main/java/org/apache/aries/jmx/framework/ServiceState.java
URL: http://svn.apache.org/viewvc/aries/trunk/sandbox/jmx-next/jmx-core/src/main/java/org/apache/aries/jmx/framework/ServiceState.java?rev=1339237&r1=1339236&r2=1339237&view=diff
==============================================================================
--- aries/trunk/sandbox/jmx-next/jmx-core/src/main/java/org/apache/aries/jmx/framework/ServiceState.java (original)
+++ aries/trunk/sandbox/jmx-next/jmx-core/src/main/java/org/apache/aries/jmx/framework/ServiceState.java Wed May 16 15:47:33 2012
@@ -21,8 +21,11 @@ import static org.apache.aries.jmx.util.
import static org.osgi.jmx.JmxConstants.PROPERTIES_TYPE;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
@@ -30,6 +33,7 @@ import java.util.concurrent.atomic.Atomi
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import javax.management.AttributeChangeNotification;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
@@ -57,7 +61,7 @@ import org.osgi.service.log.LogService;
/**
* Implementation of <code>ServiceStateMBean</code> which emits JMX <code>Notification</code> for framework
- * <code>ServiceEvent</code> events
+ * <code>ServiceEvent</code> events and changes to the <code>ServiceIds</code> attribute.
*
* @version $Rev$ $Date$
*/
@@ -69,6 +73,7 @@ public class ServiceState extends Notifi
protected ExecutorService eventDispatcher;
protected AllServiceListener serviceListener;
private AtomicInteger notificationSequenceNumber = new AtomicInteger(1);
+ private AtomicInteger attributeChangeNotificationSequenceNumber = new AtomicInteger(1);
private AtomicInteger registrations = new AtomicInteger(0);
private Lock lock = new ReentrantLock();
// notification type description
@@ -176,11 +181,17 @@ public class ServiceState extends Notifi
* @see javax.management.NotificationBroadcasterSupport#getNotificationInfo()
*/
public MBeanNotificationInfo[] getNotificationInfo() {
- String[] types = new String[] { SERVICE_EVENT };
- String name = Notification.class.getName();
- String description = "A ServiceEvent issued from the Framework describing a service lifecycle change";
- MBeanNotificationInfo info = new MBeanNotificationInfo(types, name, description);
- return new MBeanNotificationInfo[] { info };
+ MBeanNotificationInfo eventInfo = new MBeanNotificationInfo(
+ new String[] { SERVICE_EVENT },
+ Notification.class.getName(),
+ "A ServiceEvent issued from the Framework describing a service lifecycle change");
+
+ MBeanNotificationInfo attributeChangeInfo = new MBeanNotificationInfo(
+ new String[] { AttributeChangeNotification.ATTRIBUTE_CHANGE },
+ AttributeChangeNotification.class.getName(),
+ "An attribute of this MBean has changed");
+
+ return new MBeanNotificationInfo[] { eventInfo, attributeChangeInfo };
}
/**
@@ -196,6 +207,10 @@ public class ServiceState extends Notifi
ids[i] = id;
}
+ // The IDs are sorted here. It's not required by the spec but it's nice
+ // to have an ordered list returned.
+ Arrays.sort(ids);
+
return ids;
} catch (InvalidSyntaxException e) {
IOException ioe = new IOException();
@@ -236,13 +251,45 @@ public class ServiceState extends Notifi
if (serviceListener == null) {
serviceListener = new AllServiceListener() {
public void serviceChanged(ServiceEvent serviceevent) {
- final Notification notification = new Notification(EVENT, OBJECTNAME,
- notificationSequenceNumber.getAndIncrement());
try {
+ // Create a notification for the event
+ final Notification notification = new Notification(EVENT, OBJECTNAME,
+ notificationSequenceNumber.getAndIncrement());
notification.setUserData(new ServiceEventData(serviceevent).toCompositeData());
+
+ // also send notifications to the serviceIDs attribute listeners, if a service was added or removed
+ final AttributeChangeNotification attributeChangeNotification;
+ int eventType = serviceevent.getType();
+ switch (eventType) {
+ case ServiceEvent.REGISTERED:
+ case ServiceEvent.UNREGISTERING:
+ long serviceID = (Long) serviceevent.getServiceReference().getProperty(Constants.SERVICE_ID);
+ long[] ids = getServiceIds();
+
+ List<Long> without = new ArrayList<Long>(ids.length);
+ for (long id : ids) {
+ if (id != serviceID)
+ without.add(id);
+ }
+ List<Long> with = new ArrayList<Long>(without);
+ with.add(serviceID);
+ Collections.sort(with);
+
+ Long[] oldIDs = (eventType == ServiceEvent.REGISTERED ? without : with).toArray(new Long[] {});
+ Long[] newIDs = (eventType == ServiceEvent.REGISTERED ? with : without).toArray(new Long[] {});
+
+ attributeChangeNotification = new AttributeChangeNotification(OBJECTNAME, attributeChangeNotificationSequenceNumber.getAndIncrement(),
+ System.currentTimeMillis(), "ServiceIds changed", "ServiceIds", "[Ljava.lang.Long;", oldIDs, newIDs);
+ break;
+ default:
+ attributeChangeNotification = null;
+ }
+
eventDispatcher.submit(new Runnable() {
public void run() {
sendNotification(notification);
+ if (attributeChangeNotification != null)
+ sendNotification(attributeChangeNotification);
}
});
} catch (RejectedExecutionException re) {
Modified: aries/trunk/sandbox/jmx-next/jmx-core/src/test/java/org/apache/aries/jmx/framework/ServiceStateTest.java
URL: http://svn.apache.org/viewvc/aries/trunk/sandbox/jmx-next/jmx-core/src/test/java/org/apache/aries/jmx/framework/ServiceStateTest.java?rev=1339237&r1=1339236&r2=1339237&view=diff
==============================================================================
--- aries/trunk/sandbox/jmx-next/jmx-core/src/test/java/org/apache/aries/jmx/framework/ServiceStateTest.java (original)
+++ aries/trunk/sandbox/jmx-next/jmx-core/src/test/java/org/apache/aries/jmx/framework/ServiceStateTest.java Wed May 16 15:47:33 2012
@@ -39,6 +39,7 @@ import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
+import javax.management.AttributeChangeNotification;
import javax.management.MBeanServer;
import javax.management.Notification;
import javax.management.NotificationListener;
@@ -56,75 +57,82 @@ import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceReference;
/**
- *
+ *
*
* @version $Rev$ $Date$
*/
public class ServiceStateTest {
-
+
@Test
public void testNotificationsForServiceEvents() throws Exception {
-
+
BundleContext context = mock(BundleContext.class);
Logger logger = mock(Logger.class);
-
+
ServiceState serviceState = new ServiceState(context, logger);
-
+
ServiceReference reference = mock(ServiceReference.class);
Bundle b1 = mock(Bundle.class);
-
+
when(b1.getBundleId()).thenReturn(new Long(9));
when(b1.getSymbolicName()).thenReturn("bundle");
when(b1.getLocation()).thenReturn("file:/location");
when(reference.getBundle()).thenReturn(b1);
when(reference.getProperty(Constants.SERVICE_ID)).thenReturn(new Long(44));
when(reference.getProperty(Constants.OBJECTCLASS)).thenReturn(new String[] {"org.apache.aries.jmx.Mock"});
-
+
+ when(context.getAllServiceReferences(null, null)).thenReturn(new ServiceReference[] {reference});
+
ServiceEvent registeredEvent = mock(ServiceEvent.class);
when(registeredEvent.getServiceReference()).thenReturn(reference);
when(registeredEvent.getType()).thenReturn(ServiceEvent.REGISTERED);
-
+
ServiceEvent modifiedEvent = mock(ServiceEvent.class);
when(modifiedEvent.getServiceReference()).thenReturn(reference);
when(modifiedEvent.getType()).thenReturn(ServiceEvent.MODIFIED);
-
+
MBeanServer server = mock(MBeanServer.class);
-
+
//setup for notification
ObjectName objectName = new ObjectName(OBJECTNAME);
serviceState.preRegister(server, objectName);
serviceState.postRegister(true);
-
+
//holder for Notifications captured
final List<Notification> received = new LinkedList<Notification>();
-
+ final List<AttributeChangeNotification> attributeChanges = new LinkedList<AttributeChangeNotification>();
+
//add NotificationListener to receive the events
serviceState.addNotificationListener(new NotificationListener() {
public void handleNotification(Notification notification, Object handback) {
- received.add(notification);
+ if (notification instanceof AttributeChangeNotification) {
+ attributeChanges.add((AttributeChangeNotification) notification);
+ } else {
+ received.add(notification);
+ }
}
}, null, null);
-
+
// capture the ServiceListener registered with BundleContext to issue ServiceEvents
- ArgumentCaptor<AllServiceListener> argument = ArgumentCaptor.forClass(AllServiceListener.class);
+ ArgumentCaptor<AllServiceListener> argument = ArgumentCaptor.forClass(AllServiceListener.class);
verify(context).addServiceListener(argument.capture());
-
+
//send events
AllServiceListener serviceListener = argument.getValue();
serviceListener.serviceChanged(registeredEvent);
serviceListener.serviceChanged(modifiedEvent);
-
- //shutdown dispatcher via unregister callback
+
+ //shutdown dispatcher via unregister callback
serviceState.postDeregister();
//check the ServiceListener is cleaned up
verify(context).removeServiceListener(serviceListener);
-
+
ExecutorService dispatcher = serviceState.getEventDispatcher();
assertTrue(dispatcher.isShutdown());
dispatcher.awaitTermination(2, TimeUnit.SECONDS);
assertTrue(dispatcher.isTerminated());
-
+
assertEquals(2, received.size());
Notification registered = received.get(0);
assertEquals(1, registered.getSequenceNumber());
@@ -135,7 +143,7 @@ public class ServiceStateTest {
assertEquals("bundle", data.get(BUNDLE_SYMBOLIC_NAME));
assertArrayEquals(new String[] {"org.apache.aries.jmx.Mock" }, (String[]) data.get(OBJECT_CLASS));
assertEquals(ServiceEvent.REGISTERED, data.get(EVENT));
-
+
Notification modified = received.get(1);
assertEquals(2, modified.getSequenceNumber());
data = (CompositeData) modified.getUserData();
@@ -145,58 +153,64 @@ public class ServiceStateTest {
assertEquals("bundle", data.get(BUNDLE_SYMBOLIC_NAME));
assertArrayEquals(new String[] {"org.apache.aries.jmx.Mock" }, (String[]) data.get(OBJECT_CLASS));
assertEquals(ServiceEvent.MODIFIED, data.get(EVENT));
-
+
+ assertEquals(1, attributeChanges.size());
+ AttributeChangeNotification ac = attributeChanges.get(0);
+ assertEquals("ServiceIds", ac.getAttributeName());
+ assertEquals(0, ((Long [])ac.getOldValue()).length);
+ assertEquals(1, ((Long [])ac.getNewValue()).length);
+ assertEquals(new Long(44), ((Long [])ac.getNewValue())[0]);
}
-
+
@Test
public void testLifeCycleOfNotificationSupport() throws Exception {
-
+
BundleContext context = mock(BundleContext.class);
Logger logger = mock(Logger.class);
-
+
ServiceState serviceState = new ServiceState(context, logger);
-
+
MBeanServer server1 = mock(MBeanServer.class);
MBeanServer server2 = mock(MBeanServer.class);
ObjectName objectName = new ObjectName(OBJECTNAME);
serviceState.preRegister(server1, objectName);
serviceState.postRegister(true);
-
+
// capture the ServiceListener registered with BundleContext to issue ServiceEvents
- ArgumentCaptor<AllServiceListener> argument = ArgumentCaptor.forClass(AllServiceListener.class);
+ ArgumentCaptor<AllServiceListener> argument = ArgumentCaptor.forClass(AllServiceListener.class);
verify(context).addServiceListener(argument.capture());
-
+
AllServiceListener serviceListener = argument.getValue();
assertNotNull(serviceListener);
-
+
ExecutorService dispatcher = serviceState.getEventDispatcher();
-
+
//do registration with another server
serviceState.preRegister(server2, objectName);
serviceState.postRegister(true);
-
+
// check no more actions on BundleContext
- argument = ArgumentCaptor.forClass(AllServiceListener.class);
+ argument = ArgumentCaptor.forClass(AllServiceListener.class);
verify(context, atMost(1)).addServiceListener(argument.capture());
assertEquals(1, argument.getAllValues().size());
-
+
//do one unregister
serviceState.postDeregister();
-
+
//verify bundleListener not invoked
verify(context, never()).removeServiceListener(serviceListener);
assertFalse(dispatcher.isShutdown());
-
+
//do second unregister and check cleanup
serviceState.postDeregister();
verify(context).removeServiceListener(serviceListener);
assertTrue(dispatcher.isShutdown());
dispatcher.awaitTermination(2, TimeUnit.SECONDS);
assertTrue(dispatcher.isTerminated());
-
-
-
+
+
+
}
}
Modified: aries/trunk/sandbox/jmx-next/jmx-itests/src/test/java/org/apache/aries/jmx/framework/ServiceStateMBeanTest.java
URL: http://svn.apache.org/viewvc/aries/trunk/sandbox/jmx-next/jmx-itests/src/test/java/org/apache/aries/jmx/framework/ServiceStateMBeanTest.java?rev=1339237&r1=1339236&r2=1339237&view=diff
==============================================================================
--- aries/trunk/sandbox/jmx-next/jmx-itests/src/test/java/org/apache/aries/jmx/framework/ServiceStateMBeanTest.java (original)
+++ aries/trunk/sandbox/jmx-next/jmx-itests/src/test/java/org/apache/aries/jmx/framework/ServiceStateMBeanTest.java Wed May 16 15:47:33 2012
@@ -32,6 +32,8 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
+import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
@@ -39,6 +41,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
+import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;
@@ -58,6 +61,7 @@ import org.ops4j.pax.exam.junit.Configur
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
import org.osgi.jmx.JmxConstants;
import org.osgi.jmx.framework.ServiceStateMBean;
import org.osgi.service.cm.ManagedService;
@@ -209,10 +213,15 @@ public class ServiceStateMBeanTest exten
// notifications
final List<Notification> received = new ArrayList<Notification>();
+ final List<AttributeChangeNotification> attributeChanges = new ArrayList<AttributeChangeNotification>();
mbeanServer.addNotificationListener(objectName, new NotificationListener() {
public void handleNotification(Notification notification, Object handback) {
- received.add(notification);
+ if (notification instanceof AttributeChangeNotification) {
+ attributeChanges.add((AttributeChangeNotification) notification);
+ } else {
+ received.add(notification);
+ }
}
}, null, null);
@@ -231,14 +240,67 @@ public class ServiceStateMBeanTest exten
assertNotNull(refs);
assertEquals(1, refs.length);
- int i = 0;
- while (received.size() < 4 && i < 3) {
- Thread.sleep(1000);
- i++;
- }
+ waitForListToReachSize(received, 4);
assertEquals(4, received.size());
+ assertEquals(4, attributeChanges.size());
+ }
+
+ @Test
+ public void testAttributeChangeNotifications() throws Exception {
+ ObjectName objectName = waitForMBean(new ObjectName(ServiceStateMBean.OBJECTNAME));
+ ServiceStateMBean mbean = getMBean(objectName, ServiceStateMBean.class);
+ final List<AttributeChangeNotification> attributeChanges = new ArrayList<AttributeChangeNotification>();
+ mbeanServer.addNotificationListener(objectName, new NotificationListener() {
+ public void handleNotification(Notification notification, Object handback) {
+ if (notification instanceof AttributeChangeNotification) {
+ attributeChanges.add((AttributeChangeNotification) notification);
+ }
+ }
+ }, null, null);
+
+ assertEquals("Precondition", 0, attributeChanges.size());
+
+ long[] ids = mbean.getServiceIds();
+ Long[] idsWithoutService = new Long[ids.length];
+ for(int i=0; i < ids.length; i++) {
+ idsWithoutService[i] = ids[i];
+ }
+
+ String svc = "A String Service";
+ ServiceRegistration<?> reg = bundleContext.registerService(String.class.getName(), svc, null);
+ long id = (Long) reg.getReference().getProperty(Constants.SERVICE_ID);
+
+ List<Long> newIDList = new ArrayList<Long>(Arrays.asList(idsWithoutService));
+ newIDList.add(id);
+ Collections.sort(newIDList);
+ Long[] idsWithService = newIDList.toArray(new Long [] {});
+
+ waitForListToReachSize(attributeChanges, 1);
+ AttributeChangeNotification ac = attributeChanges.get(0);
+ assertEquals("ServiceIds", ac.getAttributeName());
+ assertEquals(1, ac.getSequenceNumber());
+ assertTrue(Arrays.equals(idsWithoutService, (Long []) ac.getOldValue()));
+ assertTrue(Arrays.equals(idsWithService, (Long []) ac.getNewValue()));
+
+ Dictionary<String, Object> props = new Hashtable<String, Object>();
+ props.put("somekey", "someval");
+ reg.setProperties(props);
+
+ // Setting the properties updates the service registration, however it should not cause the attribute notification
+ Thread.sleep(500); // Give the system a bit of time to send potential notifications
+ assertEquals("Changing the service registration should not cause an attribute notification",
+ 1, attributeChanges.size());
+
+ reg.unregister();
+
+ waitForListToReachSize(attributeChanges, 2);
+ AttributeChangeNotification ac2 = attributeChanges.get(1);
+ assertEquals("ServiceIds", ac2.getAttributeName());
+ assertEquals(2, ac2.getSequenceNumber());
+ assertTrue(Arrays.equals(idsWithService, (Long []) ac2.getOldValue()));
+ assertTrue(Arrays.equals(idsWithoutService, (Long []) ac2.getNewValue()));
}
@Test
@@ -370,4 +432,12 @@ public class ServiceStateMBeanTest exten
assertNull(cd.get(ServiceStateMBean.USING_BUNDLES));
}
}
+
+ private void waitForListToReachSize(List<?> list, int targetSize) throws InterruptedException {
+ int i = 0;
+ while (list.size() < targetSize && i < 3) {
+ Thread.sleep(1000);
+ i++;
+ }
+ }
}