1 /*
2 * Copyright (c) 2002-2006 by OpenSymphony
3 * All rights reserved.
4 */
5 package com.opensymphony.xwork2.config.providers;
6
7 import com.opensymphony.xwork2.Action;
8 import com.opensymphony.xwork2.ObjectFactory;
9 import com.opensymphony.xwork2.XWorkException;
10 import com.opensymphony.xwork2.config.Configuration;
11 import com.opensymphony.xwork2.config.ConfigurationException;
12 import com.opensymphony.xwork2.config.ConfigurationProvider;
13 import com.opensymphony.xwork2.config.ConfigurationUtil;
14 import com.opensymphony.xwork2.config.entities;
15 import com.opensymphony.xwork2.config.entities.UnknownHandlerConfig;
16 import com.opensymphony.xwork2.config.impl.LocatableFactory;
17 import com.opensymphony.xwork2.inject.Container;
18 import com.opensymphony.xwork2.inject.ContainerBuilder;
19 import com.opensymphony.xwork2.inject.Inject;
20 import com.opensymphony.xwork2.inject.Scope;
21 import com.opensymphony.xwork2.util;
22 import com.opensymphony.xwork2.util.location.LocatableProperties;
23 import com.opensymphony.xwork2.util.location.Location;
24 import com.opensymphony.xwork2.util.location.LocationUtils;
25 import com.opensymphony.xwork2.util.logging.Logger;
26 import com.opensymphony.xwork2.util.logging.LoggerFactory;
27 import org.w3c.dom.Document;
28 import org.w3c.dom.Element;
29 import org.w3c.dom.Node;
30 import org.w3c.dom.NodeList;
31 import org.xml.sax.InputSource;
32 import org.apache.commons.lang.StringUtils;
33
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.lang.reflect.Modifier;
37 import java.net.URL;
38 import java.util;
39
40
41 /**
42 * Looks in the classpath for an XML file, "xwork.xml" by default,
43 * and uses it for the XWork configuration.
44 *
45 * @author tmjee
46 * @author Rainer Hermanns
47 * @author Neo
48 * @version $Revision: 1985 $
49 */
50 public class XmlConfigurationProvider implements ConfigurationProvider {
51
52 private static final Logger LOG = LoggerFactory.getLogger(XmlConfigurationProvider.class);
53
54 private List<Document> documents;
55 private Set<String> includedFileNames;
56 private String configFileName;
57 private ObjectFactory objectFactory;
58
59 private Set<String> loadedFileUrls = new HashSet<String>();
60 private boolean errorIfMissing;
61 private Map<String, String> dtdMappings;
62 private Configuration configuration;
63 private boolean throwExceptionOnDuplicateBeans = true;
64
65 public XmlConfigurationProvider() {
66 this("xwork.xml", true);
67 }
68
69 public XmlConfigurationProvider(String filename) {
70 this(filename, true);
71 }
72
73 public XmlConfigurationProvider(String filename, boolean errorIfMissing) {
74 this.configFileName = filename;
75 this.errorIfMissing = errorIfMissing;
76
77 Map<String, String> mappings = new HashMap<String, String>();
78 mappings.put("-//OpenSymphony Group//XWork 2.1.3//EN", "xwork-2.1.3.dtd");
79 mappings.put("-//OpenSymphony Group//XWork 2.1//EN", "xwork-2.1.dtd");
80 mappings.put("-//OpenSymphony Group//XWork 2.0//EN", "xwork-2.0.dtd");
81 mappings.put("-//OpenSymphony Group//XWork 1.1.1//EN", "xwork-1.1.1.dtd");
82 mappings.put("-//OpenSymphony Group//XWork 1.1//EN", "xwork-1.1.dtd");
83 mappings.put("-//OpenSymphony Group//XWork 1.0//EN", "xwork-1.0.dtd");
84 setDtdMappings(mappings);
85 }
86
87 public void setThrowExceptionOnDuplicateBeans(boolean val) {
88 this.throwExceptionOnDuplicateBeans = val;
89 }
90
91 public void setDtdMappings(Map<String, String> mappings) {
92 this.dtdMappings = Collections.unmodifiableMap(mappings);
93 }
94
95 @Inject
96 public void setObjectFactory(ObjectFactory objectFactory) {
97 this.objectFactory = objectFactory;
98 }
99
100 /**
101 * Returns an unmodifiable map of DTD mappings
102 */
103 public Map<String, String> getDtdMappings() {
104 return dtdMappings;
105 }
106
107 public void init(Configuration configuration) {
108 this.configuration = configuration;
109 this.includedFileNames = configuration.getLoadedFileNames();
110 loadDocuments(configFileName);
111 }
112
113 public void destroy() {
114 }
115
116 @Override
117 public boolean equals(Object o) {
118 if (this == o) {
119 return true;
120 }
121
122 if (!(o instanceof XmlConfigurationProvider)) {
123 return false;
124 }
125
126 final XmlConfigurationProvider xmlConfigurationProvider = (XmlConfigurationProvider) o;
127
128 if ((configFileName != null) ? (!configFileName.equals(xmlConfigurationProvider.configFileName)) : (xmlConfigurationProvider.configFileName != null)) {
129 return false;
130 }
131
132 return true;
133 }
134
135 @Override
136 public int hashCode() {
137 return ((configFileName != null) ? configFileName.hashCode() : 0);
138 }
139
140 private void loadDocuments(String configFileName) {
141 try {
142 loadedFileUrls.clear();
143 documents = loadConfigurationFiles(configFileName, null);
144 } catch (ConfigurationException e) {
145 throw e;
146 } catch (Exception e) {
147 throw new ConfigurationException("Error loading configuration file " + configFileName, e);
148 }
149 }
150
151 public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException {
152 LOG.info("Parsing configuration file [" + configFileName + "]");
153 Map<String, Node> loadedBeans = new HashMap<String, Node>();
154 for (Document doc : documents) {
155 Element rootElement = doc.getDocumentElement();
156 NodeList children = rootElement.getChildNodes();
157 int childSize = children.getLength();
158
159 for (int i = 0; i < childSize; i++) {
160 Node childNode = children.item(i);
161
162 if (childNode instanceof Element) {
163 Element child = (Element) childNode;
164
165 final String nodeName = child.getNodeName();
166
167 if ("bean".equals(nodeName)) {
168 String type = child.getAttribute("type");
169 String name = child.getAttribute("name");
170 String impl = child.getAttribute("class");
171 String onlyStatic = child.getAttribute("static");
172 String scopeStr = child.getAttribute("scope");
173 boolean optional = "true".equals(child.getAttribute("optional"));
174 Scope scope = Scope.SINGLETON;
175 if ("default".equals(scopeStr)) {
176 scope = Scope.DEFAULT;
177 } else if ("request".equals(scopeStr)) {
178 scope = Scope.REQUEST;
179 } else if ("session".equals(scopeStr)) {
180 scope = Scope.SESSION;
181 } else if ("singleton".equals(scopeStr)) {
182 scope = Scope.SINGLETON;
183 } else if ("thread".equals(scopeStr)) {
184 scope = Scope.THREAD;
185 }
186
187 if (StringUtils.isEmpty(name)) {
188 name = Container.DEFAULT_NAME;
189 }
190
191 try {
192 Class cimpl = ClassLoaderUtil.loadClass(impl, getClass());
193 Class ctype = cimpl;
194 if (StringUtils.isNotEmpty(type)) {
195 ctype = ClassLoaderUtil.loadClass(type, getClass());
196 }
197 if ("true".equals(onlyStatic)) {
198 // Force loading of class to detect no class def found exceptions
199 cimpl.getDeclaredClasses();
200 containerBuilder.injectStatics(cimpl);
201 } else {
202 if (containerBuilder.contains(ctype, name)) {
203 Location loc = LocationUtils.getLocation(loadedBeans.get(ctype.getName() + name));
204 if (throwExceptionOnDuplicateBeans) {
205 throw new ConfigurationException("Bean type " + ctype + " with the name " +
206 name + " has already been loaded by " + loc, child);
207 }
208 }
209
210 // Force loading of class to detect no class def found exceptions
211 cimpl.getDeclaredConstructors();
212
213 if (LOG.isDebugEnabled()) {
214 LOG.debug("Loaded type:" + type + " name:" + name + " impl:" + impl);
215 }
216 containerBuilder.factory(ctype, name, new LocatableFactory(name, ctype, cimpl, scope, childNode), scope);
217 }
218 loadedBeans.put(ctype.getName() + name, child);
219 } catch (Throwable ex) {
220 if (!optional) {
221 throw new ConfigurationException("Unable to load bean: type:" + type + " class:" + impl, ex, childNode);
222 } else {
223 LOG.debug("Unable to load optional class: " + ex);
224 }
225 }
226 } else if ("constant".equals(nodeName)) {
227 String name = child.getAttribute("name");
228 String value = child.getAttribute("value");
229 props.setProperty(name, value, childNode);
230 } else if (nodeName.equals("unknown-handler-stack")) {
231 List<UnknownHandlerConfig> unknownHandlerStack = new ArrayList<UnknownHandlerConfig>();
232 NodeList unknownHandlers = child.getElementsByTagName("unknown-handler-ref");
233 int unknownHandlersSize = unknownHandlers.getLength();
234
235 for (int k = 0; k < unknownHandlersSize; k++) {
236 Element unknownHandler = (Element) unknownHandlers.item(k);
237 unknownHandlerStack.add(new UnknownHandlerConfig(unknownHandler.getAttribute("name")));
238 }
239
240 if (!unknownHandlerStack.isEmpty())
241 configuration.setUnknownHandlerStack(unknownHandlerStack);
242 }
243 }
244 }
245 }
246 }
247
248 public void loadPackages() throws ConfigurationException {
249 List<Element> reloads = new ArrayList<Element>();
250 for (Document doc : documents) {
251 Element rootElement = doc.getDocumentElement();
252 NodeList children = rootElement.getChildNodes();
253 int childSize = children.getLength();
254
255 for (int i = 0; i < childSize; i++) {
256 Node childNode = children.item(i);
257
258 if (childNode instanceof Element) {
259 Element child = (Element) childNode;
260
261 final String nodeName = child.getNodeName();
262
263 if ("package".equals(nodeName)) {
264 PackageConfig cfg = addPackage(child);
265 if (cfg.isNeedsRefresh()) {
266 reloads.add(child);
267 }
268 }
269 }
270 }
271 loadExtraConfiguration(doc);
272 }
273
274 if (reloads.size() > 0) {
275 reloadRequiredPackages(reloads);
276 }
277
278 for (Document doc : documents) {
279 loadExtraConfiguration(doc);
280 }
281
282 documents.clear();
283 configuration = null;
284 }
285
286 private void reloadRequiredPackages(List<Element> reloads) {
287 if (reloads.size() > 0) {
288 List<Element> result = new ArrayList<Element>();
289 for (Element pkg : reloads) {
290 PackageConfig cfg = addPackage(pkg);
291 if (cfg.isNeedsRefresh()) {
292 result.add(pkg);
293 }
294 }
295 if ((result.size() > 0) && (result.size() != reloads.size())) {
296 reloadRequiredPackages(result);
297 return;
298 }
299
300 // Print out error messages for all misconfigured inheritence packages
301 if (result.size() > 0) {
302 for (Element rp : result) {
303 String parent = rp.getAttribute("extends");
304 if (parent != null) {
305 List<PackageConfig> parents = ConfigurationUtil.buildParentsFromString(configuration, parent);
306 if (parents != null && parents.size() <= 0) {
307 LOG.error("Unable to find parent packages " + parent);
308 }
309 }
310 }
311 }
312 }
313 }
314
315 /**
316 * Tells whether the ConfigurationProvider should reload its configuration. This method should only be called
317 * if ConfigurationManager.isReloadingConfigs() is true.
318 *
319 * @return true if the file has been changed since the last time we read it
320 */
321 public boolean needsReload() {
322
323 for (String url : loadedFileUrls) {
324 if (FileManager.fileNeedsReloading(url)) {
325 return true;
326 }
327 }
328 return false;
329 }
330
331 protected void addAction(Element actionElement, PackageConfig.Builder packageContext) throws ConfigurationException {
332 String name = actionElement.getAttribute("name");
333 String className = actionElement.getAttribute("class");
334 String methodName = actionElement.getAttribute("method");
335 Location location = DomHelper.getLocationObject(actionElement);
336
337 if (location == null) {
338 LOG.warn("location null for " + className);
339 }
340 //methodName should be null if it's not set
341 methodName = (methodName.trim().length() > 0) ? methodName.trim() : null;
342
343 // if there isnt a class name specified for an <action/> then try to
344 // use the default-class-ref from the <package/>
345 if (StringUtils.isEmpty(className)) {
346 // if there is a package default-class-ref use that, otherwise use action support
347 /* if (StringUtils.isNotEmpty(packageContext.getDefaultClassRef())) {
348 className = packageContext.getDefaultClassRef();
349 } else {
350 className = ActionSupport.class.getName();
351 }*/
352
353 } else {
354 if (!verifyAction(className, name, location)) {
355 if (LOG.isErrorEnabled())
356 LOG.error("Unable to verify action [#0] with class [#1], from [#2]", name, className, location.toString());
357 return;
358 }
359 }
360
361
362
363 Map<String, ResultConfig> results;
364 try {
365 results = buildResults(actionElement, packageContext);
366 } catch (ConfigurationException e) {
367 throw new ConfigurationException("Error building results for action " + name + " in namespace " + packageContext.getNamespace(), e, actionElement);
368 }
369
370 List<InterceptorMapping> interceptorList = buildInterceptorList(actionElement, packageContext);
371
372 List<ExceptionMappingConfig> exceptionMappings = buildExceptionMappings(actionElement, packageContext);
373
374 ActionConfig actionConfig = new ActionConfig.Builder(packageContext.getName(), name, className)
375 .methodName(methodName)
376 .addResultConfigs(results)
377 .addInterceptors(interceptorList)
378 .addExceptionMappings(exceptionMappings)
379 .addParams(XmlHelper.getParams(actionElement))
380 .location(location)
381 .build();
382 packageContext.addActionConfig(name, actionConfig);
383
384 if (LOG.isDebugEnabled()) {
385 LOG.debug("Loaded " + (StringUtils.isNotEmpty(packageContext.getNamespace()) ? (packageContext.getNamespace() + "/") : "") + name + " in '" + packageContext.getName() + "' package:" + actionConfig);
386 }
387 }
388
389 protected boolean verifyAction(String className, String name, Location loc) {
390 if (className.indexOf('{') > -1) {
391 if (LOG.isDebugEnabled()) {
392 LOG.debug("Action class [" + className + "] contains a wildcard " +
393 "replacement value, so it can't be verified");
394 }
395 return true;
396 }
397 try {
398 if (objectFactory.isNoArgConstructorRequired()) {
399 Class clazz = objectFactory.getClassInstance(className);
400 if (!Modifier.isPublic(clazz.getModifiers())) {
401 throw new ConfigurationException("Action class [" + className + "] is not public", loc);
402 }
403 clazz.getConstructor(new Class[]{});
404 }
405 } catch (ClassNotFoundException e) {
406 if (LOG.isDebugEnabled()) {
407 LOG.debug("Class not found for action [" + className + "]", e);
408 }
409 throw new ConfigurationException("Action class [" + className + "] not found", loc);
410 } catch (NoSuchMethodException e) {
411 if (LOG.isDebugEnabled()) {
412 LOG.debug("No constructor found for action [" + className + "]", e);
413 }
414 throw new ConfigurationException("Action class [" + className + "] does not have a public no-arg constructor", e, loc);
415 } catch (RuntimeException ex) {
416 // Probably not a big deal, like request or session-scoped Spring 2 beans that need a real request
417 LOG.info("Unable to verify action class [" + className + "] exists at initialization");
418 if (LOG.isDebugEnabled()) {
419 LOG.debug("Action verification cause", ex);
420 }
421 } catch (Exception ex) {
422 // Default to failing fast
423 if (LOG.isDebugEnabled()) {
424 LOG.debug("Unable to verify action class [" + className + "]", ex);
425 }
426 throw new ConfigurationException(ex, loc);
427 }
428 return true;
429 }
430
431 /**
432 * Create a PackageConfig from an XML element representing it.
433 */
434 protected PackageConfig addPackage(Element packageElement) throws ConfigurationException {
435 PackageConfig.Builder newPackage = buildPackageContext(packageElement);
436
437 if (newPackage.isNeedsRefresh()) {
438 return newPackage.build();
439 }
440
441 if (LOG.isDebugEnabled()) {
442 LOG.debug("Loaded " + newPackage);
443 }
444
445 // add result types (and default result) to this package
446 addResultTypes(newPackage, packageElement);
447
448 // load the interceptors and interceptor stacks for this package
449 loadInterceptors(newPackage, packageElement);
450
451 // load the default interceptor reference for this package
452 loadDefaultInterceptorRef(newPackage, packageElement);
453
454 // load the default class ref for this package
455 loadDefaultClassRef(newPackage, packageElement);
456
457 // load the global result list for this package
458 loadGlobalResults(newPackage, packageElement);
459
460 // load the global exception handler list for this package
461 loadGobalExceptionMappings(newPackage, packageElement);
462
463 // get actions
464 NodeList actionList = packageElement.getElementsByTagName("action");
465
466 for (int i = 0; i < actionList.getLength(); i++) {
467 Element actionElement = (Element) actionList.item(i);
468 addAction(actionElement, newPackage);
469 }
470
471 // load the default action reference for this package
472 loadDefaultActionRef(newPackage, packageElement);
473
474 PackageConfig cfg = newPackage.build();
475 configuration.addPackageConfig(cfg.getName(), cfg);
476 return cfg;
477 }
478
479 protected void addResultTypes(PackageConfig.Builder packageContext, Element element) {
480 NodeList resultTypeList = element.getElementsByTagName("result-type");
481
482 for (int i = 0; i < resultTypeList.getLength(); i++) {
483 Element resultTypeElement = (Element) resultTypeList.item(i);
484 String name = resultTypeElement.getAttribute("name");
485 String className = resultTypeElement.getAttribute("class");
486 String def = resultTypeElement.getAttribute("default");
487
488 Location loc = DomHelper.getLocationObject(resultTypeElement);
489
490 Class clazz = verifyResultType(className, loc);
491 if (clazz != null) {
492 String paramName = null;
493 try {
494 paramName = (String) clazz.getField("DEFAULT_PARAM").get(null);
495 }
496 catch (Throwable t) {
497 // if we get here, the result type doesn't have a default param defined.
498 }
499 ResultTypeConfig.Builder resultType = new ResultTypeConfig.Builder(name, className).defaultResultParam(paramName)
500 .location(DomHelper.getLocationObject(resultTypeElement));
501
502 Map<String, String> params = XmlHelper.getParams(resultTypeElement);
503
504 if (!params.isEmpty()) {
505 resultType.addParams(params);
506 }
507 packageContext.addResultTypeConfig(resultType.build());
508
509 // set the default result type
510 if ("true".equals(def)) {
511 packageContext.defaultResultType(name);
512 }
513 }
514 }
515 }
516
517 protected Class verifyResultType(String className, Location loc) {
518 try {
519 return objectFactory.getClassInstance(className);
520 } catch (ClassNotFoundException e) {
521 LOG.warn("Result class [" + className + "] doesn't exist (ClassNotFoundException) at " +
522 loc.toString() + ", ignoring", e);
523 } catch (NoClassDefFoundError e) {
524 LOG.warn("Result class [" + className + "] doesn't exist (NoClassDefFoundError) at " +
525 loc.toString() + ", ignoring", e);
526 }
527
528 return null;
529 }
530
531 protected List<InterceptorMapping> buildInterceptorList(Element element, PackageConfig.Builder context) throws ConfigurationException {
532 List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>();
533 NodeList interceptorRefList = element.getElementsByTagName("interceptor-ref");
534
535 for (int i = 0; i < interceptorRefList.getLength(); i++) {
536 Element interceptorRefElement = (Element) interceptorRefList.item(i);
537
538 if (interceptorRefElement.getParentNode().equals(element) || interceptorRefElement.getParentNode().getNodeName().equals(element.getNodeName())) {
539 List<InterceptorMapping> interceptors = lookupInterceptorReference(context, interceptorRefElement);
540 interceptorList.addAll(interceptors);
541 }
542 }
543
544 return interceptorList;
545 }
546
547 /**
548 * This method builds a package context by looking for the parents of this new package.
549 * <p/>
550 * If no parents are found, it will return a root package.
551 */
552 protected PackageConfig.Builder buildPackageContext(Element packageElement) {
553 String parent = packageElement.getAttribute("extends");
554 String abstractVal = packageElement.getAttribute("abstract");
555 boolean isAbstract = Boolean.valueOf(abstractVal).booleanValue();
556 String name = StringUtils.defaultString(packageElement.getAttribute("name"));
557 String namespace = StringUtils.defaultString(packageElement.getAttribute("namespace"));
558
559
560 if (StringUtils.isNotEmpty(packageElement.getAttribute("externalReferenceResolver"))) {
561 throw new ConfigurationException("The 'externalReferenceResolver' attribute has been removed. Please use " +
562 "a custom ObjectFactory or Interceptor.", packageElement);
563 }
564
565 PackageConfig.Builder cfg = new PackageConfig.Builder(name)
566 .namespace(namespace)
567 .isAbstract(isAbstract)
568 .location(DomHelper.getLocationObject(packageElement));
569
570
571 if (StringUtils.isNotEmpty(StringUtils.defaultString(parent))) { // has parents, let's look it up
572
573 List<PackageConfig> parents = ConfigurationUtil.buildParentsFromString(configuration, parent);
574
575 if (parents.size() <= 0) {
576 cfg.needsRefresh(true);
577 } else {
578 cfg.addParents(parents);
579 }
580 }
581
582 return cfg;
583 }
584
585 /**
586 * Build a map of ResultConfig objects from below a given XML element.
587 */
588 protected Map<String, ResultConfig> buildResults(Element element, PackageConfig.Builder packageContext) {
589 NodeList resultEls = element.getElementsByTagName("result");
590
591 Map<String, ResultConfig> results = new LinkedHashMap<String, ResultConfig>();
592
593 for (int i = 0; i < resultEls.getLength(); i++) {
594 Element resultElement = (Element) resultEls.item(i);
595
596 if (resultElement.getParentNode().equals(element) || resultElement.getParentNode().getNodeName().equals(element.getNodeName())) {
597 String resultName = resultElement.getAttribute("name");
598 String resultType = resultElement.getAttribute("type");
599
600 // if you don't specify a name on <result/>, it defaults to "success"
601 if (StringUtils.isEmpty(resultName)) {
602 resultName = Action.SUCCESS;
603 }
604
605 // there is no result type, so let's inherit from the parent package
606 if (StringUtils.isEmpty(resultType)) {
607 resultType = packageContext.getFullDefaultResultType();
608
609 // now check if there is a result type now
610 if (StringUtils.isEmpty(resultType)) {
611 // uh-oh, we have a problem
612 throw new ConfigurationException("No result type specified for result named '"
613 + resultName + "', perhaps the parent package does not specify the result type?", resultElement);
614 }
615 }
616
617
618 ResultTypeConfig config = packageContext.getResultType(resultType);
619
620 if (config == null) {
621 throw new ConfigurationException("There is no result type defined for type '" + resultType
622 + "' mapped with name '" + resultName + "'."
623 + " Did you mean '" + guessResultType(resultType) + "'?", resultElement);
624 }
625
626 String resultClass = config.getClazz();
627
628 // invalid result type specified in result definition
629 if (resultClass == null) {
630 throw new ConfigurationException("Result type '" + resultType + "' is invalid");
631 }
632
633 Map<String, String> resultParams = XmlHelper.getParams(resultElement);
634
635 if (resultParams.size() == 0) // maybe we just have a body - therefore a default parameter
636 {
637 // if <result ...>something</result> then we add a parameter of 'something' as this is the most used result param
638 if (resultElement.getChildNodes().getLength() >= 1) {
639 resultParams = new LinkedHashMap<String, String>();
640
641 String paramName = config.getDefaultResultParam();
642 if (paramName != null) {
643 StringBuilder paramValue = new StringBuilder();
644 for (int j = 0; j < resultElement.getChildNodes().getLength(); j++) {
645 if (resultElement.getChildNodes().item(j).getNodeType() == Node.TEXT_NODE) {
646 String val = resultElement.getChildNodes().item(j).getNodeValue();
647 if (val != null) {
648 paramValue.append(val);
649 }
650 }
651 }
652 String val = paramValue.toString().trim();
653 if (val.length() > 0) {
654 resultParams.put(paramName, val);
655 }
656 } else {
657 LOG.warn("no default parameter defined for result of type " + config.getName());
658 }
659 }
660 }
661
662 // create new param map, so that the result param can override the config param
663 Map<String, String> params = new LinkedHashMap<String, String>();
664 Map<String, String> configParams = config.getParams();
665 if (configParams != null) {
666 params.putAll(configParams);
667 }
668 params.putAll(resultParams);
669
670 ResultConfig resultConfig = new ResultConfig.Builder(resultName, resultClass)
671 .addParams(params)
672 .location(DomHelper.getLocationObject(element))
673 .build();
674 results.put(resultConfig.getName(), resultConfig);
675 }
676 }
677
678 return results;
679 }
680
681 protected String guessResultType(String type) {
682 StringBuilder sb = null;
683 if (type != null) {
684 sb = new StringBuilder();
685 boolean capNext = false;
686 for (int x=0; x<type.length(); x++) {
687 char c = type.charAt(x);
688 if (c == '-') {
689 capNext = true;
690 continue;
691 } else if (Character.isLowerCase(c) && capNext) {
692 c = Character.toUpperCase(c);
693 capNext = false;
694 }
695 sb.append(c);
696 }
697 }
698 return (sb != null ? sb.toString() : null);
699 }
700
701 /**
702 * Build a map of ResultConfig objects from below a given XML element.
703 */
704 protected List<ExceptionMappingConfig> buildExceptionMappings(Element element, PackageConfig.Builder packageContext) {
705 NodeList exceptionMappingEls = element.getElementsByTagName("exception-mapping");
706
707 List<ExceptionMappingConfig> exceptionMappings = new ArrayList<ExceptionMappingConfig>();
708
709 for (int i = 0; i < exceptionMappingEls.getLength(); i++) {
710 Element ehElement = (Element) exceptionMappingEls.item(i);
711
712 if (ehElement.getParentNode().equals(element) || ehElement.getParentNode().getNodeName().equals(element.getNodeName())) {
713 String emName = ehElement.getAttribute("name");
714 String exceptionClassName = ehElement.getAttribute("exception");
715 String exceptionResult = ehElement.getAttribute("result");
716
717 Map<String, String> params = XmlHelper.getParams(ehElement);
718
719 if (StringUtils.isEmpty(emName)) {
720 emName = exceptionResult;
721 }
722
723 ExceptionMappingConfig ehConfig = new ExceptionMappingConfig.Builder(emName, exceptionClassName, exceptionResult)
724 .addParams(params)
725 .location(DomHelper.getLocationObject(ehElement))
726 .build();
727 exceptionMappings.add(ehConfig);
728 }
729 }
730
731 return exceptionMappings;
732 }
733
734
735 protected void loadDefaultInterceptorRef(PackageConfig.Builder packageContext, Element element) {
736 NodeList resultTypeList = element.getElementsByTagName("default-interceptor-ref");
737
738 if (resultTypeList.getLength() > 0) {
739 Element defaultRefElement = (Element) resultTypeList.item(0);
740 packageContext.defaultInterceptorRef(defaultRefElement.getAttribute("name"));
741 }
742 }
743
744 protected void loadDefaultActionRef(PackageConfig.Builder packageContext, Element element) {
745 NodeList resultTypeList = element.getElementsByTagName("default-action-ref");
746
747 if (resultTypeList.getLength() > 0) {
748 Element defaultRefElement = (Element) resultTypeList.item(0);
749 packageContext.defaultActionRef(defaultRefElement.getAttribute("name"));
750 }
751 }
752
753 /**
754 * Load all of the global results for this package from the XML element.
755 */
756 protected void loadGlobalResults(PackageConfig.Builder packageContext, Element packageElement) {
757 NodeList globalResultList = packageElement.getElementsByTagName("global-results");
758
759 if (globalResultList.getLength() > 0) {
760 Element globalResultElement = (Element) globalResultList.item(0);
761 Map<String, ResultConfig> results = buildResults(globalResultElement, packageContext);
762 packageContext.addGlobalResultConfigs(results);
763 }
764 }
765
766 protected void loadDefaultClassRef(PackageConfig.Builder packageContext, Element element) {
767 NodeList defaultClassRefList = element.getElementsByTagName("default-class-ref");
768 if (defaultClassRefList.getLength() > 0) {
769 Element defaultClassRefElement = (Element) defaultClassRefList.item(0);
770 packageContext.defaultClassRef(defaultClassRefElement.getAttribute("class"));
771 }
772 }
773
774 /**
775 * Load all of the global results for this package from the XML element.
776 */
777 protected void loadGobalExceptionMappings(PackageConfig.Builder packageContext, Element packageElement) {
778 NodeList globalExceptionMappingList = packageElement.getElementsByTagName("global-exception-mappings");
779
780 if (globalExceptionMappingList.getLength() > 0) {
781 Element globalExceptionMappingElement = (Element) globalExceptionMappingList.item(0);
782 List<ExceptionMappingConfig> exceptionMappings = buildExceptionMappings(globalExceptionMappingElement, packageContext);
783 packageContext.addGlobalExceptionMappingConfigs(exceptionMappings);
784 }
785 }
786
787 // protected void loadIncludes(Element rootElement, DocumentBuilder db) throws Exception {
788 // NodeList includeList = rootElement.getElementsByTagName("include");
789 //
790 // for (int i = 0; i < includeList.getLength(); i++) {
791 // Element includeElement = (Element) includeList.item(i);
792 // String fileName = includeElement.getAttribute("file");
793 // includedFileNames.add(fileName);
794 // loadConfigurationFile(fileName, db);
795 // }
796 // }
797 protected InterceptorStackConfig loadInterceptorStack(Element element, PackageConfig.Builder context) throws ConfigurationException {
798 String name = element.getAttribute("name");
799
800 InterceptorStackConfig.Builder config = new InterceptorStackConfig.Builder(name)
801 .location(DomHelper.getLocationObject(element));
802 NodeList interceptorRefList = element.getElementsByTagName("interceptor-ref");
803
804 for (int j = 0; j < interceptorRefList.getLength(); j++) {
805 Element interceptorRefElement = (Element) interceptorRefList.item(j);
806 List<InterceptorMapping> interceptors = lookupInterceptorReference(context, interceptorRefElement);
807 config.addInterceptors(interceptors);
808 }
809
810 return config.build();
811 }
812
813 protected void loadInterceptorStacks(Element element, PackageConfig.Builder context) throws ConfigurationException {
814 NodeList interceptorStackList = element.getElementsByTagName("interceptor-stack");
815
816 for (int i = 0; i < interceptorStackList.getLength(); i++) {
817 Element interceptorStackElement = (Element) interceptorStackList.item(i);
818
819 InterceptorStackConfig config = loadInterceptorStack(interceptorStackElement, context);
820
821 context.addInterceptorStackConfig(config);
822 }
823 }
824
825 protected void loadInterceptors(PackageConfig.Builder context, Element element) throws ConfigurationException {
826 NodeList interceptorList = element.getElementsByTagName("interceptor");
827
828 for (int i = 0; i < interceptorList.getLength(); i++) {
829 Element interceptorElement = (Element) interceptorList.item(i);
830 String name = interceptorElement.getAttribute("name");
831 String className = interceptorElement.getAttribute("class");
832
833 Map<String, String> params = XmlHelper.getParams(interceptorElement);
834 InterceptorConfig config = new InterceptorConfig.Builder(name, className)
835 .addParams(params)
836 .location(DomHelper.getLocationObject(interceptorElement))
837 .build();
838
839 context.addInterceptorConfig(config);
840 }
841
842 loadInterceptorStacks(element, context);
843 }
844
845 // protected void loadPackages(Element rootElement) throws ConfigurationException {
846 // NodeList packageList = rootElement.getElementsByTagName("package");
847 //
848 // for (int i = 0; i < packageList.getLength(); i++) {
849 // Element packageElement = (Element) packageList.item(i);
850 // addPackage(packageElement);
851 // }
852 // }
853 private List<Document> loadConfigurationFiles(String fileName, Element includeElement) {
854 List<Document> docs = new ArrayList<Document>();
855 List<Document> finalDocs = new ArrayList<Document>();
856 if (!includedFileNames.contains(fileName)) {
857 if (LOG.isDebugEnabled()) {
858 LOG.debug("Loading action configurations from: " + fileName);
859 }
860
861 includedFileNames.add(fileName);
862
863 Iterator<URL> urls = null;
864 InputStream is = null;
865
866 IOException ioException = null;
867 try {
868 urls = getConfigurationUrls(fileName);
869 } catch (IOException ex) {
870 ioException = ex;
871 }
872
873 if (urls == null || !urls.hasNext()) {
874 if (errorIfMissing) {
875 throw new ConfigurationException("Could not open files of the name " + fileName, ioException);
876 } else {
877 LOG.info("Unable to locate configuration files of the name "
878 + fileName + ", skipping");
879 return docs;
880 }
881 }
882
883 URL url = null;
884 while (urls.hasNext()) {
885 try {
886 url = urls.next();
887 is = FileManager.loadFile(url);
888
889 InputSource in = new InputSource(is);
890
891 in.setSystemId(url.toString());
892
893 docs.add(DomHelper.parse(in, dtdMappings));
894 } catch (XWorkException e) {
895 if (includeElement != null) {
896 throw new ConfigurationException("Unable to load " + url, e, includeElement);
897 } else {
898 throw new ConfigurationException("Unable to load " + url, e);
899 }
900 } catch (Exception e) {
901 final String s = "Caught exception while loading file " + fileName;
902 throw new ConfigurationException(s, e, includeElement);
903 } finally {
904 if (is != null) {
905 try {
906 is.close();
907 } catch (IOException e) {
908 LOG.error("Unable to close input stream", e);
909 }
910 }
911 }
912 }
913
914 //sort the documents, according to the "order" attribute
915 Collections.sort(docs, new Comparator<Document>() {
916 public int compare(Document doc1, Document doc2) {
917 return XmlHelper.getLoadOrder(doc1).compareTo(XmlHelper.getLoadOrder(doc2));
918 }
919 });
920
921 for (Document doc : docs) {
922 Element rootElement = doc.getDocumentElement();
923 NodeList children = rootElement.getChildNodes();
924 int childSize = children.getLength();
925
926 for (int i = 0; i < childSize; i++) {
927 Node childNode = children.item(i);
928
929 if (childNode instanceof Element) {
930 Element child = (Element) childNode;
931
932 final String nodeName = child.getNodeName();
933
934 if ("include".equals(nodeName)) {
935 String includeFileName = child.getAttribute("file");
936 if (includeFileName.indexOf('*') != -1) {
937 // handleWildCardIncludes(includeFileName, docs, child);
938 ClassPathFinder wildcardFinder = new ClassPathFinder();
939 wildcardFinder.setPattern(includeFileName);
940 Vector<String> wildcardMatches = wildcardFinder.findMatches();
941 for (String match : wildcardMatches) {
942 finalDocs.addAll(loadConfigurationFiles(match, child));
943 }
944 } else {
945 finalDocs.addAll(loadConfigurationFiles(includeFileName, child));
946 }
947 }
948 }
949 }
950 finalDocs.add(doc);
951 loadedFileUrls.add(url.toString());
952 }
953
954 if (LOG.isDebugEnabled()) {
955 LOG.debug("Loaded action configuration from: " + fileName);
956 }
957 }
958 return finalDocs;
959 }
960
961 protected Iterator<URL> getConfigurationUrls(String fileName) throws IOException {
962 return ClassLoaderUtil.getResources(fileName, XmlConfigurationProvider.class, false);
963 }
964
965 /**
966 * Allows subclasses to load extra information from the document
967 *
968 * @param doc The configuration document
969 */
970 protected void loadExtraConfiguration(Document doc) {
971 // no op
972 }
973
974 /**
975 * Looks up the Interceptor Class from the interceptor-ref name and creates an instance, which is added to the
976 * provided List, or, if this is a ref to a stack, it adds the Interceptor instances from the List to this stack.
977 *
978 * @param interceptorRefElement Element to pull interceptor ref data from
979 * @param context The PackageConfig to lookup the interceptor from
980 * @return A list of Interceptor objects
981 */
982 private List<InterceptorMapping> lookupInterceptorReference(PackageConfig.Builder context, Element interceptorRefElement) throws ConfigurationException {
983 String refName = interceptorRefElement.getAttribute("name");
984 Map<String, String> refParams = XmlHelper.getParams(interceptorRefElement);
985
986 Location loc = LocationUtils.getLocation(interceptorRefElement);
987 return InterceptorBuilder.constructInterceptorReference(context, refName, refParams, loc, objectFactory);
988 }
989
990 List<Document> getDocuments() {
991 return documents;
992 }
993 }