001: // StrutsTestCase - a JUnit extension for testing Struts actions
002: // within the context of the ActionServlet.
003: // Copyright (C) 2002 Deryl Seale
004: //
005: // This library is free software; you can redistribute it and/or
006: // modify it under the terms of the Apache Software License as
007: // published by the Apache Software Foundation; either version 1.1
008: // of the License, or (at your option) any later version.
009: //
010: // This library is distributed in the hope that it will be useful,
011: // but WITHOUT ANY WARRANTY; without even the implied warranty of
012: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013: // Apache Software Foundation Licens for more details.
014: //
015: // You may view the full text here: http://www.apache.org/LICENSE.txt
016:
017: package servletunit.struts;
018:
019: import junit.framework.AssertionFailedError;
020: import org.apache.struts.action.*;
021: import org.apache.struts.tiles.*;
022: import org.apache.struts.config.ModuleConfig;
023: import org.apache.struts.config.ForwardConfig;
024: import org.apache.struts.config.ActionConfig;
025: import org.apache.struts.Globals;
026: import org.apache.struts.util.ModuleUtils;
027: import org.apache.commons.logging.Log;
028: import org.apache.commons.logging.LogFactory;
029: import org.apache.commons.collections.Transformer;
030: import org.apache.commons.collections.iterators.TransformIterator;
031: import org.apache.commons.collections.IteratorUtils;
032:
033: import javax.servlet.ServletConfig;
034: import javax.servlet.ServletContext;
035: import javax.servlet.http.HttpServletRequest;
036: import javax.servlet.http.HttpSession;
037: import java.util.Iterator;
038: import java.util.Arrays;
039:
040: /**
041: * Contains code common to both MockStrutsTestCase and CactusStrutsTestCase.
042: * It's always good to get rid of redundancy!
043: */
044: public class Common {
045:
046: protected static final String INCLUDE_SERVLET_PATH = RequestProcessor.INCLUDE_SERVLET_PATH;
047: protected static Log logger = LogFactory.getLog(Common.class);
048:
049: /**
050: * Common method to verify action errors and action messages.
051: */
052: protected static void verifyNoActionMessages(
053: HttpServletRequest request, String key, String messageLabel) {
054: if (logger.isTraceEnabled())
055: logger.trace("Entering - request = " + request + ", key = "
056: + key + ", messageLabel = " + messageLabel);
057: ActionMessages messages = (ActionMessages) request
058: .getAttribute(key);
059: if (logger.isDebugEnabled()) {
060: logger.debug("retrieved ActionMessages = " + messages);
061: }
062: if (messages != null) {
063: Iterator iterator = messages.get();
064: if (iterator.hasNext()) {
065: StringBuffer messageText = new StringBuffer();
066: while (iterator.hasNext()) {
067: messageText.append(" \"");
068: messageText
069: .append(((ActionMessage) iterator.next())
070: .getKey());
071: messageText.append("\"");
072: }
073: throw new AssertionFailedError("was expecting no "
074: + messageLabel + " messages, but received: "
075: + messageText.toString());
076: }
077: }
078: if (logger.isTraceEnabled())
079: logger.trace("Exiting");
080: }
081:
082: /**
083: * Common method to verify action errors and action messages.
084: */
085: protected static void verifyActionMessages(
086: HttpServletRequest request, String[] messageNames,
087: String key, String messageLabel) {
088: if (logger.isTraceEnabled())
089: logger.trace("Entering - request = " + request
090: + ", messageNames = " + messageNames + ", key = "
091: + key + ", messageLabel = " + messageLabel);
092:
093: ActionMessages messages = (ActionMessages) request
094: .getAttribute(key);
095: if (logger.isDebugEnabled()) {
096: logger.debug("retrieved ActionMessages = " + messages);
097: }
098:
099: if (messages == null) {
100: throw new AssertionFailedError("was expecting some "
101: + messageLabel + " messages, but received none.");
102: }
103: /* check length of messages as optimization */
104: else if (messages.size() != messageNames.length) {
105: throw new AssertionFailedError("was expecting "
106: + messageNames.length + " " + messageLabel
107: + " message(s), but received " + messages.size()
108: + " " + messageLabel + " message(s)");
109: } else {
110: /* alphabetize the two lists of message keys and compare them */
111:
112: Iterator iter = new TransformIterator(messages.get(),
113: new Transformer() {
114: public Object transform(Object input) {
115: return ((ActionMessage) input).getKey();
116: }
117: });
118:
119: String[] messageKeys = (String[]) IteratorUtils.toArray(
120: iter, String.class);
121:
122: Arrays.sort(messageKeys);
123: Arrays.sort(messageNames);
124:
125: for (int i = 0; i < messageNames.length; i++) {
126: if (!messageNames[i].equals(messageKeys[i])) {
127: StringBuffer mks = new StringBuffer();
128: StringBuffer mns = new StringBuffer();
129:
130: for (int j = 0; j < messageKeys.length; j++)
131: mks.append(messageKeys[j] + " ");
132: for (int k = 0; k < messageNames.length; k++)
133: mns.append(messageNames[k] + " ");
134:
135: throw new AssertionFailedError("received "
136: + messageLabel + " messages: (" + mks
137: + ") but expected (" + mns + ")");
138: }
139: }
140: }
141: if (logger.isTraceEnabled())
142: logger.trace("verifyActionMessages()");
143: }
144:
145: /**
146: * Retrieves a forward uri for tile - this is required for applications
147: * using the tiles framework, since the actual forward URI must
148: * be fetched from the tile definition.
149: */
150: protected static ComponentDefinition getTilesForward(
151: String forwardPath, HttpServletRequest request,
152: ServletContext context, ServletConfig config) {
153:
154: if (logger.isTraceEnabled())
155: logger.trace("Entering - forwardPath = " + forwardPath
156: + ", request = " + request + ", context = "
157: + context + ", config = " + config);
158:
159: String result = null;
160: try {
161: ComponentDefinition definition;
162: ComponentDefinition actionDefinition;
163:
164: // Get definition of tiles/component corresponding to uri.
165: definition = TilesUtil.getDefinition(forwardPath, request,
166: context);
167: if (definition != null) {
168: if (logger.isDebugEnabled()) {
169: logger.debug("found tiles definition - '"
170: + forwardPath + "' = '" + result + "'");
171: }
172: }
173:
174: actionDefinition = DefinitionsUtil
175: .getActionDefinition(request);
176: if (actionDefinition != null) {
177: if (logger.isDebugEnabled()) {
178: logger
179: .debug("found tiles definition for action - '"
180: + forwardPath
181: + "' = '"
182: + result
183: + "'");
184: }
185: }
186:
187: if (actionDefinition != null) {
188: if (logger.isDebugEnabled())
189: logger.debug("definition attributes: "
190: + actionDefinition.getAttributes());
191: return actionDefinition;
192: } else {
193: if (logger.isDebugEnabled() && (definition != null))
194: logger.debug("definition attributes: "
195: + definition.getAttributes());
196: return definition;
197: }
198: } catch (NoSuchDefinitionException nsde) {
199: if (logger.isTraceEnabled())
200: logger
201: .trace("Exiting - caught NoSuchDefinitionException");
202: return null;
203: } catch (DefinitionsFactoryException dfe) {
204: if (logger.isTraceEnabled())
205: logger
206: .trace("Exiting - caught DefinitionsFactoryException");
207: return null;
208: } catch (NullPointerException npe) {
209: // can happen if tiles is not at all used.
210: if (logger.isDebugEnabled()) {
211: logger.debug("Exiting - caught NullPointerException");
212: }
213: return null;
214: }
215: }
216:
217: /**
218: * Verifies that ActionServlet used this logical forward or input mapping with this tile definition.
219: *
220: * @throws AssertionFailedError if the expected and actual tiles definitions do not match.
221: */
222: protected static void verifyTilesForward(String actionPath,
223: String forwardName, String expectedDefinition,
224: boolean isInputPath, HttpServletRequest request,
225: ServletContext context, ServletConfig config) {
226: if (logger.isTraceEnabled())
227: logger.trace("Entering - actionPath = " + actionPath
228: + ", forwardName = " + forwardName
229: + ", expectedDefinition = " + expectedDefinition);
230:
231: String definitionName = null;
232:
233: if ((forwardName == null) && (isInputPath)) {
234: if (logger.isDebugEnabled()) {
235: logger.debug("processing an input forward");
236: }
237: forwardName = getActionConfig(actionPath, request, context)
238: .getInput();
239: if (logger.isDebugEnabled()) {
240: logger.debug("retrieved input forward name = "
241: + forwardName);
242: }
243: if (forwardName == null)
244: throw new AssertionFailedError(
245: "Trying to validate against an input mapping, but none is defined for this Action.");
246: ComponentDefinition definition = getTilesForward(
247: forwardName, request, context, config);
248: if (definition != null)
249: definitionName = definition.getName();
250: }
251:
252: if (!isInputPath) {
253: if (logger.isDebugEnabled()) {
254: logger.debug("processing normal forward");
255: }
256: ForwardConfig expectedForward = findForward(actionPath,
257: forwardName, request, context);
258: if (expectedForward == null)
259: throw new AssertionFailedError(
260: "Cannot find forward '"
261: + forwardName
262: + "' - it is possible that it is not mapped correctly.");
263: forwardName = expectedForward.getPath();
264: if (logger.isDebugEnabled()) {
265: logger.debug("retrieved forward name = " + forwardName);
266: }
267:
268: ComponentDefinition definition = getTilesForward(
269: forwardName, request, context, config);
270: if (definition != null)
271: definitionName = definition.getName();
272: }
273: if (definitionName == null)
274: throw new AssertionFailedError(
275: "Could not find tiles definition mapped to forward '"
276: + forwardName + "'");
277: if (!definitionName.equals(expectedDefinition))
278: throw new AssertionFailedError(
279: "Was expecting tiles definition '"
280: + expectedDefinition + "' but received '"
281: + definitionName + "'");
282: if (logger.isTraceEnabled())
283: logger.trace("Exiting");
284: }
285:
286: /**
287: * Verifies that ActionServlet used this logical forward or input mapping.
288: *
289: * @throws AssertionFailedError if expected and actual paths do not match.
290: */
291: protected static void verifyForwardPath(String actionPath,
292: String forwardName, String actualForwardPath,
293: boolean isInputPath, HttpServletRequest request,
294: ServletContext context, ServletConfig config) {
295:
296: if (logger.isTraceEnabled())
297: logger.trace("Entering - actionPath = " + actionPath
298: + ", forwardName = " + forwardName
299: + ", actualForwardPath = " + actualForwardPath);
300:
301: boolean usesTiles = false;
302: boolean useModules = false;
303:
304: if ((forwardName == null) && (isInputPath)) {
305: if (logger.isDebugEnabled()) {
306: logger.debug("processing an input forward");
307: }
308: forwardName = getActionConfig(actionPath, request, context)
309: .getInput();
310: if (logger.isDebugEnabled()) {
311: logger.debug("retrieved input forward name = "
312: + forwardName);
313: }
314: if (forwardName == null)
315: throw new AssertionFailedError(
316: "Trying to validate against an input mapping, but none is defined for this Action.");
317: String tilesForward = null;
318: ComponentDefinition definition = getTilesForward(
319: forwardName, request, context, config);
320: if (definition != null)
321: tilesForward = definition.getPath();
322: if (tilesForward != null) {
323: forwardName = tilesForward;
324: usesTiles = true;
325: if (logger.isDebugEnabled()) {
326: logger
327: .debug("retrieved tiles definition for forward = "
328: + forwardName);
329: }
330: }
331: }
332: if (!isInputPath) {
333: if (logger.isDebugEnabled()) {
334: logger.debug("processing normal forward");
335: }
336:
337: // check for a null forward, now allowed in Struts 1.1
338: if (forwardName == null) {
339: if (actualForwardPath == null)
340: return;
341: else
342: throw new AssertionFailedError(
343: "Expected a null forward from action, but received '"
344: + actualForwardPath + "'");
345: }
346:
347: ForwardConfig expectedForward = findForward(actionPath,
348: forwardName, request, context);
349: if (expectedForward == null)
350: throw new AssertionFailedError(
351: "Cannot find forward '"
352: + forwardName
353: + "' - it is possible that it is not mapped correctly.");
354: forwardName = expectedForward.getPath();
355: if (logger.isDebugEnabled()) {
356: logger.debug("retrieved forward name = " + forwardName);
357: }
358:
359: String tilesForward = null;
360: ComponentDefinition definition = getTilesForward(
361: forwardName, request, context, config);
362: if (definition != null)
363: tilesForward = definition.getPath();
364: if (tilesForward != null) {
365: forwardName = tilesForward;
366:
367: usesTiles = true;
368: if (logger.isDebugEnabled()) {
369: logger
370: .debug("retrieved tiles definition for forward = "
371: + forwardName);
372: }
373: }
374: // some fowards cross outside modules - check if we need the module
375: // in the path or not.
376: useModules = (expectedForward.getModule() == null);
377: }
378:
379: String moduleName = request.getServletPath() != null ? request
380: .getServletPath() : "";
381: if ((moduleName == null || moduleName.equalsIgnoreCase(""))
382: && request.getAttribute(INCLUDE_SERVLET_PATH) != null)
383: // check to see if this is a MockStrutsTestCase call
384: moduleName = (String) request
385: .getAttribute(INCLUDE_SERVLET_PATH);
386:
387: if ((moduleName != null) && (moduleName.length() > 0))
388: moduleName = moduleName.substring(moduleName.indexOf('/'),
389: moduleName.lastIndexOf('/'));
390: else
391: moduleName = "";
392:
393: if (!forwardName.startsWith("/"))
394: forwardName = "/" + forwardName;
395:
396: if (usesTiles)
397: forwardName = request.getContextPath() + forwardName;
398: else if (useModules || isInputPath)
399: forwardName = request.getContextPath() + moduleName
400: + forwardName;
401: else
402: forwardName = request.getContextPath() + forwardName;
403: if (logger.isDebugEnabled()) {
404: logger
405: .debug("added context path and module name to forward = "
406: + forwardName);
407: }
408: if (actualForwardPath == null) {
409: if (logger.isDebugEnabled()) {
410: logger
411: .debug("actualForwardPath is null - this usually means it is not mapped properly.");
412: }
413: throw new AssertionFailedError(
414: "Was expecting '"
415: + forwardName
416: + "' but it appears the Action has tried to return an ActionForward that is not mapped correctly.");
417: }
418: if (logger.isDebugEnabled()) {
419: logger.debug("expected forward = '" + forwardName
420: + "' - actual forward = '" + actualForwardPath
421: + "'");
422: }
423: if (!forwardName.equals(stripJSessionID(actualForwardPath)))
424: throw new AssertionFailedError("was expecting '"
425: + forwardName + "' but received '"
426: + actualForwardPath + "'");
427: if (logger.isTraceEnabled())
428: logger.trace("Exiting");
429: }
430:
431: /**
432: * Strips off *.do from action paths specified as such.
433: */
434: protected static String stripActionPath(String path) {
435: if (logger.isTraceEnabled())
436: logger.trace("Entering - path = " + path);
437: if (path == null)
438: return null;
439:
440: int slash = path.lastIndexOf("/");
441: int period = path.lastIndexOf(".");
442: if ((period >= 0) && (period > slash))
443: path = path.substring(0, period);
444: if (logger.isTraceEnabled())
445: logger.trace("Exiting - returning path = " + path);
446: return path;
447: }
448:
449: /**
450: * Strip ;jsessionid=<sessionid> from path.
451: * @return stripped path
452: */
453: protected static String stripJSessionID(String path) {
454: if (logger.isTraceEnabled())
455: logger.trace("Entering - path = " + path);
456: if (path == null)
457: return null;
458:
459: String pathCopy = path.toLowerCase();
460: int jsess_idx = pathCopy.indexOf(";jsessionid=");
461: if (jsess_idx > 0) {
462: StringBuffer buf = new StringBuffer(path);
463:
464: int queryIndex = pathCopy.indexOf("?");
465: // Strip jsessionid from obtained path, but keep query string
466: if (queryIndex > 0)
467: path = buf.delete(jsess_idx, queryIndex).toString();
468: // Strip jsessionid from obtained path
469: else
470: path = buf.delete(jsess_idx, buf.length()).toString();
471: }
472: if (logger.isTraceEnabled())
473: logger.trace("Exiting - returning path = " + path);
474: return path;
475: }
476:
477: /**
478: * Returns any ActionForm instance stored in the request or session, if available.
479: */
480: protected static ActionForm getActionForm(String actionPath,
481: HttpServletRequest request, ServletContext context) {
482: if (logger.isTraceEnabled())
483: logger.trace("Entering - actionPath = " + actionPath
484: + ", request = " + request + ", context = "
485: + context);
486: ActionForm form;
487: ActionConfig actionConfig = getActionConfig(actionPath,
488: request, context);
489: if ("request".equals(actionConfig.getScope())) {
490: if (logger.isDebugEnabled()) {
491: logger.debug("looking for form in request scope");
492: }
493: form = (ActionForm) request.getAttribute(actionConfig
494: .getAttribute());
495: } else {
496: if (logger.isDebugEnabled()) {
497: logger.debug("looking for form in session scope");
498: }
499: HttpSession session = request.getSession();
500: form = (ActionForm) session.getAttribute(actionConfig
501: .getAttribute());
502: }
503: if (logger.isTraceEnabled())
504: logger.trace("Exiting");
505: return form;
506: }
507:
508: /**
509: * Returns a ForwardConfig for the given forward name. This method first searches for the forward in the supplied
510: * action mapping. If it is not defined there, or if the mapping is not provided, it searches for it globally.
511: */
512: protected static ForwardConfig findForward(String mappingName,
513: String forwardName, HttpServletRequest request,
514: ServletContext context) {
515: if (logger.isTraceEnabled())
516: logger.trace("Entering - mappingName = " + mappingName
517: + ", forwardName = " + forwardName + ", request = "
518: + request + ", context = " + context);
519: ForwardConfig forward = null;
520: // first, look for forward in mapping (if it's defined)
521: if (mappingName != null) {
522: ActionConfig mapping = getActionConfig(mappingName,
523: request, context);
524: forward = mapping == null ? null : mapping
525: .findForwardConfig(forwardName);
526: }
527: // if it's not there, check for global forwards
528: if (forward == null) {
529: if (logger.isDebugEnabled())
530: logger.debug("looking for forward globally");
531: ModuleConfig moduleConfig = getModuleConfig(request,
532: context);
533: forward = moduleConfig.findForwardConfig(forwardName);
534: }
535: if (logger.isDebugEnabled()) {
536: logger.debug("retrieved forward = " + forward);
537: }
538: if (logger.isTraceEnabled())
539: logger.trace("Exiting");
540: return forward;
541: }
542:
543: /**
544: * Returns the configuration for the given action mapping.
545: */
546: protected static ActionConfig getActionConfig(String mappingName,
547: HttpServletRequest request, ServletContext context) {
548: if (logger.isTraceEnabled())
549: logger.trace("Entering - mappingName = " + mappingName
550: + ", request = " + request + ", context = "
551: + context);
552: ModuleConfig config = getModuleConfig(request, context);
553: ActionMapping actionMapping = (ActionMapping) config
554: .findActionConfig(mappingName);
555: if (logger.isDebugEnabled()) {
556: logger.debug("retrieved mapping = " + actionMapping);
557: }
558: if (logger.isTraceEnabled())
559: logger.trace("Exiting");
560: return actionMapping;
561: }
562:
563: /**
564: * Returns the configuration for the current module.
565: */
566: protected static ModuleConfig getModuleConfig(
567: HttpServletRequest request, ServletContext context) {
568: if (logger.isTraceEnabled())
569: logger.trace("Entering - request = " + request
570: + ", context = " + context);
571: if (logger.isDebugEnabled()) {
572: logger.debug("looking for config in request context");
573: }
574: ModuleConfig config = (ModuleConfig) request
575: .getAttribute(Globals.MODULE_KEY);
576: if (config == null) {
577: if (logger.isDebugEnabled()) {
578: logger
579: .debug("looking for config in application context");
580: }
581: config = (ModuleConfig) context
582: .getAttribute(Globals.MODULE_KEY);
583: }
584: if (logger.isTraceEnabled())
585: logger.trace("Exiting - returning " + config);
586: return config;
587:
588: }
589:
590: /**
591: * Sets an ActionForm instance in the request.
592: */
593: protected static void setActionForm(ActionForm form,
594: HttpServletRequest request, String actionPath,
595: ServletContext context) {
596: if (logger.isTraceEnabled())
597: logger.trace("Entering - form = " + form + ", request = "
598: + request + ", actionPath = " + actionPath
599: + ", context = " + context);
600:
601: if (actionPath == null || actionPath.equalsIgnoreCase(""))
602: throw new IllegalStateException(
603: "You must call setRequestPathInfo() before calling setActionForm()!");
604:
605: ActionConfig actionConfig = getActionConfig(actionPath,
606: request, context);
607: if (actionConfig == null) {
608: ModuleUtils moduleUtils = ModuleUtils.getInstance();
609: moduleUtils.selectModule(request, context);
610: actionConfig = getActionConfig(actionPath, request, context);
611: }
612:
613: if (actionConfig.getScope().equals("request")) {
614: if (logger.isDebugEnabled()) {
615: logger.debug("setting form in request context");
616: }
617: request.setAttribute(actionConfig.getAttribute(), form);
618: } else {
619: if (logger.isDebugEnabled()) {
620: logger.debug("setting form in session context");
621: }
622: request.getSession().setAttribute(
623: actionConfig.getAttribute(), form);
624: }
625: if (logger.isTraceEnabled())
626: logger.trace("Exiting");
627: }
628:
629: }
|