You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2011/12/02 17:33:45 UTC

svn commit: r1209569 [19/50] - in /struts/struts2/branches/STRUTS_3_X: apps/blank/src/main/java/example/ apps/blank/src/test/java/example/ apps/jboss-blank/src/main/java/example/ apps/jboss-blank/src/test/java/example/ apps/mailreader/src/main/java/mai...

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/conversion/impl/XWorkConverter.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/conversion/impl/XWorkConverter.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/conversion/impl/XWorkConverter.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/conversion/impl/XWorkConverter.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,844 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed 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.struts2.xwork2.conversion.impl;
+
+import org.apache.struts2.xwork2.ActionContext;
+import org.apache.struts2.xwork2.ObjectFactory;
+import org.apache.struts2.xwork2.XWorkMessages;
+import org.apache.struts2.xwork2.XWorkException;
+import org.apache.struts2.xwork2.conversion.TypeConverter;
+import org.apache.struts2.xwork2.conversion.annotations.Conversion;
+import org.apache.struts2.xwork2.conversion.annotations.ConversionRule;
+import org.apache.struts2.xwork2.conversion.annotations.ConversionType;
+import org.apache.struts2.xwork2.conversion.annotations.TypeConversion;
+import org.apache.struts2.xwork2.inject.Inject;
+import org.apache.struts2.xwork2.ognl.XWorkTypeConverterWrapper;
+import org.apache.struts2.xwork2.util.logging.Logger;
+import org.apache.struts2.xwork2.util.logging.LoggerFactory;
+import org.apache.struts2.xwork2.util.reflection.ReflectionContextState;
+import org.apache.struts2.xwork2.util.AnnotationUtils;
+import org.apache.struts2.xwork2.util.ClassLoaderUtil;
+import org.apache.struts2.xwork2.util.CompoundRoot;
+import org.apache.struts2.xwork2.util.FileManager;
+import org.apache.struts2.xwork2.util.LocalizedTextUtil;
+import org.apache.struts2.xwork2.util.ValueStack;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.text.MessageFormat;
+
+
+/**
+ * XWorkConverter is a singleton used by many of the Struts 2's Ognl extention points,
+ * such as InstantiatingNullHandler, XWorkListPropertyAccessor etc to do object
+ * conversion.
+ * <p/>
+ * <!-- START SNIPPET: javadoc -->
+ * <p/>
+ * Type conversion is great for situations where you need to turn a String in to a more complex object. Because the web
+ * is type-agnostic (everything is a string in HTTP), Struts 2's type conversion features are very useful. For instance,
+ * if you were prompting a user to enter in coordinates in the form of a string (such as "3, 22"), you could have
+ * Struts 2 do the conversion both from String to Point and from Point to String.
+ * <p/>
+ * <p/> Using this "point" example, if your action (or another compound object in which you are setting properties on)
+ * has a corresponding ClassName-conversion.properties file, Struts 2 will use the configured type converters for
+ * conversion to and from strings. So turning "3, 22" in to new Point(3, 22) is done by merely adding the following
+ * entry to <b>ClassName-conversion.properties</b> (Note that the PointConverter should impl the TypeConverter
+ * interface):
+ * <p/>
+ * <p/><b>point = com.acme.PointConverter</b>
+ * <p/>
+ * <p/> Your type converter should be sure to check what class type it is being requested to convert. Because it is used
+ * for both to and from strings, you will need to split the conversion method in to two parts: one that turns Strings in
+ * to Points, and one that turns Points in to Strings.
+ * <p/>
+ * <p/> After this is done, you can now reference your point (using &lt;s:property value="point"/&gt; in JSP or ${point}
+ * in FreeMarker) and it will be printed as "3, 22" again. As such, if you submit this back to an action, it will be
+ * converted back to a Point once again.
+ * <p/>
+ * <p/> In some situations you may wish to apply a type converter globally. This can be done by editing the file
+ * <b>xwork-conversion.properties</b> in the root of your class path (typically WEB-INF/classes) and providing a
+ * property in the form of the class name of the object you wish to convert on the left hand side and the class name of
+ * the type converter on the right hand side. For example, providing a type converter for all Point objects would mean
+ * adding the following entry:
+ * <p/>
+ * <p/><b>com.acme.Point = com.acme.PointConverter</b>
+ * <p/>
+ * <!-- END SNIPPET: javadoc -->
+ * <p/>
+ * <p/>
+ * <p/>
+ * <!-- START SNIPPET: i18n-note -->
+ * <p/>
+ * Type conversion should not be used as a substitute for i18n. It is not recommended to use this feature to print out
+ * properly formatted dates. Rather, you should use the i18n features of Struts 2 (and consult the JavaDocs for JDK's
+ * MessageFormat object) to see how a properly formatted date should be displayed.
+ * <p/>
+ * <!-- END SNIPPET: i18n-note -->
+ * <p/>
+ * <p/>
+ * <p/>
+ * <!-- START SNIPPET: error-reporting -->
+ * <p/>
+ * Any error that occurs during type conversion may or may not wish to be reported. For example, reporting that the
+ * input "abc" could not be converted to a number might be important. On the other hand, reporting that an empty string,
+ * "", cannot be converted to a number might not be important - especially in a web environment where it is hard to
+ * distinguish between a user not entering a value vs. entering a blank value.
+ * <p/>
+ * <p/> By default, all conversion errors are reported using the generic i18n key <b>xwork.default.invalid.fieldvalue</b>,
+ * which you can override (the default text is <i>Invalid field value for field "xxx"</i>, where xxx is the field name)
+ * in your global i18n resource bundle.
+ * <p/>
+ * <p/>However, sometimes you may wish to override this message on a per-field basis. You can do this by adding an i18n
+ * key associated with just your action (Action.properties) using the pattern <b>invalid.fieldvalue.xxx</b>, where xxx
+ * is the field name.
+ * <p/>
+ * <p/>It is important to know that none of these errors are actually reported directly. Rather, they are added to a map
+ * called <i>conversionErrors</i> in the ActionContext. There are several ways this map can then be accessed and the
+ * errors can be reported accordingly.
+ * <p/>
+ * <!-- END SNIPPET: error-reporting -->
+ *
+ * @author <a href="mailto:plightbo@gmail.com">Pat Lightbody</a>
+ * @author Rainer Hermanns
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ * @author tm_jee
+ * @version $Date: 2011-12-02 12:24:48 +0100 (Fri, 02 Dec 2011) $ $Id: XWorkConverter.java 1209415 2011-12-02 11:24:48Z lukaszlenart $
+ * @see XWorkBasicConverter
+ */
+public class XWorkConverter extends DefaultTypeConverter {
+
+    protected static final Logger LOG = LoggerFactory.getLogger(XWorkConverter.class);
+    public static final String REPORT_CONVERSION_ERRORS = "report.conversion.errors";
+    public static final String CONVERSION_PROPERTY_FULLNAME = "conversion.property.fullName";
+    public static final String CONVERSION_ERROR_PROPERTY_PREFIX = "invalid.fieldvalue.";
+    public static final String CONVERSION_COLLECTION_PREFIX = "Collection_";
+
+    public static final String LAST_BEAN_CLASS_ACCESSED = "last.bean.accessed";
+    public static final String LAST_BEAN_PROPERTY_ACCESSED = "last.property.accessed";
+    public static final String MESSAGE_INDEX_PATTERN = "\\[\\d+\\]\\.";
+    public static final String MESSAGE_INDEX_BRACKET_PATTERN = "[\\[\\]\\.]";
+    public static final String PERIOD = ".";
+    public static final Pattern messageIndexPattern = Pattern.compile(MESSAGE_INDEX_PATTERN); 
+
+    /**
+     * Target class conversion Mappings.
+     * <pre>
+     * Map<Class, Map<String, Object>>
+     *  - Class -> convert to class
+     *  - Map<String, Object>
+     *    - String -> property name
+     *                eg. Element_property, property etc.
+     *    - Object -> String to represent properties
+     *                eg. value part of
+     *                    KeyProperty_property=id
+     *             -> TypeConverter to represent an Ognl TypeConverter
+     *                eg. value part of
+     *                    property=foo.bar.MyConverter
+     *             -> Class to represent a class
+     *                eg. value part of
+     *                    Element_property=foo.bar.MyObject
+     * </pre>
+     */
+    protected HashMap<Class, Map<String, Object>> mappings = new HashMap<Class, Map<String, Object>>(); // action
+
+    /**
+     * Unavailable target class conversion mappings, serves as a simple cache.
+     */
+    protected HashSet<Class> noMapping = new HashSet<Class>(); // action
+
+    /**
+     * Record class and its type converter mapping.
+     * <pre>
+     * - String - classname as String
+     * - TypeConverter - instance of TypeConverter
+     * </pre>
+     */
+    protected HashMap<String, TypeConverter> defaultMappings = new HashMap<String, TypeConverter>();  // non-action (eg. returned value)
+
+    /**
+     * Record classes that doesn't have conversion mapping defined.
+     * <pre>
+     * - String -> classname as String
+     * </pre>
+     */
+    protected HashSet<String> unknownMappings = new HashSet<String>();     // non-action (eg. returned value)
+
+    private TypeConverter defaultTypeConverter;
+    private ObjectFactory objectFactory;
+
+
+    protected XWorkConverter() {
+    }
+
+    @Inject
+    public void setObjectFactory(ObjectFactory factory) {
+        this.objectFactory = factory;
+        // note: this file is deprecated
+        loadConversionProperties("xwork-default-conversion.properties");
+
+        loadConversionProperties("xwork-conversion.properties");
+    }
+
+    @Inject
+    public void setDefaultTypeConverter(XWorkBasicConverter conv) {
+        this.defaultTypeConverter = conv;
+    }
+
+    public static String getConversionErrorMessage(String propertyName, ValueStack stack) {
+        String defaultMessage = LocalizedTextUtil.findDefaultText(XWorkMessages.DEFAULT_INVALID_FIELDVALUE,
+                ActionContext.getContext().getLocale(),
+                new Object[]{
+                        propertyName
+                });
+
+        List<String> indexValues = getIndexValues(propertyName);
+
+        propertyName = removeAllIndexesInProperyName(propertyName);
+
+        String getTextExpression = "getText('" + CONVERSION_ERROR_PROPERTY_PREFIX + propertyName + "','" + defaultMessage + "')";
+        String message = (String) stack.findValue(getTextExpression);
+
+        if (message == null) {
+            message = defaultMessage;
+        } else {
+            message = MessageFormat.format(message, indexValues.toArray());
+        }
+
+        return message;
+    }
+
+    private static String removeAllIndexesInProperyName(String propertyName) {
+        return propertyName.replaceAll(MESSAGE_INDEX_PATTERN, PERIOD);
+    }
+
+    private static List<String> getIndexValues(String propertyName) {
+        Matcher matcher = messageIndexPattern.matcher(propertyName);
+        List<String> indexes = new ArrayList<String>();
+        while (matcher.find()) {
+            Integer index = new Integer(matcher.group().replaceAll(MESSAGE_INDEX_BRACKET_PATTERN, "")) + 1;
+            indexes.add(Integer.toString(index));
+        }
+        return indexes;
+    }
+
+    public static String buildConverterFilename(Class clazz) {
+        String className = clazz.getName();
+        return className.replace('.', '/') + "-conversion.properties";
+    }
+
+    @Override
+    public Object convertValue(Map<String, Object> map, Object o, Class aClass) {
+        return convertValue(map, null, null, null, o, aClass);
+    }
+
+    /**
+     * Convert value from one form to another.
+     * Minimum requirement of arguments:
+     * <ul>
+     * <li>supplying context, toClass and value</li>
+     * <li>supplying context, target and value.</li>
+     * </ul>
+     *
+     * @see TypeConverter#convertValue(java.util.Map, java.lang.Object, java.lang.reflect.Member, java.lang.String, java.lang.Object, java.lang.Class)
+     */
+    @Override
+    public Object convertValue(Map<String, Object> context, Object target, Member member, String property, Object value, Class toClass) {
+        //
+        // Process the conversion using the default mappings, if one exists
+        //
+        TypeConverter tc = null;
+
+        if ((value != null) && (toClass == value.getClass())) {
+            return value;
+        }
+
+        // allow this method to be called without any context
+        // i.e. it can be called with as little as "Object value" and "Class toClass"
+        if (target != null) {
+            Class clazz = target.getClass();
+
+            Object[] classProp = null;
+
+            // this is to handle weird issues with setValue with a different type
+            if ((target instanceof CompoundRoot) && (context != null)) {
+                classProp = getClassProperty(context);
+            }
+
+            if (classProp != null) {
+                clazz = (Class) classProp[0];
+                property = (String) classProp[1];
+            }
+
+            tc = (TypeConverter) getConverter(clazz, property);
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("field-level type converter for property [" + property + "] = " + (tc == null ? "none found" : tc));
+        }
+
+        if (tc == null && context != null) {
+            // ok, let's see if we can look it up by path as requested in XW-297
+            Object lastPropertyPath = context.get(ReflectionContextState.CURRENT_PROPERTY_PATH);
+            Class clazz = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED);
+            if (lastPropertyPath != null && clazz != null) {
+                String path = lastPropertyPath + "." + property;
+                tc = (TypeConverter) getConverter(clazz, path);
+            }
+        }
+
+        if (tc == null) {
+            if (toClass.equals(String.class) && (value != null) && !(value.getClass().equals(String.class) || value.getClass().equals(String[].class))) {
+                // when converting to a string, use the source target's class's converter
+                tc = lookup(value.getClass());
+            } else {
+                // when converting from a string, use the toClass's converter
+                tc = lookup(toClass);
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("global-level type converter for property [" + property + "] = " + (tc == null ? "none found" : tc));
+        }
+
+
+        if (tc != null) {
+            try {
+                return tc.convertValue(context, target, member, property, value, toClass);
+            } catch (Exception e) {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("unable to convert value using type converter [#0]", e, tc.getClass().getName());
+                handleConversionException(context, property, value, target);
+
+                return TypeConverter.NO_CONVERSION_POSSIBLE;
+            }
+        }
+
+        if (defaultTypeConverter != null) {
+            try {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("falling back to default type converter [" + defaultTypeConverter + "]");
+                return defaultTypeConverter.convertValue(context, target, member, property, value, toClass);
+            } catch (Exception e) {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("unable to convert value using type converter [#0]", e, defaultTypeConverter.getClass().getName());
+                handleConversionException(context, property, value, target);
+
+                return TypeConverter.NO_CONVERSION_POSSIBLE;
+            }
+        } else {
+            try {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("falling back to Ognl's default type conversion");
+                return super.convertValue(value, toClass);
+            } catch (Exception e) {
+                if (LOG.isDebugEnabled())
+                    LOG.debug("unable to convert value using type converter [#0]", e, super.getClass().getName());
+                handleConversionException(context, property, value, target);
+
+                return TypeConverter.NO_CONVERSION_POSSIBLE;
+            }
+        }
+    }
+
+    /**
+     * Looks for a TypeConverter in the default mappings.
+     *
+     * @param className name of the class the TypeConverter must handle
+     * @return a TypeConverter to handle the specified class or null if none can be found
+     */
+    public TypeConverter lookup(String className) {
+        if (unknownMappings.contains(className) && !defaultMappings.containsKey(className)) {
+            return null;
+        }
+
+        TypeConverter result = defaultMappings.get(className);
+
+        //Looks for super classes
+        if (result == null) {
+            Class clazz = null;
+
+            try {
+                clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
+            } catch (ClassNotFoundException cnfe) {
+                //swallow
+            }
+
+            result = lookupSuper(clazz);
+
+            if (result != null) {
+                //Register now, the next lookup will be faster
+                registerConverter(className, result);
+            } else {
+                // if it isn't found, never look again (also faster)
+                registerConverterNotFound(className);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Looks for a TypeConverter in the default mappings.
+     *
+     * @param clazz the class the TypeConverter must handle
+     * @return a TypeConverter to handle the specified class or null if none can be found
+     */
+    public TypeConverter lookup(Class clazz) {
+        return lookup(clazz.getName());
+    }
+
+    protected Object getConverter(Class clazz, String property) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Property: " + property);
+            LOG.debug("Class: " + clazz.getName());
+        }
+        synchronized (clazz) {
+            if ((property != null) && !noMapping.contains(clazz)) {
+                try {
+                    Map<String, Object> mapping = mappings.get(clazz);
+
+                    if (mapping == null) {
+                        mapping = buildConverterMapping(clazz);
+                    } else {
+                        mapping = conditionalReload(clazz, mapping);
+                    }
+
+                    Object converter = mapping.get(property);
+                    if (LOG.isDebugEnabled() && converter == null) {
+                        LOG.debug("converter is null for property " + property + ". Mapping size: " + mapping.size());
+                        for (String next : mapping.keySet()) {
+                            LOG.debug(next + ":" + mapping.get(next));
+                        }
+                    }
+                    return converter;
+                } catch (Throwable t) {
+                    noMapping.add(clazz);
+                }
+            }
+        }
+
+        return null;
+    }
+
+    protected void handleConversionException(Map<String, Object> context, String property, Object value, Object object) {
+        if (context != null && (Boolean.TRUE.equals(context.get(REPORT_CONVERSION_ERRORS)))) {
+            String realProperty = property;
+            String fullName = (String) context.get(CONVERSION_PROPERTY_FULLNAME);
+
+            if (fullName != null) {
+                realProperty = fullName;
+            }
+
+            Map<String, Object> conversionErrors = (Map<String, Object>) context.get(ActionContext.CONVERSION_ERRORS);
+
+            if (conversionErrors == null) {
+                conversionErrors = new HashMap<String, Object>();
+                context.put(ActionContext.CONVERSION_ERRORS, conversionErrors);
+            }
+
+            conversionErrors.put(realProperty, value);
+        }
+    }
+
+    public synchronized void registerConverter(String className, TypeConverter converter) {
+        defaultMappings.put(className, converter);
+        if (unknownMappings.contains(className)) {
+            unknownMappings.remove(className);
+        }
+    }
+
+    public synchronized void registerConverterNotFound(String className) {
+        unknownMappings.add(className);
+    }
+
+    private Object[] getClassProperty(Map<String, Object> context) {
+        Object lastClass = context.get(LAST_BEAN_CLASS_ACCESSED);
+        Object lastProperty = context.get(LAST_BEAN_PROPERTY_ACCESSED);
+        return (lastClass != null && lastProperty != null) ? new Object[] {lastClass, lastProperty} : null;
+    }
+
+    /**
+     * Looks for converter mappings for the specified class and adds it to an existing map.  Only new converters are
+     * added.  If a converter is defined on a key that already exists, the converter is ignored.
+     *
+     * @param mapping an existing map to add new converter mappings to
+     * @param clazz   class to look for converter mappings for
+     */
+    protected void addConverterMapping(Map<String, Object> mapping, Class clazz) {
+        try {
+            String converterFilename = buildConverterFilename(clazz);
+            InputStream is = FileManager.loadFile(converterFilename, clazz);
+
+            if (is != null) {
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("processing conversion file [" + converterFilename + "] [class=" + clazz + "]");
+                }
+
+                Properties prop = new Properties();
+                prop.load(is);
+
+                for (Map.Entry<Object, Object> entry : prop.entrySet()) {
+                    String key = (String) entry.getKey();
+
+                    if (mapping.containsKey(key)) {
+                        break;
+                    }
+                    // for keyProperty of Set
+                    if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX)
+                            || key.startsWith(DefaultObjectTypeDeterminer.CREATE_IF_NULL_PREFIX)) {
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as String]");
+                        }
+                        mapping.put(key, entry.getValue());
+                    }
+                    //for properties of classes
+                    else if (!(key.startsWith(DefaultObjectTypeDeterminer.ELEMENT_PREFIX) ||
+                            key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX) ||
+                            key.startsWith(DefaultObjectTypeDeterminer.DEPRECATED_ELEMENT_PREFIX))
+                            ) {
+                        TypeConverter _typeConverter = createTypeConverter((String) entry.getValue());
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as TypeConverter " + _typeConverter + "]");
+                        }
+                        mapping.put(key, _typeConverter);
+                    }
+                    //for keys of Maps
+                    else if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX)) {
+
+                        Class converterClass = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue());
+
+                        //check if the converter is a type converter if it is one
+                        //then just put it in the map as is. Otherwise
+                        //put a value in for the type converter of the class
+                        if (converterClass.isAssignableFrom(TypeConverter.class)) {
+                            TypeConverter _typeConverter = createTypeConverter((String) entry.getValue());
+                            if (LOG.isDebugEnabled()) {
+                                LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as TypeConverter " + _typeConverter + "]");
+                            }
+                            mapping.put(key, _typeConverter);
+                        } else {
+                            if (LOG.isDebugEnabled()) {
+                                LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as Class " + converterClass + "]");
+                            }
+                            mapping.put(key, converterClass);
+                        }
+                    }
+                    //elements(values) of maps / lists
+                    else {
+                        Class _c = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue());
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("\t" + key + ":" + entry.getValue() + "[treated as Class " + _c + "]");
+                        }
+                        mapping.put(key, _c);
+                    }
+                }
+            }
+        } catch (Exception ex) {
+            LOG.error("Problem loading properties for " + clazz.getName(), ex);
+        }
+
+        // Process annotations
+        Annotation[] annotations = clazz.getAnnotations();
+
+        for (Annotation annotation : annotations) {
+            if (annotation instanceof Conversion) {
+                Conversion conversion = (Conversion) annotation;
+
+                for (TypeConversion tc : conversion.conversions()) {
+
+                    String key = tc.key();
+
+                    if (mapping.containsKey(key)) {
+                        break;
+                    }
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug(key + ":" + key);
+                    }
+
+                    if (key != null) {
+                        try {
+                            if (tc.type() == ConversionType.APPLICATION) {
+                                defaultMappings.put(key, createTypeConverter(tc.converter()));
+                            } else {
+                                if (tc.rule().toString().equals(ConversionRule.KEY_PROPERTY) || tc.rule().toString().equals(ConversionRule.CREATE_IF_NULL)) {
+                                    mapping.put(key, tc.value());
+                                }
+                                //for properties of classes
+                                else if (!(tc.rule().toString().equals(ConversionRule.ELEMENT.toString())) ||
+                                        tc.rule().toString().equals(ConversionRule.KEY.toString()) ||
+                                        tc.rule().toString().equals(ConversionRule.COLLECTION.toString())
+                                        ) {
+                                    mapping.put(key, createTypeConverter(tc.converter()));
+
+
+                                }
+                                //for keys of Maps
+                                else if (tc.rule().toString().equals(ConversionRule.KEY.toString())) {
+                                    Class converterClass = Thread.currentThread().getContextClassLoader().loadClass(tc.converter());
+                                    if (LOG.isDebugEnabled()) {
+                                        LOG.debug("Converter class: " + converterClass);
+                                    }
+                                    //check if the converter is a type converter if it is one
+                                    //then just put it in the map as is. Otherwise
+                                    //put a value in for the type converter of the class
+                                    if (converterClass.isAssignableFrom(TypeConverter.class)) {
+                                        mapping.put(key, createTypeConverter(tc.converter()));
+                                    } else {
+                                        mapping.put(key, converterClass);
+                                        if (LOG.isDebugEnabled()) {
+                                            LOG.debug("Object placed in mapping for key "
+                                                    + key
+                                                    + " is "
+                                                    + mapping.get(key));
+                                        }
+
+                                    }
+
+                                }
+                                //elements(values) of maps / lists
+                                else {
+                                    mapping.put(key, Thread.currentThread().getContextClassLoader().loadClass(tc.converter()));
+                                }
+                            }
+                        } catch (Exception e) {
+                        }
+                    }
+                }
+            }
+        }
+
+        Method[] methods = clazz.getMethods();
+
+        for (Method method : methods) {
+
+            annotations = method.getAnnotations();
+
+            for (Annotation annotation : annotations) {
+                if (annotation instanceof TypeConversion) {
+                    TypeConversion tc = (TypeConversion) annotation;
+
+                    String key = tc.key();
+                    if (mapping.containsKey(key)) {
+                        break;
+                    }
+                    // Default to the property name
+                    if (key != null && key.length() == 0) {
+                        key = AnnotationUtils.resolvePropertyName(method);
+                        LOG.debug("key from method name... " + key + " - " + method.getName());
+                    }
+
+
+                    if (LOG.isDebugEnabled()) {
+                        LOG.debug(key + ":" + key);
+                    }
+
+                    if (key != null) {
+                        try {
+                            if (tc.type() == ConversionType.APPLICATION) {
+                                defaultMappings.put(key, createTypeConverter(tc.converter()));
+                            } else {
+                                if (tc.rule().toString().equals(ConversionRule.KEY_PROPERTY)) {
+                                    mapping.put(key, tc.value());
+                                }
+                                //for properties of classes
+                                else if (!(tc.rule().toString().equals(ConversionRule.ELEMENT.toString())) ||
+                                        tc.rule().toString().equals(ConversionRule.KEY.toString()) ||
+                                        tc.rule().toString().equals(ConversionRule.COLLECTION.toString())
+                                        ) {
+                                    mapping.put(key, createTypeConverter(tc.converter()));
+                                }
+                                //for keys of Maps
+                                else if (tc.rule().toString().equals(ConversionRule.KEY.toString())) {
+                                    Class converterClass = Thread.currentThread().getContextClassLoader().loadClass(tc.converter());
+                                    if (LOG.isDebugEnabled()) {
+                                        LOG.debug("Converter class: " + converterClass);
+                                    }
+                                    //check if the converter is a type converter if it is one
+                                    //then just put it in the map as is. Otherwise
+                                    //put a value in for the type converter of the class
+                                    if (converterClass.isAssignableFrom(TypeConverter.class)) {
+                                        mapping.put(key, createTypeConverter(tc.converter()));
+                                    } else {
+                                        mapping.put(key, converterClass);
+                                        if (LOG.isDebugEnabled()) {
+                                            LOG.debug("Object placed in mapping for key "
+                                                    + key
+                                                    + " is "
+                                                    + mapping.get(key));
+                                        }
+
+                                    }
+
+                                }
+                                //elements(values) of maps / lists
+                                else {
+                                    mapping.put(key, Thread.currentThread().getContextClassLoader().loadClass(tc.converter()));
+                                }
+                            }
+                        } catch (Exception e) {
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Looks for converter mappings for the specified class, traversing up its class hierarchy and interfaces and adding
+     * any additional mappings it may find.  Mappings lower in the hierarchy have priority over those higher in the
+     * hierarcy.
+     *
+     * @param clazz the class to look for converter mappings for
+     * @return the converter mappings
+     */
+    protected Map<String, Object> buildConverterMapping(Class clazz) throws Exception {
+        Map<String, Object> mapping = new HashMap<String, Object>();
+
+        // check for conversion mapping associated with super classes and any implemented interfaces
+        Class curClazz = clazz;
+
+        while (!curClazz.equals(Object.class)) {
+            // add current class' mappings
+            addConverterMapping(mapping, curClazz);
+
+            // check interfaces' mappings
+            Class[] interfaces = curClazz.getInterfaces();
+
+            for (Class anInterface : interfaces) {
+                addConverterMapping(mapping, anInterface);
+            }
+
+            curClazz = curClazz.getSuperclass();
+        }
+
+        if (mapping.size() > 0) {
+            mappings.put(clazz, mapping);
+        } else {
+            noMapping.add(clazz);
+        }
+
+        return mapping;
+    }
+
+    private Map<String, Object> conditionalReload(Class clazz, Map<String, Object> oldValues) throws Exception {
+        Map<String, Object> mapping = oldValues;
+
+        if (FileManager.isReloadingConfigs()) {
+            if (FileManager.fileNeedsReloading(buildConverterFilename(clazz), clazz)) {
+                mapping = buildConverterMapping(clazz);
+            }
+        }
+
+        return mapping;
+    }
+
+    TypeConverter createTypeConverter(String className) throws Exception {
+        // type converters are used across users
+        Object obj = objectFactory.buildBean(className, null);
+        if (obj instanceof TypeConverter) {
+            return (TypeConverter) obj;
+
+            // For backwards compatibility
+        } else if (obj instanceof ognl.TypeConverter) {
+            return new XWorkTypeConverterWrapper((ognl.TypeConverter) obj);
+        } else {
+            throw new IllegalArgumentException("Type converter class " + obj.getClass() + " doesn't implement org.apache.struts2.xwork2.conversion.TypeConverter");
+        }
+    }
+
+    public void loadConversionProperties(String propsName) {
+        loadConversionProperties(propsName, false);
+    }
+
+    public void loadConversionProperties(String propsName, boolean require) {
+        try {
+            Iterator<URL> resources = ClassLoaderUtil.getResources(propsName, getClass(), true);
+            while (resources.hasNext()) {
+                URL url = resources.next();
+                Properties props = new Properties();
+                props.load(url.openStream());
+
+                if (LOG.isDebugEnabled()) {
+                    LOG.debug("processing conversion file [" + propsName + "]");
+                }
+
+                for (Object o : props.entrySet()) {
+                    Map.Entry entry = (Map.Entry) o;
+                    String key = (String) entry.getKey();
+
+                    try {
+                        TypeConverter _typeConverter = createTypeConverter((String) entry.getValue());
+                        if (LOG.isDebugEnabled()) {
+                            LOG.debug("\t" + key + ":" + entry.getValue() + " [treated as TypeConverter " + _typeConverter + "]");
+                        }
+                        defaultMappings.put(key, _typeConverter);
+                    } catch (Exception e) {
+                        LOG.error("Conversion registration error", e);
+                    }
+                }
+            }
+        } catch (IOException ex) {
+            if (require) {
+                throw new XWorkException("Cannot load conversion properties file: "+propsName, ex);
+            } else {
+                LOG.debug("Cannot load conversion properties file: "+propsName, ex);
+            }
+        }
+    }
+
+    /**
+     * Recurses through a class' interfaces and class hierarchy looking for a TypeConverter in the default mapping that
+     * can handle the specified class.
+     *
+     * @param clazz the class the TypeConverter must handle
+     * @return a TypeConverter to handle the specified class or null if none can be found
+     */
+    TypeConverter lookupSuper(Class clazz) {
+        TypeConverter result = null;
+
+        if (clazz != null) {
+            result = defaultMappings.get(clazz.getName());
+
+            if (result == null) {
+                // Looks for direct interfaces (depth = 1 )
+                Class[] interfaces = clazz.getInterfaces();
+
+                for (Class anInterface : interfaces) {
+                    if (defaultMappings.containsKey(anInterface.getName())) {
+                        result = (TypeConverter) defaultMappings.get(anInterface.getName());
+                        break;
+                    }
+                }
+
+                if (result == null) {
+                    // Looks for the superclass
+                    // If 'clazz' is the Object class, an interface, a primitive type or void then clazz.getSuperClass() returns null
+                    result = lookupSuper(clazz.getSuperclass());
+                }
+            }
+        }
+
+        return result;
+    }
+
+
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/conversion/metadata/ConversionDescription.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/conversion/metadata/ConversionDescription.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/conversion/metadata/ConversionDescription.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/conversion/metadata/ConversionDescription.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2002-2006,2009 The Apache Software Foundation.
+ * 
+ * Licensed 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.struts2.xwork2.conversion.metadata;
+
+import org.apache.struts2.xwork2.conversion.annotations.ConversionRule;
+import org.apache.struts2.xwork2.conversion.impl.DefaultObjectTypeDeterminer;
+import org.apache.struts2.xwork2.util.logging.Logger;
+import org.apache.struts2.xwork2.util.logging.LoggerFactory;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * <code>ConversionDescription</code>
+ *
+ * @author Rainer Hermanns
+ * @version $Id: ConversionDescription.java 1209415 2011-12-02 11:24:48Z lukaszlenart $
+ */
+public class ConversionDescription {
+
+    /**
+     * Jakarta commons-logging reference.
+     */
+    protected static Logger log = null;
+
+
+    public static final String KEY_PREFIX = "Key_";
+    public static final String ELEMENT_PREFIX = "Element_";
+    public static final String KEY_PROPERTY_PREFIX = "KeyProperty_";
+    public static final String DEPRECATED_ELEMENT_PREFIX = "Collection_";
+
+    /**
+     * Key used for type conversion of maps.
+     */
+    String MAP_PREFIX = "Map_";
+
+    public String property;
+    public String typeConverter = "";
+    public String rule = "";
+    public String value = "";
+    public String fullQualifiedClassName;
+    public String type = null;
+
+    public ConversionDescription() {
+        log = LoggerFactory.getLogger(this.getClass());
+    }
+
+    /**
+     * Creates an ConversionDescription with the specified property name.
+     *
+     * @param property
+     */
+    public ConversionDescription(String property) {
+        this.property = property;
+        log = LoggerFactory.getLogger(this.getClass());
+    }
+
+    /**
+     * <p>
+     * Sets the property name to be inserted into the related conversion.properties file.<br/>
+     * Note: Do not add COLLECTION_PREFIX or MAP_PREFIX keys to property names.
+     * </p>
+     *
+     * @param property The property to be converted.
+     */
+    public void setProperty(String property) {
+        this.property = property;
+    }
+
+    /**
+     * Sets the class name of the type converter to be used.
+     *
+     * @param typeConverter The class name of the type converter.
+     */
+    public void setTypeConverter(String typeConverter) {
+        this.typeConverter = typeConverter;
+    }
+
+    /**
+     * Sets the rule prefix for COLLECTION_PREFIX or MAP_PREFIX key.
+     * Defaults to en emtpy String.
+     *
+     * @param rule
+     */
+    public void setRule(String rule) {
+        if (rule != null && rule.length() > 0) {
+            if (rule.equals(ConversionRule.COLLECTION.toString())) {
+                this.rule = DefaultObjectTypeDeterminer.DEPRECATED_ELEMENT_PREFIX;
+            } else if (rule.equals(ConversionRule.ELEMENT.toString())) {
+                this.rule = DefaultObjectTypeDeterminer.ELEMENT_PREFIX;
+            } else if (rule.equals(ConversionRule.KEY.toString())) {
+                this.rule = DefaultObjectTypeDeterminer.KEY_PREFIX;
+            } else if (rule.equals(ConversionRule.KEY_PROPERTY.toString())) {
+                this.rule = DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX;
+            } else if (rule.equals(ConversionRule.MAP.toString())) {
+                this.rule = MAP_PREFIX;
+            }
+        }
+    }
+
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    /**
+     * Returns the conversion description as property entry.
+     * <p>
+     * Example:<br/>
+     * property.name = converter.className<br/>
+     * Collection_property.name = converter.className<br/>
+     * Map_property.name = converter.className
+     * KeyProperty_name = id
+     * </p>
+     *
+     * @return the conversion description as property entry.
+     */
+    public String asProperty() {
+        StringWriter sw = new StringWriter();
+        PrintWriter writer = null;
+        try {
+            writer = new PrintWriter(sw);
+            writer.print(rule);
+            writer.print(property);
+            writer.print("=");
+            if ( rule.startsWith(DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX) && value != null && value.length() > 0 ) {
+                writer.print(value);
+            } else {
+                writer.print(typeConverter);
+            }
+        } finally {
+            if (writer != null) {
+                writer.flush();
+                writer.close();
+            }
+        }
+
+        return sw.toString();
+
+    }
+
+    /**
+     * Returns the fullQualifiedClassName attribute is used to create the special <code>conversion.properties</code> file name.
+     *
+     * @return fullQualifiedClassName
+     */
+    public String getFullQualifiedClassName() {
+        return fullQualifiedClassName;
+    }
+
+    /**
+     * The fullQualifiedClassName attribute is used to create the special <code>conversion.properties</code> file name.
+     *
+     * @param fullQualifiedClassName
+     */
+    public void setFullQualifiedClassName(String fullQualifiedClassName) {
+        this.fullQualifiedClassName = fullQualifiedClassName;
+    }
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/conversion/metadata/package.html
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/conversion/metadata/package.html?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/conversion/metadata/package.html (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/conversion/metadata/package.html Fri Dec  2 16:33:03 2011
@@ -0,0 +1 @@
+<body>Type conversion meta data classes.</body>

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/inject/ConstructionContext.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/inject/ConstructionContext.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/inject/ConstructionContext.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/inject/ConstructionContext.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,124 @@
+/**
+ * Copyright (C) 2006 Google Inc.
+ *
+ * Licensed 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.struts2.xwork2.inject;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Context of a dependency construction. Used to manage circular references.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+class ConstructionContext<T> {
+
+  T currentReference;
+  boolean constructing;
+
+  List<DelegatingInvocationHandler<T>> invocationHandlers;
+
+  T getCurrentReference() {
+    return currentReference;
+  }
+
+  void removeCurrentReference() {
+    this.currentReference = null;
+  }
+
+  void setCurrentReference(T currentReference) {
+    this.currentReference = currentReference;
+  }
+
+  boolean isConstructing() {
+    return constructing;
+  }
+
+  void startConstruction() {
+    this.constructing = true;
+  }
+
+  void finishConstruction() {
+    this.constructing = false;
+    invocationHandlers = null;
+  }
+
+  Object createProxy(Class<? super T> expectedType) {
+    // TODO: if I create a proxy which implements all the interfaces of
+    // the implementation type, I'll be able to get away with one proxy
+    // instance (as opposed to one per caller).
+
+    if (!expectedType.isInterface()) {
+      throw new DependencyException(
+          expectedType.getName() + " is not an interface.");
+    }
+
+    if (invocationHandlers == null) {
+      invocationHandlers = new ArrayList<DelegatingInvocationHandler<T>>();
+    }
+
+    DelegatingInvocationHandler<T> invocationHandler =
+        new DelegatingInvocationHandler<T>();
+    invocationHandlers.add(invocationHandler);
+
+    return Proxy.newProxyInstance(
+      expectedType.getClassLoader(),
+      new Class[] { expectedType },
+      invocationHandler
+    );
+  }
+
+  void setProxyDelegates(T delegate) {
+    if (invocationHandlers != null) {
+      for (DelegatingInvocationHandler<T> invocationHandler
+          : invocationHandlers) {
+        invocationHandler.setDelegate(delegate);
+      }
+    }
+  }
+
+  static class DelegatingInvocationHandler<T> implements InvocationHandler {
+
+    T delegate;
+
+    public Object invoke(Object proxy, Method method, Object[] args)
+        throws Throwable {
+      if (delegate == null) {
+        throw new IllegalStateException(
+            "Not finished constructing. Please don't call methods on this"
+                + " object until the caller's construction is complete.");
+      }
+
+      try {
+        return method.invoke(delegate, args);
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException(e);
+      } catch (IllegalArgumentException e) {
+        throw new RuntimeException(e);
+      } catch (InvocationTargetException e) {
+        throw e.getTargetException();
+      }
+    }
+
+    void setDelegate(T delegate) {
+      this.delegate = delegate;
+    }
+  }
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/inject/Container.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/inject/Container.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/inject/Container.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/inject/Container.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,113 @@
+/**
+ * Copyright (C) 2006 Google Inc.
+ *
+ * Licensed 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.struts2.xwork2.inject;
+
+import java.io.Serializable;
+import java.util.Set;
+
+/**
+ * Injects dependencies into constructors, methods and fields annotated with
+ * {@link Inject}. Immutable.
+ *
+ * <p>When injecting a method or constructor, you can additionally annotate
+ * its parameters with {@link Inject} and specify a dependency name. When a
+ * parameter has no annotation, the container uses the name from the method or
+ * constructor's {@link Inject} annotation respectively.
+ *
+ * <p>For example:
+ *
+ * <pre>
+ *  class Foo {
+ *
+ *    // Inject the int constant named "i".
+ *    &#64;Inject("i") int i;
+ *
+ *    // Inject the default implementation of Bar and the String constant
+ *    // named "s".
+ *    &#64;Inject Foo(Bar bar, @Inject("s") String s) {
+ *      ...
+ *    }
+ *
+ *    // Inject the default implementation of Baz and the Bob implementation
+ *    // named "foo".
+ *    &#64;Inject void initialize(Baz baz, @Inject("foo") Bob bob) {
+ *      ...
+ *    }
+ *
+ *    // Inject the default implementation of Tee.
+ *    &#64;Inject void setTee(Tee tee) {
+ *      ...
+ *    }
+ *  }
+ * </pre>
+ *
+ * <p>To create and inject an instance of {@code Foo}:
+ *
+ * <pre>
+ *  Container c = ...;
+ *  Foo foo = c.inject(Foo.class);
+ * </pre>
+ *
+ * @see ContainerBuilder
+ * @author crazybob@google.com (Bob Lee)
+ */
+public interface Container extends Serializable {
+
+  /**
+   * Default dependency name.
+   */
+  String DEFAULT_NAME = "default";
+
+  /**
+   * Injects dependencies into the fields and methods of an existing object.
+   */
+  void inject(Object o);
+
+  /**
+   * Creates and injects a new instance of type {@code implementation}.
+   */
+  <T> T inject(Class<T> implementation);
+
+  /**
+   * Gets an instance of the given dependency which was declared in
+   * {@link ContainerBuilder}.
+   */
+  <T> T getInstance(Class<T> type, String name);
+
+  /**
+   * Convenience method.&nbsp;Equivalent to {@code getInstance(type,
+   * DEFAULT_NAME)}.
+   */
+  <T> T getInstance(Class<T> type);
+  
+  /**
+   * Gets a set of all registered names for the given type
+   * @param type The instance type
+   * @return A set of registered names
+   */
+  Set<String> getInstanceNames(Class<?> type);
+
+  /**
+   * Sets the scope strategy for the current thread.
+   */
+  void setScopeStrategy(Scope.Strategy scopeStrategy);
+
+  /**
+   * Removes the scope strategy for the current thread.
+   */
+  void removeScopeStrategy();
+}

Added: struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/inject/ContainerBuilder.java
URL: http://svn.apache.org/viewvc/struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/inject/ContainerBuilder.java?rev=1209569&view=auto
==============================================================================
--- struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/inject/ContainerBuilder.java (added)
+++ struts/struts2/branches/STRUTS_3_X/xwork-core/src/main/java/org/apache/struts2/xwork2/inject/ContainerBuilder.java Fri Dec  2 16:33:03 2011
@@ -0,0 +1,527 @@
+/**
+ * Copyright (C) 2006 Google Inc.
+ *
+ * Licensed 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.struts2.xwork2.inject;
+
+import java.lang.reflect.Member;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * Builds a dependency injection {@link Container}. The combination of
+ * dependency type and name uniquely identifies a dependency mapping; you can
+ * use the same name for two different types. Not safe for concurrent use.
+ *
+ * <p>Adds the following factories by default:
+ *
+ * <ul>
+ *   <li>Injects the current {@link Container}.
+ *   <li>Injects the {@link Logger} for the injected member's declaring class.
+ * </ul>
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public final class ContainerBuilder {
+
+  final Map<Key<?>, InternalFactory<?>> factories =
+      new HashMap<Key<?>, InternalFactory<?>>();
+  final List<InternalFactory<?>> singletonFactories =
+      new ArrayList<InternalFactory<?>>();
+  final List<Class<?>> staticInjections = new ArrayList<Class<?>>();
+  boolean created;
+  boolean allowDuplicates = false;
+
+  private static final InternalFactory<Container> CONTAINER_FACTORY =
+      new InternalFactory<Container>() {
+        public Container create(InternalContext context) {
+          return context.getContainer();
+        }
+      };
+
+  private static final InternalFactory<Logger> LOGGER_FACTORY =
+      new InternalFactory<Logger>() {
+        public Logger create(InternalContext context) {
+          Member member = context.getExternalContext().getMember();
+          return member == null ? Logger.getAnonymousLogger()
+              : Logger.getLogger(member.getDeclaringClass().getName());
+        }
+      };
+
+  /**
+   * Constructs a new builder.
+   */
+  public ContainerBuilder() {
+    // In the current container as the default Container implementation.
+    factories.put(Key.newInstance(Container.class, Container.DEFAULT_NAME),
+        CONTAINER_FACTORY);
+
+    // Inject the logger for the injected member's declaring class.
+    factories.put(Key.newInstance(Logger.class, Container.DEFAULT_NAME),
+        LOGGER_FACTORY);
+  }
+
+  /**
+   * Maps a dependency. All methods in this class ultimately funnel through
+   * here.
+   */
+  private <T> ContainerBuilder factory(final Key<T> key,
+      InternalFactory<? extends T> factory, Scope scope) {
+    ensureNotCreated();
+    checkKey(key);
+    final InternalFactory<? extends T> scopedFactory =
+        scope.scopeFactory(key.getType(), key.getName(), factory);
+    factories.put(key, scopedFactory);
+    if (scope == Scope.SINGLETON) {
+      singletonFactories.add(new InternalFactory<T>() {
+        public T create(InternalContext context) {
+          try {
+            context.setExternalContext(ExternalContext.newInstance(
+                null, key, context.getContainerImpl()));
+            return scopedFactory.create(context);
+          } finally {
+            context.setExternalContext(null);
+          }
+        }
+      });
+    }
+    return this;
+  }
+  
+  /**
+   * Ensures a key isn't already mapped.
+   */
+  private void checkKey(Key<?> key) {
+    if (factories.containsKey(key) && !allowDuplicates) {
+      throw new DependencyException(
+          "Dependency mapping for " + key + " already exists.");
+    }
+  }
+
+  /**
+   * Maps a factory to a given dependency type and name.
+   *
+   * @param type of dependency
+   * @param name of dependency
+   * @param factory creates objects to inject
+   * @param scope scope of injected instances
+   * @return this builder
+   */
+  public <T> ContainerBuilder factory(final Class<T> type, final String name,
+      final Factory<? extends T> factory, Scope scope) {
+    InternalFactory<T> internalFactory =
+        new InternalFactory<T>() {
+
+      public T create(InternalContext context) {
+        try {
+          Context externalContext = context.getExternalContext();
+          return factory.create(externalContext);
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+
+      @Override
+      public String toString() {
+        return new LinkedHashMap<String, Object>() {{
+          put("type", type);
+          put("name", name);
+          put("factory", factory);
+        }}.toString();
+      }
+    };
+
+    return factory(Key.newInstance(type, name), internalFactory, scope);
+  }
+
+  /**
+   * Convenience method.&nbsp;Equivalent to {@code factory(type,
+   * Container.DEFAULT_NAME, factory, scope)}.
+   *
+   * @see #factory(Class, String, Factory, Scope)
+   */
+  public <T> ContainerBuilder factory(Class<T> type,
+      Factory<? extends T> factory, Scope scope) {
+    return factory(type, Container.DEFAULT_NAME, factory, scope);
+  }
+
+  /**
+   * Convenience method.&nbsp;Equivalent to {@code factory(type, name, factory,
+   * Scope.DEFAULT)}.
+   *
+   * @see #factory(Class, String, Factory, Scope)
+   */
+  public <T> ContainerBuilder factory(Class<T> type, String name,
+      Factory<? extends T> factory) {
+    return factory(type, name, factory, Scope.DEFAULT);
+  }
+
+  /**
+   * Convenience method.&nbsp;Equivalent to {@code factory(type,
+   * Container.DEFAULT_NAME, factory, Scope.DEFAULT)}.
+   *
+   * @see #factory(Class, String, Factory, Scope)
+   */
+  public <T> ContainerBuilder factory(Class<T> type,
+      Factory<? extends T> factory) {
+    return factory(type, Container.DEFAULT_NAME, factory, Scope.DEFAULT);
+  }
+
+  /**
+   * Maps an implementation class to a given dependency type and name. Creates
+   * instances using the container, recursively injecting dependencies.
+   *
+   * @param type of dependency
+   * @param name of dependency
+   * @param implementation class
+   * @param scope scope of injected instances
+   * @return this builder
+   */
+  public <T> ContainerBuilder factory(final Class<T> type, final String name,
+      final Class<? extends T> implementation, final Scope scope) {
+    // This factory creates new instances of the given implementation.
+    // We have to lazy load the constructor because the Container
+    // hasn't been created yet.
+    InternalFactory<? extends T> factory = new InternalFactory<T>() {
+
+      volatile ContainerImpl.ConstructorInjector<? extends T> constructor;
+
+      @SuppressWarnings("unchecked")
+      public T create(InternalContext context) {
+        if (constructor == null) {
+          this.constructor =
+              context.getContainerImpl().getConstructor(implementation);
+        }
+        return (T) constructor.construct(context, type);
+      }
+
+      @Override
+      public String toString() {
+        return new LinkedHashMap<String, Object>() {{
+          put("type", type);
+          put("name", name);
+          put("implementation", implementation);
+          put("scope", scope);
+        }}.toString();
+      }
+    };
+
+    return factory(Key.newInstance(type, name), factory, scope);
+  }
+
+  /**
+   * Maps an implementation class to a given dependency type and name. Creates
+   * instances using the container, recursively injecting dependencies.
+   *
+   * <p>Sets scope to value from {@link Scoped} annotation on the
+   * implementation class. Defaults to {@link Scope#DEFAULT} if no annotation
+   * is found.
+   *
+   * @param type of dependency
+   * @param name of dependency
+   * @param implementation class
+   * @return this builder
+   */
+  public <T> ContainerBuilder factory(final Class<T> type, String name,
+      final Class<? extends T> implementation) {
+    Scoped scoped = implementation.getAnnotation(Scoped.class);
+    Scope scope = scoped == null ? Scope.DEFAULT : scoped.value();
+    return factory(type, name, implementation, scope);
+  }
+
+  /**
+   * Convenience method.&nbsp;Equivalent to {@code factory(type,
+   * Container.DEFAULT_NAME, implementation)}.
+   *
+   * @see #factory(Class, String, Class)
+   */
+  public <T> ContainerBuilder factory(Class<T> type,
+      Class<? extends T> implementation) {
+    return factory(type, Container.DEFAULT_NAME, implementation);
+  }
+
+  /**
+   * Convenience method.&nbsp;Equivalent to {@code factory(type,
+   * Container.DEFAULT_NAME, type)}.
+   *
+   * @see #factory(Class, String, Class)
+   */
+  public <T> ContainerBuilder factory(Class<T> type) {
+    return factory(type, Container.DEFAULT_NAME, type);
+  }
+
+  /**
+   * Convenience method.&nbsp;Equivalent to {@code factory(type, name, type)}.
+   *
+   * @see #factory(Class, String, Class)
+   */
+  public <T> ContainerBuilder factory(Class<T> type, String name) {
+    return factory(type, name, type);
+  }
+
+  /**
+   * Convenience method.&nbsp;Equivalent to {@code factory(type,
+   * Container.DEFAULT_NAME, implementation, scope)}.
+   *
+   * @see #factory(Class, String, Class, Scope)
+   */
+  public <T> ContainerBuilder factory(Class<T> type,
+      Class<? extends T> implementation, Scope scope) {
+    return factory(type, Container.DEFAULT_NAME, implementation, scope);
+  }
+
+  /**
+   * Convenience method.&nbsp;Equivalent to {@code factory(type,
+   * Container.DEFAULT_NAME, type, scope)}.
+   *
+   * @see #factory(Class, String, Class, Scope)
+   */
+  public <T> ContainerBuilder factory(Class<T> type, Scope scope) {
+    return factory(type, Container.DEFAULT_NAME, type, scope);
+  }
+
+  /**
+   * Convenience method.&nbsp;Equivalent to {@code factory(type, name, type,
+   * scope)}.
+   *
+   * @see #factory(Class, String, Class, Scope)
+   */
+  public <T> ContainerBuilder factory(Class<T> type, String name, Scope scope) {
+    return factory(type, name, type, scope);
+  }
+  
+  /**
+   * Convenience method.&nbsp;Equivalent to {@code alias(type, Container.DEFAULT_NAME,
+   * type)}.
+   *
+   * @see #alias(Class, String, String)
+   */
+  public <T> ContainerBuilder alias(Class<T> type, String alias) {
+    return alias(type, Container.DEFAULT_NAME, alias);
+  }
+  
+  /**
+   * Maps an existing factory to a new name. 
+   * 
+   * @param type of dependency
+   * @param name of dependency
+   * @param alias of to the dependency
+   * @return this builder
+   */
+  public <T> ContainerBuilder alias(Class<T> type, String name, String alias) {
+    return alias(Key.newInstance(type, name), Key.newInstance(type, alias));
+  }
+  
+  /**
+   * Maps an existing dependency. All methods in this class ultimately funnel through
+   * here.
+   */
+  private <T> ContainerBuilder alias(final Key<T> key,
+      final Key<T> aliasKey) {
+    ensureNotCreated();
+    checkKey(aliasKey);
+    
+    final InternalFactory<? extends T> scopedFactory = 
+      (InternalFactory<? extends T>)factories.get(key);
+    if (scopedFactory == null) {
+        throw new DependencyException(
+                "Dependency mapping for " + key + " doesn't exists.");
+    }
+    factories.put(aliasKey, scopedFactory);
+    return this;
+  }
+
+  /**
+   * Maps a constant value to the given name.
+   */
+  public ContainerBuilder constant(String name, String value) {
+    return constant(String.class, name, value);
+  }
+
+  /**
+   * Maps a constant value to the given name.
+   */
+  public ContainerBuilder constant(String name, int value) {
+    return constant(int.class, name, value);
+  }
+
+  /**
+   * Maps a constant value to the given name.
+   */
+  public ContainerBuilder constant(String name, long value) {
+    return constant(long.class, name, value);
+  }
+
+  /**
+   * Maps a constant value to the given name.
+   */
+  public ContainerBuilder constant(String name, boolean value) {
+    return constant(boolean.class, name, value);
+  }
+
+  /**
+   * Maps a constant value to the given name.
+   */
+  public ContainerBuilder constant(String name, double value) {
+    return constant(double.class, name, value);
+  }
+
+  /**
+   * Maps a constant value to the given name.
+   */
+  public ContainerBuilder constant(String name, float value) {
+    return constant(float.class, name, value);
+  }
+
+  /**
+   * Maps a constant value to the given name.
+   */
+  public ContainerBuilder constant(String name, short value) {
+    return constant(short.class, name, value);
+  }
+
+  /**
+   * Maps a constant value to the given name.
+   */
+  public ContainerBuilder constant(String name, char value) {
+    return constant(char.class, name, value);
+  }
+
+  /**
+   * Maps a class to the given name.
+   */
+  public ContainerBuilder constant(String name, Class value) {
+    return constant(Class.class, name, value);
+  }
+
+  /**
+   * Maps an enum to the given name.
+   */
+  public <E extends Enum<E>> ContainerBuilder constant(String name, E value) {
+    return constant(value.getDeclaringClass(), name, value);
+  }
+
+  /**
+   * Maps a constant value to the given type and name.
+   */
+  private <T> ContainerBuilder constant(final Class<T> type, final String name,
+      final T value) {
+    InternalFactory<T> factory = new InternalFactory<T>() {
+      public T create(InternalContext ignored) {
+        return value;
+      }
+
+      @Override
+      public String toString() {
+        return new LinkedHashMap<String, Object>() {
+          {
+            put("type", type);
+            put("name", name);
+            put("value", value);
+          }
+        }.toString();
+      }
+    };
+
+    return factory(Key.newInstance(type, name), factory, Scope.DEFAULT);
+  }
+
+  /**
+   * Upon creation, the {@link Container} will inject static fields and methods
+   * into the given classes.
+   *
+   * @param types for which static members will be injected
+   */
+  public ContainerBuilder injectStatics(Class<?>... types) {
+    staticInjections.addAll(Arrays.asList(types));
+    return this;
+  }
+
+  /**
+   * Returns true if this builder contains a mapping for the given type and
+   * name.
+   */
+  public boolean contains(Class<?> type, String name) {
+    return factories.containsKey(Key.newInstance(type, name));
+  }
+
+  /**
+   * Convenience method.&nbsp;Equivalent to {@code contains(type,
+   * Container.DEFAULT_NAME)}.
+   */
+  public boolean contains(Class<?> type) {
+    return contains(type, Container.DEFAULT_NAME);
+  }
+
+  /**
+   * Creates a {@link Container} instance. Injects static members for classes
+   * which were registered using {@link #injectStatics(Class...)}.
+   *
+   * @param loadSingletons If true, the container will load all singletons
+   *  now. If false, the container will lazily load singletons. Eager loading
+   *  is appropriate for production use while lazy loading can speed
+   *  development.
+   * @throws IllegalStateException if called more than once
+   */
+  public Container create(boolean loadSingletons) {
+    ensureNotCreated();
+    created = true;
+    final ContainerImpl container = new ContainerImpl(
+        new HashMap<Key<?>, InternalFactory<?>>(factories));
+    if (loadSingletons) {
+      container.callInContext(new ContainerImpl.ContextualCallable<Void>() {
+        public Void call(InternalContext context) {
+          for (InternalFactory<?> factory : singletonFactories) {
+            factory.create(context);
+          }
+          return null;
+        }
+      });
+    }
+    container.injectStatics(staticInjections);
+    return container;
+  }
+
+  /**
+   * Currently we only support creating one Container instance per builder.
+   * If we want to support creating more than one container per builder,
+   * we should move to a "factory factory" model where we create a factory
+   * instance per Container. Right now, one factory instance would be
+   * shared across all the containers, singletons synchronize on the
+   * container when lazy loading, etc.
+   */
+  private void ensureNotCreated() {
+    if (created) {
+      throw new IllegalStateException("Container already created.");
+    }
+  }
+  
+  public void setAllowDuplicates(boolean val) {
+      allowDuplicates = val;
+  }
+
+  /**
+   * Implemented by classes which participate in building a container.
+   */
+  public interface Command {
+
+    /**
+     * Contributes factories to the given builder.
+     *
+     * @param builder
+     */
+    void build(ContainerBuilder builder);
+  }
+}