1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
45
46
47
48
49
50
51
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
87 CALLING_BUNDLE.set(Boolean.TRUE);
88
89
90 Object obj = decoratedServiceFactory.getService(bundle, registration);
91
92
93 Object passedObject = OsgiBundleScope.CALLING_BUNDLE.get();
94
95
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
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
118 CALLING_BUNDLE.set(Boolean.TRUE);
119
120 decoratedServiceFactory.ungetService(bundle, registration, service);
121
122
123 Runnable callback = (Runnable) callbacks.remove(bundle);
124 if (callback != null)
125 callback.run();
126 }
127 finally {
128
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
164 if (isExternalBundleCalling()) {
165 Object bean = objectFactory.getObject();
166
167 return bean;
168 }
169
170 else {
171
172
173
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
192 if (isExternalBundleCalling())
193 CALLING_BUNDLE.set(callback);
194
195 else {
196 destructionCallbacks.put(name, callback);
197 }
198 }
199
200
201
202
203
204
205
206 public Object remove(String name) {
207 throw new UnsupportedOperationException();
208 }
209
210
211
212
213
214
215
216 public void destroy() {
217 boolean debug = log.isDebugEnabled();
218
219
220
221
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 }