/*******************************************************************************
* Copyright (c) 2015 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
* Jan S. Rellermeyer, IBM Research - initial API and implementation
*******************************************************************************/
package org.eclipse.concierge.service.eventadmin;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceEvent;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import org.osgi.service.event.TopicPermission;
/**
* EventAdmin backport for OSGi R3 frameworks.
*
* @author Jan S. Rellermeyer, ETH Zurich
*/
final class EventAdminImpl implements FrameworkListener, BundleListener,
ServiceListener, EventAdmin {
/**
* strings for framework events.
*/
private static final String[] FRAMEWORK_EVENT = { "STARTED", "ERROR",
"PACKAGES_REFRESHED", "STARTLEVEL_CHANGED", "WARNING" };
/**
* strings for bundle events.
*/
private static final String[] BUNDLE_EVENT = { "INSTALLED", "STARTED",
"STOPPED", "UPDATED", "UNINSTALLED", "RESOLVED", "UNRESOLVED" };
/**
* strings for service events.
*/
private static final String[] SERVICE_EVENT = { "REGISTERED", "MODIFIED",
"UNREGISTERING" };
/**
* queue for asynchronous event delivery.
*/
private List eventQueue = new ArrayList(2);
/**
* event handler subscribrions.
*/
private HashMap eventHandlerSubscriptions = new HashMap(2);
/**
* the security manager.
*/
static SecurityManager security;
/**
* thread variable.
*/
boolean running = true;
/**
* create a new EventAdminImpl instance.
*/
public EventAdminImpl() {
security = System.getSecurityManager();
// bootstrapping: find all registered event handlers
ServiceReference[] refs = null;
try {
refs = EventAdminActivator.context.getServiceReferences(
EventHandler.class.getName(), null);
} catch (InvalidSyntaxException e) {
// does not happen
e.printStackTrace();
}
if (refs != null) {
for (int i = 0; i < refs.length; i++) {
try {
final Object handler = EventAdminActivator.context
.getService(refs[i]);
if (handler == this) {
continue;
}
final String[] topics = (String[]) refs[i]
.getProperty(EventConstants.EVENT_TOPIC);
final String filter = (String) refs[i]
.getProperty(EventConstants.EVENT_FILTER);
final Filter filterObj = filter != null ? EventAdminActivator.context
.createFilter(filter)
: null;
eventHandlerSubscriptions.put(refs[i]
.getProperty(Constants.SERVICE_ID),
new Subscription((EventHandler) handler, topics,
filterObj));
} catch (InvalidSyntaxException e) {
e.printStackTrace();
}
}
}
new EventDispatchingThread().start();
}
/**
* receive a <code>FrameworkEvent</code>.
*
* @param fEvent
* the framework event.
* @see org.osgi.framework.FrameworkListener#frameworkEvent(org.osgi.framework.FrameworkEvent)
*/
public void frameworkEvent(final FrameworkEvent fEvent) {
Dictionary props = new Hashtable();
props.put(EventConstants.EVENT, fEvent);
props.put(EventConstants.TIMESTAMP,
new Long(System.currentTimeMillis()));
final Bundle bundle;
if ((bundle = fEvent.getBundle()) != null) {
props.put("bundle.id", new Long(bundle.getBundleId()));
props.put(EventConstants.BUNDLE_SYMBOLICNAME, "null");
props.put("bundle", bundle);
}
final Throwable throwable;
if ((throwable = fEvent.getThrowable()) != null) {
props.put(EventConstants.EXECPTION_CLASS, throwable.getClass()
.getName());
props.put(EventConstants.EXCEPTION_MESSAGE, throwable.getMessage());
props.put(EventConstants.EXCEPTION, throwable);
}
int t = log2(fEvent.getType());
String type = t < 5 ? FRAMEWORK_EVENT[t] : "UNDEFINED";
Event event = new Event("org/osgi/framework/FrameworkEvent/" + type,
props);
postEvent(event);
}
/**
* receive a <code>BundleEvent</code>.
*
* @param bEvent
* the bundle event.
* @see org.osgi.framework.BundleListener#bundleChanged(org.osgi.framework.BundleEvent)
*/
public void bundleChanged(final BundleEvent bEvent) {
Dictionary props = new Hashtable();
props.put(EventConstants.TIMESTAMP,
new Long(System.currentTimeMillis()));
Bundle bundle = bEvent.getBundle();
props.put(EventConstants.EVENT, bEvent);
props.put("bundle.id", new Long(bundle.getBundleId()));
props.put("bundle", bundle);
int t = log2(bEvent.getType());
String type = t < 7 ? BUNDLE_EVENT[t] : "UNDEFINED";
Event event = new Event("org/osgi/framework/BundleEvent/" + type, props);
postEvent(event);
}
/**
* receive a <code>ServiceEvent</code>.
*
* @param sEvent
* the service event.
* @see org.osgi.framework.ServiceListener#serviceChanged(org.osgi.framework.ServiceEvent)
*/
public void serviceChanged(final ServiceEvent sEvent) {
/*
* check if an event handler is affected, in this case, we have to
* update our list of known event handlers
*/
try {
ServiceReference ref = sEvent.getServiceReference();
final List objClasses = Arrays.asList((String[]) ref
.getProperty("objectClass"));
if (objClasses.contains("org.osgi.service.event.EventHandler")) {
final Long serviceID = (Long) ref
.getProperty(Constants.SERVICE_ID);
final Object handler = EventAdminActivator.context
.getService(ref);
switch (sEvent.getType()) {
case ServiceEvent.REGISTERED: {
final String[] topics = (String[]) ref
.getProperty(EventConstants.EVENT_TOPIC);
final String filter = (String) ref
.getProperty(EventConstants.EVENT_FILTER);
final Filter filterObj = filter != null ? EventAdminActivator.context
.createFilter(filter)
: null;
Subscription ehandler = new Subscription(
(EventHandler) handler, topics, filterObj);
eventHandlerSubscriptions.put(serviceID, ehandler);
break;
}
case ServiceEvent.UNREGISTERING: {
eventHandlerSubscriptions.remove(serviceID);
EventAdminActivator.context.ungetService(ref);
break;
}
case ServiceEvent.MODIFIED: {
final Subscription subscr = (Subscription) eventHandlerSubscriptions
.get(serviceID);
if (subscr != null) {
final String[] topics = (String[]) ref
.getProperty(EventConstants.EVENT_TOPIC);
final String filter = (String) ref
.getProperty(EventConstants.EVENT_FILTER);
final Filter filterObj = filter != null ? EventAdminActivator.context
.createFilter(filter)
: null;
subscr.update(topics, filterObj);
}
}
default:
}
}
Dictionary props = new Hashtable();
props.put(EventConstants.EVENT, sEvent);
props.put(EventConstants.TIMESTAMP, new Long(System
.currentTimeMillis()));
props.put(EventConstants.SERVICE, sEvent.getServiceReference());
int t = log2(sEvent.getType());
String type = t < 4 ? SERVICE_EVENT[t] : "UNDEFINED";
Event event = new Event("org/osgi/framework/ServiceEvent/" + type,
props);
postEvent(event);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* send an event asynchronously.
*
* @param event
* the Event.
*
* @see org.osgi.service.event.EventAdmin#postEvent(org.osgi.service.event.Event)
*/
public void postEvent(final Event event) {
if (security != null) {
// TODO: cache permissions
security.checkPermission(new TopicPermission(event.getTopic(),
TopicPermission.PUBLISH));
}
final Subscription[] subscriptions = (Subscription[]) eventHandlerSubscriptions.values()
.toArray(new Subscription[eventHandlerSubscriptions.size()]);
final ArrayList handlers = new ArrayList(subscriptions.length);
for (int i = 0; i < subscriptions.length; i++) {
if (subscriptions[i].matches(event)) {
handlers.add(subscriptions[i].getHandler());
}
}
synchronized (eventQueue) {
eventQueue.add(new QueueElement(event, (EventHandler[]) handlers
.toArray(new EventHandler[handlers.size()])));
eventQueue.notifyAll();
}
}
/**
* send an event synchronously.
*
* @param event
* the event.
* @see org.osgi.service.event.EventAdmin#sendEvent(org.osgi.service.event.Event)
*/
public void sendEvent(final Event event) {
if (security != null) {
// TODO: cache permissions
security.checkPermission(new TopicPermission(event.getTopic(),
TopicPermission.PUBLISH));
}
final Subscription[] subscriptions = (Subscription[]) eventHandlerSubscriptions.values()
.toArray(new Subscription[eventHandlerSubscriptions.size()]);
for (int i = 0; i < subscriptions.length; i++) {
if (subscriptions[i].matches(event)) {
subscriptions[i].sendEvent(event);
}
}
}
/**
* get the logarithm with base 2.
*
* @param num
* the value.
* @return the logarithm.
*/
private static int log2(final int num) {
int i = num;
int j = -1;
while (i > 0) {
i = i >> 1;
j++;
}
return j;
}
/**
* EventDispatchingThread dispatches events on the local framework.
*/
private final class EventDispatchingThread extends Thread {
/**
* creates and starts a new EventDispatchingThread.
*/
private EventDispatchingThread() {
setDaemon(true);
}
/**
* thread loop.
*
* @see java.lang.Thread#run()
*/
public void run() {
try {
while (running) {
synchronized (eventQueue) {
if (eventQueue.isEmpty()) {
// wait until something arrives
eventQueue.wait();
}
// get the element and deliver it
QueueElement element = (QueueElement) eventQueue
.remove(0);
final EventHandler[] handlers = element.handlers;
final Event event = element.event;
try {
for (int i = 0; i < handlers.length; i++) {
handlers[i].handleEvent(event);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
/**
* Queue element.
*/
private static final class QueueElement {
/**
* the event.
*/
final Event event;
/**
* the handlers
*/
final EventHandler[] handlers;
/**
* create a new QueueElement.
*
* @param event
* the event.
* @param handlers
* the handlers.
*/
private QueueElement(final Event event, final EventHandler[] handlers) {
this.event = event;
this.handlers = handlers;
}
}
}