| /****************************************************************************** |
| * 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.extender.internal.support; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| import org.eclipse.gemini.blueprint.context.ConfigurableOsgiBundleApplicationContext; |
| import org.eclipse.gemini.blueprint.context.event.OsgiBundleApplicationContextEventMulticaster; |
| import org.eclipse.gemini.blueprint.context.event.OsgiBundleApplicationContextEventMulticasterAdapter; |
| import org.eclipse.gemini.blueprint.context.event.OsgiBundleApplicationContextListener; |
| import org.eclipse.gemini.blueprint.context.support.OsgiBundleXmlApplicationContext; |
| import org.eclipse.gemini.blueprint.extender.OsgiApplicationContextCreator; |
| import org.eclipse.gemini.blueprint.extender.OsgiBeanFactoryPostProcessor; |
| import org.eclipse.gemini.blueprint.extender.OsgiServiceDependencyFactory; |
| import org.eclipse.gemini.blueprint.extender.internal.dependencies.startup.MandatoryImporterDependencyFactory; |
| import org.eclipse.gemini.blueprint.extender.support.internal.ConfigUtils; |
| import org.eclipse.gemini.blueprint.util.BundleDelegatingClassLoader; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleActivator; |
| import org.osgi.framework.BundleContext; |
| import org.springframework.beans.BeanUtils; |
| import org.springframework.beans.factory.DisposableBean; |
| import org.springframework.context.event.SimpleApplicationEventMulticaster; |
| import org.springframework.core.task.TaskExecutor; |
| import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; |
| import org.springframework.util.Assert; |
| import org.springframework.util.ObjectUtils; |
| |
| import java.io.UnsupportedEncodingException; |
| import java.net.URL; |
| import java.net.URLDecoder; |
| import java.util.*; |
| |
| /** |
| * Configuration class for the extender. Takes care of locating the extender specific configurations and merging the |
| * results with the defaults. |
| * |
| * @author Costin Leau |
| */ |
| public class ExtenderConfiguration implements BundleActivator { |
| |
| /** logger */ |
| protected final Log log = LogFactory.getLog(getClass()); |
| |
| private static final String TASK_EXECUTOR_NAME = "taskExecutor"; |
| |
| private static final String SHUTDOWN_TASK_EXECUTOR_NAME = "shutdownTaskExecutor"; |
| |
| private static final String CONTEXT_CREATOR_NAME = "applicationContextCreator"; |
| |
| private static final String APPLICATION_EVENT_MULTICASTER_BEAN_NAME = "osgiApplicationEventMulticaster"; |
| |
| private static final String CONTEXT_LISTENER_NAME = "osgiApplicationContextListener"; |
| |
| private static final String PROPERTIES_NAME = "extenderProperties"; |
| |
| private static final String SHUTDOWN_ASYNCHRONOUS_KEY = "shutdown.asynchronously"; |
| |
| private static final String SHUTDOWN_WAIT_KEY = "shutdown.wait.time"; |
| |
| private static final String PROCESS_ANNOTATIONS_KEY = "process.annotations"; |
| |
| private static final String WAIT_FOR_DEPS_TIMEOUT_KEY = "dependencies.wait.time"; |
| |
| private static final String EXTENDER_CFG_LOCATION = "META-INF/spring/extender"; |
| |
| private static final String XML_PATTERN = "*.xml"; |
| |
| private static final String ANNOTATION_DEPENDENCY_FACTORY = |
| "org.eclipse.gemini.blueprint.extensions.annotation.ServiceReferenceDependencyBeanFactoryPostProcessor"; |
| |
| /** annotation processing system property (kept for backwards compatibility) */ |
| private static final String AUTO_ANNOTATION_PROCESSING = |
| "org.eclipse.gemini.blueprint.extender.annotation.auto.processing"; |
| |
| // |
| // defaults |
| // |
| |
| // default dependency wait time (in milliseconds) |
| private static final long DEFAULT_DEP_WAIT = ConfigUtils.DIRECTIVE_TIMEOUT_DEFAULT * 1000; |
| private static final boolean DEFAULT_NS_BUNDLE_STATE = true; |
| private static final boolean DEFAULT_SHUTDOWN_ASYNCHRONOUS = true; |
| private static final long DEFAULT_SHUTDOWN_WAIT = 10 * 1000; |
| |
| private static final boolean DEFAULT_PROCESS_ANNOTATION = true; |
| |
| private ConfigurableOsgiBundleApplicationContext extenderConfiguration; |
| |
| private TaskExecutor taskExecutor, shutdownTaskExecutor; |
| |
| private boolean isTaskExecutorManagedInternally; |
| |
| private boolean isShutdownTaskExecutorManagedInternally; |
| |
| private boolean isMulticasterManagedInternally; |
| |
| private long shutdownWaitTime, dependencyWaitTime; |
| |
| private boolean shutdownAsynchronously; |
| |
| private boolean processAnnotation, nsBundledResolved; |
| |
| private OsgiBundleApplicationContextEventMulticaster eventMulticaster; |
| |
| private OsgiBundleApplicationContextListener contextEventListener; |
| |
| private boolean forceThreadShutdown; |
| |
| private OsgiApplicationContextCreator contextCreator = null; |
| |
| /** bundle wrapped class loader */ |
| private ClassLoader classLoader; |
| /** List of context post processors */ |
| private final List<OsgiBeanFactoryPostProcessor> postProcessors = |
| Collections.synchronizedList(new ArrayList<OsgiBeanFactoryPostProcessor>(0)); |
| /** List of service dependency factories */ |
| private final List<OsgiServiceDependencyFactory> dependencyFactories = |
| Collections.synchronizedList(new ArrayList<OsgiServiceDependencyFactory>(0)); |
| |
| // fields reading/writing lock |
| private final Object lock = new Object(); |
| |
| /** |
| * Constructs a new <code>ExtenderConfiguration</code> instance. Locates the extender configuration, creates an |
| * application context which will returned the extender items. |
| * |
| * @param extenderBundleContext extender OSGi bundle context |
| */ |
| public void start(BundleContext extenderBundleContext) { |
| Bundle bundle = extenderBundleContext.getBundle(); |
| Properties properties = new Properties(createDefaultProperties()); |
| |
| Enumeration<?> enm = bundle.findEntries(EXTENDER_CFG_LOCATION, XML_PATTERN, false); |
| |
| if (enm == null) { |
| log.info("No custom extender configuration detected; using defaults..."); |
| synchronized (lock) { |
| taskExecutor = createDefaultTaskExecutor(); |
| shutdownTaskExecutor = createDefaultShutdownTaskExecutor(); |
| eventMulticaster = createDefaultEventMulticaster(); |
| contextEventListener = createDefaultApplicationContextListener(); |
| } |
| classLoader = BundleDelegatingClassLoader.createBundleClassLoaderFor(bundle); |
| } else { |
| String[] configs = copyEnumerationToList(enm); |
| |
| log.info("Detected extender custom configurations at " + ObjectUtils.nullSafeToString(configs)); |
| // create OSGi specific XML context |
| ConfigurableOsgiBundleApplicationContext extenderAppCtx = new OsgiBundleXmlApplicationContext(configs); |
| extenderAppCtx.setBundleContext(extenderBundleContext); |
| extenderAppCtx.refresh(); |
| |
| synchronized (lock) { |
| extenderConfiguration = extenderAppCtx; |
| // initialize beans |
| taskExecutor = |
| extenderConfiguration.containsBean(TASK_EXECUTOR_NAME) ? extenderConfiguration |
| .getBean(TASK_EXECUTOR_NAME, TaskExecutor.class) : createDefaultTaskExecutor(); |
| |
| shutdownTaskExecutor = |
| extenderConfiguration.containsBean(SHUTDOWN_TASK_EXECUTOR_NAME) ? extenderConfiguration |
| .getBean(SHUTDOWN_TASK_EXECUTOR_NAME, TaskExecutor.class) |
| : createDefaultShutdownTaskExecutor(); |
| |
| eventMulticaster = |
| extenderConfiguration.containsBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME) ? extenderConfiguration |
| .getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, |
| OsgiBundleApplicationContextEventMulticaster.class) |
| : createDefaultEventMulticaster(); |
| |
| contextCreator = |
| extenderConfiguration.containsBean(CONTEXT_CREATOR_NAME) ? extenderConfiguration |
| .getBean(CONTEXT_CREATOR_NAME, OsgiApplicationContextCreator.class) |
| : null; |
| |
| contextEventListener = |
| extenderConfiguration.containsBean(CONTEXT_LISTENER_NAME) ? extenderConfiguration |
| .getBean(CONTEXT_LISTENER_NAME, OsgiBundleApplicationContextListener.class) |
| : createDefaultApplicationContextListener(); |
| } |
| |
| // get post processors |
| postProcessors.addAll(extenderConfiguration.getBeansOfType(OsgiBeanFactoryPostProcessor.class).values()); |
| |
| // get dependency factories |
| dependencyFactories.addAll(extenderConfiguration.getBeansOfType(OsgiServiceDependencyFactory.class) |
| .values()); |
| |
| classLoader = extenderConfiguration.getClassLoader(); |
| // extender properties using the defaults as backup |
| if (extenderConfiguration.containsBean(PROPERTIES_NAME)) { |
| Properties customProperties = |
| extenderConfiguration.getBean(PROPERTIES_NAME, Properties.class); |
| Enumeration<?> propertyKey = customProperties.propertyNames(); |
| while (propertyKey.hasMoreElements()) { |
| String property = (String) propertyKey.nextElement(); |
| properties.setProperty(property, customProperties.getProperty(property)); |
| } |
| } |
| } |
| |
| synchronized (lock) { |
| shutdownWaitTime = getShutdownWaitTime(properties); |
| shutdownAsynchronously = getShutdownAsynchronously(properties); |
| dependencyWaitTime = getDependencyWaitTime(properties); |
| processAnnotation = getProcessAnnotations(properties); |
| } |
| |
| // load default dependency factories |
| addDefaultDependencyFactories(); |
| |
| // allow post processing |
| contextCreator = postProcess(contextCreator); |
| } |
| |
| /** |
| * Allows post processing of the context creator. |
| * |
| * @param contextCreator |
| * @return |
| */ |
| protected OsgiApplicationContextCreator postProcess(OsgiApplicationContextCreator contextCreator) { |
| return contextCreator; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * Cleanup the configuration items. |
| */ |
| public void stop(BundleContext extenderBundleContext) { |
| |
| synchronized (lock) { |
| if (isMulticasterManagedInternally) { |
| eventMulticaster.removeAllListeners(); |
| eventMulticaster = null; |
| } |
| |
| if (extenderConfiguration != null) { |
| extenderConfiguration.close(); |
| extenderConfiguration = null; |
| } |
| |
| // postpone the task executor shutdown |
| if (forceThreadShutdown) { |
| |
| if (isTaskExecutorManagedInternally) { |
| log.warn("Forcing the (internally created) taskExecutor to stop..."); |
| ThreadGroup th = ((ThreadPoolTaskExecutor) taskExecutor).getThreadGroup(); |
| if (!th.isDestroyed()) { |
| // ask the threads nicely to stop |
| th.interrupt(); |
| } |
| } |
| taskExecutor = null; |
| } |
| |
| if (isShutdownTaskExecutorManagedInternally) { |
| try { |
| ((DisposableBean) shutdownTaskExecutor).destroy(); |
| } catch (Exception ex) { |
| log.debug("Received exception while shutting down shutdown task executor", ex); |
| } |
| shutdownTaskExecutor = null; |
| } |
| } |
| } |
| |
| /** |
| * Copies the URLs returned by the given enumeration and returns them as an array of Strings for consumption by the |
| * application context. |
| * |
| * @param enm |
| * @return |
| */ |
| @SuppressWarnings("deprecation") |
| private String[] copyEnumerationToList(Enumeration<?> enm) { |
| List<String> urls = new ArrayList<String>(4); |
| while (enm != null && enm.hasMoreElements()) { |
| URL configURL = (URL) enm.nextElement(); |
| if (configURL != null) { |
| String configURLAsString = configURL.toExternalForm(); |
| try { |
| urls.add(URLDecoder.decode(configURLAsString, "UTF8")); |
| } catch (UnsupportedEncodingException uee) { |
| log.warn("UTF8 encoding not supported, using the platform default"); |
| urls.add(URLDecoder.decode(configURLAsString)); |
| } |
| } |
| } |
| |
| return urls.toArray(new String[urls.size()]); |
| } |
| |
| private Properties createDefaultProperties() { |
| Properties properties = new Properties(); |
| properties.setProperty(SHUTDOWN_WAIT_KEY, "" + DEFAULT_SHUTDOWN_WAIT); |
| properties.setProperty(SHUTDOWN_ASYNCHRONOUS_KEY, "" + DEFAULT_SHUTDOWN_ASYNCHRONOUS); |
| properties.setProperty(PROCESS_ANNOTATIONS_KEY, "" + DEFAULT_PROCESS_ANNOTATION); |
| properties.setProperty(WAIT_FOR_DEPS_TIMEOUT_KEY, "" + DEFAULT_DEP_WAIT); |
| |
| return properties; |
| } |
| |
| protected void addDefaultDependencyFactories() { |
| boolean debug = log.isDebugEnabled(); |
| |
| // default JDK 1.4 processor |
| dependencyFactories.add(0, new MandatoryImporterDependencyFactory()); |
| |
| // load through reflection the dependency and injection processors if running on JDK 1.5 and annotation |
| // processing is enabled |
| if (processAnnotation) { |
| // dependency processor |
| Class<?> annotationProcessor = null; |
| try { |
| annotationProcessor = |
| Class.forName(ANNOTATION_DEPENDENCY_FACTORY, false, ExtenderConfiguration.class |
| .getClassLoader()); |
| } catch (ClassNotFoundException cnfe) { |
| log.warn("Gemini Blueprint extensions bundle not present, annotation processing disabled."); |
| log.debug("Gemini Blueprint extensions bundle not present, annotation processing disabled.", cnfe); |
| return; |
| } |
| Object processor = BeanUtils.instantiateClass(annotationProcessor); |
| Assert.isInstanceOf(OsgiServiceDependencyFactory.class, processor); |
| dependencyFactories.add(1, (OsgiServiceDependencyFactory) processor); |
| |
| if (debug) |
| log.debug("Succesfully loaded annotation dependency processor [" + ANNOTATION_DEPENDENCY_FACTORY + "]"); |
| |
| // add injection processor (first in line) |
| postProcessors.add(0, new OsgiAnnotationPostProcessor()); |
| log.info("Gemini Blueprint extensions annotation processing enabled"); |
| } else { |
| if (debug) { |
| log.debug("Gemini Blueprint extensions annotation processing disabled; [" + ANNOTATION_DEPENDENCY_FACTORY |
| + "] not loaded"); |
| } |
| } |
| |
| } |
| |
| private TaskExecutor createDefaultTaskExecutor() { |
| // create thread-pool for starting contexts |
| ThreadGroup threadGroup = |
| new ThreadGroup("eclipse-gemini-blueprint-extender[" + ObjectUtils.getIdentityHexString(this) + "]-threads"); |
| threadGroup.setDaemon(false); |
| |
| ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); |
| taskExecutor.setMaxPoolSize(Runtime.getRuntime().availableProcessors()); |
| taskExecutor.setThreadGroup(threadGroup); |
| taskExecutor.setThreadNamePrefix("EclipseGeminiBlueprintExtenderThread-"); |
| taskExecutor.initialize(); |
| |
| isTaskExecutorManagedInternally = true; |
| |
| return taskExecutor; |
| } |
| |
| private TaskExecutor createDefaultShutdownTaskExecutor() { |
| ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); |
| taskExecutor.setThreadNamePrefix("Gemini Blueprint context shutdown thread "); |
| taskExecutor.setDaemon(true); |
| taskExecutor.setMaxPoolSize(1); |
| taskExecutor.initialize(); |
| |
| isShutdownTaskExecutorManagedInternally = true; |
| |
| return taskExecutor; |
| } |
| |
| private OsgiBundleApplicationContextEventMulticaster createDefaultEventMulticaster() { |
| isMulticasterManagedInternally = true; |
| return new OsgiBundleApplicationContextEventMulticasterAdapter(new SimpleApplicationEventMulticaster()); |
| } |
| |
| private OsgiBundleApplicationContextListener createDefaultApplicationContextListener() { |
| return new DefaultOsgiBundleApplicationContextListener(log); |
| } |
| |
| private long getShutdownWaitTime(Properties properties) { |
| return Long.parseLong(properties.getProperty(SHUTDOWN_WAIT_KEY)); |
| } |
| |
| private boolean getShutdownAsynchronously(Properties properties) { |
| return Boolean.valueOf(properties.getProperty(SHUTDOWN_ASYNCHRONOUS_KEY)); |
| } |
| |
| private long getDependencyWaitTime(Properties properties) { |
| return Long.parseLong(properties.getProperty(WAIT_FOR_DEPS_TIMEOUT_KEY)); |
| } |
| |
| private boolean getProcessAnnotations(Properties properties) { |
| return Boolean.valueOf(properties.getProperty(PROCESS_ANNOTATIONS_KEY)).booleanValue() |
| || Boolean.getBoolean(AUTO_ANNOTATION_PROCESSING); |
| } |
| |
| /** |
| * Returns the taskExecutor. |
| * |
| * @return Returns the taskExecutor |
| */ |
| public TaskExecutor getTaskExecutor() { |
| synchronized (lock) { |
| return taskExecutor; |
| } |
| } |
| |
| /** |
| * Returns the shutdown task executor. |
| * |
| * @return Returns the shutdown task executor |
| */ |
| public TaskExecutor getShutdownTaskExecutor() { |
| synchronized (lock) { |
| return shutdownTaskExecutor; |
| } |
| } |
| |
| /** |
| * Returns the contextEventListener. |
| * |
| * @return Returns the contextEventListener |
| */ |
| public OsgiBundleApplicationContextListener getContextEventListener() { |
| synchronized (lock) { |
| return contextEventListener; |
| } |
| } |
| |
| /** |
| * Returns the shutdownWaitTime. |
| * |
| * @return Returns the shutdownWaitTime |
| */ |
| public long getShutdownWaitTime() { |
| synchronized (lock) { |
| return shutdownWaitTime; |
| } |
| } |
| |
| /** |
| * Indicates if the process annotation is enabled or not. |
| * |
| * @return Returns true if the annotation should be processed or not otherwise. |
| */ |
| public boolean shouldProcessAnnotation() { |
| synchronized (lock) { |
| return processAnnotation; |
| } |
| } |
| |
| /** |
| * @return whether the application context shutdown during the bundle stop phase shall be |
| * performed asynchronously. |
| */ |
| public boolean shouldShutdownAsynchronously() { |
| synchronized (lock) { |
| return this.shutdownAsynchronously; |
| } |
| } |
| |
| /** |
| * Returns the dependencyWaitTime. |
| * |
| * @return Returns the dependencyWaitTime |
| */ |
| public long getDependencyWaitTime() { |
| synchronized (lock) { |
| return dependencyWaitTime; |
| } |
| } |
| |
| /** |
| * Returns the eventMulticaster. |
| * |
| * @return Returns the eventMulticaster |
| */ |
| public OsgiBundleApplicationContextEventMulticaster getEventMulticaster() { |
| synchronized (lock) { |
| return eventMulticaster; |
| } |
| } |
| |
| /** |
| * Sets the flag to force the taskExtender to close up in case of runaway threads - this applies *only* if the |
| * taskExecutor has been created internally. |
| * |
| * <p/> The flag will cause a best attempt to shutdown the threads. |
| * |
| * @param forceThreadShutdown The forceThreadShutdown to set. |
| */ |
| public void setForceThreadShutdown(boolean forceThreadShutdown) { |
| synchronized (lock) { |
| this.forceThreadShutdown = forceThreadShutdown; |
| } |
| } |
| |
| /** |
| * Returns the contextCreator. |
| * |
| * @return Returns the contextCreator |
| */ |
| public OsgiApplicationContextCreator getContextCreator() { |
| synchronized (lock) { |
| return contextCreator; |
| } |
| } |
| |
| /** |
| * Returns the postProcessors. |
| * |
| * @return Returns the postProcessors |
| */ |
| public List<OsgiBeanFactoryPostProcessor> getPostProcessors() { |
| return postProcessors; |
| } |
| |
| /** |
| * Returns the class loader wrapped around the extender bundle. |
| * |
| * @return extender bundle class loader |
| */ |
| public ClassLoader getClassLoader() { |
| return classLoader; |
| } |
| |
| /** |
| * Returns the dependencies factories declared by the extender configuration. The list automatically contains the |
| * default listeners (such as the annotation one). |
| * |
| * @return list of dependency factories |
| */ |
| public List<OsgiServiceDependencyFactory> getDependencyFactories() { |
| return dependencyFactories; |
| } |
| } |