/** * 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.common.osgi; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import org.osgi.framework.BundleContext; 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; /** * The {@link ServiceBinder} class is a utility class to bind an <i>OSGi</i> service and to * inject the bound service into a specified object by calling the annotated method on it. * <p> * This class is an <i>OSGi</i> utility class which can be simply used for injecting a service by an API instead of * using a declarative way. * <p> * The annotated method for binding a service must have the signature {@code public void methodName(<ServiceType>)} and * the annotated method for unbinding a service must have the signature {@code public void methodName(<ServiceType>)} or * {@code public void methodName()}. * <p> * After starting the binder with {@link #open()} the service is tracked and if a service is found and bound, the * specified injection method is called with the service as parameter. If the service disappears again or the method * {@link #close()} is called to stop the binder, the specified injection method is called with {@code null} as * parameter if the unbind method is the same as for the binding, otherwise the released service instance is used as * parameter. * <p> * <b>Usage Example:</b> <code><pre> * ServiceBinder serviceBinder = new ServiceBinder(bundleContext, new MyObject()); * serviceBinder.open(); * serviceBinder.close(); * ... * public class MyObject { * private AnyService anyService; * * @Bind * @Unbind * public void setAnyService(AnyService anyService) { * this.anyService = anyService; * } * } * </pre></code> * * @author Michael Grammling - Initial Contribution */ public class ServiceBinder { @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Bind { } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Unbind { } private ServiceTracker<?, ?> serviceTracker; private Object trackedService; private Logger logger = LoggerFactory.getLogger(ServiceBinder.class); /** * Creates a new instance of this class with the specified parameters. * * @param bundleContext the bundle context to be used for tracking the service (must not be null) * @param targetObject the object into which the service to be injected (must not be null) * * @throws IllegalArgumentException if any of the parameters is null, * or if the annotated methods do not follow the scheme */ public ServiceBinder(final BundleContext bundleContext, final Object targetObject) throws IllegalArgumentException { if (bundleContext == null) { throw new IllegalArgumentException("The bundle context must not be null!"); } if (targetObject == null) { throw new IllegalArgumentException("The target object to be invoked must not be null!"); } final Method bindMethod = findAnnotatedMethod(targetObject, Bind.class); if (bindMethod == null) { throw new IllegalArgumentException("The annotated bind method is missing!"); } Class<?> serviceClazz = determineServiceType(bindMethod, true); final Method unbindMethod = findAnnotatedMethod(targetObject, Unbind.class); Class<?> serviceUnbindClazz = determineServiceType(unbindMethod, false); if ((serviceUnbindClazz != null) && (serviceUnbindClazz != serviceClazz)) { throw new IllegalArgumentException("The unbind method uses a wrong parameter type!"); } this.serviceTracker = new ServiceTracker<>(bundleContext, serviceClazz.getName(), new ServiceTrackerCustomizer<Class<?>, Object>() { @Override public synchronized Object addingService(ServiceReference<Class<?>> reference) { Object service = bundleContext.getService(reference); if ((trackedService == null) && (service != null)) { trackedService = service; injectService(bindMethod, targetObject, service); return service; } return null; } @Override public void modifiedService(ServiceReference<Class<?>> reference, Object service) { } @Override public synchronized void removedService(ServiceReference<Class<?>> reference, Object service) { if (service == trackedService) { trackedService = null; if (bindMethod.equals(unbindMethod)) { injectService(unbindMethod, targetObject, null); } else { injectService(unbindMethod, targetObject, service); } } } }); } /** * Starts tracking and binding the service. */ public void open() { this.serviceTracker.open(); } /** * Stops tracking the service and unbind it. */ public void close() { this.serviceTracker.close(); } private Method findAnnotatedMethod(Object object, Class<? extends Annotation> annotationClass) throws IllegalArgumentException { Method[] methods = object.getClass().getDeclaredMethods(); for (Method method : methods) { if (method.isAnnotationPresent(annotationClass)) { return method; } } return null; } private Class<?> determineServiceType(Method method, boolean required) throws IllegalArgumentException { if (method != null) { Class<?>[] paramTypes = method.getParameterTypes(); if (paramTypes.length == 1) { return paramTypes[0]; } else if (paramTypes.length > 1) { throw new IllegalArgumentException("The annotated method '" + method.getName() + "' must contain one and only one parameter!"); } else if (required) { throw new IllegalArgumentException("The annotated method '" + method.getName() + "' must contain one parameter!"); } } return null; } private void injectService(Method method, Object object, Object service) { if (method != null) { try { if (method.getParameterTypes().length == 1) { method.invoke(object, service); } else { method.invoke(object); } } catch (Exception ex) { logger.error("Error while executing inject method: " + ex.getMessage(), ex); } } } }