/****************************************************************************** | |
* 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 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.service.importer.ServiceReferenceProxy; | |
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; | |
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; | |
/** | |
* 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); | |
// Never expose the internal proxies; they cannot be used to safely obtain service instances. | |
if (ref != null) { | |
ref = ((ServiceReferenceProxy) ref).getTargetServiceReference(); | |
} | |
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; | |
} | |
} |