1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
148 extenderConfiguration = new OsgiBundleXmlApplicationContext(configs);
149 extenderConfiguration.setBundleContext(bundleContext);
150 extenderConfiguration.refresh();
151
152
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
171 postProcessors.addAll(extenderConfiguration.getBeansOfType(OsgiBeanFactoryPostProcessor.class).values());
172
173
174 dependencyFactories.addAll(extenderConfiguration.getBeansOfType(OsgiServiceDependencyFactory.class).values());
175
176 classLoader = extenderConfiguration.getClassLoader();
177
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
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
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
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
274 dependencyFactories.add(0, new MandatoryImporterDependencyFactory());
275
276
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
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 }