0001: /*
0002: * Copyright 2001-2007 Geert Bevin <gbevin[remove] at uwyn dot com>
0003: * Distributed under the terms of either:
0004: * - the common development and distribution license (CDDL), v1.0; or
0005: * - the GNU Lesser General Public License, v2.1 or later
0006: *
0007: * Parts are Copyright 1999-2004 Mort Bay Consulting Pty. Ltd.
0008: * ------------------------------------------------------------------------
0009: * Licensed under the Apache License, Version 2.0 (the "License");
0010: * you may not use this file except in compliance with the License.
0011: * You may obtain a copy of the License at
0012: * http:*www.apache.org/licenses/LICENSE-2.0
0013: * Unless required by applicable law or agreed to in writing, software
0014: * distributed under the License is distributed on an "AS IS" BASIS,
0015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0016: * See the License for the specific language governing permissions and
0017: * limitations under the License.
0018: *
0019: * $Id: MockResponse.java 3669 2007-02-26 13:51:23Z gbevin $
0020: */
0021: package com.uwyn.rife.test;
0022:
0023: import com.uwyn.rife.engine.*;
0024: import java.io.*;
0025: import java.util.*;
0026:
0027: import com.uwyn.rife.cmf.loader.xhtml.Jdk14Loader;
0028: import com.uwyn.rife.engine.exceptions.EngineException;
0029: import com.uwyn.rife.template.Template;
0030: import com.uwyn.rife.test.MockHeaders;
0031: import com.uwyn.rife.test.exceptions.InvalidXmlException;
0032: import com.uwyn.rife.tools.ExceptionUtils;
0033: import com.uwyn.rife.tools.StringUtils;
0034: import java.util.regex.Matcher;
0035: import java.util.regex.Pattern;
0036: import javax.servlet.http.Cookie;
0037: import javax.servlet.http.HttpServletResponse;
0038: import javax.servlet.http.HttpSession;
0039: import javax.xml.namespace.QName;
0040: import javax.xml.xpath.XPath;
0041: import javax.xml.xpath.XPathConstants;
0042: import javax.xml.xpath.XPathExpressionException;
0043: import javax.xml.xpath.XPathFactory;
0044: import org.w3c.dom.Node;
0045: import org.w3c.dom.NodeList;
0046: import org.xml.sax.InputSource;
0047: import org.xml.sax.SAXException;
0048:
0049: /**
0050: * Provides a {@link Response} implementation that is suitable for testing a
0051: * web application outside of a servlet container.
0052: *
0053: * @author Geert Bevin (gbevin[remove] at uwyn dot com)
0054: * @version $Revision: 3669 $
0055: * @since 1.1
0056: */
0057: public class MockResponse extends AbstractResponse {
0058: private final static String HEADER_CONTENT_TYPE = "Content-Type";
0059: private final static String HEADER_CONTENT_LANGUAGE = "Content-Language";
0060: private final static String HEADER_CONTENT_LENGTH = "Content-Length";
0061: private final static String HEADER_LOCATION = "Location";
0062:
0063: private final static int SC_200_OK = 200;
0064: private final static int SC_302_MOVED_TEMPORARILY = 302;
0065:
0066: private final static Pattern STRIP_XHTML_XMLNS = Pattern
0067: .compile("html\\s+xmlns=\"[^\"]*\"");
0068:
0069: private MockConversation mMockConversation;
0070: private MockHeaders mHeaders = new MockHeaders();
0071: private HashMap<String, Cookie> mNewCookies = new HashMap<String, Cookie>();
0072: private String mContentType;
0073: private String mCharacterEncoding;
0074: private int mStatus = SC_200_OK;
0075: private String mReason;
0076: private Locale mLocale;
0077: private ByteArrayOutputStream mMockOutputStream = new ByteArrayOutputStream();
0078: private PrintWriter mMockWriter;
0079: private Template mTemplate;
0080: private Map<String, MockResponse> mEmbeddedResponses = new LinkedHashMap<String, MockResponse>();
0081:
0082: MockResponse(MockConversation conversation, Request request) {
0083: this (conversation, request, false);
0084: }
0085:
0086: private MockResponse(MockConversation conversation,
0087: Request request, boolean embedded) {
0088: super (request, embedded);
0089:
0090: mMockConversation = conversation;
0091: }
0092:
0093: MockConversation getMockConversation() {
0094: return mMockConversation;
0095: }
0096:
0097: /**
0098: * Retrieves the {@link ElementInfo} of the element that was last
0099: * processed with this response.
0100: *
0101: * @return the <code>ElementInfo</code> of the last element
0102: * @see #getLastElement
0103: * @see #getLastElementId
0104: * @since 1.1
0105: */
0106: public ElementInfo getLastElementInfo() {
0107: ElementSupport element = getLastElement();
0108: if (null == element) {
0109: return null;
0110: }
0111:
0112: return element.getElementInfo();
0113: }
0114:
0115: /**
0116: * Retrieves the identifier of the element that was last processed with
0117: * this response.
0118: *
0119: * @return the identifier of the last element
0120: * @see #getLastElement
0121: * @see #getLastElementInfo
0122: * @since 1.1
0123: */
0124: public String getLastElementId() {
0125: ElementInfo element_info = getLastElementInfo();
0126: if (null == element_info) {
0127: return null;
0128: }
0129:
0130: return element_info.getId();
0131: }
0132:
0133: /**
0134: * Retrieves the an array of all the bytes that have been written to this
0135: * reponse.
0136: *
0137: * @return an array of bytes with the response content
0138: * @see #getText
0139: * @see #getTemplate
0140: * @see #getParsedHtml
0141: * @since 1.1
0142: */
0143: public byte[] getBytes() {
0144: return mMockOutputStream.toByteArray();
0145: }
0146:
0147: /**
0148: * Retrieves the content of this reponse as text.
0149: *
0150: * @return the response content as text
0151: * @see #getBytes
0152: * @see #getTemplate
0153: * @see #getParsedHtml
0154: * @since 1.1
0155: */
0156: public String getText() {
0157: String charset = mCharacterEncoding;
0158: if (null == charset) {
0159: charset = StringUtils.ENCODING_ISO_8859_1;
0160: }
0161: try {
0162: return new String(getBytes(), charset);
0163: } catch (UnsupportedEncodingException e) {
0164: return ExceptionUtils.getExceptionStackTrace(e);
0165: }
0166: }
0167:
0168: /**
0169: * Retrieves the template instance that was printed to the response.
0170: *
0171: * @return the template instance that was printed to the response; or
0172: * <p><code>null</code> of no template was printed to the response
0173: * @see #getBytes
0174: * @see #getText
0175: * @see #getParsedHtml
0176: * @since 1.1
0177: */
0178: public Template getTemplate() {
0179: return mTemplate;
0180: }
0181:
0182: /**
0183: * Retrieves the content of this reponse as parsed HTML.
0184: *
0185: * @exception IOException when exception occured during the retrieval on
0186: * the response content
0187: * @exception SAXException when exception occured during the parsing of
0188: * the content as HTML
0189: * @return the response content as parsed HTML
0190: * @see #getBytes
0191: * @see #getText
0192: * @see #getTemplate
0193: * @since 1.1
0194: */
0195: public ParsedHtml getParsedHtml() throws IOException, SAXException {
0196: return ParsedHtml.parse(this );
0197: }
0198:
0199: public String getContentType() {
0200: return getHeader(HEADER_CONTENT_TYPE);
0201: }
0202:
0203: /**
0204: * Evaluate an XPath expression in the context of the response text and
0205: * return the result as a list of DOM nodes.
0206: * <p>More information about XPath can be found in the <a
0207: * href="http://www.w3.org/TR/xpath">original specification</a> or in this
0208: * <a href="http://zvon.org/xxl/XMLTutorial/General/book.html">tutorial</a>.
0209: *
0210: * @exception XPathExpressionException if expression cannot be evaluated.
0211: * @return the result as a <code>NodeList</code>
0212: * @see #xpathNode(String)
0213: * @see #xpathString(String)
0214: * @see #xpathBoolean(String)
0215: * @see #xpathNumber(String)
0216: * @since 1.1
0217: */
0218: public NodeList xpathNodeSet(String expression)
0219: throws XPathExpressionException {
0220: return (NodeList) xpath(expression, XPathConstants.NODESET);
0221: }
0222:
0223: /**
0224: * Evaluate an XPath expression in the context of the response text and
0225: * return the result as a DOM node.
0226: *
0227: * @exception XPathExpressionException if expression cannot be evaluated.
0228: * @return the result as a <code>Node</code>
0229: * @see #xpathNodeSet(String)
0230: * @see #xpathString(String)
0231: * @see #xpathBoolean(String)
0232: * @see #xpathNumber(String)
0233: * @since 1.1
0234: */
0235: public Node xpathNode(String expression)
0236: throws XPathExpressionException {
0237: return (Node) xpath(expression, XPathConstants.NODE);
0238: }
0239:
0240: /**
0241: * Evaluate an XPath expression in the context of the response text and
0242: * return the result as a string.
0243: *
0244: * @exception XPathExpressionException if expression cannot be evaluated.
0245: * @return the result as a <code>Node</code>
0246: * @see #xpathNodeSet(String)
0247: * @see #xpathNode(String)
0248: * @see #xpathBoolean(String)
0249: * @see #xpathNumber(String)
0250: * @since 1.1
0251: */
0252: public String xpathString(String expression)
0253: throws XPathExpressionException {
0254: return (String) xpath(expression, XPathConstants.STRING);
0255: }
0256:
0257: /**
0258: * Evaluate an XPath expression in the context of the response text and
0259: * return the result as a boolean.
0260: *
0261: * @exception XPathExpressionException if expression cannot be evaluated.
0262: * @return the result as a <code>Node</code>
0263: * @see #xpathNodeSet(String)
0264: * @see #xpathNode(String)
0265: * @see #xpathString(String)
0266: * @see #xpathNumber(String)
0267: * @since 1.1
0268: */
0269: public Boolean xpathBoolean(String expression)
0270: throws XPathExpressionException {
0271: return (Boolean) xpath(expression, XPathConstants.BOOLEAN);
0272: }
0273:
0274: /**
0275: * Evaluate an XPath expression in the context of the response text and
0276: * return the result as a number.
0277: *
0278: * @exception XPathExpressionException if expression cannot be evaluated.
0279: * @return the result as a <code>Node</code>
0280: * @see #xpathNodeSet(String)
0281: * @see #xpathNode(String)
0282: * @see #xpathString(String)
0283: * @see #xpathBoolean(String)
0284: * @since 1.1
0285: */
0286: public Double xpathNumber(String expression)
0287: throws XPathExpressionException {
0288: return (Double) xpath(expression, XPathConstants.NUMBER);
0289: }
0290:
0291: private Object xpath(String expression, QName returnType)
0292: throws XPathExpressionException {
0293: Matcher matcher = STRIP_XHTML_XMLNS.matcher(getText());
0294: String text = matcher.replaceAll("html xmlns=\"\"");
0295: Reader reader = new StringReader(text);
0296:
0297: InputSource inputsource = new InputSource(reader);
0298: XPath xpath = XPathFactory.newInstance().newXPath();
0299: return xpath.evaluate(expression, inputsource, returnType);
0300: }
0301:
0302: /**
0303: * Evaluate an XPath expression in the provided context object and
0304: * return the result as a list of DOM nodes.
0305: * <p>More information about XPath can be found in the <a
0306: * href="http://www.w3.org/TR/xpath">original specification</a> or in this
0307: * <a href="http://zvon.org/xxl/XMLTutorial/General/book.html">tutorial</a>.
0308: *
0309: * @exception XPathExpressionException if expression cannot be evaluated.
0310: * @return the result as a <code>NodeList</code>
0311: * @see #xpathNode(String, Object)
0312: * @see #xpathString(String, Object)
0313: * @see #xpathBoolean(String, Object)
0314: * @see #xpathNumber(String, Object)
0315: * @since 1.2
0316: */
0317: public NodeList xpathNodeSet(String expression, Object context)
0318: throws XPathExpressionException {
0319: return (NodeList) xpath(expression, context,
0320: XPathConstants.NODESET);
0321: }
0322:
0323: /**
0324: * Evaluate an XPath expression in the provided context object and
0325: * return the result as a DOM node.
0326: *
0327: * @exception XPathExpressionException if expression cannot be evaluated.
0328: * @return the result as a <code>Node</code>
0329: * @see #xpathNodeSet(String, Object)
0330: * @see #xpathString(String, Object)
0331: * @see #xpathBoolean(String, Object)
0332: * @see #xpathNumber(String, Object)
0333: * @since 1.2
0334: */
0335: public Node xpathNode(String expression, Object context)
0336: throws XPathExpressionException {
0337: return (Node) xpath(expression, context, XPathConstants.NODE);
0338: }
0339:
0340: /**
0341: * Evaluate an XPath expression in the provided context object and
0342: * return the result as a string.
0343: *
0344: * @exception XPathExpressionException if expression cannot be evaluated.
0345: * @return the result as a <code>Node</code>
0346: * @see #xpathNodeSet(String, Object)
0347: * @see #xpathNode(String, Object)
0348: * @see #xpathBoolean(String, Object)
0349: * @see #xpathNumber(String, Object)
0350: * @since 1.2
0351: */
0352: public String xpathString(String expression, Object context)
0353: throws XPathExpressionException {
0354: return (String) xpath(expression, context,
0355: XPathConstants.STRING);
0356: }
0357:
0358: /**
0359: * Evaluate an XPath expression in the provided context object and
0360: * return the result as a boolean.
0361: *
0362: * @exception XPathExpressionException if expression cannot be evaluated.
0363: * @return the result as a <code>Node</code>
0364: * @see #xpathNodeSet(String, Object)
0365: * @see #xpathNode(String, Object)
0366: * @see #xpathString(String, Object)
0367: * @see #xpathNumber(String, Object)
0368: * @since 1.2
0369: */
0370: public Boolean xpathBoolean(String expression, Object context)
0371: throws XPathExpressionException {
0372: return (Boolean) xpath(expression, context,
0373: XPathConstants.BOOLEAN);
0374: }
0375:
0376: /**
0377: * Evaluate an XPath expression in the provided context object and
0378: * return the result as a number.
0379: *
0380: * @exception XPathExpressionException if expression cannot be evaluated.
0381: * @return the result as a <code>Node</code>
0382: * @see #xpathNodeSet(String, Object)
0383: * @see #xpathNode(String, Object)
0384: * @see #xpathString(String, Object)
0385: * @see #xpathBoolean(String, Object)
0386: * @since 1.2
0387: */
0388: public Double xpathNumber(String expression, Object context)
0389: throws XPathExpressionException {
0390: return (Double) xpath(expression, context,
0391: XPathConstants.NUMBER);
0392: }
0393:
0394: private Object xpath(String expression, Object context,
0395: QName returnType) throws XPathExpressionException {
0396: XPath xpath = XPathFactory.newInstance().newXPath();
0397: return xpath.evaluate(expression, context, returnType);
0398: }
0399:
0400: /**
0401: * Validates the response as an XML document.
0402: *
0403: * @exception InvalidXmlException when the XML document isn't valid
0404: * @since 1.2
0405: */
0406: public void validateAsXml() throws InvalidXmlException {
0407: Set<String> errors = new LinkedHashSet<String>();
0408: new Jdk14Loader().loadFromString(getText(), false, errors);
0409: if (errors.size() > 0) {
0410: throw new InvalidXmlException(errors);
0411: }
0412: }
0413:
0414: public void print(Template template) throws EngineException {
0415: mTemplate = template;
0416:
0417: super .print(template);
0418: }
0419:
0420: protected void _setContentType(String contentType) {
0421: if (contentType == null) {
0422: mContentType = null;
0423: if (mHeaders != null) {
0424: mHeaders.removeHeader(HEADER_CONTENT_TYPE);
0425: }
0426: } else {
0427: // Look for encoding in contentType
0428: int i0 = contentType.indexOf(';');
0429:
0430: if (i0 > 0) {
0431: // Strip params off mimetype
0432: mContentType = contentType.substring(0, i0).trim();
0433:
0434: // Look for charset
0435: int i1 = contentType.indexOf("charset=", i0);
0436: if (i1 >= 0) {
0437: i1 += 8;
0438: int i2 = contentType.indexOf(' ', i1);
0439: mCharacterEncoding = (0 < i2) ? contentType
0440: .substring(i1, i2) : contentType
0441: .substring(i1);
0442: mCharacterEncoding = QuotedStringTokenizer
0443: .unquote(mCharacterEncoding);
0444: } else // No encoding in the params.
0445: {
0446: if (mCharacterEncoding != null) {
0447: // Add any previously set encoding.
0448: contentType += ";charset="
0449: + QuotedStringTokenizer.quote(
0450: mCharacterEncoding, ";= ");
0451: }
0452: }
0453: } else // No encoding and no other params
0454: {
0455: mContentType = contentType;
0456: // Add any previously set encoding.
0457: if (mCharacterEncoding != null) {
0458: contentType += ";charset="
0459: + QuotedStringTokenizer.quote(
0460: mCharacterEncoding, ";= ");
0461: }
0462: }
0463:
0464: setHeader(HEADER_CONTENT_TYPE, contentType);
0465: }
0466:
0467: setHeader(HEADER_CONTENT_TYPE, contentType);
0468: }
0469:
0470: protected String _getCharacterEncoding() {
0471: return mCharacterEncoding;
0472: }
0473:
0474: protected void _setContentLength(int length) {
0475: setIntHeader(HEADER_CONTENT_LENGTH, length);
0476: }
0477:
0478: protected void _sendRedirect(String location) {
0479: clearBuffer();
0480:
0481: // TODO : correctly handle absolute and relative locations
0482: setStatus(SC_302_MOVED_TEMPORARILY);
0483: setHeader(HEADER_LOCATION, location);
0484:
0485: // complete
0486: }
0487:
0488: protected OutputStream _getOutputStream() throws IOException {
0489: return mMockOutputStream;
0490: }
0491:
0492: public Response createEmbeddedResponse(String valueId,
0493: String differentiator) {
0494: MockResponse response = new MockResponse(mMockConversation,
0495: getRequest(), true);
0496: mEmbeddedResponses.put(valueId, response);
0497: return response;
0498: }
0499:
0500: public void addCookie(Cookie cookie) {
0501: mNewCookies.put(MockConversation.buildCookieId(cookie), cookie);
0502: mMockConversation.addCookie(cookie);
0503: }
0504:
0505: /**
0506: * Retrieves the embedded responses that were processed.
0507: *
0508: * @return the collection of embedded responses; or
0509: * <p>an empty collection if no embedded elements were processed
0510: * @since 1.4
0511: */
0512: public List<MockResponse> getEmbeddedResponses() {
0513: return new ArrayList<MockResponse>(mEmbeddedResponses.values());
0514: }
0515:
0516: /**
0517: * Retrieves the embedded response that corresponds to a specific
0518: * value in the embedding template.
0519: *
0520: * @param valueId the template value in which the embedded element has
0521: * been processed, the "ELEMENT:" prefix is optional and will be
0522: * automatically added if you leave it off
0523: * @return the embedded responses that corresponds to the value; or
0524: * <p><code>null</code> if no such value could be found
0525: * @since 1.4
0526: */
0527: public MockResponse getEmbeddedResponse(String valueId) {
0528: if (valueId != null
0529: && !valueId.startsWith(ElementContext.PREFIX_ELEMENT)) {
0530: valueId = ElementContext.PREFIX_ELEMENT + valueId;
0531: }
0532: return mEmbeddedResponses.get(valueId);
0533: }
0534:
0535: /**
0536: * Retrieves the list of cookies that have been added in this reponse.
0537: *
0538: * @return the list of added cookies; or
0539: * <p>an empty list if no cookies have been added
0540: * @since 1.1
0541: */
0542: public List<String> getNewCookieNames() {
0543: ArrayList<String> names = new ArrayList<String>();
0544: for (Cookie cookie : mNewCookies.values()) {
0545: if (!names.contains(cookie.getName())) {
0546: names.add(cookie.getName());
0547: }
0548: }
0549:
0550: return names;
0551: }
0552:
0553: /**
0554: * Returns the value of the specified response header as a long value that
0555: * represents a Date object. Use this method with headers that contain
0556: * dates.
0557: * <p>The date is returned as the number of milliseconds since January 1,
0558: * 1970 GMT. The header name is case insensitive.
0559: * <p>If the response did not have a header of the specified name, this
0560: * method returns <code>-1</code>. If the header can't be converted to a
0561: * date, the method throws an <code>IllegalArgumentException</code>.
0562: *
0563: * @param name the name of the header
0564: * @exception java.lang.IllegalArgumentException if the header value can't
0565: * be converted to a date
0566: * @return a <code>long</code> value representing the date specified in
0567: * the header expressed as the number of milliseconds since January 1,
0568: * 1970 GMT; or
0569: * <p><code>-1</code> if the named header was not included with the
0570: * response
0571: * @since 1.1
0572: */
0573: public long getDateHeader(String name) {
0574: return mHeaders.getDateHeader(name);
0575: }
0576:
0577: /**
0578: * Returns the value of the specified response header as a
0579: * <code>String</code>. If the reponse did not include a header of the
0580: * specified name, this method returns <code>null</code>. The header name
0581: * is case insensitive. You can use this method with any response header.
0582: *
0583: * @param name the name of the header
0584: * @return a <code>String</code> containing the value of the response
0585: * header; or
0586: * <p><code>null</code> if the response does not have a header of that
0587: * name
0588: * @since 1.1
0589: */
0590: public String getHeader(String name) {
0591: return mHeaders.getHeader(name);
0592: }
0593:
0594: /**
0595: * Returns the value of the specified response header as a
0596: * <code>String</code>. If the reponse did not include a header of the
0597: * specified name, this method returns <code>null</code>. The header name
0598: * is case insensitive. You can use this method with any response header.
0599: *
0600: * @return a <code>Collection</code> of all the header names sent with
0601: * this response; or
0602: * <p>if the response has no headers, an empty <code>Collection</code>
0603: * @since 1.1
0604: */
0605: public Collection getHeaderNames() {
0606: return mHeaders.getHeaderNames();
0607: }
0608:
0609: /**
0610: * Returns all the values of the specified response header as an
0611: * <code>Collection</code> of <code>String</code> objects.
0612: * <p>If the response did not include any headers of the specified name,
0613: * this method returns an empty <code>Collection</code>. The header name
0614: * is case insensitive. You can use this method with any response header.
0615: *
0616: * @param name the name of the header
0617: * @return a <code>Collection</code> containing the values of the response
0618: * header; or
0619: * <p>if the response does not have any headers of that name return an
0620: * empty <code>Collection</code>
0621: * @since 1.1
0622: */
0623: public Collection getHeaders(String name) {
0624: return mHeaders.getHeaders(name);
0625: }
0626:
0627: /**
0628: * Returns the value of the specified response header as an
0629: * <code>int</code>. If the response does not have a header of the
0630: * specified name, this method returns <code>-1</code>. If the header
0631: * cannot be converted to an <code>integer</code>, this method throws a
0632: * <code>NumberFormatException</code>.
0633: * <p>The header name is case insensitive.
0634: *
0635: * @param name the name of the header
0636: * @return an <code>integer</code> expressing the value of the response
0637: * header; or
0638: * <p><code>-1</code> if the response doesn't have a header of this name
0639: * @exception java.lang.NumberFormatException if the header value can't be
0640: * converted to an <code>int</code>
0641: * @since 1.1
0642: */
0643: public int getIntHeader(String name) {
0644: return mHeaders.getIntHeader(name);
0645: }
0646:
0647: public void addHeader(String name, String value) {
0648: mHeaders.addHeader(name, value);
0649: }
0650:
0651: public void addDateHeader(String name, long date) {
0652: mHeaders.addDateHeader(name, date);
0653: }
0654:
0655: public void addIntHeader(String name, int integer) {
0656: mHeaders.addIntHeader(name, integer);
0657: }
0658:
0659: public boolean containsHeader(String name) {
0660: return mHeaders.containsHeader(name);
0661: }
0662:
0663: public void setDateHeader(String name, long date) {
0664: mHeaders.setDateHeader(name, date);
0665: }
0666:
0667: public void setHeader(String name, String value) {
0668: mHeaders.setHeader(name, value);
0669: }
0670:
0671: public void setIntHeader(String name, int value) {
0672: mHeaders.setIntHeader(name, value);
0673: }
0674:
0675: /**
0676: * Removes a response header with the given name.
0677: *
0678: * @param name the name of the header to remove
0679: * @since 1.1
0680: */
0681: public void removeHeader(String name) {
0682: mHeaders.removeHeader(name);
0683: }
0684:
0685: /**
0686: * Returns the status code of this response.
0687: *
0688: * @return an <code>integer</code> expressing the status code of this
0689: * response
0690: * @since 1.2
0691: */
0692: public int getStatus() {
0693: return mStatus;
0694: }
0695:
0696: /**
0697: * Returns the error reason of this response.
0698: *
0699: * @return an <code>String</code> expressing the reason of this response error
0700: * @since 1.2
0701: */
0702: public String getReason() {
0703: return mReason;
0704: }
0705:
0706: public void setStatus(int statusCode) {
0707: mStatus = statusCode;
0708: }
0709:
0710: public void sendError(int statusCode) throws EngineException {
0711: sendError(statusCode, null);
0712: }
0713:
0714: public void sendError(int statusCode, String message)
0715: throws EngineException {
0716: mStatus = statusCode;
0717: mReason = message;
0718: }
0719:
0720: public String encodeURL(String url) {
0721: MockRequest request = (MockRequest) getRequest();
0722:
0723: // should not encode if cookies in evidence
0724: if (null == request || request.isRequestedSessionIdFromCookie()) {
0725: return url;
0726: }
0727:
0728: // get session
0729: HttpSession session = getRequest().getSession(false);
0730:
0731: // no session or no url
0732: if (null == session || null == url) {
0733: return url;
0734: }
0735:
0736: // invalid session
0737: String id = session.getId();
0738: if (null == id) {
0739: return url;
0740: }
0741:
0742: // Already encoded
0743: int prefix = url.indexOf(MockConversation.SESSION_URL_PREFIX);
0744: if (prefix != -1) {
0745: int suffix = url.indexOf("?", prefix);
0746: if (suffix < 0) {
0747: suffix = url.indexOf("#", prefix);
0748: }
0749:
0750: if (suffix <= prefix) {
0751: return url.substring(0, prefix
0752: + MockConversation.SESSION_URL_PREFIX.length())
0753: + id;
0754: }
0755:
0756: return url.substring(0, prefix
0757: + MockConversation.SESSION_URL_PREFIX.length())
0758: + id + url.substring(suffix);
0759: }
0760:
0761: // edit the session
0762: int suffix = url.indexOf('?');
0763: if (suffix < 0) {
0764: suffix = url.indexOf('#');
0765: }
0766:
0767: if (suffix < 0) {
0768: return url + MockConversation.SESSION_URL_PREFIX + id;
0769: }
0770:
0771: return url.substring(0, suffix)
0772: + MockConversation.SESSION_URL_PREFIX + id
0773: + url.substring(suffix);
0774: }
0775:
0776: public void setLocale(Locale locale) {
0777: if (null == locale) {
0778: return;
0779: }
0780:
0781: mLocale = locale;
0782: setHeader(HEADER_CONTENT_LANGUAGE, locale.toString().replace(
0783: '_', '-'));
0784: }
0785:
0786: public Locale getLocale() {
0787: if (null == mLocale) {
0788: return Locale.getDefault();
0789: }
0790:
0791: return mLocale;
0792: }
0793:
0794: public PrintWriter getWriter() throws IOException {
0795: mMockOutputStream.flush();
0796:
0797: /* if there is no writer yet */
0798: if (mMockWriter == null) {
0799: /* get encoding from Content-Type header */
0800: String encoding = getCharacterEncoding();
0801:
0802: if (encoding == null) {
0803: encoding = StringUtils.ENCODING_ISO_8859_1;
0804: }
0805:
0806: setCharacterEncoding(encoding);
0807:
0808: /* construct Writer using correct encoding */
0809: mMockWriter = new PrintWriter(new OutputStreamWriter(
0810: mMockOutputStream, encoding));
0811: }
0812:
0813: return mMockWriter;
0814: }
0815:
0816: private void setCharacterEncoding(String encoding) {
0817: if (null == encoding) {
0818: // Clear any encoding.
0819: if (mCharacterEncoding != null) {
0820: mCharacterEncoding = null;
0821: setHeader(HEADER_CONTENT_TYPE, mContentType);
0822: }
0823: } else {
0824: // No, so just add this one to the mimetype
0825: mCharacterEncoding = encoding;
0826: if (mContentType != null) {
0827: setHeader(HEADER_CONTENT_TYPE, mContentType
0828: + ";charset="
0829: + QuotedStringTokenizer.quote(
0830: mCharacterEncoding, ";= "));
0831: }
0832: }
0833: }
0834:
0835: public HttpServletResponse getHttpServletResponse() {
0836: return null;
0837: }
0838:
0839: // ========================================================================
0840: // Copyright 1999-2004 Mort Bay Consulting Pty. Ltd.
0841: // ------------------------------------------------------------------------
0842: // Licensed under the Apache License, Version 2.0 (the "License");
0843: // you may not use this file except in compliance with the License.
0844: // You may obtain a copy of the License at
0845: // http://www.apache.org/licenses/LICENSE-2.0
0846: // Unless required by applicable law or agreed to in writing, software
0847: // distributed under the License is distributed on an "AS IS" BASIS,
0848: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0849: // See the License for the specific language governing permissions and
0850: // limitations under the License.
0851: // ========================================================================
0852:
0853: /* ------------------------------------------------------------ */
0854: /**
0855: * StringTokenizer with Quoting support. This class is a copy of the
0856: * java.util.StringTokenizer API and the behaviour is the same, except
0857: * that single and doulbe quoted string values are recognized. Delimiters
0858: * within quotes are not considered delimiters. Quotes can be escaped with
0859: * '\'.
0860: *
0861: * @see java.util.StringTokenizer
0862: * @author Greg Wilkins (gregw)
0863: */
0864: static class QuotedStringTokenizer extends StringTokenizer {
0865: private final static String __delim = "\t\n\r";
0866: private String _string;
0867: private String _delim = __delim;
0868: private boolean _returnQuotes = false;
0869: private boolean _returnTokens = false;
0870: private StringBuilder _token;
0871: private boolean _hasToken = false;
0872: private int _i = 0;
0873: private int _lastStart = 0;
0874:
0875: /* ------------------------------------------------------------ */
0876: public QuotedStringTokenizer(String str, String delim,
0877: boolean returnTokens, boolean returnQuotes) {
0878: super ("");
0879: _string = str;
0880: if (delim != null)
0881: _delim = delim;
0882: _returnTokens = returnTokens;
0883: _returnQuotes = returnQuotes;
0884:
0885: if (_delim.indexOf('\'') >= 0 || _delim.indexOf('"') >= 0)
0886: throw new Error("Can't use quotes as delimiters: "
0887: + _delim);
0888:
0889: _token = new StringBuilder(_string.length() > 1024 ? 512
0890: : _string.length() / 2);
0891: }
0892:
0893: /* ------------------------------------------------------------ */
0894: public QuotedStringTokenizer(String str, String delim,
0895: boolean returnTokens) {
0896: this (str, delim, returnTokens, false);
0897: }
0898:
0899: /* ------------------------------------------------------------ */
0900: public QuotedStringTokenizer(String str, String delim) {
0901: this (str, delim, false, false);
0902: }
0903:
0904: /* ------------------------------------------------------------ */
0905: public QuotedStringTokenizer(String str) {
0906: this (str, null, false, false);
0907: }
0908:
0909: /* ------------------------------------------------------------ */
0910: public boolean hasMoreTokens() {
0911: // Already found a token
0912: if (_hasToken)
0913: return true;
0914:
0915: _lastStart = _i;
0916:
0917: int state = 0;
0918: boolean escape = false;
0919: while (_i < _string.length()) {
0920: char c = _string.charAt(_i++);
0921:
0922: switch (state) {
0923: case 0: // Start
0924: if (_delim.indexOf(c) >= 0) {
0925: if (_returnTokens) {
0926: _token.append(c);
0927: return _hasToken = true;
0928: }
0929: } else if (c == '\'') {
0930: if (_returnQuotes)
0931: _token.append(c);
0932: state = 2;
0933: } else if (c == '\"') {
0934: if (_returnQuotes)
0935: _token.append(c);
0936: state = 3;
0937: } else {
0938: _token.append(c);
0939: _hasToken = true;
0940: state = 1;
0941: }
0942: continue;
0943:
0944: case 1: // Token
0945: _hasToken = true;
0946: if (_delim.indexOf(c) >= 0) {
0947: if (_returnTokens)
0948: _i--;
0949: return _hasToken;
0950: } else if (c == '\'') {
0951: if (_returnQuotes)
0952: _token.append(c);
0953: state = 2;
0954: } else if (c == '\"') {
0955: if (_returnQuotes)
0956: _token.append(c);
0957: state = 3;
0958: } else
0959: _token.append(c);
0960: continue;
0961:
0962: case 2: // Single Quote
0963: _hasToken = true;
0964: if (escape) {
0965: escape = false;
0966: _token.append(c);
0967: } else if (c == '\'') {
0968: if (_returnQuotes)
0969: _token.append(c);
0970: state = 1;
0971: } else if (c == '\\') {
0972: if (_returnQuotes)
0973: _token.append(c);
0974: escape = true;
0975: } else
0976: _token.append(c);
0977: continue;
0978:
0979: case 3: // Double Quote
0980: _hasToken = true;
0981: if (escape) {
0982: escape = false;
0983: _token.append(c);
0984: } else if (c == '\"') {
0985: if (_returnQuotes)
0986: _token.append(c);
0987: state = 1;
0988: } else if (c == '\\') {
0989: if (_returnQuotes)
0990: _token.append(c);
0991: escape = true;
0992: } else
0993: _token.append(c);
0994: continue;
0995: }
0996: }
0997:
0998: return _hasToken;
0999: }
1000:
1001: /* ------------------------------------------------------------ */
1002: public String nextToken() throws NoSuchElementException {
1003: if (!hasMoreTokens() || _token == null)
1004: throw new NoSuchElementException();
1005: String t = _token.toString();
1006: _token.setLength(0);
1007: _hasToken = false;
1008: return t;
1009: }
1010:
1011: /* ------------------------------------------------------------ */
1012: public String nextToken(String delim)
1013: throws NoSuchElementException {
1014: _delim = delim;
1015: _i = _lastStart;
1016: _token.setLength(0);
1017: _hasToken = false;
1018: return nextToken();
1019: }
1020:
1021: /* ------------------------------------------------------------ */
1022: public boolean hasMoreElements() {
1023: return hasMoreTokens();
1024: }
1025:
1026: /* ------------------------------------------------------------ */
1027: public Object nextElement() throws NoSuchElementException {
1028: return nextToken();
1029: }
1030:
1031: /* ------------------------------------------------------------ */
1032: /**
1033: * Not implemented.
1034: */
1035: public int countTokens() {
1036: return -1;
1037: }
1038:
1039: /* ------------------------------------------------------------ */
1040: /**
1041: * Quote a string. The string is quoted only if quoting is required
1042: * due to embeded delimiters, quote characters or the empty string.
1043: *
1044: * @param s The string to quote.
1045: * @return quoted string
1046: */
1047: public static String quote(String s, String delim) {
1048: if (s == null)
1049: return null;
1050: if (s.length() == 0)
1051: return "\"\"";
1052:
1053: for (int i = 0; i < s.length(); i++) {
1054: char c = s.charAt(i);
1055: if (c == '"' || c == '\\' || c == '\''
1056: || delim.indexOf(c) >= 0) {
1057: StringBuilder b = new StringBuilder(s.length() + 8);
1058: quote(b, s);
1059: return b.toString();
1060: }
1061: }
1062:
1063: return s;
1064: }
1065:
1066: /* ------------------------------------------------------------ */
1067: /**
1068: * Quote a string into a StringBuilder.
1069: *
1070: * @param buf The StringBuilder
1071: * @param s The String to quote.
1072: */
1073: public static void quote(StringBuilder buf, String s) {
1074: buf.append('"');
1075: for (int i = 0; i < s.length(); i++) {
1076: char c = s.charAt(i);
1077: if (c == '"') {
1078: buf.append("\\\"");
1079: continue;
1080: }
1081: if (c == '\\') {
1082: buf.append("\\\\");
1083: continue;
1084: }
1085: buf.append(c);
1086: continue;
1087: }
1088: buf.append('"');
1089: }
1090:
1091: /* ------------------------------------------------------------ */
1092: /**
1093: * Unquote a string.
1094: *
1095: * @param s The string to unquote.
1096: * @return quoted string
1097: */
1098: public static String unquote(String s) {
1099: if (s == null)
1100: return null;
1101: if (s.length() < 2)
1102: return s;
1103:
1104: char first = s.charAt(0);
1105: char last = s.charAt(s.length() - 1);
1106: if (first != last || (first != '"' && first != '\''))
1107: return s;
1108:
1109: StringBuilder b = new StringBuilder(s.length() - 2);
1110: boolean quote = false;
1111: for (int i = 1; i < s.length() - 1; i++) {
1112: char c = s.charAt(i);
1113:
1114: if (c == '\\' && !quote) {
1115: quote = true;
1116: continue;
1117: }
1118: quote = false;
1119: b.append(c);
1120: }
1121:
1122: return b.toString();
1123: }
1124: }
1125: }
|