/* * 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.ActionContext; import com.opensymphony.xwork2.DefaultTextProvider; import com.opensymphony.xwork2.FileManager; import com.opensymphony.xwork2.FileManagerFactory; import com.opensymphony.xwork2.ObjectFactory; import com.opensymphony.xwork2.TextProvider; import com.opensymphony.xwork2.XWorkConstants; import com.opensymphony.xwork2.config.Configuration; import com.opensymphony.xwork2.config.ConfigurationException; import com.opensymphony.xwork2.config.ConfigurationProvider; import com.opensymphony.xwork2.config.ContainerProvider; import com.opensymphony.xwork2.config.FileManagerFactoryProvider; import com.opensymphony.xwork2.config.FileManagerProvider; import com.opensymphony.xwork2.config.PackageProvider; import com.opensymphony.xwork2.config.RuntimeConfiguration; import com.opensymphony.xwork2.config.entities.ActionConfig; import com.opensymphony.xwork2.config.entities.InterceptorMapping; import com.opensymphony.xwork2.config.entities.PackageConfig; import com.opensymphony.xwork2.config.entities.ResultConfig; import com.opensymphony.xwork2.config.entities.ResultTypeConfig; import com.opensymphony.xwork2.config.entities.UnknownHandlerConfig; import com.opensymphony.xwork2.config.providers.InterceptorBuilder; import com.opensymphony.xwork2.conversion.ConversionAnnotationProcessor; import com.opensymphony.xwork2.conversion.ConversionFileProcessor; import com.opensymphony.xwork2.conversion.ConversionPropertiesProcessor; import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; import com.opensymphony.xwork2.conversion.TypeConverter; import com.opensymphony.xwork2.conversion.TypeConverterCreator; import com.opensymphony.xwork2.conversion.TypeConverterHolder; import com.opensymphony.xwork2.conversion.impl.ArrayConverter; import com.opensymphony.xwork2.conversion.impl.CollectionConverter; import com.opensymphony.xwork2.conversion.impl.DateConverter; import com.opensymphony.xwork2.conversion.impl.DefaultConversionAnnotationProcessor; import com.opensymphony.xwork2.conversion.impl.DefaultConversionFileProcessor; import com.opensymphony.xwork2.conversion.impl.DefaultConversionPropertiesProcessor; import com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer; import com.opensymphony.xwork2.conversion.impl.DefaultTypeConverterCreator; import com.opensymphony.xwork2.conversion.impl.DefaultTypeConverterHolder; import com.opensymphony.xwork2.conversion.impl.NumberConverter; import com.opensymphony.xwork2.conversion.impl.StringConverter; import com.opensymphony.xwork2.conversion.impl.XWorkBasicConverter; import com.opensymphony.xwork2.conversion.impl.XWorkConverter; import com.opensymphony.xwork2.factory.ActionFactory; import com.opensymphony.xwork2.factory.ConverterFactory; import com.opensymphony.xwork2.factory.DefaultActionFactory; import com.opensymphony.xwork2.factory.DefaultConverterFactory; import com.opensymphony.xwork2.factory.DefaultInterceptorFactory; import com.opensymphony.xwork2.factory.DefaultResultFactory; import com.opensymphony.xwork2.factory.InterceptorFactory; import com.opensymphony.xwork2.factory.ResultFactory; import com.opensymphony.xwork2.inject.Container; import com.opensymphony.xwork2.inject.ContainerBuilder; import com.opensymphony.xwork2.inject.Context; import com.opensymphony.xwork2.inject.Factory; import com.opensymphony.xwork2.inject.Scope; 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.CompoundRoot; import com.opensymphony.xwork2.util.PatternMatcher; import com.opensymphony.xwork2.util.ValueStack; import com.opensymphony.xwork2.util.ValueStackFactory; 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.logging.Logger; import com.opensymphony.xwork2.util.logging.LoggerFactory; import com.opensymphony.xwork2.util.reflection.ReflectionProvider; import ognl.PropertyAccessor; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; 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 { protected static final Logger LOG = LoggerFactory.getLogger(DefaultConfiguration.class); // Programmatic Action Configurations protected Map<String, PackageConfig> packageContexts = new LinkedHashMap<String, PackageConfig>(); protected RuntimeConfiguration runtimeConfiguration; protected Container container; protected String defaultFrameworkBeanName; protected Set<String> loadedFileNames = new TreeSet<String>(); 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())) { if (LOG.isDebugEnabled()) { LOG.debug("The package name '" + name + "' is already been loaded by the same location and could be removed: " + 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<ContainerProvider>(); 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<PackageProvider>(); 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(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(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<String, Map<String, ActionConfig>>(); Map<String, String> namespaceConfigs = new LinkedHashMap<String, String>(); 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<String, ActionConfig>(); } 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<String, String>(baseConfig.getParams()); Map<String, ResultConfig> results = new TreeMap<String, ResultConfig>(); 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<InterceptorMapping>(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<String, ActionConfigMatcher>(); 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) && (namespace != null) && (!"".equals(namespace.trim()))) { 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 "+key+" - old value: "+oldValue+" new value: "+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<String>(getProperty(key), getPropertyLocation(key))); } } } }