/******************************************************************************
* 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.context.support.internal.scope;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceRegistration;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.core.NamedThreadLocal;
import org.springframework.util.Assert;
/**
* OSGi bundle {@link org.springframework.beans.factory.config.Scope}
* implementation.
*
* Will allow per--calling-bundle object instance, thus this scope becomes
* useful when enabled on localBeans exposed as OSGi services.
*
* @author Costin Leau
*
*/
// This class relies heavily on the OSGi ServiceFactory (SF) behaviour.
// Since the OSGi platform automatically calls get/ungetService on a SF
// and caches the getService() object there is no need for caching inside the
// scope.
// This also means that the scope cannot interact with the cache and acts
// only as an object creator and nothing more in favor of the ServiceFactory.
// However, note that for the inner bundle, the scope has to mimic the OSGi
// behaviour.
//
public class OsgiBundleScope implements Scope, DisposableBean {
public static final String SCOPE_NAME = "bundle";
private static final Log log = LogFactory.getLog(OsgiBundleScope.class);
/**
* Decorating {@link org.osgi.framework.ServiceFactory} used for supporting
* 'bundle' scoped localBeans.
*
* @author Costin Leau
*
*/
public static class BundleScopeServiceFactory implements ServiceFactory {
private ServiceFactory decoratedServiceFactory;
/** destruction callbacks for bean instances */
private final Map<Bundle, Runnable> callbacks = new ConcurrentHashMap<Bundle, Runnable>(4);
public BundleScopeServiceFactory(ServiceFactory serviceFactory) {
Assert.notNull(serviceFactory);
this.decoratedServiceFactory = serviceFactory;
}
/**
* Called if a bundle requests a service for the first time (start the
* scope).
*
* @see org.osgi.framework.ServiceFactory#getService(org.osgi.framework.Bundle,
* org.osgi.framework.ServiceRegistration)
*/
public Object getService(Bundle bundle, ServiceRegistration registration) {
try {
// tell the scope, it's an outside bundle that does the call
EXTERNAL_BUNDLE.set(Boolean.TRUE);
// create the new object (call the container)
Object obj = decoratedServiceFactory.getService(bundle, registration);
// get callback (registered through the scope)
Object passedObject = OsgiBundleScope.EXTERNAL_BUNDLE.get();
// make sure it's not the marker object
if (passedObject != null && passedObject instanceof Runnable) {
Runnable callback = (Runnable) OsgiBundleScope.EXTERNAL_BUNDLE.get();
if (callback != null)
callbacks.put(bundle, callback);
}
return obj;
}
finally {
// clean ThreadLocal
OsgiBundleScope.EXTERNAL_BUNDLE.set(null);
}
}
/**
* Called if a bundle releases the service (stop the scope).
*
* @see org.osgi.framework.ServiceFactory#ungetService(org.osgi.framework.Bundle,
* org.osgi.framework.ServiceRegistration, java.lang.Object)
*/
public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
try {
// tell the scope, it's an outside bundle that does the call
EXTERNAL_BUNDLE.set(Boolean.TRUE);
// unget object first
decoratedServiceFactory.ungetService(bundle, registration, service);
// then apply the destruction callback (if any)
Runnable callback = callbacks.remove(bundle);
if (callback != null)
callback.run();
}
finally {
// clean ThreadLocal
EXTERNAL_BUNDLE.set(null);
}
}
}
/**
* ThreadLocal used for passing objects around {@link OsgiBundleScope} and
* {@link BundleScopeServiceFactory} (there is only one scope instance but
* multiple BSSFs).
*/
public static final ThreadLocal<Object> EXTERNAL_BUNDLE = new NamedThreadLocal<Object>(
"Current in-creation scoped bean");
/**
* Map of localBeans imported by the current bundle from other bundles. This
* map is sychronized and is used by
* {@link org.eclipse.gemini.blueprint.context.support.internal.scope.OsgiBundleScope}
* .
*/
private final Map<String, Object> localBeans = new LinkedHashMap<String, Object>(4);
/**
* Unsynchronized map of callbacks for the services used by the running
* bundle.
*
* Uses the bean name as key and as value, a list of callbacks associated
* with the bean instances.
*/
private final Map<String, Runnable> destructionCallbacks = new LinkedHashMap<String, Runnable>(8);
private boolean isExternalBundleCalling() {
return (EXTERNAL_BUNDLE.get() != null);
}
public Object get(String name, ObjectFactory<?> objectFactory) {
// outside bundle calling (no need to cache things)
if (isExternalBundleCalling()) {
Object bean = objectFactory.getObject();
return bean;
}
// in-appCtx call
else {
// use local bean repository
// cannot use a concurrent map since we want to postpone the call to
// getObject
synchronized (localBeans) {
Object bean = localBeans.get(name);
if (bean == null) {
bean = objectFactory.getObject();
localBeans.put(name, bean);
}
return bean;
}
}
}
public String getConversationId() {
return null;
}
public void registerDestructionCallback(String name, Runnable callback) {
// pass the destruction callback to the ServiceFactory
if (isExternalBundleCalling())
EXTERNAL_BUNDLE.set(callback);
// otherwise destroy the bean from the local cache
else {
destructionCallbacks.put(name, callback);
}
}
/*
* Unable to do this as we cannot invalidate the OSGi cache.
*/
public Object remove(String name) {
throw new UnsupportedOperationException();
}
public Object resolveContextualObject(String key) {
return null;
}
/*
* Clean up the scope (context refresh/close()).
*/
public void destroy() {
boolean debug = log.isDebugEnabled();
// handle only the local cache/localBeans
// the ServiceFactory object will be destroyed upon service
// unregistration
for (Iterator<Map.Entry<String, Runnable>> iter = destructionCallbacks.entrySet().iterator(); iter.hasNext();) {
Map.Entry<String, Runnable> entry = iter.next();
Runnable callback = (Runnable) entry.getValue();
if (debug)
log.debug("destroying local bundle scoped bean [" + entry.getKey() + "]");
callback.run();
}
destructionCallbacks.clear();
localBeans.clear();
}
}