struts-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From lukaszlen...@apache.org
Subject [45/57] [partial] struts git commit: Merges xwork packages into struts
Date Wed, 17 Jun 2015 21:09:45 GMT
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java
new file mode 100644
index 0000000..0cf7059
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlConfigurationProvider.java
@@ -0,0 +1,1071 @@
+/*
+ * 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.config.providers;
+
+import com.opensymphony.xwork2.*;
+import com.opensymphony.xwork2.config.Configuration;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.ConfigurationProvider;
+import com.opensymphony.xwork2.config.ConfigurationUtil;
+import com.opensymphony.xwork2.config.entities.*;
+import com.opensymphony.xwork2.config.impl.LocatableFactory;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.ContainerBuilder;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.inject.Scope;
+import com.opensymphony.xwork2.util.ClassLoaderUtil;
+import com.opensymphony.xwork2.util.ClassPathFinder;
+import com.opensymphony.xwork2.util.DomHelper;
+import com.opensymphony.xwork2.util.TextParseUtil;
+import com.opensymphony.xwork2.util.location.LocatableProperties;
+import com.opensymphony.xwork2.util.location.Location;
+import com.opensymphony.xwork2.util.location.LocationUtils;
+import org.apache.commons.lang3.BooleanUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.util.*;
+
+
+/**
+ * Looks in the classpath for an XML file, "xwork.xml" by default,
+ * and uses it for the XWork configuration.
+ *
+ * @author tmjee
+ * @author Rainer Hermanns
+ * @author Neo
+ * @version $Revision$
+ */
+public class XmlConfigurationProvider implements ConfigurationProvider {
+
+    private static final Logger LOG = LogManager.getLogger(XmlConfigurationProvider.class);
+
+    private List<Document> documents;
+    private Set<String> includedFileNames;
+    private String configFileName;
+    private ObjectFactory objectFactory;
+
+    private Set<String> loadedFileUrls = new HashSet<>();
+    private boolean errorIfMissing;
+    private Map<String, String> dtdMappings;
+    private Configuration configuration;
+    private boolean throwExceptionOnDuplicateBeans = true;
+    private Map<String, Element> declaredPackages = new HashMap<>();
+
+    private FileManager fileManager;
+
+    public XmlConfigurationProvider() {
+        this("xwork.xml", true);
+    }
+
+    public XmlConfigurationProvider(String filename) {
+        this(filename, true);
+    }
+
+    public XmlConfigurationProvider(String filename, boolean errorIfMissing) {
+        this.configFileName = filename;
+        this.errorIfMissing = errorIfMissing;
+
+        Map<String, String> mappings = new HashMap<>();
+        mappings.put("-//Apache Struts//XWork 2.3//EN", "xwork-2.3.dtd");
+        mappings.put("-//Apache Struts//XWork 2.1.3//EN", "xwork-2.1.3.dtd");
+        mappings.put("-//Apache Struts//XWork 2.1//EN", "xwork-2.1.dtd");
+        mappings.put("-//Apache Struts//XWork 2.0//EN", "xwork-2.0.dtd");
+        mappings.put("-//Apache Struts//XWork 1.1.1//EN", "xwork-1.1.1.dtd");
+        mappings.put("-//Apache Struts//XWork 1.1//EN", "xwork-1.1.dtd");
+        mappings.put("-//Apache Struts//XWork 1.0//EN", "xwork-1.0.dtd");
+        setDtdMappings(mappings);
+    }
+
+    public void setThrowExceptionOnDuplicateBeans(boolean val) {
+        this.throwExceptionOnDuplicateBeans = val;
+    }
+
+    public void setDtdMappings(Map<String, String> mappings) {
+        this.dtdMappings = Collections.unmodifiableMap(mappings);
+    }
+
+    @Inject
+    public void setObjectFactory(ObjectFactory objectFactory) {
+        this.objectFactory = objectFactory;
+    }
+
+    @Inject
+    public void setFileManagerFactory(FileManagerFactory fileManagerFactory) {
+        this.fileManager = fileManagerFactory.getFileManager();
+    }
+
+    /**
+     * Returns an unmodifiable map of DTD mappings
+     */
+    public Map<String, String> getDtdMappings() {
+        return dtdMappings;
+    }
+
+    public void init(Configuration configuration) {
+        this.configuration = configuration;
+        this.includedFileNames = configuration.getLoadedFileNames();
+        loadDocuments(configFileName);
+    }
+
+    public void destroy() {
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof XmlConfigurationProvider)) {
+            return false;
+        }
+
+        final XmlConfigurationProvider xmlConfigurationProvider = (XmlConfigurationProvider) o;
+
+        if ((configFileName != null) ? (!configFileName.equals(xmlConfigurationProvider.configFileName)) : (xmlConfigurationProvider.configFileName != null)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return ((configFileName != null) ? configFileName.hashCode() : 0);
+    }
+
+    private void loadDocuments(String configFileName) {
+        try {
+            loadedFileUrls.clear();
+            documents = loadConfigurationFiles(configFileName, null);
+        } catch (ConfigurationException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new ConfigurationException("Error loading configuration file " + configFileName, e);
+        }
+    }
+
+    public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException {
+        LOG.info("Parsing configuration file [{}]", configFileName);
+        Map<String, Node> loadedBeans = new HashMap<>();
+        for (Document doc : documents) {
+            Element rootElement = doc.getDocumentElement();
+            NodeList children = rootElement.getChildNodes();
+            int childSize = children.getLength();
+
+            for (int i = 0; i < childSize; i++) {
+                Node childNode = children.item(i);
+
+                if (childNode instanceof Element) {
+                    Element child = (Element) childNode;
+
+                    final String nodeName = child.getNodeName();
+
+                    if ("bean".equals(nodeName)) {
+                        String type = child.getAttribute("type");
+                        String name = child.getAttribute("name");
+                        String impl = child.getAttribute("class");
+                        String onlyStatic = child.getAttribute("static");
+                        String scopeStr = child.getAttribute("scope");
+                        boolean optional = "true".equals(child.getAttribute("optional"));
+                        Scope scope = Scope.SINGLETON;
+                        if ("default".equals(scopeStr)) {
+                            scope = Scope.DEFAULT;
+                        } else if ("request".equals(scopeStr)) {
+                            scope = Scope.REQUEST;
+                        } else if ("session".equals(scopeStr)) {
+                            scope = Scope.SESSION;
+                        } else if ("singleton".equals(scopeStr)) {
+                            scope = Scope.SINGLETON;
+                        } else if ("thread".equals(scopeStr)) {
+                            scope = Scope.THREAD;
+                        }
+
+                        if (StringUtils.isEmpty(name)) {
+                            name = Container.DEFAULT_NAME;
+                        }
+
+                        try {
+                            Class classImpl = ClassLoaderUtil.loadClass(impl, getClass());
+                            Class classType = classImpl;
+                            if (StringUtils.isNotEmpty(type)) {
+                                classType = ClassLoaderUtil.loadClass(type, getClass());
+                            }
+                            if ("true".equals(onlyStatic)) {
+                                // Force loading of class to detect no class def found exceptions
+                                classImpl.getDeclaredClasses();
+                                containerBuilder.injectStatics(classImpl);
+                            } else {
+                                if (containerBuilder.contains(classType, name)) {
+                                    Location loc = LocationUtils.getLocation(loadedBeans.get(classType.getName() + name));
+                                    if (throwExceptionOnDuplicateBeans) {
+                                        throw new ConfigurationException("Bean type " + classType + " with the name " +
+                                                name + " has already been loaded by " + loc, child);
+                                    }
+                                }
+
+                                // Force loading of class to detect no class def found exceptions
+                                classImpl.getDeclaredConstructors();
+
+                                LOG.debug("Loaded type: {} name: {} impl: {}", type, name, impl);
+                                containerBuilder.factory(classType, name, new LocatableFactory(name, classType, classImpl, scope, childNode), scope);
+                            }
+                            loadedBeans.put(classType.getName() + name, child);
+                        } catch (Throwable ex) {
+                            if (!optional) {
+                                throw new ConfigurationException("Unable to load bean: type:" + type + " class:" + impl, ex, childNode);
+                            } else {
+                                LOG.debug("Unable to load optional class: {}", impl);
+                            }
+                        }
+                    } else if ("constant".equals(nodeName)) {
+                        String name = child.getAttribute("name");
+                        String value = child.getAttribute("value");
+                        props.setProperty(name, value, childNode);
+                    } else if (nodeName.equals("unknown-handler-stack")) {
+                        List<UnknownHandlerConfig> unknownHandlerStack = new ArrayList<UnknownHandlerConfig>();
+                        NodeList unknownHandlers = child.getElementsByTagName("unknown-handler-ref");
+                        int unknownHandlersSize = unknownHandlers.getLength();
+
+                        for (int k = 0; k < unknownHandlersSize; k++) {
+                            Element unknownHandler = (Element) unknownHandlers.item(k);
+                            Location location = LocationUtils.getLocation(unknownHandler);
+                            unknownHandlerStack.add(new UnknownHandlerConfig(unknownHandler.getAttribute("name"), location));
+                        }
+
+                        if (!unknownHandlerStack.isEmpty())
+                            configuration.setUnknownHandlerStack(unknownHandlerStack);
+                    }
+                }
+            }
+        }
+    }
+
+    public void loadPackages() throws ConfigurationException {
+        List<Element> reloads = new ArrayList<Element>();
+        verifyPackageStructure();
+
+        for (Document doc : documents) {
+            Element rootElement = doc.getDocumentElement();
+            NodeList children = rootElement.getChildNodes();
+            int childSize = children.getLength();
+
+            for (int i = 0; i < childSize; i++) {
+                Node childNode = children.item(i);
+
+                if (childNode instanceof Element) {
+                    Element child = (Element) childNode;
+
+                    final String nodeName = child.getNodeName();
+
+                    if ("package".equals(nodeName)) {
+                        PackageConfig cfg = addPackage(child);
+                        if (cfg.isNeedsRefresh()) {
+                            reloads.add(child);
+                        }
+                    }
+                }
+            }
+            loadExtraConfiguration(doc);
+        }
+
+        if (reloads.size() > 0) {
+            reloadRequiredPackages(reloads);
+        }
+
+        for (Document doc : documents) {
+            loadExtraConfiguration(doc);
+        }
+
+        documents.clear();
+        declaredPackages.clear();
+        configuration = null;
+    }
+
+    private void verifyPackageStructure() {
+        DirectedGraph<String> graph = new DirectedGraph<>();
+
+        for (Document doc : documents) {
+            Element rootElement = doc.getDocumentElement();
+            NodeList children = rootElement.getChildNodes();
+            int childSize = children.getLength();
+            for (int i = 0; i < childSize; i++) {
+                Node childNode = children.item(i);
+                if (childNode instanceof Element) {
+                    Element child = (Element) childNode;
+
+                    final String nodeName = child.getNodeName();
+
+                    if ("package".equals(nodeName)) {
+                        String packageName = child.getAttribute("name");
+                        declaredPackages.put(packageName, child);
+                        graph.addNode(packageName);
+
+                        String extendsAttribute = child.getAttribute("extends");
+                        List<String> parents = ConfigurationUtil.buildParentListFromString(extendsAttribute);
+                        for (String parent : parents) {
+                            graph.addNode(parent);
+                            graph.addEdge(packageName, parent);
+                        }
+                    }
+                }
+            }
+        }
+
+        CycleDetector<String> detector = new CycleDetector<>(graph);
+        if (detector.containsCycle()) {
+            StringBuilder builder = new StringBuilder("The following packages participate in cycles:");
+            for (String packageName : detector.getVerticesInCycles()) {
+                builder.append(" ");
+                builder.append(packageName);
+            }
+            throw new ConfigurationException(builder.toString());
+        }
+    }
+
+    private void reloadRequiredPackages(List<Element> reloads) {
+        if (reloads.size() > 0) {
+            List<Element> result = new ArrayList<>();
+            for (Element pkg : reloads) {
+                PackageConfig cfg = addPackage(pkg);
+                if (cfg.isNeedsRefresh()) {
+                    result.add(pkg);
+                }
+            }
+            if ((result.size() > 0) && (result.size() != reloads.size())) {
+                reloadRequiredPackages(result);
+                return;
+            }
+
+            // Print out error messages for all misconfigured inheritance packages
+            if (result.size() > 0) {
+                for (Element rp : result) {
+                    String parent = rp.getAttribute("extends");
+                    if (parent != null) {
+                        List<PackageConfig> parents = ConfigurationUtil.buildParentsFromString(configuration, parent);
+                        if (parents != null && parents.size() <= 0) {
+                            LOG.error("Unable to find parent packages {}", parent);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Tells whether the ConfigurationProvider should reload its configuration. This method should only be called
+     * if ConfigurationManager.isReloadingConfigs() is true.
+     *
+     * @return true if the file has been changed since the last time we read it
+     */
+    public boolean needsReload() {
+
+        for (String url : loadedFileUrls) {
+            if (fileManager.fileNeedsReloading(url)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected void addAction(Element actionElement, PackageConfig.Builder packageContext) throws ConfigurationException {
+        String name = actionElement.getAttribute("name");
+        String className = actionElement.getAttribute("class");
+        //methodName should be null if it's not set
+        String methodName = StringUtils.trimToNull(actionElement.getAttribute("method"));
+        Location location = DomHelper.getLocationObject(actionElement);
+
+        if (location == null) {
+            LOG.warn("Location null for {}", className);
+        }
+
+        // if there isn't a class name specified for an <action/> then try to
+        // use the default-class-ref from the <package/>
+        if (StringUtils.isEmpty(className)) {
+            // if there is a package default-class-ref use that, otherwise use action support
+           /* if (StringUtils.isNotEmpty(packageContext.getDefaultClassRef())) {
+                className = packageContext.getDefaultClassRef();
+            } else {
+                className = ActionSupport.class.getName();
+            }*/
+
+        } else {
+            if (!verifyAction(className, name, location)) {
+                LOG.error("Unable to verify action [{}] with class [{}], from [{}]", name, className, location);
+                return;
+            }
+        }
+
+        Map<String, ResultConfig> results;
+        try {
+            results = buildResults(actionElement, packageContext);
+        } catch (ConfigurationException e) {
+            throw new ConfigurationException("Error building results for action " + name + " in namespace " + packageContext.getNamespace(), e, actionElement);
+        }
+
+        List<InterceptorMapping> interceptorList = buildInterceptorList(actionElement, packageContext);
+
+        List<ExceptionMappingConfig> exceptionMappings = buildExceptionMappings(actionElement, packageContext);
+
+        Set<String> allowedMethods = buildAllowedMethods(actionElement, packageContext);
+
+        ActionConfig actionConfig = new ActionConfig.Builder(packageContext.getName(), name, className)
+                .methodName(methodName)
+                .addResultConfigs(results)
+                .addInterceptors(interceptorList)
+                .addExceptionMappings(exceptionMappings)
+                .addParams(XmlHelper.getParams(actionElement))
+                .addAllowedMethod(allowedMethods)
+                .location(location)
+                .build();
+        packageContext.addActionConfig(name, actionConfig);
+
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("Loaded {}{} in '{}' package: {}",
+                    StringUtils.isNotEmpty(packageContext.getNamespace()) ? (packageContext.getNamespace() + "/") : "",
+                    name, packageContext.getName(), actionConfig);
+        }
+    }
+
+    protected boolean verifyAction(String className, String name, Location loc) {
+        if (className.contains("{")) {
+            LOG.debug("Action class [{}] contains a wildcard replacement value, so it can't be verified", className);
+            return true;
+        }
+        try {
+            if (objectFactory.isNoArgConstructorRequired()) {
+                Class clazz = objectFactory.getClassInstance(className);
+                if (!Modifier.isPublic(clazz.getModifiers())) {
+                    throw new ConfigurationException("Action class [" + className + "] is not public", loc);
+                }
+                clazz.getConstructor(new Class[]{});
+            }
+        } catch (ClassNotFoundException e) {
+            LOG.debug("Class not found for action [{}]", className, e);
+            throw new ConfigurationException("Action class [" + className + "] not found", loc);
+        } catch (NoSuchMethodException e) {
+            LOG.debug("No constructor found for action [{}]", className, e);
+            throw new ConfigurationException("Action class [" + className + "] does not have a public no-arg constructor", e, loc);
+        } catch (RuntimeException ex) {
+            // Probably not a big deal, like request or session-scoped Spring beans that need a real request
+            LOG.info("Unable to verify action class [{}] exists at initialization", className);
+            LOG.debug("Action verification cause", ex);
+        } catch (Exception ex) {
+            // Default to failing fast
+            LOG.debug("Unable to verify action class [{}]", className, ex);
+            throw new ConfigurationException(ex, loc);
+        }
+        return true;
+    }
+
+    /**
+     * Create a PackageConfig from an XML element representing it.
+     */
+    protected PackageConfig addPackage(Element packageElement) throws ConfigurationException {
+        String packageName = packageElement.getAttribute("name");
+        PackageConfig packageConfig = configuration.getPackageConfig(packageName);
+        if (packageConfig != null) {
+            LOG.debug("Package [{}] already loaded, skipping re-loading it and using existing PackageConfig [{}]", packageName, packageConfig);
+            return packageConfig;
+        }
+
+        PackageConfig.Builder newPackage = buildPackageContext(packageElement);
+
+        if (newPackage.isNeedsRefresh()) {
+            return newPackage.build();
+        }
+
+        LOG.debug("Loaded {}", newPackage);
+
+        // add result types (and default result) to this package
+        addResultTypes(newPackage, packageElement);
+
+        // load the interceptors and interceptor stacks for this package
+        loadInterceptors(newPackage, packageElement);
+
+        // load the default interceptor reference for this package
+        loadDefaultInterceptorRef(newPackage, packageElement);
+
+        // load the default class ref for this package
+        loadDefaultClassRef(newPackage, packageElement);
+
+        // load the global result list for this package
+        loadGlobalResults(newPackage, packageElement);
+
+        // load the global exception handler list for this package
+        loadGobalExceptionMappings(newPackage, packageElement);
+
+        // get actions
+        NodeList actionList = packageElement.getElementsByTagName("action");
+
+        for (int i = 0; i < actionList.getLength(); i++) {
+            Element actionElement = (Element) actionList.item(i);
+            addAction(actionElement, newPackage);
+        }
+
+        // load the default action reference for this package
+        loadDefaultActionRef(newPackage, packageElement);
+
+        PackageConfig cfg = newPackage.build();
+        configuration.addPackageConfig(cfg.getName(), cfg);
+        return cfg;
+    }
+
+    protected void addResultTypes(PackageConfig.Builder packageContext, Element element) {
+        NodeList resultTypeList = element.getElementsByTagName("result-type");
+
+        for (int i = 0; i < resultTypeList.getLength(); i++) {
+            Element resultTypeElement = (Element) resultTypeList.item(i);
+            String name = resultTypeElement.getAttribute("name");
+            String className = resultTypeElement.getAttribute("class");
+            String def = resultTypeElement.getAttribute("default");
+
+            Location loc = DomHelper.getLocationObject(resultTypeElement);
+
+            Class clazz = verifyResultType(className, loc);
+            if (clazz != null) {
+                String paramName = null;
+                try {
+                    paramName = (String) clazz.getField("DEFAULT_PARAM").get(null);
+                } catch (Throwable t) {
+                    LOG.debug("The result type [{}] doesn't have a default param [DEFAULT_PARAM] defined!", className, t);
+                }
+                ResultTypeConfig.Builder resultType = new ResultTypeConfig.Builder(name, className).defaultResultParam(paramName)
+                        .location(DomHelper.getLocationObject(resultTypeElement));
+
+                Map<String, String> params = XmlHelper.getParams(resultTypeElement);
+
+                if (!params.isEmpty()) {
+                    resultType.addParams(params);
+                }
+                packageContext.addResultTypeConfig(resultType.build());
+
+                // set the default result type
+                if (BooleanUtils.toBoolean(def)) {
+                    packageContext.defaultResultType(name);
+                }
+            }
+        }
+    }
+
+    protected Class verifyResultType(String className, Location loc) {
+        try {
+            return objectFactory.getClassInstance(className);
+        } catch (ClassNotFoundException | NoClassDefFoundError e) {
+            LOG.warn("Result class [{}] doesn't exist ({}) at {}, ignoring", className, e.getClass().getSimpleName(), loc, e);
+        }
+
+        return null;
+    }
+
+    protected List<InterceptorMapping> buildInterceptorList(Element element, PackageConfig.Builder context) throws ConfigurationException {
+        List<InterceptorMapping> interceptorList = new ArrayList<>();
+        NodeList interceptorRefList = element.getElementsByTagName("interceptor-ref");
+
+        for (int i = 0; i < interceptorRefList.getLength(); i++) {
+            Element interceptorRefElement = (Element) interceptorRefList.item(i);
+
+            if (interceptorRefElement.getParentNode().equals(element) || interceptorRefElement.getParentNode().getNodeName().equals(element.getNodeName())) {
+                List<InterceptorMapping> interceptors = lookupInterceptorReference(context, interceptorRefElement);
+                interceptorList.addAll(interceptors);
+            }
+        }
+
+        return interceptorList;
+    }
+
+    /**
+     * This method builds a package context by looking for the parents of this new package.
+     * <p/>
+     * If no parents are found, it will return a root package.
+     */
+    protected PackageConfig.Builder buildPackageContext(Element packageElement) {
+        String parent = packageElement.getAttribute("extends");
+        String abstractVal = packageElement.getAttribute("abstract");
+        boolean isAbstract = Boolean.parseBoolean(abstractVal);
+        String name = StringUtils.defaultString(packageElement.getAttribute("name"));
+        String namespace = StringUtils.defaultString(packageElement.getAttribute("namespace"));
+        String strictDMIVal = StringUtils.defaultString(packageElement.getAttribute("strict-method-invocation"));
+        boolean strictDMI = Boolean.parseBoolean(strictDMIVal);
+
+        if (StringUtils.isNotEmpty(packageElement.getAttribute("externalReferenceResolver"))) {
+            throw new ConfigurationException("The 'externalReferenceResolver' attribute has been removed.  Please use " +
+                    "a custom ObjectFactory or Interceptor.", packageElement);
+        }
+
+        PackageConfig.Builder cfg = new PackageConfig.Builder(name)
+                .namespace(namespace)
+                .isAbstract(isAbstract)
+                .strictMethodInvocation(strictDMI)
+                .location(DomHelper.getLocationObject(packageElement));
+
+        if (StringUtils.isNotEmpty(StringUtils.defaultString(parent))) { // has parents, let's look it up
+            List<PackageConfig> parents = new ArrayList<>();
+            for (String parentPackageName : ConfigurationUtil.buildParentListFromString(parent)) {
+                if (configuration.getPackageConfigNames().contains(parentPackageName)) {
+                    parents.add(configuration.getPackageConfig(parentPackageName));
+                } else if (declaredPackages.containsKey(parentPackageName)) {
+                    if (configuration.getPackageConfig(parentPackageName) == null) {
+                        addPackage(declaredPackages.get(parentPackageName));
+                    }
+                    parents.add(configuration.getPackageConfig(parentPackageName));
+                } else {
+                    throw new ConfigurationException("Parent package is not defined: " + parentPackageName);
+                }
+
+            }
+
+            if (parents.size() <= 0) {
+                cfg.needsRefresh(true);
+            } else {
+                cfg.addParents(parents);
+            }
+        }
+
+        return cfg;
+    }
+
+    /**
+     * Build a map of ResultConfig objects from below a given XML element.
+     */
+    protected Map<String, ResultConfig> buildResults(Element element, PackageConfig.Builder packageContext) {
+        NodeList resultEls = element.getElementsByTagName("result");
+
+        Map<String, ResultConfig> results = new LinkedHashMap<>();
+
+        for (int i = 0; i < resultEls.getLength(); i++) {
+            Element resultElement = (Element) resultEls.item(i);
+
+            if (resultElement.getParentNode().equals(element) || resultElement.getParentNode().getNodeName().equals(element.getNodeName())) {
+                String resultName = resultElement.getAttribute("name");
+                String resultType = resultElement.getAttribute("type");
+
+                // if you don't specify a name on <result/>, it defaults to "success"
+                if (StringUtils.isEmpty(resultName)) {
+                    resultName = Action.SUCCESS;
+                }
+
+                // there is no result type, so let's inherit from the parent package
+                if (StringUtils.isEmpty(resultType)) {
+                    resultType = packageContext.getFullDefaultResultType();
+
+                    // now check if there is a result type now
+                    if (StringUtils.isEmpty(resultType)) {
+                        // uh-oh, we have a problem
+                        throw new ConfigurationException("No result type specified for result named '"
+                                + resultName + "', perhaps the parent package does not specify the result type?", resultElement);
+                    }
+                }
+
+
+                ResultTypeConfig config = packageContext.getResultType(resultType);
+
+                if (config == null) {
+                    throw new ConfigurationException("There is no result type defined for type '" + resultType
+                            + "' mapped with name '" + resultName + "'."
+                            + "  Did you mean '" + guessResultType(resultType) + "'?", resultElement);
+                }
+
+                String resultClass = config.getClassName();
+
+                // invalid result type specified in result definition
+                if (resultClass == null) {
+                    throw new ConfigurationException("Result type '" + resultType + "' is invalid");
+                }
+
+                Map<String, String> resultParams = XmlHelper.getParams(resultElement);
+
+                if (resultParams.size() == 0) // maybe we just have a body - therefore a default parameter
+                {
+                    // if <result ...>something</result> then we add a parameter of 'something' as this is the most used result param
+                    if (resultElement.getChildNodes().getLength() >= 1) {
+                        resultParams = new LinkedHashMap<String, String>();
+
+                        String paramName = config.getDefaultResultParam();
+                        if (paramName != null) {
+                            StringBuilder paramValue = new StringBuilder();
+                            for (int j = 0; j < resultElement.getChildNodes().getLength(); j++) {
+                                if (resultElement.getChildNodes().item(j).getNodeType() == Node.TEXT_NODE) {
+                                    String val = resultElement.getChildNodes().item(j).getNodeValue();
+                                    if (val != null) {
+                                        paramValue.append(val);
+                                    }
+                                }
+                            }
+                            String val = paramValue.toString().trim();
+                            if (val.length() > 0) {
+                                resultParams.put(paramName, val);
+                            }
+                        } else {
+                            LOG.warn("No default parameter defined for result [{}] of type [{}] ", config.getName(), config.getClassName());
+                        }
+                    }
+                }
+
+                // create new param map, so that the result param can override the config param
+                Map<String, String> params = new LinkedHashMap<String, String>();
+                Map<String, String> configParams = config.getParams();
+                if (configParams != null) {
+                    params.putAll(configParams);
+                }
+                params.putAll(resultParams);
+
+                ResultConfig resultConfig = new ResultConfig.Builder(resultName, resultClass)
+                        .addParams(params)
+                        .location(DomHelper.getLocationObject(element))
+                        .build();
+                results.put(resultConfig.getName(), resultConfig);
+            }
+        }
+
+        return results;
+    }
+
+    protected String guessResultType(String type) {
+        StringBuilder sb = null;
+        if (type != null) {
+            sb = new StringBuilder();
+            boolean capNext = false;
+            for (int x=0; x<type.length(); x++) {
+                char c = type.charAt(x);
+                if (c == '-') {
+                    capNext = true;
+                    continue;
+                } else if (Character.isLowerCase(c) && capNext) {
+                    c = Character.toUpperCase(c);
+                    capNext = false;
+                }
+                sb.append(c);
+            }
+        }
+        return (sb != null ? sb.toString() : null);
+    }
+
+    /**
+     * Build a map of ResultConfig objects from below a given XML element.
+     */
+    protected List<ExceptionMappingConfig> buildExceptionMappings(Element element, PackageConfig.Builder packageContext) {
+        NodeList exceptionMappingEls = element.getElementsByTagName("exception-mapping");
+
+        List<ExceptionMappingConfig> exceptionMappings = new ArrayList<>();
+
+        for (int i = 0; i < exceptionMappingEls.getLength(); i++) {
+            Element ehElement = (Element) exceptionMappingEls.item(i);
+
+            if (ehElement.getParentNode().equals(element) || ehElement.getParentNode().getNodeName().equals(element.getNodeName())) {
+                String emName = ehElement.getAttribute("name");
+                String exceptionClassName = ehElement.getAttribute("exception");
+                String exceptionResult = ehElement.getAttribute("result");
+
+                Map<String, String> params = XmlHelper.getParams(ehElement);
+
+                if (StringUtils.isEmpty(emName)) {
+                    emName = exceptionResult;
+                }
+
+                ExceptionMappingConfig ehConfig = new ExceptionMappingConfig.Builder(emName, exceptionClassName, exceptionResult)
+                        .addParams(params)
+                        .location(DomHelper.getLocationObject(ehElement))
+                        .build();
+                exceptionMappings.add(ehConfig);
+            }
+        }
+
+        return exceptionMappings;
+    }
+
+    protected Set<String> buildAllowedMethods(Element element, PackageConfig.Builder packageContext) {
+        NodeList allowedMethodsEls = element.getElementsByTagName("allowed-methods");
+
+        Set<String> allowedMethods = null;
+
+        if (allowedMethodsEls.getLength() > 0) {
+            allowedMethods = new HashSet<>();
+            Node n = allowedMethodsEls.item(0).getFirstChild();
+            if (n != null) {
+                String s = n.getNodeValue().trim();
+                if (s.length() > 0) {
+                    allowedMethods = TextParseUtil.commaDelimitedStringToSet(s);
+                }
+            }
+        } else if (packageContext.isStrictMethodInvocation()) {
+            allowedMethods = new HashSet<>();
+        }
+
+        return allowedMethods;
+    }
+
+    protected void loadDefaultInterceptorRef(PackageConfig.Builder packageContext, Element element) {
+        NodeList resultTypeList = element.getElementsByTagName("default-interceptor-ref");
+
+        if (resultTypeList.getLength() > 0) {
+            Element defaultRefElement = (Element) resultTypeList.item(0);
+            packageContext.defaultInterceptorRef(defaultRefElement.getAttribute("name"));
+        }
+    }
+
+    protected void loadDefaultActionRef(PackageConfig.Builder packageContext, Element element) {
+        NodeList resultTypeList = element.getElementsByTagName("default-action-ref");
+
+        if (resultTypeList.getLength() > 0) {
+            Element defaultRefElement = (Element) resultTypeList.item(0);
+            packageContext.defaultActionRef(defaultRefElement.getAttribute("name"));
+        }
+    }
+
+    /**
+     * Load all of the global results for this package from the XML element.
+     */
+    protected void loadGlobalResults(PackageConfig.Builder packageContext, Element packageElement) {
+        NodeList globalResultList = packageElement.getElementsByTagName("global-results");
+
+        if (globalResultList.getLength() > 0) {
+            Element globalResultElement = (Element) globalResultList.item(0);
+            Map<String, ResultConfig> results = buildResults(globalResultElement, packageContext);
+            packageContext.addGlobalResultConfigs(results);
+        }
+    }
+
+    protected void loadDefaultClassRef(PackageConfig.Builder packageContext, Element element) {
+        NodeList defaultClassRefList = element.getElementsByTagName("default-class-ref");
+        if (defaultClassRefList.getLength() > 0) {
+            Element defaultClassRefElement = (Element) defaultClassRefList.item(0);
+            packageContext.defaultClassRef(defaultClassRefElement.getAttribute("class"));
+        }
+    }
+
+    /**
+     * Load all of the global results for this package from the XML element.
+     */
+    protected void loadGobalExceptionMappings(PackageConfig.Builder packageContext, Element packageElement) {
+        NodeList globalExceptionMappingList = packageElement.getElementsByTagName("global-exception-mappings");
+
+        if (globalExceptionMappingList.getLength() > 0) {
+            Element globalExceptionMappingElement = (Element) globalExceptionMappingList.item(0);
+            List<ExceptionMappingConfig> exceptionMappings = buildExceptionMappings(globalExceptionMappingElement, packageContext);
+            packageContext.addGlobalExceptionMappingConfigs(exceptionMappings);
+        }
+    }
+
+    protected InterceptorStackConfig loadInterceptorStack(Element element, PackageConfig.Builder context) throws ConfigurationException {
+        String name = element.getAttribute("name");
+
+        InterceptorStackConfig.Builder config = new InterceptorStackConfig.Builder(name)
+                .location(DomHelper.getLocationObject(element));
+        NodeList interceptorRefList = element.getElementsByTagName("interceptor-ref");
+
+        for (int j = 0; j < interceptorRefList.getLength(); j++) {
+            Element interceptorRefElement = (Element) interceptorRefList.item(j);
+            List<InterceptorMapping> interceptors = lookupInterceptorReference(context, interceptorRefElement);
+            config.addInterceptors(interceptors);
+        }
+
+        return config.build();
+    }
+
+    protected void loadInterceptorStacks(Element element, PackageConfig.Builder context) throws ConfigurationException {
+        NodeList interceptorStackList = element.getElementsByTagName("interceptor-stack");
+
+        for (int i = 0; i < interceptorStackList.getLength(); i++) {
+            Element interceptorStackElement = (Element) interceptorStackList.item(i);
+
+            InterceptorStackConfig config = loadInterceptorStack(interceptorStackElement, context);
+
+            context.addInterceptorStackConfig(config);
+        }
+    }
+
+    protected void loadInterceptors(PackageConfig.Builder context, Element element) throws ConfigurationException {
+        NodeList interceptorList = element.getElementsByTagName("interceptor");
+
+        for (int i = 0; i < interceptorList.getLength(); i++) {
+            Element interceptorElement = (Element) interceptorList.item(i);
+            String name = interceptorElement.getAttribute("name");
+            String className = interceptorElement.getAttribute("class");
+
+            Map<String, String> params = XmlHelper.getParams(interceptorElement);
+            InterceptorConfig config = new InterceptorConfig.Builder(name, className)
+                    .addParams(params)
+                    .location(DomHelper.getLocationObject(interceptorElement))
+                    .build();
+
+            context.addInterceptorConfig(config);
+        }
+
+        loadInterceptorStacks(element, context);
+    }
+
+    //    protected void loadPackages(Element rootElement) throws ConfigurationException {
+    //        NodeList packageList = rootElement.getElementsByTagName("package");
+    //
+    //        for (int i = 0; i < packageList.getLength(); i++) {
+    //            Element packageElement = (Element) packageList.item(i);
+    //            addPackage(packageElement);
+    //        }
+    //    }
+    private List<Document> loadConfigurationFiles(String fileName, Element includeElement) {
+        List<Document> docs = new ArrayList<>();
+        List<Document> finalDocs = new ArrayList<>();
+        if (!includedFileNames.contains(fileName)) {
+            LOG.debug("Loading action configurations from: {}", fileName);
+
+            includedFileNames.add(fileName);
+
+            Iterator<URL> urls = null;
+            InputStream is = null;
+
+            IOException ioException = null;
+            try {
+                urls = getConfigurationUrls(fileName);
+            } catch (IOException ex) {
+                ioException = ex;
+            }
+
+            if (urls == null || !urls.hasNext()) {
+                if (errorIfMissing) {
+                    throw new ConfigurationException("Could not open files of the name " + fileName, ioException);
+                } else {
+                    LOG.info("Unable to locate configuration files of the name {}, skipping", fileName);
+                    return docs;
+                }
+            }
+
+            URL url = null;
+            while (urls.hasNext()) {
+                try {
+                    url = urls.next();
+                    is = fileManager.loadFile(url);
+
+                    InputSource in = new InputSource(is);
+
+                    in.setSystemId(url.toString());
+
+                    docs.add(DomHelper.parse(in, dtdMappings));
+                    loadedFileUrls.add(url.toString());
+                } catch (XWorkException e) {
+                    if (includeElement != null) {
+                        throw new ConfigurationException("Unable to load " + url, e, includeElement);
+                    } else {
+                        throw new ConfigurationException("Unable to load " + url, e);
+                    }
+                } catch (Exception e) {
+                    throw new ConfigurationException("Caught exception while loading file " + fileName, e, includeElement);
+                } finally {
+                    if (is != null) {
+                        try {
+                            is.close();
+                        } catch (IOException e) {
+                            LOG.error("Unable to close input stream", e);
+                        }
+                    }
+                }
+            }
+
+            //sort the documents, according to the "order" attribute
+            Collections.sort(docs, new Comparator<Document>() {
+                public int compare(Document doc1, Document doc2) {
+                    return XmlHelper.getLoadOrder(doc1).compareTo(XmlHelper.getLoadOrder(doc2));
+                }
+            });
+
+            for (Document doc : docs) {
+                Element rootElement = doc.getDocumentElement();
+                NodeList children = rootElement.getChildNodes();
+                int childSize = children.getLength();
+
+                for (int i = 0; i < childSize; i++) {
+                    Node childNode = children.item(i);
+
+                    if (childNode instanceof Element) {
+                        Element child = (Element) childNode;
+
+                        final String nodeName = child.getNodeName();
+
+                        if ("include".equals(nodeName)) {
+                            String includeFileName = child.getAttribute("file");
+                            if (includeFileName.indexOf('*') != -1) {
+                                // handleWildCardIncludes(includeFileName, docs, child);
+                                ClassPathFinder wildcardFinder = new ClassPathFinder();
+                                wildcardFinder.setPattern(includeFileName);
+                                Vector<String> wildcardMatches = wildcardFinder.findMatches();
+                                for (String match : wildcardMatches) {
+                                    finalDocs.addAll(loadConfigurationFiles(match, child));
+                                }
+                            } else {
+                                finalDocs.addAll(loadConfigurationFiles(includeFileName, child));
+                            }
+                        }
+                    }
+                }
+                finalDocs.add(doc);
+            }
+
+            LOG.debug("Loaded action configuration from: {}", fileName);
+        }
+        return finalDocs;
+    }
+
+    protected Iterator<URL> getConfigurationUrls(String fileName) throws IOException {
+        return ClassLoaderUtil.getResources(fileName, XmlConfigurationProvider.class, false);
+    }
+
+    /**
+     * Allows subclasses to load extra information from the document
+     *
+     * @param doc The configuration document
+     */
+    protected void loadExtraConfiguration(Document doc) {
+        // no op
+    }
+
+    /**
+     * Looks up the Interceptor Class from the interceptor-ref name and creates an instance, which is added to the
+     * provided List, or, if this is a ref to a stack, it adds the Interceptor instances from the List to this stack.
+     *
+     * @param interceptorRefElement Element to pull interceptor ref data from
+     * @param context               The PackageConfig to lookup the interceptor from
+     * @return A list of Interceptor objects
+     */
+    private List<InterceptorMapping> lookupInterceptorReference(PackageConfig.Builder context, Element interceptorRefElement) throws ConfigurationException {
+        String refName = interceptorRefElement.getAttribute("name");
+        Map<String, String> refParams = XmlHelper.getParams(interceptorRefElement);
+
+        Location loc = LocationUtils.getLocation(interceptorRefElement);
+        return InterceptorBuilder.constructInterceptorReference(context, refName, refParams, loc, objectFactory);
+    }
+
+    List<Document> getDocuments() {
+        return documents;
+    }
+
+    @Override
+    public String toString() {
+        return "XmlConfigurationProvider{" +
+                "configFileName='" + configFileName + '\'' +
+                '}';
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlHelper.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlHelper.java b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlHelper.java
new file mode 100644
index 0000000..84e09d3
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/XmlHelper.java
@@ -0,0 +1,127 @@
+/*
+ * 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.config.providers;
+
+import org.apache.commons.lang3.StringUtils;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+
+/**
+ * XML utilities.
+ *
+ * @author Mike
+ */
+public class XmlHelper {
+
+
+    /**
+     * This method will find all the parameters under this <code>paramsElement</code> and return them as
+     * Map<String, String>. For example,
+     * <pre>
+     *   <result ... >
+     *      <param name="param1">value1</param>
+     *      <param name="param2">value2</param>
+     *      <param name="param3">value3</param>
+     *   </result>
+     * </pre>
+     * will returns a Map<String, String> with the following key, value pairs :-
+     * <ul>
+     * <li>param1 - value1</li>
+     * <li>param2 - value2</li>
+     * <li>param3 - value3</li>
+     * </ul>
+     *
+     * @param paramsElement
+     * @return
+     */
+    public static Map<String, String> getParams(Element paramsElement) {
+        LinkedHashMap<String, String> params = new LinkedHashMap<>();
+
+        if (paramsElement == null) {
+            return params;
+        }
+
+        NodeList childNodes = paramsElement.getChildNodes();
+
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            Node childNode = childNodes.item(i);
+
+            if ((childNode.getNodeType() == Node.ELEMENT_NODE) && "param".equals(childNode.getNodeName())) {
+                Element paramElement = (Element) childNode;
+                String paramName = paramElement.getAttribute("name");
+
+                String val = getContent(paramElement);
+                if (val.length() > 0) {
+                    params.put(paramName, val);
+                }
+            }
+        }
+
+        return params;
+    }
+
+    /**
+     * This method will return the content of this particular <code>element</code>.
+     * For example,
+     * <p/>
+     * <pre>
+     *    <result>something_1</result>
+     * </pre>
+     * When the {@link org.w3c.dom.Element} <code>&lt;result&gt;</code> is passed in as
+     * argument (<code>element</code> to this method, it returns the content of it,
+     * namely, <code>something_1</code> in the example above.
+     *
+     * @return
+     */
+    public static String getContent(Element element) {
+        StringBuilder paramValue = new StringBuilder();
+        NodeList childNodes = element.getChildNodes();
+        for (int j = 0; j < childNodes.getLength(); j++) {
+            Node currentNode = childNodes.item(j);
+            if (currentNode != null && currentNode.getNodeType() == Node.TEXT_NODE) {
+                String val = currentNode.getNodeValue();
+                if (val != null) {
+                    paramValue.append(val.trim());
+                }
+            }
+        }
+        return paramValue.toString().trim();
+    }
+
+    /**
+     * Return the value of the "order" attribute from the root element
+     */
+     public static Integer getLoadOrder(Document doc) {
+        Element rootElement = doc.getDocumentElement();
+        String number = rootElement.getAttribute("order");
+        if (StringUtils.isNotBlank(number)) {
+            try {
+                return Integer.parseInt(number);
+            } catch (NumberFormatException e) {
+                return Integer.MAX_VALUE;
+            }
+        } else {
+            //no order specified
+            return Integer.MAX_VALUE;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/providers/package.html
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/config/providers/package.html b/core/src/main/java/com/opensymphony/xwork2/config/providers/package.html
new file mode 100644
index 0000000..946fc4e
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/config/providers/package.html
@@ -0,0 +1 @@
+<body>Configuration provider classes.</body>

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/ConversionAnnotationProcessor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/ConversionAnnotationProcessor.java b/core/src/main/java/com/opensymphony/xwork2/conversion/ConversionAnnotationProcessor.java
new file mode 100644
index 0000000..159f8d5
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/ConversionAnnotationProcessor.java
@@ -0,0 +1,23 @@
+package com.opensymphony.xwork2.conversion;
+
+import com.opensymphony.xwork2.conversion.annotations.TypeConversion;
+
+import java.util.Map;
+
+/**
+ * Used to process {@link com.opensymphony.xwork2.conversion.annotations.TypeConversion}
+ * annotation to read defined Converters
+ */
+public interface ConversionAnnotationProcessor {
+
+    /**
+     * Process annotation and build {@link TypeConverter} base on provided annotation
+     * and assigning it under given key
+     *
+     * @param mapping keeps converters per given key
+     * @param tc annotation which keeps information about converter
+     * @param key key under which converter should be registered
+     */
+    void process(Map<String, Object> mapping, TypeConversion tc, String key);
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/ConversionFileProcessor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/ConversionFileProcessor.java b/core/src/main/java/com/opensymphony/xwork2/conversion/ConversionFileProcessor.java
new file mode 100644
index 0000000..8d2803f
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/ConversionFileProcessor.java
@@ -0,0 +1,19 @@
+package com.opensymphony.xwork2.conversion;
+
+import java.util.Map;
+
+/**
+ * Used to process <clazz>-conversion.properties file to read defined Converters
+ */
+public interface ConversionFileProcessor {
+
+    /**
+     * Process conversion file to create mapping for key (property, type) and corresponding converter
+     *
+     * @param mapping keeps converters per given key
+     * @param clazz class which should be converted by the converter
+     * @param converterFilename to read converters from
+     */
+    void process(Map<String, Object> mapping, Class clazz, String converterFilename);
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/ConversionPropertiesProcessor.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/ConversionPropertiesProcessor.java b/core/src/main/java/com/opensymphony/xwork2/conversion/ConversionPropertiesProcessor.java
new file mode 100644
index 0000000..8d0bab8
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/ConversionPropertiesProcessor.java
@@ -0,0 +1,22 @@
+package com.opensymphony.xwork2.conversion;
+
+/**
+ * Used to read converters from Properties file
+ */
+public interface ConversionPropertiesProcessor {
+
+    /**
+     * Process given property to load converters as not required (Properties file doesn't have to exist)
+     *
+     * @param propsName Properties file name
+     */
+    void process(String propsName);
+
+    /**
+     * Process given property to load converters as required (Properties file must exist)
+     *
+     * @param propsName Properties file name
+     */
+    void processRequired(String propsName);
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/NullHandler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/NullHandler.java b/core/src/main/java/com/opensymphony/xwork2/conversion/NullHandler.java
new file mode 100644
index 0000000..86d71f0
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/NullHandler.java
@@ -0,0 +1,54 @@
+//--------------------------------------------------------------------------
+//Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard
+//All rights reserved.
+//
+//Redistribution and use in source and binary forms, with or without
+//modification, are permitted provided that the following conditions are
+//met:
+//
+//Redistributions of source code must retain the above copyright notice,
+//this list of conditions and the following disclaimer.
+//Redistributions in binary form must reproduce the above copyright
+//notice, this list of conditions and the following disclaimer in the
+//documentation and/or other materials provided with the distribution.
+//Neither the name of the Drew Davidson nor the names of its contributors
+//may be used to endorse or promote products derived from this software
+//without specific prior written permission.
+//
+//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+//"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+//LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+//FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+//COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+//INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+//BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+//OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+//AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+//OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+//THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+//DAMAGE.
+//--------------------------------------------------------------------------
+package com.opensymphony.xwork2.conversion;
+
+import java.util.Map;
+
+/**
+* Interface for handling null results from Chains.
+* Object has the opportunity to substitute an object for the
+* null and continue.
+* @author Luke Blanshard (blanshlu@netscape.net)
+* @author Drew Davidson (drew@ognl.org)
+*/
+public interface NullHandler
+{
+    /**
+        Method called on target returned null.
+     */
+    public Object nullMethodResult(Map<String, Object> context, Object target, String methodName, Object[] args);
+    
+    /**
+        Property in target evaluated to null.  Property can be a constant
+        String property name or a DynamicSubscript.
+     */
+    public Object nullPropertyValue(Map<String, Object> context, Object target, Object property);
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/ObjectTypeDeterminer.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/ObjectTypeDeterminer.java b/core/src/main/java/com/opensymphony/xwork2/conversion/ObjectTypeDeterminer.java
new file mode 100644
index 0000000..fe2b748
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/ObjectTypeDeterminer.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * Determines what the key and and element class of a Map or Collection should be. For Maps, the elements are the
+ * values. For Collections, the elements are the elements of the collection.
+ * <p/>
+ * See the implementations for javadoc description for the methods as they are dependent on the concrete implementation.
+ *
+ * @author Gabriel Zimmerman
+ */
+public interface ObjectTypeDeterminer {
+
+    public Class getKeyClass(Class parentClass, String property);
+
+    public Class getElementClass(Class parentClass, String property, Object key);
+
+    public String getKeyProperty(Class parentClass, String property);
+    
+    public boolean shouldCreateIfNew(Class parentClass,  String property,  Object target, String keyProperty, boolean isIndexAccessed);
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConversionException.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConversionException.java b/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConversionException.java
new file mode 100644
index 0000000..033ee73
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConversionException.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+import com.opensymphony.xwork2.XWorkException;
+
+
+/**
+ * TypeConversionException should be thrown by any TypeConverters which fail to convert values
+ *
+ * @author Jason Carreira
+ *         Created Oct 3, 2003 12:18:33 AM
+ */
+public class TypeConversionException extends XWorkException {
+
+    /**
+     * Constructs a <code>XWorkException</code> with no detail  message.
+     */
+    public TypeConversionException() {
+    }
+
+    /**
+     * Constructs a <code>XWorkException</code> with the specified
+     * detail message.
+     *
+     * @param s the detail message.
+     */
+    public TypeConversionException(String s) {
+        super(s);
+    }
+
+    /**
+     * Constructs a <code>XWorkException</code> with no detail  message.
+     */
+    public TypeConversionException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs a <code>XWorkException</code> with the specified
+     * detail message.
+     *
+     * @param s the detail message.
+     */
+    public TypeConversionException(String s, Throwable cause) {
+        super(s, cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverter.java b/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverter.java
new file mode 100644
index 0000000..0fb67d7
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverter.java
@@ -0,0 +1,64 @@
+//--------------------------------------------------------------------------
+//  Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard
+//  All rights reserved.
+//
+//  Redistribution and use in source and binary forms, with or without
+//  modification, are permitted provided that the following conditions are
+//  met:
+//
+//  Redistributions of source code must retain the above copyright notice,
+//  this list of conditions and the following disclaimer.
+//  Redistributions in binary form must reproduce the above copyright
+//  notice, this list of conditions and the following disclaimer in the
+//  documentation and/or other materials provided with the distribution.
+//  Neither the name of the Drew Davidson nor the names of its contributors
+//  may be used to endorse or promote products derived from this software
+//  without specific prior written permission.
+//
+//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+//  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+//  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+//  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+//  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+//  OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+//  AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+//  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+//  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+//  DAMAGE.
+//--------------------------------------------------------------------------
+package com.opensymphony.xwork2.conversion;
+
+import java.lang.reflect.Member;
+import java.util.Map;
+
+/**
+ * Interface for accessing the type conversion facilities within a context.
+ * 
+ * This interface was copied from OGNL's TypeConverter
+ * 
+ * @author Luke Blanshard (blanshlu@netscape.net)
+ * @author Drew Davidson (drew@ognl.org)
+ */
+public interface TypeConverter
+{
+    /**
+       * Converts the given value to a given type.  The OGNL context, target, member and
+       * name of property being set are given.  This method should be able to handle
+       * conversion in general without any context, target, member or property name specified.
+       * @param context context under which the conversion is being done
+       * @param target target object in which the property is being set
+       * @param member member (Constructor, Method or Field) being set
+       * @param propertyName property name being set
+       * @param value value to be converted
+       * @param toType type to which value is converted
+       * @return Converted value of type toType or TypeConverter.NoConversionPossible to indicate that the
+                 conversion was not possible.
+     */
+    public Object convertValue(Map<String, Object> context, Object target, Member member, String propertyName, Object value, Class toType);
+    
+    public static final Object NO_CONVERSION_POSSIBLE = "ognl.NoConversionPossible";
+    
+    public static final String TYPE_CONVERTER_CONTEXT_KEY = "_typeConverter";
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverterCreator.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverterCreator.java b/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverterCreator.java
new file mode 100644
index 0000000..738bbe8
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverterCreator.java
@@ -0,0 +1,17 @@
+package com.opensymphony.xwork2.conversion;
+
+/**
+ * Instantiate converter classes, if cannot create TypeConverter throws exception
+ */
+public interface TypeConverterCreator {
+
+    /**
+     * Creates {@link TypeConverter} from given class
+     *
+     * @param className convert class
+     * @return instance of {@link TypeConverter}
+     * @throws Exception when cannot create/cast to {@link TypeConverter}
+     */
+    TypeConverter createTypeConverter(String className) throws Exception;
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverterHolder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverterHolder.java b/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverterHolder.java
new file mode 100644
index 0000000..6a67b8b
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/TypeConverterHolder.java
@@ -0,0 +1,82 @@
+package com.opensymphony.xwork2.conversion;
+
+import java.util.Map;
+
+/**
+ * Holds all mappings related to {@link TypeConverter}s
+ */
+public interface TypeConverterHolder {
+
+    /**
+     * Adds mapping for default type converters - application scoped
+     *
+     * @param className     name of the class with associated converter
+     * @param typeConverter {@link TypeConverter} instance for associated class
+     */
+    void addDefaultMapping(String className, TypeConverter typeConverter);
+
+    /**
+     * Checks if converter was already defined for given class
+     *
+     * @param className name of the class to check for
+     * @return true if default mapping was already specified
+     */
+    boolean containsDefaultMapping(String className);
+
+    /**
+     * Returns instance of {@link TypeConverter} associated with given class
+     *
+     * @param className name of the class to return converter for
+     * @return instance of {@link TypeConverter} to be used to convert class
+     */
+    TypeConverter getDefaultMapping(String className);
+
+    /**
+     * Target class conversion Mappings.
+     *
+     * @param clazz class to convert to/from
+     * @return {@link TypeConverter} for given class
+     */
+    Map<String, Object> getMapping(Class clazz);
+
+    /**
+     * Assign mapping of converters for given class
+     *
+     * @param clazz   class to convert to/from
+     * @param mapping property converters
+     */
+    void addMapping(Class clazz, Map<String, Object> mapping);
+
+    /**
+     * Check if there is no mapping for given class to convert
+     *
+     * @param clazz class to convert to/from
+     * @return true if mapping couldn't be found
+     */
+    boolean containsNoMapping(Class clazz);
+
+    /**
+     * Adds no mapping flag for give class
+     *
+     * @param clazz class to register missing converter
+     */
+    void addNoMapping(Class clazz);
+
+    /**
+     * Checks if no mapping was defined for given class name
+     * FIXME lukaszlenart: maybe it should be merged with NoMapping
+     *
+     * @param className name of the class to check for
+     * @return true if converter was defined for given class name
+     */
+    boolean containsUnknownMapping(String className);
+
+    /**
+     * Adds no converter flag for given class name
+     * FIXME lukaszlenart: maybe it should be merged with NoMapping
+     *
+     * @param className name of the class to mark there is no converter for it
+     */
+    void addUnknownMapping(String className);
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/Conversion.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/Conversion.java b/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/Conversion.java
new file mode 100644
index 0000000..07a50d1
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/Conversion.java
@@ -0,0 +1,95 @@
+/*
+ * 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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <!-- START SNIPPET: description -->
+ * A marker annotation for type conversions at Type level.
+ * <!-- END SNIPPET: description -->
+ *
+ * <p/> <u>Annotation usage:</u>
+ *
+ * <!-- START SNIPPET: usage -->
+ * The Conversion annotation must be applied at Type level.
+ * <!-- END SNIPPET: usage -->
+ *
+ * <p/> <u>Annotation parameters:</u>
+ *
+ * <!-- START SNIPPET: parameters -->
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>Parameter</th>
+ * <th>Required</th>
+ * <th>Default</th>
+ * <th>Description</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>conversion</td>
+ * <td>no</td>
+ * <td>&nbsp;</td>
+ * <td>used for Type Conversions applied at Type level.</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p/> <u>Example code:</u>
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * &#64;Conversion(
+ *     conversions = {
+ *          // key must be the name of a property for which converter should be used
+ *          &#64;TypeConversion(key = "date", converter = "org.demo.converter.DateConverter")
+ *     }
+ * )
+ * public class ConversionAction implements Action {
+ *
+ *     private Date date;
+ *
+ *     public setDate(Date date) {
+ *         this.date = date;
+ *     }
+ *
+ *     public Date getDate() {
+ *         return date;
+ *     }
+ *
+ * }
+ *
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author Rainer Hermanns
+ * @version $Id$
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Conversion {
+
+    /**
+     * Allow Type Conversions being applied at Type level.
+     */
+    TypeConversion[] conversions() default {};
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/ConversionRule.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/ConversionRule.java b/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/ConversionRule.java
new file mode 100644
index 0000000..e30bea4
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/ConversionRule.java
@@ -0,0 +1,33 @@
+/*
+ * 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.annotations;
+
+/**
+ * <code>ConversionRule</code>
+ *
+ * @author Rainer Hermanns
+ * @version $Id$
+ */
+public enum ConversionRule {
+
+    PROPERTY, COLLECTION, MAP, KEY, KEY_PROPERTY, ELEMENT, CREATE_IF_NULL;
+
+    @Override
+    public String toString() {
+        return super.toString().toUpperCase();
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/ConversionType.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/ConversionType.java b/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/ConversionType.java
new file mode 100644
index 0000000..d80a926
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/ConversionType.java
@@ -0,0 +1,34 @@
+/*
+ * 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.annotations;
+
+/**
+ * <code>ConversionType</code>
+ *
+ * @author <a href="mailto:hermanns@aixcept.de">Rainer Hermanns</a>
+ * @version $Id$
+ */
+public enum ConversionType {
+
+
+    APPLICATION, CLASS;
+
+    @Override
+    public String toString() {
+        return super.toString().toUpperCase();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/TypeConversion.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/TypeConversion.java b/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/TypeConversion.java
new file mode 100644
index 0000000..d54bd2f
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/TypeConversion.java
@@ -0,0 +1,178 @@
+/*
+ * 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.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * <!-- START SNIPPET: description -->
+ * <p/>This annotation is used for class and application wide conversion rules.
+ * <p>
+ * Class wide conversion:<br/>
+ * The conversion rules will be assembled in a file called <code>XXXAction-conversion.properties</code>
+ * within the same package as the related action class.
+ * Set type to: <code>type = ConversionType.CLASS</code>
+ * </p>
+ * <p>
+ * Allication wide conversion:<br/>
+ * The conversion rules will be assembled within the <code>xwork-conversion.properties</code> file within the classpath root.
+ * Set type to: <code>type = ConversionType.APPLICATION</code>
+ * <p/>
+ * <!-- END SNIPPET: description -->
+ *
+ * <p/> <u>Annotation usage:</u>
+ *
+ * <!-- START SNIPPET: usage -->
+ * The TypeConversion annotation can be applied at property and method level.
+ * <!-- END SNIPPET: usage -->
+ *
+ * <p/> <u>Annotation parameters:</u>
+ *
+ * <!-- START SNIPPET: parameters -->
+ * <table>
+ * <thead>
+ * <tr>
+ * <th>Parameter</th>
+ * <th>Required</th>
+ * <th>Default</th>
+ * <th>Description</th>
+ * </tr>
+ * </thead>
+ * <tbody>
+ * <tr>
+ * <td>key</td>
+ * <td>no</td>
+ * <td>The annotated property/key name</td>
+ * <td>The optional property name mostly used within TYPE level annotations.</td>
+ * </tr>
+ * <tr>
+ * <td>type</td>
+ * <td>no</td>
+ * <td>ConversionType.CLASS</td>
+ * <td>Enum value of ConversionType.  Determines whether the conversion should be applied at application or class level.</td>
+ * </tr>
+ * <tr>
+ * <td>rule</td>
+ * <td>no</td>
+ * <td>ConversionRule.PROPERTY</td>
+ * <td>Enum value of ConversionRule. The ConversionRule can be a property, a Collection or a Map.</td>
+ * </tr>
+ * <tr>
+ * <td>converter</td>
+ * <td>either this or value</td>
+ * <td>&nbsp;</td>
+ * <td>The class name of the TypeConverter to be used as converter.</td>
+ * </tr>
+ * <tr>
+ * <td>value</td>
+ * <td>either converter or this</td>
+ * <td>&nbsp;</td>
+ * <td>The value to set for ConversionRule.KEY_PROPERTY.</td>
+ * </tr>
+ * </tbody>
+ * </table>
+ *
+ * <!-- END SNIPPET: parameters -->
+ *
+ * <p/> <u>Example code:</u>
+ *
+ * <pre>
+ * <!-- START SNIPPET: example -->
+ * &#64;Conversion()
+ * public class ConversionAction implements Action {
+ *
+ *   private String convertInt;
+ *
+ *   private String convertDouble;
+ *   private List users = null;
+ *
+ *   private HashMap keyValues = null;
+ *
+ *   &#64;TypeConversion(type = ConversionType.APPLICATION, converter = "com.opensymphony.xwork2.util.XWorkBasicConverter")
+ *   public void setConvertInt( String convertInt ) {
+ *       this.convertInt = convertInt;
+ *   }
+ *
+ *   &#64;TypeConversion(converter = "com.opensymphony.xwork2.util.XWorkBasicConverter")
+ *   public void setConvertDouble( String convertDouble ) {
+ *       this.convertDouble = convertDouble;
+ *   }
+ *
+ *   &#64;TypeConversion(rule = ConversionRule.COLLECTION, converter = "java.util.String")
+ *   public void setUsers( List users ) {
+ *       this.users = users;
+ *   }
+ *
+ *   &#64;TypeConversion(rule = ConversionRule.MAP, converter = "java.math.BigInteger")
+ *   public void setKeyValues( HashMap keyValues ) {
+ *       this.keyValues = keyValues;
+ *   }
+ *
+ *   &#64;TypeConversion(type = ConversionType.APPLICATION, property = "java.util.Date", converter = "com.opensymphony.xwork2.util.XWorkBasicConverter")
+ *   public String execute() throws Exception {
+ *       return SUCCESS;
+ *   }
+ * }
+ * <!-- END SNIPPET: example -->
+ * </pre>
+ *
+ * @author Rainer Hermanns
+ * @version $Id$
+ */
+@Target({ ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface TypeConversion {
+
+    /**
+     * The optional key name used within TYPE level annotations.
+     * Defaults to the property name.
+     */
+    String key() default "";
+
+    /**
+     * The ConversionType can be either APPLICATION or CLASS.
+     * Defaults to CLASS.
+     *
+     * Note: If you use ConversionType.APPLICATION, you can not set a value!
+     */
+    ConversionType type() default ConversionType.CLASS;
+
+    /**
+     * The ConversionRule can be a PROPERTY, KEY, KEY_PROPERTY, ELEMENT, COLLECTION (deprecated) or a MAP.
+     * Note: Collection and Map conversion rules can be determined via com.opensymphony.xwork2.util.DefaultObjectTypeDeterminer.
+     *
+     * @see com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer
+     */
+    ConversionRule rule() default ConversionRule.PROPERTY;
+
+    /**
+     * The class of the TypeConverter to be used as converter.
+     *
+     * Note: This can not be used with ConversionRule.KEY_PROPERTY! 
+     */
+    String converter() default "";
+
+    /**
+     * If used with ConversionRule.KEY_PROPERTY specify a value here!
+     *
+     * Note: If you use ConversionType.APPLICATION, you can not set a value!
+     */
+    String value() default "";
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/package.html
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/package.html b/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/package.html
new file mode 100644
index 0000000..e2a91d0
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/annotations/package.html
@@ -0,0 +1 @@
+<body>Type conversion annotations.</body>


Mime
View raw message