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.extender.internal.support;
18  
19  import java.io.UnsupportedEncodingException;
20  import java.net.URL;
21  import java.net.URLDecoder;
22  import java.util.ArrayList;
23  import java.util.Enumeration;
24  import java.util.List;
25  import java.util.Properties;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.osgi.framework.Bundle;
30  import org.osgi.framework.BundleContext;
31  import org.springframework.beans.BeanUtils;
32  import org.springframework.beans.factory.DisposableBean;
33  import org.springframework.context.event.SimpleApplicationEventMulticaster;
34  import org.springframework.core.JdkVersion;
35  import org.springframework.core.task.SimpleAsyncTaskExecutor;
36  import org.springframework.core.task.TaskExecutor;
37  import org.springframework.osgi.context.ConfigurableOsgiBundleApplicationContext;
38  import org.springframework.osgi.context.event.OsgiBundleApplicationContextEventMulticaster;
39  import org.springframework.osgi.context.event.OsgiBundleApplicationContextEventMulticasterAdapter;
40  import org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext;
41  import org.springframework.osgi.extender.OsgiApplicationContextCreator;
42  import org.springframework.osgi.extender.OsgiBeanFactoryPostProcessor;
43  import org.springframework.osgi.extender.OsgiServiceDependencyFactory;
44  import org.springframework.osgi.extender.internal.dependencies.startup.MandatoryImporterDependencyFactory;
45  import org.springframework.osgi.extender.support.DefaultOsgiApplicationContextCreator;
46  import org.springframework.osgi.util.BundleDelegatingClassLoader;
47  import org.springframework.scheduling.timer.TimerTaskExecutor;
48  import org.springframework.util.Assert;
49  import org.springframework.util.ObjectUtils;
50  
51  /**
52   * Configuration class for the extender. Takes care of locating the extender
53   * specific configurations and merging the results with the defaults.
54   * 
55   * <p/> Note that this configuration will consider mandatory options required by
56   * 
57   * @author Costin Leau
58   * 
59   */
60  public class ExtenderConfiguration implements DisposableBean {
61  
62  	/** logger */
63  	private static final Log log = LogFactory.getLog(ExtenderConfiguration.class);
64  
65  	private static final String TASK_EXECUTOR_NAME = "taskExecutor";
66  
67  	private static final String SHUTDOWN_TASK_EXECUTOR_NAME = "shutdownTaskExecutor";
68  
69  	private static final String CONTEXT_CREATOR_NAME = "applicationContextCreator";
70  
71  	private static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "osgiApplicationEventMulticaster";
72  
73  	private static final String PROPERTIES_NAME = "extenderProperties";
74  
75  	private static final String SHUTDOWN_WAIT_KEY = "shutdown.wait.time";
76  
77  	private static final String PROCESS_ANNOTATIONS_KEY = "process.annotations";
78  
79  	private static final String EXTENDER_CFG_LOCATION = "META-INF/spring/extender";
80  
81  	private static final String XML_PATTERN = "*.xml";
82  
83  	private static final String ANNOTATION_DEPENDENCY_FACTORY = "ANNOTATION FACTORY";
84  
85  	//
86  	// defaults
87  	//
88  	private static final long DEFAULT_SHUTDOWN_WAIT = 10 * 1000;
89  	private static final boolean DEFAULT_PROCESS_ANNOTATION = false;
90  
91  	private ConfigurableOsgiBundleApplicationContext extenderConfiguration;
92  
93  	private TaskExecutor taskExecutor, shutdownTaskExecutor;
94  
95  	private boolean isTaskExecutorManagedInternally = false;
96  
97  	private boolean isShutdownTaskExecutorManagedInternally = false;
98  
99  	private boolean isMulticasterManagedInternally = false;
100 
101 	private long shutdownWaitTime;
102 
103 	private boolean processAnnotation;
104 
105 	private OsgiBundleApplicationContextEventMulticaster eventMulticaster;
106 
107 	private boolean forceThreadShutdown;
108 
109 	private OsgiApplicationContextCreator contextCreator;
110 
111 	/** bundle wrapped class loader */
112 	private final ClassLoader classLoader;
113 	/** List of context post processors */
114 	private final List postProcessors = new ArrayList(0);
115 	/** List of service dependency factories */
116 	private final List dependencyFactories = new ArrayList(0);
117 
118 
119 	/**
120 	 * Constructs a new <code>ExtenderConfiguration</code> instance. Locates
121 	 * the extender configuration, creates an application context which will
122 	 * returned the extender items.
123 	 * 
124 	 * @param bundleContext extender OSGi bundle context
125 	 */
126 	public ExtenderConfiguration(BundleContext bundleContext) {
127 		Bundle bundle = bundleContext.getBundle();
128 		Properties properties = new Properties(createDefaultProperties());
129 
130 		Enumeration enm = bundle.findEntries(EXTENDER_CFG_LOCATION, XML_PATTERN, false);
131 
132 		if (enm == null) {
133 			log.info("No custom extender configuration detected; using defaults...");
134 
135 			taskExecutor = createDefaultTaskExecutor();
136 			shutdownTaskExecutor = createDefaultShutdownTaskExecutor();
137 			eventMulticaster = createDefaultEventMulticaster();
138 
139 			isMulticasterManagedInternally = true;
140 			contextCreator = createDefaultApplicationContextCreator();
141 			classLoader = BundleDelegatingClassLoader.createBundleClassLoaderFor(bundle);
142 		}
143 		else {
144 			String[] configs = copyEnumerationToList(enm);
145 
146 			log.info("Detected extender custom configurations at " + ObjectUtils.nullSafeToString(configs));
147 			// create OSGi specific XML context
148 			extenderConfiguration = new OsgiBundleXmlApplicationContext(configs);
149 			extenderConfiguration.setBundleContext(bundleContext);
150 			extenderConfiguration.refresh();
151 
152 			// initialize beans
153 
154 			taskExecutor = extenderConfiguration.containsBean(TASK_EXECUTOR_NAME) ? (TaskExecutor) extenderConfiguration.getBean(
155 				TASK_EXECUTOR_NAME, TaskExecutor.class)
156 					: createDefaultTaskExecutor();
157 
158 			shutdownTaskExecutor = extenderConfiguration.containsBean(SHUTDOWN_TASK_EXECUTOR_NAME) ? (TaskExecutor) extenderConfiguration.getBean(
159 				SHUTDOWN_TASK_EXECUTOR_NAME, TaskExecutor.class)
160 					: createDefaultShutdownTaskExecutor();
161 
162 			eventMulticaster = extenderConfiguration.containsBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME) ? (OsgiBundleApplicationContextEventMulticaster) extenderConfiguration.getBean(
163 				APPLICATION_EVENT_MULTICASTER_BEAN_NAME, OsgiBundleApplicationContextEventMulticaster.class)
164 					: createDefaultEventMulticaster();
165 
166 			contextCreator = extenderConfiguration.containsBean(CONTEXT_CREATOR_NAME) ? (OsgiApplicationContextCreator) extenderConfiguration.getBean(
167 				CONTEXT_CREATOR_NAME, OsgiApplicationContextCreator.class)
168 					: createDefaultApplicationContextCreator();
169 
170 			// get post processors
171 			postProcessors.addAll(extenderConfiguration.getBeansOfType(OsgiBeanFactoryPostProcessor.class).values());
172 
173 			// get dependency factories
174 			dependencyFactories.addAll(extenderConfiguration.getBeansOfType(OsgiServiceDependencyFactory.class).values());
175 
176 			classLoader = extenderConfiguration.getClassLoader();
177 			// extender properties using the defaults as backup
178 			if (extenderConfiguration.containsBean(PROPERTIES_NAME)) {
179 				Properties customProperties = (Properties) extenderConfiguration.getBean(PROPERTIES_NAME,
180 					Properties.class);
181 				Enumeration propertyKey = customProperties.propertyNames();
182 				while (propertyKey.hasMoreElements()) {
183 					String property = (String) propertyKey.nextElement();
184 					properties.setProperty(property, customProperties.getProperty(property));
185 				}
186 			}
187 		}
188 
189 		shutdownWaitTime = getShutdownWaitTime(properties);
190 		processAnnotation = getProcessAnnotations(properties);
191 
192 		// load default dependency factories
193 		addDefaultDependencyFactories();
194 	}
195 
196 	/**
197 	 * {@inheritDoc}
198 	 * 
199 	 * Cleanup the configuration items.
200 	 */
201 	public void destroy() {
202 
203 		if (isMulticasterManagedInternally) {
204 			eventMulticaster.removeAllListeners();
205 			eventMulticaster = null;
206 		}
207 
208 		if (extenderConfiguration != null) {
209 			extenderConfiguration.close();
210 			extenderConfiguration = null;
211 		}
212 
213 		// postpone the task executor shutdown
214 		if (forceThreadShutdown) {
215 
216 			if (isTaskExecutorManagedInternally) {
217 				log.warn("Forcing the (internally created) taskExecutor to stop...");
218 				ThreadGroup th = ((SimpleAsyncTaskExecutor) taskExecutor).getThreadGroup();
219 				if (!th.isDestroyed()) {
220 					// ask the threads nicely to stop
221 					th.interrupt();
222 				}
223 			}
224 			taskExecutor = null;
225 		}
226 
227 		if (isShutdownTaskExecutorManagedInternally) {
228 			try {
229 				((DisposableBean) shutdownTaskExecutor).destroy();
230 			}
231 			catch (Exception ex) {
232 				log.debug("Received exception while shutting down shutdown task executor", ex);
233 			}
234 			shutdownTaskExecutor = null;
235 		}
236 	}
237 
238 	/**
239 	 * Copies the URLs returned by the given enumeration and returns them as an
240 	 * array of Strings for consumption by the application context.
241 	 * 
242 	 * @param enm
243 	 * @return
244 	 */
245 	private String[] copyEnumerationToList(Enumeration enm) {
246 		List urls = new ArrayList(4);
247 		while (enm != null && enm.hasMoreElements()) {
248 			URL configURL = (URL) enm.nextElement();
249 			String configURLAsString = configURL.toExternalForm();
250 			try {
251 				urls.add(URLDecoder.decode(configURLAsString, "UTF8"));
252 			}
253 			catch (UnsupportedEncodingException uee) {
254 				log.warn("UTF8 encoding not supported, using the platform default");
255 				urls.add(URLDecoder.decode(configURLAsString));
256 			}
257 		}
258 
259 		return (String[]) urls.toArray(new String[urls.size()]);
260 	}
261 
262 	private Properties createDefaultProperties() {
263 		Properties properties = new Properties();
264 		properties.setProperty(SHUTDOWN_WAIT_KEY, "" + DEFAULT_SHUTDOWN_WAIT);
265 		properties.setProperty(PROCESS_ANNOTATIONS_KEY, "" + DEFAULT_PROCESS_ANNOTATION);
266 
267 		return properties;
268 	}
269 
270 	private void addDefaultDependencyFactories() {
271 		boolean debug = log.isDebugEnabled();
272 
273 		// default JDK 1.4 processor
274 		dependencyFactories.add(0, new MandatoryImporterDependencyFactory());
275 
276 		// load through reflection the processor if running on JDK 1.5 and annotation processing is enabled
277 		if (processAnnotation) {
278 			if (JdkVersion.isAtLeastJava15()) {
279 
280 				Class annotationProcessor = null;
281 				try {
282 					annotationProcessor = Class.forName(ANNOTATION_DEPENDENCY_FACTORY, false,
283 						ExtenderConfiguration.class.getClassLoader());
284 				}
285 				catch (ClassNotFoundException cnfe) {
286 					log.warn("Spring-DM annotation package not found, annotation processing disabled.", cnfe);
287 					return;
288 				}
289 				Object processor = BeanUtils.instantiateClass(annotationProcessor);
290 				Assert.isInstanceOf(OsgiServiceDependencyFactory.class, processor);
291 				dependencyFactories.add(1, (OsgiServiceDependencyFactory) processor);
292 				if (debug)
293 					log.debug("Succesfully loaded annotation dependency processor [" + ANNOTATION_DEPENDENCY_FACTORY
294 							+ "]");
295 			}
296 			else if (debug)
297 				log.debug("JDK 5 not available [" + ANNOTATION_DEPENDENCY_FACTORY + "] not loaded");
298 		}
299 		else {
300 			if (debug) {
301 				log.debug("Annotation processing disabled; [" + ANNOTATION_DEPENDENCY_FACTORY + "] not loaded");
302 			}
303 		}
304 
305 	}
306 
307 	private TaskExecutor createDefaultTaskExecutor() {
308 		// create thread-pool for starting contexts
309 		ThreadGroup threadGroup = new ThreadGroup("spring-osgi-extender[" + ObjectUtils.getIdentityHexString(this)
310 				+ "]-threads");
311 		threadGroup.setDaemon(false);
312 
313 		SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
314 		taskExecutor.setThreadGroup(threadGroup);
315 		taskExecutor.setThreadNamePrefix("SpringOsgiExtenderThread-");
316 
317 		isTaskExecutorManagedInternally = true;
318 
319 		return taskExecutor;
320 	}
321 
322 	private TaskExecutor createDefaultShutdownTaskExecutor() {
323 		TimerTaskExecutor taskExecutor = new TimerTaskExecutor();
324 		taskExecutor.afterPropertiesSet();
325 		isShutdownTaskExecutorManagedInternally = true;
326 		return taskExecutor;
327 	}
328 
329 	private OsgiBundleApplicationContextEventMulticaster createDefaultEventMulticaster() {
330 		return new OsgiBundleApplicationContextEventMulticasterAdapter(new SimpleApplicationEventMulticaster());
331 	}
332 
333 	private OsgiApplicationContextCreator createDefaultApplicationContextCreator() {
334 		return new DefaultOsgiApplicationContextCreator();
335 	}
336 
337 	private long getShutdownWaitTime(Properties properties) {
338 		return Long.parseLong(properties.getProperty(SHUTDOWN_WAIT_KEY));
339 	}
340 
341 	private boolean getProcessAnnotations(Properties properties) {
342 		return Boolean.valueOf(properties.getProperty(PROCESS_ANNOTATIONS_KEY)).booleanValue();
343 	}
344 
345 	/**
346 	 * Returns the taskExecutor.
347 	 * 
348 	 * @return Returns the taskExecutor
349 	 */
350 	public TaskExecutor getTaskExecutor() {
351 		return taskExecutor;
352 	}
353 
354 	/**
355 	 * Returns the shutdown task executor.
356 	 * 
357 	 * @return Returns the shutdown task executor
358 	 */
359 	public TaskExecutor getShutdownTaskExecutor() {
360 		return shutdownTaskExecutor;
361 	}
362 
363 	/**
364 	 * Returns the shutdownWaitTime.
365 	 * 
366 	 * @return Returns the shutdownWaitTime
367 	 */
368 	public long getShutdownWaitTime() {
369 		return shutdownWaitTime;
370 	}
371 
372 	/**
373 	 * Indicates if the process annotation is enabled or not.
374 	 * 
375 	 * @return Returns true if the annotation should be processed or not
376 	 * otherwise.
377 	 */
378 	public boolean shouldProcessAnnotation() {
379 		return processAnnotation;
380 	}
381 
382 	/**
383 	 * Returns the eventMulticaster.
384 	 * 
385 	 * @return Returns the eventMulticaster
386 	 */
387 	public OsgiBundleApplicationContextEventMulticaster getEventMulticaster() {
388 		return eventMulticaster;
389 	}
390 
391 	/**
392 	 * Sets the flag to force the taskExtender to close up in case of runaway
393 	 * threads - this applies *only* if the taskExecutor has been created
394 	 * internally.
395 	 * 
396 	 * <p/> The flag will cause a best attempt to shutdown the threads.
397 	 * 
398 	 * @param forceThreadShutdown The forceThreadShutdown to set.
399 	 */
400 	public void setForceThreadShutdown(boolean forceThreadShutdown) {
401 		this.forceThreadShutdown = forceThreadShutdown;
402 	}
403 
404 	/**
405 	 * Returns the contextCreator.
406 	 * 
407 	 * @return Returns the contextCreator
408 	 */
409 	public OsgiApplicationContextCreator getContextCreator() {
410 		return contextCreator;
411 	}
412 
413 	/**
414 	 * Returns the postProcessors.
415 	 * 
416 	 * @return Returns the postProcessors
417 	 */
418 	public List getPostProcessors() {
419 		return postProcessors;
420 	}
421 
422 	/**
423 	 * Returns the class loader wrapped around the extender bundle.
424 	 * 
425 	 * @return extender bundle class loader
426 	 */
427 	public ClassLoader getClassLoader() {
428 		return classLoader;
429 	}
430 
431 	/**
432 	 * Returns the dependencies factories declared by the extender
433 	 * configuration. The list automatically contains the default listeners
434 	 * (such as the annotation one).
435 	 * 
436 	 * @return list of dependency factories
437 	 */
438 	public List getDependencyFactories() {
439 		return dependencyFactories;
440 	}
441 }