View Javadoc

1   /*
2    * Copyright 2006-2008 the original author or authors.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.osgi.config;
18  
19  import java.lang.reflect.Method;
20  import java.util.Map;
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.osgi.framework.ServiceReference;
25  import org.springframework.beans.BeansException;
26  import org.springframework.beans.factory.BeanFactory;
27  import org.springframework.beans.factory.BeanFactoryAware;
28  import org.springframework.beans.factory.InitializingBean;
29  import org.springframework.osgi.service.importer.ImportedOsgiServiceProxy;
30  import org.springframework.osgi.service.importer.OsgiServiceLifecycleListener;
31  import org.springframework.osgi.util.internal.ReflectionUtils;
32  import org.springframework.util.Assert;
33  import org.springframework.util.StringUtils;
34  
35  /**
36   * OsgiServiceLifecycleListener wrapper for custom beans, useful when custom
37   * methods are being used.
38   * 
39   * <p/> <strong>Note:</strong> To support cyclic injection, this adapter does
40   * dependency lookup for the actual listener.
41   * 
42   * @author Costin Leau
43   */
44  class OsgiServiceLifecycleListenerAdapter implements OsgiServiceLifecycleListener, InitializingBean, BeanFactoryAware {
45  
46  	private static final Log log = LogFactory.getLog(OsgiServiceLifecycleListenerAdapter.class);
47  
48  	/**
49  	 * Map of methods keyed by the first parameter which indicates the service
50  	 * type expected.
51  	 */
52  	private Map bindMethods, unbindMethods;
53  
54  	/**
55  	 * anyName(ServiceReference reference) method signature.
56  	 */
57  	private Method bindReference, unbindReference;
58  
59  	private String bindMethod, unbindMethod;
60  
61  	/** does the target implement the listener interface */
62  	private boolean isLifecycleListener;
63  
64  	/** target bean factory */
65  	private BeanFactory beanFactory;
66  
67  	/** used when dealing with a cycle */
68  	private String targetBeanName;
69  
70  	/** target object (can be null at first when dealing with a cycle */
71  	private Object target;
72  
73  	/** init flag */
74  	private boolean initialized;
75  
76  
77  	public void afterPropertiesSet() {
78  		Assert.notNull(beanFactory);
79  		Assert.isTrue(target != null || StringUtils.hasText(targetBeanName),
80  			"one of 'target' or 'targetBeanName' properties has to be set");
81  
82  		if (target != null)
83  			initialized = true;
84  
85  		// do validation (on the target type)
86  		initialize();
87  		// postpone target initialization until one of bind/unbind method is called
88  	}
89  
90  	private void retrieveTarget() {
91  		target = beanFactory.getBean(targetBeanName);
92  		initialized = true;
93  	}
94  
95  	/**
96  	 * Initialise adapter. Determine custom methods and do validation.
97  	 */
98  	private void initialize() {
99  
100 		Class clazz = (target == null ? beanFactory.getType(targetBeanName) : target.getClass());
101 
102 		isLifecycleListener = OsgiServiceLifecycleListener.class.isAssignableFrom(clazz);
103 		if (isLifecycleListener)
104 			if (log.isDebugEnabled())
105 				log.debug(clazz.getName() + " is a lifecycle listener");
106 
107 		bindMethods = CustomListenerAdapterUtils.determineCustomMethods(clazz, bindMethod);
108 		unbindMethods = CustomListenerAdapterUtils.determineCustomMethods(clazz, unbindMethod);
109 
110 		if (StringUtils.hasText(bindMethod)) {
111 			// determine methods using ServiceReference signature
112 			bindReference = org.springframework.util.ReflectionUtils.findMethod(clazz, bindMethod,
113 				new Class[] { ServiceReference.class });
114 
115 			if (bindReference != null)
116 				org.springframework.util.ReflectionUtils.makeAccessible(bindReference);
117 		}
118 		if (StringUtils.hasText(unbindMethod)) {
119 			unbindReference = org.springframework.util.ReflectionUtils.findMethod(clazz, unbindMethod,
120 				new Class[] { ServiceReference.class });
121 
122 			if (unbindReference != null)
123 				org.springframework.util.ReflectionUtils.makeAccessible(unbindReference);
124 		}
125 
126 		if (!isLifecycleListener
127 				&& (bindMethods.isEmpty() && unbindMethods.isEmpty() && bindReference == null && unbindReference == null))
128 			throw new IllegalArgumentException("target object needs to implement "
129 					+ OsgiServiceLifecycleListener.class.getName()
130 					+ " or custom bind/unbind methods have to be specified");
131 	}
132 
133 	/**
134 	 * Invoke method with signature <code>bla(ServiceReference ref)</code>.
135 	 * 
136 	 * @param target
137 	 * @param method
138 	 * @param service
139 	 */
140 	private void invokeCustomServiceReferenceMethod(Object target, Method method, Object service) {
141 		if (method != null) {
142 			boolean trace = log.isTraceEnabled();
143 
144 			// get the service reference
145 			// find the compatible types (accept null service)
146 			if (trace)
147 				log.trace("invoking listener custom method " + method);
148 
149 			ServiceReference ref = (service != null ? ((ImportedOsgiServiceProxy) service).getServiceReference() : null);
150 
151 			try {
152 				ReflectionUtils.invokeMethod(method, target, new Object[] { ref });
153 			}
154 			// make sure to log exceptions and continue with the
155 			// rest of
156 			// the listeners
157 			catch (Exception ex) {
158 				Exception cause = ReflectionUtils.getInvocationException(ex);
159 				log.warn("custom method [" + method + "] threw exception when passing service reference ["
160 						+ (service != null ? service.getClass().getName() : null) + "]", cause);
161 			}
162 		}
163 	}
164 
165 	public void bind(Object service, Map properties) throws Exception {
166 		boolean trace = log.isTraceEnabled();
167 		if (trace)
168 			log.trace("invoking bind method for service " + service + " with props=" + properties);
169 
170 		if (!initialized)
171 			retrieveTarget();
172 
173 		// first call interface method (if it exists)
174 		if (isLifecycleListener) {
175 			if (trace)
176 				log.trace("invoking listener interface methods");
177 
178 			try {
179 				((OsgiServiceLifecycleListener) target).bind(service, properties);
180 			}
181 			catch (Exception ex) {
182 				log.warn("standard bind method on [" + target.getClass().getName() + "] threw exception", ex);
183 			}
184 		}
185 
186 		CustomListenerAdapterUtils.invokeCustomMethods(target, bindMethods, service, properties);
187 		invokeCustomServiceReferenceMethod(target, bindReference, service);
188 	}
189 
190 	public void unbind(Object service, Map properties) throws Exception {
191 		boolean trace = log.isTraceEnabled();
192 		if (!initialized)
193 			retrieveTarget();
194 
195 		if (trace)
196 			log.trace("invoking unbind method for service " + service + " with props=" + properties);
197 
198 		// first call interface method (if it exists)
199 		if (isLifecycleListener) {
200 			if (trace)
201 				log.trace("invoking listener interface methods");
202 			try {
203 				((OsgiServiceLifecycleListener) target).unbind(service, properties);
204 			}
205 			catch (Exception ex) {
206 				log.warn("standard unbind method on [" + target.getClass().getName() + "] threw exception", ex);
207 			}
208 		}
209 
210 		CustomListenerAdapterUtils.invokeCustomMethods(target, unbindMethods, service, properties);
211 		invokeCustomServiceReferenceMethod(target, unbindReference, service);
212 	}
213 
214 	/**
215 	 * @param bindMethod The bindMethod to set.
216 	 */
217 	public void setBindMethod(String bindMethod) {
218 		this.bindMethod = bindMethod;
219 	}
220 
221 	/**
222 	 * @param unbindMethod The unbindMethod to set.
223 	 */
224 	public void setUnbindMethod(String unbindMethod) {
225 		this.unbindMethod = unbindMethod;
226 	}
227 
228 	public void setTarget(Object target) {
229 		this.target = target;
230 	}
231 
232 	public void setTargetBeanName(String targetName) {
233 		this.targetBeanName = targetName;
234 	}
235 
236 	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
237 		this.beanFactory = beanFactory;
238 	}
239 }