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++;
+        }
+    }
 }