1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.shiro.config;
20
21 import org.apache.commons.beanutils.BeanUtils;
22 import org.apache.commons.beanutils.PropertyUtils;
23 import org.apache.shiro.codec.Base64;
24 import org.apache.shiro.codec.Hex;
25 import org.apache.shiro.util.*;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 import java.beans.PropertyDescriptor;
30 import java.util.*;
31
32
33
34
35
36
37
38
39
40
41
42
43 public class ReflectionBuilder {
44
45
46
47 private static final Logger log = LoggerFactory.getLogger(ReflectionBuilder.class);
48
49 private static final String OBJECT_REFERENCE_BEGIN_TOKEN = "$";
50 private static final String ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN = "\\$";
51 private static final String GLOBAL_PROPERTY_PREFIX = "shiro";
52 private static final char MAP_KEY_VALUE_DELIMITER = ':';
53 private static final String HEX_BEGIN_TOKEN = "0x";
54 private static final String NULL_VALUE_TOKEN = "null";
55 private static final String EMPTY_STRING_VALUE_TOKEN = "\"\"";
56 private static final char STRING_VALUE_DELIMETER = '"';
57 private static final char MAP_PROPERTY_BEGIN_TOKEN = '[';
58 private static final char MAP_PROPERTY_END_TOKEN = ']';
59
60 private Map<String, ?> objects;
61
62 public ReflectionBuilder() {
63 this.objects = new LinkedHashMap<String, Object>();
64 }
65
66 public ReflectionBuilder(Map<String, ?> defaults) {
67 this.objects = CollectionUtils.isEmpty(defaults) ? new LinkedHashMap<String, Object>() : defaults;
68 }
69
70 public Map<String, ?> getObjects() {
71 return objects;
72 }
73
74 public void setObjects(Map<String, ?> objects) {
75 this.objects = CollectionUtils.isEmpty(objects) ? new LinkedHashMap<String, Object>() : objects;
76 }
77
78 public Object getBean(String id) {
79 return objects.get(id);
80 }
81
82 @SuppressWarnings({"unchecked"})
83 public <T> T getBean(String id, Class<T> requiredType) {
84 if (requiredType == null) {
85 throw new NullPointerException("requiredType argument cannot be null.");
86 }
87 Object bean = getBean(id);
88 if (bean == null) {
89 return null;
90 }
91 if (!requiredType.isAssignableFrom(bean.getClass())) {
92 throw new IllegalStateException("Bean with id [" + id + "] is not of the required type [" +
93 requiredType.getName() + "].");
94 }
95 return (T) bean;
96 }
97
98 @SuppressWarnings({"unchecked"})
99 public Map<String, ?> buildObjects(Map<String, String> kvPairs) {
100 if (kvPairs != null && !kvPairs.isEmpty()) {
101
102
103
104
105
106 Map<String, String> instanceMap = new LinkedHashMap<String, String>();
107 Map<String, String> propertyMap = new LinkedHashMap<String, String>();
108
109 for (Map.Entry<String, String> entry : kvPairs.entrySet()) {
110 if (entry.getKey().indexOf('.') < 0 || entry.getKey().endsWith(".class")) {
111 instanceMap.put(entry.getKey(), entry.getValue());
112 } else {
113 propertyMap.put(entry.getKey(), entry.getValue());
114 }
115 }
116
117
118 for (Map.Entry<String, String> entry : instanceMap.entrySet()) {
119 createNewInstance((Map<String, Object>) objects, entry.getKey(), entry.getValue());
120 }
121
122
123 for (Map.Entry<String, String> entry : propertyMap.entrySet()) {
124 applyProperty(entry.getKey(), entry.getValue(), objects);
125 }
126 }
127
128
129 LifecycleUtils.init(objects.values());
130
131 return objects;
132 }
133
134 protected void createNewInstance(Map<String, Object> objects, String name, String value) {
135
136 Object currentInstance = objects.get(name);
137 if (currentInstance != null) {
138 log.info("An instance with name '{}' already exists. " +
139 "Redefining this object as a new instance of type {}", name, value);
140 }
141
142 Object instance;
143 try {
144 instance = ClassUtils.newInstance(value);
145 if (instance instanceof Nameable) {
146 ((Nameable) instance).setName(name);
147 }
148 } catch (Exception e) {
149 String msg = "Unable to instantiate class [" + value + "] for object named '" + name + "'. " +
150 "Please ensure you've specified the fully qualified class name correctly.";
151 throw new ConfigurationException(msg, e);
152 }
153 objects.put(name, instance);
154 }
155
156 protected void applyProperty(String key, String value, Map objects) {
157
158 int index = key.indexOf('.');
159
160 if (index >= 0) {
161 String name = key.substring(0, index);
162 String property = key.substring(index + 1, key.length());
163
164 if (GLOBAL_PROPERTY_PREFIX.equalsIgnoreCase(name)) {
165 applyGlobalProperty(objects, property, value);
166 } else {
167 applySingleProperty(objects, name, property, value);
168 }
169
170 } else {
171 throw new IllegalArgumentException("All property keys must contain a '.' character. " +
172 "(e.g. myBean.property = value) These should already be separated out by buildObjects().");
173 }
174 }
175
176 protected void applyGlobalProperty(Map objects, String property, String value) {
177 for (Object instance : objects.values()) {
178 try {
179 PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(instance, property);
180 if (pd != null) {
181 applyProperty(instance, property, value);
182 }
183 } catch (Exception e) {
184 String msg = "Error retrieving property descriptor for instance " +
185 "of type [" + instance.getClass().getName() + "] " +
186 "while setting property [" + property + "]";
187 throw new ConfigurationException(msg, e);
188 }
189 }
190 }
191
192 protected void applySingleProperty(Map objects, String name, String property, String value) {
193 Object instance = objects.get(name);
194 if (property.equals("class")) {
195 throw new IllegalArgumentException("Property keys should not contain 'class' properties since these " +
196 "should already be separated out by buildObjects().");
197
198 } else if (instance == null) {
199 String msg = "Configuration error. Specified object [" + name + "] with property [" +
200 property + "] without first defining that object's class. Please first " +
201 "specify the class property first, e.g. myObject = fully_qualified_class_name " +
202 "and then define additional properties.";
203 throw new IllegalArgumentException(msg);
204
205 } else {
206 applyProperty(instance, property, value);
207 }
208 }
209
210 protected boolean isReference(String value) {
211 return value != null && value.startsWith(OBJECT_REFERENCE_BEGIN_TOKEN);
212 }
213
214 protected String getId(String referenceToken) {
215 return referenceToken.substring(OBJECT_REFERENCE_BEGIN_TOKEN.length());
216 }
217
218 protected Object getReferencedObject(String id) {
219 Object o = objects != null && !objects.isEmpty() ? objects.get(id) : null;
220 if (o == null) {
221 String msg = "The object with id [" + id + "] has not yet been defined and therefore cannot be " +
222 "referenced. Please ensure objects are defined in the order in which they should be " +
223 "created and made available for future reference.";
224 throw new UnresolveableReferenceException(msg);
225 }
226 return o;
227 }
228
229 protected String unescapeIfNecessary(String value) {
230 if (value != null && value.startsWith(ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN)) {
231 return value.substring(ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN.length() - 1);
232 }
233 return value;
234 }
235
236 protected Object resolveReference(String reference) {
237 String id = getId(reference);
238 log.debug("Encountered object reference '{}'. Looking up object with id '{}'", reference, id);
239 final Object referencedObject = getReferencedObject(id);
240 if (referencedObject instanceof Factory) {
241 return ((Factory) referencedObject).getInstance();
242 }
243 return referencedObject;
244 }
245
246 protected boolean isTypedProperty(Object object, String propertyName, Class clazz) {
247 if (clazz == null) {
248 throw new NullPointerException("type (class) argument cannot be null.");
249 }
250 try {
251 PropertyDescriptor descriptor = PropertyUtils.getPropertyDescriptor(object, propertyName);
252 if (descriptor == null) {
253 String msg = "Property '" + propertyName + "' does not exist for object of " +
254 "type " + object.getClass().getName() + ".";
255 throw new ConfigurationException(msg);
256 }
257 Class propertyClazz = descriptor.getPropertyType();
258 return clazz.isAssignableFrom(propertyClazz);
259 } catch (ConfigurationException ce) {
260
261 throw ce;
262 } catch (Exception e) {
263 String msg = "Unable to determine if property [" + propertyName + "] represents a " + clazz.getName();
264 throw new ConfigurationException(msg, e);
265 }
266 }
267
268 protected Set<?> toSet(String sValue) {
269 String[] tokens = StringUtils.split(sValue);
270 if (tokens == null || tokens.length <= 0) {
271 return null;
272 }
273
274
275 if (tokens.length == 1 && isReference(tokens[0])) {
276 Object reference = resolveReference(tokens[0]);
277 if (reference instanceof Set) {
278 return (Set)reference;
279 }
280 }
281
282 Set<String> setTokens = new LinkedHashSet<String>(Arrays.asList(tokens));
283
284
285 Set<Object> values = new LinkedHashSet<Object>(setTokens.size());
286 for (String token : setTokens) {
287 Object value = resolveValue(token);
288 values.add(value);
289 }
290 return values;
291 }
292
293 protected Map<?, ?> toMap(String sValue) {
294 String[] tokens = StringUtils.split(sValue, StringUtils.DEFAULT_DELIMITER_CHAR,
295 StringUtils.DEFAULT_QUOTE_CHAR, StringUtils.DEFAULT_QUOTE_CHAR, true, true);
296 if (tokens == null || tokens.length <= 0) {
297 return null;
298 }
299
300
301 if (tokens.length == 1 && isReference(tokens[0])) {
302 Object reference = resolveReference(tokens[0]);
303 if (reference instanceof Map) {
304 return (Map)reference;
305 }
306 }
307
308 Map<String, String> mapTokens = new LinkedHashMap<String, String>(tokens.length);
309 for (String token : tokens) {
310 String[] kvPair = StringUtils.split(token, MAP_KEY_VALUE_DELIMITER);
311 if (kvPair == null || kvPair.length != 2) {
312 String msg = "Map property value [" + sValue + "] contained key-value pair token [" +
313 token + "] that does not properly split to a single key and pair. This must be the " +
314 "case for all map entries.";
315 throw new ConfigurationException(msg);
316 }
317 mapTokens.put(kvPair[0], kvPair[1]);
318 }
319
320
321 Map<Object, Object> map = new LinkedHashMap<Object, Object>(mapTokens.size());
322 for (Map.Entry<String, String> entry : mapTokens.entrySet()) {
323 Object key = resolveValue(entry.getKey());
324 Object value = resolveValue(entry.getValue());
325 map.put(key, value);
326 }
327 return map;
328 }
329
330
331
332 private Collection<?> toCollection(String sValue) {
333
334 String[] tokens = StringUtils.split(sValue);
335 if (tokens == null || tokens.length <= 0) {
336 return null;
337 }
338
339
340 if (tokens.length == 1 && isReference(tokens[0])) {
341 Object reference = resolveReference(tokens[0]);
342 if (reference instanceof Collection) {
343 return (Collection)reference;
344 }
345 }
346
347
348 List<Object> values = new ArrayList<Object>(tokens.length);
349 for (String token : tokens) {
350 Object value = resolveValue(token);
351 values.add(value);
352 }
353 return values;
354 }
355
356 protected List<?> toList(String sValue) {
357 String[] tokens = StringUtils.split(sValue);
358 if (tokens == null || tokens.length <= 0) {
359 return null;
360 }
361
362
363 if (tokens.length == 1 && isReference(tokens[0])) {
364 Object reference = resolveReference(tokens[0]);
365 if (reference instanceof List) {
366 return (List)reference;
367 }
368 }
369
370
371 List<Object> values = new ArrayList<Object>(tokens.length);
372 for (String token : tokens) {
373 Object value = resolveValue(token);
374 values.add(value);
375 }
376 return values;
377 }
378
379 protected byte[] toBytes(String sValue) {
380 if (sValue == null) {
381 return null;
382 }
383 byte[] bytes;
384 if (sValue.startsWith(HEX_BEGIN_TOKEN)) {
385 String hex = sValue.substring(HEX_BEGIN_TOKEN.length());
386 bytes = Hex.decode(hex);
387 } else {
388
389 bytes = Base64.decode(sValue);
390 }
391 return bytes;
392 }
393
394 protected Object resolveValue(String stringValue) {
395 Object value;
396 if (isReference(stringValue)) {
397 value = resolveReference(stringValue);
398 } else {
399 value = unescapeIfNecessary(stringValue);
400 }
401 return value;
402 }
403
404 protected String checkForNullOrEmptyLiteral(String stringValue) {
405 if (stringValue == null) {
406 return null;
407 }
408
409 if (stringValue.equals("\"null\"")) {
410 return NULL_VALUE_TOKEN;
411 }
412
413 else if (stringValue.equals("\"\"\"\"")) {
414 return EMPTY_STRING_VALUE_TOKEN;
415 } else {
416 return stringValue;
417 }
418 }
419
420 protected void applyProperty(Object object, String propertyPath, Object value) {
421
422 int mapBegin = propertyPath.indexOf(MAP_PROPERTY_BEGIN_TOKEN);
423 int mapEnd = -1;
424 String mapPropertyPath = null;
425 String keyString = null;
426
427 String remaining = null;
428
429 if (mapBegin >= 0) {
430
431 mapPropertyPath = propertyPath.substring(0, mapBegin);
432
433 mapEnd = propertyPath.indexOf(MAP_PROPERTY_END_TOKEN, mapBegin);
434
435 keyString = propertyPath.substring(mapBegin+1, mapEnd);
436
437
438 if (propertyPath.length() > (mapEnd+1)) {
439 remaining = propertyPath.substring(mapEnd+1);
440 if (remaining.startsWith(".")) {
441 remaining = StringUtils.clean(remaining.substring(1));
442 }
443 }
444 }
445
446 if (remaining == null) {
447
448 if (keyString == null) {
449
450 setProperty(object, propertyPath, value);
451 } else {
452
453 if (isTypedProperty(object, mapPropertyPath, Map.class)) {
454 Map map = (Map)getProperty(object, mapPropertyPath);
455 Object mapKey = resolveValue(keyString);
456
457 map.put(mapKey, value);
458 } else {
459
460 int index = Integer.valueOf(keyString);
461 setIndexedProperty(object, mapPropertyPath, index, value);
462 }
463 }
464 } else {
465
466
467 Object referencedValue = null;
468 if (isTypedProperty(object, mapPropertyPath, Map.class)) {
469 Map map = (Map)getProperty(object, mapPropertyPath);
470 Object mapKey = resolveValue(keyString);
471 referencedValue = map.get(mapKey);
472 } else {
473
474 int index = Integer.valueOf(keyString);
475 referencedValue = getIndexedProperty(object, mapPropertyPath, index);
476 }
477
478 if (referencedValue == null) {
479 throw new ConfigurationException("Referenced map/array value '" + mapPropertyPath + "[" +
480 keyString + "]' does not exist.");
481 }
482
483 applyProperty(referencedValue, remaining, value);
484 }
485 }
486
487 private void setProperty(Object object, String propertyPath, Object value) {
488 try {
489 if (log.isTraceEnabled()) {
490 log.trace("Applying property [{}] value [{}] on object of type [{}]",
491 new Object[]{propertyPath, value, object.getClass().getName()});
492 }
493 BeanUtils.setProperty(object, propertyPath, value);
494 } catch (Exception e) {
495 String msg = "Unable to set property '" + propertyPath + "' with value [" + value + "] on object " +
496 "of type " + (object != null ? object.getClass().getName() : null) + ". If " +
497 "'" + value + "' is a reference to another (previously defined) object, prefix it with " +
498 "'" + OBJECT_REFERENCE_BEGIN_TOKEN + "' to indicate that the referenced " +
499 "object should be used as the actual value. " +
500 "For example, " + OBJECT_REFERENCE_BEGIN_TOKEN + value;
501 throw new ConfigurationException(msg, e);
502 }
503 }
504
505 private Object getProperty(Object object, String propertyPath) {
506 try {
507 return PropertyUtils.getProperty(object, propertyPath);
508 } catch (Exception e) {
509 throw new ConfigurationException("Unable to access property '" + propertyPath + "'", e);
510 }
511 }
512
513 private void setIndexedProperty(Object object, String propertyPath, int index, Object value) {
514 try {
515 PropertyUtils.setIndexedProperty(object, propertyPath, index, value);
516 } catch (Exception e) {
517 throw new ConfigurationException("Unable to set array property '" + propertyPath + "'", e);
518 }
519 }
520
521 private Object getIndexedProperty(Object object, String propertyPath, int index) {
522 try {
523 return PropertyUtils.getIndexedProperty(object, propertyPath, index);
524 } catch (Exception e) {
525 throw new ConfigurationException("Unable to acquire array property '" + propertyPath + "'", e);
526 }
527 }
528
529 protected boolean isIndexedPropertyAssignment(String propertyPath) {
530 return propertyPath.endsWith("" + MAP_PROPERTY_END_TOKEN);
531 }
532
533 protected void applyProperty(Object object, String propertyName, String stringValue) {
534
535 Object value;
536
537 if (NULL_VALUE_TOKEN.equals(stringValue)) {
538 value = null;
539 } else if (EMPTY_STRING_VALUE_TOKEN.equals(stringValue)) {
540 value = StringUtils.EMPTY_STRING;
541 } else if (isIndexedPropertyAssignment(propertyName)) {
542 String checked = checkForNullOrEmptyLiteral(stringValue);
543 value = resolveValue(checked);
544 } else if (isTypedProperty(object, propertyName, Set.class)) {
545 value = toSet(stringValue);
546 } else if (isTypedProperty(object, propertyName, Map.class)) {
547 value = toMap(stringValue);
548 } else if (isTypedProperty(object, propertyName, List.class)) {
549 value = toList(stringValue);
550 } else if (isTypedProperty(object, propertyName, Collection.class)) {
551 value = toCollection(stringValue);
552 } else if (isTypedProperty(object, propertyName, byte[].class)) {
553 value = toBytes(stringValue);
554 } else if (isTypedProperty(object, propertyName, ByteSource.class)) {
555 byte[] bytes = toBytes(stringValue);
556 value = ByteSource.Util.bytes(bytes);
557 } else {
558 String checked = checkForNullOrEmptyLiteral(stringValue);
559 value = resolveValue(checked);
560 }
561
562 applyProperty(object, propertyName, value);
563 }
564
565 }