content: /**
   *  Licensed to the Apache Software Foundation (ASF) under one or more
   *  contributor license agreements.  See the NOTICE file distributed with
   *  this work for additional information regarding copyright ownership.
   *  The ASF licenses this file to You under the Apache License, Version 2.0
   *  (the "License"); you may not use this file except in compliance with
   *  the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   */
  package org.apache.aries.jmx.framework;
  
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertNotNull;
  import static org.junit.Assert.assertNull;
  import static org.junit.Assert.assertTrue;
  import static org.ops4j.pax.exam.CoreOptions.options;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Dictionary;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Hashtable;
  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;
  import javax.management.openmbean.CompositeData;
  import javax.management.openmbean.TabularData;
  
  import org.apache.aries.jmx.AbstractIntegrationTest;
  import org.apache.aries.jmx.codec.PropertyData;
  import org.apache.aries.jmx.test.bundlea.api.InterfaceA;
  import org.apache.aries.jmx.test.bundleb.api.InterfaceB;
  import org.junit.Before;
  import org.junit.Test;
  import org.ops4j.pax.exam.Configuration;
  import org.ops4j.pax.exam.Option;
  import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
  import org.ops4j.pax.exam.spi.reactors.PerMethod;
  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;
  import org.osgi.service.cm.ManagedServiceFactory;
  
  /**
   *
   *
   * @version $Rev$ $Date$
   */
  @ExamReactorStrategy(PerMethod.class)
  public class ServiceStateMBeanTest extends AbstractIntegrationTest {
  	
      private ObjectName objectName;
  	private ServiceStateMBean mbean;
  
      @Configuration
      public Option[] configuration() {
  		return options(
                  setupRemoteDebugging(),
                  jmxRuntime(),
  				bundlea(),
  				bundleb()
  				);
      }
      
      @Before
      public void doSetUp() {
          waitForMBean(ServiceStateMBean.OBJECTNAME);
          objectName = waitForMBean(ServiceStateMBean.OBJECTNAME);
          mbean = getMBean(objectName, ServiceStateMBean.class);
          assertNotNull(mbean);
      }
  
      @Test
      public void testObjectName() throws Exception {
          Set<ObjectName> names = mbeanServer.queryNames(new ObjectName(ServiceStateMBean.OBJECTNAME + ",*"), null);
          assertEquals(1, names.size());
          ObjectName name = names.iterator().next();
          Hashtable<String, String> props = name.getKeyPropertyList();
          assertEquals(context().getProperty(Constants.FRAMEWORK_UUID), props.get("uuid"));
          assertEquals(context().getBundle(0).getSymbolicName(), props.get("framework"));
      }
  
      @Test
      public void testMBeanInterface() throws Exception {
          //get bundles
          Bundle a = getBundleByName("org.apache.aries.jmx.test.bundlea");
          assertBundleStarted(a);
          Bundle b = getBundleByName("org.apache.aries.jmx.test.bundleb");
          assertBundleStarted(b);
  
          // get services
  
          ServiceReference refA = bundleContext.getServiceReference(InterfaceA.class.getName());
          assertNotNull(refA);
          long serviceAId = (Long) refA.getProperty(Constants.SERVICE_ID);
          assertTrue(serviceAId > -1);
  
          ServiceReference refB = bundleContext.getServiceReference(InterfaceB.class.getName());
          assertNotNull(refB);
          long serviceBId = (Long) refB.getProperty(Constants.SERVICE_ID);
          assertTrue(serviceBId > -1);
  
          ServiceReference[] refs = bundleContext.getServiceReferences(ManagedServiceFactory.class.getName(), "(" + Constants.SERVICE_PID + "=jmx.test.B.factory)");
          assertNotNull(refs);
          assertEquals(1, refs.length);
          ServiceReference msf = refs[0];
  
  
          // getBundleIdentifier
  
          assertEquals(a.getBundleId(), mbean.getBundleIdentifier(serviceAId));
  
          //getObjectClass
  
          String[] objectClass = mbean.getObjectClass(serviceAId);
          assertEquals(2, objectClass.length);
          List<String> classNames = Arrays.asList(objectClass);
          assertTrue(classNames.contains(InterfaceA.class.getName()));
          assertTrue(classNames.contains(ManagedService.class.getName()));
  
          // getProperties
  
          TabularData serviceProperties = mbean.getProperties(serviceBId);
          assertNotNull(serviceProperties);
          assertEquals(JmxConstants.PROPERTIES_TYPE, serviceProperties.getTabularType());
          assertTrue(serviceProperties.values().size() > 1);
          assertEquals("org.apache.aries.jmx.test.ServiceB",
                  PropertyData.from(serviceProperties.get(new Object[] { Constants.SERVICE_PID })).getValue());
  
          // getUsingBundles
  
          long[] usingBundles = mbean.getUsingBundles(serviceBId);
          assertEquals(1, usingBundles.length);
          assertEquals(a.getBundleId(), usingBundles[0]);
  
          // listServices
  
          ServiceReference[] allSvsRefs = bundleContext.getAllServiceReferences(null, null);
          TabularData allServices = mbean.listServices();
          assertNotNull(allServices);
          assertEquals(allSvsRefs.length, allServices.values().size());
  
          // 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) {
                  if (notification instanceof AttributeChangeNotification) {
                      attributeChanges.add((AttributeChangeNotification) notification);
                  } else {
                      received.add(notification);
                  }
              }
          }, null, null);
  
  
          assertNotNull(refB);
          assertNotNull(msf);
          b.stop();
          refB = bundleContext.getServiceReference(InterfaceB.class.getName());
          refs = bundleContext.getServiceReferences(ManagedServiceFactory.class.getName(), "(" + Constants.SERVICE_PID + "=jmx.test.B.factory)");
          assertNull(refs);
          assertNull(refB);
          b.start();
          refB = bundleContext.getServiceReference(InterfaceB.class.getName());
          refs = bundleContext.getServiceReferences(ManagedServiceFactory.class.getName(), "(" + Constants.SERVICE_PID + "=jmx.test.B.factory)");
          assertNotNull(refB);
          assertNotNull(refs);
          assertEquals(1, refs.length);
  
          waitForListToReachSize(received, 4);
  
          assertEquals(4, received.size());
          assertEquals(4, attributeChanges.size());
      }
  
  	private void assertBundleStarted(Bundle bundle) {
  		assertEquals("Bundle " + bundle.getSymbolicName() + " should be started but is in state " + bundle.getState(),
  				Bundle.ACTIVE, bundle.getState());
  	}
  
  	@Test
      public void testAttributeChangeNotifications() throws Exception {
          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[] idsWithout = mbean.getServiceIds();
  
          String svc = "A String Service";
          ServiceRegistration reg = bundleContext.registerService(String.class.getName(), svc, null);
          long id = (Long) reg.getReference().getProperty(Constants.SERVICE_ID);
  
          long[] idsWith = new long[idsWithout.length + 1];
          System.arraycopy(idsWithout, 0, idsWith, 0, idsWithout.length);
          idsWith[idsWith.length - 1] = id;
          Arrays.sort(idsWith);
  
          waitForListToReachSize(attributeChanges, 1);
          AttributeChangeNotification ac = attributeChanges.get(0);
          assertEquals("ServiceIds", ac.getAttributeName());
          long seq1 = ac.getSequenceNumber();
          assertTrue(Arrays.equals(idsWithout, (long []) ac.getOldValue()));
          assertTrue(Arrays.equals(idsWith, (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(seq1 +1, ac2.getSequenceNumber());
          assertTrue(Arrays.equals(idsWith, (long []) ac2.getOldValue()));
          assertTrue(Arrays.equals(idsWithout, (long []) ac2.getNewValue()));
      }
  
      @Test
      public void testGetServiceIds() throws Exception {
          ServiceReference[] allSvsRefs = bundleContext.getAllServiceReferences(null, null);
          long[] expectedServiceIds = new long[allSvsRefs.length];
          for (int i=0; i < allSvsRefs.length; i++) {
              expectedServiceIds[i] = (Long) allSvsRefs[i].getProperty(Constants.SERVICE_ID);
          }
          long[] actualServiceIds = mbean.getServiceIds();
          Arrays.sort(expectedServiceIds);
          Arrays.sort(actualServiceIds);
          assertTrue(Arrays.equals(expectedServiceIds, actualServiceIds));
      }
  
      @Test
      public void testGetServiceAndGetProperty() throws Exception {
      	
          ServiceReference sref = bundleContext.getServiceReference(InterfaceA.class);
          // Get service to increase service references
          context().getService(sref);
          Long serviceID = (Long) sref.getProperty(Constants.SERVICE_ID);
  
          CompositeData svcData  = mbean.getService(serviceID);
          assertEquals(serviceID, svcData.get(ServiceStateMBean.IDENTIFIER));
          assertEquals(sref.getBundle().getBundleId(), svcData.get(ServiceStateMBean.BUNDLE_IDENTIFIER));
          Set<String> expectedClasses = new HashSet<String>(Arrays.asList(InterfaceA.class.getName(), ManagedService.class.getName()));
          Set<String> actualClasses = new HashSet<String>(Arrays.asList((String []) svcData.get(ServiceStateMBean.OBJECT_CLASS)));
          assertEquals(expectedClasses, actualClasses);
          Bundle[] ub = sref.getUsingBundles();
          assertEquals("Precondition", 1, ub.length);
          assertTrue(Arrays.equals(new Long[] {ub[0].getBundleId()}, (Long[]) svcData.get("UsingBundles")));
  
          // Test mbean.getProperty()
          String pid = (String) sref.getProperty(Constants.SERVICE_PID);
          CompositeData pidData = mbean.getProperty(serviceID, Constants.SERVICE_PID);
          assertEquals(pid, pidData.get("Value"));
          assertEquals("String", pidData.get("Type"));
  
          CompositeData idData = mbean.getProperty(serviceID, Constants.SERVICE_ID);
          assertEquals("" + serviceID, idData.get("Value"));
          assertEquals("Long", idData.get("Type"));
  
          CompositeData ocData = mbean.getProperty(serviceID, Constants.OBJECTCLASS);
          String form1 = InterfaceA.class.getName() + "," + ManagedService.class.getName();
          String form2 = ManagedService.class.getName() + "," + InterfaceA.class.getName();
          assertTrue(ocData.get("Value").equals(form1) ||
                     ocData.get("Value").equals(form2));
          assertEquals("Array of String", ocData.get("Type"));
          context().ungetService(sref);
      }
  
      @Test
      @SuppressWarnings("unchecked")
      public void testServicePropertiesInListServices() throws Exception {
          ServiceReference[] refs = bundleContext.getAllServiceReferences(InterfaceA.class.getName(), null);
          assertEquals("Precondition", 1, refs.length);
          ServiceReference ref = refs[0];
  
          TabularData svcTab = mbean.listServices();
          CompositeData svcData = svcTab.get(new Object [] {ref.getProperty(Constants.SERVICE_ID)});
  
          Set<String> expectedOCs = new HashSet<String>(Arrays.asList(
                  InterfaceA.class.getName(), ManagedService.class.getName()));
          Set<String> actualOCs = new HashSet<String>(
                  Arrays.asList((String [])svcData.get(Constants.OBJECTCLASS)));
          assertEquals(expectedOCs, actualOCs);
  
          Map<String, Object> expectedProperties = new HashMap<String, Object>();
          for (String key : ref.getPropertyKeys()) {
              Object value = ref.getProperty(key);
              if (value.getClass().isArray())
                  continue;
  
              expectedProperties.put(key, value);
          }
  
          Map<String, Object> actualProperties = new HashMap<String, Object>();
          TabularData actualProps = (TabularData) svcData.get(ServiceStateMBean.PROPERTIES);
          for (CompositeData cd : (Collection<CompositeData>) actualProps.values()) {
              Object type = cd.get(JmxConstants.TYPE);
              if (JmxConstants.STRING.equals(type)) {
                  actualProperties.put((String) cd.get(JmxConstants.KEY), cd.get(JmxConstants.VALUE));
              } else if (JmxConstants.LONG.equals(type)) {
                  actualProperties.put((String) cd.get(JmxConstants.KEY), Long.valueOf(cd.get(JmxConstants.VALUE).toString()));
              }
          }
  
          assertEquals(expectedProperties, actualProperties);
      }
  
      @Test
      public void testListServices() throws Exception {
          String filter = "(" + Constants.SERVICE_PID + "=*)";
          ServiceReference[] refs = bundleContext.getAllServiceReferences(null, filter);
          TabularData svcData = mbean.listServices(null, filter);
          assertEquals(refs.length, svcData.size());
  
          ServiceReference sref = bundleContext.getServiceReference(InterfaceA.class);
          TabularData svcTab = mbean.listServices(InterfaceA.class.getName(), null);
          assertEquals(1, svcTab.size());
          CompositeData actualSvc = (CompositeData) svcTab.values().iterator().next();
          CompositeData expectedSvc = mbean.getService((Long) sref.getProperty(Constants.SERVICE_ID));
          assertEquals(expectedSvc, actualSvc);
      }
  
      @SuppressWarnings("unchecked")
  	@Test
      public void testListServicesSelectiveItems() throws Exception {
          String filter = "(|(service.pid=org.apache.aries.jmx.test.ServiceB)(service.pid=jmx.test.B.factory))";
          ServiceReference[] refs = bundleContext.getAllServiceReferences(null, filter);
          TabularData svcData = mbean.listServices(null, filter, ServiceStateMBean.BUNDLE_IDENTIFIER);
          assertEquals(refs.length, svcData.size());
  
          long id = refs[0].getBundle().getBundleId();
          for (ServiceReference ref : refs) {
              assertEquals("Precondition", id, ref.getBundle().getBundleId());
          }
  
          for (CompositeData cd : new ArrayList<CompositeData>((Collection<CompositeData>) svcData.values())) {
              assertEquals(id, cd.get(ServiceStateMBean.BUNDLE_IDENTIFIER));
              assertNotNull(cd.get(ServiceStateMBean.IDENTIFIER));
              assertNull(cd.get(ServiceStateMBean.OBJECT_CLASS));
              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++;
          }
      }
  }
  