1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.springframework.osgi.extender.internal.support;
18
19 import java.io.FileNotFoundException;
20 import java.io.IOException;
21 import java.lang.reflect.Field;
22 import java.util.Iterator;
23 import java.util.Map;
24
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27 import org.osgi.framework.Bundle;
28 import org.springframework.beans.factory.DisposableBean;
29 import org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver;
30 import org.springframework.beans.factory.xml.DelegatingEntityResolver;
31 import org.springframework.beans.factory.xml.NamespaceHandler;
32 import org.springframework.beans.factory.xml.NamespaceHandlerResolver;
33 import org.springframework.core.CollectionFactory;
34 import org.springframework.osgi.util.BundleDelegatingClassLoader;
35 import org.springframework.osgi.util.OsgiStringUtils;
36 import org.springframework.util.ReflectionUtils;
37 import org.xml.sax.EntityResolver;
38 import org.xml.sax.InputSource;
39 import org.xml.sax.SAXException;
40
41 /**
42 * Spring schema handler/resolver for OSGi environments.
43 *
44 * Besides delegation this class also does type filtering to avoid wiring the
45 * wrong bundle if multiple versions of the same library (which support the same
46 * schema) are available.
47 *
48 * @author Hal Hildebrand
49 * @author Costin Leau
50 *
51 */
52 public class NamespacePlugins implements NamespaceHandlerResolver, EntityResolver, DisposableBean {
53
54 /**
55 * Wrapper class which implements both {@link EntityResolver} and
56 * {@link NamespaceHandlerResolver} interfaces.
57 *
58 * Simply delegates to the actual implementation discovered in a specific
59 * bundle.
60 */
61 private static class Plugin implements NamespaceHandlerResolver, EntityResolver {
62
63 private final NamespaceHandlerResolver namespace;
64
65 private final EntityResolver entity;
66
67 private final Bundle bundle;
68
69
70 private Plugin(Bundle bundle) {
71 this.bundle = bundle;
72
73 ClassLoader loader = BundleDelegatingClassLoader.createBundleClassLoaderFor(bundle);
74
75 entity = new DelegatingEntityResolver(loader);
76 namespace = new DefaultNamespaceHandlerResolver(loader);
77 }
78
79 public NamespaceHandler resolve(String namespaceUri) {
80 return namespace.resolve(namespaceUri);
81 }
82
83 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
84 return entity.resolveEntity(publicId, systemId);
85 }
86
87 public Bundle getBundle() {
88 return bundle;
89 }
90 }
91
92
93 private static final Log log = LogFactory.getLog(NamespacePlugins.class);
94
95 private static final String CACHE_CLASS = "org.springframework.osgi.context.support.TrackingUtil";
96
97 private static final String FIELD_NAME = "invokingBundle";
98
99 private final Map plugins = CollectionFactory.createConcurrentMap(5);
100
101
102 public void addHandler(Bundle bundle) {
103 if (log.isDebugEnabled())
104 log.debug("Adding as handler " + OsgiStringUtils.nullSafeNameAndSymName(bundle));
105
106 plugins.put(bundle, new Plugin(bundle));
107 }
108
109 /**
110 * Return true if a handler mapping was removed for the given bundle.
111 *
112 * @param bundle bundle to look at
113 * @return true if the bundle was used in the plugin map
114 */
115 public boolean removeHandler(Bundle bundle) {
116 if (log.isDebugEnabled())
117 log.debug("Removing handler " + OsgiStringUtils.nullSafeNameAndSymName(bundle));
118
119 return (plugins.remove(bundle) != null);
120 }
121
122 public NamespaceHandler resolve(String namespaceUri) {
123 boolean debug = log.isDebugEnabled();
124
125 if (debug)
126 log.debug("Trying to resolving namespace handler for " + namespaceUri);
127
128 for (Iterator i = plugins.values().iterator(); i.hasNext();) {
129 Plugin plugin = (Plugin) i.next();
130 try {
131 NamespaceHandler handler = plugin.resolve(namespaceUri);
132 if (handler != null) {
133 if (debug)
134 log.debug("Namespace handler for " + namespaceUri + " found inside "
135 + OsgiStringUtils.nullSafeNameAndSymName(plugin.getBundle()));
136
137 return handler;
138 }
139 }
140 catch (IllegalArgumentException ex) {
141 if (debug)
142 log.debug("Namespace handler for " + namespaceUri + " not found inside "
143 + OsgiStringUtils.nullSafeNameAndSymName(plugin.getBundle()));
144
145 }
146 }
147 return null;
148 }
149
150 public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
151 boolean debug = log.isDebugEnabled();
152
153 if (debug)
154 log.debug("Trying to resolving entity for " + publicId + "|" + systemId);
155
156 if (systemId != null) {
157 for (Iterator i = plugins.values().iterator(); i.hasNext();) {
158 InputSource inputSource;
159 Plugin plugin = (Plugin) i.next();
160 try {
161 inputSource = plugin.resolveEntity(publicId, systemId);
162 if (inputSource != null) {
163 if (debug)
164 log.debug("XML schema for " + publicId + "|" + systemId + " found inside "
165 + OsgiStringUtils.nullSafeNameAndSymName(plugin.getBundle()));
166 return inputSource;
167 }
168
169 }
170 catch (FileNotFoundException ex) {
171 if (debug)
172 log.debug("XML schema for " + publicId + "|" + systemId + " not found inside "
173 + OsgiStringUtils.nullSafeNameAndSymName(plugin.getBundle()), ex);
174 }
175 }
176 }
177
178 return null;
179 }
180
181 public void destroy() {
182 plugins.clear();
183 }
184
185 /**
186 * Returns the namespace/resolver invoker plugin. To do that, the Spring-DM
187 * core classes will be used assuming that its infrastructure is being used.
188 *
189 * @return the invoking bundle
190 */
191 private Bundle getInvokingBundle() {
192
193 ClassLoader coreClassLoader = OsgiStringUtils.class.getClassLoader();
194 try {
195 Class cacheClass = coreClassLoader.loadClass(CACHE_CLASS);
196 Field field = cacheClass.getField(FIELD_NAME);
197 ReflectionUtils.makeAccessible(field);
198 return (Bundle) ((ThreadLocal) field.get(null)).get();
199 }
200 catch (Exception ex) {
201 log.trace("Could not determine invoking bundle", ex);
202 return null;
203 }
204 }
205 }