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><result></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 ([email protected]) +* @author Drew Davidson ([email protected]) +*/ +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 ([email protected]) + * @author Drew Davidson ([email protected]) + */ +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> </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 --> + * @Conversion( + * conversions = { + * // key must be the name of a property for which converter should be used + * @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:[email protected]">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> </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> </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 --> + * @Conversion() + * public class ConversionAction implements Action { + * + * private String convertInt; + * + * private String convertDouble; + * private List users = null; + * + * private HashMap keyValues = null; + * + * @TypeConversion(type = ConversionType.APPLICATION, converter = "com.opensymphony.xwork2.util.XWorkBasicConverter") + * public void setConvertInt( String convertInt ) { + * this.convertInt = convertInt; + * } + * + * @TypeConversion(converter = "com.opensymphony.xwork2.util.XWorkBasicConverter") + * public void setConvertDouble( String convertDouble ) { + * this.convertDouble = convertDouble; + * } + * + * @TypeConversion(rule = ConversionRule.COLLECTION, converter = "java.util.String") + * public void setUsers( List users ) { + * this.users = users; + * } + * + * @TypeConversion(rule = ConversionRule.MAP, converter = "java.math.BigInteger") + * public void setKeyValues( HashMap keyValues ) { + * this.keyValues = keyValues; + * } + * + * @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>
