/**
* 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;
}
}