/****************************************************************************
* Copyright (c) 2005, 2010 IBM Corporation, Composent, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Composent, Inc. - initial API and implementation
* IBM Corporation - initial API and implementation (non-distributed EventAdmin)
* Markus Alexander Kuppe - https://bugs.eclipse.org/412261
*****************************************************************************/
package org.eclipse.ecf.remoteservice.eventadmin;
import java.io.IOException;
import java.io.NotSerializableException;
import java.security.Permission;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.ecf.core.IContainer;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.sharedobject.BaseSharedObject;
import org.eclipse.ecf.core.sharedobject.SharedObjectMsg;
import org.eclipse.ecf.core.sharedobject.events.ISharedObjectCreateResponseEvent;
import org.eclipse.ecf.core.sharedobject.events.ISharedObjectMessageEvent;
import org.eclipse.ecf.internal.remoteservice.eventadmin.DefaultSerializationHandler;
import org.eclipse.ecf.internal.remoteservice.eventadmin.EventHandlerTracker;
import org.eclipse.ecf.internal.remoteservice.eventadmin.EventHandlerWrapper;
import org.eclipse.ecf.internal.remoteservice.eventadmin.LogTracker;
import org.eclipse.ecf.remoteservice.eventadmin.serialization.SerializationHandler;
import org.eclipse.osgi.framework.eventmgr.CopyOnWriteIdentityMap;
import org.eclipse.osgi.framework.eventmgr.EventManager;
import org.eclipse.osgi.framework.eventmgr.ListenerQueue;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.osgi.service.event.EventHandler;
import org.osgi.service.event.TopicPermission;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
public class DistributedEventAdmin extends BaseSharedObject implements
EventAdmin {
/**
* @since 1.2
* @noreference
* protected non-final for unit tests only!!!
*/
protected static boolean ignoreSerializationExceptions = Boolean
.getBoolean(DistributedEventAdmin.class.getName()
+ ".IgnoreSerialzationFailures");
private LogTracker logTracker;
private LogService log;
private EventManager eventManager;
private ServiceTracker etfServiceTracker;
private ServiceTracker shServiceTracker;
private final Set eventFilters = new HashSet();
private final Map topic2serializationHandler = new HashMap();
private static final String SHARED_OBJECT_MESSAGE_METHOD = "__handlePostEventSharedObjectMsg";
/**
* @since 1.2
* @noreference This field is not intended to be referenced by clients.
*/
protected EventHandlerTracker eventHandlerTracker;
/**
* @since 1.2
*/
protected BundleContext context;
/**
* @noreference
*/
protected DistributedEventAdmin() {
// nop
}
/**
* Create a Distributed EventAdmin implementation.
*
* @param context
* the BundleContext to be used. Must not be <code>null</code>.
* @param log
* the {@link LogService} to use. May be <code>null</code>. If
* <code>null</code>, then a LogTracker is created and
* used to find and use a {@link LogService}.
*
* @since 1.1
*/
public DistributedEventAdmin(BundleContext context, LogService log) {
Assert.isNotNull(context);
this.context = context;
if (log == null) {
// create log tracker and set the log to it
this.logTracker = new LogTracker(context, System.out);
this.log = this.logTracker;
} else {
this.logTracker = null;
this.log = log;
}
// Now create eventHandler tracker
this.eventHandlerTracker = new EventHandlerTracker(context, log);
}
/**
* Create a Distributed EventAdmin implementation.
*
* @param context
* the BundleContext to be used. Must not be <code>null</code>.
*
* @since 1.1
*/
public DistributedEventAdmin(BundleContext context) {
this(context, null);
}
/**
* Start this distributed event admin instance. This method should be called
* prior to registering this object as an {@link EventAdmin} implementation
* with the OSGi service registry.
*/
public void start() {
if (logTracker != null)
logTracker.open();
ThreadGroup eventGroup = new ThreadGroup("Distributed EventAdmin"); //$NON-NLS-1$
eventGroup.setDaemon(true);
eventManager = new EventManager(
"Distributed EventAdmin Async Event Dispatcher Thread",
eventGroup);
eventHandlerTracker.open();
// Other services can contribute Event topic filters which will be ignored
// by the distribution part of DistributedEventAdmin. This is primarily useful
// in cases where the Event data cannot be serialized or serialization is too
// expensive.
etfServiceTracker = new ServiceTracker(this.context, EventTopicFilter.class, new ServiceTrackerCustomizer() {
public Object addingService(ServiceReference reference) {
final EventTopicFilter etf = (EventTopicFilter) context.getService(reference);
addEventTopicFilters(etf.getFilters());
return etf;
}
public void modifiedService(ServiceReference reference,
Object service) {
// nop
}
public void removedService(ServiceReference reference,
Object service) {
final EventTopicFilter etf = (EventTopicFilter) service;
removeEventTopicFilters(etf.getFilters());
}
});
etfServiceTracker.open();
// SerializationHandler are responsible to handle serialization of Event properties
shServiceTracker = new ServiceTracker(this.context, SerializationHandler.class, new ServiceTrackerCustomizer() {
public Object addingService(ServiceReference reference) {
final SerializationHandler sh = (SerializationHandler) context.getService(reference);
topic2serializationHandler.put(sh.getTopic(), sh);
return sh;
}
public void modifiedService(ServiceReference reference,
Object service) {
// nop
}
public void removedService(ServiceReference reference,
Object service) {
final SerializationHandler sh = (SerializationHandler) service;
topic2serializationHandler.remove(sh.getTopic());
}
});
shServiceTracker.open();
}
/**
* Stop this distributed event admin instance. This method should be called
* after unregistering the {@link ServiceRegistration} created on
* registration with the OSGi service registry.
*/
public void stop() {
eventHandlerTracker.close();
if (eventManager != null) {
eventManager.close();
eventManager = null;
}
if (logTracker != null)
logTracker.close();
if (etfServiceTracker != null) {
etfServiceTracker.close();
etfServiceTracker = null;
}
if (shServiceTracker != null) {
shServiceTracker.close();
shServiceTracker = null;
}
}
/**
* Send the given event synchronously. The default implementation of this
* method simply does a <b>local-only</b> dispatch to {@link EventHandler}s.
* It does <b>not</b> attempt to distribute the given event as does
* {@link #postEvent(Event)}.
*
* @param event
* the Event to send synchronously to local {@link EventHandler}s
* (only). Must not be <code>null</code>.
*/
public void sendEvent(Event event) {
localDispatch(event, false);
// see bug https://bugs.eclipse.org/412303
}
/**
* Post an event for asynchronous delivery via this distributed event admin.
* This is the primary entry point for the distributed event admin
* implementation for asynchronous delivery to a set of receivers (known to
* the this shared object and it's enclosing {@link IContainer}. The event
* to post must not be <code>null</code>.
* <p>
* This implementation goes through the following steps
* <ol>
* <li>Call {@link #getEventToSend(Event)}. If the Event returned from
* getEventToSend is <code>null</code>, then the following three method
* calls do not occur, and postEvent returns immediately.</li>
* <li>Call {@link #sendMessage(Event)} with the non-<code>null</code>
* result of {@link #getEventToSend(Event)}</li>
* <li>Call {@link #notifyPostSendMessage(Event)}</li>
* <li>Call {@link #localDispatch(Event, boolean)}</li>
* </ol>
*
* @param event
* the Event to send asynchronously to matching
* {@link EventHandler}s. Must not be <code>null</code>.
* @since 1.1
*/
public void postEvent(final Event event) {
// First thing, we allow subclasses to decide whether the given event
// should be translated before message
// send into a new Event, or if it should not be sent at all
Event eventToSend = getEventToSend(event);
if (eventToSend != null) {
if (!eventFilters.contains(event.getTopic())) {
sendMessage(eventToSend);
// sent successfully, so now dispatch to any appropriate local
// EventHandlers
notifyPostSendMessage(eventToSend);
}
// This does local dispatch asynchronously
localDispatch(event, true);
}
}
/**
* Send the event as a shared object message. The given event will be
* serialized and sent via
* {@link #sendSharedObjectMsgTo(ID, SharedObjectMsg)}.
* <p>
* Prior to actual sending, the {@link #getTarget(Event)} method will be
* called, to allow subclasses to determine the target receiver. Then the
* {@link #createMessageDataFromEvent(ID, Event)} method is called, to
* create an Object[] of data for sending in the message. The Object[]
* returned from {@link #createMessageDataFromEvent(ID, Event)} must be
* serializable. See {@link #createMessageDataFromEvent(ID, Event)}.
* <p>
* Subclasses may override this method to customize or replace this
* sendMessage behavior.
* <p>
* If an exception occurs on serialization or sending, the
* {@link #handleSendMessageException(String, Event, Object[], IOException)}
* method will be called to handle it.
*
* @param eventToSend
* the event to send. Will not be <code>null</code>.
* @since 1.1
*/
protected void sendMessage(Event eventToSend) {
ID target = null;
Object[] messageData = null;
try {
target = getTarget(eventToSend);
messageData = createMessageDataFromEvent(target, eventToSend);
sendSharedObjectMsgTo(target, SharedObjectMsg.createMsg(
SHARED_OBJECT_MESSAGE_METHOD, messageData));
} catch (IOException e) {
handleSendMessageException("send exception to target=" + target,
eventToSend, messageData, e);
}
}
/**
* Create message data for deliver to a target (which could be
* <code>null</code> to designate multiple target receivers), The resulting
* Object[] must be Serializable and in a form that receivers can
* deserialize via {@link #createEventFromMessageData(ID, Object[])} on the
* receiver.
* <p>
* The default implementation creates a single {@link EventMessage} instance
* and adds it to an Object[] of length 1.
* <p>
* Subclasses may override as appropriate to customize the serialization of
* the given eventToSend.
* <p>
* Subclasses may override as appropriate. If this method is overridden,
* then {@link #createEventFromMessageData(ID, Object[])} should also be
* overridden as well on the receiver.
*
* @param target
* the target {@link ID} that is the intended receiver returned
* from {@link #getTarget(Event)}.
* @param eventToSend
* the event to send. Will not be <code>null</code>.
* @return Object[] the actual message data that will be serialized (must be
* Serializable to use in {@link #sendMessage(Event)}). The default
* implementation creates a single {@link EventMessage} instance and
* adds it to an Object[] of length 1.
* @throws NotSerializableException
* if the eventToSend cannot be serialized.
* @since 1.1
*/
protected Object[] createMessageDataFromEvent(ID target, Event eventToSend)
throws NotSerializableException {
final Object[] results = { new EventMessage(eventToSend, getSerializationHandler(eventToSend.getTopic())) };
return results;
}
/**
* @param topic topic
* @return SerializationHandler the serialization handler associated with topic
* @since 1.2
*/
protected SerializationHandler getSerializationHandler(String topic) {
final SerializationHandler sh = (SerializationHandler) topic2serializationHandler.get(topic);
if (sh == null) {
return DefaultSerializationHandler.INST;
}
return sh;
}
/**
* Create a local {@link Event} from deserialized messageData. The fromID
* will be a non-<code>null</code> {@link ID} instance that is the container
* ID of the sender DistributedEventAdmin. The default implementation
* assumes that a single {@link EventMessage} is in the first array element
* of the messageData, casts the messageData[0] to EventMessage, and then
* returns eventMessage.getEvent().
* <p>
* Subclasses can override as appropriate. If this method is overridden,
* then {@link #createMessageDataFromEvent(ID, Event)} should also be
* overridden as well on the sender.
*
* @param fromID
* the ID of the message sender. Will not be <code>null</code>.
* @param messageData
* Object[] received from fromID. Will be a deserialized
* local version of the Object[] from fromID.
* @return Event to be delivered to local {@link EventHandler}s. Should not
* be <code>null</code>.
*
* @since 1.1
*/
protected Event createEventFromMessageData(ID fromID, Object[] messageData) {
final EventMessage eventMessage = (EventMessage) messageData[0];
eventMessage.setSerializationHandler(getSerializationHandler(eventMessage.getTopic()));
return eventMessage.getEvent();
}
/**
* Handle any exceptions occuring as part of Event serialization or message
* send. The default is to call {@link #logError(String, Throwable)} with
* the eventToSend and messageParams appended to the message parameter.
*
* @param message
* a message associated with the exception.
* @param eventToSend
* the event that was to be sent.
* @param messageParams
* the message params that were to be
* @param exception exception
* @since 1.1
*/
protected void handleSendMessageException(String message,
Event eventToSend, Object[] messageParams, IOException exception) {
String exceptionMessage = ((message == null) ? "" : message)
+ " eventToSend="
+ eventToSend
+ " messageParams="
+ ((messageParams == null) ? null : Arrays
.asList(messageParams));
// By default we throw a runtime exception, but
// only throw an exception if not explicitly turned off
if (!(exception instanceof NotSerializableException)
|| !ignoreSerializationExceptions) {
logError(exceptionMessage, exception);
throw new ServiceException(exceptionMessage, exception);
} else {
logWarning(exceptionMessage, exception);
}
}
/**
* Get the target receiver for the eventToSend. The returned {@link ID} will
* be used to send a shared object message to either a single
* {@link IContainer}, or a group of {@link IContainer}s. To send to the
* entire group, this method should return <code>null</code>. The default
* implementation is to return <code>null</code>, meaning that the given
* eventToSend is to be sent to all receivers connected to this shared
* object's enclosing {@link IContainer}.
*
* @param eventToSend
* the eventToSend. Will not be <code>null</code>.
* @return ID the ID target for {@link #sendMessage(Event)} to send to. May
* be <code>null</code>. <code>null</code> is the default
* implementation, meaning that the Event will be delivered to all
* members of the group known to this shared object's
* {@link IContainer}.
*
* @since 1.1
*/
protected ID getTarget(Event eventToSend) {
return null;
}
/**
* Get the actual event to pass to {@link #sendMessage(Event)}. The default
* implementation of this method is to simply return the event passed in as
* the method argument.
* <p>
* Subclasses may override...to filter or transform the event prior to
* calling {@link #sendMessage(Event)}.
*
* @param event
* the event. Will not be <code>null</code>.
* @return Event to send. By default, the event provided as the argument is
* returned.
*
* @since 1.1
*/
protected Event getEventToSend(Event event) {
// By default, we distribute the same event that is passed in
return event;
}
/**
* Method called after {@link #sendMessage(Event)} is called (typically from
* within {@link #postEvent(Event)}), but prior to local dispatch. The
* default implementation is to do nothing.
*
* @param eventSent
* the event that was sent. Will not be <code>null</code>.
* @since 1.1
*/
protected void notifyPostSendMessage(Event eventSent) {
}
/**
* Method called from within {@link #localDispatch(Event, boolean)} prior to
* actual deliver to matching {@link EventHandler}s. The default
* implementation returns the given event. Subclasses may override as
* appropriate. If the returned Event is <code>null</code> then no local
* dispatch will occur for the given Event.
* <p>
* Subclasses may override as appropriate.
*
* @param event
* the Event to dispatch. Will not be <code>null</code>.
* @return Event the event to actually dispatch. If <code>null</code>, no
* local dispatch is done for this event.
*
* @since 1.1
*/
protected Event notifyPreLocalDispatch(Event event) {
return event;
}
/**
* Notification called after local dispatch has been done. The default
* implemenation does nothing. Note that this method is called by the thread
* that calls {@link #localDispatch(Event, boolean)}, and if the actual
* dispatch is done by another thread (i.e. second param to localDispatch is
* <code>true</code>), then the dispatch could occur before, after, or
* during the actual handling via the matching {@link EventHandler}s.
* <p>
* Subclasses may override as appropriate.
*
* @param event
* the Event that was delivered to matching {@link EventHandler}
* s. Will not be <code>null</code>.
* @since 1.1
*/
protected void notifyPostLocalDispatch(Event event) {
}
/**
* Locally dispatch an Event. This method is used to deliver an
* {@link Event} to matching {@link EventHandler}s that are registered in
* the local OSGi service registry.
*
* @param dispatchedEvent
* the Event to dispatch. Will not be <code>null</code>.
* @param isAsync
* <code>true</code> if the dispatch should be done
* asynchronously (non-blocking), <code>false</code> if the
* dispatch should be done synchronously.
*/
protected void localDispatch(Event dispatchedEvent, boolean isAsync) {
EventManager currentManager = eventManager;
if (currentManager == null) {
return;
}
if (dispatchedEvent == null) {
log.log(LogService.LOG_ERROR,
"Null event passed to EventAdmin was ignored.");
}
Event event = notifyPreLocalDispatch(dispatchedEvent);
if (event != null) {
String eventTopic = event.getTopic();
try {
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkPermission(new TopicPermission(eventTopic,
TopicPermission.PUBLISH));
} catch (SecurityException e) {
logError(
"Caller bundle does not have TopicPermission to publish topic "
+ eventTopic, e);
throw e;
}
Set eventHandlerWrappers = eventHandlerTracker
.getHandlers(eventTopic);
SecurityManager sm = System.getSecurityManager();
Permission perm = (sm == null) ? null : new TopicPermission(
eventTopic, TopicPermission.SUBSCRIBE);
CopyOnWriteIdentityMap listeners = new CopyOnWriteIdentityMap();
Iterator iter = eventHandlerWrappers.iterator();
while (iter.hasNext()) {
EventHandlerWrapper wrapper = (EventHandlerWrapper) iter.next();
listeners.put(wrapper, perm);
}
ListenerQueue listenerQueue = new ListenerQueue(currentManager);
listenerQueue.queueListeners(listeners.entrySet(),
eventHandlerTracker);
if (isAsync) {
listenerQueue.dispatchEventAsynchronous(0, event);
} else {
listenerQueue.dispatchEventSynchronous(0, event);
}
notifyPostLocalDispatch(event);
}
}
/**
* Handle the shared object message. This method is called on receiver implementations of
* the DistributedEventAdmin, so that they can deliver to locally registered {@link EventHandler}s.
* <p>
* This implementation does the following:
* <ol>
* <li>Verifies that the value {@link SharedObjectMsg#getMethod()} matches the appropriate String.</li>
* <li>Calls #createEventFromMessageData(ID, Object[]) to convert the message data (returned from #createMessageDataFromEvent(ID, Event)
* on the sender)</li>
* <li>If the Event returned from #createEventFromMessageData(ID, Object[]) is non-<code>null</code>, then call
* <ol>
* <li>#notifyReceivedEvent(ID, Event) to allow subclasses to be notified prior to local dispatch</li>
* <li>#localDispatch(Event, boolean) to actually do the local dispatch asynchronously</li>
* </ol></li>
* <li>
* </ol>
* @since 1.1
*/
protected boolean handleSharedObjectMsg(ID fromID, SharedObjectMsg msg) {
String soMethod = msg.getMethod();
if (SHARED_OBJECT_MESSAGE_METHOD.equals(soMethod)) {
try {
Object[] messageData = msg.getParameters();
Event receivedEvent = createEventFromMessageData(fromID,
messageData);
if (receivedEvent != null) {
notifyReceivedEvent(fromID, receivedEvent);
localDispatch(receivedEvent, true);
}
} catch (Exception e) {
logError(
"DistributedEventAdmin handleSharedObjectMsg error receiving msg="
+ msg, e);
}
return true;
} else {
logError("DistributedEventAdmin received bad shared object msg="
+ msg + " from=" + fromID);
}
return false;
}
/**
* @param fromID fromID
* @param receivedEvent received event
* @since 1.1
*/
protected void notifyReceivedEvent(ID fromID, Event receivedEvent) {
}
/**
* Override of BaseSharedObject.handleSharedObjectMsgEvent. Subclasses must
* not override this method.
*
* @since 1.1
*/
protected final boolean handleSharedObjectMsgEvent(
ISharedObjectMessageEvent event) {
boolean result = false;
if (event instanceof ISharedObjectCreateResponseEvent)
result = handleSharedObjectCreateResponseEvent((ISharedObjectCreateResponseEvent) event);
else {
SharedObjectMsg msg = getSharedObjectMsgFromEvent(event);
if (msg != null)
result = handleSharedObjectMsg(event.getRemoteContainerID(),
msg);
}
return result;
}
// log methods
/**
* Log a warning.
* <p>
* Subclasses may override as appropriate.
*
* @param message
* the message to include in the warning. Should not be
* <code>null</code>.
* @since 1.1
*/
protected void logWarning(String message) {
logWarning(message, null);
}
/**
* Log a warning.
* <p>
* Subclasses may override as appropriate.
*
* @param message
* the message to include in the warning. Should not be
* <code>null</code>.
* @param exception
* the exception to include in the warning. May be
* <code>null</code>. If non-<code>null</code> then exception
* will be printed to System.out.
*
* @since 1.1
*/
protected void logWarning(String message, Throwable exception) {
if (log != null) {
log.log(LogService.LOG_WARNING, message, exception);
} else {
System.out.println(message);
if (exception != null)
exception.printStackTrace(System.out);
}
}
/**
* Log an error.
* <p>
* Subclasses may override as appropriate.
*
* @param message
* the message to include in the error. Should not be
* <code>null</code>.
* @since 1.1
*/
protected void logError(String message) {
logError(message, null);
}
/**
* Log an error.
* <p>
* Subclasses may override as appropriate.
*
* @param message
* the message to include in the error. Should not be
* <code>null</code>.
* @param exception
* the exception to include in the warning. May be
* <code>null</code>. If non-<code>null</code> then exception
* will be printed to System.out.
*/
protected void logError(String message, Throwable exception) {
if (log != null) {
log.log(LogService.LOG_ERROR, message, exception);
} else {
System.err.println(message);
if (exception != null)
exception.printStackTrace(System.err);
}
}
/**
* @param filters topic filters to add
* @return boolean true if given filters added, false otherwise
* @since 1.2
*/
public boolean addEventTopicFilters(String[] filters) {
final List asList = Arrays.asList(filters);
return eventFilters.addAll(asList);
}
/**
* @param filters topic filters to add
* @return boolean true if given filters added, false otherwise
* @since 1.2
*/
public boolean removeEventTopicFilters(String[] filters) {
final List asList = Arrays.asList(filters);
return eventFilters.removeAll(asList);
}
}