0001: /*
0002: * Helma License Notice
0003: *
0004: * The contents of this file are subject to the Helma License
0005: * Version 2.0 (the "License"). You may not use this file except in
0006: * compliance with the License. A copy of the License is available at
0007: * http://adele.helma.org/download/helma/license.txt
0008: *
0009: * Copyright 1998-2003 Helma Software. All Rights Reserved.
0010: *
0011: * $RCSfile$
0012: * $Author: hannes $
0013: * $Revision: 8705 $
0014: * $Date: 2007-12-11 17:32:22 +0100 (Die, 11 Dez 2007) $
0015: */
0016:
0017: package helma.framework;
0018:
0019: import helma.framework.core.Skin;
0020: import helma.framework.core.Application;
0021: import helma.util.*;
0022:
0023: import javax.servlet.http.HttpServletResponse;
0024: import java.io.*;
0025: import java.security.*;
0026: import java.util.*;
0027:
0028: import org.apache.xmlrpc.XmlRpcResponseProcessor;
0029:
0030: /**
0031: * A Transmitter for a response to the servlet client. Objects of this
0032: * class are directly exposed to JavaScript as global property res.
0033: */
0034: public final class ResponseTrans extends Writer implements Serializable {
0035:
0036: static final long serialVersionUID = -8627370766119740844L;
0037: static final int INITIAL_BUFFER_SIZE = 2048;
0038:
0039: static final String newLine = System.getProperty("line.separator");
0040:
0041: // MIME content type of the response.
0042: private String contentType = "text/html";
0043:
0044: // Charset (encoding) to use for the response.
0045: private String charset;
0046:
0047: // Used to allow or disable client side caching
0048: private boolean cacheable = true;
0049:
0050: // HTTP response code, defaults to 200 (OK).
0051: private int status = 200;
0052:
0053: // HTTP authentication realm
0054: private String realm;
0055:
0056: // the actual response
0057: private byte[] response = null;
0058:
0059: // contains the redirect URL
0060: private String redir = null;
0061:
0062: // the forward (internal redirect) URL
0063: private String forward = null;
0064:
0065: // the last-modified date, if it should be set in the response
0066: private long lastModified = -1;
0067:
0068: // flag to signal that resource has not been modified
0069: private boolean notModified = false;
0070:
0071: // Entity Tag for this response, used for conditional GETs
0072: private String etag = null;
0073:
0074: // cookies
0075: Map cookies;
0076:
0077: // the buffer used to build the response
0078: private transient StringBuffer buffer = null;
0079:
0080: // an idle StringBuffer waiting to be reused
0081: private transient StringBuffer cachedBuffer = null;
0082:
0083: // these are used to implement the _as_string variants for Hop templates.
0084: private transient Stack buffers;
0085:
0086: // the path used to tell where to look for skins
0087: private transient Object[] skinpath = null;
0088:
0089: // hashmap for skin caching
0090: private transient HashMap skincache;
0091:
0092: // buffer for debug messages - will be automatically appended to response
0093: private transient StringBuffer debugBuffer;
0094:
0095: // field for generic message to be displayed
0096: private transient String message;
0097:
0098: // field for error message
0099: private transient String error;
0100:
0101: // the res.data map of form and cookie data
0102: private transient Map values = new SystemMap();
0103:
0104: // the res.handlers map of macro handlers
0105: private transient Map handlers = new SystemMap();
0106:
0107: // the res.meta map for meta response data
0108: private transient Map meta = new SystemMap();
0109:
0110: // the request trans for this response
0111: private transient RequestTrans reqtrans;
0112:
0113: // the message digest used to generate composed digests for ETag headers
0114: private transient MessageDigest digest;
0115:
0116: // the skin current or last rendered skin
0117: private transient volatile Skin activeSkin;
0118:
0119: // the application
0120: Application app;
0121:
0122: /**
0123: * Creates a new ResponseTrans object.
0124: *
0125: * @param req the RequestTrans for this response
0126: */
0127: public ResponseTrans(Application app, RequestTrans req) {
0128: this .app = app;
0129: reqtrans = req;
0130: }
0131:
0132: /**
0133: * Get a value from the responses map by key.
0134: */
0135: public Object get(String name) {
0136: try {
0137: return values.get(name);
0138: } catch (Exception x) {
0139: return null;
0140: }
0141: }
0142:
0143: /**
0144: * Get the data map for this response transmitter.
0145: */
0146: public Map getResponseData() {
0147: return values;
0148: }
0149:
0150: /**
0151: * Get the macro handlers map for this response transmitter.
0152: */
0153: public Map getMacroHandlers() {
0154: return handlers;
0155: }
0156:
0157: /**
0158: * Get the meta info map for this response transmitter.
0159: */
0160: public Map getMetaData() {
0161: return meta;
0162: }
0163:
0164: /**
0165: * Returns the ServletResponse instance for this ResponseTrans.
0166: * Returns null for internal and XML-RPC requests.
0167: */
0168: public HttpServletResponse getServletResponse() {
0169: return reqtrans.getServletResponse();
0170: }
0171:
0172: /**
0173: * Reset the current response buffer.
0174: */
0175: public synchronized void resetBuffer() {
0176: if (buffer != null) {
0177: buffer.setLength(0);
0178: }
0179: }
0180:
0181: /**
0182: * Reset the response object to its initial empty state.
0183: */
0184: public synchronized void reset() {
0185: if (buffer != null) {
0186: buffer.setLength(0);
0187: }
0188:
0189: buffers = null;
0190: response = null;
0191: cacheable = true;
0192: redir = forward = message = error = null;
0193: etag = realm = charset = null;
0194: contentType = "text/html";
0195: values.clear();
0196: handlers.clear();
0197: meta.clear();
0198: lastModified = -1;
0199: notModified = false;
0200: skinpath = null;
0201: skincache = null;
0202: cookies = null;
0203:
0204: if (digest != null) {
0205: digest.reset();
0206: }
0207: }
0208:
0209: /**
0210: * This is called before a skin is rendered as string
0211: * (renderSkinAsString) to redirect the output to a new
0212: * string buffer.
0213: * @param buf the StringBuffer to use, or null
0214: * @return the new StringBuffer instance
0215: */
0216: public synchronized StringBuffer pushBuffer(StringBuffer buf) {
0217: if (buffers == null) {
0218: buffers = new Stack();
0219: }
0220:
0221: if (buffer != null) {
0222: buffers.push(buffer);
0223: }
0224:
0225: if (buf != null) {
0226: buffer = buf;
0227: } else if (cachedBuffer != null) {
0228: buffer = cachedBuffer;
0229: cachedBuffer = null;
0230: } else {
0231: buffer = new StringBuffer(64);
0232: }
0233: return buffer;
0234: }
0235:
0236: /**
0237: * Returns the content of the current string buffer and switches back to the previos one.
0238: */
0239: public synchronized String popString() {
0240: StringBuffer buf = popBuffer();
0241: String str = buf.toString();
0242: // store stringbuffer for later reuse
0243: buf.setLength(0);
0244: cachedBuffer = buf;
0245: return str;
0246: }
0247:
0248: public synchronized StringBuffer popBuffer() {
0249: if (buffer == null) {
0250: throw new RuntimeException(
0251: "Can't pop string buffer: buffer is null");
0252: } else if (buffers == null) {
0253: throw new RuntimeException(
0254: "Can't pop string buffer: buffer stack is empty");
0255: }
0256: // get local reference
0257: StringBuffer buf = buffer;
0258: // restore the previous buffer, which may be null
0259: buffer = buffers.empty() ? null : (StringBuffer) buffers.pop();
0260: return buf;
0261: }
0262:
0263: /**
0264: * Get the response buffer, creating it if it doesn't exist
0265: */
0266: public synchronized StringBuffer getBuffer() {
0267: if (buffer == null) {
0268: buffer = new StringBuffer(INITIAL_BUFFER_SIZE);
0269: }
0270:
0271: return buffer;
0272: }
0273:
0274: /**
0275: * Append a string to the response unchanged.
0276: */
0277: public synchronized void write(String str) {
0278: if (str != null) {
0279: if (buffer == null) {
0280: buffer = new StringBuffer(Math.max(str.length() + 100,
0281: INITIAL_BUFFER_SIZE));
0282: }
0283: buffer.append(str);
0284: }
0285: }
0286:
0287: /**
0288: * Appends a objct to the response unchanged.
0289: * The object is first converted to a string.
0290: */
0291: public void write(Object what) {
0292: if (what != null) {
0293: write(what.toString());
0294: }
0295: }
0296:
0297: /**
0298: * Appends a part from a char array to the response buffer.
0299: *
0300: * @param chars
0301: * @param offset
0302: * @param length
0303: */
0304: public synchronized void write(char[] chars, int offset, int length) {
0305: if (buffer == null) {
0306: buffer = new StringBuffer(Math.max(length + 100,
0307: INITIAL_BUFFER_SIZE));
0308: }
0309: buffer.append(chars, offset, length);
0310: }
0311:
0312: /**
0313: * Appends a char array to the response buffer.
0314: *
0315: * @param chars
0316: */
0317: public void write(char chars[]) {
0318: write(chars, 0, chars.length);
0319: }
0320:
0321: /**
0322: * Appends a signle character to the response buffer.
0323: * @param c
0324: */
0325: public synchronized void write(int c) {
0326: if (buffer == null) {
0327: buffer = new StringBuffer(INITIAL_BUFFER_SIZE);
0328: }
0329: buffer.append((char) c);
0330: }
0331:
0332: /**
0333: * Appends a part from a string to the response buffer.
0334: * @param str
0335: * @param offset
0336: * @param length
0337: */
0338: public void write(String str, int offset, int length) {
0339: char cbuf[] = new char[length];
0340: str.getChars(offset, (offset + length), cbuf, 0);
0341: write(cbuf, 0, length);
0342: }
0343:
0344: /**
0345: * Write object to response buffer and append a platform dependent newline sequence.
0346: */
0347: public synchronized void writeln(Object what) {
0348: if (what != null) {
0349: write(what.toString());
0350: } else if (buffer == null) {
0351: // if what is null, buffer may still be uninitialized
0352: buffer = new StringBuffer(INITIAL_BUFFER_SIZE);
0353: }
0354: buffer.append(newLine);
0355: }
0356:
0357: /**
0358: * Writes a platform dependent newline sequence to response buffer.
0359: */
0360: public synchronized void writeln() {
0361: // buffer may still be uninitialized
0362: if (buffer == null) {
0363: buffer = new StringBuffer(INITIAL_BUFFER_SIZE);
0364: }
0365: buffer.append(newLine);
0366: }
0367:
0368: /**
0369: * Insert string somewhere in the response buffer. Caller has to make sure
0370: * that buffer exists and its length is larger than offset. str may be null, in which
0371: * case nothing happens.
0372: */
0373: public void debug(Object message) {
0374: if (debugBuffer == null) {
0375: debugBuffer = new StringBuffer();
0376: }
0377:
0378: String str = (message == null) ? "null" : message.toString();
0379:
0380: debugBuffer
0381: .append("<div class=\"helma-debug-line\" style=\"background: yellow; ");
0382: debugBuffer
0383: .append("color: black; border-top: 1px solid black;\">");
0384: debugBuffer.append(str);
0385: debugBuffer.append("</div>");
0386: }
0387:
0388: /**
0389: * Replace special characters with entities, including <, > and ", thus allowing
0390: * no HTML tags.
0391: */
0392: public synchronized void encode(Object what) {
0393: if (what != null) {
0394: String str = what.toString();
0395:
0396: if (buffer == null) {
0397: buffer = new StringBuffer(Math.max(str.length() + 100,
0398: INITIAL_BUFFER_SIZE));
0399: }
0400:
0401: HtmlEncoder.encodeAll(str, buffer);
0402: }
0403: }
0404:
0405: /**
0406: * Replace special characters with entities but pass through HTML tags
0407: */
0408: public synchronized void format(Object what) {
0409: if (what != null) {
0410: String str = what.toString();
0411:
0412: if (buffer == null) {
0413: buffer = new StringBuffer(Math.max(str.length() + 100,
0414: INITIAL_BUFFER_SIZE));
0415: }
0416:
0417: HtmlEncoder.encode(str, buffer);
0418: }
0419: }
0420:
0421: /**
0422: * Replace special characters with entities, including <, > and ", thus allowing
0423: * no HTML tags.
0424: */
0425: public synchronized void encodeXml(Object what) {
0426: if (what != null) {
0427: String str = what.toString();
0428:
0429: if (buffer == null) {
0430: buffer = new StringBuffer(Math.max(str.length() + 100,
0431: INITIAL_BUFFER_SIZE));
0432: }
0433:
0434: HtmlEncoder.encodeXml(str, buffer);
0435: }
0436: }
0437:
0438: /**
0439: * Encode HTML entities, but leave newlines alone. This is for the content of textarea forms.
0440: */
0441: public synchronized void encodeForm(Object what) {
0442: if (what != null) {
0443: String str = what.toString();
0444:
0445: if (buffer == null) {
0446: buffer = new StringBuffer(Math.max(str.length() + 100,
0447: INITIAL_BUFFER_SIZE));
0448: }
0449:
0450: HtmlEncoder.encodeAll(str, buffer, false);
0451: }
0452: }
0453:
0454: /**
0455: *
0456: *
0457: * @param url ...
0458: *
0459: * @throws RedirectException ...
0460: */
0461: public void redirect(String url) throws RedirectException {
0462: // remove newline chars to prevent response splitting attack
0463: redir = url == null ? null : url.replaceAll("[\r\n]", "");
0464: throw new RedirectException(redir);
0465: }
0466:
0467: /**
0468: *
0469: *
0470: * @return ...
0471: */
0472: public String getRedirect() {
0473: return redir;
0474: }
0475:
0476: /**
0477: *
0478: *
0479: * @param url ...
0480: *
0481: * @throws RedirectException ...
0482: */
0483: public void forward(String url) throws RedirectException {
0484: // remove newline chars to prevent response splitting attack
0485: forward = url == null ? null : url.replaceAll("[\r\n]", "");
0486: throw new RedirectException(forward);
0487: }
0488:
0489: /**
0490: *
0491: *
0492: * @return ...
0493: */
0494: public String getForward() {
0495: return forward;
0496: }
0497:
0498: /**
0499: * Allow to directly set the byte array for the response. Calling this more than once will
0500: * overwrite the previous output. We take a generic object as parameter to be able to
0501: * generate a better error message, but it must be byte[].
0502: */
0503: public void writeBinary(byte[] what) {
0504: response = what;
0505: }
0506:
0507: /**
0508: * Proxy to HttpServletResponse.addHeader()
0509: * @param name the header name
0510: * @param value the header value
0511: */
0512: public void addHeader(String name, String value) {
0513: HttpServletResponse res = getServletResponse();
0514: if (res != null)
0515: res.addHeader(name, value);
0516: }
0517:
0518: /**
0519: * Proxy to HttpServletResponse.addDateHeader()
0520: * @param name the header name
0521: * @param value the header value
0522: */
0523: public void addDateHeader(String name, Date value) {
0524: HttpServletResponse res = getServletResponse();
0525: if (res != null)
0526: res.addDateHeader(name, value.getTime());
0527: }
0528:
0529: /**
0530: * Proxy to HttpServletResponse.setHeader()
0531: * @param name the header name
0532: * @param value the header value
0533: */
0534: public void setHeader(String name, String value) {
0535: HttpServletResponse res = getServletResponse();
0536: if (res != null)
0537: res.setHeader(name, value);
0538: }
0539:
0540: /**
0541: * Proxy to HttpServletResponse.setDateHeader()
0542: * @param name the header name
0543: * @param value the header value
0544: */
0545: public void setDateHeader(String name, Date value) {
0546: HttpServletResponse res = getServletResponse();
0547: if (res != null)
0548: res.setDateHeader(name, value.getTime());
0549: }
0550:
0551: /**
0552: * Write a vanilla error report. Callers should make sure the ResponeTrans is
0553: * new or has been reset.
0554: *
0555: * @param appName the application name
0556: * @param message the error message
0557: */
0558: public void reportError(String appName, String message) {
0559: if (reqtrans.isXmlRpc()) {
0560: writeXmlRpcError(new RuntimeException(message));
0561: } else {
0562: status = 500;
0563: if (!"true".equalsIgnoreCase(app
0564: .getProperty("suppressErrorPage"))) {
0565: write("<html><body><h3>");
0566: write("Error in application ");
0567: write(appName);
0568: write("</h3>");
0569: write(message);
0570: write("</body></html>");
0571: }
0572: }
0573: }
0574:
0575: public void writeXmlRpcResponse(Object result) {
0576: try {
0577: reset();
0578: contentType = "text/xml";
0579: if (charset == null) {
0580: charset = "UTF-8";
0581: }
0582: XmlRpcResponseProcessor xresproc = new XmlRpcResponseProcessor();
0583: writeBinary(xresproc.encodeResponse(result, charset));
0584: } catch (Exception x) {
0585: writeXmlRpcError(x);
0586: }
0587: }
0588:
0589: public void writeXmlRpcError(Exception x) {
0590: contentType = "text/xml";
0591: if (charset == null) {
0592: charset = "UTF-8";
0593: }
0594: XmlRpcResponseProcessor xresproc = new XmlRpcResponseProcessor();
0595: writeBinary(xresproc.encodeException(x, charset));
0596: }
0597:
0598: public void flush() {
0599: // does nothing!
0600: }
0601:
0602: /**
0603: * This has to be called after writing to this response has finished and before it is shipped back to the
0604: * web server. Transforms the string buffer into a byte array for transmission.
0605: */
0606: public void close() throws UnsupportedEncodingException {
0607: close(null);
0608: }
0609:
0610: /**
0611: * This has to be called after writing to this response has finished and before it is shipped back to the
0612: * web server. Transforms the string buffer into a byte array for transmission.
0613: */
0614: public synchronized void close(String cset)
0615: throws UnsupportedEncodingException {
0616: // if the response was already written and committed by the application
0617: // there's no point in closing the response buffer
0618: HttpServletResponse res = reqtrans.getServletResponse();
0619: if (res != null && res.isCommitted()) {
0620: return;
0621: }
0622:
0623: // only use default charset if not explicitly set for this response.
0624: if (charset == null) {
0625: charset = cset;
0626: }
0627:
0628: // if charset is not set, use western encoding
0629: if (charset == null) {
0630: charset = "ISO-8859-1";
0631: }
0632:
0633: boolean encodingError = false;
0634:
0635: // only close if the response hasn't been closed yet
0636: if (response == null) {
0637: // if debug buffer exists, append it to main buffer
0638: if (contentType != null
0639: && contentType.startsWith("text/html")
0640: && debugBuffer != null) {
0641: debugBuffer.append("</div>");
0642: if (buffer == null) {
0643: buffer = debugBuffer;
0644: } else {
0645: buffer.append(debugBuffer);
0646: }
0647: }
0648:
0649: // get the buffer's bytes in the specified encoding
0650: if (buffer != null) {
0651: try {
0652: response = buffer.toString().getBytes(charset);
0653: } catch (UnsupportedEncodingException uee) {
0654: encodingError = true;
0655: response = buffer.toString().getBytes();
0656: }
0657:
0658: // make sure this is done only once, even with more requsts attached
0659: buffer = null;
0660: } else {
0661: response = new byte[0];
0662: }
0663: }
0664:
0665: boolean autoETags = "true".equals(app.getProperty("autoETags",
0666: "true"));
0667: // if etag is not set, calc MD5 digest and check it, but only if
0668: // not a redirect or error
0669: if (autoETags && etag == null && lastModified == -1
0670: && status == 200 && redir == null) {
0671: try {
0672: digest = MessageDigest.getInstance("MD5");
0673: // if (contentType != null)
0674: // digest.update (contentType.getBytes());
0675: byte[] b = digest.digest(response);
0676: etag = "\"" + new String(Base64.encode(b)) + "\"";
0677: // only set response to 304 not modified if no cookies were set
0678: if (reqtrans.hasETag(etag) && countCookies() == 0) {
0679: response = new byte[0];
0680: notModified = true;
0681: }
0682: } catch (Exception ignore) {
0683: // Etag creation failed for some reason. Ignore.
0684: }
0685: }
0686:
0687: notifyAll();
0688:
0689: // if there was a problem with the encoding, let the app know
0690: if (encodingError) {
0691: throw new UnsupportedEncodingException(charset);
0692: }
0693: }
0694:
0695: /**
0696: * If we just attached to evaluation we call this instead of close because only the primary thread
0697: * is responsible for closing the result
0698: */
0699: public synchronized void waitForClose() {
0700: try {
0701: if (response == null) {
0702: wait(10000L);
0703: }
0704: } catch (InterruptedException ix) {
0705: // Ignore
0706: }
0707: }
0708:
0709: /**
0710: * Get the body content for this response as byte array, encoded using the
0711: * response's charset.
0712: *
0713: * @return the response body
0714: */
0715: public byte[] getContent() {
0716: return (response == null) ? new byte[0] : response;
0717: }
0718:
0719: /**
0720: * Get the number of bytes of the response body.
0721: *
0722: * @return the length of the response body
0723: */
0724: public int getContentLength() {
0725: if (response != null) {
0726: return response.length;
0727: }
0728:
0729: return 0;
0730: }
0731:
0732: /**
0733: * Get the response's MIME content type
0734: *
0735: * @return the MIME type for this response
0736: */
0737: public String getContentType() {
0738: if (charset != null) {
0739: return contentType + "; charset=" + charset;
0740: }
0741:
0742: return contentType;
0743: }
0744:
0745: /**
0746: * Set the response's MIME content type
0747: *
0748: * @param contentType MIME type for this response
0749: */
0750: public void setContentType(String contentType) {
0751: this .contentType = contentType;
0752: }
0753:
0754: /**
0755: * Set the Last-Modified header for this response
0756: *
0757: * @param modified the Last-Modified header in milliseconds
0758: */
0759: public void setLastModified(long modified) {
0760: // date headers don't do milliseconds, round to seconds
0761: lastModified = (modified / 1000) * 1000;
0762: if (reqtrans.getIfModifiedSince() == lastModified) {
0763: notModified = true;
0764: throw new RedirectException(null);
0765: }
0766: }
0767:
0768: /**
0769: * Get the value of the Last-Modified header for this response.
0770: *
0771: * @return the Last-Modified header in milliseconds
0772: */
0773: public long getLastModified() {
0774: return lastModified;
0775: }
0776:
0777: /**
0778: * Set the ETag header value for this response.
0779: *
0780: * @param value the ETag header value
0781: */
0782: public void setETag(String value) {
0783: etag = (value == null) ? null : ("\"" + value + "\"");
0784: if (etag != null && reqtrans.hasETag(etag)) {
0785: notModified = true;
0786: throw new RedirectException(null);
0787: }
0788: }
0789:
0790: /**
0791: * Get the ETag header value for this response.
0792: *
0793: * @return the ETag header value
0794: */
0795: public String getETag() {
0796: return etag;
0797: }
0798:
0799: /**
0800: * Check if this response should generate a Not-Modified response.
0801: *
0802: * @return true if the the response wasn't modified since the client last saw it.
0803: */
0804: public boolean getNotModified() {
0805: return notModified;
0806: }
0807:
0808: /**
0809: * Add a dependency to this response.
0810: *
0811: * @param what an item this response's output depends on.
0812: */
0813: public void dependsOn(Object what) {
0814: if (digest == null) {
0815: try {
0816: digest = MessageDigest.getInstance("MD5");
0817: } catch (NoSuchAlgorithmException nsa) {
0818: // MD5 should always be available
0819: }
0820: }
0821:
0822: if (what == null) {
0823: digest.update(new byte[0]);
0824: } else if (what instanceof Date) {
0825: digest.update(MD5Encoder.toBytes(((Date) what).getTime()));
0826: } else if (what instanceof byte[]) {
0827: digest.update((byte[]) what);
0828: } else {
0829: String str = what.toString();
0830:
0831: if (str != null) {
0832: digest.update(str.getBytes());
0833: } else {
0834: digest.update(new byte[0]);
0835: }
0836: }
0837: }
0838:
0839: /**
0840: * Digest all dependencies to a checksum to see if the response has changed.
0841: */
0842: public void digestDependencies() {
0843: if (digest == null) {
0844: return;
0845: }
0846:
0847: // add the application checksum as dependency to make ETag
0848: // generation sensitive to changes in the app
0849: byte[] b = digest.digest(MD5Encoder.toBytes(app.getChecksum()));
0850:
0851: setETag(new String(Base64.encode(b)));
0852: }
0853:
0854: /**
0855: * Set the path in which to look for skins. This may contain file locations and
0856: * HopObjects.
0857: *
0858: * @param arr the skin path
0859: */
0860: public void setSkinpath(Object[] arr) {
0861: this .skinpath = arr;
0862: skincache = null;
0863: }
0864:
0865: /**
0866: * Get the path in which to look for skins. This may contain file locations and
0867: * HopObjects.
0868: *
0869: * @return the skin path
0870: */
0871: public Object[] getSkinpath() {
0872: if (skinpath == null) {
0873: skinpath = new Object[0];
0874: }
0875:
0876: return skinpath;
0877: }
0878:
0879: /**
0880: * Look up a cached skin.
0881: *
0882: * @param id the skin key
0883: * @return the skin, or null if no skin is cached for the given key
0884: */
0885: public Skin getCachedSkin(Object id) {
0886: if (skincache == null) {
0887: return null;
0888: }
0889:
0890: return (Skin) skincache.get(id);
0891: }
0892:
0893: /**
0894: * Cache a skin for the length of this response.
0895: *
0896: * @param id the skin key
0897: * @param skin the skin to cache
0898: */
0899: public void cacheSkin(Object id, Skin skin) {
0900: if (skincache == null) {
0901: skincache = new HashMap();
0902: }
0903:
0904: skincache.put(id, skin);
0905: }
0906:
0907: /**
0908: * Set the skin currently being rendered, returning the previously active skin.
0909: * @param skin the new active skin
0910: * @return the previously active skin
0911: */
0912: public Skin switchActiveSkin(Skin skin) {
0913: Skin previousSkin = activeSkin;
0914: activeSkin = skin;
0915: return previousSkin;
0916: }
0917:
0918: /**
0919: * Return the skin currently being rendered, or none.
0920: * @return the currently active skin
0921: */
0922: public Skin getActiveSkin() {
0923: return activeSkin;
0924: }
0925:
0926: /**
0927: * Set a cookie.
0928: *
0929: * @param key the cookie key
0930: * @param value the cookie value
0931: * @param days the cookie's lifespan in days
0932: * @param path the URL path to apply the cookie to
0933: * @param domain the domain to apply the cookie to
0934: */
0935: public void setCookie(String key, String value, int days,
0936: String path, String domain) {
0937: CookieTrans c = null;
0938:
0939: if (cookies == null) {
0940: cookies = new HashMap();
0941: } else {
0942: c = (CookieTrans) cookies.get(key);
0943: }
0944:
0945: // remove newline chars to prevent response splitting attack
0946: if (value != null) {
0947: value = value.replaceAll("[\r\n]", "");
0948: }
0949:
0950: if (c == null) {
0951: c = new CookieTrans(key, value);
0952: cookies.put(key, c);
0953: } else {
0954: c.setValue(value);
0955: }
0956:
0957: c.setDays(days);
0958: c.setPath(path);
0959: c.setDomain(domain);
0960: }
0961:
0962: /**
0963: * Reset all previously set cookies.
0964: */
0965: public void resetCookies() {
0966: if (cookies != null) {
0967: cookies.clear();
0968: }
0969: }
0970:
0971: /**
0972: * Get the number of cookies set in this response.
0973: *
0974: * @return the number of cookies
0975: */
0976: public int countCookies() {
0977: if (cookies != null) {
0978: return cookies.size();
0979: }
0980:
0981: return 0;
0982: }
0983:
0984: /**
0985: * Get the cookies set in this response.
0986: *
0987: * @return the cookies
0988: */
0989: public CookieTrans[] getCookies() {
0990: if (cookies == null) {
0991: return new CookieTrans[0];
0992: }
0993:
0994: CookieTrans[] c = new CookieTrans[cookies.size()];
0995: cookies.values().toArray(c);
0996: return c;
0997: }
0998:
0999: /**
1000: * Get the message to display to the user, if any.
1001: * @return the message
1002: */
1003: public String getMessage() {
1004: return message;
1005: }
1006:
1007: /**
1008: * Set a message to display to the user.
1009: * @param message the message
1010: */
1011: public void setMessage(String message) {
1012: this .message = message;
1013: }
1014:
1015: /**
1016: * Get the error message to display to the user, if any.
1017: * @return the error message
1018: */
1019: public String getError() {
1020: return error;
1021: }
1022:
1023: /**
1024: * Set a message to display to the user.
1025: * @param error the error message
1026: */
1027: public void setError(String error) {
1028: this .error = error;
1029: }
1030:
1031: /**
1032: * Get debug messages to append to the response, if any.
1033: * @return the response's debug buffer
1034: */
1035: public StringBuffer getDebugBuffer() {
1036: return debugBuffer;
1037: }
1038:
1039: /**
1040: * Set debug messages to append to the response.
1041: * @param debugBuffer the response's debug buffer
1042: */
1043: public void setDebugBuffer(StringBuffer debugBuffer) {
1044: this .debugBuffer = debugBuffer;
1045: }
1046:
1047: /**
1048: * Get the charset/encoding for this response
1049: * @return the charset name
1050: */
1051: public String getCharset() {
1052: return charset;
1053: }
1054:
1055: /**
1056: * Set the charset/encoding for this response
1057: * @param charset the charset name
1058: */
1059: public void setCharset(String charset) {
1060: this .charset = charset;
1061: }
1062:
1063: /**
1064: * Returns true if this response may be cached by the client
1065: * @return true if the response may be cached
1066: */
1067: public boolean isCacheable() {
1068: return cacheable;
1069: }
1070:
1071: /**
1072: * Set the cacheability of this response
1073: * @param cache true if the response may be cached
1074: */
1075: public void setCacheable(boolean cache) {
1076: this .cacheable = cache;
1077: }
1078:
1079: /**
1080: * Get the HTTP response status code
1081: * @return the HTTP response code
1082: */
1083: public int getStatus() {
1084: return status;
1085: }
1086:
1087: /**
1088: * Set the HTTP response status code
1089: * @param status the HTTP response code
1090: */
1091: public void setStatus(int status) {
1092: this .status = status;
1093: }
1094:
1095: /**
1096: * Get the HTTP authentication realm
1097: * @return the name of the authentication realm
1098: */
1099: public String getRealm() {
1100: return realm;
1101: }
1102:
1103: /**
1104: * Set the HTTP authentication realm
1105: * @param realm the name of the authentication realm
1106: */
1107: public void setRealm(String realm) {
1108: this.realm = realm;
1109: }
1110: }
|