/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you under the Apache License, Version 2.0 (the | |
* "License"); you may not use this file except in compliance | |
* with the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, | |
* software distributed under the License is distributed on an | |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
* KIND, either express or implied. See the License for the | |
* specific language governing permissions and limitations | |
* under the License. | |
*/ | |
package org.apache.ki.config; | |
import java.beans.PropertyDescriptor; | |
import java.util.HashMap; | |
import java.util.LinkedHashMap; | |
import java.util.Map; | |
import org.apache.commons.beanutils.BeanUtils; | |
import org.apache.commons.beanutils.PropertyUtils; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.apache.ki.util.ClassUtils; | |
import org.apache.ki.util.Nameable; | |
/** | |
* Object builder that uses reflection and Apache Commons BeanUtils to build objects given a | |
* map of "property values". Typically these come from the Ki INI configuration and are used | |
* to construct or modify the SecurityManager, its dependencies, and web-based security filters. | |
* | |
* @author Les Hazlewood | |
* @author Jeremy Haile | |
* @since 0.9 | |
*/ | |
@SuppressWarnings("unchecked") | |
public class ReflectionBuilder { | |
//TODO - complete JavaDoc | |
private static final Logger log = LoggerFactory.getLogger(ReflectionBuilder.class); | |
private static final String OBJECT_REFERENCE_BEGIN_TOKEN = "$"; | |
private static final String ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN = "\\$"; | |
private static final String GLOBAL_PROPERTY_PREFIX = "ki"; | |
protected Map objects; | |
public ReflectionBuilder() { | |
setObjects(new LinkedHashMap<String, Object>()); | |
} | |
public ReflectionBuilder(Map defaults) { | |
setObjects(defaults); | |
} | |
public Map getObjects() { | |
return objects; | |
} | |
public void setObjects(Map objects) { | |
this.objects = objects; | |
} | |
public Map buildObjects(Map<String, String> kvPairs) { | |
if (kvPairs != null && !kvPairs.isEmpty()) { | |
// Separate key value pairs into object declarations and property assignment | |
// so that all objects can be created up front | |
Map<String, String> instanceMap = new HashMap<String, String>(); | |
Map<String, String> propertyMap = new HashMap<String, String>(); | |
for (Map.Entry<String, String> entry : kvPairs.entrySet()) { | |
if (entry.getKey().indexOf('.') < 0 || entry.getKey().endsWith(".class")) { | |
instanceMap.put(entry.getKey(), entry.getValue()); | |
} else { | |
propertyMap.put(entry.getKey(), entry.getValue()); | |
} | |
} | |
// Create all instances | |
for (Map.Entry<String, String> entry : instanceMap.entrySet()) { | |
createNewInstance(objects, entry.getKey(), entry.getValue()); | |
} | |
// Set all properties | |
for (Map.Entry<String, String> entry : propertyMap.entrySet()) { | |
applyProperty(entry.getKey(), entry.getValue(), objects); | |
} | |
} | |
return objects; | |
} | |
protected void createNewInstance(Map objects, String name, String value) { | |
Object currentInstance = objects.get(name); | |
if (currentInstance != null) { | |
log.info("An instance with name [" + name + "] already exists. " + | |
"Redefining this object as a new instance of type [" + value + "]."); | |
} | |
Object instance;//name with no property, assume right hand side of equals sign is the class name: | |
try { | |
instance = ClassUtils.newInstance(value); | |
if (instance instanceof Nameable) { | |
((Nameable) instance).setName(name); | |
} | |
} catch (Exception e) { | |
String msg = "Unable to instantiate class [" + value + "] for object named '" + name + "'. " + | |
"Please ensure you've specified the fully qualified class name correctly."; | |
throw new ConfigurationException(msg, e); | |
} | |
objects.put(name, instance); | |
} | |
protected void applyProperty(String key, String value, Map objects) { | |
int index = key.indexOf('.'); | |
if (index >= 0) { | |
String name = key.substring(0, index); | |
String property = key.substring(index + 1, key.length()); | |
if (GLOBAL_PROPERTY_PREFIX.equalsIgnoreCase(name)) { | |
applyGlobalProperty(objects, property, value); | |
} else { | |
applySingleProperty(objects, name, property, value); | |
} | |
} else { | |
throw new IllegalArgumentException("All property keys must contain a '.' character. " + | |
"(e.g. myBean.property = value) These should already be separated out by buildObjects()."); | |
} | |
} | |
protected void applyGlobalProperty(Map objects, String property, String value) { | |
for (Object instance : objects.values()) { | |
try { | |
PropertyDescriptor pd = PropertyUtils.getPropertyDescriptor(instance, property); | |
if (pd != null) { | |
applyProperty(instance, property, value); | |
} | |
} catch (Exception e) { | |
String msg = "Error retrieving property descriptor for instance " + | |
"of type [" + instance.getClass().getName() + "] " + | |
"while setting property [" + property + "]"; | |
throw new ConfigurationException(msg, e); | |
} | |
} | |
} | |
protected void applySingleProperty(Map objects, String name, String property, String value) { | |
Object instance = objects.get(name); | |
if (property.equals("class")) { | |
throw new IllegalArgumentException("Property keys should not contain 'class' properties since these " + | |
"should already be separated out by buildObjects()."); | |
} else if (instance == null) { | |
String msg = "Configuration error. Specified object [" + name + "] with property [" + | |
property + "] without first defining that object's class. Please first " + | |
"specify the class property first, e.g. myObject.class = fully_qualified_class_name " + | |
"and then define additional properties."; | |
throw new IllegalArgumentException(msg); | |
} else { | |
applyProperty(instance, property, value); | |
} | |
} | |
protected boolean isReference(String value) { | |
return value != null && value.startsWith(OBJECT_REFERENCE_BEGIN_TOKEN); | |
} | |
protected String getId(String referenceToken) { | |
return referenceToken.substring(OBJECT_REFERENCE_BEGIN_TOKEN.length()); | |
} | |
protected Object getReferencedObject(String id) { | |
Object o = objects != null && !objects.isEmpty() ? objects.get(id) : null; | |
if (o == null) { | |
String msg = "The object with id [" + id + "] has not yet been defined and therefore cannot be " + | |
"referenced. Please ensure objects are defined in the order in which they should be " + | |
"created and made avaialable for future reference."; | |
throw new UnresolveableReferenceException(msg); | |
} | |
return o; | |
} | |
protected String unescapeIfNecessary(String value) { | |
if (value != null && value.startsWith(ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN)) { | |
return value.substring(ESCAPED_OBJECT_REFERENCE_BEGIN_TOKEN.length() - 1); | |
} | |
return value; | |
} | |
protected void applyProperty(Object object, String propertyName, String stringValue) { | |
Object value; | |
if (isReference(stringValue)) { | |
String id = getId(stringValue); | |
if (log.isDebugEnabled()) { | |
log.debug("Encountered object reference [" + stringValue + "]. Looking up object " + | |
"with id [" + id + "]"); | |
} | |
value = getReferencedObject(id); | |
} else { | |
value = unescapeIfNecessary(stringValue); | |
} | |
try { | |
if (log.isTraceEnabled()) { | |
log.trace("Applying property [" + propertyName + "] value [" + value + "] on object of type [" + object.getClass().getName() + "]"); | |
} | |
BeanUtils.setProperty(object, propertyName, value); | |
} catch (Exception e) { | |
String msg = "Unable to set property [" + propertyName + "] with value [" + stringValue + "]. If " + | |
"'" + stringValue + "' is a reference to another (previously defined) object, please prefix it with " + | |
"'" + OBJECT_REFERENCE_BEGIN_TOKEN + "' to indicate that the referenced " + | |
"object should be used as the actual value. " + | |
"For example, " + OBJECT_REFERENCE_BEGIN_TOKEN + stringValue; | |
throw new ConfigurationException(msg, e); | |
} | |
} | |
} |