/** * Copyright (c) 2014-2017 by the respective copyright holders. * 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 */ package org.eclipse.smarthome.core.internal.events; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.Dictionary; import java.util.HashSet; import java.util.Hashtable; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeoutException; import org.eclipse.smarthome.core.common.SafeMethodCaller; import org.eclipse.smarthome.core.common.SafeMethodCaller.ActionWithException; import org.eclipse.smarthome.core.events.Event; import org.eclipse.smarthome.core.events.EventFactory; import org.eclipse.smarthome.core.events.EventFilter; import org.eclipse.smarthome.core.events.EventPublisher; import org.eclipse.smarthome.core.events.EventSubscriber; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.service.component.ComponentContext; import org.osgi.service.event.EventAdmin; import org.osgi.service.event.EventHandler; import org.osgi.util.tracker.ServiceTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; /** * The {@link OSGiEventManager} provides an OSGi based default implementation of the Eclipse SmartHome event bus. * * The OSGiEventHandler tracks {@link EventSubscriber}s and {@link EventFactory}s, receives OSGi events (by * implementing the OSGi {@link EventHandler} interface) and dispatches the received OSGi events as ESH {@link Event}s * to the {@link EventSubscriber}s if the provided filter applies. * * The {@link OSGiEventManager} also serves as {@link EventPublisher} by implementing the EventPublisher interface. * Events are send in an asynchronous way via OSGi Event Admin mechanism. * * @author Stefan Bußweiler - Initial contribution */ public class OSGiEventManager implements EventHandler, EventPublisher { @SuppressWarnings("rawtypes") private class EventSubscriberServiceTracker extends ServiceTracker { @SuppressWarnings("unchecked") public EventSubscriberServiceTracker(BundleContext context) { super(context, EventSubscriber.class.getName(), null); } @SuppressWarnings("unchecked") @Override public Object addingService(ServiceReference reference) { EventSubscriber eventSubscriber = (EventSubscriber) this.context.getService(reference); if (eventSubscriber != null) { addEventSubscriber(eventSubscriber); return eventSubscriber; } else { return null; } } @Override public void removedService(ServiceReference reference, Object service) { removeEventSubscriber((EventSubscriber) service); } } private Logger logger = LoggerFactory.getLogger(OSGiEventManager.class); private EventAdmin osgiEventAdmin; private final Map<String, EventFactory> typedEventFactories = new ConcurrentHashMap<String, EventFactory>(); private final SetMultimap<String, EventSubscriber> typedEventSubscribers = Multimaps .synchronizedSetMultimap(HashMultimap.<String, EventSubscriber> create()); private EventSubscriberServiceTracker eventSubscriberServiceTracker; protected void activate(ComponentContext componentContext) { eventSubscriberServiceTracker = new EventSubscriberServiceTracker(componentContext.getBundleContext()); eventSubscriberServiceTracker.open(); } protected void deactivate(ComponentContext componentContext) { if (eventSubscriberServiceTracker != null) { eventSubscriberServiceTracker.close(); } } protected void setEventAdmin(EventAdmin eventAdmin) { this.osgiEventAdmin = eventAdmin; } protected void unsetEventAdmin(EventAdmin eventAdmin) { this.osgiEventAdmin = null; } protected void addEventFactory(EventFactory eventFactory) { Set<String> supportedEventTypes = eventFactory.getSupportedEventTypes(); for (String supportedEventType : supportedEventTypes) { synchronized (this) { if (!typedEventFactories.containsKey(supportedEventType)) { typedEventFactories.put(supportedEventType, eventFactory); } } } } protected void removeEventFactory(EventFactory eventFactory) { Set<String> supportedEventTypes = eventFactory.getSupportedEventTypes(); for (String supportedEventType : supportedEventTypes) { typedEventFactories.remove(supportedEventType); } } @Override public void handleEvent(org.osgi.service.event.Event osgiEvent) { Object typeObj = osgiEvent.getProperty("type"); Object payloadObj = osgiEvent.getProperty("payload"); Object topicObj = osgiEvent.getProperty("topic"); Object sourceObj = osgiEvent.getProperty("source"); if (typeObj instanceof String && payloadObj instanceof String && topicObj instanceof String) { String typeStr = (String) typeObj; String payloadStr = (String) payloadObj; String topicStr = (String) topicObj; String sourceStr = (sourceObj instanceof String) ? (String) sourceObj : null; if (!typeStr.isEmpty() && !payloadStr.isEmpty() && !topicStr.isEmpty()) { handleEvent(typeStr, payloadStr, topicStr, sourceStr); } } else { logger.error( "The handled OSGi event is invalid. Expect properties as string named 'type', 'payload' and 'topic'. " + "Received event properties are: " + osgiEvent.getPropertyNames()); } } private void handleEvent(final String type, final String payload, final String topic, final String source) { EventFactory eventFactory = typedEventFactories.get(type); if (eventFactory != null) { Set<EventSubscriber> eventSubscribers = getEventSubscribers(type); if (!eventSubscribers.isEmpty()) { Event eshEvent = createESHEvent(eventFactory, type, payload, topic, source); if (eshEvent != null) { dispatchESHEvent(eventSubscribers, eshEvent); } } } else { logger.warn("Could not find an Event Factory for the event type '" + type + "'."); } } private Event createESHEvent(final EventFactory eventFactory, final String type, final String payload, final String topic, final String source) { Event eshEvent = null; try { eshEvent = eventFactory.createEvent(type, topic, payload, source); } catch (Exception e) { logger.error("Creation of ESH-Event failed, " + "because one of the registered event factories has thrown an exception: " + e.getMessage(), e); } return eshEvent; } private void dispatchESHEvent(final Set<EventSubscriber> eventSubscribers, final Event event) { for (final EventSubscriber eventSubscriber : eventSubscribers) { try { EventFilter filter = eventSubscriber.getEventFilter(); if (filter == null || filter.apply(event)) { SafeMethodCaller.call(new ActionWithException<Void>() { @Override public Void call() throws Exception { eventSubscriber.receive(event); return null; } }); } } catch (TimeoutException timeoutException) { logger.warn("Dispatching event to subscriber '{}' takes more than {}ms.", eventSubscriber.toString(), SafeMethodCaller.DEFAULT_TIMEOUT); } catch (Throwable t) { logger.error("Dispatching/filtering event for subscriber '" + EventSubscriber.class.getName() + "' failed: " + t.getMessage(), t); } } } private Set<EventSubscriber> getEventSubscribers(String eventType) { Set<EventSubscriber> eventTypeSubscribers = typedEventSubscribers.get(eventType); Set<EventSubscriber> allEventTypeSubscribers = typedEventSubscribers.get(EventSubscriber.ALL_EVENT_TYPES); Set<EventSubscriber> subscribers = new HashSet<EventSubscriber>(); if (eventTypeSubscribers != null) { subscribers.addAll(eventTypeSubscribers); } if (allEventTypeSubscribers != null) { subscribers.addAll(allEventTypeSubscribers); } return subscribers; } @Override public void post(final Event event) throws IllegalArgumentException, IllegalStateException { EventAdmin eventAdmin = this.osgiEventAdmin; assertValidArgument(event); assertValidState(eventAdmin); postAsOSGiEvent(eventAdmin, event); } private void postAsOSGiEvent(final EventAdmin eventAdmin, final Event event) throws IllegalStateException { try { AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { @Override public Void run() throws Exception { Dictionary<String, Object> properties = new Hashtable<String, Object>(3); properties.put("type", event.getType()); properties.put("payload", event.getPayload()); properties.put("topic", event.getTopic()); if (event.getSource() != null) { properties.put("source", event.getSource()); } eventAdmin.postEvent(new org.osgi.service.event.Event("smarthome", properties)); return null; } }); } catch (PrivilegedActionException pae) { Exception e = pae.getException(); throw new IllegalStateException("Cannot post the event via the event bus. Error message: " + e.getMessage(), e); } } private void assertValidArgument(Event event) throws IllegalArgumentException { String errorMsg = "The %s of the 'event' argument must not be null or empty."; Preconditions.checkArgument(event != null, "Argument 'event' must not be null."); Preconditions.checkArgument(event.getType() != null && !event.getType().isEmpty(), String.format(errorMsg, "type")); Preconditions.checkArgument(event.getPayload() != null && !event.getPayload().isEmpty(), String.format(errorMsg, "payload")); Preconditions.checkArgument(event.getTopic() != null && !event.getTopic().isEmpty(), String.format(errorMsg, "topic")); } private void assertValidState(EventAdmin eventAdmin) throws IllegalStateException { Preconditions.checkArgument(eventAdmin != null, "The event bus module is not available!"); } private void addEventSubscriber(EventSubscriber eventSubscriber) { Set<String> subscribedEventTypes = eventSubscriber.getSubscribedEventTypes(); for (String subscribedEventType : subscribedEventTypes) { synchronized (this) { if (!typedEventSubscribers.containsEntry(subscribedEventType, eventSubscriber)) { typedEventSubscribers.put(subscribedEventType, eventSubscriber); } } } } private void removeEventSubscriber(EventSubscriber eventSubscriber) { Set<String> subscribedEventTypes = eventSubscriber.getSubscribedEventTypes(); for (String subscribedEventType : subscribedEventTypes) { typedEventSubscribers.remove(subscribedEventType, eventSubscriber); } } }