1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.shiro.config.ogdl;
20
21 import java.beans.PropertyDescriptor;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Collection;
25 import java.util.Collections;
26 import java.util.LinkedHashMap;
27 import java.util.LinkedHashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.function.Function;
32
33 import org.apache.commons.beanutils.BeanUtilsBean;
34 import org.apache.commons.beanutils.ConvertUtilsBean;
35 import org.apache.commons.beanutils.FluentPropertyBeanIntrospector;
36 import org.apache.commons.beanutils.SuppressPropertiesBeanIntrospector;
37 import org.apache.shiro.lang.codec.Base64;
38 import org.apache.shiro.lang.codec.Hex;
39 import org.apache.shiro.config.ConfigurationException;
40 import org.apache.shiro.config.ogdl.event.BeanEvent;
41 import org.apache.shiro.config.ogdl.event.ConfiguredBeanEvent;
42 import org.apache.shiro.config.ogdl.event.DestroyedBeanEvent;
43 import org.apache.shiro.config.ogdl.event.InitializedBeanEvent;
44 import org.apache.shiro.config.ogdl.event.InstantiatedBeanEvent;
45 import org.apache.shiro.event.EventBus;
46 import org.apache.shiro.event.EventBusAware;
47 import org.apache.shiro.event.Subscribe;
48 import org.apache.shiro.event.support.DefaultEventBus;
49 import org.apache.shiro.lang.util.Assert;
50 import org.apache.shiro.lang.util.ByteSource;
51 import org.apache.shiro.lang.util.ClassUtils;
52 import org.apache.shiro.lang.util.Factory;
53 import org.apache.shiro.lang.util.LifecycleUtils;
54 import org.apache.shiro.lang.util.Nameable;
55 import org.apache.shiro.lang.util.StringUtils;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59
60
61
62
63
64
65
66
67
68
69
70 @SuppressWarnings("checkstyle:MethodCount")
71 public class ReflectionBuilder {
72
73 private static final Logger LOGGER = LoggerFactory.getLogger(ReflectionBuilder.class);
74
75 private static final String OBJECT_REFERENCE_BEGIN_TOKEN = "$";
76 private static final String ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN = "\\$";
77 private static final String GLOBAL_PROPERTY_PREFIX = "shiro";
78 private static final char MAP_KEY_VALUE_DELIMITER = ':';
79 private static final String HEX_BEGIN_TOKEN = "0x";
80 private static final String NULL_VALUE_TOKEN = "null";
81 private static final String EMPTY_STRING_VALUE_TOKEN = "\"\"";
82 private static final char STRING_VALUE_DELIMETER = '"';
83 private static final char MAP_PROPERTY_BEGIN_TOKEN = '[';
84 private static final char MAP_PROPERTY_END_TOKEN = ']';
85
86 private static final String EVENT_BUS_NAME = "eventBus";
87
88 private final Map<String, Object> objects;
89
90
91
92
93
94
95 private Interpolator interpolator;
96
97
98
99
100 private EventBus eventBus;
101
102
103
104
105
106
107
108
109
110 private final Map<String, Object> registeredEventSubscribers;
111
112
113
114
115 private final BeanUtilsBean beanUtilsBean;
116
117 private Function<String, ?> alternateObjectSupplier = name -> null;
118
119 public ReflectionBuilder() {
120 this(null);
121 }
122
123 public ReflectionBuilder(Map<String, ?> defaults) {
124
125
126
127 beanUtilsBean = new BeanUtilsBean(new ConvertUtilsBean() {
128 @Override
129 @SuppressWarnings("unchecked")
130 public Object convert(String value, Class<?> clazz) {
131 if (clazz.isEnum()) {
132 return Enum.valueOf((Class<Enum>) clazz, value);
133 } else {
134 return super.convert(value, clazz);
135 }
136 }
137 });
138 beanUtilsBean.getPropertyUtils().addBeanIntrospector(SuppressPropertiesBeanIntrospector.SUPPRESS_CLASS);
139 beanUtilsBean.getPropertyUtils().addBeanIntrospector(new FluentPropertyBeanIntrospector());
140
141 this.interpolator = createInterpolator();
142
143 this.objects = createDefaultObjectMap();
144 this.registeredEventSubscribers = new LinkedHashMap<>();
145 apply(defaults);
146 }
147
148 private void apply(Map<String, ?> objects) {
149 if (!isEmpty(objects)) {
150 this.objects.putAll(objects);
151 }
152 EventBus found = findEventBus(this.objects);
153 Assert.notNull(found, "An " + EventBus.class.getName() + " instance must be present in the object defaults");
154 enableEvents(found);
155 }
156
157
158 private Map<String, Object> createDefaultObjectMap() {
159 Map<String, Object> map = new LinkedHashMap<String, Object>();
160 map.put(EVENT_BUS_NAME, new DefaultEventBus());
161 return map;
162 }
163
164 public Map<String, ?> getObjects() {
165 return objects;
166 }
167
168
169
170
171 public void setObjects(Map<String, ?> objects) {
172 this.objects.clear();
173 this.objects.putAll(createDefaultObjectMap());
174 apply(objects);
175 }
176
177
178 private void enableEvents(EventBus eventBus) {
179 Assert.notNull(eventBus, "EventBus argument cannot be null.");
180
181 for (Object subscriber : this.registeredEventSubscribers.values()) {
182 this.eventBus.unregister(subscriber);
183 }
184 this.registeredEventSubscribers.clear();
185
186 this.eventBus = eventBus;
187
188 for (Map.Entry<String, Object> entry : this.objects.entrySet()) {
189 enableEventsIfNecessary(entry.getValue(), entry.getKey());
190 }
191 }
192
193
194 private void enableEventsIfNecessary(Object bean, String name) {
195 boolean applied = applyEventBusIfNecessary(bean);
196 if (!applied) {
197
198
199
200
201
202 if (isEventSubscriber(bean, name)) {
203
204 this.eventBus.register(bean);
205 this.registeredEventSubscribers.put(name, bean);
206 }
207 }
208 }
209
210
211 private boolean isEventSubscriber(Object bean, String name) {
212 List<?> annotatedMethods = ClassUtils.getAnnotatedMethods(bean.getClass(), Subscribe.class);
213 return !isEmpty(annotatedMethods);
214 }
215
216
217
218
219
220
221
222 public void setAlternateObjectSupplier(Function<String, ?> alternateObjectSupplier) {
223 this.alternateObjectSupplier = alternateObjectSupplier;
224 }
225
226
227 protected EventBus findEventBus(Map<String, ?> objects) {
228
229 if (isEmpty(objects)) {
230 return null;
231 }
232
233
234 Object value = objects.get(EVENT_BUS_NAME);
235 if (value instanceof EventBus) {
236 return (EventBus) value;
237 }
238
239
240 for (Object v : objects.values()) {
241 if (v instanceof EventBus) {
242 return (EventBus) v;
243 }
244 }
245
246 return null;
247 }
248
249 private boolean applyEventBusIfNecessary(Object value) {
250 if (value instanceof EventBusAware) {
251 ((EventBusAware) value).setEventBus(this.eventBus);
252 return true;
253 }
254 return false;
255 }
256
257 public Object getBean(String id) {
258 return objects.get(id);
259 }
260
261 @SuppressWarnings("unchecked")
262 public <T> T getBean(String id, Class<T> requiredType) {
263 if (requiredType == null) {
264 throw new NullPointerException("requiredType argument cannot be null.");
265 }
266 Object bean = getBean(id);
267 if (bean == null) {
268 return null;
269 }
270 Assert.state(requiredType.isAssignableFrom(bean.getClass()),
271 "Bean with id [" + id + "] is not of the required type [" + requiredType.getName() + "].");
272 return (T) bean;
273 }
274
275 private String parseBeanId(String lhs) {
276 Assert.notNull(lhs);
277 if (lhs.indexOf('.') < 0) {
278 return lhs;
279 }
280 String classSuffix = ".class";
281 int index = lhs.indexOf(classSuffix);
282 if (index >= 0) {
283 return lhs.substring(0, index);
284 }
285 return null;
286 }
287
288 @SuppressWarnings({"unchecked"})
289 public Map<String, ?> buildObjects(Map<String, String> kvPairs) {
290
291 if (kvPairs != null && !kvPairs.isEmpty()) {
292
293 BeanConfigurationProcessor processor = new BeanConfigurationProcessor();
294
295 for (Map.Entry<String, String> entry : kvPairs.entrySet()) {
296 String lhs = entry.getKey();
297 String rhs = interpolator.interpolate(entry.getValue());
298
299 String beanId = parseBeanId(lhs);
300
301 if (beanId != null) {
302 processor.add(new InstantiationStatement(beanId, rhs));
303
304 } else {
305 processor.add(new AssignmentStatement(lhs, rhs));
306 }
307 }
308
309 processor.execute();
310
311
312 objects.keySet().stream()
313 .filter(key -> !kvPairs.containsKey(key))
314 .forEach(key -> LifecycleUtils.init(objects.get(key)));
315 } else {
316
317 LifecycleUtils.init(objects.values());
318 }
319
320 return objects;
321 }
322
323 public void destroy() {
324 final Map<String, Object> immutableObjects = Collections.unmodifiableMap(objects);
325
326
327 List<Map.Entry<String, ?>> entries = new ArrayList<Map.Entry<String, ?>>(objects.entrySet());
328 Collections.reverse(entries);
329
330 for (Map.Entry<String, ?> entry : entries) {
331 String id = entry.getKey();
332 Object bean = entry.getValue();
333
334
335
336 if (bean != this.eventBus) {
337 LifecycleUtils.destroy(bean);
338 BeanEvent event = new DestroyedBeanEvent(id, bean, immutableObjects);
339 eventBus.publish(event);
340
341 this.eventBus.unregister(bean);
342 }
343 }
344
345 LifecycleUtils.destroy(this.eventBus);
346 }
347
348 protected void createNewInstance(Map<String, Object> objects, String name, String value) {
349
350 Object currentInstance = objects.get(name);
351 if (currentInstance != null) {
352 LOGGER.info("An instance with name '{}' already exists. "
353 + "Redefining this object as a new instance of type {}", name, value);
354 }
355
356
357 Object instance;
358 try {
359 instance = ClassUtils.newInstance(value);
360 if (instance instanceof Nameable) {
361 ((Nameable) instance).setName(name);
362 }
363 } catch (Exception e) {
364 instance = alternateObjectSupplier.apply(value);
365 if (instance == null) {
366 String msg = "Unable to instantiate class [" + value + "] for object named '" + name + "'. "
367 + "Please ensure you've specified the fully qualified class name correctly.";
368 throw new ConfigurationException(msg, e);
369 }
370 }
371 objects.put(name, instance);
372 }
373
374 protected void applyProperty(String key, String value, Map objects) {
375
376 int index = key.indexOf('.');
377
378 if (index >= 0) {
379 String name = key.substring(0, index);
380 String property = key.substring(index + 1, key.length());
381
382 if (GLOBAL_PROPERTY_PREFIX.equalsIgnoreCase(name)) {
383 applyGlobalProperty(objects, property, value);
384 } else {
385 applySingleProperty(objects, name, property, value);
386 }
387
388 } else {
389 throw new IllegalArgumentException("All property keys must contain a '.' character. "
390 + "(e.g. myBean.property = value) These should already be separated out by buildObjects().");
391 }
392 }
393
394 protected void applyGlobalProperty(Map objects, String property, String value) {
395 for (Object instance : objects.values()) {
396 try {
397 PropertyDescriptor pd = beanUtilsBean.getPropertyUtils().getPropertyDescriptor(instance, property);
398 if (pd != null) {
399 applyProperty(instance, property, value);
400 }
401 } catch (Exception e) {
402 String msg = "Error retrieving property descriptor for instance "
403 + "of type [" + instance.getClass().getName() + "] "
404 + "while setting property [" + property + "]";
405 throw new ConfigurationException(msg, e);
406 }
407 }
408 }
409
410 protected void applySingleProperty(Map objects, String name, String property, String value) {
411 Object instance = objects.get(name);
412 if (property.equals("class")) {
413 throw new IllegalArgumentException("Property keys should not contain 'class' properties since these "
414 + "should already be separated out by buildObjects().");
415
416 } else if (instance == null) {
417 String msg = "Configuration error. Specified object [" + name + "] with property ["
418 + property + "] without first defining that object's class. Please first "
419 + "specify the class property first, e.g. myObject = fully_qualified_class_name "
420 + "and then define additional properties.";
421 throw new IllegalArgumentException(msg);
422
423 } else {
424 applyProperty(instance, property, value);
425 }
426 }
427
428 protected boolean isReference(String value) {
429 return value != null && value.startsWith(OBJECT_REFERENCE_BEGIN_TOKEN);
430 }
431
432 protected String getId(String referenceToken) {
433 return referenceToken.substring(OBJECT_REFERENCE_BEGIN_TOKEN.length());
434 }
435
436 protected Object getReferencedObject(String id) {
437 Object o = objects != null && !objects.isEmpty() ? objects.get(id) : null;
438 if (o == null) {
439 String msg = "The object with id [" + id + "] has not yet been defined and therefore cannot be "
440 + "referenced. Please ensure objects are defined in the order in which they should be "
441 + "created and made available for future reference.";
442 throw new UnresolveableReferenceException(msg);
443 }
444 return o;
445 }
446
447 protected String unescapeIfNecessary(String value) {
448 if (value != null && value.startsWith(ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN)) {
449 return value.substring(ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN.length() - 1);
450 }
451 return value;
452 }
453
454 protected Object resolveReference(String reference) {
455 String id = getId(reference);
456 LOGGER.debug("Encountered object reference '{}'. Looking up object with id '{}'", reference, id);
457 final Object referencedObject = getReferencedObject(id);
458 if (referencedObject instanceof Factory) {
459 return ((Factory) referencedObject).getInstance();
460 }
461 return referencedObject;
462 }
463
464 protected boolean isTypedProperty(Object object, String propertyName, Class<?> clazz) {
465 if (clazz == null) {
466 throw new NullPointerException("type (class) argument cannot be null.");
467 }
468 try {
469 PropertyDescriptor descriptor = beanUtilsBean.getPropertyUtils().getPropertyDescriptor(object, propertyName);
470 if (descriptor == null) {
471 String msg = "Property '" + propertyName + "' does not exist for object of "
472 + "type " + object.getClass().getName() + ".";
473 throw new ConfigurationException(msg);
474 }
475 Class<?> propertyClazz = descriptor.getPropertyType();
476 return clazz.isAssignableFrom(propertyClazz);
477 } catch (ConfigurationException ce) {
478
479 throw ce;
480 } catch (Exception e) {
481 String msg = "Unable to determine if property [" + propertyName + "] represents a " + clazz.getName();
482 throw new ConfigurationException(msg, e);
483 }
484 }
485
486 protected Set<?> toSet(String sValue) {
487 String[] tokens = StringUtils.split(sValue);
488 if (tokens == null || tokens.length <= 0) {
489 return null;
490 }
491
492
493 if (tokens.length == 1 && isReference(tokens[0])) {
494 Object reference = resolveReference(tokens[0]);
495 if (reference instanceof Set) {
496 return (Set) reference;
497 }
498 }
499
500 Set<String> setTokens = new LinkedHashSet<String>(Arrays.asList(tokens));
501
502
503 Set<Object> values = new LinkedHashSet<Object>(setTokens.size());
504 for (String token : setTokens) {
505 Object value = resolveValue(token);
506 values.add(value);
507 }
508 return values;
509 }
510
511 protected Map<?, ?> toMap(String sValue) {
512 String[] tokens = StringUtils.split(sValue, StringUtils.DEFAULT_DELIMITER_CHAR,
513 StringUtils.DEFAULT_QUOTE_CHAR, StringUtils.DEFAULT_QUOTE_CHAR, true, true);
514 if (tokens == null || tokens.length <= 0) {
515 return null;
516 }
517
518
519 if (tokens.length == 1 && isReference(tokens[0])) {
520 Object reference = resolveReference(tokens[0]);
521 if (reference instanceof Map) {
522 return (Map) reference;
523 }
524 }
525
526 Map<String, String> mapTokens = new LinkedHashMap<String, String>(tokens.length);
527 for (String token : tokens) {
528 String[] kvPair = StringUtils.split(token, MAP_KEY_VALUE_DELIMITER);
529 if (kvPair == null || kvPair.length != 2) {
530 String msg = "Map property value [" + sValue + "] contained key-value pair token ["
531 + token + "] that does not properly split to a single key and pair. This must be the "
532 + "case for all map entries.";
533 throw new ConfigurationException(msg);
534 }
535 mapTokens.put(kvPair[0], kvPair[1]);
536 }
537
538
539 Map<Object, Object> map = new LinkedHashMap<Object, Object>(mapTokens.size());
540 for (Map.Entry<String, String> entry : mapTokens.entrySet()) {
541 Object key = resolveValue(entry.getKey());
542 Object value = resolveValue(entry.getValue());
543 map.put(key, value);
544 }
545 return map;
546 }
547
548
549 protected Collection<?> toCollection(String sValue) {
550
551 String[] tokens = StringUtils.split(sValue);
552 if (tokens == null || tokens.length <= 0) {
553 return null;
554 }
555
556
557 if (tokens.length == 1 && isReference(tokens[0])) {
558 Object reference = resolveReference(tokens[0]);
559 if (reference instanceof Collection) {
560 return (Collection) reference;
561 }
562 }
563
564
565 List<Object> values = new ArrayList<Object>(tokens.length);
566 for (String token : tokens) {
567 Object value = resolveValue(token);
568 values.add(value);
569 }
570 return values;
571 }
572
573 protected List<?> toList(String sValue) {
574 String[] tokens = StringUtils.split(sValue);
575 if (tokens == null || tokens.length <= 0) {
576 return null;
577 }
578
579
580 if (tokens.length == 1 && isReference(tokens[0])) {
581 Object reference = resolveReference(tokens[0]);
582 if (reference instanceof List) {
583 return (List) reference;
584 }
585 }
586
587
588 List<Object> values = new ArrayList<Object>(tokens.length);
589 for (String token : tokens) {
590 Object value = resolveValue(token);
591 values.add(value);
592 }
593 return values;
594 }
595
596 protected byte[] toBytes(String sValue) {
597 if (sValue == null) {
598 return null;
599 }
600 byte[] bytes;
601 if (sValue.startsWith(HEX_BEGIN_TOKEN)) {
602 String hex = sValue.substring(HEX_BEGIN_TOKEN.length());
603 bytes = Hex.decode(hex);
604 } else {
605
606 bytes = Base64.decode(sValue);
607 }
608 return bytes;
609 }
610
611 protected Object resolveValue(String stringValue) {
612 Object value;
613 if (isReference(stringValue)) {
614 value = resolveReference(stringValue);
615 } else {
616 value = unescapeIfNecessary(stringValue);
617 }
618 return value;
619 }
620
621 protected String checkForNullOrEmptyLiteral(String stringValue) {
622 if (stringValue == null) {
623 return null;
624 }
625
626 if ("\"null\"".equals(stringValue)) {
627 return NULL_VALUE_TOKEN;
628
629 } else if ("\"\"\"\"".equals(stringValue)) {
630 return EMPTY_STRING_VALUE_TOKEN;
631 } else {
632 return stringValue;
633 }
634 }
635
636 protected void applyProperty(Object object, String propertyPath, Object value) {
637
638 int mapBegin = propertyPath.indexOf(MAP_PROPERTY_BEGIN_TOKEN);
639 int mapEnd = -1;
640 String mapPropertyPath = null;
641 String keyString = null;
642
643 String remaining = null;
644
645 if (mapBegin >= 0) {
646
647 mapPropertyPath = propertyPath.substring(0, mapBegin);
648
649 mapEnd = propertyPath.indexOf(MAP_PROPERTY_END_TOKEN, mapBegin);
650
651 keyString = propertyPath.substring(mapBegin + 1, mapEnd);
652
653
654 if (propertyPath.length() > (mapEnd + 1)) {
655 remaining = propertyPath.substring(mapEnd + 1);
656 if (remaining.startsWith(".")) {
657 remaining = StringUtils.clean(remaining.substring(1));
658 }
659 }
660 }
661
662 if (remaining == null) {
663
664 if (keyString == null) {
665
666 setProperty(object, propertyPath, value);
667 } else {
668
669 if (isTypedProperty(object, mapPropertyPath, Map.class)) {
670 @SuppressWarnings("unchecked")
671 var map = (Map<Object, Object>) getProperty(object, mapPropertyPath);
672 Object mapKey = resolveValue(keyString);
673
674 map.put(mapKey, value);
675 } else {
676
677 int index = Integer.valueOf(keyString);
678 setIndexedProperty(object, mapPropertyPath, index, value);
679 }
680 }
681 } else {
682
683
684 Object referencedValue = null;
685 if (isTypedProperty(object, mapPropertyPath, Map.class)) {
686 Map map = (Map) getProperty(object, mapPropertyPath);
687 Object mapKey = resolveValue(keyString);
688 referencedValue = map.get(mapKey);
689 } else {
690
691 int index = Integer.valueOf(keyString);
692 referencedValue = getIndexedProperty(object, mapPropertyPath, index);
693 }
694
695 if (referencedValue == null) {
696 throw new ConfigurationException("Referenced map/array value '" + mapPropertyPath + "["
697 + keyString + "]' does not exist.");
698 }
699
700 applyProperty(referencedValue, remaining, value);
701 }
702 }
703
704 private void setProperty(Object object, String propertyPath, Object value) {
705 try {
706 if (LOGGER.isTraceEnabled()) {
707 LOGGER.trace("Applying property [{}] value [{}] on object of type [{}]",
708 new Object[] {propertyPath, value, object.getClass().getName()});
709 }
710 beanUtilsBean.setProperty(object, propertyPath, value);
711 } catch (Exception e) {
712 String msg = "Unable to set property '" + propertyPath + "' with value [" + value + "] on object "
713 + "of type " + (object != null ? object.getClass().getName() : null) + ". If "
714 + "'" + value + "' is a reference to another (previously defined) object, prefix it with "
715 + "'" + OBJECT_REFERENCE_BEGIN_TOKEN + "' to indicate that the referenced "
716 + "object should be used as the actual value. "
717 + "For example, " + OBJECT_REFERENCE_BEGIN_TOKEN + value;
718 throw new ConfigurationException(msg, e);
719 }
720 }
721
722 private Object getProperty(Object object, String propertyPath) {
723 try {
724 return beanUtilsBean.getPropertyUtils().getProperty(object, propertyPath);
725 } catch (Exception e) {
726 throw new ConfigurationException("Unable to access property '" + propertyPath + "'", e);
727 }
728 }
729
730 private void setIndexedProperty(Object object, String propertyPath, int index, Object value) {
731 try {
732 beanUtilsBean.getPropertyUtils().setIndexedProperty(object, propertyPath, index, value);
733 } catch (Exception e) {
734 throw new ConfigurationException("Unable to set array property '" + propertyPath + "'", e);
735 }
736 }
737
738 private Object getIndexedProperty(Object object, String propertyPath, int index) {
739 try {
740 return beanUtilsBean.getPropertyUtils().getIndexedProperty(object, propertyPath, index);
741 } catch (Exception e) {
742 throw new ConfigurationException("Unable to acquire array property '" + propertyPath + "'", e);
743 }
744 }
745
746 protected boolean isIndexedPropertyAssignment(String propertyPath) {
747 return propertyPath.endsWith("" + MAP_PROPERTY_END_TOKEN);
748 }
749
750 protected void applyProperty(Object object, String propertyName, String stringValue) {
751
752 Object value;
753
754 if (NULL_VALUE_TOKEN.equals(stringValue)) {
755 value = null;
756 } else if (EMPTY_STRING_VALUE_TOKEN.equals(stringValue)) {
757 value = StringUtils.EMPTY_STRING;
758 } else if (isIndexedPropertyAssignment(propertyName)) {
759 String checked = checkForNullOrEmptyLiteral(stringValue);
760 value = resolveValue(checked);
761 } else if (isTypedProperty(object, propertyName, Set.class)) {
762 value = toSet(stringValue);
763 } else if (isTypedProperty(object, propertyName, Map.class)) {
764 value = toMap(stringValue);
765 } else if (isTypedProperty(object, propertyName, List.class)) {
766 value = toList(stringValue);
767 } else if (isTypedProperty(object, propertyName, Collection.class)) {
768 value = toCollection(stringValue);
769 } else if (isTypedProperty(object, propertyName, byte[].class)) {
770 value = toBytes(stringValue);
771 } else if (isTypedProperty(object, propertyName, ByteSource.class)) {
772 byte[] bytes = toBytes(stringValue);
773 value = ByteSource.Util.bytes(bytes);
774 } else {
775 String checked = checkForNullOrEmptyLiteral(stringValue);
776 value = resolveValue(checked);
777 }
778
779 applyProperty(object, propertyName, value);
780 }
781
782 private Interpolator createInterpolator() {
783
784 if (ClassUtils.isAvailable("org.apache.commons.configuration2.interpol.ConfigurationInterpolator")) {
785 return new CommonsInterpolator();
786 }
787
788 return new DefaultInterpolator();
789 }
790
791
792
793
794
795
796 public void setInterpolator(Interpolator interpolator) {
797 this.interpolator = interpolator;
798 }
799
800 private final class BeanConfigurationProcessor {
801
802 private final List<Statement> statements = new ArrayList<Statement>();
803 private final List<BeanConfiguration> beanConfigurations = new ArrayList<BeanConfiguration>();
804
805 public void add(Statement statement) {
806
807 statements.add(statement);
808
809 if (statement instanceof InstantiationStatement) {
810 InstantiationStatement is = (InstantiationStatement) statement;
811 beanConfigurations.add(new BeanConfiguration(is));
812 } else {
813 AssignmentStatement as = (AssignmentStatement) statement;
814
815
816 boolean addedToConfig = false;
817 String beanName = as.getRootBeanName();
818 for (int i = beanConfigurations.size() - 1; i >= 0; i--) {
819 BeanConfiguration mostRecent = beanConfigurations.get(i);
820 String mostRecentBeanName = mostRecent.getBeanName();
821 if (beanName.equals(mostRecentBeanName)) {
822 mostRecent.add(as);
823 addedToConfig = true;
824 break;
825 }
826 }
827
828 if (!addedToConfig) {
829
830
831
832
833 beanConfigurations.add(new BeanConfiguration(as));
834 }
835 }
836 }
837
838 public void execute() {
839
840 for (Statement statement : statements) {
841
842 statement.execute();
843
844 BeanConfiguration bd = statement.getBeanConfiguration();
845
846
847 if (bd.isExecuted()) {
848
849
850 if (bd.getBeanName().equals(EVENT_BUS_NAME)) {
851 EventBus eventBus = (EventBus) bd.getBean();
852 enableEvents(eventBus);
853 }
854
855
856 if (!bd.isGlobalConfig()) {
857 BeanEvent event = new ConfiguredBeanEvent(bd.getBeanName(), bd.getBean(),
858 Collections.unmodifiableMap(objects));
859 eventBus.publish(event);
860 }
861
862
863 LifecycleUtils.init(bd.getBean());
864
865
866 if (!bd.isGlobalConfig()) {
867 BeanEvent event = new InitializedBeanEvent(bd.getBeanName(), bd.getBean(),
868 Collections.unmodifiableMap(objects));
869 eventBus.publish(event);
870 }
871 }
872 }
873 }
874 }
875
876 private final class BeanConfiguration {
877
878 private final InstantiationStatement instantiationStatement;
879 private final List<AssignmentStatement> assignments = new ArrayList<AssignmentStatement>();
880 private final String beanName;
881 private Object bean;
882
883 private BeanConfiguration(InstantiationStatement statement) {
884 statement.setBeanConfiguration(this);
885 this.instantiationStatement = statement;
886 this.beanName = statement.lhs;
887 }
888
889 private BeanConfiguration(AssignmentStatement as) {
890 this.instantiationStatement = null;
891 this.beanName = as.getRootBeanName();
892 add(as);
893 }
894
895 public String getBeanName() {
896 return this.beanName;
897 }
898
899
900
901
902
903
904 public boolean isGlobalConfig() {
905
906 return GLOBAL_PROPERTY_PREFIX.equals(getBeanName());
907 }
908
909 public void add(AssignmentStatement as) {
910 as.setBeanConfiguration(this);
911 assignments.add(as);
912 }
913
914
915
916
917
918
919
920 public void setBean(Object bean) {
921 this.bean = bean;
922 }
923
924 public Object getBean() {
925 return this.bean;
926 }
927
928
929
930
931
932
933 public boolean isExecuted() {
934 if (instantiationStatement != null && !instantiationStatement.isExecuted()) {
935 return false;
936 }
937 for (AssignmentStatement as : assignments) {
938 if (!as.isExecuted()) {
939 return false;
940 }
941 }
942 return true;
943 }
944 }
945
946 private abstract class Statement {
947
948 protected final String lhs;
949 protected final String rhs;
950 protected Object bean;
951 private Object result;
952 private boolean executed;
953 private BeanConfiguration beanConfiguration;
954
955 private Statement(String lhs, String rhs) {
956 this.lhs = lhs;
957 this.rhs = rhs;
958 this.executed = false;
959 }
960
961 public void setBeanConfiguration(BeanConfiguration bd) {
962 this.beanConfiguration = bd;
963 }
964
965 public BeanConfiguration getBeanConfiguration() {
966 return this.beanConfiguration;
967 }
968
969 public Object execute() {
970 if (!isExecuted()) {
971 this.result = doExecute();
972 this.executed = true;
973 }
974 if (!getBeanConfiguration().isGlobalConfig()) {
975 Assert.notNull(this.bean, "Implementation must set the root bean for which it executed.");
976 }
977 return this.result;
978 }
979
980 public Object getBean() {
981 return this.bean;
982 }
983
984 protected void setBean(Object bean) {
985 this.bean = bean;
986 if (this.beanConfiguration.getBean() == null) {
987 this.beanConfiguration.setBean(bean);
988 }
989 }
990
991 public Object getResult() {
992 return result;
993 }
994
995 protected abstract Object doExecute();
996
997 public boolean isExecuted() {
998 return executed;
999 }
1000 }
1001
1002 private final class InstantiationStatement extends Statement {
1003
1004 private InstantiationStatement(String lhs, String rhs) {
1005 super(lhs, rhs);
1006 }
1007
1008 @Override
1009 protected Object doExecute() {
1010 String beanName = this.lhs;
1011 createNewInstance(objects, beanName, this.rhs);
1012 Object instantiated = objects.get(beanName);
1013 setBean(instantiated);
1014
1015
1016
1017
1018 enableEventsIfNecessary(instantiated, beanName);
1019
1020 BeanEvent event = new InstantiatedBeanEvent(beanName, instantiated, Collections.unmodifiableMap(objects));
1021 eventBus.publish(event);
1022
1023 return instantiated;
1024 }
1025 }
1026
1027 private final class AssignmentStatement extends Statement {
1028
1029 private final String rootBeanName;
1030
1031 private AssignmentStatement(String lhs, String rhs) {
1032 super(lhs, rhs);
1033 int index = lhs.indexOf('.');
1034 this.rootBeanName = lhs.substring(0, index);
1035 }
1036
1037 @Override
1038 protected Object doExecute() {
1039 applyProperty(lhs, rhs, objects);
1040 Object bean = objects.get(this.rootBeanName);
1041 setBean(bean);
1042 return null;
1043 }
1044
1045 public String getRootBeanName() {
1046 return this.rootBeanName;
1047 }
1048 }
1049
1050
1051
1052
1053
1054
1055 private static boolean isEmpty(Map m) {
1056 return m == null || m.isEmpty();
1057 }
1058
1059 private static boolean isEmpty(Collection c) {
1060 return c == null || c.isEmpty();
1061 }
1062
1063 }