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