http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorListHolder.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorListHolder.java b/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorListHolder.java new file mode 100644 index 0000000..4323116 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorListHolder.java @@ -0,0 +1,31 @@ +/* + * 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.entities; + +import java.util.List; + +/** + * InterceptorListHolder + * + * @author Jason Carreira + * Created Jun 1, 2003 1:02:48 AM + */ +public interface InterceptorListHolder { + + InterceptorListHolder addInterceptor(InterceptorMapping interceptor); + + InterceptorListHolder addInterceptors(List<InterceptorMapping> interceptors); +}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorLocator.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorLocator.java b/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorLocator.java new file mode 100644 index 0000000..aa74960 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorLocator.java @@ -0,0 +1,14 @@ +package com.opensymphony.xwork2.config.entities; + +/** + * Defines an object that can be used to retrieve interceptor configuration + */ +public interface InterceptorLocator { + + /** + * Gets an interceptor configuration object. + * @param name The interceptor or interceptor stack name + * @return Either an {@link InterceptorConfig} or {@link InterceptorStackConfig} object + */ + Object getInterceptorConfig(String name); +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java b/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java new file mode 100644 index 0000000..846575e --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorMapping.java @@ -0,0 +1,71 @@ +/* + * 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.entities; + +import com.opensymphony.xwork2.interceptor.Interceptor; + +import java.io.Serializable; + +/** + * <code>InterceptorMapping</code> + * + * @author <a href="mailto:[email protected]">Rainer Hermanns</a> + * @version $Id$ + */ +public class InterceptorMapping implements Serializable { + + private String name; + private Interceptor interceptor; + + public InterceptorMapping(String name, Interceptor interceptor) { + this.name = name; + this.interceptor = interceptor; + } + + public String getName() { + return name; + } + + public Interceptor getInterceptor() { + return interceptor; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final InterceptorMapping that = (InterceptorMapping) o; + + if (name != null ? !name.equals(that.name) : that.name != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result; + result = (name != null ? name.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "InterceptorMapping: [" + name + "] => [" + interceptor.getClass().getName() + ']'; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorStackConfig.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorStackConfig.java b/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorStackConfig.java new file mode 100644 index 0000000..b9e2f78 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/InterceptorStackConfig.java @@ -0,0 +1,182 @@ +/* + * 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.entities; + +import com.opensymphony.xwork2.util.location.Located; +import com.opensymphony.xwork2.util.location.Location; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + + +/** + * Configuration for InterceptorStack. + * <p/> + * In the xml configuration file this is defined as the <code>interceptor-stack</code> tag. + * + * @author Mike + * @author Rainer Hermanns + */ +public class InterceptorStackConfig extends Located implements Serializable { + + private static final long serialVersionUID = 2897260918170270343L; + + /** + * A list of InterceptorMapping object + */ + protected List<InterceptorMapping> interceptors; + protected String name; + + /** + * Creates an InterceptorStackConfig object. + */ + protected InterceptorStackConfig() { + this.interceptors = new ArrayList<InterceptorMapping>(); + } + + /** + * Creates an InterceptorStackConfig object with a particular <code>name</code>. + * + * @param name + */ + protected InterceptorStackConfig(InterceptorStackConfig orig) { + this.name = orig.name; + this.interceptors = new ArrayList<>(orig.interceptors); + this.location = orig.location; + } + + + /** + * Returns a <code>Collection</code> of InterceptorMapping objects. + * + * @return + */ + public Collection<InterceptorMapping> getInterceptors() { + return interceptors; + } + + /** + * Get the name of this interceptor stack configuration. + * + * @return String + */ + public String getName() { + return name; + } + + /** + * An InterceptorStackConfig object is equals with <code>o</code> only if + * <ul> + * <li>o is an InterceptorStackConfig object</li> + * <li>both names are equals</li> + * <li>all of their <code>InterceptorMapping</code>s are equals</li> + * </ul> + */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof InterceptorStackConfig)) { + return false; + } + + final InterceptorStackConfig interceptorStackConfig = (InterceptorStackConfig) o; + + if ((interceptors != null) ? (!interceptors.equals(interceptorStackConfig.interceptors)) : (interceptorStackConfig.interceptors != null)) { + return false; + } + + if ((name != null) ? (!name.equals(interceptorStackConfig.name)) : (interceptorStackConfig.name != null)) { + return false; + } + + return true; + } + + /** + * Generate hashcode based on <code>InterceptorStackConfig</code>'s name and its + * <code>InterceptorMapping</code>s. + */ + @Override + public int hashCode() { + int result; + result = ((name != null) ? name.hashCode() : 0); + result = (29 * result) + ((interceptors != null) ? interceptors.hashCode() : 0); + + return result; + } + + @Override + public String toString() { + return "InterceptorStackConfig: [" + name + "] contains " + interceptors; + } + + /** + * The builder for this object. An instance of this object is the only way to construct a new instance. The + * purpose is to enforce the immutability of the object. The methods are structured in a way to support chaining. + * After setting any values you need, call the {@link #build()} method to create the object. + */ + public static class Builder implements InterceptorListHolder { + protected InterceptorStackConfig target; + + public Builder(String name) { + target = new InterceptorStackConfig(); + target.name = name; + } + + public Builder name(String name) { + target.name = name; + return this; + } + + /** + * Add an <code>InterceptorMapping</code> object. + */ + public Builder addInterceptor(InterceptorMapping interceptor) { + target.interceptors.add(interceptor); + return this; + } + + /** + * Add a List of <code>InterceptorMapping</code> objects. + */ + public Builder addInterceptors(List<InterceptorMapping> interceptors) { + target.interceptors.addAll(interceptors); + return this; + } + + public Builder location(Location loc) { + target.location = loc; + return this; + } + + public InterceptorStackConfig build() { + embalmTarget(); + InterceptorStackConfig result = target; + target = new InterceptorStackConfig(target); + return result; + } + + protected void embalmTarget() { + target.interceptors = Collections.unmodifiableList(target.interceptors); + } + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java b/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java new file mode 100644 index 0000000..d4a3c5c --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/PackageConfig.java @@ -0,0 +1,615 @@ +/* + * 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.entities; + +import com.opensymphony.xwork2.util.location.Located; +import com.opensymphony.xwork2.util.location.Location; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.Serializable; +import java.util.*; + + +/** + * Configuration for Package. + * <p/> + * In the xml configuration file this is defined as the <code>package</code> tag. + * + * @author Rainer Hermanns + * @version $Revision$ + */ +public class PackageConfig extends Located implements Comparable, Serializable, InterceptorLocator { + + private static final Logger LOG = LogManager.getLogger(PackageConfig.class); + + protected Map<String, ActionConfig> actionConfigs; + protected Map<String, ResultConfig> globalResultConfigs; + protected Map<String, Object> interceptorConfigs; + protected Map<String, ResultTypeConfig> resultTypeConfigs; + protected List<ExceptionMappingConfig> globalExceptionMappingConfigs; + protected List<PackageConfig> parents; + protected String defaultInterceptorRef; + protected String defaultActionRef; + protected String defaultResultType; + protected String defaultClassRef; + protected String name; + protected String namespace = ""; + protected boolean isAbstract = false; + protected boolean needsRefresh; + + protected PackageConfig(String name) { + this.name = name; + actionConfigs = new LinkedHashMap<>(); + globalResultConfigs = new LinkedHashMap<>(); + interceptorConfigs = new LinkedHashMap<>(); + resultTypeConfigs = new LinkedHashMap<>(); + globalExceptionMappingConfigs = new ArrayList<>(); + parents = new ArrayList<>(); + } + + protected PackageConfig(PackageConfig orig) { + this.defaultInterceptorRef = orig.defaultInterceptorRef; + this.defaultActionRef = orig.defaultActionRef; + this.defaultResultType = orig.defaultResultType; + this.defaultClassRef = orig.defaultClassRef; + this.name = orig.name; + this.namespace = orig.namespace; + this.isAbstract = orig.isAbstract; + this.needsRefresh = orig.needsRefresh; + this.actionConfigs = new LinkedHashMap<>(orig.actionConfigs); + this.globalResultConfigs = new LinkedHashMap<>(orig.globalResultConfigs); + this.interceptorConfigs = new LinkedHashMap<>(orig.interceptorConfigs); + this.resultTypeConfigs = new LinkedHashMap<>(orig.resultTypeConfigs); + this.globalExceptionMappingConfigs = new ArrayList<>(orig.globalExceptionMappingConfigs); + this.parents = new ArrayList<>(orig.parents); + this.location = orig.location; + } + + public boolean isAbstract() { + return isAbstract; + } + + public Map<String, ActionConfig> getActionConfigs() { + return actionConfigs; + } + + /** + * returns the Map of all the ActionConfigs available in the current package. + * ActionConfigs defined in ancestor packages will be included in this Map. + * + * @return a Map of ActionConfig Objects with the action name as the key + * @see ActionConfig + */ + public Map<String, ActionConfig> getAllActionConfigs() { + Map<String, ActionConfig> retMap = new LinkedHashMap<>(); + + if (!parents.isEmpty()) { + for (PackageConfig parent : parents) { + retMap.putAll(parent.getAllActionConfigs()); + } + } + + retMap.putAll(getActionConfigs()); + + return retMap; + } + + /** + * returns the Map of all the global ResultConfigs available in the current package. + * Global ResultConfigs defined in ancestor packages will be included in this Map. + * + * @return a Map of Result Objects with the result name as the key + * @see ResultConfig + */ + public Map<String, ResultConfig> getAllGlobalResults() { + Map<String, ResultConfig> retMap = new LinkedHashMap<>(); + + if (!parents.isEmpty()) { + for (PackageConfig parentConfig : parents) { + retMap.putAll(parentConfig.getAllGlobalResults()); + } + } + + retMap.putAll(getGlobalResultConfigs()); + + return retMap; + } + + /** + * returns the Map of all InterceptorConfigs and InterceptorStackConfigs available in the current package. + * InterceptorConfigs defined in ancestor packages will be included in this Map. + * + * @return a Map of InterceptorConfig and InterceptorStackConfig Objects with the ref-name as the key + * @see InterceptorConfig + * @see InterceptorStackConfig + */ + public Map<String, Object> getAllInterceptorConfigs() { + Map<String, Object> retMap = new LinkedHashMap<>(); + + if (!parents.isEmpty()) { + for (PackageConfig parentContext : parents) { + retMap.putAll(parentContext.getAllInterceptorConfigs()); + } + } + + retMap.putAll(getInterceptorConfigs()); + + return retMap; + } + + /** + * returns the Map of all the ResultTypeConfigs available in the current package. + * ResultTypeConfigs defined in ancestor packages will be included in this Map. + * + * @return a Map of ResultTypeConfig Objects with the result type name as the key + * @see ResultTypeConfig + */ + public Map<String, ResultTypeConfig> getAllResultTypeConfigs() { + Map<String, ResultTypeConfig> retMap = new LinkedHashMap<>(); + + if (!parents.isEmpty()) { + for (PackageConfig parentContext : parents) { + retMap.putAll(parentContext.getAllResultTypeConfigs()); + } + } + + retMap.putAll(getResultTypeConfigs()); + + return retMap; + } + + /** + * returns the List of all the ExceptionMappingConfigs available in the current package. + * ExceptionMappingConfigs defined in ancestor packages will be included in this list. + * + * @return a List of ExceptionMappingConfigs Objects with the result type name as the key + * @see ExceptionMappingConfig + */ + public List<ExceptionMappingConfig> getAllExceptionMappingConfigs() { + List<ExceptionMappingConfig> allExceptionMappings = new ArrayList<>(); + + if (!parents.isEmpty()) { + for (PackageConfig parentContext : parents) { + allExceptionMappings.addAll(parentContext.getAllExceptionMappingConfigs()); + } + } + + allExceptionMappings.addAll(getGlobalExceptionMappingConfigs()); + + return allExceptionMappings; + } + + + public String getDefaultInterceptorRef() { + return defaultInterceptorRef; + } + + public String getDefaultActionRef() { + return defaultActionRef; + } + + public String getDefaultClassRef() { + if ((defaultClassRef == null) && !parents.isEmpty()) { + for (PackageConfig parent : parents) { + String parentDefault = parent.getDefaultClassRef(); + if (parentDefault != null) { + return parentDefault; + } + } + } + return defaultClassRef; + } + + /** + * Returns the default result type for this package. + */ + public String getDefaultResultType() { + return defaultResultType; + } + + /** + * gets the default interceptor-ref name. If this is not set on this PackageConfig, it searches the parent + * PackageConfigs in order until it finds one. + */ + public String getFullDefaultInterceptorRef() { + if ((defaultInterceptorRef == null) && !parents.isEmpty()) { + for (PackageConfig parent : parents) { + String parentDefault = parent.getFullDefaultInterceptorRef(); + + if (parentDefault != null) { + return parentDefault; + } + } + } + + return defaultInterceptorRef; + } + + /** + * gets the default action-ref name. If this is not set on this PackageConfig, it searches the parent + * PackageConfigs in order until it finds one. + */ + public String getFullDefaultActionRef() { + if ((defaultActionRef == null) && !parents.isEmpty()) { + for (PackageConfig parent : parents) { + String parentDefault = parent.getFullDefaultActionRef(); + + if (parentDefault != null) { + return parentDefault; + } + } + } + return defaultActionRef; + } + + /** + * Returns the default result type for this package. + * <p/> + * If there is no default result type, but this package has parents - we will try to + * look up the default result type of a parent. + */ + public String getFullDefaultResultType() { + if ((defaultResultType == null) && !parents.isEmpty()) { + for (PackageConfig parent : parents) { + String parentDefault = parent.getFullDefaultResultType(); + + if (parentDefault != null) { + return parentDefault; + } + } + } + + return defaultResultType; + } + + /** + * gets the global ResultConfigs local to this package + * + * @return a Map of ResultConfig objects keyed by result name + * @see ResultConfig + */ + public Map<String, ResultConfig> getGlobalResultConfigs() { + return globalResultConfigs; + } + + /** + * gets the InterceptorConfigs and InterceptorStackConfigs local to this package + * + * @return a Map of InterceptorConfig and InterceptorStackConfig objects keyed by ref-name + * @see InterceptorConfig + * @see InterceptorStackConfig + */ + public Map<String, Object> getInterceptorConfigs() { + return interceptorConfigs; + } + + public String getName() { + return name; + } + + public String getNamespace() { + return namespace; + } + + public List<PackageConfig> getParents() { + return new ArrayList<>(parents); + } + + /** + * gets the ResultTypeConfigs local to this package + * + * @return a Map of ResultTypeConfig objects keyed by result name + * @see ResultTypeConfig + */ + public Map<String, ResultTypeConfig> getResultTypeConfigs() { + return resultTypeConfigs; + } + + + public boolean isNeedsRefresh() { + return needsRefresh; + } + + /** + * gets the ExceptionMappingConfigs local to this package + * + * @return a Map of ExceptionMappingConfig objects keyed by result name + * @see ExceptionMappingConfig + */ + public List<ExceptionMappingConfig> getGlobalExceptionMappingConfigs() { + return globalExceptionMappingConfigs; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof PackageConfig)) { + return false; + } + + final PackageConfig packageConfig = (PackageConfig) o; + + if (isAbstract != packageConfig.isAbstract) { + return false; + } + + if ((actionConfigs != null) ? (!actionConfigs.equals(packageConfig.actionConfigs)) : (packageConfig.actionConfigs != null)) { + return false; + } + + if ((defaultResultType != null) ? (!defaultResultType.equals(packageConfig.defaultResultType)) : (packageConfig.defaultResultType != null)) { + return false; + } + + if ((defaultClassRef != null) ? (!defaultClassRef.equals(packageConfig.defaultClassRef)) : (packageConfig.defaultClassRef != null)) { + return false; + } + + if ((globalResultConfigs != null) ? (!globalResultConfigs.equals(packageConfig.globalResultConfigs)) : (packageConfig.globalResultConfigs != null)) { + return false; + } + + if ((interceptorConfigs != null) ? (!interceptorConfigs.equals(packageConfig.interceptorConfigs)) : (packageConfig.interceptorConfigs != null)) { + return false; + } + + if ((name != null) ? (!name.equals(packageConfig.name)) : (packageConfig.name != null)) { + return false; + } + + if ((namespace != null) ? (!namespace.equals(packageConfig.namespace)) : (packageConfig.namespace != null)) { + return false; + } + + if ((parents != null) ? (!parents.equals(packageConfig.parents)) : (packageConfig.parents != null)) { + return false; + } + + if ((resultTypeConfigs != null) ? (!resultTypeConfigs.equals(packageConfig.resultTypeConfigs)) : (packageConfig.resultTypeConfigs != null)) { + return false; + } + + if ((globalExceptionMappingConfigs != null) ? (!globalExceptionMappingConfigs.equals(packageConfig.globalExceptionMappingConfigs)) : (packageConfig.globalExceptionMappingConfigs != null)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result; + result = ((name != null) ? name.hashCode() : 0); + result = (29 * result) + ((parents != null) ? parents.hashCode() : 0); + result = (29 * result) + ((actionConfigs != null) ? actionConfigs.hashCode() : 0); + result = (29 * result) + ((globalResultConfigs != null) ? globalResultConfigs.hashCode() : 0); + result = (29 * result) + ((interceptorConfigs != null) ? interceptorConfigs.hashCode() : 0); + result = (29 * result) + ((resultTypeConfigs != null) ? resultTypeConfigs.hashCode() : 0); + result = (29 * result) + ((globalExceptionMappingConfigs != null) ? globalExceptionMappingConfigs.hashCode() : 0); + result = (29 * result) + ((defaultResultType != null) ? defaultResultType.hashCode() : 0); + result = (29 * result) + ((defaultClassRef != null) ? defaultClassRef.hashCode() : 0); + result = (29 * result) + ((namespace != null) ? namespace.hashCode() : 0); + result = (29 * result) + (isAbstract ? 1 : 0); + + return result; + } + + @Override + public String toString() { + return "PackageConfig: [" + name + "] for namespace [" + namespace + "] with parents [" + parents + "]"; + } + + public int compareTo(Object o) { + PackageConfig other = (PackageConfig) o; + String full = namespace + "!" + name; + String otherFull = other.namespace + "!" + other.name; + + // note, this isn't perfect (could come from different parents), but it is "good enough" + return full.compareTo(otherFull); + } + + public Object getInterceptorConfig(String name) { + return getAllInterceptorConfigs().get(name); + } + + /** + * The builder for this object. An instance of this object is the only way to construct a new instance. The + * purpose is to enforce the immutability of the object. The methods are structured in a way to support chaining. + * After setting any values you need, call the {@link #build()} method to create the object. + */ + public static class Builder implements InterceptorLocator { + + protected PackageConfig target; + private boolean strictDMI; + + public Builder(String name) { + target = new PackageConfig(name); + } + + public Builder(PackageConfig config) { + target = new PackageConfig(config); + } + + public Builder name(String name) { + target.name = name; + return this; + } + + public Builder isAbstract(boolean isAbstract) { + target.isAbstract = isAbstract; + return this; + } + + public Builder defaultInterceptorRef(String name) { + target.defaultInterceptorRef = name; + return this; + } + + public Builder defaultActionRef(String name) { + target.defaultActionRef = name; + return this; + } + + public Builder defaultClassRef(String defaultClassRef) { + target.defaultClassRef = defaultClassRef; + return this; + } + + /** + * sets the default Result type for this package + * + * @param defaultResultType + */ + public Builder defaultResultType(String defaultResultType) { + target.defaultResultType = defaultResultType; + return this; + } + + public Builder namespace(String namespace) { + if (namespace == null) { + target.namespace = ""; + } else { + target.namespace = namespace; + } + return this; + } + + public Builder needsRefresh(boolean needsRefresh) { + target.needsRefresh = needsRefresh; + return this; + } + + public Builder addActionConfig(String name, ActionConfig action) { + target.actionConfigs.put(name, action); + return this; + } + + public Builder addParents(List<PackageConfig> parents) { + for (PackageConfig config : parents) { + addParent(config); + } + return this; + } + + public Builder addGlobalResultConfig(ResultConfig resultConfig) { + target.globalResultConfigs.put(resultConfig.getName(), resultConfig); + return this; + } + + public Builder addGlobalResultConfigs(Map<String, ResultConfig> resultConfigs) { + target.globalResultConfigs.putAll(resultConfigs); + return this; + } + + public Builder addExceptionMappingConfig(ExceptionMappingConfig exceptionMappingConfig) { + target.globalExceptionMappingConfigs.add(exceptionMappingConfig); + return this; + } + + public Builder addGlobalExceptionMappingConfigs(List<ExceptionMappingConfig> exceptionMappingConfigs) { + target.globalExceptionMappingConfigs.addAll(exceptionMappingConfigs); + return this; + } + + public Builder addInterceptorConfig(InterceptorConfig config) { + target.interceptorConfigs.put(config.getName(), config); + return this; + } + + public Builder addInterceptorStackConfig(InterceptorStackConfig config) { + target.interceptorConfigs.put(config.getName(), config); + return this; + } + + public Builder addParent(PackageConfig parent) { + target.parents.add(0, parent); + return this; + } + + public Builder addResultTypeConfig(ResultTypeConfig config) { + target.resultTypeConfigs.put(config.getName(), config); + return this; + } + + public Builder location(Location loc) { + target.location = loc; + return this; + } + + public boolean isNeedsRefresh() { + return target.needsRefresh; + } + + public String getDefaultClassRef() { + return target.defaultClassRef; + } + + public String getName() { + return target.name; + } + + public String getNamespace() { + return target.namespace; + } + + public String getFullDefaultResultType() { + return target.getFullDefaultResultType(); + } + + public ResultTypeConfig getResultType(String type) { + return target.getAllResultTypeConfigs().get(type); + } + + public Object getInterceptorConfig(String name) { + return target.getAllInterceptorConfigs().get(name); + } + + public Builder strictMethodInvocation(boolean strict) { + strictDMI = strict; + return this; + } + + public boolean isStrictMethodInvocation() { + return strictDMI; + } + + public PackageConfig build() { + embalmTarget(); + PackageConfig result = target; + target = new PackageConfig(result); + return result; + } + + protected void embalmTarget() { + target.actionConfigs = Collections.unmodifiableMap(target.actionConfigs); + target.globalResultConfigs = Collections.unmodifiableMap(target.globalResultConfigs); + target.interceptorConfigs = Collections.unmodifiableMap(target.interceptorConfigs); + target.resultTypeConfigs = Collections.unmodifiableMap(target.resultTypeConfigs); + target.globalExceptionMappingConfigs = Collections.unmodifiableList(target.globalExceptionMappingConfigs); + target.parents = Collections.unmodifiableList(target.parents); + } + + @Override + public String toString() { + return "[BUILDER] " + target.toString(); + } + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/entities/Parameterizable.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/Parameterizable.java b/core/src/main/java/com/opensymphony/xwork2/config/entities/Parameterizable.java new file mode 100644 index 0000000..42b6cb3 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/Parameterizable.java @@ -0,0 +1,40 @@ +/* + * 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.entities; + +import java.util.Map; + +/** + * <!-- START SNIPPET: javadoc --> + * <p/> + * Actions implementing Parameterizable will receive a map of the static parameters defined in the action + * configuration. + * <p/> + * <p/> The {@link com.opensymphony.xwork2.interceptor.StaticParametersInterceptor} must be in the action's interceptor + * queue for this to work. + * <p/> + * <!-- END SNIPPET: javadoc --> + * + * @author Jason Carreira + */ +public interface Parameterizable { + + public void addParam(String name, String value); + + void setParams(Map<String, String> params); + + Map<String, String> getParams(); +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/entities/ResultConfig.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/ResultConfig.java b/core/src/main/java/com/opensymphony/xwork2/config/entities/ResultConfig.java new file mode 100644 index 0000000..b9ed588 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/ResultConfig.java @@ -0,0 +1,159 @@ +/* + * 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.entities; + +import com.opensymphony.xwork2.util.location.Located; +import com.opensymphony.xwork2.util.location.Location; + +import java.io.Serializable; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + + +/** + * Configuration for Result. + * <p/> + * In the xml configuration file this is defined as the <code>result</code> tag. + * + * @author Mike + */ +public class ResultConfig extends Located implements Serializable { + + protected Map<String,String> params; + protected String className; + protected String name; + + protected ResultConfig(String name, String className) { + this.name = name; + this.className = className; + params = new LinkedHashMap<>(); + } + + protected ResultConfig(ResultConfig orig) { + this.params = orig.params; + this.name = orig.name; + this.className = orig.className; + this.location = orig.location; + } + + public String getClassName() { + return className; + } + + public String getName() { + return name; + } + + public Map<String,String> getParams() { + return params; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (!(o instanceof ResultConfig)) { + return false; + } + + final ResultConfig resultConfig = (ResultConfig) o; + + if ((className != null) ? (!className.equals(resultConfig.className)) : (resultConfig.className != null)) { + return false; + } + + if ((name != null) ? (!name.equals(resultConfig.name)) : (resultConfig.name != null)) { + return false; + } + + if ((params != null) ? (!params.equals(resultConfig.params)) : (resultConfig.params != null)) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result; + result = ((name != null) ? name.hashCode() : 0); + result = (29 * result) + ((className != null) ? className.hashCode() : 0); + result = (29 * result) + ((params != null) ? params.hashCode() : 0); + + return result; + } + + @Override + public String toString() { + return "ResultConfig: [" + name + "] => [" + className + "] with params " + params; + } + + /** + * The builder for this object. An instance of this object is the only way to construct a new instance. The + * purpose is to enforce the immutability of the object. The methods are structured in a way to support chaining. + * After setting any values you need, call the {@link #build()} method to create the object. + */ + public static final class Builder { + protected ResultConfig target; + + public Builder(String name, String className) { + target = new ResultConfig(name, className); + } + + public Builder(ResultConfig orig) { + target = new ResultConfig(orig); + } + + public Builder name(String name) { + target.name = name; + return this; + } + + public Builder className(String name) { + target.className = name; + return this; + } + + public Builder addParam(String name, String value) { + target.params.put(name, value); + return this; + } + + public Builder addParams(Map<String,String> params) { + target.params.putAll(params); + return this; + } + + public Builder location(Location loc) { + target.location = loc; + return this; + } + + public ResultConfig build() { + embalmTarget(); + ResultConfig result = target; + target = new ResultConfig(target); + return result; + } + + protected void embalmTarget() { + target.params = Collections.unmodifiableMap(target.params); + } + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/entities/ResultTypeConfig.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/ResultTypeConfig.java b/core/src/main/java/com/opensymphony/xwork2/config/entities/ResultTypeConfig.java new file mode 100644 index 0000000..96ff4ff --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/ResultTypeConfig.java @@ -0,0 +1,170 @@ +/* + * 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.entities; + +import com.opensymphony.xwork2.util.location.Located; +import com.opensymphony.xwork2.util.location.Location; + +import java.io.Serializable; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + + +/** + * Configuration class for result types. + * <p/> + * In the xml configuration file this is defined as the <code>result-type</code> tag. + * + * @author Mike + * @author Rainer Hermanns + * @author Neo + */ +public class ResultTypeConfig extends Located implements Serializable { + + protected String className; + protected String name; + protected String defaultResultParam; + protected Map<String,String> params; + + protected ResultTypeConfig(String name, String className) { + this.name = name; + this.className = className; + params = new LinkedHashMap<>(); + } + + protected ResultTypeConfig(ResultTypeConfig orig) { + this.name = orig.name; + this.className = orig.className; + this.defaultResultParam = orig.defaultResultParam; + this.params = orig.params; + this.location = orig.location; + } + + public void setDefaultResultParam(String defaultResultParam) { + this.defaultResultParam = defaultResultParam; + } + + public String getDefaultResultParam() { + return this.defaultResultParam; + } + + /** + * @deprecated Since 2.1, use {@link #getClassName()} instead + */ + @Deprecated public String getClazz() { + return className; + } + + public String getClassName() { + return className; + } + + public String getName() { + return name; + } + + public Map<String,String> getParams() { + return this.params; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final ResultTypeConfig that = (ResultTypeConfig) o; + + if (className != null ? !className.equals(that.className) : that.className != null) return false; + if (name != null ? !name.equals(that.name) : that.name != null) return false; + if (params != null ? !params.equals(that.params) : that.params != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result; + result = (className != null ? className.hashCode() : 0); + result = 29 * result + (name != null ? name.hashCode() : 0); + result = 29 * result + (params != null ? params.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "ResultTypeConfig: [" + name + "] => [" + className + "] " + + "with defaultParam [" + defaultResultParam + "] with params " + params; + } + + /** + * The builder for this object. An instance of this object is the only way to construct a new instance. The + * purpose is to enforce the immutability of the object. The methods are structured in a way to support chaining. + * After setting any values you need, call the {@link #build()} method to create the object. + */ + public static final class Builder { + protected ResultTypeConfig target; + + public Builder(String name, String className) { + target = new ResultTypeConfig(name, className); + } + + public Builder(ResultTypeConfig orig) { + target = new ResultTypeConfig(orig); + } + + public Builder name(String name) { + target.name = name; + return this; + } + + public Builder className(String name) { + target.className = name; + return this; + } + + public Builder addParam(String name, String value) { + target.params.put(name, value); + return this; + } + + public Builder addParams(Map<String,String> params) { + target.params.putAll(params); + return this; + } + + public Builder defaultResultParam(String defaultResultParam) { + target.defaultResultParam = defaultResultParam; + return this; + } + + public Builder location(Location loc) { + target.location = loc; + return this; + } + + public ResultTypeConfig build() { + embalmTarget(); + ResultTypeConfig result = target; + target = new ResultTypeConfig(target); + return result; + } + + protected void embalmTarget() { + target.params = Collections.unmodifiableMap(target.params); + } + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/entities/UnknownHandlerConfig.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/UnknownHandlerConfig.java b/core/src/main/java/com/opensymphony/xwork2/config/entities/UnknownHandlerConfig.java new file mode 100644 index 0000000..3d6692f --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/UnknownHandlerConfig.java @@ -0,0 +1,43 @@ +/* + * 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.entities; + +import com.opensymphony.xwork2.util.location.Located; +import com.opensymphony.xwork2.util.location.Location; + +public class UnknownHandlerConfig extends Located { + + private String name; + + public UnknownHandlerConfig(String name, Location location) { + this.name = name; + this.location = location; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String toString() { + return "UnknownHandlerConfig: [" + name + "]"; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/entities/package.html ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/entities/package.html b/core/src/main/java/com/opensymphony/xwork2/config/entities/package.html new file mode 100644 index 0000000..d05a357 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/config/entities/package.html @@ -0,0 +1,18 @@ +<body> + +<p> +Configuration entity classes. All objects ending in "Config" are immutable and must be constructed using +their inner "Builder" class. For example, a PackageConfig object can be created via: +</p> +<pre> + PackageConfig config = new PackageConfig.Builder("myPackage").build(); +</pre> +<p> + The methods on the builder object are chainable to support constructions like this: +</p> +<pre> + ResultConfig config = new ResultConfig.Builder("success", "myapp.MyResult") + .addParam("location", "/foo.jsp") + .build(); +</pre> +</body> http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/impl/AbstractMatcher.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/impl/AbstractMatcher.java b/core/src/main/java/com/opensymphony/xwork2/config/impl/AbstractMatcher.java new file mode 100644 index 0000000..5deb7cb --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/config/impl/AbstractMatcher.java @@ -0,0 +1,267 @@ +/* + * $Id$ + * + * Copyright 2003,2004 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.impl; + +import com.opensymphony.xwork2.util.PatternMatcher; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.Serializable; +import java.util.*; + +/** + * <p> Matches patterns against pre-compiled wildcard expressions pulled from + * target objects. It uses the wildcard matcher from the Apache Cocoon + * project. Patterns will be matched in the order they were added. The first + * match wins, so more specific patterns should be defined before less specific + * patterns. + * + * @since 2.1 + */ +public abstract class AbstractMatcher<E> implements Serializable { + /** + * <p> The logging instance </p> + */ + private static final Logger log = LogManager.getLogger(AbstractMatcher.class); + + /** + * <p> Handles all wildcard pattern matching. </p> + */ + PatternMatcher<Object> wildcard; + + /** + * <p> The compiled patterns and their associated target objects </p> + */ + List<Mapping<E>> compiledPatterns = new ArrayList<>(); + ; + + public AbstractMatcher(PatternMatcher<?> helper) { + this.wildcard = (PatternMatcher<Object>) helper; + } + + /** + * <p> + * Finds and precompiles the wildcard patterns. Patterns will be evaluated + * in the order they were added. Only patterns that actually contain a + * wildcard will be compiled. + * </p> + * + * <p> + * Patterns can optionally be matched "loosely". When the end of the pattern + * matches \*[^*]\*$ (wildcard, no wildcard, wildcard), if the pattern + * fails, it is also matched as if the last two characters didn't exist. The + * goal is to support the legacy "*!*" syntax, where the "!*" is optional. + * </p> + * + * @param name The pattern + * @param target The object to associate with the pattern + * @param looseMatch + * To loosely match wildcards or not + */ + public void addPattern(String name, E target, boolean looseMatch) { + + Object pattern; + + if (!wildcard.isLiteral(name)) { + if (looseMatch && (name.length() > 0) && (name.charAt(0) == '/')) { + name = name.substring(1); + } + + log.debug("Compiling pattern '{}'", name); + + pattern = wildcard.compilePattern(name); + compiledPatterns.add(new Mapping<E>(name, pattern, target)); + + if (looseMatch) { + int lastStar = name.lastIndexOf('*'); + if (lastStar > 1 && lastStar == name.length() - 1) { + if (name.charAt(lastStar - 1) != '*') { + pattern = wildcard.compilePattern(name.substring(0, lastStar - 1)); + compiledPatterns.add(new Mapping<E>(name, pattern, target)); + } + } + } + } + } + + public void freeze() { + compiledPatterns = Collections.unmodifiableList(new ArrayList<Mapping<E>>()); + } + + /** + * <p> Matches the path against the compiled wildcard patterns. </p> + * + * @param potentialMatch The portion of the request URI for selecting a config. + * @return The action config if matched, else null + */ + public E match(String potentialMatch) { + E config = null; + + if (compiledPatterns.size() > 0) { + log.debug("Attempting to match '{}' to a wildcard pattern, {} available", potentialMatch, compiledPatterns.size()); + + Map<String,String> vars = new LinkedHashMap<String,String>(); + for (Mapping<E> m : compiledPatterns) { + if (wildcard.match(vars, potentialMatch, m.getPattern())) { + log.debug("Value matches pattern '{}'", m.getOriginalPattern()); + config = convert(potentialMatch, m.getTarget(), vars); + break; + } + } + } + + return config; + } + + /** + * <p> Clones the target object and its children, replacing various + * properties with the values of the wildcard-matched strings. </p> + * + * @param path The requested path + * @param orig The original object + * @param vars A Map of wildcard-matched strings + * @return A cloned object with appropriate properties replaced with + * wildcard-matched values + */ + protected abstract E convert(String path, E orig, Map<String, String> vars); + + /** + * <p> Replaces parameter values + * </p> + * + * @param orig The original parameters with placeholder values + * @param vars A Map of wildcard-matched strings + */ + protected Map<String,String> replaceParameters(Map<String, String> orig, Map<String,String> vars) { + Map<String, String> map = new LinkedHashMap<>(); + + //this will set the group index references, like {1} + for (String key : orig.keySet()) { + map.put(key, convertParam(orig.get(key), vars)); + } + + //the values map will contain entries like name->"Lex Luthor" and 1->"Lex Luthor" + //now add the non-numeric values + for (String key: vars.keySet()) { + if (!NumberUtils.isNumber(key)) { + map.put(key, vars.get(key)); + } + } + + return map; + } + + /** + * <p> Inserts into a value wildcard-matched strings where specified + * with the {x} syntax. If a wildcard-matched value isn't found, the + * replacement token is turned into an empty string. + * </p> + * + * @param val The value to convert + * @param vars A Map of wildcard-matched strings + * @return The new value + */ + protected String convertParam(String val, Map<String, String> vars) { + if (val == null) { + return null; + } + + int len = val.length(); + StringBuilder ret = new StringBuilder(); + char c; + String varVal; + for (int x=0; x<len; x++) { + c = val.charAt(x); + if (x < len - 2 && + c == '{' && '}' == val.charAt(x+2)) { + varVal = (String)vars.get(String.valueOf(val.charAt(x + 1))); + if (varVal != null) { + ret.append(varVal); + } + x += 2; + } else { + ret.append(c); + } + } + + return ret.toString(); + } + + /** + * <p> Stores a compiled wildcard pattern and the object it came + * from. </p> + */ + private static class Mapping<E> implements Serializable { + /** + * <p> The original pattern. </p> + */ + private String original; + + + /** + * <p> The compiled pattern. </p> + */ + private Object pattern; + + /** + * <p> The original object. </p> + */ + private E config; + + /** + * <p> Contructs a read-only Mapping instance. </p> + * + * @param original The original pattern + * @param pattern The compiled pattern + * @param config The original object + */ + public Mapping(String original, Object pattern, E config) { + this.original = original; + this.pattern = pattern; + this.config = config; + } + + /** + * <p> Gets the compiled wildcard pattern. </p> + * + * @return The compiled pattern + */ + public Object getPattern() { + return this.pattern; + } + + /** + * <p> Gets the object that contains the pattern. </p> + * + * @return The associated object + */ + public E getTarget() { + return this.config; + } + + /** + * <p> Gets the original wildcard pattern. </p> + * + * @return The original pattern + */ + public String getOriginalPattern() { + return this.original; + } + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/impl/ActionConfigMatcher.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/impl/ActionConfigMatcher.java b/core/src/main/java/com/opensymphony/xwork2/config/impl/ActionConfigMatcher.java new file mode 100644 index 0000000..e282c1a --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/config/impl/ActionConfigMatcher.java @@ -0,0 +1,152 @@ +/* + * $Id$ + * + * Copyright 2003,2004 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.impl; + +import com.opensymphony.xwork2.config.entities.ActionConfig; +import com.opensymphony.xwork2.config.entities.ExceptionMappingConfig; +import com.opensymphony.xwork2.config.entities.ResultConfig; +import com.opensymphony.xwork2.util.PatternMatcher; +import com.opensymphony.xwork2.util.WildcardHelper; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * <p> Matches paths against pre-compiled wildcard expressions pulled from + * action configs. It uses the wildcard matcher from the Apache Cocoon + * project. Patterns will be matched in the order they exist in the + * config file. The first match wins, so more specific patterns should be + * defined before less specific patterns. + */ +public class ActionConfigMatcher extends AbstractMatcher<ActionConfig> implements Serializable { + + /** + * <p> Finds and precompiles the wildcard patterns from the ActionConfig + * "path" attributes. ActionConfig's will be evaluated in the order they + * exist in the config file. Only paths that actually contain a + * wildcard will be compiled. Patterns will matched strictly.</p> + * + * @param configs An array of ActionConfig's to process + * @deprecated Since 2.1, use {@link #ActionConfigMatcher(PatternMatcher, Map, boolean)} instead + */ + @Deprecated public ActionConfigMatcher(Map<String, ActionConfig> configs) { + this(configs, false); + } + + /** + * <p> Finds and precompiles the wildcard patterns from the ActionConfig + * "path" attributes. ActionConfig's will be evaluated in the order they + * exist in the config file. Only paths that actually contain a + * wildcard will be compiled. </p> + * + * <p>Patterns can optionally be matched "loosely". When + * the end of the pattern matches \*[^*]\*$ (wildcard, no wildcard, + * wildcard), if the pattern fails, it is also matched as if the + * last two characters didn't exist. The goal is to support the + * legacy "*!*" syntax, where the "!*" is optional.</p> + * + * @param configs An array of ActionConfig's to process + * @param looseMatch To loosely match wildcards or not + * @deprecated Since 2.1, use {@link #ActionConfigMatcher(PatternMatcher, Map, boolean)} instead + */ + @Deprecated public ActionConfigMatcher(Map<String, ActionConfig> configs, + boolean looseMatch) { + + this(new WildcardHelper(), configs, looseMatch); + } + + /** + * <p> Finds and precompiles the wildcard patterns from the ActionConfig + * "path" attributes. ActionConfig's will be evaluated in the order they + * exist in the config file. Only paths that actually contain a + * wildcard will be compiled. </p> + * + * <p>Patterns can optionally be matched "loosely". When + * the end of the pattern matches \*[^*]\*$ (wildcard, no wildcard, + * wildcard), if the pattern fails, it is also matched as if the + * last two characters didn't exist. The goal is to support the + * legacy "*!*" syntax, where the "!*" is optional.</p> + * + * @param configs An array of ActionConfig's to process + * @param looseMatch To loosely match wildcards or not + */ + public ActionConfigMatcher(PatternMatcher<?> patternMatcher, + Map<String, ActionConfig> configs, + boolean looseMatch) { + super(patternMatcher); + for (String name : configs.keySet()) { + addPattern(name, configs.get(name), looseMatch); + } + } + + /** + * <p> Clones the ActionConfig and its children, replacing various + * properties with the values of the wildcard-matched strings. </p> + * + * @param path The requested path + * @param orig The original ActionConfig + * @param vars A Map of wildcard-matched strings + * @return A cloned ActionConfig with appropriate properties replaced with + * wildcard-matched values + */ + @Override public ActionConfig convert(String path, ActionConfig orig, + Map<String, String> vars) { + + String methodName = convertParam(orig.getMethodName(), vars); + if (!orig.isAllowedMethod(methodName)) { + return null; + } + + String className = convertParam(orig.getClassName(), vars); + String pkgName = convertParam(orig.getPackageName(), vars); + + Map<String,String> params = replaceParameters(orig.getParams(), vars); + + Map<String, ResultConfig> results = new LinkedHashMap<>(); + for (String name : orig.getResults().keySet()) { + ResultConfig result = orig.getResults().get(name); + name = convertParam(name, vars); + ResultConfig r = new ResultConfig.Builder(name, convertParam(result.getClassName(), vars)) + .addParams(replaceParameters(result.getParams(), vars)) + .build(); + results.put(name, r); + } + + List<ExceptionMappingConfig> exs = new ArrayList<ExceptionMappingConfig>(); + for (ExceptionMappingConfig ex : orig.getExceptionMappings()) { + String name = convertParam(ex.getName(), vars); + String exClassName = convertParam(ex.getExceptionClassName(), vars); + String exResult = convertParam(ex.getResult(), vars); + Map<String,String> exParams = replaceParameters(ex.getParams(), vars); + ExceptionMappingConfig e = new ExceptionMappingConfig.Builder(name, exClassName, exResult).addParams(exParams).build(); + exs.add(e); + } + + return new ActionConfig.Builder(pkgName, orig.getName(), className) + .methodName(methodName) + .addParams(params) + .addResultConfigs(results) + .addInterceptors(orig.getInterceptors()) + .addExceptionMappings(exs) + .location(orig.getLocation()) + .build(); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java new file mode 100644 index 0000000..7d5e5cc --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/config/impl/DefaultConfiguration.java @@ -0,0 +1,514 @@ +/* + * 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.impl; + +import com.opensymphony.xwork2.*; +import com.opensymphony.xwork2.config.*; +import com.opensymphony.xwork2.config.entities.*; +import com.opensymphony.xwork2.config.providers.InterceptorBuilder; +import com.opensymphony.xwork2.conversion.*; +import com.opensymphony.xwork2.conversion.impl.*; +import com.opensymphony.xwork2.factory.*; +import com.opensymphony.xwork2.inject.*; +import com.opensymphony.xwork2.ognl.OgnlReflectionProvider; +import com.opensymphony.xwork2.ognl.OgnlUtil; +import com.opensymphony.xwork2.ognl.OgnlValueStackFactory; +import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor; +import com.opensymphony.xwork2.util.*; +import com.opensymphony.xwork2.util.fs.DefaultFileManager; +import com.opensymphony.xwork2.util.fs.DefaultFileManagerFactory; +import com.opensymphony.xwork2.util.location.LocatableProperties; +import com.opensymphony.xwork2.util.reflection.ReflectionProvider; +import ognl.PropertyAccessor; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.*; + + +/** + * DefaultConfiguration + * + * @author Jason Carreira + * Created Feb 24, 2003 7:38:06 AM + */ +public class DefaultConfiguration implements Configuration { + + protected static final Logger LOG = LogManager.getLogger(DefaultConfiguration.class); + + + // Programmatic Action Configurations + protected Map<String, PackageConfig> packageContexts = new LinkedHashMap<>(); + protected RuntimeConfiguration runtimeConfiguration; + protected Container container; + protected String defaultFrameworkBeanName; + protected Set<String> loadedFileNames = new TreeSet<>(); + protected List<UnknownHandlerConfig> unknownHandlerStack; + + + ObjectFactory objectFactory; + + public DefaultConfiguration() { + this("xwork"); + } + + public DefaultConfiguration(String defaultBeanName) { + this.defaultFrameworkBeanName = defaultBeanName; + } + + + public PackageConfig getPackageConfig(String name) { + return packageContexts.get(name); + } + + public List<UnknownHandlerConfig> getUnknownHandlerStack() { + return unknownHandlerStack; + } + + public void setUnknownHandlerStack(List<UnknownHandlerConfig> unknownHandlerStack) { + this.unknownHandlerStack = unknownHandlerStack; + } + + public Set<String> getPackageConfigNames() { + return packageContexts.keySet(); + } + + public Map<String, PackageConfig> getPackageConfigs() { + return packageContexts; + } + + public Set<String> getLoadedFileNames() { + return loadedFileNames; + } + + public RuntimeConfiguration getRuntimeConfiguration() { + return runtimeConfiguration; + } + + /** + * @return the container + */ + public Container getContainer() { + return container; + } + + public void addPackageConfig(String name, PackageConfig packageContext) { + PackageConfig check = packageContexts.get(name); + if (check != null) { + if (check.getLocation() != null && packageContext.getLocation() != null + && check.getLocation().equals(packageContext.getLocation())) { + LOG.debug("The package name '{}' is already been loaded by the same location and could be removed: {}", + name, packageContext.getLocation()); + } else { + throw new ConfigurationException("The package name '" + name + + "' at location "+packageContext.getLocation() + + " is already been used by another package at location " + check.getLocation(), + packageContext); + } + } + packageContexts.put(name, packageContext); + } + + public PackageConfig removePackageConfig(String packageName) { + return packageContexts.remove(packageName); + } + + /** + * Allows the configuration to clean up any resources used + */ + public void destroy() { + packageContexts.clear(); + loadedFileNames.clear(); + } + + public void rebuildRuntimeConfiguration() { + runtimeConfiguration = buildRuntimeConfiguration(); + } + + /** + * Calls the ConfigurationProviderFactory.getConfig() to tell it to reload the configuration and then calls + * buildRuntimeConfiguration(). + * + * @throws ConfigurationException + */ + public synchronized void reload(List<ConfigurationProvider> providers) throws ConfigurationException { + + // Silly copy necessary due to lack of ability to cast generic lists + List<ContainerProvider> contProviders = new ArrayList<>(); + contProviders.addAll(providers); + + reloadContainer(contProviders); + } + + /** + * Calls the ConfigurationProviderFactory.getConfig() to tell it to reload the configuration and then calls + * buildRuntimeConfiguration(). + * + * @throws ConfigurationException + */ + public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException { + packageContexts.clear(); + loadedFileNames.clear(); + List<PackageProvider> packageProviders = new ArrayList<>(); + + ContainerProperties props = new ContainerProperties(); + ContainerBuilder builder = new ContainerBuilder(); + Container bootstrap = createBootstrapContainer(providers); + for (final ContainerProvider containerProvider : providers) + { + bootstrap.inject(containerProvider); + containerProvider.init(this); + containerProvider.register(builder, props); + } + props.setConstants(builder); + + builder.factory(Configuration.class, new Factory<Configuration>() { + public Configuration create(Context context) throws Exception { + return DefaultConfiguration.this; + } + }); + + ActionContext oldContext = ActionContext.getContext(); + try { + // Set the bootstrap container for the purposes of factory creation + + setContext(bootstrap); + container = builder.create(false); + setContext(container); + objectFactory = container.getInstance(ObjectFactory.class); + + // Process the configuration providers first + for (final ContainerProvider containerProvider : providers) + { + if (containerProvider instanceof PackageProvider) { + container.inject(containerProvider); + ((PackageProvider)containerProvider).loadPackages(); + packageProviders.add((PackageProvider)containerProvider); + } + } + + // Then process any package providers from the plugins + Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class); + for (String name : packageProviderNames) { + PackageProvider provider = container.getInstance(PackageProvider.class, name); + provider.init(this); + provider.loadPackages(); + packageProviders.add(provider); + } + + rebuildRuntimeConfiguration(); + } finally { + if (oldContext == null) { + ActionContext.setContext(null); + } + } + return packageProviders; + } + + protected ActionContext setContext(Container cont) { + ActionContext context = ActionContext.getContext(); + if (context == null) { + ValueStack vs = cont.getInstance(ValueStackFactory.class).createValueStack(); + context = new ActionContext(vs.getContext()); + ActionContext.setContext(context); + } + return context; + } + + protected Container createBootstrapContainer(List<ContainerProvider> providers) { + ContainerBuilder builder = new ContainerBuilder(); + boolean fmFactoryRegistered = false; + for (ContainerProvider provider : providers) { + if (provider instanceof FileManagerProvider) { + provider.register(builder, null); + } + if (provider instanceof FileManagerFactoryProvider) { + provider.register(builder, null); + fmFactoryRegistered = true; + } + } + builder.factory(ObjectFactory.class, Scope.SINGLETON); + builder.factory(ActionFactory.class, DefaultActionFactory.class, Scope.SINGLETON); + builder.factory(ResultFactory.class, DefaultResultFactory.class, Scope.SINGLETON); + builder.factory(InterceptorFactory.class, DefaultInterceptorFactory.class, Scope.SINGLETON); + builder.factory(com.opensymphony.xwork2.factory.ValidatorFactory.class, com.opensymphony.xwork2.factory.DefaultValidatorFactory.class, Scope.SINGLETON); + builder.factory(ConverterFactory.class, DefaultConverterFactory.class, Scope.SINGLETON); + builder.factory(UnknownHandlerFactory.class, DefaultUnknownHandlerFactory.class, Scope.SINGLETON); + + builder.factory(FileManager.class, "system", DefaultFileManager.class, Scope.SINGLETON); + if (!fmFactoryRegistered) { + builder.factory(FileManagerFactory.class, DefaultFileManagerFactory.class, Scope.SINGLETON); + } + builder.factory(ReflectionProvider.class, OgnlReflectionProvider.class, Scope.SINGLETON); + builder.factory(ValueStackFactory.class, OgnlValueStackFactory.class, Scope.SINGLETON); + + builder.factory(XWorkConverter.class, Scope.SINGLETON); + builder.factory(ConversionPropertiesProcessor.class, DefaultConversionPropertiesProcessor.class, Scope.SINGLETON); + builder.factory(ConversionFileProcessor.class, DefaultConversionFileProcessor.class, Scope.SINGLETON); + builder.factory(ConversionAnnotationProcessor.class, DefaultConversionAnnotationProcessor.class, Scope.SINGLETON); + builder.factory(TypeConverterCreator.class, DefaultTypeConverterCreator.class, Scope.SINGLETON); + builder.factory(TypeConverterHolder.class, DefaultTypeConverterHolder.class, Scope.SINGLETON); + + builder.factory(XWorkBasicConverter.class, Scope.SINGLETON); + builder.factory(TypeConverter.class, XWorkConstants.COLLECTION_CONVERTER, CollectionConverter.class, Scope.SINGLETON); + builder.factory(TypeConverter.class, XWorkConstants.ARRAY_CONVERTER, ArrayConverter.class, Scope.SINGLETON); + builder.factory(TypeConverter.class, XWorkConstants.DATE_CONVERTER, DateConverter.class, Scope.SINGLETON); + builder.factory(TypeConverter.class, XWorkConstants.NUMBER_CONVERTER, NumberConverter.class, Scope.SINGLETON); + builder.factory(TypeConverter.class, XWorkConstants.STRING_CONVERTER, StringConverter.class, Scope.SINGLETON); + + builder.factory(TextParser.class, OgnlTextParser.class, Scope.SINGLETON); + builder.factory(TextProvider.class, "system", DefaultTextProvider.class, Scope.SINGLETON); + + builder.factory(ObjectTypeDeterminer.class, DefaultObjectTypeDeterminer.class, Scope.SINGLETON); + builder.factory(PropertyAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON); + builder.factory(OgnlUtil.class, Scope.SINGLETON); + + builder.constant(XWorkConstants.DEV_MODE, "false"); + builder.constant(XWorkConstants.LOG_MISSING_PROPERTIES, "false"); + builder.constant(XWorkConstants.ENABLE_OGNL_EVAL_EXPRESSION, "false"); + builder.constant(XWorkConstants.ENABLE_OGNL_EXPRESSION_CACHE, "true"); + builder.constant(XWorkConstants.RELOAD_XML_CONFIGURATION, "false"); + + return builder.create(true); + } + + /** + * This builds the internal runtime configuration used by Xwork for finding and configuring Actions from the + * programmatic configuration data structures. All of the old runtime configuration will be discarded and rebuilt. + * + * <p> + * It basically flattens the data structures to make the information easier to access. It will take + * an {@link ActionConfig} and combine its data with all inherited dast. For example, if the {@link ActionConfig} + * is in a package that contains a global result and it also contains a result, the resulting {@link ActionConfig} + * will have two results. + */ + protected synchronized RuntimeConfiguration buildRuntimeConfiguration() throws ConfigurationException { + Map<String, Map<String, ActionConfig>> namespaceActionConfigs = new LinkedHashMap<>(); + Map<String, String> namespaceConfigs = new LinkedHashMap<>(); + + for (PackageConfig packageConfig : packageContexts.values()) { + + if (!packageConfig.isAbstract()) { + String namespace = packageConfig.getNamespace(); + Map<String, ActionConfig> configs = namespaceActionConfigs.get(namespace); + + if (configs == null) { + configs = new LinkedHashMap<>(); + } + + Map<String, ActionConfig> actionConfigs = packageConfig.getAllActionConfigs(); + + for (Object o : actionConfigs.keySet()) { + String actionName = (String) o; + ActionConfig baseConfig = actionConfigs.get(actionName); + configs.put(actionName, buildFullActionConfig(packageConfig, baseConfig)); + } + + namespaceActionConfigs.put(namespace, configs); + if (packageConfig.getFullDefaultActionRef() != null) { + namespaceConfigs.put(namespace, packageConfig.getFullDefaultActionRef()); + } + } + } + + PatternMatcher<int[]> matcher = container.getInstance(PatternMatcher.class); + return new RuntimeConfigurationImpl(Collections.unmodifiableMap(namespaceActionConfigs), + Collections.unmodifiableMap(namespaceConfigs), matcher); + } + + private void setDefaultResults(Map<String, ResultConfig> results, PackageConfig packageContext) { + String defaultResult = packageContext.getFullDefaultResultType(); + + for (Map.Entry<String, ResultConfig> entry : results.entrySet()) { + + if (entry.getValue() == null) { + ResultTypeConfig resultTypeConfig = packageContext.getAllResultTypeConfigs().get(defaultResult); + entry.setValue(new ResultConfig.Builder(null, resultTypeConfig.getClassName()).build()); + } + } + } + + /** + * Builds the full runtime actionconfig with all of the defaults and inheritance + * + * @param packageContext the PackageConfig which holds the base config we're building from + * @param baseConfig the ActionConfig which holds only the configuration specific to itself, without the defaults + * and inheritance + * @return a full ActionConfig for runtime configuration with all of the inherited and default params + * @throws com.opensymphony.xwork2.config.ConfigurationException + * + */ + private ActionConfig buildFullActionConfig(PackageConfig packageContext, ActionConfig baseConfig) throws ConfigurationException { + Map<String, String> params = new TreeMap<>(baseConfig.getParams()); + Map<String, ResultConfig> results = new TreeMap<>(); + + if (!baseConfig.getPackageName().equals(packageContext.getName()) && packageContexts.containsKey(baseConfig.getPackageName())) { + results.putAll(packageContexts.get(baseConfig.getPackageName()).getAllGlobalResults()); + } else { + results.putAll(packageContext.getAllGlobalResults()); + } + + results.putAll(baseConfig.getResults()); + + setDefaultResults(results, packageContext); + + List<InterceptorMapping> interceptors = new ArrayList<>(baseConfig.getInterceptors()); + + if (interceptors.size() <= 0) { + String defaultInterceptorRefName = packageContext.getFullDefaultInterceptorRef(); + + if (defaultInterceptorRefName != null) { + interceptors.addAll(InterceptorBuilder.constructInterceptorReference(new PackageConfig.Builder(packageContext), defaultInterceptorRefName, + new LinkedHashMap<String, String>(), packageContext.getLocation(), objectFactory)); + } + } + + return new ActionConfig.Builder(baseConfig) + .addParams(params) + .addResultConfigs(results) + .defaultClassName(packageContext.getDefaultClassRef()) // fill in default if non class has been provided + .interceptors(interceptors) + .addExceptionMappings(packageContext.getAllExceptionMappingConfigs()) + .build(); + } + + + private static class RuntimeConfigurationImpl implements RuntimeConfiguration { + + private Map<String, Map<String, ActionConfig>> namespaceActionConfigs; + private Map<String, ActionConfigMatcher> namespaceActionConfigMatchers; + private NamespaceMatcher namespaceMatcher; + private Map<String, String> namespaceConfigs; + + public RuntimeConfigurationImpl(Map<String, Map<String, ActionConfig>> namespaceActionConfigs, + Map<String, String> namespaceConfigs, + PatternMatcher<int[]> matcher) { + this.namespaceActionConfigs = namespaceActionConfigs; + this.namespaceConfigs = namespaceConfigs; + + this.namespaceActionConfigMatchers = new LinkedHashMap<>(); + this.namespaceMatcher = new NamespaceMatcher(matcher, namespaceActionConfigs.keySet()); + + for (String ns : namespaceActionConfigs.keySet()) { + namespaceActionConfigMatchers.put(ns, new ActionConfigMatcher(matcher, namespaceActionConfigs.get(ns), true)); + } + } + + + /** + * Gets the configuration information for an action name, or returns null if the + * name is not recognized. + * + * @param name the name of the action + * @param namespace the namespace for the action or null for the empty namespace, "" + * @return the configuration information for action requested + */ + public ActionConfig getActionConfig(String namespace, String name) { + ActionConfig config = findActionConfigInNamespace(namespace, name); + + // try wildcarded namespaces + if (config == null) { + NamespaceMatch match = namespaceMatcher.match(namespace); + if (match != null) { + config = findActionConfigInNamespace(match.getPattern(), name); + + // If config found, place all the matches found in the namespace processing in the action's parameters + if (config != null) { + config = new ActionConfig.Builder(config) + .addParams(match.getVariables()) + .build(); + } + } + } + + // fail over to empty namespace + if (config == null && StringUtils.isNotBlank(namespace)) { + config = findActionConfigInNamespace("", name); + } + + + return config; + } + + private ActionConfig findActionConfigInNamespace(String namespace, String name) { + ActionConfig config = null; + if (namespace == null) { + namespace = ""; + } + Map<String, ActionConfig> actions = namespaceActionConfigs.get(namespace); + if (actions != null) { + config = actions.get(name); + // Check wildcards + if (config == null) { + config = namespaceActionConfigMatchers.get(namespace).match(name); + // fail over to default action + if (config == null) { + String defaultActionRef = namespaceConfigs.get(namespace); + if (defaultActionRef != null) { + config = actions.get(defaultActionRef); + } + } + } + } + return config; + } + + /** + * Gets the configuration settings for every action. + * + * @return a Map of namespace - > Map of ActionConfig objects, with the key being the action name + */ + public Map<String, Map<String, ActionConfig>> getActionConfigs() { + return namespaceActionConfigs; + } + + @Override + public String toString() { + StringBuilder buff = new StringBuilder("RuntimeConfiguration - actions are\n"); + + for (String namespace : namespaceActionConfigs.keySet()) { + Map<String, ActionConfig> actionConfigs = namespaceActionConfigs.get(namespace); + + for (String s : actionConfigs.keySet()) { + buff.append(namespace).append("/").append(s).append("\n"); + } + } + + return buff.toString(); + } + } + + class ContainerProperties extends LocatableProperties { + private static final long serialVersionUID = -7320625750836896089L; + + @Override + public Object setProperty(String key, String value) { + String oldValue = getProperty(key); + if (LOG.isInfoEnabled() && oldValue != null && !oldValue.equals(value) && !defaultFrameworkBeanName.equals(oldValue)) { + LOG.info("Overriding property {} - old value: {} new value: {}", key, oldValue, value); + } + return super.setProperty(key, value); + } + + public void setConstants(ContainerBuilder builder) { + for (Object keyobj : keySet()) { + String key = (String)keyobj; + builder.factory(String.class, key, new LocatableConstantFactory<>(getProperty(key), getPropertyLocation(key))); + } + } + } +}
