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.config;
18  
19  import java.util.Iterator;
20  import java.util.List;
21  import java.util.Locale;
22  import java.util.Set;
23  
24  import org.springframework.beans.MutablePropertyValues;
25  import org.springframework.beans.factory.BeanFactoryUtils;
26  import org.springframework.beans.factory.config.BeanDefinitionHolder;
27  import org.springframework.beans.factory.config.BeanReferenceFactoryBean;
28  import org.springframework.beans.factory.config.RuntimeBeanReference;
29  import org.springframework.beans.factory.support.AbstractBeanDefinition;
30  import org.springframework.beans.factory.support.BeanDefinitionBuilder;
31  import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
32  import org.springframework.beans.factory.support.GenericBeanDefinition;
33  import org.springframework.beans.factory.support.ManagedList;
34  import org.springframework.beans.factory.support.RootBeanDefinition;
35  import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
36  import org.springframework.beans.factory.xml.ParserContext;
37  import org.springframework.core.Conventions;
38  import org.springframework.core.enums.StaticLabeledEnumResolver;
39  import org.springframework.osgi.config.ParserUtils.AttributeCallback;
40  import org.springframework.osgi.service.importer.support.Cardinality;
41  import org.springframework.util.Assert;
42  import org.springframework.util.StringUtils;
43  import org.springframework.util.xml.DomUtils;
44  import org.w3c.dom.Attr;
45  import org.w3c.dom.Document;
46  import org.w3c.dom.Element;
47  import org.w3c.dom.NamedNodeMap;
48  import org.w3c.dom.Node;
49  import org.w3c.dom.NodeList;
50  
51  /**
52   * Base class for parsing reference declarations. Contains common functionality
53   * such as adding listeners (and their custom methods), interfaces, cardinality
54   * and so on.
55   * 
56   * <p/>
57   * 
58   * <strong>Note:</strong> This parser also handles the cyclic injection between
59   * an importer and its listeners by breaking the chain by creating an adapter
60   * instead of the listener. The adapter will then do dependency lookup for the
61   * listener.
62   * 
63   * @author Costin Leau
64   * 
65   */
66  abstract class AbstractReferenceDefinitionParser extends AbstractBeanDefinitionParser {
67  
68  	/**
69  	 * Attribute callback dealing with 'cardinality' attribute.
70  	 * 
71  	 * @author Costin Leau
72  	 */
73  	class ReferenceAttributesCallback implements AttributeCallback {
74  
75  		/** global cardinality setting */
76  		public boolean isCardinalitySpecified = false;
77  
78  
79  		public boolean process(Element parent, Attr attribute, BeanDefinitionBuilder builder) {
80  			String name = attribute.getLocalName();
81  			String value = attribute.getValue();
82  
83  			// make sure the attribute is
84  			if (CARDINALITY.equals(name)) {
85  				isCardinalitySpecified = true;
86  				builder.addPropertyValue(CARDINALITY_PROP, determineCardinality(value));
87  				return false;
88  			}
89  
90  			else if (SERVICE_BEAN_NAME.equals(name)) {
91  				builder.addPropertyValue(SERVICE_BEAN_NAME_PROP, value);
92  				return false;
93  			}
94  
95  			else if (INTERFACE.equals(name)) {
96  				builder.addPropertyValue(INTERFACES_PROP, value);
97  				return false;
98  			}
99  
100 			else if (CONTEXT_CLASSLOADER.equals(name)) {
101 				// convert constant to upper case to let Spring do the
102 				// conversion
103 				String val = value.toUpperCase(Locale.ENGLISH).replace('-', '_');
104 				builder.addPropertyValue(CCL_PROP, val);
105 				return false;
106 			}
107 
108 			return true;
109 		}
110 	};
111 
112 
113 	// Class properties
114 	private static final String LISTENERS_PROP = "listeners";
115 
116 	private static final String CARDINALITY_PROP = "cardinality";
117 
118 	private static final String SERVICE_BEAN_NAME_PROP = "serviceBeanName";
119 
120 	private static final String INTERFACES_PROP = "interfaces";
121 
122 	private static final String CCL_PROP = "contextClassLoader";
123 
124 	private static final String TARGET_BEAN_NAME_PROP = "targetBeanName";
125 
126 	private static final String TARGET_PROP = "target";
127 
128 	// XML attributes/elements
129 	private static final String LISTENER = "listener";
130 
131 	private static final String REF = "ref";
132 
133 	private static final String INTERFACE = "interface";
134 
135 	private static final String INTERFACES = "interfaces";
136 
137 	private static final String CARDINALITY = "cardinality";
138 
139 	private static final String ZERO = "0";
140 
141 	private static final String SERVICE_BEAN_NAME = "bean-name";
142 
143 	private static final String CONTEXT_CLASSLOADER = "context-class-loader";
144 
145 	// document defaults
146 	protected OsgiDefaultsDefinition defaults = null;
147 
148 
149 	/**
150 	 * Get OSGi defaults (in case they haven't been resolved).
151 	 * 
152 	 * @param document
153 	 * @return
154 	 */
155 	private OsgiDefaultsDefinition resolveDefaults(Document document) {
156 		if (defaults == null) {
157 			defaults = ParserUtils.initOsgiDefaults(document);
158 		}
159 		return defaults;
160 	}
161 
162 	protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
163 		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
164 
165 		Class beanClass = getBeanClass(element);
166 		Assert.notNull(beanClass);
167 
168 		if (beanClass != null) {
169 			builder.getRawBeanDefinition().setBeanClass(beanClass);
170 		}
171 
172 		builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
173 		if (parserContext.isNested()) {
174 			// Inner bean definition must receive same scope as containing bean.
175 			builder.setScope(parserContext.getContainingBeanDefinition().getScope());
176 		}
177 		if (parserContext.isDefaultLazyInit()) {
178 			// Default-lazy-init applies to custom bean definitions as well.
179 			builder.setLazyInit(true);
180 		}
181 		doParse(element, parserContext, builder);
182 
183 		// check whether the bean is mandatory (and if it is, make it top-level
184 		// bean)
185 
186 		AbstractBeanDefinition def = builder.getBeanDefinition();
187 
188 		if (parserContext.isNested()) {
189 			StringBuffer id = new StringBuffer();
190 			String value = element.getAttribute(AbstractBeanDefinitionParser.ID_ATTRIBUTE);
191 			if (StringUtils.hasText(value)) {
192 				id.append(value);
193 				id.append(BeanFactoryUtils.GENERATED_BEAN_NAME_SEPARATOR);
194 			}
195 
196 			id.append(parserContext.getReaderContext().generateBeanName(def));
197 			BeanDefinitionHolder holder = new BeanDefinitionHolder(def, id.toString());
198 			BeanDefinitionReaderUtils.registerBeanDefinition(holder, parserContext.getRegistry());
199 			return createBeanReferenceDefinition(id.toString());
200 		}
201 
202 		return def;
203 	}
204 
205 	private AbstractBeanDefinition createBeanReferenceDefinition(String beanName) {
206 		GenericBeanDefinition def = new GenericBeanDefinition();
207 		def.setBeanClass(BeanReferenceFactoryBean.class);
208 		MutablePropertyValues mpv = new MutablePropertyValues();
209 		mpv.addPropertyValue(TARGET_BEAN_NAME_PROP, beanName);
210 		def.setPropertyValues(mpv);
211 		return def;
212 	}
213 
214 	protected void doParse(Element element, ParserContext context, BeanDefinitionBuilder builder) {
215 		if (defaults == null)
216 			resolveDefaults(element.getOwnerDocument());
217 
218 		ReferenceAttributesCallback callback = new ReferenceAttributesCallback();
219 
220 		parseAttributes(element, builder, new AttributeCallback[] { callback });
221 
222 		if (!callback.isCardinalitySpecified) {
223 			applyDefaultCardinality(builder, defaults);
224 		}
225 
226 		parseNestedElements(element, context, builder);
227 
228 		handleNestedDefinition(element, context, builder);
229 	}
230 
231 	/**
232 	 * If the reference is a nested bean, make it a top-level bean if it's a
233 	 * mandatory dependency. This is done so that the beans can be discovered at
234 	 * startup and the appCtx can start waiting.
235 	 * 
236 	 * @param element
237 	 * @param context
238 	 * @param builder
239 	 */
240 	protected void handleNestedDefinition(Element element, ParserContext context, BeanDefinitionBuilder builder) {
241 
242 	}
243 
244 	/**
245 	 * Allow subclasses to add their own callbacks.
246 	 * 
247 	 * @param element
248 	 * @param builder
249 	 * @param callbacks
250 	 */
251 	protected void parseAttributes(Element element, BeanDefinitionBuilder builder, AttributeCallback[] callbacks) {
252 		ParserUtils.parseCustomAttributes(element, builder, callbacks);
253 	}
254 
255 	/**
256 	 * Subclasses should override this method to provide the proper mandatory
257 	 * cardinality option/string.
258 	 * 
259 	 * @return mandatory cardinality as a string.
260 	 */
261 	protected abstract String mandatoryCardinality();
262 
263 	/**
264 	 * Subclasses should overide this method to provide the proper optional
265 	 * cardinality option/string.
266 	 * 
267 	 * @return optional cardinality as a string
268 	 */
269 	protected abstract String optionalCardinality();
270 
271 	/**
272 	 * Indicate the bean definition class for this element.
273 	 * 
274 	 * @param element
275 	 * @return
276 	 */
277 	protected abstract Class getBeanClass(Element element);
278 
279 	/**
280 	 * Utility method declared for reusability. It maintains the
281 	 * optional/mandatory option of the cardinality option and returns a
282 	 * specialized (singular/multiple) cardinality string.
283 	 * 
284 	 * @param value cardinality string
285 	 * @return the specialized (singular/multiple) cardinality.
286 	 */
287 	protected Object determineCardinality(String value) {
288 		return processCardinalityString((value.startsWith(ZERO) ? optionalCardinality() : mandatoryCardinality()));
289 	}
290 
291 	/**
292 	 * Since cardinality contains numbers and the constants name cannot start
293 	 * with a number we have to do conversion of the name or of the string. the
294 	 * latter is easier and quicker.
295 	 * 
296 	 * @param value
297 	 * @return
298 	 */
299 	private Cardinality processCardinalityString(String value) {
300 		return (Cardinality) StaticLabeledEnumResolver.instance().getLabeledEnumByLabel(Cardinality.class,
301 			value.toUpperCase(Locale.ENGLISH));
302 	}
303 
304 	/**
305 	 * Apply default cardinality.
306 	 * 
307 	 * @param builder
308 	 * @param defaults
309 	 */
310 	protected void applyDefaultCardinality(BeanDefinitionBuilder builder, OsgiDefaultsDefinition defaults) {
311 		builder.addPropertyValue(CARDINALITY_PROP, determineCardinality(defaults.getCardinality()));
312 	}
313 
314 	/**
315 	 * Parse nested elements. In case of a reference definition, this means
316 	 * using the listeners.
317 	 * 
318 	 * 
319 	 * @param element
320 	 * @param context
321 	 * @param builder
322 	 */
323 	protected void parseNestedElements(Element element, ParserContext context, BeanDefinitionBuilder builder) {
324 		parseInterfaces(element, context, builder);
325 		parseListeners(element, context, builder);
326 	}
327 
328 	/**
329 	 * Parse interfaces.
330 	 * 
331 	 * @param element
332 	 * @param context
333 	 * @param builder
334 	 */
335 	protected void parseInterfaces(Element parent, ParserContext parserContext, BeanDefinitionBuilder builder) {
336 
337 		Element element = DomUtils.getChildElementByTagName(parent, INTERFACES);
338 		if (element != null) {
339 			// check shortcut on the parent
340 			if (parent.hasAttribute(INTERFACE)) {
341 				parserContext.getReaderContext().error(
342 					"either 'interface' attribute or <intefaces> sub-element has be specified", parent);
343 			}
344 			Set interfaces = parserContext.getDelegate().parseSetElement(element, builder.getBeanDefinition());
345 			builder.addPropertyValue(INTERFACES_PROP, interfaces);
346 		}
347 	}
348 
349 	/**
350 	 * Parse listeners.
351 	 * 
352 	 * @param element
353 	 * @param context
354 	 * @param builder
355 	 */
356 	protected void parseListeners(Element element, ParserContext context, BeanDefinitionBuilder builder) {
357 		List listeners = DomUtils.getChildElementsByTagName(element, LISTENER);
358 
359 		ManagedList listenersRef = new ManagedList();
360 		// loop on listeners
361 		for (Iterator iter = listeners.iterator(); iter.hasNext();) {
362 			Element listnr = (Element) iter.next();
363 
364 			// wrapper target object
365 			Object target = null;
366 
367 			// target bean name (in case of a reference)
368 			String targetName = null;
369 
370 			// filter elements
371 			NodeList nl = listnr.getChildNodes();
372 
373 			for (int i = 0; i < nl.getLength(); i++) {
374 				Node node = nl.item(i);
375 				if (node instanceof Element) {
376 					Element beanDef = (Element) node;
377 
378 					// check inline ref
379 					if (listnr.hasAttribute(REF))
380 						context.getReaderContext().error(
381 							"nested bean declaration is not allowed if 'ref' attribute has been specified", beanDef);
382 
383 					target = context.getDelegate().parsePropertySubElement(beanDef, builder.getBeanDefinition());
384 
385 					// if this is a bean reference (nested <ref>), extract the name
386 					if (target instanceof RuntimeBeanReference) {
387 						targetName = ((RuntimeBeanReference) target).getBeanName();
388 					}
389 				}
390 			}
391 
392 			// extract bind/unbind attributes from <osgi:listener>
393 			// Element
394 			MutablePropertyValues vals = new MutablePropertyValues();
395 
396 			NamedNodeMap attrs = listnr.getAttributes();
397 			for (int x = 0; x < attrs.getLength(); x++) {
398 				Attr attribute = (Attr) attrs.item(x);
399 				String name = attribute.getLocalName();
400 
401 				// extract ref value
402 				if (REF.equals(name))
403 					targetName = attribute.getValue();
404 				else
405 					vals.addPropertyValue(Conventions.attributeNameToPropertyName(name), attribute.getValue());
406 			}
407 
408 			// create serviceListener adapter
409 			RootBeanDefinition wrapperDef = new RootBeanDefinition(OsgiServiceLifecycleListenerAdapter.class);
410 
411 			// set the target name (if we have one)
412 			if (targetName != null)
413 				vals.addPropertyValue(TARGET_BEAN_NAME_PROP, targetName);
414 			// else set the actual target
415 			else
416 				vals.addPropertyValue(TARGET_PROP, target);
417 
418 			wrapperDef.setPropertyValues(vals);
419 			// add listener to list
420 			listenersRef.add(wrapperDef);
421 		}
422 
423 		builder.addPropertyValue(LISTENERS_PROP, listenersRef);
424 	}
425 }