Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,25 @@
package com.opensymphony.xwork2.config;

/**
* When implemented allows to alias already existing beans
* A {@link ConfigurationProvider} that selects and aliases bean implementations.
* <p>
* Implementations of this interface are responsible for selecting which bean implementation
* to use for a given interface type. The selection is typically based on configuration properties
* that specify the bean name or class name.
* </p>
* <p>
* The aliasing mechanism works as follows:
* </p>
* <ol>
* <li>Look for a bean by the name specified in the configuration property</li>
* <li>If found, alias it to the default name so it becomes the default implementation</li>
* <li>If not found, try to load the value as a class name and register it as a factory</li>
* <li>If class loading fails, delegate to {@link org.apache.struts2.ObjectFactory} at runtime
* (useful for Spring bean names)</li>
* </ol>
*
* @see AbstractBeanSelectionProvider
* @see StrutsBeanSelectionProvider
*/
public interface BeanSelectionProvider extends ConfigurationProvider {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@
import org.apache.logging.log4j.Logger;
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.conversion.StrutsConversionPropertiesProcessor;
import org.apache.struts2.conversion.UserConversionPropertiesProcessor;
import org.apache.struts2.conversion.UserConversionPropertiesProvider;
import org.apache.struts2.conversion.StrutsTypeConverterCreator;
import org.apache.struts2.conversion.StrutsTypeConverterHolder;
import org.apache.struts2.factory.StrutsResultFactory;
Expand All @@ -125,12 +127,8 @@
import java.util.TreeMap;
import java.util.TreeSet;


/**
* DefaultConfiguration
*
* @author Jason Carreira
* Created Feb 24, 2003 7:38:06 AM
*/
public class DefaultConfiguration implements Configuration {

Expand Down Expand Up @@ -224,7 +222,7 @@ public void addPackageConfig(String name, PackageConfig packageContext) {
name, packageContext.getLocation());
} else {
throw new ConfigurationException("The package name '" + name
+ "' at location "+packageContext.getLocation()
+ "' at location " + packageContext.getLocation()
+ " is already been used by another package at location " + check.getLocation(),
packageContext);
}
Expand Down Expand Up @@ -257,7 +255,6 @@ public void rebuildRuntimeConfiguration() {
*
* @param providers list of ContainerProvider
* @return list of package providers
*
* @throws ConfigurationException in case of any configuration errors
*/
@Override
Expand All @@ -269,8 +266,7 @@ public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider
ContainerProperties props = new ContainerProperties();
ContainerBuilder builder = new ContainerBuilder();
Container bootstrap = createBootstrapContainer(providers);
for (final ContainerProvider containerProvider : providers)
{
for (final ContainerProvider containerProvider : providers) {
bootstrap.inject(containerProvider);
containerProvider.init(this);
containerProvider.register(builder, props);
Expand Down Expand Up @@ -298,13 +294,16 @@ public Class<? extends Configuration> type() {
setContext(container);
objectFactory = container.getInstance(ObjectFactory.class);

// Trigger late initialization of user conversion properties (WW-4291)
// This must happen after full container is built so SpringObjectFactory is available
container.getInstance(UserConversionPropertiesProcessor.class);

// Process the configuration providers first
for (final ContainerProvider containerProvider : providers)
{
for (final ContainerProvider containerProvider : providers) {
if (containerProvider instanceof PackageProvider) {
container.inject(containerProvider);
((PackageProvider)containerProvider).loadPackages();
packageProviders.add((PackageProvider)containerProvider);
((PackageProvider) containerProvider).loadPackages();
packageProviders.add((PackageProvider) containerProvider);
}
}

Expand Down Expand Up @@ -380,6 +379,8 @@ public static ContainerBuilder bootstrapFactories(ContainerBuilder builder) {
.factory(ConversionAnnotationProcessor.class, DefaultConversionAnnotationProcessor.class, Scope.SINGLETON)
.factory(TypeConverterCreator.class, StrutsTypeConverterCreator.class, Scope.SINGLETON)
.factory(TypeConverterHolder.class, StrutsTypeConverterHolder.class, Scope.SINGLETON)
.factory(UserConversionPropertiesProvider.class, StrutsConversionPropertiesProcessor.class, Scope.SINGLETON)
.factory(UserConversionPropertiesProcessor.class, Scope.SINGLETON)

.factory(TextProvider.class, "system", DefaultTextProvider.class, Scope.SINGLETON)
.factory(LocalizedTextProvider.class, StrutsLocalizedTextProvider.class, Scope.SINGLETON)
Expand Down Expand Up @@ -443,10 +444,9 @@ protected synchronized RuntimeConfiguration buildRuntimeConfiguration() throws C

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));
for (Map.Entry<String, ActionConfig> entry : actionConfigs.entrySet()) {
ActionConfig baseConfig = entry.getValue();
configs.put(entry.getKey(), buildFullActionConfig(packageConfig, baseConfig));
}

namespaceActionConfigs.put(namespace, configs);
Expand Down Expand Up @@ -487,8 +487,7 @@ private void setDefaultResults(Map<String, ResultConfig> results, PackageConfig
* @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
*
* @throws com.opensymphony.xwork2.config.ConfigurationException in case of any configuration errors
*/
private ActionConfig buildFullActionConfig(PackageConfig packageContext, ActionConfig baseConfig) throws ConfigurationException {
Map<String, String> params = new TreeMap<>(baseConfig.getParams());
Expand All @@ -500,7 +499,7 @@ private ActionConfig buildFullActionConfig(PackageConfig packageContext, ActionC
results.putAll(packageContext.getAllGlobalResults());
}

results.putAll(baseConfig.getResults());
results.putAll(baseConfig.getResults());

setDefaultResults(results, packageContext);

Expand All @@ -511,7 +510,7 @@ private ActionConfig buildFullActionConfig(PackageConfig packageContext, ActionC

if (defaultInterceptorRefName != null) {
interceptors.addAll(InterceptorBuilder.constructInterceptorReference(new PackageConfig.Builder(packageContext), defaultInterceptorRefName,
new LinkedHashMap<String, String>(), packageContext.getLocation(), objectFactory));
new LinkedHashMap<>(), packageContext.getLocation(), objectFactory));
}
}

Expand All @@ -523,14 +522,14 @@ private ActionConfig buildFullActionConfig(PackageConfig packageContext, ActionC
LOG.debug("Using pattern [{}] to match allowed methods when SMI is disabled!", methodRegex);

return new ActionConfig.Builder(baseConfig)
.addParams(params)
.addResultConfigs(results)
.defaultClassName(packageContext.getDefaultClassRef()) // fill in default if non class has been provided
.interceptors(interceptors)
.setStrictMethodInvocation(packageContext.isStrictMethodInvocation())
.setDefaultMethodRegex(methodRegex)
.addExceptionMappings(packageContext.getAllExceptionMappingConfigs())
.build();
.addParams(params)
.addResultConfigs(results)
.defaultClassName(packageContext.getDefaultClassRef()) // fill in default if non class has been provided
.interceptors(interceptors)
.setStrictMethodInvocation(packageContext.isStrictMethodInvocation())
.setDefaultMethodRegex(methodRegex)
.addExceptionMappings(packageContext.getAllExceptionMappingConfigs())
.build();
}


Expand All @@ -546,8 +545,7 @@ public RuntimeConfigurationImpl(Map<String, Map<String, ActionConfig>> namespace
Map<String, String> namespaceConfigs,
PatternMatcher<int[]> matcher,
boolean appendNamedParameters,
boolean fallbackToEmptyNamespace)
{
boolean fallbackToEmptyNamespace) {
this.namespaceActionConfigs = namespaceActionConfigs;
this.namespaceConfigs = namespaceConfigs;
this.fallbackToEmptyNamespace = fallbackToEmptyNamespace;
Expand Down Expand Up @@ -630,7 +628,7 @@ private ActionConfig findActionConfigInNamespace(String namespace, String name)
* @return a Map of namespace - > Map of ActionConfig objects, with the key being the action name
*/
@Override
public Map<String, Map<String, ActionConfig>> getActionConfigs() {
public Map<String, Map<String, ActionConfig>> getActionConfigs() {
return namespaceActionConfigs;
}

Expand Down Expand Up @@ -664,7 +662,7 @@ public Object setProperty(String key, String value) {

public void setConstants(ContainerBuilder builder) {
for (Object keyobj : keySet()) {
String key = (String)keyobj;
String key = (String) keyobj;
builder.factory(String.class, key, new LocatableConstantFactory<>(getProperty(key), getPropertyLocation(key)));
}
}
Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/org/apache/struts2/StrutsConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ public final class StrutsConstants {
public static final String STRUTS_CONVERTER_ANNOTATION_PROCESSOR = "struts.converter.annotation.processor";
public static final String STRUTS_CONVERTER_CREATOR = "struts.converter.creator";
public static final String STRUTS_CONVERTER_HOLDER = "struts.converter.holder";
public static final String STRUTS_CONVERTER_USER_PROPERTIES_PROVIDER = "struts.converter.userPropertiesProvider";

public static final String STRUTS_EXPRESSION_PARSER = "struts.expression.parser";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,44 @@
import java.util.Properties;

/**
* TODO lukaszlenart: write a JavaDoc
* Base implementation of {@link BeanSelectionProvider} that provides bean aliasing functionality.
* <p>
* This class provides the {@link #alias(Class, String, ContainerBuilder, Properties, Scope)} method
* which is used to select and register bean implementations based on configuration properties.
* </p>
*
* <h2>Bean Selection Process</h2>
* <p>
* The {@code alias} method selects a bean implementation using the following process:
* </p>
* <ol>
* <li>Read the property value for the given key from the configuration properties</li>
* <li>If no property is set, use {@value #DEFAULT_BEAN_NAME} as the default bean name</li>
* <li>Check if a bean with that name already exists in the container:
* <ul>
* <li>If found, alias it to {@link Container#DEFAULT_NAME} making it the default</li>
* <li>If not found, try to load the property value as a fully qualified class name</li>
* </ul>
* </li>
* <li>If class loading succeeds, register the class as a factory for the interface type</li>
* <li>If class loading fails and the name is not the default, create a delegate factory
* that will resolve the bean through {@link ObjectFactory} at runtime. This allows
* Spring bean names to be used in configuration.</li>
* </ol>
*
* <h2>Usage Example</h2>
* <pre>
* // In struts.properties or struts.xml:
* // struts.objectFactory = spring
* // struts.converter.collection = myCustomCollectionConverter
*
* // In a subclass:
* alias(ObjectFactory.class, StrutsConstants.STRUTS_OBJECTFACTORY, builder, props);
* alias(CollectionConverter.class, StrutsConstants.STRUTS_CONVERTER_COLLECTION, builder, props);
* </pre>
*
* @see BeanSelectionProvider
* @see StrutsBeanSelectionProvider
*/
public abstract class AbstractBeanSelectionProvider implements BeanSelectionProvider {

Expand Down Expand Up @@ -73,7 +110,7 @@ protected void alias(Class type, String key, ContainerBuilder builder, Propertie
// Perhaps a spring bean id, so we'll delegate to the object factory at runtime
LOG.trace("Choosing bean ({}) for ({}) to be loaded from the ObjectFactory", foundName, type.getName());
if (DEFAULT_BEAN_NAME.equals(foundName)) {
// Probably an optional bean, will ignore
LOG.trace("No bean registered for type ({}) with default name '{}', skipping as optional", type.getName(), DEFAULT_BEAN_NAME);
} else {
if (ObjectFactory.class != type) {
builder.factory(type, new ObjectFactoryDelegateFactory(foundName, type), scope);
Expand Down Expand Up @@ -103,7 +140,7 @@ public Object create(Context context) throws Exception {
try {
return objFactory.buildBean(name, null, true);
} catch (ClassNotFoundException ex) {
throw new ConfigurationException("Unable to load bean "+type.getName()+" ("+name+")");
throw new ConfigurationException(String.format("Unable to load bean %s (name = %s)", type.getName(), name));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import org.apache.struts2.StrutsConstants;
import org.apache.struts2.components.UrlRenderer;
import org.apache.struts2.components.date.DateFormatter;
import org.apache.struts2.conversion.UserConversionPropertiesProvider;
import org.apache.struts2.dispatcher.DispatcherErrorHandler;
import org.apache.struts2.dispatcher.StaticContentLoader;
import org.apache.struts2.dispatcher.mapper.ActionMapper;
Expand All @@ -88,7 +89,7 @@
*
* <p>
* The following is a list of the allowed extension points:
*
* <p>
* <!-- START SNIPPET: extensionPoints -->
* <table border="1" summary="">
* <tr>
Expand Down Expand Up @@ -353,7 +354,7 @@
* <td>Provides access to resource bundles used to localise messages (since 2.5.11)</td>
* </tr>
* </table>
*
* <p>
* <!-- END SNIPPET: extensionPoints -->
*
* <p>
Expand Down Expand Up @@ -405,6 +406,7 @@ public void register(ContainerBuilder builder, LocatableProperties props) {
alias(ConversionAnnotationProcessor.class, StrutsConstants.STRUTS_CONVERTER_ANNOTATION_PROCESSOR, builder, props);
alias(TypeConverterCreator.class, StrutsConstants.STRUTS_CONVERTER_CREATOR, builder, props);
alias(TypeConverterHolder.class, StrutsConstants.STRUTS_CONVERTER_HOLDER, builder, props);
alias(UserConversionPropertiesProvider.class, StrutsConstants.STRUTS_CONVERTER_USER_PROPERTIES_PROVIDER, builder, props);

alias(TextProvider.class, StrutsConstants.STRUTS_TEXT_PROVIDER, builder, props, Scope.PROTOTYPE);
alias(TextProviderFactory.class, StrutsConstants.STRUTS_TEXT_PROVIDER_FACTORY, builder, props, Scope.PROTOTYPE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import java.util.Map;
import java.util.Properties;

public class StrutsConversionPropertiesProcessor implements ConversionPropertiesProcessor, EarlyInitializable {
public class StrutsConversionPropertiesProcessor implements ConversionPropertiesProcessor, EarlyInitializable, UserConversionPropertiesProvider {

private static final Logger LOG = LogManager.getLogger(StrutsConversionPropertiesProcessor.class);

Expand All @@ -58,8 +58,27 @@ public void setTypeConverterHolder(TypeConverterHolder converterHolder) {

@Override
public void init() {
LOG.debug("Processing default conversion properties files");
// Early phase: Only process framework defaults (class names only)
// User properties are processed later in initUserConversions() when
// SpringObjectFactory is available for bean name resolution (WW-4291)
LOG.debug("Processing default conversion properties files (early phase)");
processRequired(STRUTS_DEFAULT_CONVERSION_PROPERTIES);
}

/**
* Process user conversion properties. Called during late initialization
* when SpringObjectFactory is available for bean name resolution.
* <p>
* This allows users to reference Spring bean names in struts-conversion.properties
* instead of only fully qualified class names.
* </p>
*
* @see <a href="https://issues.apache.org/jira/browse/WW-4291">WW-4291</a>
* @since 7.2.0
*/
@Override
public void initUserConversions() {
LOG.debug("Processing user conversion properties files (late phase)");
process(STRUTS_CONVERSION_PROPERTIES);
process(XWORK_CONVERSION_PROPERTIES);
}
Expand All @@ -78,16 +97,15 @@ public void loadConversionProperties(String propsName, boolean require) {
while (resources.hasNext()) {
if (XWORK_CONVERSION_PROPERTIES.equals(propsName)) {
LOG.warn("Instead of using deprecated {} please use the new file name {}",
XWORK_CONVERSION_PROPERTIES, STRUTS_CONVERSION_PROPERTIES);
XWORK_CONVERSION_PROPERTIES, STRUTS_CONVERSION_PROPERTIES);
}
URL url = resources.next();
Properties props = new Properties();
props.load(url.openStream());

LOG.debug("Processing conversion file [{}]", propsName);

for (Object o : props.entrySet()) {
Map.Entry entry = (Map.Entry) o;
for (Map.Entry<Object, Object> entry : props.entrySet()) {
String key = (String) entry.getKey();

try {
Expand Down
Loading