/****************************************************************************** * Copyright (c) 2006, 2010 VMware Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html and the Apache License v2.0 * is available at http://www.opensource.org/licenses/apache2.0.php. * You may elect to redistribute this code under either of these licenses. * * Contributors: * VMware Inc. *****************************************************************************/ package org.eclipse.gemini.blueprint.config.internal.adapter; import java.lang.reflect.Method; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.gemini.blueprint.context.support.internal.security.SecurityUtils; import org.eclipse.gemini.blueprint.service.importer.ImportedOsgiServiceProxy; import org.eclipse.gemini.blueprint.service.importer.OsgiServiceLifecycleListener; import org.eclipse.gemini.blueprint.util.internal.ReflectionUtils; import org.osgi.framework.ServiceReference; import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; /** * OsgiServiceLifecycleListener wrapper for custom beans, useful when custom methods are being used. * * <p/> <strong>Note:</strong> To support cyclic injection, this adapter does dependency lookup for the actual listener. * * @author Costin Leau */ public class OsgiServiceLifecycleListenerAdapter implements OsgiServiceLifecycleListener, InitializingBean, BeanFactoryAware { private static final Log log = LogFactory.getLog(OsgiServiceLifecycleListenerAdapter.class); /** * Map of methods keyed by the first parameter which indicates the service type expected. */ private Map<Class<?>, List<Method>> bindMethods, unbindMethods; private boolean isBlueprintCompliant = false; /** * anyName(ServiceReference reference) method signature. */ private Method bindReference, unbindReference; private String bindMethod, unbindMethod; /** does the target implement the listener interface */ private boolean isLifecycleListener; /** target bean factory */ private BeanFactory beanFactory; /** used when dealing with a cycle */ private String targetBeanName; /** target object (can be null at first when dealing with a cycle */ private Object target; /** init flag */ private boolean initialized; public void afterPropertiesSet() { Assert.notNull(beanFactory); Assert.isTrue(target != null || StringUtils.hasText(targetBeanName), "one of 'target' or 'targetBeanName' properties has to be set"); if (target != null) initialized = true; // do validation (on the target type) initialize(); // postpone target initialization until one of bind/unbind method is called } private void retrieveTarget() { target = beanFactory.getBean(targetBeanName); initialized = true; } /** * Initialise adapter. Determine custom methods and do validation. */ private void initialize() { Class<?> clazz = (target == null ? beanFactory.getType(targetBeanName) : target.getClass()); Assert.notNull(clazz, "listener " + targetBeanName + " class type cannot be determined"); isLifecycleListener = OsgiServiceLifecycleListener.class.isAssignableFrom(clazz); if (isLifecycleListener) if (log.isDebugEnabled()) log.debug(clazz.getName() + " is a lifecycle listener"); bindMethods = CustomListenerAdapterUtils.determineCustomMethods(clazz, bindMethod, isBlueprintCompliant); boolean isSecurityEnabled = System.getSecurityManager() != null; final Class<?> clz = clazz; // determine methods using ServiceReference signature if (StringUtils.hasText(bindMethod)) { if (isSecurityEnabled) { bindReference = AccessController.doPrivileged(new PrivilegedAction<Method>() { public Method run() { return findServiceReferenceMethod(clz, bindMethod); } }); } else { bindReference = findServiceReferenceMethod(clz, bindMethod); } if (bindMethods.isEmpty()) { String beanName = (target == null ? "" : " bean [" + targetBeanName + "] ;"); throw new IllegalArgumentException("Custom bind method [" + bindMethod + "] not found on " + beanName + "class " + clazz); } } unbindMethods = CustomListenerAdapterUtils.determineCustomMethods(clazz, unbindMethod, isBlueprintCompliant); if (StringUtils.hasText(unbindMethod)) { if (isSecurityEnabled) { unbindReference = AccessController.doPrivileged(new PrivilegedAction<Method>() { public Method run() { return findServiceReferenceMethod(clz, unbindMethod); } }); } else { unbindReference = findServiceReferenceMethod(clz, unbindMethod); } if (unbindMethods.isEmpty()) { String beanName = (target == null ? "" : " bean [" + targetBeanName + "] ;"); throw new IllegalArgumentException("Custom unbind method [" + unbindMethod + "] not found on " + beanName + "class " + clazz); } } if (!isLifecycleListener && (bindMethods.isEmpty() && unbindMethods.isEmpty() && bindReference == null && unbindReference == null)) throw new IllegalArgumentException("target object needs to implement " + OsgiServiceLifecycleListener.class.getName() + " or custom bind/unbind methods have to be specified"); if (log.isTraceEnabled()) { StringBuilder builder = new StringBuilder(); builder.append("Discovered bind methods="); builder.append(bindMethods.values()); builder.append(", bind ServiceReference="); builder.append(bindReference); builder.append("\nunbind methods="); builder.append(unbindMethods.values()); builder.append(", unbind ServiceReference="); builder.append(unbindReference); log.trace(builder.toString()); } } private Method findServiceReferenceMethod(Class<?> clazz, String methodName) { Method method = org.springframework.util.ReflectionUtils.findMethod(clazz, methodName, new Class[] { ServiceReference.class }); if (method != null) { org.springframework.util.ReflectionUtils.makeAccessible(method); } return method; } /** * Invoke method with signature <code>bla(ServiceReference ref)</code>. * * @param target * @param method * @param service */ private void invokeCustomServiceReferenceMethod(Object target, Method method, Object service) { if (method != null) { boolean trace = log.isTraceEnabled(); // get the service reference // find the compatible types (accept null service) if (trace) log.trace("invoking listener custom method " + method); ServiceReference ref = (service != null ? ((ImportedOsgiServiceProxy) service).getServiceReference() : null); try { ReflectionUtils.invokeMethod(method, target, new Object[] { ref }); } // make sure to log exceptions and continue with the // rest of the listeners catch (Exception ex) { Exception cause = ReflectionUtils.getInvocationException(ex); log.warn("custom method [" + method + "] threw exception when passing service [" + ObjectUtils.identityToString(service) + "]", cause); } } } public void bind(final Object service, final Map properties) throws Exception { boolean trace = log.isTraceEnabled(); if (trace) log.trace("Invoking bind method for service " + ObjectUtils.identityToString(service) + " with props=" + properties); if (!initialized) retrieveTarget(); boolean isSecurityEnabled = (System.getSecurityManager() != null); AccessControlContext acc = null; if (isSecurityEnabled) { acc = SecurityUtils.getAccFrom(beanFactory); } // first call interface method (if it exists) if (isLifecycleListener) { if (trace) log.trace("Invoking listener interface methods"); try { if (isSecurityEnabled) { AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { public Object run() throws Exception { ((OsgiServiceLifecycleListener) target).bind(service, properties); return null; } }, acc); } else { ((OsgiServiceLifecycleListener) target).bind(service, properties); } } catch (Exception ex) { if (ex instanceof PrivilegedActionException) { ex = ((PrivilegedActionException) ex).getException(); } log.warn("standard bind method on [" + target.getClass().getName() + "] threw exception", ex); } } if (isSecurityEnabled) { AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { CustomListenerAdapterUtils.invokeCustomMethods(target, bindMethods, service, properties); invokeCustomServiceReferenceMethod(target, bindReference, service); return null; } }, acc); } else { CustomListenerAdapterUtils.invokeCustomMethods(target, bindMethods, service, properties); invokeCustomServiceReferenceMethod(target, bindReference, service); } } public void unbind(final Object service, final Map properties) throws Exception { boolean trace = log.isTraceEnabled(); if (!initialized) retrieveTarget(); if (trace) log.trace("Invoking unbind method for service " + ObjectUtils.identityToString(service) + " with props=" + properties); boolean isSecurityEnabled = (System.getSecurityManager() != null); AccessControlContext acc = null; if (isSecurityEnabled) { acc = SecurityUtils.getAccFrom(beanFactory); } // first call interface method (if it exists) if (isLifecycleListener) { if (trace) log.trace("Invoking listener interface methods"); try { if (isSecurityEnabled) { AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { public Object run() throws Exception { ((OsgiServiceLifecycleListener) target).unbind(service, properties); return null; } }, acc); } else { ((OsgiServiceLifecycleListener) target).unbind(service, properties); } } catch (Exception ex) { log.warn("Standard unbind method on [" + target.getClass().getName() + "] threw exception", ex); } } if (isSecurityEnabled) { AccessController.doPrivileged(new PrivilegedAction<Object>() { public Object run() { CustomListenerAdapterUtils.invokeCustomMethods(target, unbindMethods, service, properties); invokeCustomServiceReferenceMethod(target, unbindReference, service); return null; } }, acc); } else { CustomListenerAdapterUtils.invokeCustomMethods(target, unbindMethods, service, properties); invokeCustomServiceReferenceMethod(target, unbindReference, service); } } /** * @param bindMethod The bindMethod to set. */ public void setBindMethod(String bindMethod) { this.bindMethod = bindMethod; } /** * @param unbindMethod The unbindMethod to set. */ public void setUnbindMethod(String unbindMethod) { this.unbindMethod = unbindMethod; } public void setTarget(Object target) { this.target = target; } public void setTargetBeanName(String targetName) { this.targetBeanName = targetName; } public void setBlueprintCompliant(boolean compliant) { this.isBlueprintCompliant = compliant; } public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } }