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 <s:property value="point"/> 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".
+ * @Inject("i") int i;
+ *
+ * // Inject the default implementation of Bar and the String constant
+ * // named "s".
+ * @Inject Foo(Bar bar, @Inject("s") String s) {
+ * ...
+ * }
+ *
+ * // Inject the default implementation of Baz and the Bob implementation
+ * // named "foo".
+ * @Inject void initialize(Baz baz, @Inject("foo") Bob bob) {
+ * ...
+ * }
+ *
+ * // Inject the default implementation of Tee.
+ * @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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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);
+ }
+}