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