001: /*
002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
003: * Distributed under the terms of either:
004: * - the common development and distribution license (CDDL), v1.0; or
005: * - the GNU Lesser General Public License, v2.1 or later
006: * $Id: MockConversation.java 3634 2007-01-08 21:42:24Z gbevin $
007: */
008: package com.uwyn.rife.test;
009:
010: import com.uwyn.rife.engine.Gate;
011: import com.uwyn.rife.engine.Site;
012: import com.uwyn.rife.engine.exceptions.EngineException;
013: import com.uwyn.rife.tools.ArrayUtils;
014: import com.uwyn.rife.tools.StringUtils;
015: import java.io.UnsupportedEncodingException;
016: import java.net.URLDecoder;
017: import java.util.HashMap;
018: import java.util.List;
019: import java.util.Map;
020: import javax.servlet.http.Cookie;
021:
022: /**
023: * Simulates a conversation between a web browser and a servlet container.
024: * <p>Cookies will be remembered between requests and can be easily examined.
025: * To check which new cookies have been set during a request, the {@link
026: * MockResponse#getNewCookieNames} method can be used.
027: * <p>An instance of this class is tied to a regular {@link Site} structure
028: * instance. Your tests can thus reference existing site XML declarations,
029: * combine different sites into one, build a new site-structure on-the-fly in
030: * Java, modify existing element declarations, override property injections,
031: * ...
032: * <p>Note that RIFE relies on {@link com.uwyn.rife.engine.EngineClassLoader}
033: * to provide continuations functionalities to pure Java elements. If you want
034: * to test elements that use continuations, you have to make sure the first
035: * class in your test setup is loaded by {@link
036: * com.uwyn.rife.engine.EngineClassLoader}. The easiest way to do so is to run
037: * your main class with {@link RunWithEngineClassLoader}.
038: *
039: * @author Geert Bevin (gbevin[remove] at uwyn dot com)
040: * @version $Revision: 3634 $
041: * @since 1.1
042: */
043: public class MockConversation {
044: final static String SESSION_ID_COOKIE = "JSESSIONID";
045: final static String SESSION_ID_URL = "jsessionid";
046: final static String SESSION_URL_PREFIX = ";" + SESSION_ID_URL + "=";
047:
048: private Gate mGate = null;
049:
050: private HashMap<String, Cookie> mCookies = new HashMap<String, Cookie>();
051: private HashMap<String, MockSession> mSessions = new HashMap<String, MockSession>();
052: private String mScheme = "http";
053: private String mServerName = "localhost";
054: private int mServerPort = 80;
055: private String mContextPath = "";
056:
057: /**
058: * Creates a new <code>MockConversation</code> instance for a particular
059: * site.
060: *
061: * @param site the site structure that will be tested
062: * @since 1.1
063: */
064: public MockConversation(Site site) throws EngineException {
065: MockInitConfig initconfig = new MockInitConfig();
066: mGate = new Gate(site);
067: mGate.init(initconfig);
068: }
069:
070: /**
071: * Perform a request for a particular URL.
072: *
073: * @param url the url that should be tested
074: * @return the response of the request as a {@link MockResponse} instance;
075: * or
076: * <p><code>null</code> if the scheme, hostname and port don't correspond
077: * to the conversation setup
078: * @see #doRequest(String, MockRequest)
079: * @since 1.1
080: */
081: public MockResponse doRequest(String url) throws EngineException {
082: return doRequest(url, new MockRequest());
083: }
084:
085: /**
086: * Perform a request for a particular URL and request configuration.
087: * <p>The request can either be complete with the scheme and hostname, or
088: * an absolute path. These two URLs are thus considered the same:
089: * <pre>http://localhost/some/url?name1=value1&name2=value2</pre>
090: * <pre>/some/url?name1=value1&name2=value2</pre>
091: * <p>Note that when the complete URL form is used, it should correspond
092: * to the scheme, hostname and port configuration of this conversation.
093: *
094: * @param url the url that should be tested
095: * @param request the request that will be used
096: * @return the response of the request as a {@link MockResponse} instance;
097: * or
098: * <p><code>null</code> if the scheme, hostname and port don't correspond
099: * to the conversation setup
100: * @see #doRequest(String)
101: * @since 1.1
102: */
103: public MockResponse doRequest(String url, MockRequest request)
104: throws EngineException {
105: if (null == url)
106: throw new IllegalArgumentException("url can't be null");
107: if (null == request)
108: throw new IllegalArgumentException("request can't be null");
109:
110: request.setMockConversation(this );
111:
112: // strip away the server root URL, or fail the request in case
113: // the url doesn't start with the correct server root
114: String server_root = request.getServerRootUrl(-1);
115: if (url.indexOf(":/") != -1) {
116: if (!url.startsWith(server_root)) {
117: return null;
118: }
119:
120: url = url.substring(server_root.length());
121: }
122:
123: // add the parameters in the URL to the request parameters
124: Map<String, String[]> parameters = extractParameters(url);
125: if (parameters != null) {
126: for (Map.Entry<String, String[]> entry : parameters
127: .entrySet()) {
128: if (!request.hasParameter(entry.getKey())) {
129: request.setParameter(entry.getKey(), entry
130: .getValue());
131: }
132: }
133: }
134:
135: // get the path parameters
136: String path_parameters = null;
137: int path_parameters_index = url.indexOf(";");
138: if (path_parameters_index != -1) {
139: path_parameters = url.substring(0, path_parameters_index);
140: }
141:
142: // remove the query string
143: int index_query = url.indexOf("?");
144: if (index_query != -1) {
145: url = url.substring(0, index_query);
146: }
147:
148: // perform the request
149: MockResponse response = new MockResponse(this , request);
150: request.setMockResponse(response);
151: request.setRequestedSessionId(path_parameters);
152: mGate.handleRequest("", url, request, response);
153: return response;
154: }
155:
156: /**
157: * Retrieves the scheme that is used by this conversation.
158: *
159: * @return the scheme of this conversation
160: * @see #setScheme
161: * @see #scheme
162: * @since 1.1
163: */
164: public String getScheme() {
165: return mScheme;
166: }
167:
168: /**
169: * Sets the scheme that will be used by this conversation.
170: *
171: * @param scheme the scheme
172: * @see #getScheme
173: * @see #scheme
174: * @since 1.1
175: */
176: public void setScheme(String scheme) {
177: mScheme = scheme;
178: }
179:
180: /**
181: * Sets the scheme that will be used by this conversation.
182: *
183: * @param scheme the scheme
184: * @return this <code>MockConversation</code> instance
185: * @see #getScheme
186: * @see #setScheme
187: * @since 1.1
188: */
189: public MockConversation scheme(String scheme) {
190: setScheme(scheme);
191:
192: return this ;
193: }
194:
195: /**
196: * Retrieves the server name that is used by this conversation.
197: *
198: * @return the server name of this conversation
199: * @see #setServerName
200: * @see #serverName
201: * @since 1.1
202: */
203: public String getServerName() {
204: return mServerName;
205: }
206:
207: /**
208: * Sets the server name that will be used by this conversation.
209: *
210: * @param serverName the server name
211: * @see #getServerName
212: * @see #serverName
213: * @since 1.1
214: */
215: public void setServerName(String serverName) {
216: mServerName = serverName;
217: }
218:
219: /**
220: * Sets the server name that will be used by this conversation.
221: *
222: * @param serverName the server name
223: * @return this <code>MockConversation</code> instance
224: * @see #getServerName
225: * @see #setServerName
226: * @since 1.1
227: */
228: public MockConversation serverName(String serverName) {
229: setServerName(serverName);
230:
231: return this ;
232: }
233:
234: /**
235: * Retrieves the server port that is used by this conversation.
236: *
237: * @return the server port of this conversation
238: * @see #setServerPort
239: * @see #serverPort
240: * @since 1.1
241: */
242: public int getServerPort() {
243: return mServerPort;
244: }
245:
246: /**
247: * Sets the server port that will be used by this conversation.
248: *
249: * @param serverPort the server port
250: * @see #getServerPort
251: * @see #serverPort
252: * @since 1.1
253: */
254: public void setServerPort(int serverPort) {
255: mServerPort = serverPort;
256: }
257:
258: /**
259: * Sets the server port that will be used by this conversation.
260: *
261: * @param serverPort the server port
262: * @return this <code>MockConversation</code> instance
263: * @see #getServerPort
264: * @see #setServerPort
265: * @since 1.1
266: */
267: public MockConversation serverPort(int serverPort) {
268: setServerPort(serverPort);
269:
270: return this ;
271: }
272:
273: /**
274: * Retrieves the context path that is used by this conversation.
275: *
276: * @return the context path of this conversation
277: * @see #setContextPath
278: * @see #contextPath
279: * @since 1.1
280: */
281: public String getContextPath() {
282: return mContextPath;
283: }
284:
285: /**
286: * Sets the context path that will be used by this conversation.
287: *
288: * @param contextPath the context path
289: * @see #getContextPath
290: * @see #contextPath
291: * @since 1.1
292: */
293: public void setContextPath(String contextPath) {
294: mContextPath = contextPath;
295: }
296:
297: /**
298: * Sets the context path that will be used by this conversation.
299: *
300: * @param contextPath the context path
301: * @return this <code>MockConversation</code> instance
302: * @see #getContextPath
303: * @see #setContextPath
304: * @since 1.1
305: */
306: public MockConversation contextPath(String contextPath) {
307: setContextPath(contextPath);
308:
309: return this ;
310: }
311:
312: /**
313: * Checks whether a cookie is present.
314: *
315: * @param name the name of the cookie.
316: * @return <code>true</code> if the cookie was present; or
317: * <p><code>false</code> otherwise
318: * @see #getCookie(String)
319: * @see #getCookieValue(String)
320: * @see #getCookies()
321: * @see #addCookie(Cookie)
322: * @see #addCookie(String, String)
323: * @since 1.1
324: */
325: public boolean hasCookie(String name) {
326: if (null == name)
327: throw new IllegalArgumentException("name can't be null");
328: if (0 == name.length())
329: throw new IllegalArgumentException("name can't be empty");
330:
331: if (null == mCookies) {
332: return false;
333: }
334:
335: for (Cookie cookie : mCookies.values()) {
336: if (cookie.getName().equals(name)) {
337: return true;
338: }
339: }
340:
341: return false;
342: }
343:
344: /**
345: * Retrieves a cookie.
346: *
347: * @param name the name of the cookie.
348: * @return the instance of the cookie; or
349: * <p><code>null</code> if no such cookie is present
350: * @see #hasCookie(String)
351: * @see #getCookieValue(String)
352: * @see #getCookies()
353: * @see #addCookie(Cookie)
354: * @see #addCookie(String, String)
355: * @since 1.1
356: */
357: public Cookie getCookie(String name) {
358: if (null == name)
359: throw new IllegalArgumentException("name can't be null");
360: if (0 == name.length())
361: throw new IllegalArgumentException("name can't be empty");
362:
363: if (null == mCookies) {
364: return null;
365: }
366:
367: for (Cookie cookie : mCookies.values()) {
368: if (cookie.getName().equals(name)) {
369: return cookie;
370: }
371: }
372:
373: return null;
374: }
375:
376: /**
377: * Retrieves the value of a cookie.
378: *
379: * @param name the name of the cookie.
380: * @return the value of the cookie; or
381: * <p><code>null</code> if no such cookie is present
382: * @see #hasCookie(String)
383: * @see #getCookie(String)
384: * @see #getCookies()
385: * @see #addCookie(Cookie)
386: * @see #addCookie(String, String)
387: * @since 1.1
388: */
389: public String getCookieValue(String name) {
390: if (null == name)
391: throw new IllegalArgumentException("name can't be null");
392: if (0 == name.length())
393: throw new IllegalArgumentException("name can't be empty");
394:
395: Cookie cookie = getCookie(name);
396: if (null == cookie) {
397: return null;
398: }
399:
400: return cookie.getValue();
401: }
402:
403: /**
404: * Retrieves all cookies.
405: *
406: * @return an array with all the cookies; or
407: * <p><code>null</code> if no cookies are present
408: * @see #hasCookie(String)
409: * @see #getCookie(String)
410: * @see #getCookieValue(String)
411: * @see #addCookie(Cookie)
412: * @see #addCookie(String, String)
413: * @since 1.1
414: */
415: public Cookie[] getCookies() {
416: if (null == mCookies || 0 == mCookies.size()) {
417: return null;
418: }
419:
420: Cookie[] cookies = new Cookie[mCookies.size()];
421: mCookies.values().toArray(cookies);
422: return cookies;
423: }
424:
425: /**
426: * Add a cookie.
427: *
428: * @param cookie the cookie instance that will be added
429: * @see #hasCookie(String)
430: * @see #getCookie(String)
431: * @see #getCookieValue(String)
432: * @see #getCookies()
433: * @see #addCookie(String, String)
434: * @since 1.1
435: */
436: public void addCookie(Cookie cookie) {
437: if (null == cookie) {
438: return;
439: }
440:
441: mCookies.put(buildCookieId(cookie), cookie);
442: }
443:
444: /**
445: * Add a cookie with only a name and a value, the other fields will be
446: * empty.
447: *
448: * @param name the name of the cookie
449: * @param value the value of the cookie
450: * @see #hasCookie(String)
451: * @see #getCookie(String)
452: * @see #getCookieValue(String)
453: * @see #getCookies()
454: * @see #addCookie(Cookie)
455: * @since 1.1
456: */
457: public void addCookie(String name, String value) {
458: if (null == name)
459: throw new IllegalArgumentException("name can't be null");
460: if (0 == name.length())
461: throw new IllegalArgumentException("name can't be empty");
462:
463: addCookie(new Cookie(name, value));
464: }
465:
466: /**
467: * Add a cookie.
468: *
469: * @param cookie the cookie instance that will be added
470: * @return this <code>MockConversation</code> instance
471: * @see #hasCookie(String)
472: * @see #getCookie(String)
473: * @see #getCookieValue(String)
474: * @see #getCookies()
475: * @see #addCookie(Cookie)
476: * @see #addCookie(String, String)
477: * @since 1.1
478: */
479: public MockConversation cookie(Cookie cookie) {
480: addCookie(cookie);
481:
482: return this ;
483: }
484:
485: /**
486: * Add a cookie with only a name and a value, the other fields will be
487: * empty.
488: *
489: * @param name the name of the cookie
490: * @param value the value of the cookie
491: * @return this <code>MockConversation</code> instance
492: * @see #hasCookie(String)
493: * @see #getCookie(String)
494: * @see #getCookieValue(String)
495: * @see #getCookies()
496: * @see #addCookie(Cookie)
497: * @see #addCookie(String, String)
498: * @since 1.1
499: */
500: public MockConversation cookie(String name, String value) {
501: addCookie(name, value);
502:
503: return this ;
504: }
505:
506: static String buildCookieId(Cookie cookie) {
507: StringBuilder cookie_id = new StringBuilder();
508: if (cookie.getDomain() != null) {
509: cookie_id.append(cookie.getDomain());
510: }
511: cookie_id.append("\n");
512: if (cookie.getPath() != null) {
513: cookie_id.append(cookie.getPath());
514: }
515: cookie_id.append("\n");
516: if (cookie.getName() != null) {
517: cookie_id.append(cookie.getName());
518: }
519: return cookie_id.toString();
520: }
521:
522: static Map<String, String[]> extractParameters(String url) {
523: if (null == url) {
524: return null;
525: }
526:
527: int index_query = url.indexOf("?");
528: int index_anchor = url.indexOf("#");
529:
530: if (-1 == index_query) {
531: return null;
532: }
533:
534: String query = null;
535: if (index_anchor != -1) {
536: query = url.substring(index_query + 1, index_anchor);
537: } else {
538: query = url.substring(index_query + 1);
539: }
540:
541: Map<String, String[]> parameters = new HashMap<String, String[]>();
542:
543: List<String> query_parts = StringUtils.split(query, "&");
544: for (String query_part : query_parts) {
545: List<String> parameter = StringUtils.split(query_part, "=");
546: if (2 == parameter.size()) {
547: try {
548: String name = URLDecoder.decode(parameter.get(0),
549: StringUtils.ENCODING_ISO_8859_1);
550: String value = URLDecoder.decode(parameter.get(1),
551: StringUtils.ENCODING_ISO_8859_1);
552:
553: String[] values = parameters.get(name);
554: if (null == values) {
555: values = new String[] { value };
556: } else {
557: values = ArrayUtils.join(values, value);
558: }
559:
560: parameters.put(name, values);
561: } catch (UnsupportedEncodingException e) {
562: // can't happen, encoding is always supported
563: }
564: }
565: }
566:
567: return parameters;
568: }
569:
570: MockSession getSession(String id) {
571: return mSessions.get(id);
572: }
573:
574: MockSession newHttpSession() {
575: MockSession session = new MockSession(this );
576: mSessions.put(session.getId(), session);
577:
578: return session;
579: }
580:
581: void removeSession(String id) {
582: mSessions.remove(id);
583: }
584: }
|