1 /*
2 * Copyright (c) 2002-2006 by OpenSymphony
3 * All rights reserved.
4 */
5 package com.opensymphony.xwork2.config.impl;
6
7 import com.opensymphony.xwork2.ActionContext;
8 import com.opensymphony.xwork2.DefaultTextProvider;
9 import com.opensymphony.xwork2.ObjectFactory;
10 import com.opensymphony.xwork2.TextProvider;
11 import com.opensymphony.xwork2.config;
12 import com.opensymphony.xwork2.config.entities;
13 import com.opensymphony.xwork2.config.providers.InterceptorBuilder;
14 import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer;
15 import com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer;
16 import com.opensymphony.xwork2.conversion.impl.XWorkBasicConverter;
17 import com.opensymphony.xwork2.conversion.impl.XWorkConverter;
18 import com.opensymphony.xwork2.inject;
19 import com.opensymphony.xwork2.ognl.OgnlReflectionProvider;
20 import com.opensymphony.xwork2.ognl.OgnlUtil;
21 import com.opensymphony.xwork2.ognl.OgnlValueStackFactory;
22 import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor;
23 import com.opensymphony.xwork2.util.CompoundRoot;
24 import com.opensymphony.xwork2.util.PatternMatcher;
25 import com.opensymphony.xwork2.util.ValueStack;
26 import com.opensymphony.xwork2.util.ValueStackFactory;
27 import com.opensymphony.xwork2.util.location.LocatableProperties;
28 import com.opensymphony.xwork2.util.logging.Logger;
29 import com.opensymphony.xwork2.util.logging.LoggerFactory;
30 import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
31 import ognl.PropertyAccessor;
32
33 import java.util;
34
35
36 /**
37 * DefaultConfiguration
38 *
39 * @author Jason Carreira
40 * Created Feb 24, 2003 7:38:06 AM
41 */
42 public class DefaultConfiguration implements Configuration {
43
44 protected static final Logger LOG = LoggerFactory.getLogger(DefaultConfiguration.class);
45
46
47 // Programmatic Action Configurations
48 protected Map<String, PackageConfig> packageContexts = new LinkedHashMap<String, PackageConfig>();
49 protected RuntimeConfiguration runtimeConfiguration;
50 protected Container container;
51 protected String defaultFrameworkBeanName;
52 protected Set<String> loadedFileNames = new TreeSet<String>();
53
54
55 ObjectFactory objectFactory;
56
57 public DefaultConfiguration() {
58 this("xwork");
59 }
60
61 public DefaultConfiguration(String defaultBeanName) {
62 this.defaultFrameworkBeanName = defaultBeanName;
63 }
64
65
66 public PackageConfig getPackageConfig(String name) {
67 return packageContexts.get(name);
68 }
69
70 public Set getPackageConfigNames() {
71 return packageContexts.keySet();
72 }
73
74 public Map getPackageConfigs() {
75 return packageContexts;
76 }
77
78 public Set<String> getLoadedFileNames() {
79 return loadedFileNames;
80 }
81
82 public RuntimeConfiguration getRuntimeConfiguration() {
83 return runtimeConfiguration;
84 }
85
86 /**
87 * @return the container
88 */
89 public Container getContainer() {
90 return container;
91 }
92
93 public void addPackageConfig(String name, PackageConfig packageContext) {
94 PackageConfig check = packageContexts.get(name);
95 if (check != null) {
96 if (check.getLocation() != null && packageContext.getLocation() != null
97 && check.getLocation().equals(packageContext.getLocation())) {
98 if (LOG.isDebugEnabled()) {
99 LOG.debug("The package name '" + name
100 + "' is already been loaded by the same location and could be removed: "
101 + packageContext.getLocation());
102 }
103 } else {
104 throw new ConfigurationException("The package name '" + name
105 + "' at location "+packageContext.getLocation()
106 + " is already been used by another package at location " + check.getLocation(),
107 packageContext);
108 }
109 }
110 packageContexts.put(name, packageContext);
111 }
112
113 /**
114 * Allows the configuration to clean up any resources used
115 */
116 public void destroy() {
117 packageContexts.clear();
118 loadedFileNames.clear();
119 }
120
121 public void rebuildRuntimeConfiguration() {
122 runtimeConfiguration = buildRuntimeConfiguration();
123 }
124
125 /**
126 * Calls the ConfigurationProviderFactory.getConfig() to tell it to reload the configuration and then calls
127 * buildRuntimeConfiguration().
128 *
129 * @throws ConfigurationException
130 */
131 public synchronized void reload(List<ConfigurationProvider> providers) throws ConfigurationException {
132
133 // Silly copy necessary due to lack of ability to cast generic lists
134 List<ContainerProvider> contProviders = new ArrayList<ContainerProvider>();
135 contProviders.addAll(providers);
136
137 reloadContainer(contProviders);
138 }
139
140 /**
141 * Calls the ConfigurationProviderFactory.getConfig() to tell it to reload the configuration and then calls
142 * buildRuntimeConfiguration().
143 *
144 * @throws ConfigurationException
145 */
146 public synchronized List<PackageProvider> reloadContainer(List<ContainerProvider> providers) throws ConfigurationException {
147 packageContexts.clear();
148 loadedFileNames.clear();
149 List<PackageProvider> packageProviders = new ArrayList<PackageProvider>();
150
151 ContainerProperties props = new ContainerProperties();
152 ContainerBuilder builder = new ContainerBuilder();
153 for (final ContainerProvider containerProvider : providers)
154 {
155 containerProvider.init(this);
156 containerProvider.register(builder, props);
157 }
158 props.setConstants(builder);
159
160 builder.factory(Configuration.class, new Factory<Configuration>() {
161 public Configuration create(Context context) throws Exception {
162 return DefaultConfiguration.this;
163 }
164 });
165
166 try {
167 // Set the bootstrap container for the purposes of factory creation
168 Container bootstrap = createBootstrapContainer();
169 setContext(bootstrap);
170 container = builder.create(false);
171 setContext(container);
172 objectFactory = container.getInstance(ObjectFactory.class);
173
174 // Process the configuration providers first
175 for (final ContainerProvider containerProvider : providers)
176 {
177 if (containerProvider instanceof PackageProvider) {
178 container.inject(containerProvider);
179 ((PackageProvider)containerProvider).loadPackages();
180 packageProviders.add((PackageProvider)containerProvider);
181 }
182 }
183
184 // Then process any package providers from the plugins
185 Set<String> packageProviderNames = container.getInstanceNames(PackageProvider.class);
186 if (packageProviderNames != null) {
187 for (String name : packageProviderNames) {
188 PackageProvider provider = container.getInstance(PackageProvider.class, name);
189 provider.init(this);
190 provider.loadPackages();
191 packageProviders.add(provider);
192 }
193 }
194
195 rebuildRuntimeConfiguration();
196 } finally {
197 ActionContext.setContext(null);
198 }
199 return packageProviders;
200 }
201
202 protected ActionContext setContext(Container cont) {
203 ValueStack vs = cont.getInstance(ValueStackFactory.class).createValueStack();
204 ActionContext context = new ActionContext(vs.getContext());
205 ActionContext.setContext(context);
206 return context;
207 }
208
209 protected Container createBootstrapContainer() {
210 ContainerBuilder builder = new ContainerBuilder();
211 builder.factory(ObjectFactory.class, Scope.SINGLETON);
212 builder.factory(ReflectionProvider.class, OgnlReflectionProvider.class, Scope.SINGLETON);
213 builder.factory(ValueStackFactory.class, OgnlValueStackFactory.class, Scope.SINGLETON);
214 builder.factory(XWorkConverter.class, Scope.SINGLETON);
215 builder.factory(XWorkBasicConverter.class, Scope.SINGLETON);
216 builder.factory(TextProvider.class, "system", DefaultTextProvider.class, Scope.SINGLETON);
217 builder.factory(ObjectTypeDeterminer.class, DefaultObjectTypeDeterminer.class, Scope.SINGLETON);
218 builder.factory(PropertyAccessor.class, CompoundRoot.class.getName(), CompoundRootAccessor.class, Scope.SINGLETON);
219 builder.factory(OgnlUtil.class, Scope.SINGLETON);
220 builder.constant("devMode", "false");
221 return builder.create(true);
222 }
223
224 /**
225 * This builds the internal runtime configuration used by Xwork for finding and configuring Actions from the
226 * programmatic configuration data structures. All of the old runtime configuration will be discarded and rebuilt.
227 *
228 * <p>
229 * It basically flattens the data structures to make the information easier to access. It will take
230 * an {@link ActionConfig} and combine its data with all inherited dast. For example, if the {@link ActionConfig}
231 * is in a package that contains a global result and it also contains a result, the resulting {@link ActionConfig}
232 * will have two results.
233 */
234 protected synchronized RuntimeConfiguration buildRuntimeConfiguration() throws ConfigurationException {
235 Map<String, Map<String, ActionConfig>> namespaceActionConfigs = new LinkedHashMap<String, Map<String, ActionConfig>>();
236 Map<String, String> namespaceConfigs = new LinkedHashMap<String, String>();
237
238 for (PackageConfig packageConfig : packageContexts.values()) {
239
240 if (!packageConfig.isAbstract()) {
241 String namespace = packageConfig.getNamespace();
242 Map<String, ActionConfig> configs = namespaceActionConfigs.get(namespace);
243
244 if (configs == null) {
245 configs = new LinkedHashMap<String, ActionConfig>();
246 }
247
248 Map actionConfigs = packageConfig.getAllActionConfigs();
249
250 for (Object o : actionConfigs.keySet()) {
251 String actionName = (String) o;
252 ActionConfig baseConfig = (ActionConfig) actionConfigs.get(actionName);
253 configs.put(actionName, buildFullActionConfig(packageConfig, baseConfig));
254 }
255
256
257
258 namespaceActionConfigs.put(namespace, configs);
259 if (packageConfig.getFullDefaultActionRef() != null) {
260 namespaceConfigs.put(namespace, packageConfig.getFullDefaultActionRef());
261 }
262 }
263 }
264
265 return new RuntimeConfigurationImpl(namespaceActionConfigs, namespaceConfigs);
266 }
267
268 private void setDefaultResults(Map<String, ResultConfig> results, PackageConfig packageContext) {
269 String defaultResult = packageContext.getFullDefaultResultType();
270
271 for (Map.Entry<String, ResultConfig> entry : results.entrySet()) {
272
273 if (entry.getValue() == null) {
274 ResultTypeConfig resultTypeConfig = packageContext.getAllResultTypeConfigs().get(defaultResult);
275 entry.setValue(new ResultConfig.Builder(null, resultTypeConfig.getClassName()).build());
276 }
277 }
278 }
279
280 /**
281 * Builds the full runtime actionconfig with all of the defaults and inheritance
282 *
283 * @param packageContext the PackageConfig which holds the base config we're building from
284 * @param baseConfig the ActionConfig which holds only the configuration specific to itself, without the defaults
285 * and inheritance
286 * @return a full ActionConfig for runtime configuration with all of the inherited and default params
287 * @throws com.opensymphony.xwork2.config.ConfigurationException
288 *
289 */
290 private ActionConfig buildFullActionConfig(PackageConfig packageContext, ActionConfig baseConfig) throws ConfigurationException {
291 Map<String, String> params = new TreeMap<String, String>(baseConfig.getParams());
292 Map<String, ResultConfig> results = new TreeMap<String, ResultConfig>();
293
294 if (!baseConfig.getPackageName().equals(packageContext.getName()) && packageContexts.containsKey(baseConfig.getPackageName())) {
295 results.putAll(packageContexts.get(baseConfig.getPackageName()).getAllGlobalResults());
296 } else {
297 results.putAll(packageContext.getAllGlobalResults());
298 }
299
300 results.putAll(baseConfig.getResults());
301
302 setDefaultResults(results, packageContext);
303
304 List<InterceptorMapping> interceptors = new ArrayList<InterceptorMapping>(baseConfig.getInterceptors());
305
306 if (interceptors.size() <= 0) {
307 String defaultInterceptorRefName = packageContext.getFullDefaultInterceptorRef();
308
309 if (defaultInterceptorRefName != null) {
310 interceptors.addAll(InterceptorBuilder.constructInterceptorReference(new PackageConfig.Builder(packageContext), defaultInterceptorRefName,
311 new LinkedHashMap(), packageContext.getLocation(), objectFactory));
312 }
313 }
314
315
316
317 ActionConfig config = new ActionConfig.Builder(baseConfig)
318 .addParams(params)
319 .addResultConfigs(results)
320 .defaultClassName(packageContext.getDefaultClassRef()) // fill in default if non class has been provided
321 .interceptors(interceptors)
322 .addExceptionMappings(packageContext.getAllExceptionMappingConfigs())
323 .build();
324
325 return config;
326 }
327
328
329 private class RuntimeConfigurationImpl implements RuntimeConfiguration {
330 private Map<String, Map<String, ActionConfig>> namespaceActionConfigs;
331 private Map<String, ActionConfigMatcher> namespaceActionConfigMatchers;
332 private NamespaceMatcher namespaceMatcher;
333 private Map<String, String> namespaceConfigs;
334
335 public RuntimeConfigurationImpl(Map<String, Map<String, ActionConfig>> namespaceActionConfigs, Map<String, String> namespaceConfigs) {
336 this.namespaceActionConfigs = namespaceActionConfigs;
337 this.namespaceConfigs = namespaceConfigs;
338
339 PatternMatcher matcher = container.getInstance(PatternMatcher.class);
340
341 this.namespaceActionConfigMatchers = new LinkedHashMap<String, ActionConfigMatcher>();
342 this.namespaceMatcher = new NamespaceMatcher(matcher, namespaceActionConfigs.keySet());
343
344 for (String ns : namespaceActionConfigs.keySet()) {
345 namespaceActionConfigMatchers.put(ns,
346 new ActionConfigMatcher(matcher,
347 namespaceActionConfigs.get(ns), true));
348 }
349 }
350
351
352 /**
353 * Gets the configuration information for an action name, or returns null if the
354 * name is not recognized.
355 *
356 * @param name the name of the action
357 * @param namespace the namespace for the action or null for the empty namespace, ""
358 * @return the configuration information for action requested
359 */
360 public synchronized ActionConfig getActionConfig(String namespace, String name) {
361 ActionConfig config = findActionConfigInNamespace(namespace, name);
362
363 // try wildcarded namespaces
364 if (config == null) {
365 NamespaceMatch match = namespaceMatcher.match(namespace);
366 if (match != null) {
367 config = findActionConfigInNamespace(match.getPattern(), name);
368
369 // If config found, place all the matches found in the namespace processing in the action's parameters
370 if (config != null) {
371 config = new ActionConfig.Builder(config)
372 .addParams(match.getVariables())
373 .build();
374 }
375 }
376 }
377
378 // fail over to empty namespace
379 if ((config == null) && (namespace != null) && (!namespace.trim().equals(""))) {
380 config = findActionConfigInNamespace("", name);
381 }
382
383
384 return config;
385 }
386
387 ActionConfig findActionConfigInNamespace(String namespace, String name) {
388 ActionConfig config = null;
389 if (namespace == null) {
390 namespace = "";
391 }
392 Map<String, ActionConfig> actions = namespaceActionConfigs.get(namespace);
393 if (actions != null) {
394 config = actions.get(name);
395 // Check wildcards
396 if (config == null) {
397 config = namespaceActionConfigMatchers.get(namespace).match(name);
398 // fail over to default action
399 if (config == null) {
400 String defaultActionRef = namespaceConfigs.get(namespace);
401 if (defaultActionRef != null) {
402 config = actions.get(defaultActionRef);
403 }
404 }
405 }
406 }
407 return config;
408 }
409
410 /**
411 * Gets the configuration settings for every action.
412 *
413 * @return a Map of namespace - > Map of ActionConfig objects, with the key being the action name
414 */
415 public synchronized Map getActionConfigs() {
416 return namespaceActionConfigs;
417 }
418
419 public String toString() {
420 StringBuffer buff = new StringBuffer("RuntimeConfiguration - actions are\n");
421
422 for (String namespace : namespaceActionConfigs.keySet()) {
423 Map<String, ActionConfig> actionConfigs = namespaceActionConfigs.get(namespace);
424
425 for (String s : actionConfigs.keySet()) {
426 buff.append(namespace).append("/").append(s).append("\n");
427 }
428 }
429
430 return buff.toString();
431 }
432 }
433
434 class ContainerProperties extends LocatableProperties {
435 private static final long serialVersionUID = -7320625750836896089L;
436
437 public Object setProperty(String key, String value) {
438 String oldValue = getProperty(key);
439 if (oldValue != null && !oldValue.equals(value) && !defaultFrameworkBeanName.equals(oldValue)) {
440 LOG.info("Overriding property "+key+" - old value: "+oldValue+" new value: "+value);
441 }
442 return super.setProperty(key, value);
443 }
444
445 public void setConstants(ContainerBuilder builder) {
446 for (Object keyobj : keySet()) {
447 String key = (String)keyobj;
448 builder.factory(String.class, key,
449 new LocatableConstantFactory<String>(getProperty(key), getPropertyLocation(key)));
450 }
451 }
452 }
453 }