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  package org.springframework.osgi.context.support.internal;
17  
18  import java.util.Iterator;
19  import java.util.LinkedHashMap;
20  import java.util.Map;
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  import org.osgi.framework.Bundle;
25  import org.osgi.framework.ServiceFactory;
26  import org.osgi.framework.ServiceRegistration;
27  import org.springframework.beans.factory.DisposableBean;
28  import org.springframework.beans.factory.ObjectFactory;
29  import org.springframework.beans.factory.config.Scope;
30  import org.springframework.core.CollectionFactory;
31  import org.springframework.util.Assert;
32  
33  /**
34   * Osgi bundle {@link org.springframework.beans.factory.config.Scope}
35   * implementation.
36   * 
37   * Will allow per--calling-bundle object instance similar thus this scope
38   * becomes useful when enabled on beans exposed as OSGi services.
39   * 
40   * @author Costin Leau
41   * 
42   */
43  
44  // This class relies heavily on the OSGi ServiceFactory (SF) behaviour.
45  // Since the OSGi platform automatically calls get/ungetService on a SF
46  // and caches the getService() object there is no need for caching inside the
47  // scope.
48  // This also means that the scope cannot interact with the cache and acts
49  // only as an object creator and nothing more in favor of the ServiceFactory.
50  // However, note that for the inner bundle, the scope has to mimic the OSGi
51  // behaviour.
52  // 
53  public class OsgiBundleScope implements Scope, DisposableBean {
54  
55  	public static final String SCOPE_NAME = "bundle";
56  
57  	private static final Log log = LogFactory.getLog(OsgiBundleScope.class);
58  
59  	/**
60  	 * Decorating {@link org.osgi.framework.ServiceFactory} used for supporting
61  	 * 'bundle' scoped beans.
62  	 * 
63  	 * @author Costin Leau
64  	 * 
65  	 */
66  	public static class BundleScopeServiceFactory implements ServiceFactory {
67  		private ServiceFactory decoratedServiceFactory;
68  
69  		/** destruction callbacks for bean instances */
70  		private final Map callbacks = CollectionFactory.createConcurrentMap(4);
71  
72  		public BundleScopeServiceFactory(ServiceFactory serviceFactory) {
73  			Assert.notNull(serviceFactory);
74  			this.decoratedServiceFactory = serviceFactory;
75  		}
76  
77  		/**
78  		 * Called if a bundle requests a service for the first time (start the
79  		 * scope).
80  		 * 
81  		 * @see org.osgi.framework.ServiceFactory#getService(org.osgi.framework.Bundle,
82  		 * org.osgi.framework.ServiceRegistration)
83  		 */
84  		public Object getService(Bundle bundle, ServiceRegistration registration) {
85  			try {
86  				// tell the scope, it's an outside bundle that does the call
87  				CALLING_BUNDLE.set(Boolean.TRUE);
88  
89  				// create the new object
90  				Object obj = decoratedServiceFactory.getService(bundle, registration);
91  
92  				// get callback (registered through the scope)
93  				Object passedObject = OsgiBundleScope.CALLING_BUNDLE.get();
94  
95  				// make sure it's not the marker object
96  				if (passedObject != null && passedObject instanceof Runnable) {
97  					Runnable callback = (Runnable) OsgiBundleScope.CALLING_BUNDLE.get();
98  					if (callback != null)
99  						callbacks.put(bundle, callback);
100 				}
101 				return obj;
102 			}
103 			finally {
104 				// clean ThreadLocal
105 				OsgiBundleScope.CALLING_BUNDLE.set(null);
106 			}
107 		}
108 
109 		/**
110 		 * Called if a bundle releases the service (stop the scope).
111 		 * 
112 		 * @see org.osgi.framework.ServiceFactory#ungetService(org.osgi.framework.Bundle,
113 		 * org.osgi.framework.ServiceRegistration, java.lang.Object)
114 		 */
115 		public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {
116 			try {
117 				// tell the scope, it's an outside bundle that does the call
118 				CALLING_BUNDLE.set(Boolean.TRUE);
119 				// unget object first
120 				decoratedServiceFactory.ungetService(bundle, registration, service);
121 
122 				// then apply the destruction callback (if any)
123 				Runnable callback = (Runnable) callbacks.remove(bundle);
124 				if (callback != null)
125 					callback.run();
126 			}
127 			finally {
128 				// clean ThreadLocal
129 				CALLING_BUNDLE.set(null);
130 			}
131 		}
132 
133 	}
134 
135 	/**
136 	 * ThreadLocal used for passing objects around {@link OsgiBundleScope} and
137 	 * {@link BundleScopeServiceFactory} (there is only one scope instance but
138 	 * multiple BSSFs).
139 	 */
140 	public static final ThreadLocal CALLING_BUNDLE = new ThreadLocal();
141 
142 	/**
143 	 * Map of beans imported by the current bundle from other bundles. This map
144 	 * is sychronized and is used by
145 	 * {@link org.springframework.osgi.context.support.internal.OsgiBundleScope}.
146 	 */
147 	private final Map beans = new LinkedHashMap(4);
148 
149 	/**
150 	 * Unsynchronized map of callbacks for the services used by the running
151 	 * bundle.
152 	 * 
153 	 * Uses the bean name as key and as value, a list of callbacks associated
154 	 * with the bean instances.
155 	 */
156 	private final Map destructionCallbacks = new LinkedHashMap(8);
157 
158 	private boolean isExternalBundleCalling() {
159 		return (CALLING_BUNDLE.get() != null);
160 	}
161 
162 	public Object get(String name, ObjectFactory objectFactory) {
163 		// outside bundle calling (no need to cache things)
164 		if (isExternalBundleCalling()) {
165 			Object bean = objectFactory.getObject();
166 
167 			return bean;
168 		}
169 		// in-appCtx call
170 		else {
171 			// use local bean repository
172 			// cannot use a concurrent map since we want to postpone the call to
173 			// getObject
174 			synchronized (beans) {
175 				Object bean = beans.get(name);
176 				if (bean == null) {
177 					bean = objectFactory.getObject();
178 					beans.put(name, bean);
179 				}
180 				return bean;
181 			}
182 		}
183 
184 	}
185 
186 	public String getConversationId() {
187 		return null;
188 	}
189 
190 	public void registerDestructionCallback(String name, Runnable callback) {
191 		// pass the destruction callback to the ServiceFactory
192 		if (isExternalBundleCalling())
193 			CALLING_BUNDLE.set(callback);
194 		// otherwise destroy the bean from the local cache
195 		else {
196 			destructionCallbacks.put(name, callback);
197 		}
198 	}
199 
200 	/*
201 	 * Unable to do this as we cannot invalidate the OSGi cache.
202 	 * 
203 	 * (non-Javadoc)
204 	 * @see org.springframework.beans.factory.config.Scope#remove(java.lang.String)
205 	 */
206 	public Object remove(String name) {
207 		throw new UnsupportedOperationException();
208 	}
209 
210 	/*
211 	 * Clean up the scope (context refresh/close()).
212 	 * 
213 	 * (non-Javadoc)
214 	 * @see org.springframework.beans.factory.DisposableBean#destroy()
215 	 */
216 	public void destroy() {
217 		boolean debug = log.isDebugEnabled();
218 
219 		// handle only the local cache/beans
220 		// the ServiceFactory object will be destroyed upon service
221 		// unregistration
222 		for (Iterator iter = destructionCallbacks.entrySet().iterator(); iter.hasNext();) {
223 			Map.Entry entry = (Map.Entry) iter.next();
224 			Runnable callback = (Runnable) entry.getValue();
225 
226 			if (debug)
227 				log.debug("destroying local bundle scoped bean [" + entry.getKey() + "]");
228 
229 			callback.run();
230 		}
231 
232 		destructionCallbacks.clear();
233 		beans.clear();
234 	}
235 
236 }