KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > beans > factory > support > PropertiesBeanDefinitionReader


1 /*
2  * Copyright 2002-2006 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.beans.factory.support;
18
19 import java.io.IOException JavaDoc;
20 import java.io.InputStream JavaDoc;
21 import java.io.InputStreamReader JavaDoc;
22 import java.util.Enumeration JavaDoc;
23 import java.util.HashMap JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.util.Map JavaDoc;
26 import java.util.Properties JavaDoc;
27 import java.util.ResourceBundle JavaDoc;
28
29 import org.springframework.beans.BeansException;
30 import org.springframework.beans.MutablePropertyValues;
31 import org.springframework.beans.PropertyAccessor;
32 import org.springframework.beans.factory.BeanDefinitionStoreException;
33 import org.springframework.beans.factory.CannotLoadBeanClassException;
34 import org.springframework.beans.factory.config.ConstructorArgumentValues;
35 import org.springframework.beans.factory.config.RuntimeBeanReference;
36 import org.springframework.core.io.Resource;
37 import org.springframework.core.io.support.EncodedResource;
38 import org.springframework.util.DefaultPropertiesPersister;
39 import org.springframework.util.PropertiesPersister;
40 import org.springframework.util.StringUtils;
41
42 /**
43  * Bean definition reader for a simple properties format.
44  *
45  * <p>Provides bean definition registration methods for Map/Properties and
46  * ResourceBundle. Typically applied to a DefaultListableBeanFactory.
47  *
48  * <p><b>Example:</b>
49  *
50  * <pre class="code">employee.(class)=MyClass // bean is of class MyClass
51  * employee.(abstract)=true // this bean can't be instantiated directly
52  * employee.group=Insurance // real property
53  * employee.usesDialUp=false // real property (potentially overridden)
54  *
55  * salesrep.(parent)=employee // derives from "employee" bean definition
56  * salesrep.(lazy-init)=true // lazily initialize this singleton bean
57  * salesrep.manager(ref)=tony // reference to another bean
58  * salesrep.department=Sales // real property
59  *
60  * techie.(parent)=employee // derives from "employee" bean definition
61  * techie.(singleton)=false // bean is a prototype (not a shared instance)
62  * techie.manager(ref)=jeff // reference to another bean
63  * techie.department=Engineering // real property
64  * techie.usesDialUp=true // real property (overriding parent value)</pre>
65  *
66  * ceo.$0(ref)=secretary // inject 'secretary' bean as 0th constructor arg
67  * ceo.$1=1000000 // inject value '1000000' at 1st constructor arg
68  *
69  * <em><b>Note:</b> As of Spring 1.2.6, the use of <code>class</code> and
70  * <code>parent</code> has been deprecated in favor of <code>(class)</code> and
71  * <code>(parent)</code>, for consistency with all other special properties.
72  * Users should note that support for <code>class</code> and <code>parent</code>
73  * as special properties rather then actual bean properties will be removed in a
74  * future version.</em>
75  *
76  * @author Rod Johnson
77  * @author Juergen Hoeller
78  * @author Rob Harrop
79  * @since 26.11.2003
80  * @see DefaultListableBeanFactory
81  */

82 public class PropertiesBeanDefinitionReader extends AbstractBeanDefinitionReader {
83
84     /**
85      * Value of a T/F attribute that represents true.
86      * Anything else represents false. Case seNsItive.
87      */

88     public static final String JavaDoc TRUE_VALUE = "true";
89
90     /**
91      * Separator between bean name and property name.
92      * We follow normal Java conventions.
93      */

94     public static final String JavaDoc SEPARATOR = ".";
95
96     /**
97      * Special key to distinguish owner.(class)=com.myapp.MyClass
98      */

99     public static final String JavaDoc CLASS_KEY = "(class)";
100
101     /**
102      * Special key to distinguish owner.class=com.myapp.MyClass
103      * Deprecated in favor of .(class)=
104      */

105     private static final String JavaDoc DEPRECATED_CLASS_KEY = "class";
106
107     /**
108      * Special key to distinguish owner.(parent)=parentBeanName
109      */

110     public static final String JavaDoc PARENT_KEY = "(parent)";
111
112     /**
113      * Special key to distinguish owner.(abstract)=true
114      * Default is "false".
115      */

116     public static final String JavaDoc ABSTRACT_KEY = "(abstract)";
117
118     /**
119      * Special key to distinguish owner.(singleton)=true
120      * Default is "true".
121      */

122     public static final String JavaDoc SINGLETON_KEY = "(singleton)";
123
124     /**
125      * Special key to distinguish owner.(lazy-init)=true
126      * Default is "false".
127      */

128     public static final String JavaDoc LAZY_INIT_KEY = "(lazy-init)";
129
130     /**
131      * Property suffix for references to other beans in the current
132      * BeanFactory: e.g. owner.dog(ref)=fido.
133      * Whether this is a reference to a singleton or a prototype
134      * will depend on the definition of the target bean.
135      */

136     public static final String JavaDoc REF_SUFFIX = "(ref)";
137
138     /**
139      * Prefix before values referencing other beans.
140      */

141     public static final String JavaDoc REF_PREFIX = "*";
142
143     /**
144      * Prefix used to denote a constructor argument definition.
145      */

146     public static final String JavaDoc CONSTRUCTOR_ARG_PREFIX = "$";
147
148
149     private String JavaDoc defaultParentBean;
150
151     private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();
152
153
154     /**
155      * Create new PropertiesBeanDefinitionReader for the given bean factory.
156      */

157     public PropertiesBeanDefinitionReader(BeanDefinitionRegistry beanFactory) {
158         super(beanFactory);
159     }
160
161     /**
162      * Set the default parent bean for this bean factory.
163      * If a child bean definition handled by this factory provides neither
164      * a parent nor a class attribute, this default value gets used.
165      * <p>Can be used e.g. for view definition files, to define a parent
166      * with a default view class and common attributes for all views.
167      * View definitions that define their own parent or carry their own
168      * class can still override this.
169      * <p>Strictly speaking, the rule that a default parent setting does
170      * not apply to a bean definition that carries a class is there for
171      * backwards compatiblity reasons. It still matches the typical use case.
172      */

173     public void setDefaultParentBean(String JavaDoc defaultParentBean) {
174         this.defaultParentBean = defaultParentBean;
175     }
176
177     /**
178      * Return the default parent bean for this bean factory.
179      */

180     public String JavaDoc getDefaultParentBean() {
181         return defaultParentBean;
182     }
183
184     /**
185      * Set the PropertiesPersister to use for parsing properties files.
186      * The default is DefaultPropertiesPersister.
187      * @see org.springframework.util.DefaultPropertiesPersister
188      */

189     public void setPropertiesPersister(PropertiesPersister propertiesPersister) {
190         this.propertiesPersister =
191                 (propertiesPersister != null ? propertiesPersister : new DefaultPropertiesPersister());
192     }
193
194     /**
195      * Return the PropertiesPersister to use for parsing properties files.
196      */

197     public PropertiesPersister getPropertiesPersister() {
198         return propertiesPersister;
199     }
200
201
202     /**
203      * Load bean definitions from the specified properties file,
204      * using all property keys (i.e. not filtering by prefix).
205      * @param resource the resource descriptor for the properties file
206      * @return the number of bean definitions found
207      * @throws BeansException in case of loading or parsing errors
208      * @see #loadBeanDefinitions(org.springframework.core.io.Resource, String)
209      */

210     public int loadBeanDefinitions(Resource resource) throws BeansException {
211         return loadBeanDefinitions(new EncodedResource(resource), null);
212     }
213
214     /**
215      * Load bean definitions from the specified properties file.
216      * @param resource the resource descriptor for the properties file
217      * @param prefix match or filter within the keys in the map: e.g. 'beans.'
218      * (can be empty or <code>null</code>)
219      * @return the number of bean definitions found
220      * @throws BeansException in case of loading or parsing errors
221      */

222     public int loadBeanDefinitions(Resource resource, String JavaDoc prefix) throws BeansException {
223         return loadBeanDefinitions(new EncodedResource(resource), prefix);
224     }
225
226     /**
227      * Load bean definitions from the specified properties file.
228      * @param encodedResource the resource descriptor for the properties file,
229      * allowing to specify an encoding to use for parsing the file
230      * @return the number of bean definitions found
231      * @throws BeansException in case of loading or parsing errors
232      */

233     public int loadBeanDefinitions(EncodedResource encodedResource) throws BeansException {
234         return loadBeanDefinitions(encodedResource, null);
235     }
236
237     /**
238      * Load bean definitions from the specified properties file.
239      * @param encodedResource the resource descriptor for the properties file,
240      * allowing to specify an encoding to use for parsing the file
241      * @return the number of bean definitions found
242      * @throws BeansException in case of loading or parsing errors
243      */

244     public int loadBeanDefinitions(EncodedResource encodedResource, String JavaDoc prefix) throws BeansException {
245         Properties JavaDoc props = new Properties JavaDoc();
246         try {
247             InputStream JavaDoc is = encodedResource.getResource().getInputStream();
248             try {
249                 if (encodedResource.getEncoding() != null) {
250                     getPropertiesPersister().load(props, new InputStreamReader JavaDoc(is, encodedResource.getEncoding()));
251                 }
252                 else {
253                     getPropertiesPersister().load(props, is);
254                 }
255             }
256             finally {
257                 is.close();
258             }
259             return registerBeanDefinitions(props, prefix, encodedResource.getResource().getDescription());
260         }
261         catch (IOException JavaDoc ex) {
262             throw new BeanDefinitionStoreException("Could not parse properties from " + encodedResource.getResource(), ex);
263         }
264     }
265
266     /**
267      * Register bean definitions contained in a resource bundle,
268      * using all property keys (i.e. not filtering by prefix).
269      * @param rb the ResourceBundle to load from
270      * @return the number of bean definitions found
271      * @throws BeansException in case of loading or parsing errors
272      * @see #registerBeanDefinitions(java.util.ResourceBundle, String)
273      */

274     public int registerBeanDefinitions(ResourceBundle JavaDoc rb) throws BeanDefinitionStoreException {
275         return registerBeanDefinitions(rb, null);
276     }
277
278     /**
279      * Register bean definitions contained in a ResourceBundle.
280      * <p>Similar syntax as for a Map. This method is useful to enable
281      * standard Java internationalization support.
282      * @param rb the ResourceBundle to load from
283      * @param prefix match or filter within the keys in the map: e.g. 'beans.'
284      * (can be empty or <code>null</code>)
285      * @return the number of bean definitions found
286      * @throws BeansException in case of loading or parsing errors
287      */

288     public int registerBeanDefinitions(ResourceBundle JavaDoc rb, String JavaDoc prefix) throws BeanDefinitionStoreException {
289         // Simply create a map and call overloaded method.
290
Map JavaDoc map = new HashMap JavaDoc();
291         Enumeration JavaDoc keys = rb.getKeys();
292         while (keys.hasMoreElements()) {
293             String JavaDoc key = (String JavaDoc) keys.nextElement();
294             map.put(key, rb.getObject(key));
295         }
296         return registerBeanDefinitions(map, prefix);
297     }
298
299
300     /**
301      * Register bean definitions contained in a Map,
302      * using all property keys (i.e. not filtering by prefix).
303      * @param map Map: name -> property (String or Object). Property values
304      * will be strings if coming from a Properties file etc. Property names
305      * (keys) <b>must</b> be Strings. Class keys must be Strings.
306      * @return the number of bean definitions found
307      * @throws BeansException in case of loading or parsing errors
308      * @see #registerBeanDefinitions(java.util.Map, String, String)
309      */

310     public int registerBeanDefinitions(Map JavaDoc map) throws BeansException {
311         return registerBeanDefinitions(map, null);
312     }
313
314     /**
315      * Register bean definitions contained in a Map.
316      * Ignore ineligible properties.
317      * @param map Map name -> property (String or Object). Property values
318      * will be strings if coming from a Properties file etc. Property names
319      * (keys) <b>must</b> be Strings. Class keys must be Strings.
320      * @param prefix The match or filter within the keys in the map: e.g. 'beans.'
321      * @return the number of bean definitions found
322      * @throws BeansException in case of loading or parsing errors
323      */

324     public int registerBeanDefinitions(Map JavaDoc map, String JavaDoc prefix) throws BeansException {
325         return registerBeanDefinitions(map, prefix, "Map " + map);
326     }
327
328     /**
329      * Register bean definitions contained in a Map.
330      * Ignore ineligible properties.
331      * @param map Map name -> property (String or Object). Property values
332      * will be strings if coming from a Properties file etc. Property names
333      * (keys) <b>must</b> be strings. Class keys must be Strings.
334      * @param prefix match or filter within the keys in the map: e.g. 'beans.'
335      * (can be empty or <code>null</code>)
336      * @param resourceDescription description of the resource that the Map came from
337      * (for logging purposes)
338      * @return the number of bean definitions found
339      * @throws BeansException in case of loading or parsing errors
340      * @see #registerBeanDefinitions(Map, String)
341      */

342     public int registerBeanDefinitions(Map JavaDoc map, String JavaDoc prefix, String JavaDoc resourceDescription)
343             throws BeansException {
344
345         if (prefix == null) {
346             prefix = "";
347         }
348         int beanCount = 0;
349
350         for (Iterator JavaDoc it = map.keySet().iterator(); it.hasNext();) {
351             Object JavaDoc key = it.next();
352             if (!(key instanceof String JavaDoc)) {
353                 throw new IllegalArgumentException JavaDoc("Illegal key [" + key + "]: only Strings allowed");
354             }
355             String JavaDoc keyString = (String JavaDoc) key;
356             if (keyString.startsWith(prefix)) {
357                 // Key is of form: prefix<name>.property
358
String JavaDoc nameAndProperty = keyString.substring(prefix.length());
359                 // Find dot before property name, ignoring dots in property keys.
360
int sepIdx = -1;
361                 int propKeyIdx = nameAndProperty.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX);
362                 if (propKeyIdx != -1) {
363                     sepIdx = nameAndProperty.lastIndexOf(SEPARATOR, propKeyIdx);
364                 }
365                 else {
366                     sepIdx = nameAndProperty.lastIndexOf(SEPARATOR);
367                 }
368                 if (sepIdx != -1) {
369                     String JavaDoc beanName = nameAndProperty.substring(0, sepIdx);
370                     if (logger.isDebugEnabled()) {
371                         logger.debug("Found bean name '" + beanName + "'");
372                     }
373                     if (!getBeanFactory().containsBeanDefinition(beanName)) {
374                         // If we haven't already registered it...
375
registerBeanDefinition(beanName, map, prefix + beanName, resourceDescription);
376                         ++beanCount;
377                     }
378                 }
379                 else {
380                     // Ignore it: It wasn't a valid bean name and property,
381
// although it did start with the required prefix.
382
if (logger.isDebugEnabled()) {
383                         logger.debug("Invalid bean name and property [" + nameAndProperty + "]");
384                     }
385                 }
386             }
387         }
388
389         return beanCount;
390     }
391
392     /**
393      * Get all property values, given a prefix (which will be stripped)
394      * and add the bean they define to the factory with the given name
395      * @param beanName name of the bean to define
396      * @param map Map containing string pairs
397      * @param prefix prefix of each entry, which will be stripped
398      * @param resourceDescription description of the resource that the Map came from
399      * (for logging purposes)
400      * @throws BeansException if the bean definition could not be parsed or registered
401      */

402     protected void registerBeanDefinition(String JavaDoc beanName, Map JavaDoc map, String JavaDoc prefix, String JavaDoc resourceDescription)
403             throws BeansException {
404
405         String JavaDoc className = null;
406         String JavaDoc parent = null;
407         boolean isAbstract = false;
408         boolean singleton = true;
409         boolean lazyInit = false;
410
411         ConstructorArgumentValues cas = new ConstructorArgumentValues();
412         MutablePropertyValues pvs = new MutablePropertyValues();
413
414         for (Iterator JavaDoc it = map.entrySet().iterator(); it.hasNext();) {
415             Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
416             String JavaDoc key = StringUtils.trimWhitespace((String JavaDoc) entry.getKey());
417             if (key.startsWith(prefix + SEPARATOR)) {
418                 String JavaDoc property = key.substring(prefix.length() + SEPARATOR.length());
419                 if (isClassKey(property)) {
420                     className = StringUtils.trimWhitespace((String JavaDoc) entry.getValue());
421                 }
422                 else if (PARENT_KEY.equals(property)) {
423                     parent = StringUtils.trimWhitespace((String JavaDoc) entry.getValue());
424                 }
425                 else if (ABSTRACT_KEY.equals(property)) {
426                     String JavaDoc val = StringUtils.trimWhitespace((String JavaDoc) entry.getValue());
427                     isAbstract = TRUE_VALUE.equals(val);
428                 }
429                 else if (SINGLETON_KEY.equals(property)) {
430                     String JavaDoc val = StringUtils.trimWhitespace((String JavaDoc) entry.getValue());
431                     singleton = (val == null) || TRUE_VALUE.equals(val);
432                 }
433                 else if (LAZY_INIT_KEY.equals(property)) {
434                     String JavaDoc val = StringUtils.trimWhitespace((String JavaDoc) entry.getValue());
435                     lazyInit = TRUE_VALUE.equals(val);
436                 }
437                 else if (property.startsWith(CONSTRUCTOR_ARG_PREFIX)) {
438                     if (property.endsWith(REF_SUFFIX)) {
439                         int index = Integer.parseInt(property.substring(1, property.length() - REF_SUFFIX.length()));
440                         cas.addIndexedArgumentValue(index, new RuntimeBeanReference(entry.getValue().toString()));
441                     }
442                     else {
443                         int index = Integer.parseInt(property.substring(1));
444                         cas.addIndexedArgumentValue(index, readValue(entry));
445                     }
446                 }
447                 else if (property.endsWith(REF_SUFFIX)) {
448                     // This isn't a real property, but a reference to another prototype
449
// Extract property name: property is of form dog(ref)
450
property = property.substring(0, property.length() - REF_SUFFIX.length());
451                     String JavaDoc ref = StringUtils.trimWhitespace((String JavaDoc) entry.getValue());
452
453                     // It doesn't matter if the referenced bean hasn't yet been registered:
454
// this will ensure that the reference is resolved at runtime.
455
Object JavaDoc val = new RuntimeBeanReference(ref);
456                     pvs.addPropertyValue(property, val);
457                 }
458                 else{
459                     // It's a normal bean property.
460
pvs.addPropertyValue(property, readValue(entry));
461                 }
462             }
463         }
464
465         if (logger.isDebugEnabled()) {
466             logger.debug("Registering bean definition for bean name '" + beanName + "' with " + pvs);
467         }
468
469         // Just use default parent if we're not dealing with the parent itself,
470
// and if there's no class name specified. The latter has to happen for
471
// backwards compatibility reasons.
472
if (parent == null && className == null && !beanName.equals(this.defaultParentBean)) {
473             parent = this.defaultParentBean;
474         }
475
476         try {
477             AbstractBeanDefinition bd = BeanDefinitionReaderUtils.createBeanDefinition(
478                     parent, className, getBeanClassLoader());
479             bd.setAbstract(isAbstract);
480             bd.setSingleton(singleton);
481             bd.setLazyInit(lazyInit);
482             bd.setConstructorArgumentValues(cas);
483             bd.setPropertyValues(pvs);
484             getBeanFactory().registerBeanDefinition(beanName, bd);
485         }
486         catch (ClassNotFoundException JavaDoc ex) {
487             throw new CannotLoadBeanClassException(resourceDescription, beanName, className, ex);
488         }
489         catch (LinkageError JavaDoc err) {
490             throw new CannotLoadBeanClassException(resourceDescription, beanName, className, err);
491         }
492     }
493
494     /**
495      * Indicates whether the supplied property matches the class property of
496      * the bean definition.
497      */

498     private boolean isClassKey(String JavaDoc property) {
499         if (CLASS_KEY.equals(property)) {
500             return true;
501         }
502         else if (DEPRECATED_CLASS_KEY.equals(property)) {
503             if (logger.isWarnEnabled()) {
504                 logger.warn("Use of 'class' property in [" + getClass().getName() + "] is deprecated in favor of '(class)'");
505             }
506             return true;
507         }
508         return false;
509     }
510
511     /**
512      * Reads the value of the entry. Correctly interprets bean references for
513      * values that are prefixed with an asterisk.
514      */

515     private Object JavaDoc readValue(Map.Entry JavaDoc entry) {
516         Object JavaDoc val = entry.getValue();
517         if (val instanceof String JavaDoc) {
518             String JavaDoc strVal = (String JavaDoc) val;
519             // If it starts with a reference prefix...
520
if (strVal.startsWith(REF_PREFIX)) {
521                 // Expand the reference.
522
String JavaDoc targetName = strVal.substring(1);
523                 if (targetName.startsWith(REF_PREFIX)) {
524                     // Escaped prefix -> use plain value.
525
val = targetName;
526                 }
527                 else {
528                     val = new RuntimeBeanReference(targetName);
529                 }
530             }
531         }
532         return val;
533     }
534
535 }
536
Popular Tags