/** * 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.apache.aries.jmx.util.FrameworkUtils.getBundleIds; import static org.apache.aries.jmx.util.FrameworkUtils.resolveService; 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; import java.util.concurrent.atomic.AtomicInteger; 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; import javax.management.Notification; import javax.management.NotificationBroadcasterSupport; import javax.management.ObjectName; import javax.management.openmbean.CompositeData; import javax.management.openmbean.TabularData; import javax.management.openmbean.TabularDataSupport; import org.apache.aries.jmx.JMXThreadFactory; import org.apache.aries.jmx.Logger; import org.apache.aries.jmx.codec.PropertyData; import org.apache.aries.jmx.codec.ServiceData; import org.apache.aries.jmx.codec.ServiceEventData; import org.osgi.framework.AllServiceListener; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceReference; import org.osgi.jmx.framework.ServiceStateMBean; import org.osgi.service.log.LogService; /** * Implementation of <code>ServiceStateMBean</code> which emits JMX <code>Notification</code> for framework * <code>ServiceEvent</code> events and changes to the <code>ServiceIds</code> attribute. * * @version $Rev$ $Date$ */ public class ServiceState extends NotificationBroadcasterSupport implements ServiceStateMBean, MBeanRegistration { protected Logger logger; private BundleContext bundleContext; private StateConfig stateConfig; 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 public static String SERVICE_EVENT = "org.osgi.service.event"; public ServiceState(BundleContext bundleContext, StateConfig stateConfig, Logger logger) { if (bundleContext == null) { throw new IllegalArgumentException("Argument bundleContext cannot be null"); } this.bundleContext = bundleContext; this.stateConfig = stateConfig; this.logger = logger; } /** * @see org.osgi.jmx.framework.ServiceStateMBean#getBundleIdentifier(long) */ public long getBundleIdentifier(long serviceId) throws IOException { ServiceReference reference = resolveService(bundleContext, serviceId); return reference.getBundle().getBundleId(); } /** * @see org.osgi.jmx.framework.ServiceStateMBean#getObjectClass(long) */ public String[] getObjectClass(long serviceId) throws IOException { ServiceReference reference = resolveService(bundleContext, serviceId); return (String[]) reference.getProperty(Constants.OBJECTCLASS); } /** * @see org.osgi.jmx.framework.ServiceStateMBean#getProperties(long) */ public TabularData getProperties(long serviceId) throws IOException { ServiceReference reference = resolveService(bundleContext, serviceId); TabularData propertiesTable = new TabularDataSupport(PROPERTIES_TYPE); for (String propertyKey : reference.getPropertyKeys()) { propertiesTable.put(PropertyData.newInstance(propertyKey, reference.getProperty(propertyKey)) .toCompositeData()); } return propertiesTable; } /** * @see org.osgi.jmx.framework.ServiceStateMBean#getProperty(long, java.lang.String) */ public CompositeData getProperty(long serviceId, String key) throws IOException { ServiceReference reference = resolveService(bundleContext, serviceId); return PropertyData.newInstance(key, reference.getProperty(key)).toCompositeData(); } /** * @see org.osgi.jmx.framework.ServiceStateMBean#getUsingBundles(long) */ public long[] getUsingBundles(long serviceId) throws IOException { ServiceReference reference = resolveService(bundleContext, serviceId); Bundle[] usingBundles = reference.getUsingBundles(); return getBundleIds(usingBundles); } /** * @see org.osgi.jmx.framework.ServiceStateMBean#getService(long) */ public CompositeData getService(long serviceId) throws IOException { return new ServiceData(resolveService(bundleContext, serviceId)).toCompositeData(); } /** * @see org.osgi.jmx.framework.ServiceStateMBean#listServices() */ public TabularData listServices() throws IOException { return listServices(null, null); } /** * @see org.osgi.jmx.framework.ServiceStateMBean#listServices(java.lang.String, java.lang.String) */ public TabularData listServices(String clazz, String filter) throws IOException { return listServices(clazz, filter, ServiceStateMBean.SERVICE_TYPE.keySet()); } /** * @see org.osgi.jmx.framework.ServiceStateMBean#listServices(java.lang.String, java.lang.String, java.lang.String...) */ public TabularData listServices(String clazz, String filter, String ... serviceTypeItems) throws IOException { return listServices(clazz, filter, Arrays.asList(serviceTypeItems)); } private TabularData listServices(String clazz, String filter, Collection<String> serviceTypeItems) throws IOException { TabularData servicesTable = new TabularDataSupport(SERVICES_TYPE); ServiceReference[] allServiceReferences = null; try { allServiceReferences = bundleContext.getAllServiceReferences(clazz, filter); } catch (InvalidSyntaxException e) { throw new IllegalStateException("Failed to retrieve all service references", e); } if (allServiceReferences != null) { for (ServiceReference reference : allServiceReferences) { servicesTable.put(new ServiceData(reference).toCompositeData(serviceTypeItems)); } } return servicesTable; } /** * @see javax.management.NotificationBroadcasterSupport#getNotificationInfo() */ public MBeanNotificationInfo[] getNotificationInfo() { 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 }; } /** * @see org.osgi.jmx.framework.ServiceStateMBean#getServiceIds() */ public long[] getServiceIds() throws IOException { try { ServiceReference<?>[] refs = bundleContext.getAllServiceReferences(null, null); long[] ids = new long[refs.length]; for (int i=0; i < refs.length; i++) { ServiceReference<?> ref = refs[i]; long id = (Long) ref.getProperty(Constants.SERVICE_ID); 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(); ioe.initCause(e); throw ioe; } } /** * @see javax.management.MBeanRegistration#postDeregister() */ public void postDeregister() { if (registrations.decrementAndGet() < 1) { shutDownDispatcher(); } } /** * @see javax.management.MBeanRegistration#postRegister(java.lang.Boolean) */ public void postRegister(Boolean registrationDone) { if (registrationDone && registrations.incrementAndGet() == 1) { eventDispatcher = Executors.newSingleThreadExecutor(new JMXThreadFactory("JMX OSGi Service State Event Dispatcher")); bundleContext.addServiceListener(serviceListener); } } public void preDeregister() throws Exception { // No action } /** * @see javax.management.MBeanRegistration#preRegister(javax.management.MBeanServer, javax.management.ObjectName) */ public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception { lock.lock(); try { if (serviceListener == null) { serviceListener = new AllServiceListener() { public void serviceChanged(ServiceEvent serviceevent) { if (stateConfig != null && !stateConfig.isServiceChangeNotificationEnabled()) { return; } 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 = getAttributeChangeNotification(serviceevent); eventDispatcher.submit(new Runnable() { public void run() { sendNotification(notification); if (attributeChangeNotification != null) sendNotification(attributeChangeNotification); } }); } catch (RejectedExecutionException re) { logger.log(LogService.LOG_WARNING, "Task rejected for JMX Notification dispatch of event [" + serviceevent + "] - Dispatcher may have been shutdown"); } catch (Exception e) { logger.log(LogService.LOG_WARNING, "Exception occured on JMX Notification dispatch for event [" + serviceevent + "]", e); } } }; } } finally { lock.unlock(); } return name; } protected AttributeChangeNotification getAttributeChangeNotification(ServiceEvent serviceevent) throws IOException { if (stateConfig != null && !stateConfig.isAttributeChangeNotificationEnabled()) { return null; } 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); // Sorting is not mandatory, but its nice for the user, note that getServiceIds() also returns a sorted array Collections.sort(with); List<Long> oldList = eventType == ServiceEvent.REGISTERED ? without : with; List<Long> newList = eventType == ServiceEvent.REGISTERED ? with : without; long[] oldIDs = new long[oldList.size()]; for (int i = 0; i < oldIDs.length; i++) { oldIDs[i] = oldList.get(i); } long[] newIDs = new long[newList.size()]; for (int i = 0; i < newIDs.length; i++) { newIDs[i] = newList.get(i); } return new AttributeChangeNotification(OBJECTNAME, attributeChangeNotificationSequenceNumber.getAndIncrement(), System.currentTimeMillis(), "ServiceIds changed", "ServiceIds", "Array of long", oldIDs, newIDs); default: return null; } } /* * Shuts down the notification dispatcher * [ARIES-259] MBeans not getting unregistered reliably */ protected void shutDownDispatcher() { if (serviceListener != null) { try { bundleContext.removeServiceListener(serviceListener); } catch (Exception e) { // ignore } } if (eventDispatcher != null) { eventDispatcher.shutdown(); } } /* * Returns the ExecutorService used to dispatch Notifications */ protected ExecutorService getEventDispatcher() { return eventDispatcher; } }