/* * Copyright Ericsson AB 2011-2014. All Rights Reserved. * * The contents of this file are subject to the Lesser GNU Public License, * (the "License"), either version 2.1 of the License, or * (at your option) any later version.; you may not use this file except in * compliance with the License. You should have received a copy of the * License along with this software. If not, it can be * retrieved online at https://www.gnu.org/licenses/lgpl.html. Moreover * it could also be requested from Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO * WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. * EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR * OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, * EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE * LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, * YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. * * IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING * WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR * REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR * DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL * DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY * (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED * INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE * OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH * HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * */ package com.ericsson.deviceaccess.spi.event; import com.ericsson.deviceaccess.api.GenericDevice; import com.ericsson.deviceaccess.api.genericdevice.GDEventListener; import static com.ericsson.deviceaccess.api.genericdevice.GDEventListener.DEVICE_ID; import static com.ericsson.deviceaccess.api.genericdevice.GDEventListener.DEVICE_NAME; import static com.ericsson.deviceaccess.api.genericdevice.GDEventListener.DEVICE_PROTOCOL; import static com.ericsson.deviceaccess.api.genericdevice.GDEventListener.DEVICE_STATE; import static com.ericsson.deviceaccess.api.genericdevice.GDEventListener.DEVICE_URN; import static com.ericsson.deviceaccess.api.genericdevice.GDEventListener.GENERICDEVICE_FILTER; import static com.ericsson.deviceaccess.api.genericdevice.GDEventListener.SERVICE_NAME; import com.ericsson.deviceaccess.api.genericdevice.GDEventListener.Type; import java.util.Dictionary; import java.util.HashMap; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.osgi.framework.BundleContext; import org.osgi.framework.Constants; import org.osgi.framework.Filter; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.InvalidSyntaxException; import org.osgi.framework.ServiceEvent; import org.osgi.framework.ServiceListener; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Event manager that handles issuing if events at changes in properties. * Matches events against listeners filter (see {@link GDEventListener} for * details). * */ public class EventManager implements ServiceListener, Runnable, ServiceTrackerCustomizer<GenericDevice, Object> { private static final Logger logger = LoggerFactory.getLogger(EventManager.class); private static final String LISTENER_FILTER = "(" + Constants.OBJECTCLASS + "=" + GDEventListener.class .getName() + ")"; private static final Pattern DELTA_PATTERN = Pattern.compile("\\((([^(]*)__delta)"); private BundleContext context; private final AtomicBoolean running = new AtomicBoolean(false); private final Map<GDEventListener, Filter> listeners = new ConcurrentHashMap<>(); private final BlockingQueue<GenericDeviceEvent> events = new LinkedBlockingQueue<>(); private final Map<String, Object> deltaValues = new HashMap<>(); private ServiceTracker deviceTracker; private final Map<String, GenericDevice> devices = new ConcurrentHashMap<>(); private Thread thread; private final GenericDeviceEvent POISON = new GenericDeviceEvent(null, null, null, null); private Filter ALLOW_ALL = new Filter() { @Override public boolean match(ServiceReference reference) { return true; } @Override public boolean match(Dictionary dictionary) { return true; } @Override public boolean matchCase(Dictionary dictionary) { return true; } @Override public boolean matches(Map<String, ?> map) { return true; } }; public EventManager() { super(); } public void setContext(BundleContext context) { this.context = context; } /** * Thread body that consumes the event queue and issues events to listeners. */ @Override public void run() { startListenGenericDeviceEvents(); createTracker(); GenericDeviceEvent event; while (running.get()) { try { // Wait for a GenericDeviceEvent to be received and forward this to all listeners event = events.take(); } catch (InterruptedException ex) { continue; } //POISON is invalid and because running is set to false this exits. if (isEventInvalid(event)) { continue; } Map<String, Object> matching = new HashMap<>(); if (!event.propertyEvent) { addForChangeEvent(event, matching); } invokeListeners(event, matching); } } /** * Track GenericDevice service registrations (used to only allow events from * registered instances) */ private void createTracker() { deviceTracker = new ServiceTracker(context, GenericDevice.class, this); deviceTracker.open(); } /** * Checks if event is invalid * * @param event * @return is invalid? */ private boolean isEventInvalid(GenericDeviceEvent event) { if (event.serviceId == null || event.deviceId == null) { return true; } return event.properties == null && !event.propertyEvent; } /** * Adds properties needed in state change events * * @param event * @param matching */ private void addForChangeEvent(GenericDeviceEvent event, Map<String, Object> matching) { matching.put(DEVICE_ID, event.deviceId); matching.put(SERVICE_NAME, event.serviceId); matching.put(DEVICE_PROTOCOL, event.device.getProtocol()); matching.put(DEVICE_URN, event.device.getURN()); matching.put(DEVICE_NAME, event.device.getName()); matching.putAll(event.properties); } /** * Invokes listeners that listen this kind of event * * @param event * @param matchingProperties */ private void invokeListeners(GenericDeviceEvent event, Map<String, Object> matchingProperties) { String deviceId = event.deviceId; String serviceName = event.serviceId; listeners.forEach((listener, filter) -> { checkForDeltaProperty(filter, event, matchingProperties); if (event.propertyEvent) { listener.notifyGDPropertyEvent(event.type, deviceId, serviceName, event.propertyId); } else if (filter.matches(matchingProperties)) { System.out.println(event); listener.notifyGDEvent(deviceId, serviceName, event.properties); } }); } /** * Registers EventManager to listen generic device events */ private void startListenGenericDeviceEvents() { try { context.addServiceListener(this, LISTENER_FILTER); // Check if there are already registered listeners context.getServiceReferences(GDEventListener.class, null) .forEach(reference -> serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, reference))); } catch (InvalidSyntaxException e) { logger.warn("Filter format was hardcoded wrong", e); } } /** * If filter and event is for delta property, this then updates it * * @param filter * @param event * @param matchingProperties */ private void checkForDeltaProperty(Filter filter, GenericDeviceEvent event, Map<String, Object> matchingProperties) { if (filter != null) { String deltaProperty = filter.toString(); Matcher matcher = DELTA_PATTERN.matcher(deltaProperty); if (matcher.find()) { deltaProperty = matcher.group(2); // Is this an event update for the delta property? if (event.properties.get(deltaProperty) != null) { updateDelta(matchingProperties, deltaProperty, event, matcher); } } } } /** * Updates delta property * * @param matchingProperties * @param deltaProperty * @param event * @param matcher */ private void updateDelta(Map<String, Object> matchingProperties, String deltaProperty, GenericDeviceEvent event, Matcher matcher) { Object newProperty = matchingProperties.get(deltaProperty); String id = event.deviceId + event.serviceId + deltaProperty; // Any old values saved to calculate delta from? if (deltaValues.containsKey(id)) { Object delta = calculateDelta(newProperty, deltaValues.get(id)); if (delta != null) { String deltaString = matcher.group(1); event.properties.put(deltaString, delta); matchingProperties.put(deltaString, delta); } } deltaValues.put(id, newProperty); } /** * Calculates delta value between new and old values * * @param newPropert * @param oldProperty * @return delta */ private Object calculateDelta(Object newPropert, Object oldProperty) { if (newPropert instanceof Integer) { return Math.abs((Integer) oldProperty - (Integer) newPropert); } else if (newPropert instanceof Float) { return Math.abs(substract((Float) oldProperty, (Float) newPropert)); } return null; } /** * Hack to get around bit errors when doing subtract of floats * * @param a * @param b * @return a - b */ private float substract(float a, float b) { float delta = Math.round(a * 1000) - Math.round(b * 1000); return delta / 1000; } /** * Handle notifications of new/removed GenericDeviceEventListeners * * @param event */ @Override public void serviceChanged(ServiceEvent event) { ServiceReference reference = event.getServiceReference(); GDEventListener listener = (GDEventListener) context.getService(reference); switch (event.getType()) { case ServiceEvent.REGISTERED: Object filter = reference.getProperty(GENERICDEVICE_FILTER); listeners.put(listener, getFilter(filter)); break; case ServiceEvent.MODIFIED: break; case ServiceEvent.UNREGISTERING: listeners.remove(listener); break; } } /** * Gets filter from an object * * @param object * @return filter */ private Filter getFilter(Object object) { if (object instanceof String) { try { return FrameworkUtil.createFilter((String) object); } catch (InvalidSyntaxException e) { throw new IllegalArgumentException("The filter string could not be parsed into a filter", e); } } else if (object instanceof Filter) { return (Filter) object; } else if (object == null) { return ALLOW_ALL; } else { throw new IllegalArgumentException("The filter must be null, string or Filter"); } } /** * Starts the event manager. */ public void start() { synchronized (running) { if (!running.compareAndSet(false, true)) { throw new IllegalStateException("There is thread already running"); } thread = new Thread(this); try { thread.start(); } catch (Throwable e) { logger.warn("Failed to start Event Manager: " + e); running.set(false); thread = null; } } } /** * Shuts the event manager. */ public void shutdown() { synchronized (running) { if (!running.compareAndSet(true, false)) { throw new IllegalStateException("There wasn't thread running to shutdown"); } events.add(POISON); thread = null; if (deviceTracker != null) { deviceTracker.close(); } } } /** * Notify about a changed state. To be called by protocol adaptors. * * @param deviceId * @param serviceId * @param properties */ public void addPropertyEvent(String deviceId, String serviceId, Map<String, Object> properties) { addEvent(deviceId, device -> new GenericDeviceEvent(device, deviceId, serviceId, properties)); } public void addStateEvent(String deviceId, String serviceId, String propertyId, Type type) { addEvent(deviceId, device -> new GenericDeviceEvent(device, deviceId, serviceId, propertyId, type)); } /** * Adds event to be received by listeners from existing devices only * * @param deviceId * @param func */ private void addEvent(String deviceId, Function<GenericDevice, GenericDeviceEvent> func) { if (running.get()) { logger.warn("Tried to notify event on closed event manager, dropping it!"); return; } // Ignore events from devices that are not registered yet GenericDevice device = devices.get(deviceId); if (device != null) { events.add(func.apply(device)); System.out.println(events); } else { logger.warn("There was no device registered with deviceID: " + deviceId); } } @Override public Object addingService(ServiceReference<GenericDevice> reference) { GenericDevice device = context.getService(reference); devices.put(device.getId(), device); // Always generate a state event when a new device is registered Map<String, Object> properties = new HashMap<>(); properties.put(DEVICE_STATE, device.getState()); addPropertyEvent(device.getId(), "DeviceProperties", properties); return device; } @Override public void modifiedService(ServiceReference<GenericDevice> reference, Object service) { } @Override public void removedService(ServiceReference<GenericDevice> reference, Object service) { devices.remove(context.getService(reference).getId()); } /** * Internal class to hold an event */ private class GenericDeviceEvent { public String deviceId; public String serviceId; public Map<String, Object> properties; public boolean propertyEvent; public String propertyId; public Type type; public GenericDevice device; GenericDeviceEvent(GenericDevice device, String deviceId, String serviceId, Map<String, Object> properties) { propertyEvent = false; this.device = device; this.deviceId = deviceId; this.serviceId = serviceId; this.properties = properties; } GenericDeviceEvent(GenericDevice device, String deviceId, String serviceId, String propertyId, Type type) { propertyEvent = true; this.device = device; this.deviceId = deviceId; this.serviceId = serviceId; this.propertyId = propertyId; this.type = type; this.properties = new HashMap<>(); properties.put(DEVICE_ID, deviceId); properties.put(propertyId, new Object()); properties.put(SERVICE_NAME, serviceId); } @Override public String toString() { if (propertyId == null) { return deviceId + " " + serviceId + " " + properties; } return deviceId + " " + serviceId + " " + propertyId + " " + type; } } }