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 2015/06/17 23:09:43 UTC
[43/57] [partial] struts git commit: Merges xwork packages into struts
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverter.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverter.java
new file mode 100644
index 0000000..69e78b7
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverter.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2002-2007,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 com.opensymphony.xwork2.conversion.impl;
+
+import com.opensymphony.xwork2.XWorkConstants;
+import com.opensymphony.xwork2.XWorkException;
+import com.opensymphony.xwork2.conversion.TypeConverter;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.Inject;
+
+import java.lang.reflect.Member;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+
+
+/**
+ * <!-- START SNIPPET: javadoc -->
+ * <p/>
+ * XWork will automatically handle the most common type conversion for you. This includes support for converting to
+ * and from Strings for each of the following:
+ * <p/>
+ * <ul>
+ * <li>String</li>
+ * <li>boolean / Boolean</li>
+ * <li>char / Character</li>
+ * <li>int / Integer, float / Float, long / Long, double / Double</li>
+ * <li>dates - uses the SHORT format for the Locale associated with the current request</li>
+ * <li>arrays - assuming the individual strings can be coverted to the individual items</li>
+ * <li>collections - if not object type can be determined, it is assumed to be a String and a new ArrayList is
+ * created</li>
+ * </ul>
+ * <p/> Note that with arrays the type conversion will defer to the type of the array elements and try to convert each
+ * item individually. As with any other type conversion, if the conversion can't be performed the standard type
+ * conversion error reporting is used to indicate a problem occurred while processing the type conversion.
+ * <!-- END SNIPPET: javadoc -->
+ *
+ * @author <a href="mailto:plightbo@gmail.com">Pat Lightbody</a>
+ * @author Mike Mosiewicz
+ * @author Rainer Hermanns
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class XWorkBasicConverter extends DefaultTypeConverter {
+
+ private Container container;
+
+ @Inject
+ public void setContainer(Container container) {
+ this.container = container;
+ }
+
+ @Override
+ public Object convertValue(Map<String, Object> context, Object o, Member member, String propertyName, Object value, Class toType) {
+ Object result = null;
+
+ if (value == null || toType.isAssignableFrom(value.getClass())) {
+ // no need to convert at all, right?
+ return value;
+ }
+
+ if (toType == String.class) {
+ /* the code below has been disabled as it causes sideffects in Struts2 (XW-512)
+ // if input (value) is a number then use special conversion method (XW-490)
+ Class inputType = value.getClass();
+ if (Number.class.isAssignableFrom(inputType)) {
+ result = doConvertFromNumberToString(context, value, inputType);
+ if (result != null) {
+ return result;
+ }
+ }*/
+ // okay use default string conversion
+ result = doConvertToString(context, value);
+ } else if (toType == boolean.class) {
+ result = doConvertToBoolean(value);
+ } else if (toType == Boolean.class) {
+ result = doConvertToBoolean(value);
+ } else if (toType.isArray()) {
+ result = doConvertToArray(context, o, member, propertyName, value, toType);
+ } else if (Date.class.isAssignableFrom(toType)) {
+ result = doConvertToDate(context, value, toType);
+ } else if (Calendar.class.isAssignableFrom(toType)) {
+ result = doConvertToCalendar(context, value);
+ } else if (Collection.class.isAssignableFrom(toType)) {
+ result = doConvertToCollection(context, o, member, propertyName, value, toType);
+ } else if (toType == Character.class) {
+ result = doConvertToCharacter(value);
+ } else if (toType == char.class) {
+ result = doConvertToCharacter(value);
+ } else if (Number.class.isAssignableFrom(toType) || toType.isPrimitive()) {
+ result = doConvertToNumber(context, value, toType);
+ } else if (toType == Class.class) {
+ result = doConvertToClass(value);
+ }
+
+ if (result == null) {
+ if (value instanceof Object[]) {
+ Object[] array = (Object[]) value;
+
+ if (array.length >= 1) {
+ value = array[0];
+ } else {
+ value = null;
+ }
+
+ // let's try to convert the first element only
+ result = convertValue(context, o, member, propertyName, value, toType);
+ } else if (!"".equals(value)) { // we've already tried the types we know
+ result = super.convertValue(context, value, toType);
+ }
+
+ if (result == null && value != null && !"".equals(value)) {
+ throw new XWorkException("Cannot create type " + toType + " from value " + value);
+ }
+ }
+
+ return result;
+ }
+
+ private Object doConvertToCalendar(Map<String, Object> context, Object value) {
+ Object result = null;
+ Date dateResult = (Date) doConvertToDate(context, value, Date.class);
+ if (dateResult != null) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(dateResult);
+ result = calendar;
+ }
+ return result;
+ }
+
+ private Object doConvertToCharacter(Object value) {
+ if (value instanceof String) {
+ String cStr = (String) value;
+ return (cStr.length() > 0) ? cStr.charAt(0) : null;
+ }
+ return null;
+ }
+
+ private Object doConvertToBoolean(Object value) {
+ if (value instanceof String) {
+ String bStr = (String) value;
+ return Boolean.valueOf(bStr);
+ }
+ return null;
+ }
+
+ private Class doConvertToClass(Object value) {
+ Class clazz = null;
+ if (value != null && value instanceof String && ((String) value).length() > 0) {
+ try {
+ clazz = Class.forName((String) value);
+ } catch (ClassNotFoundException e) {
+ throw new XWorkException(e.getLocalizedMessage(), e);
+ }
+ }
+ return clazz;
+ }
+
+ private Object doConvertToCollection(Map<String, Object> context, Object o, Member member, String prop, Object value, Class toType) {
+ TypeConverter converter = container.getInstance(CollectionConverter.class);
+ if (converter == null) {
+ throw new XWorkException("TypeConverter with name [#0] must be registered first!", XWorkConstants.COLLECTION_CONVERTER);
+ }
+ return converter.convertValue(context, o, member, prop, value, toType);
+ }
+
+ private Object doConvertToArray(Map<String, Object> context, Object o, Member member, String prop, Object value, Class toType) {
+ TypeConverter converter = container.getInstance(ArrayConverter.class);
+ if (converter == null) {
+ throw new XWorkException("TypeConverter with name [#0] must be registered first!", XWorkConstants.ARRAY_CONVERTER);
+ }
+ return converter.convertValue(context, o, member, prop, value, toType);
+ }
+
+ private Object doConvertToDate(Map<String, Object> context, Object value, Class toType) {
+ TypeConverter converter = container.getInstance(DateConverter.class);
+ if (converter == null) {
+ throw new XWorkException("TypeConverter with name [#0] must be registered first!", XWorkConstants.DATE_CONVERTER);
+ }
+ return converter.convertValue(context, null, null, null, value, toType);
+ }
+
+ private Object doConvertToNumber(Map<String, Object> context, Object value, Class toType) {
+ TypeConverter converter = container.getInstance(NumberConverter.class);
+ if (converter == null) {
+ throw new XWorkException("TypeConverter with name [#0] must be registered first!", XWorkConstants.NUMBER_CONVERTER);
+ }
+ return converter.convertValue(context, null, null, null, value, toType);
+ }
+
+ private Object doConvertToString(Map<String, Object> context, Object value) {
+ TypeConverter converter = container.getInstance(StringConverter.class);
+ if (converter == null) {
+ throw new XWorkException("TypeConverter with name [#0] must be registered first!", XWorkConstants.STRING_CONVERTER);
+ }
+ return converter.convertValue(context, null, null, null, value, null);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java
new file mode 100644
index 0000000..c0086ee
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java
@@ -0,0 +1,595 @@
+/*
+ * 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 com.opensymphony.xwork2.conversion.impl;
+
+import com.opensymphony.xwork2.*;
+import com.opensymphony.xwork2.conversion.*;
+import com.opensymphony.xwork2.conversion.annotations.Conversion;
+import com.opensymphony.xwork2.conversion.annotations.TypeConversion;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.*;
+import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * 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$ $Id$
+ * @see XWorkBasicConverter
+ */
+public class XWorkConverter extends DefaultTypeConverter {
+
+ private static final Logger LOG = LogManager.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);
+
+ private TypeConverter defaultTypeConverter;
+ private FileManager fileManager;
+ private boolean reloadingConfigs;
+
+ private ConversionFileProcessor fileProcessor;
+ private ConversionAnnotationProcessor annotationProcessor;
+
+ private TypeConverterHolder converterHolder;
+
+ protected XWorkConverter() {
+ }
+
+ @Inject
+ public void setDefaultTypeConverter(XWorkBasicConverter converter) {
+ this.defaultTypeConverter = converter;
+ }
+
+ @Inject
+ public void setFileManagerFactory(FileManagerFactory fileManagerFactory) {
+ this.fileManager = fileManagerFactory.getFileManager();
+ }
+
+ @Inject(value = XWorkConstants.RELOAD_XML_CONFIGURATION, required = false)
+ public void setReloadingConfigs(String reloadingConfigs) {
+ this.reloadingConfigs = Boolean.parseBoolean(reloadingConfigs);
+ }
+
+ @Inject
+ public void setConversionPropertiesProcessor(ConversionPropertiesProcessor propertiesProcessor) {
+ // note: this file is deprecated
+ propertiesProcessor.process("xwork-default-conversion.properties");
+ propertiesProcessor.process("xwork-conversion.properties");
+ }
+
+ @Inject
+ public void setConversionFileProcessor(ConversionFileProcessor fileProcessor) {
+ this.fileProcessor = fileProcessor;
+ }
+
+ @Inject
+ public void setConversionAnnotationProcessor(ConversionAnnotationProcessor annotationProcessor) {
+ this.annotationProcessor = annotationProcessor;
+ }
+
+ @Inject
+ public void setTypeConverterHolder(TypeConverterHolder converterHolder) {
+ this.converterHolder = converterHolder;
+ }
+
+ 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 = removeAllIndexesInPropertyName(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 removeAllIndexesInPropertyName(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<>();
+ while (matcher.find()) {
+ Integer index = new Integer(matcher.group().replaceAll(MESSAGE_INDEX_BRACKET_PATTERN, "")) + 1;
+ indexes.add(Integer.toString(index));
+ }
+ return indexes;
+ }
+
+ public 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);
+ 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) {
+ LOG.debug("Unable to convert value using type converter [{}]", tc.getClass().getName(), e);
+ handleConversionException(context, property, value, target);
+
+ return TypeConverter.NO_CONVERSION_POSSIBLE;
+ }
+ }
+
+ if (defaultTypeConverter != null) {
+ try {
+ LOG.debug("Falling back to default type converter [{}]", defaultTypeConverter);
+ return defaultTypeConverter.convertValue(context, target, member, property, value, toClass);
+ } catch (Exception e) {
+ LOG.debug("Unable to convert value using type converter [{}]", defaultTypeConverter.getClass().getName(), e);
+ handleConversionException(context, property, value, target);
+
+ return TypeConverter.NO_CONVERSION_POSSIBLE;
+ }
+ } else {
+ try {
+ LOG.debug("Falling back to Ognl's default type conversion");
+ return super.convertValue(value, toClass);
+ } catch (Exception e) {
+ LOG.debug("Unable to convert value using type converter [{}]", super.getClass().getName(), e);
+ 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, boolean isPrimitive) {
+ if (converterHolder.containsUnknownMapping(className) && !converterHolder.containsDefaultMapping(className)) {
+ return null;
+ }
+
+ TypeConverter result = converterHolder.getDefaultMapping(className);
+
+ //Looks for super classes
+ if (result == null && !isPrimitive) {
+ Class clazz = null;
+
+ try {
+ clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
+ } catch (ClassNotFoundException cnfe) {
+ LOG.debug("Cannot load class {}", className, cnfe);
+ }
+
+ 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) {
+ TypeConverter result = lookup(clazz.getName(), clazz.isPrimitive());
+
+ if (result == null && clazz.isPrimitive()) {
+ /**
+ * if it is primitive use default converter which allows to define different converters per type
+ * @see XWorkBasicConverter
+ */
+ return defaultTypeConverter;
+ }
+
+ return result;
+ }
+
+ protected Object getConverter(Class clazz, String property) {
+ LOG.debug("Retrieving convert for class [{}] and property [{}]", clazz, property);
+
+ synchronized (clazz) {
+ if ((property != null) && !converterHolder.containsNoMapping(clazz)) {
+ try {
+ Map<String, Object> mapping = converterHolder.getMapping(clazz);
+
+ if (mapping == null) {
+ mapping = buildConverterMapping(clazz);
+ } else {
+ mapping = conditionalReload(clazz, mapping);
+ }
+
+ Object converter = mapping.get(property);
+ if (converter == null && LOG.isDebugEnabled()) {
+ LOG.debug("Converter is null for property [{}]. Mapping size [{}]:", property, mapping.size());
+ for (String next : mapping.keySet()) {
+ LOG.debug("{}:{}", next, mapping.get(next));
+ }
+ }
+ return converter;
+ } catch (Throwable t) {
+ LOG.debug("Got exception trying to resolve convert for class [{}] and property [{}]", clazz, property, t);
+ converterHolder.addNoMapping(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<>();
+ context.put(ActionContext.CONVERSION_ERRORS, conversionErrors);
+ }
+
+ conversionErrors.put(realProperty, value);
+ }
+ }
+
+ public synchronized void registerConverter(String className, TypeConverter converter) {
+ converterHolder.addDefaultMapping(className, converter);
+ }
+
+ public synchronized void registerConverterNotFound(String className) {
+ converterHolder.addUnknownMapping(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) {
+ // Process <clazz>-conversion.properties file
+ String converterFilename = buildConverterFilename(clazz);
+ fileProcessor.process(mapping, clazz, converterFilename);
+
+ // Process annotations
+ Annotation[] annotations = clazz.getAnnotations();
+
+ for (Annotation annotation : annotations) {
+ if (annotation instanceof Conversion) {
+ Conversion conversion = (Conversion) annotation;
+ for (TypeConversion tc : conversion.conversions()) {
+ if (mapping.containsKey(tc.key())) {
+ break;
+ }
+ if (LOG.isDebugEnabled()) {
+ if (StringUtils.isEmpty(tc.key())) {
+ LOG.debug("WARNING! key of @TypeConversion [{}] applied to [{}] is empty!", tc.converter(), clazz.getName());
+ } else {
+ LOG.debug("TypeConversion [{}] with key: [{}]", tc.converter(), tc.key());
+ }
+ }
+ annotationProcessor.process(mapping, tc, tc.key());
+ }
+ }
+ }
+
+ // Process annotated methods
+ for (Method method : clazz.getMethods()) {
+ annotations = method.getAnnotations();
+ for (Annotation annotation : annotations) {
+ if (annotation instanceof TypeConversion) {
+ TypeConversion tc = (TypeConversion) annotation;
+ if (mapping.containsKey(tc.key())) {
+ break;
+ }
+ String key = tc.key();
+ // Default to the property name
+ if (StringUtils.isEmpty(key)) {
+ key = AnnotationUtils.resolvePropertyName(method);
+ LOG.debug("Retrieved key [{}] from method name [{}]", key, method.getName());
+ }
+ annotationProcessor.process(mapping, tc, key);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * 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<>();
+
+ // 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) {
+ converterHolder.addMapping(clazz, mapping);
+ } else {
+ converterHolder.addNoMapping(clazz);
+ }
+
+ return mapping;
+ }
+
+ private Map<String, Object> conditionalReload(Class clazz, Map<String, Object> oldValues) throws Exception {
+ Map<String, Object> mapping = oldValues;
+
+ if (reloadingConfigs) {
+ URL fileUrl = ClassLoaderUtil.getResource(buildConverterFilename(clazz), clazz);
+ if (fileManager.fileNeedsReloading(fileUrl)) {
+ mapping = buildConverterMapping(clazz);
+ }
+ }
+
+ return mapping;
+ }
+
+ /**
+ * 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 = converterHolder.getDefaultMapping(clazz.getName());
+
+ if (result == null) {
+ // Looks for direct interfaces (depth = 1 )
+ Class[] interfaces = clazz.getInterfaces();
+
+ for (Class anInterface : interfaces) {
+ if (converterHolder.containsDefaultMapping(anInterface.getName())) {
+ result = converterHolder.getDefaultMapping(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;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/ConversionDescription.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/ConversionDescription.java b/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/ConversionDescription.java
new file mode 100644
index 0000000..340dc84
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/ConversionDescription.java
@@ -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 com.opensymphony.xwork2.conversion.metadata;
+
+import com.opensymphony.xwork2.conversion.annotations.ConversionRule;
+import com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * <code>ConversionDescription</code>
+ *
+ * @author Rainer Hermanns
+ * @version $Id$
+ */
+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 = LogManager.getLogger(this.getClass());
+ }
+
+ /**
+ * Creates an ConversionDescription with the specified property name.
+ *
+ * @param property
+ */
+ public ConversionDescription(String property) {
+ this.property = property;
+ log = LogManager.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;
+ }
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/package.html
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/package.html b/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/package.html
new file mode 100644
index 0000000..c50a611
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/package.html
@@ -0,0 +1 @@
+<body>Type conversion meta data classes.</body>
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/ActionFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/ActionFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/ActionFactory.java
new file mode 100644
index 0000000..d33cb1d
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/ActionFactory.java
@@ -0,0 +1,18 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+
+import java.util.Map;
+
+/**
+ * Used by {@link com.opensymphony.xwork2.ObjectFactory} to build actions
+ */
+public interface ActionFactory {
+
+ /**
+ * Builds action instance
+ */
+ Object buildAction(String actionName, String namespace, ActionConfig config, Map<String, Object> extraContext) throws Exception;
+
+}
+
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/ConverterFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/ConverterFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/ConverterFactory.java
new file mode 100644
index 0000000..289eefe
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/ConverterFactory.java
@@ -0,0 +1,21 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.conversion.TypeConverter;
+
+import java.util.Map;
+
+/**
+ * Dedicated interface used by {@link com.opensymphony.xwork2.ObjectFactory} to build {@link TypeConverter}
+ */
+public interface ConverterFactory {
+
+ /**
+ * Build converter of given type
+ *
+ * @param converterClass to instantiate
+ * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext}
+ * @return instance of converterClass with inject dependencies
+ */
+ TypeConverter buildConverter(Class<? extends TypeConverter> converterClass, Map<String, Object> extraContext) throws Exception;
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/DefaultActionFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultActionFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultActionFactory.java
new file mode 100644
index 0000000..a17484a
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultActionFactory.java
@@ -0,0 +1,25 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.inject.Inject;
+
+import java.util.Map;
+
+/**
+ * Default implementation
+ */
+public class DefaultActionFactory implements ActionFactory {
+
+ private ObjectFactory objectFactory;
+
+ @Inject
+ public void setObjectFactory(ObjectFactory objectFactory) {
+ this.objectFactory = objectFactory;
+ }
+
+ public Object buildAction(String actionName, String namespace, ActionConfig config, Map<String, Object> extraContext) throws Exception {
+ return objectFactory.buildBean(config.getClassName(), extraContext);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/DefaultConverterFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultConverterFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultConverterFactory.java
new file mode 100644
index 0000000..e35222e
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultConverterFactory.java
@@ -0,0 +1,30 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.conversion.TypeConverter;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.Inject;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+
+import java.util.Map;
+
+/**
+ * Default implementation
+ */
+public class DefaultConverterFactory implements ConverterFactory {
+
+ private static final Logger LOG = LogManager.getLogger(DefaultConverterFactory.class);
+
+ private Container container;
+
+ @Inject
+ public void setContainer(Container container) {
+ this.container = container;
+ }
+
+ public TypeConverter buildConverter(Class<? extends TypeConverter> converterClass, Map<String, Object> extraContext) throws Exception {
+ LOG.debug("Creating converter of type [{}]", converterClass.getCanonicalName());
+ return container.getInstance(converterClass);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java
new file mode 100644
index 0000000..9f0195f
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java
@@ -0,0 +1,67 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.entities.InterceptorConfig;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.interceptor.Interceptor;
+import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Default implementation
+ */
+public class DefaultInterceptorFactory implements InterceptorFactory {
+
+ private ObjectFactory objectFactory;
+ private ReflectionProvider reflectionProvider;
+
+ @Inject
+ public void setObjectFactory(ObjectFactory objectFactory) {
+ this.objectFactory = objectFactory;
+ }
+
+ @Inject
+ public void setReflectionProvider(ReflectionProvider reflectionProvider) {
+ this.reflectionProvider = reflectionProvider;
+ }
+
+ public Interceptor buildInterceptor(InterceptorConfig interceptorConfig, Map<String, String> interceptorRefParams) throws ConfigurationException {
+ String interceptorClassName = interceptorConfig.getClassName();
+ Map<String, String> thisInterceptorClassParams = interceptorConfig.getParams();
+ Map<String, String> params = (thisInterceptorClassParams == null) ? new HashMap<String, String>() : new HashMap<>(thisInterceptorClassParams);
+ params.putAll(interceptorRefParams);
+
+ String message;
+ Throwable cause;
+
+ try {
+ // interceptor instances are long-lived and used across user sessions, so don't try to pass in any extra context
+ Interceptor interceptor = (Interceptor) objectFactory.buildBean(interceptorClassName, null);
+ reflectionProvider.setProperties(params, interceptor);
+ interceptor.init();
+
+ return interceptor;
+ } catch (InstantiationException e) {
+ cause = e;
+ message = "Unable to instantiate an instance of Interceptor class [" + interceptorClassName + "].";
+ } catch (IllegalAccessException e) {
+ cause = e;
+ message = "IllegalAccessException while attempting to instantiate an instance of Interceptor class [" + interceptorClassName + "].";
+ } catch (ClassCastException e) {
+ cause = e;
+ message = "Class [" + interceptorClassName + "] does not implement com.opensymphony.xwork2.interceptor.Interceptor";
+ } catch (Exception e) {
+ cause = e;
+ message = "Caught Exception while registering Interceptor class " + interceptorClassName;
+ } catch (NoClassDefFoundError e) {
+ cause = e;
+ message = "Could not load class " + interceptorClassName + ". Perhaps it exists but certain dependencies are not available?";
+ }
+
+ throw new ConfigurationException(message, cause, interceptorConfig);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java
new file mode 100644
index 0000000..a5a6c79
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java
@@ -0,0 +1,54 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.Result;
+import com.opensymphony.xwork2.config.entities.ResultConfig;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.reflection.ReflectionException;
+import com.opensymphony.xwork2.util.reflection.ReflectionExceptionHandler;
+import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
+
+import java.util.Map;
+
+/**
+ * Default implementation
+ */
+public class DefaultResultFactory implements ResultFactory {
+
+ private ObjectFactory objectFactory;
+ private ReflectionProvider reflectionProvider;
+
+ @Inject
+ public void setObjectFactory(ObjectFactory objectFactory) {
+ this.objectFactory = objectFactory;
+ }
+
+ @Inject
+ public void setReflectionProvider(ReflectionProvider reflectionProvider) {
+ this.reflectionProvider = reflectionProvider;
+ }
+
+ public Result buildResult(ResultConfig resultConfig, Map<String, Object> extraContext) throws Exception {
+ String resultClassName = resultConfig.getClassName();
+ Result result = null;
+
+ if (resultClassName != null) {
+ result = (Result) objectFactory.buildBean(resultClassName, extraContext);
+ Map<String, String> params = resultConfig.getParams();
+ if (params != null) {
+ for (Map.Entry<String, String> paramEntry : params.entrySet()) {
+ try {
+ reflectionProvider.setProperty(paramEntry.getKey(), paramEntry.getValue(), result, extraContext, true);
+ } catch (ReflectionException ex) {
+ if (result instanceof ReflectionExceptionHandler) {
+ ((ReflectionExceptionHandler) result).handle(ex);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/DefaultUnknownHandlerFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultUnknownHandlerFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultUnknownHandlerFactory.java
new file mode 100644
index 0000000..58fb339
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultUnknownHandlerFactory.java
@@ -0,0 +1,25 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.UnknownHandler;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.Inject;
+
+import java.util.Map;
+
+/**
+ * Default implementation
+ */
+public class DefaultUnknownHandlerFactory implements UnknownHandlerFactory {
+
+ private Container container;
+
+ @Inject
+ public void setContainer(Container container) {
+ this.container = container;
+ }
+
+ public UnknownHandler buildUnknownHandler(String unknownHandlerName, Map<String, Object> extraContext) throws Exception {
+ return container.getInstance(UnknownHandler.class, unknownHandlerName);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/DefaultValidatorFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultValidatorFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultValidatorFactory.java
new file mode 100644
index 0000000..8ce8b77
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultValidatorFactory.java
@@ -0,0 +1,34 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
+import com.opensymphony.xwork2.validator.Validator;
+
+import java.util.Map;
+
+/**
+ * Default implementation
+ */
+public class DefaultValidatorFactory implements ValidatorFactory {
+
+ private ObjectFactory objectFactory;
+ private ReflectionProvider reflectionProvider;
+
+ @Inject
+ public void setObjectFactory(ObjectFactory objectFactory) {
+ this.objectFactory = objectFactory;
+ }
+
+ @Inject
+ public void setReflectionProvider(ReflectionProvider reflectionProvider) {
+ this.reflectionProvider = reflectionProvider;
+ }
+
+ public Validator buildValidator(String className, Map<String, Object> params, Map<String, Object> extraContext) throws Exception {
+ Validator validator = (Validator) objectFactory.buildBean(className, extraContext);
+ reflectionProvider.setProperties(params, validator, extraContext);
+
+ return validator;
+ }
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/InterceptorFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/InterceptorFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/InterceptorFactory.java
new file mode 100644
index 0000000..742e7ab
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/InterceptorFactory.java
@@ -0,0 +1,28 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.entities.InterceptorConfig;
+import com.opensymphony.xwork2.interceptor.Interceptor;
+
+import java.util.Map;
+
+/**
+ * Dedicated interface used by {@link com.opensymphony.xwork2.ObjectFactory} to build {@link com.opensymphony.xwork2.interceptor.Interceptor}
+ */
+public interface InterceptorFactory {
+
+ /**
+ * Builds an Interceptor from the InterceptorConfig and the Map of
+ * parameters from the interceptor reference. Implementations of this method
+ * should ensure that the Interceptor is parameterized with both the
+ * parameters from the Interceptor config and the interceptor ref Map (the
+ * interceptor ref params take precedence), and that the Interceptor.init()
+ * method is called on the Interceptor instance before it is returned.
+ *
+ * @param interceptorConfig the InterceptorConfig from the configuration
+ * @param interceptorRefParams a Map of params provided in the Interceptor reference in the
+ * Action mapping or InterceptorStack definition
+ */
+ Interceptor buildInterceptor(InterceptorConfig interceptorConfig, Map<String, String> interceptorRefParams) throws ConfigurationException;
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/ResultFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/ResultFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/ResultFactory.java
new file mode 100644
index 0000000..c7191cb
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/ResultFactory.java
@@ -0,0 +1,15 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.Result;
+import com.opensymphony.xwork2.config.entities.ResultConfig;
+
+import java.util.Map;
+
+/**
+ * Used by {@link com.opensymphony.xwork2.ObjectFactory} to build {@link com.opensymphony.xwork2.Result}
+ */
+public interface ResultFactory {
+
+ Result buildResult(ResultConfig resultConfig, Map<String, Object> extraContext) throws Exception;
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/UnknownHandlerFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/UnknownHandlerFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/UnknownHandlerFactory.java
new file mode 100644
index 0000000..244f3ab
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/UnknownHandlerFactory.java
@@ -0,0 +1,21 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.UnknownHandler;
+
+import java.util.Map;
+
+/**
+ * Dedicated interface used by {@link com.opensymphony.xwork2.ObjectFactory} to build {@link com.opensymphony.xwork2.UnknownHandler}
+ */
+public interface UnknownHandlerFactory {
+
+ /**
+ * Builds unknown handler of given name
+ *
+ * @param unknownHandlerName name of unknown handler defined in struts.xml
+ * @param extraContext extra params
+ * @return instance of {@link com.opensymphony.xwork2.UnknownHandler} with injected dependencies
+ */
+ UnknownHandler buildUnknownHandler(String unknownHandlerName, Map<String, Object> extraContext) throws Exception;
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/ValidatorFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/ValidatorFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/ValidatorFactory.java
new file mode 100644
index 0000000..bbaa36e
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/ValidatorFactory.java
@@ -0,0 +1,21 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.validator.Validator;
+
+import java.util.Map;
+
+/**
+ * Dedicated interface used by {@link com.opensymphony.xwork2.ObjectFactory} to build {@link Validator}
+ */
+public interface ValidatorFactory {
+
+ /**
+ * Build a Validator of the given type and set the parameters on it
+ *
+ * @param className the type of Validator to build
+ * @param params property name -> value Map to set onto the Validator instance
+ * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext}
+ */
+ Validator buildValidator(String className, Map<String, Object> params, Map<String, Object> extraContext) throws Exception;
+
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/ConstructionContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/ConstructionContext.java b/core/src/main/java/com/opensymphony/xwork2/inject/ConstructionContext.java
new file mode 100644
index 0000000..d75d464
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/inject/ConstructionContext.java
@@ -0,0 +1,119 @@
+/**
+ * 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 com.opensymphony.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<>();
+ 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 | IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+
+ void setDelegate(T delegate) {
+ this.delegate = delegate;
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/Container.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/Container.java b/core/src/main/java/com/opensymphony/xwork2/inject/Container.java
new file mode 100644
index 0000000..bca6032
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/inject/Container.java
@@ -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 com.opensymphony.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 com.opensymphony.xwork2.inject.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 or empty set if no instances are registered for that type
+ */
+ 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();
+}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/ContainerBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/ContainerBuilder.java b/core/src/main/java/com/opensymphony/xwork2/inject/ContainerBuilder.java
new file mode 100644
index 0000000..54f5be6
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/inject/ContainerBuilder.java
@@ -0,0 +1,510 @@
+/**
+ * Copyright (C) 2006 Google Inc.
+ * <p/>
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 com.opensymphony.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<>();
+ final List<InternalFactory<?>> singletonFactories = new ArrayList<>();
+ final List<Class<?>> staticInjections = new ArrayList<>();
+ 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/>
+ * <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<>(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);
+ }
+}