org.springframework.osgi.context.ContextLoaderBundleActivator.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.osgi.context.ContextLoaderBundleActivator.java

Source

/*
 * Copyright 2002-2006 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Created on 23-Jan-2006 by Adrian Colyer
 */
package org.springframework.osgi.context;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Dictionary;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.osgi.context.support.DefaultOsgiBundleXmlApplicationContextFactory;
import org.springframework.osgi.context.support.OsgiBundleXmlApplicationContext;
import org.springframework.osgi.context.support.OsgiBundleXmlApplicationContextFactory;
import org.springframework.osgi.service.OsgiServiceUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

/**
 * OSGi bundle activator class to be used as the bundle activator for OSGi
 * bundles using Spring services. When the bundle is activated it creates an
 * application context and destroys it when the bundled is stopped.
 * 
 * By default the ContextLoaderBundleActivator will look for an application
 * context file in the location
 * /META-INF/<bundle-symbolic-name>-context.xml. You can override this
 * default behaviour by adding a bundle manifest header entry of the form
 * 
 * Spring-Context: <comma-delimited list of context file locations>
 * 
 * The manifest entry may contain any number of resource paths, separated by any
 * number of commas and spaces.
 * 
 * TODO: support parent application context via additional header giving name of
 * parent application context service (by default this will be
 * <bundle-symbolic-name>-springApplicationContext.
 * 
 * @author Adrian Colyer
 * @since 2.0
 */
public class ContextLoaderBundleActivator implements BundleActivator {

    private static final String CONTEXT_LOCATION_HEADER = "Spring-Context";
    private static final String PARENT_CONTEXT_SERVICE_NAME_HEADER = "Spring-Parent-Context";
    private static final String CONTEXT_LOCATION_DELIMITERS = ", ";
    private static final String DEFAULT_CONTEXT_PREFIX = "/META-INF/";
    private static final String DEFAULT_CONTEXT_POSTFIX = "-context.xml";
    private static final String CONTEXT_OPTIONS = "Spring-Context-Options";
    /**
     * is this really required? - if no parent is found, an exception is thrown
     * anyway
     */
    private static final String FAIL_FAST_OPTION = "honor-dependent-services";

    private OsgiBundleXmlApplicationContextFactory contextFactory = new DefaultOsgiBundleXmlApplicationContextFactory();
    private ConfigurableApplicationContext applicationContext;
    private ServiceReference parentServiceReference;
    private ServiceTracker serviceTracker;

    private static final Log log = LogFactory.getLog(ContextLoaderBundleActivator.class);

    /**
     * BundleActivator.start
     */
    public void start(BundleContext bundleContext) throws Exception {
        Bundle myBundle = bundleContext.getBundle();
        log.info("starting bundle " + myBundle.getSymbolicName() + myBundle.getBundleId());
        String[] applicationContextLocations = getApplicationContextLocations(myBundle);
        ApplicationContext parent = getParentApplicationContext(bundleContext);
        this.applicationContext = this.contextFactory.createApplicationContext(parent, bundleContext,
                applicationContextLocations);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
     */
    public void stop(BundleContext bundleContext) throws Exception {
        if (this.applicationContext != null) {
            this.applicationContext.close();
        }
        if (this.parentServiceReference != null) {
            bundleContext.ungetService(this.parentServiceReference);
        }

        if (this.serviceTracker != null)
            serviceTracker.close();
    }

    /**
     * Search for the Spring-Parent-Context application context.
     * 
     * @param context
     * @return
     */
    protected ApplicationContext getParentApplicationContext(final BundleContext context) {
        String parentContextServiceName = (String) context.getBundle().getHeaders()
                .get(PARENT_CONTEXT_SERVICE_NAME_HEADER);
        if (parentContextServiceName == null) {
            if (log.isDebugEnabled())
                log.debug("no need to look for a parent context");

            return null;
        } else {
            if (log.isDebugEnabled())
                log.debug("looking for a parent context...");

            // try to find the service
            String filter = "(" + OsgiBundleXmlApplicationContext.APPLICATION_CONTEXT_SERVICE_NAME_HEADER + "="
                    + parentContextServiceName + ")";

            parentServiceReference = OsgiServiceUtils.getService(context, ApplicationContext.class, filter);

            // TODO: register as service listener..., probably in a proxy to the
            // app context
            // that we create here and return instead.
            // Costin: done, should be verified though.

            return createApplicationContextProxy(context, parentServiceReference);
        }
    }

    /**
     * Create a proxy around the target application context.
     * 
     * @param parent
     * @return
     */
    protected ApplicationContext createApplicationContextProxy(BundleContext context,
            ServiceReference serviceReference) {

        LookupApplicationContextInvocationHandler handler = new LookupApplicationContextInvocationHandler(context,
                serviceReference, serviceTracker);

        // TODO: interfaces are detected dynamically - is this dangerous (for
        // example if the parent context changes)
        // As the child depends on it, recreating the parent context should
        // trigger the whole process again.
        ApplicationContext target = handler.getTarget();
        Class[] ifaces = (target == null ? new Class[] { ApplicationContext.class }
                : ClassUtils.getAllInterfaces(target));

        return (ApplicationContext) Proxy.newProxyInstance(getClass().getClassLoader(), ifaces, handler);
    }

    /**
     * Retrieves the org.springframework.context manifest header attribute and
     * parses it to create a String[] of resource names for creating the
     * application context.
     * 
     * If the org.springframework.context header is not present, the default
     * <bundle-symbolic-name>-context.xml file will be returned.
     */
    protected String[] getApplicationContextLocations(Bundle bundle) {
        Dictionary manifestHeaders = bundle.getHeaders();
        String contextLocationsHeader = (String) manifestHeaders.get(CONTEXT_LOCATION_HEADER);
        if (contextLocationsHeader != null) {
            // (Dictionary does not offer a "containsKey" operation)
            return addBundlePrefixTo(
                    StringUtils.tokenizeToStringArray(contextLocationsHeader, CONTEXT_LOCATION_DELIMITERS));
        } else {
            String defaultName = DEFAULT_CONTEXT_PREFIX + bundle.getSymbolicName() + DEFAULT_CONTEXT_POSTFIX;
            return addBundlePrefixTo(new String[] { defaultName });
        }
    }

    /**
     * add the "bundle:" prefix to the resource location paths in the given
     * argument. This ensures that only this bundle will be searched for
     * matching resources.
     * 
     * Modifies the argument in place and returns it.
     */
    private String[] addBundlePrefixTo(String[] resourcePaths) {
        for (int i = 0; i < resourcePaths.length; i++) {
            resourcePaths[i] = OsgiBundleResource.BUNDLE_URL_PREFIX + resourcePaths[i];
        }
        return resourcePaths;
    }

    // for testing...
    protected void setApplicationContext(ConfigurableApplicationContext context) {
        this.applicationContext = context;
    }

    // for testing...
    protected void setParentServiceReference(ServiceReference ref) {
        this.parentServiceReference = ref;
    }

    // for testing...
    protected void setApplicationContextFactory(OsgiBundleXmlApplicationContextFactory factory) {
        this.contextFactory = factory;
    }

    /**
     * Simple lookup proxy using a ServiceTracker underneath.
     * 
     * @author Costin Leau
     * 
     */
    private static class LookupApplicationContextInvocationHandler implements InvocationHandler {
        private final ServiceReference serviceReference;
        private ApplicationContext target;

        public LookupApplicationContextInvocationHandler(final BundleContext context, ServiceReference serviceRef,
                ServiceTracker serviceTracker) {
            this.serviceReference = serviceRef;

            ApplicationContext parent = (ApplicationContext) context.getService(serviceReference);

            serviceTracker = new ServiceTracker(context, serviceReference, new ServiceTrackerCustomizer() {
                public Object addingService(ServiceReference ref) {
                    if (log.isDebugEnabled())
                        log.debug("parentApplicationContext has been discovered");

                    // multiple parent contexts are already handled by
                    // OsgiServiceUtils.getService()
                    target = (ApplicationContext) context.getService(ref);
                    return target;
                }

                public void modifiedService(ServiceReference ref, Object service) {
                    if (log.isDebugEnabled())
                        log.debug("parentApplicationContext has been modified");
                    // TODO: should we refresh the child context (happens
                    // automatically if the parent is refreshed)
                }

                public void removedService(ServiceReference ref, Object service) {
                    if (log.isDebugEnabled())
                        log.debug("parentApplicationContext has been removed");
                    target = null;

                    // TODO: should we close the child context?
                }
            });
        }

        /**
         * Used to get the discovered target object (for example for detecting
         * the implemented interfaces).
         * 
         * @return
         */
        protected ApplicationContext getTarget() {
            return target;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();

            if (methodName.equals("equals")) {
                // Only consider equal when proxies are identical.
                return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
            } else if (methodName.equals("hashCode")) {
                // Use hashCode of SessionFactory proxy.
                return new Integer(hashCode());
            }

            try {
                if (target != null)
                    return method.invoke(this.target, args);
            } catch (InvocationTargetException ex) {
                throw ex.getTargetException();
            }
            throw new UnsupportedOperationException("no parentApplicationContext in place");
        }
    }

    /**
     * Close suppressing invocation handler - proxy used as a 'shield' against
     * forbidden close inside an OSGi environment.
     * 
     * Not used at the moment.
     * 
     * @author Costin Leau
     * 
     */
    private static class CloseSuppresingApplicationContextInvocationHandler implements InvocationHandler {
        private final ApplicationContext target;

        public CloseSuppresingApplicationContextInvocationHandler(ApplicationContext appContext) {
            this.target = appContext;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();

            // suppress close calls:
            // applicationContext close
            // Lifecycle interface
            // TODO: what about refresh() ?
            if (methodName.equals("close") || methodName.equals("stop"))
                return null;

            if (methodName.equals("equals")) {
                // Only consider equal when proxies are identical.
                return (proxy == args[0] ? Boolean.TRUE : Boolean.FALSE);
            } else if (methodName.equals("hashCode")) {
                // Use hashCode of SessionFactory proxy.
                return new Integer(hashCode());
            }

            try {
                return method.invoke(this.target, args);
            } catch (InvocationTargetException ex) {
                throw ex.getTargetException();
            }
        }
    }

}