View Javadoc

1   /*
2    * Copyright 2002-20067 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  
18  package org.springframework.osgi.context.support;
19  
20  import java.io.IOException;
21  
22  import org.osgi.framework.BundleContext;
23  import org.springframework.beans.BeansException;
24  import org.springframework.beans.factory.support.DefaultListableBeanFactory;
25  import org.springframework.beans.factory.xml.DefaultNamespaceHandlerResolver;
26  import org.springframework.beans.factory.xml.DelegatingEntityResolver;
27  import org.springframework.beans.factory.xml.NamespaceHandlerResolver;
28  import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
29  import org.springframework.context.ApplicationContext;
30  import org.springframework.osgi.io.OsgiBundleResource;
31  import org.springframework.osgi.util.OsgiStringUtils;
32  import org.springframework.util.Assert;
33  import org.xml.sax.EntityResolver;
34  
35  /**
36   * Stand-alone XML application context, backed by an OSGi bundle.
37   * 
38   * <p>
39   * The configuration location defaults can be overridden via
40   * {@link #getDefaultConfigLocations()}. Note that locations can either denote
41   * concrete files like <code>/myfiles/context.xml</code> or <em>Ant-style</em>
42   * patterns like <code>/myfiles/*-context.xml</code> (see the
43   * {@link org.springframework.util.AntPathMatcher} javadoc for pattern details).
44   * </p>
45   * 
46   * <p>
47   * <strong>Note:</strong> In case of multiple configuration locations, later
48   * bean definitions will override ones defined in earlier loaded files. This can
49   * be leveraged to deliberately override certain bean definitions via an extra
50   * XML file.
51   * </p>
52   * 
53   * <p/> <b>This is the main ApplicationContext class for OSGi environments.</b>
54   * 
55   * @author Adrian Colyer
56   * @author Costin Leau
57   * @author Andy Piper
58   * @author Hal Hildebrand
59   */
60  public class OsgiBundleXmlApplicationContext extends AbstractDelegatedExecutionApplicationContext {
61  
62  	/** Default config location for the root context(s) */
63  	public static final String DEFAULT_CONFIG_LOCATION = OsgiBundleResource.BUNDLE_URL_PREFIX
64  			+ "/META-INF/spring/*.xml";
65  
66  
67  	/**
68  	 * 
69  	 * Creates a new <code>OsgiBundleXmlApplicationContext</code> with no
70  	 * parent.
71  	 * 
72  	 */
73  	public OsgiBundleXmlApplicationContext() {
74  		this((String[]) null);
75  	}
76  
77  	/**
78  	 * Creates a new <code>OsgiBundleXmlApplicationContext</code> with the
79  	 * given parent context.
80  	 * 
81  	 * @param parent the parent context
82  	 */
83  	public OsgiBundleXmlApplicationContext(ApplicationContext parent) {
84  		this(null, parent);
85  	}
86  
87  	/**
88  	 * Creates a new <code>OsgiBundleXmlApplicationContext</code> with the
89  	 * given configLocations.
90  	 * 
91  	 * @param configLocations array of configuration resources
92  	 */
93  	public OsgiBundleXmlApplicationContext(String[] configLocations) {
94  		this(configLocations, null);
95  	}
96  
97  	/**
98  	 * Creates a new <code>OsgiBundleXmlApplicationContext</code> with the
99  	 * given configLocations and parent context.
100 	 * 
101 	 * @param configLocations array of configuration resources
102 	 * @param parent the parent context
103 	 */
104 	public OsgiBundleXmlApplicationContext(String[] configLocations, ApplicationContext parent) {
105 		super(parent);
106 		setConfigLocations(configLocations);
107 	}
108 
109 	/**
110 	 * {@inheritDoc}
111 	 * 
112 	 * <p/> Loads the bean definitions via an
113 	 * <code>XmlBeanDefinitionReader</code>.
114 	 */
115 	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
116 		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
117 		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
118 
119 		// Configure the bean definition reader with this context's
120 		// resource loading environment.
121 		beanDefinitionReader.setResourceLoader(this);
122 
123 		NamespaceHandlerResolver nsResolver = createNamespaceHandlerResolver(getBundleContext(), getClassLoader());
124 		EntityResolver enResolver = createEntityResolver(getBundleContext(), getClassLoader());
125 
126 		beanDefinitionReader.setEntityResolver(enResolver);
127 		beanDefinitionReader.setNamespaceHandlerResolver(nsResolver);
128 
129 		// Allow a subclass to provide custom initialisation of the reader,
130 		// then proceed with actually loading the bean definitions.
131 		initBeanDefinitionReader(beanDefinitionReader);
132 		loadBeanDefinitions(beanDefinitionReader);
133 	}
134 
135 	/**
136 	 * Allows subclasses to do custom initialisation here.
137 	 * 
138 	 * @param beanDefinitionReader
139 	 */
140 	protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {
141 	}
142 
143 	/**
144 	 * Loads the bean definitions with the given
145 	 * <code>XmlBeanDefinitionReader</code>.
146 	 * <p>
147 	 * The lifecycle of the bean factory is handled by the refreshBeanFactory
148 	 * method; therefore this method is just supposed to load and/or register
149 	 * bean definitions.
150 	 * <p>
151 	 * Delegates to a ResourcePatternResolver for resolving location patterns
152 	 * into Resource instances.
153 	 * 
154 	 * @throws org.springframework.beans.BeansException in case of bean
155 	 * registration errors
156 	 * @throws java.io.IOException if the required XML document isn't found
157 	 * @see #refreshBeanFactory
158 	 * @see #getConfigLocations
159 	 * @see #getResources
160 	 * @see #getResourcePatternResolver
161 	 */
162 	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
163 		String[] configLocations = getConfigLocations();
164 		if (configLocations != null) {
165 			for (int i = 0; i < configLocations.length; i++) {
166 				reader.loadBeanDefinitions(configLocations[i]);
167 			}
168 		}
169 	}
170 
171 	/**
172 	 * Provide default locations for XML files. This implementation returns
173 	 * <code>META-INF/spring/*.xml</code> relying on the default resource
174 	 * environment for actual localisation. By default, the bundle space will be
175 	 * used for locating the resources.
176 	 * 
177 	 * <p/> <strong>Note:</strong> Instead of overriding this method, consider
178 	 * using the Spring-DM specific header inside your manifest bundle.
179 	 * 
180 	 * @return default XML configuration locations
181 	 */
182 	protected String[] getDefaultConfigLocations() {
183 		return new String[] { DEFAULT_CONFIG_LOCATION };
184 	}
185 
186 	/**
187 	 * Creates a special OSGi namespace handler resolver that first searches the
188 	 * bundle class path falling back to the namespace service published by
189 	 * Spring-DM. This allows embedded libraries that provide namespace handlers
190 	 * take priority over namespace provided by other bundles.
191 	 * 
192 	 * @param bundleContext the OSGi context of which the resolver should be
193 	 * aware of
194 	 * @param bundleClassLoader classloader for creating the OSGi namespace
195 	 * resolver proxy
196 	 * @return a OSGi aware namespace handler resolver
197 	 */
198 	private NamespaceHandlerResolver createNamespaceHandlerResolver(BundleContext bundleContext,
199 			ClassLoader bundleClassLoader) {
200 		Assert.notNull(bundleContext, "bundleContext is required");
201 		// create local namespace resolver
202 		// we'll use the default resolver which uses the bundle local class-loader
203 		NamespaceHandlerResolver localNamespaceResolver = new DefaultNamespaceHandlerResolver(bundleClassLoader);
204 
205 		// hook in OSGi namespace resolver
206 		NamespaceHandlerResolver osgiServiceNamespaceResolver = lookupNamespaceHandlerResolver(bundleContext,
207 			localNamespaceResolver);
208 
209 		DelegatedNamespaceHandlerResolver delegate = new DelegatedNamespaceHandlerResolver();
210 		delegate.addNamespaceHandler(localNamespaceResolver, "LocalNamespaceResolver for bundle "
211 				+ OsgiStringUtils.nullSafeNameAndSymName(bundleContext.getBundle()));
212 		delegate.addNamespaceHandler(osgiServiceNamespaceResolver, "OSGi Service resolver");
213 
214 		return delegate;
215 	}
216 
217 	/**
218 	 * Similar to
219 	 * {@link #createNamespaceHandlerResolver(BundleContext, ClassLoader, ClassLoader)},
220 	 * this method creates a special OSGi entity resolver that considers the
221 	 * bundle class path first, falling back to the entity resolver service
222 	 * provided by the Spring DM extender.
223 	 * 
224 	 * @param bundleContext the OSGi context of which the resolver should be
225 	 * aware of
226 	 * @param bundleClassLoader classloader for creating the OSGi namespace
227 	 * resolver proxy
228 	 * @return a OSGi aware entity resolver
229 	 */
230 	private EntityResolver createEntityResolver(BundleContext bundleContext, ClassLoader bundleClassLoader) {
231 		Assert.notNull(bundleContext, "bundleContext is required");
232 		// create local namespace resolver
233 		EntityResolver localEntityResolver = new DelegatingEntityResolver(bundleClassLoader);
234 		// hook in OSGi namespace resolver
235 		EntityResolver osgiServiceEntityResolver = lookupEntityResolver(bundleContext, localEntityResolver);
236 
237 		DelegatedEntityResolver delegate = new DelegatedEntityResolver();
238 		delegate.addEntityResolver(localEntityResolver, "LocalEntityResolver for bundle "
239 				+ OsgiStringUtils.nullSafeNameAndSymName(bundleContext.getBundle()));
240 
241 		// hook in OSGi namespace resolver
242 		delegate.addEntityResolver(osgiServiceEntityResolver, "OSGi Service resolver");
243 
244 		return delegate;
245 	}
246 
247 	private NamespaceHandlerResolver lookupNamespaceHandlerResolver(BundleContext bundleContext, Object fallbackObject) {
248 		return (NamespaceHandlerResolver) TrackingUtil.getService(new Class[] { NamespaceHandlerResolver.class }, null,
249 			NamespaceHandlerResolver.class.getClassLoader(), bundleContext, fallbackObject);
250 	}
251 
252 	private EntityResolver lookupEntityResolver(BundleContext bundleContext, Object fallbackObject) {
253 		return (EntityResolver) TrackingUtil.getService(new Class[] { EntityResolver.class }, null,
254 			EntityResolver.class.getClassLoader(), bundleContext, fallbackObject);
255 	}
256 
257 	public String[] getConfigLocations() {
258 		return super.getConfigLocations();
259 	}
260 }