blob: 6445ea2903e0dff0de0e588961d7d780525d3ab1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.equinox.internal.event;
import java.security.Permission;
import java.util.*;
import org.eclipse.osgi.framework.eventmgr.EventDispatcher;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;
import org.osgi.util.tracker.ServiceTracker;
public class EventHandlerTracker extends ServiceTracker<EventHandler, EventHandlerWrapper> implements EventDispatcher<EventHandlerWrapper, Permission, Event> {
private final LogTracker log;
//* List<EventHandlerWrapper> of all handlers with topic of "*"
private final List<EventHandlerWrapper> globalWildcard;
// Map<String,List<EventHandlerWrapper>> key is topic prefix of partial wildcard
private final Map<String, List<EventHandlerWrapper>> partialWildcard;
// Map<String,List<EventHandlerWrapper>> key is topic name
private final Map<String, List<EventHandlerWrapper>> topicName;
public EventHandlerTracker(BundleContext context, LogTracker log) {
super(context, EventHandler.class.getName(), null);
this.log = log;
globalWildcard = new ArrayList<>();
partialWildcard = new HashMap<>();
topicName = new HashMap<>();
}
@Override
public EventHandlerWrapper addingService(ServiceReference<EventHandler> reference) {
EventHandlerWrapper wrapper = new EventHandlerWrapper(reference, context, log);
synchronized (this) {
if (wrapper.init()) {
bucket(wrapper);
}
}
return wrapper;
}
@Override
public void modifiedService(ServiceReference<EventHandler> reference, EventHandlerWrapper service) {
synchronized (this) {
unbucket(service);
if (service.init()) {
bucket(service);
return;
}
}
service.flush(); // needs to be called outside sync region
}
@Override
public void removedService(ServiceReference<EventHandler> reference, EventHandlerWrapper service) {
synchronized (this) {
unbucket(service);
}
service.flush(); // needs to be called outside sync region
}
/**
* Place the wrapper into the appropriate buckets.
* This is a performance optimization for event delivery.
*
* @param wrapper The wrapper to place in buckets.
* @GuardedBy this
*/
private void bucket(EventHandlerWrapper wrapper) {
final String[] topics = wrapper.getTopics();
final int length = (topics == null) ? 0 : topics.length;
for (int i = 0; i < length; i++) {
String topic = topics[i];
// global wildcard
if (topic.equals("*")) { //$NON-NLS-1$
globalWildcard.add(wrapper);
}
// partial wildcard
else if (topic.endsWith("/*")) { //$NON-NLS-1$
String key = topic.substring(0, topic.length() - 2); // Strip off "/*" from the end
List<EventHandlerWrapper> wrappers = partialWildcard.get(key);
if (wrappers == null) {
wrappers = new ArrayList<>();
partialWildcard.put(key, wrappers);
}
wrappers.add(wrapper);
}
// simple topic name
else {
List<EventHandlerWrapper> wrappers = topicName.get(topic);
if (wrappers == null) {
wrappers = new ArrayList<>();
topicName.put(topic, wrappers);
}
wrappers.add(wrapper);
}
}
}
/**
* Remove the wrapper from the buckets.
*
* @param wrapper The wrapper to remove from the buckets.
* @GuardedBy this
*/
private void unbucket(EventHandlerWrapper wrapper) {
final String[] topics = wrapper.getTopics();
final int length = (topics == null) ? 0 : topics.length;
for (int i = 0; i < length; i++) {
String topic = topics[i];
// global wilcard
if (topic.equals("*")) { //$NON-NLS-1$
globalWildcard.remove(wrapper);
}
// partial wildcard
else if (topic.endsWith("/*")) { //$NON-NLS-1$
String key = topic.substring(0, topic.length() - 2); // Strip off "/*" from the end
List<EventHandlerWrapper> wrappers = partialWildcard.get(key);
if (wrappers != null) {
wrappers.remove(wrapper);
if (wrappers.isEmpty()) {
partialWildcard.remove(key);
}
}
}
// simple topic name
else {
List<EventHandlerWrapper> wrappers = topicName.get(topic);
if (wrappers != null) {
wrappers.remove(wrapper);
if (wrappers.isEmpty()) {
topicName.remove(topic);
}
}
}
}
}
/**
* Return the set of handlers which subscribe to the event topic.
* A set is used to ensure a handler is not called for an event more than once.
*
* @param topic
* @return a set of handlers
*/
public synchronized Set<EventHandlerWrapper> getHandlers(final String topic) {
// Use a set to remove duplicates
Set<EventHandlerWrapper> handlers = new LinkedHashSet<>();
// Add the "*" handlers
handlers.addAll(globalWildcard);
// Add the handlers with partial matches
if (partialWildcard.size() > 0) {
int index = topic.lastIndexOf('/');
while (index >= 0) {
String subTopic = topic.substring(0, index);
List<EventHandlerWrapper> wrappers = partialWildcard.get(subTopic);
if (wrappers != null) {
handlers.addAll(wrappers);
}
// Strip the last level from the topic. For example, org/osgi/framework becomes org/osgi.
// Wildcard topics are inserted into the map with the "/*" stripped off.
index = subTopic.lastIndexOf('/');
}
}
// Add the handlers for matching topic names
List<EventHandlerWrapper> wrappers = topicName.get(topic);
if (wrappers != null) {
handlers.addAll(wrappers);
}
return handlers;
}
/**
* Dispatches Event to EventHandlers
*
* @param eventListener
* @param listenerObject
* @param eventAction
* @param eventObject
* @see org.eclipse.osgi.framework.eventmgr.EventDispatcher#dispatchEvent(java.lang.Object,
* java.lang.Object, int, java.lang.Object)
*/
@Override
public void dispatchEvent(EventHandlerWrapper eventListener, Permission listenerObject, int eventAction, Event eventObject) {
eventListener.handleEvent(eventObject, listenerObject);
}
}