1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
102
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
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
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
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
175 builder.setScope(parserContext.getContainingBeanDefinition().getScope());
176 }
177 if (parserContext.isDefaultLazyInit()) {
178
179 builder.setLazyInit(true);
180 }
181 doParse(element, parserContext, builder);
182
183
184
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
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
361 for (Iterator iter = listeners.iterator(); iter.hasNext();) {
362 Element listnr = (Element) iter.next();
363
364
365 Object target = null;
366
367
368 String targetName = null;
369
370
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
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
386 if (target instanceof RuntimeBeanReference) {
387 targetName = ((RuntimeBeanReference) target).getBeanName();
388 }
389 }
390 }
391
392
393
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
402 if (REF.equals(name))
403 targetName = attribute.getValue();
404 else
405 vals.addPropertyValue(Conventions.attributeNameToPropertyName(name), attribute.getValue());
406 }
407
408
409 RootBeanDefinition wrapperDef = new RootBeanDefinition(OsgiServiceLifecycleListenerAdapter.class);
410
411
412 if (targetName != null)
413 vals.addPropertyValue(TARGET_BEAN_NAME_PROP, targetName);
414
415 else
416 vals.addPropertyValue(TARGET_PROP, target);
417
418 wrapperDef.setPropertyValues(vals);
419
420 listenersRef.add(wrapperDef);
421 }
422
423 builder.addPropertyValue(LISTENERS_PROP, listenersRef);
424 }
425 }