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  
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 		// get the spring-dm core class loader
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 }