001: // JigsawHttpServletReponse.java
002: // $Id: JigsawHttpServletResponse.java,v 1.53 2003/02/04 16:21:43 ylafon Exp $
003: // (c) COPYRIGHT MIT and INRIA, 1996.
004: // Please first read the full copyright statement in file COPYRIGHT.html
005:
006: package org.w3c.jigsaw.servlet;
007:
008: import java.io.ByteArrayOutputStream;
009: import java.io.DataOutputStream;
010: import java.io.IOException;
011: import java.io.OutputStream;
012: import java.io.OutputStreamWriter;
013: import java.io.PipedOutputStream;
014: import java.io.PrintWriter;
015: import java.io.UnsupportedEncodingException;
016:
017: import javax.servlet.ServletOutputStream;
018:
019: import javax.servlet.http.Cookie;
020: import javax.servlet.http.HttpServletResponse;
021: import javax.servlet.http.HttpSession;
022:
023: import java.net.MalformedURLException;
024: import java.net.URL;
025:
026: import java.util.Locale;
027:
028: import org.w3c.www.mime.MimeType;
029: import org.w3c.www.mime.MimeTypeFormatException;
030: import org.w3c.www.mime.Utils;
031:
032: import org.w3c.www.http.BasicValue;
033: import org.w3c.www.http.HttpAcceptCharsetList;
034: import org.w3c.www.http.HttpAcceptEncodingList;
035: import org.w3c.www.http.HttpAcceptLanguageList;
036: import org.w3c.www.http.HttpAcceptList;
037: import org.w3c.www.http.HttpCookie;
038: import org.w3c.www.http.HttpCookieList;
039: import org.w3c.www.http.HttpEntityMessage;
040: import org.w3c.www.http.HttpEntityTagList;
041: import org.w3c.www.http.HttpExt;
042: import org.w3c.www.http.HttpExtList;
043: import org.w3c.www.http.HttpFactory;
044: import org.w3c.www.http.HttpMessage;
045: import org.w3c.www.http.HttpParamList;
046: import org.w3c.www.http.HttpRangeList;
047: import org.w3c.www.http.HttpReplyMessage;
048: import org.w3c.www.http.HttpRequestMessage;
049: import org.w3c.www.http.HttpSetCookie;
050: import org.w3c.www.http.HttpSetCookieList;
051: import org.w3c.www.http.HttpString;
052: import org.w3c.www.http.HttpTokenList;
053: import org.w3c.www.http.HttpWarningList;
054:
055: import org.w3c.jigsaw.html.HtmlGenerator;
056:
057: import org.w3c.jigsaw.http.Reply;
058: import org.w3c.jigsaw.http.Request;
059:
060: import org.w3c.www.http.HeaderValue;
061:
062: /**
063: * @author Alexandre Rafalovitch <alex@access.com.au>
064: * @author Anselm Baird-Smith <abaird@w3.org>
065: * @author Benoît Mahé (bmahe@w3.org)
066: * @author Roland Mainz (Roland.Mainz@informatik.med.uni-giessen.de)
067: */
068:
069: public class JigsawHttpServletResponse implements HttpServletResponse {
070:
071: public final static String CHARSET_PARAMETER = "charset";
072:
073: public final static int DEFAULT_BUFFER_SIZE = 8 * 1024; // 8KB buffer
074: public final static int MIN_BUFFER_SIZE = 4 * 1024; // 4KB buffer
075:
076: private final static int STATE_INITIAL = 0;
077: private final static int STATE_HEADERS_DONE = 1;
078: private final static int STATE_ALL_DONE = 2;
079:
080: private final static int STREAM_STATE_INITIAL = 0;
081: private final static int STREAM_WRITER_USED = 1;
082: private final static int OUTPUT_STREAM_USED = 2;
083:
084: private int stream_state = STREAM_STATE_INITIAL;
085:
086: private JigsawServletOutputStream output = null;
087: private PrintWriter writer = null;
088:
089: private MimeTypeFormatException setContentTypeException = null;
090:
091: // servlet has set a fixed content length or not,
092: // and cut (see flushStream) any data which are too much here...
093: private final static int CALC_CONTENT_LENGTH = -1;
094: private int fixedContentLength = CALC_CONTENT_LENGTH;
095:
096: // Our Locale
097: protected Locale locale;
098:
099: /**
100: * Our temp stream.
101: */
102: protected ByteArrayOutputStream out = null;
103: protected PipedOutputStream pout = null;
104:
105: protected JigsawHttpServletRequest jrequest = null;
106:
107: protected int buffer_size;
108:
109: protected void setServletRequest(JigsawHttpServletRequest jrequest) {
110: this .jrequest = jrequest;
111: }
112:
113: public static final String INCLUDED = "org.w3c.jigsaw.servlet.included";
114: public static final String STREAM = "org.w3c.jigsaw.servlet.stream";
115: public static final String MONITOR = "org.w3c.jigsaw.servlet.monitor";
116:
117: int state = STATE_INITIAL;
118: Reply reply = null;
119: Request request = null;
120:
121: /**
122: * Sets the content length for this response.
123: * @param len - the content length
124: */
125: public void setContentLength(int i) {
126: fixedContentLength = i;
127: reply.setContentLength(i);
128: }
129:
130: /**
131: * Sets the content type for this response. This type may later be
132: * implicitly modified by addition of properties such as the MIME
133: * charset=<value> if the service finds it necessary, and the appropriate
134: * media type property has not been set.
135: * <p>This response property may only be assigned one time. If a writer
136: * is to be used to write a text response, this method must be
137: * called before the method getWriter. If an output stream will be used
138: * to write a response, this method must be called before the
139: * output stream is used to write response data.
140: * @param spec - the content's MIME type
141: * @see JigsawHttpServletResponse#getOutputStream
142: * @see JigsawHttpServletResponse#getWriter
143: */
144: public void setContentType(String spec) {
145: try {
146: MimeType type = new MimeType(spec);
147: reply.setContentType(type);
148: setContentTypeException = null;
149: } catch (MimeTypeFormatException ex) {
150: //store exception
151: setContentTypeException = ex;
152: }
153: }
154:
155: protected boolean isStreamObtained() {
156: return (stream_state != STREAM_STATE_INITIAL);
157: }
158:
159: protected Reply getReply() {
160: return reply;
161: }
162:
163: /**
164: * Returns an output stream for writing binary response data.
165: * @return A ServletOutputStream
166: * @exception IOException if an I/O exception has occurred
167: * @exception IllegalStateException if getWriter has been called on this
168: * same request.
169: * @see JigsawHttpServletResponse#getWriter
170: */
171: public synchronized ServletOutputStream getOutputStream()
172: throws IOException {
173: if (stream_state == STREAM_WRITER_USED) {
174: // obviously output is not null, but it doesn't cost...
175: if ((output != null) && output.isCommitted()) {
176: throw new IllegalStateException("Writer used");
177: }
178: output.reset();
179: }
180: stream_state = OUTPUT_STREAM_USED;
181: return getJigsawOutputStream(false);
182: }
183:
184: /**
185: * returns the raw output stream regardless of what happened before
186: * used for internal operation (e.g. writing an exception trailer).
187: * @return an underlying output stream if available (even when a writer is
188: * used)
189: */
190:
191: protected synchronized OutputStream getRawOutputStream() {
192: return output;
193: }
194:
195: synchronized ServletOutputStream getJigsawOutputStream(
196: boolean writerUsed) throws IOException {
197: if (output != null)
198: return output;
199:
200: if (state == STATE_ALL_DONE) {
201: throw new IOException("Processing finished");
202: }
203: // any exception during setContentType ?
204: if (setContentTypeException != null) {
205: // "wrap" the exception from setContentType in an IOException
206: throw new IOException("Illegal Content Type: "
207: + setContentTypeException.toString());
208: }
209:
210: if (request.hasState(INCLUDED)) {
211: out = new ByteArrayOutputStream();
212: output = new JigsawServletOutputStream(this ,
213: new DataOutputStream(out), buffer_size, writerUsed);
214: reply.setState(STREAM, new Object());
215: } else {
216: if (reply.hasState(STREAM)) {
217: try {
218: pout = (PipedOutputStream) reply.getState(STREAM);
219: DataOutputStream dos = new DataOutputStream(pout);
220: output = new JigsawServletOutputStream(this , dos,
221: buffer_size, writerUsed);
222: reply.setState(STREAM, null);
223: } catch (ClassCastException ex) {
224: // it is null, no OutputStream -> redirect done
225: // we will eat this anyway
226: output = new JigsawServletOutputStream(this , reply,
227: buffer_size, writerUsed);
228: reply.setState(STREAM, new Object());
229: }
230: } else {
231: output = new JigsawServletOutputStream(this , reply,
232: buffer_size, writerUsed);
233: reply.setState(STREAM, new Object());
234: }
235: }
236: return output;
237: }
238:
239: synchronized void notifyClient() {
240: Object o = reply.getState(MONITOR);
241: if (o != null) {
242: synchronized (o) {
243: o.notifyAll();
244: }
245: }
246: }
247:
248: /**
249: * Sets the status code and message for this response. If the field had
250: * already been set, the new value overwrites the previous one. The message
251: * is sent as the body of an HTML page, which is returned to the user to
252: * describe the problem. The page is sent with a default HTML header; the
253: * message is enclosed in simple body tags (<body></body>).
254: * @param i - the status code
255: * @param reason - the status message
256: * @deprecated since jsdk2.1
257: */
258: public void setStatus(int i, String reason) {
259: reply.setStatus(i);
260: reply.setReason(reason);
261: }
262:
263: /**
264: * Sets the status code for this response. This method is used to set the
265: * return status code when there is no error (for example, for the status
266: * codes SC_OK or SC_MOVED_TEMPORARILY). If there is an error, the
267: * sendError method should be used instead.
268: * @param i - the status code
269: * @see JigsawHttpServletResponse#sendError
270: */
271: public void setStatus(int i) {
272: setStatus(i, reply.getStandardReason(i));
273: }
274:
275: /**
276: * Adds a field to the response header with the given name and value. If
277: * the field had already been set, the new value overwrites the previous
278: * one. The containsHeader method can be used to test for the presence of a
279: * header before setting its value.
280: * @param name - the name of the header field
281: * @param value - the header field's value
282: * @see JigsawHttpServletResponse#containsHeader
283: */
284: public void setHeader(String name, String value) {
285: reply.setValue(name, value);
286: }
287:
288: /**
289: * Adds a field to the response header with the given name and integer
290: * value. If the field had already been set, the new value overwrites the
291: * previous one. The containsHeader method can be used to test for the
292: * presence of a header before setting its value.
293: * @param name - the name of the header field
294: * @param value - the header field's integer value
295: * @see JigsawHttpServletResponse#containsHeader
296: */
297: public void setIntHeader(String name, int value) {
298: setHeader(name, String.valueOf(value));
299: }
300:
301: /**
302: * Adds a field to the response header with the given name and date-valued
303: * field. The date is specified in terms of milliseconds since the epoch.
304: * If the date field had already been set, the new value overwrites the
305: * previous one. The containsHeader method can be used to test for the
306: * presence of a header before setting its value.
307: * @param name - the name of the header field
308: * @param value - the header field's date value
309: * @see JigsawHttpServletResponse#containsHeader
310: */
311: public void setDateHeader(String name, long date) {
312: setHeader(name, HttpFactory.makeDate(date).toExternalForm());
313: }
314:
315: public void unsetHeader(String name) {
316: setHeader(name, null);
317: }
318:
319: /**
320: * Sends an error response to the client using the specified status code
321: * and descriptive message. If setStatus has previously been called, it is
322: * reset to the error status code. The message is sent as the body of an
323: * HTML page, which is returned to the user to describe the problem. The
324: * page is sent with a default HTML header; the message is enclosed in
325: * simple body tags (<body></body>).
326: * @param sc - the status code
327: * @param msg - the detail message
328: * @exception IOException If an I/O error has occurred.
329: */
330: public synchronized void sendError(int i, String msg)
331: throws IOException {
332: if (isStreamObtained() && (output != null)
333: && output.isCommitted()) {
334: throw new IOException("Reply already started in servlet");
335: }
336: if (output != null) {
337: output.reset();
338: }
339: setStatus(i);
340: reply.setContent(msg);
341: state = STATE_ALL_DONE;
342: reply.setState(STREAM, new Object());
343: notifyClient();
344: }
345:
346: /**
347: * Sends an error response to the client using the specified status
348: * code and a default message.
349: * @param sc - the status code
350: * @exception IOException If an I/O error has occurred.
351: */
352: public synchronized void sendError(int i) throws IOException {
353: sendError(i, reply.getStandardReason(i));
354: }
355:
356: /**
357: * Sends a temporary redirect response to the client using the specified
358: * redirect location URL. The URL must be absolute (for example,
359: * https://hostname/path/file.html). Relative URLs are not permitted here.
360: * @param url - the redirect location URL
361: * @exception IOException If an I/O error has occurred.
362: */
363: public synchronized void sendRedirect(String url)
364: throws IOException {
365: URL loc = null;
366: if (isStreamObtained() && (output != null)
367: && output.isCommitted()) {
368: throw new IOException("Reply already started in servlet");
369: }
370: if (output != null) {
371: output.reset();
372: }
373: try {
374: String requri = jrequest.getRequestURI();
375: URL requrl = request.getURL();
376: loc = new URL(requrl.getProtocol(), requrl.getHost(),
377: requrl.getPort(), requri);
378: loc = new URL(loc, url);
379: // Removed (netscape doesn't know SEE OTHER! sig)
380: // if (jrequest.getMethod().equalsIgnoreCase("POST") &&
381: // jrequest.getProtocol().equals("HTTP/1.1")) {
382: // setStatus(SC_SEE_OTHER);
383: // } else {
384: setStatus(SC_MOVED_TEMPORARILY);
385: // }
386: reply.setLocation(loc);
387: HtmlGenerator g = new HtmlGenerator("Moved");
388: g
389: .append("<P>This resource has moved, click on the link if your"
390: + " browser doesn't support automatic redirection<BR>"
391: + "<A HREF=\""
392: + loc.toExternalForm()
393: + "\">" + loc.toExternalForm() + "</A>");
394: reply.setStream(g);
395: } catch (Exception ex) {
396: ex.printStackTrace();
397: } finally {
398: state = STATE_ALL_DONE;
399: reply.setState(STREAM, new Object());
400: notifyClient();
401: }
402: }
403:
404: /**
405: * Checks whether the response message header has a field with the
406: * specified name.
407: * @param name - the header field name
408: * @return true if the response message header has a field with the
409: * specified name; false otherwise
410: */
411: public boolean containsHeader(String header) {
412: return reply.hasHeader(header);
413: }
414:
415: /**
416: * Adds the specified cookie to the response. It can be called multiple
417: * times to set more than one cookie.
418: * @param cookie - the Cookie to return to the client
419: */
420: public void addCookie(Cookie cookie) {
421: HttpSetCookieList clist = reply.getSetCookie();
422: if (clist == null) {
423: HttpSetCookie cookies[] = new HttpSetCookie[1];
424: cookies[0] = convertCookie(cookie);
425: clist = new HttpSetCookieList(cookies);
426: } else {
427: clist.addSetCookie(convertCookie(cookie));
428: }
429: reply.setSetCookie(clist);
430: }
431:
432: private HttpSetCookie convertCookie(Cookie cookie) {
433: HttpSetCookie scookie = new HttpSetCookie(true, cookie
434: .getName(), cookie.getValue());
435: scookie.setComment(cookie.getComment());
436: scookie.setDomain(cookie.getDomain());
437: scookie.setMaxAge(cookie.getMaxAge());
438: scookie.setPath(cookie.getPath());
439: scookie.setSecurity(cookie.getSecure());
440: scookie.setVersion(cookie.getVersion());
441: return scookie;
442: }
443:
444: /**
445: * Encodes the specified URL for use in the sendRedirect method or, if
446: * encoding is not needed, returns the URL unchanged. The implementation
447: * of this method should include the logic to determine whether the
448: * session ID needs to be encoded in the URL.
449: * Because the rules for making this determination differ from those used
450: * to decide whether to encode a normal link, this method is seperate from
451: * the encodeUrl method.
452: * <p>All URLs sent to the HttpServletResponse.sendRedirect method should
453: * be run through this method. Otherwise, URL rewriting canont be used
454: * with browsers which do not support cookies.
455: * @param url - the url to be encoded.
456: * @return the encoded URL if encoding is needed; the unchanged URL
457: * otherwise.
458: * @deprecated since jsdk2.1
459: * @see JigsawHttpServletResponse#sendRedirect
460: * @see JigsawHttpServletResponse#encodeUrl
461: */
462: public String encodeRedirectUrl(String url) {
463: try {
464: URL redirect = new URL(url);
465: URL requested = new URL(jrequest.getRequestURI());
466: if (redirect.getHost().equals(requested.getHost())
467: && redirect.getPort() == requested.getPort())
468: return encodeUrl(url);
469: } catch (MalformedURLException ex) {
470: //error so return url.
471: return url;
472: }
473: return url;
474: }
475:
476: /**
477: * Encodes the specified URL for use in the sendRedirect method or, if
478: * encoding is not needed, returns the URL unchanged. The implementation
479: * of this method should include the logic to determine whether the
480: * session ID needs to be encoded in the URL.
481: * Because the rules for making this determination differ from those used
482: * to decide whether to encode a normal link, this method is seperate from
483: * the encodeUrl method.
484: * <p>All URLs sent to the HttpServletResponse.sendRedirect method should
485: * be run through this method. Otherwise, URL rewriting canont be used
486: * with browsers which do not support cookies.
487: * @param url - the url to be encoded.
488: * @return the encoded URL if encoding is needed; the unchanged URL
489: * otherwise.
490: * @see JigsawHttpServletResponse#sendRedirect
491: * @see JigsawHttpServletResponse#encodeUrl
492: */
493: public String encodeRedirectURL(String url) {
494: return encodeRedirectUrl(url);
495: }
496:
497: /**
498: * Encodes the specified URL by including the session ID in it, or, if
499: * encoding is not needed, returns the URL unchanged. The implementation of
500: * this method should include the logic to determine whether the session ID
501: * needs to be encoded in the URL. For example, if the browser supports
502: * cookies, or session tracking is turned off, URL encoding is unnecessary.
503: * <p>All URLs emitted by a Servlet should be run through this method.
504: * Otherwise, URL rewriting cannot be used with browsers which do not
505: * support cookies.
506: * @param url - the url to be encoded.
507: * @return the encoded URL if encoding is needed; the unchanged URL
508: * otherwise.
509: * @deprecated since jsdk2.1
510: */
511: public String encodeUrl(String url) {
512: if (!jrequest.isRequestedSessionIdFromCookie()) {
513: url = url + ((url.indexOf("?") != -1) ? "&" : "?")
514: + jrequest.getCookieName() + "="
515: + jrequest.getSession(true).getId();
516: }
517: return url;
518: }
519:
520: /**
521: * Encodes the specified URL by including the session ID in it, or, if
522: * encoding is not needed, returns the URL unchanged. The implementation of
523: * this method should include the logic to determine whether the session ID
524: * needs to be encoded in the URL. For example, if the browser supports
525: * cookies, or session tracking is turned off, URL encoding is unnecessary.
526: * <p>All URLs emitted by a Servlet should be run through this method.
527: * Otherwise, URL rewriting cannot be used with browsers which do not
528: * support cookies.
529: * @param url - the url to be encoded.
530: * @return the encoded URL if encoding is needed; the unchanged URL
531: * otherwise.
532: */
533: public String encodeURL(String url) {
534: return encodeUrl(url);
535: }
536:
537: /**
538: * Return the Charset parameter of content type
539: * @return A String instance
540: */
541: public String getCharacterEncoding() {
542: org.w3c.www.mime.MimeType type = reply.getContentType();
543: if ((type != null) && (type.hasParameter(CHARSET_PARAMETER))) {
544: return type.getParameterValue(CHARSET_PARAMETER);
545: }
546: // as specified in the javadoc, default to iso-8859-1
547: return "ISO-8859-1".intern();
548: }
549:
550: /**
551: * Returns a print writer for writing formatted text responses.
552: * The MIME type of the response will be modified, if necessary, to
553: * reflect the character encoding used, through the charset=... property.
554: * This means that the content type must be set before calling this
555: * method.
556: * @exception UnsupportedEncodingException if no such encoding can be
557: * provided
558: * @exception IllegalStateException if getOutputStream has been called
559: * on this same request.
560: * @exception IOException on other errors.
561: * @see JigsawHttpServletResponse#getOutputStream
562: * @see JigsawHttpServletResponse#setContentType
563: */
564: public synchronized PrintWriter getWriter() throws IOException,
565: UnsupportedEncodingException {
566: if (stream_state == OUTPUT_STREAM_USED) {
567: // obviously output is not null, but it doesn't cost...
568: if ((output != null) && output.isCommitted()) {
569: throw new IllegalStateException("Output stream used");
570: }
571: output.reset();
572: }
573:
574: stream_state = STREAM_WRITER_USED;
575:
576: if (writer == null) {
577: writer = new PrintWriter(
578: new OutputStreamWriter(getJigsawOutputStream(true),
579: getCharacterEncoding()));
580: }
581: return writer;
582: }
583:
584: /**
585: * Flush the output stream.
586: * @param close Close the stream if true.
587: * @exception IOException if an IO error occurs.
588: */
589: protected synchronized void flushStream(boolean close)
590: throws IOException {
591: if (state == STATE_ALL_DONE) {
592: return;
593: }
594: int writeLength;
595:
596: if (stream_state == OUTPUT_STREAM_USED) {
597: output.flush();
598: } else if (stream_state == STREAM_WRITER_USED) {
599: writer.flush();
600: if (close) {
601: output.realFlush();
602: } else {
603: output.flush();
604: }
605: } else {
606: // force flush even if no stream are openned
607: getWriter();
608: writer.flush();
609: output.realFlush();
610: }
611:
612: if (request.hasState(INCLUDED)) {
613: if (out == null)
614: return;
615:
616: if (fixedContentLength != CALC_CONTENT_LENGTH) {
617: writeLength = (out.size() < fixedContentLength) ? (out
618: .size()) : (fixedContentLength);
619: } else {
620: writeLength = out.size();
621: }
622: reply.setContentLength(writeLength);
623:
624: OutputStream rout = reply.getOutputStream(false);
625: byte content[] = out.toByteArray();
626: if (close)
627: out.close();
628: else
629: out.reset();
630: rout.write(content);
631: rout.flush();
632: } else {
633: if ((pout != null) && close) {
634: pout.flush();
635: pout.close();
636: }
637: }
638: }
639:
640: // 2.2
641:
642: /**
643: * Sets the preferred buffer size for the body of the response.
644: * The servlet container will use a buffer at least as large as
645: * the size requested. The actual buffer size used can be found
646: * using <code>getBufferSize</code>.
647: *
648: * <p>A larger buffer allows more content to be written before anything is
649: * actually sent, thus providing the servlet with more time to set
650: * appropriate status codes and headers. A smaller buffer decreases
651: * server memory load and allows the client to start receiving data more
652: * quickly.
653: *
654: * <p>This method must be called before any response body content is
655: * written; if content has been written, this method throws an
656: * <code>IllegalStateException</code>.
657: * @param size the preferred buffer size
658: * @exception IllegalStateException if this method is called after
659: * content has been written
660: * @see #getBufferSize
661: * @see #flushBuffer
662: * @see #isCommitted
663: * @see #reset
664: */
665: public void setBufferSize(int size) {
666: if (stream_state != STREAM_STATE_INITIAL) {
667: throw new IllegalStateException(
668: "Stream already initialized");
669: }
670: buffer_size = size < MIN_BUFFER_SIZE ? MIN_BUFFER_SIZE : size;
671: }
672:
673: /**
674: * Returns the actual buffer size used for the response. If no buffering
675: * is used, this method returns 0.
676: * @return the actual buffer size used
677: * @see #setBufferSize
678: * @see #flushBuffer
679: * @see #isCommitted
680: * @see #reset
681: */
682: public int getBufferSize() {
683: return buffer_size;
684: }
685:
686: /**
687: * Forces any content in the buffer to be written to the client. A call
688: * to this method automatically commits the response, meaning the status
689: * code and headers will be written.
690: * @see #setBufferSize
691: * @see #getBufferSize
692: * @see #isCommitted
693: * @see #reset
694: */
695: public void flushBuffer() throws IOException {
696: if (output != null) {
697: if (stream_state == STREAM_WRITER_USED) {
698: writer.flush();
699: }
700: output.flush();
701: }
702: }
703:
704: /**
705: * Returns a boolean indicating if the response has been
706: * committed. A commited response has already had its status
707: * code and headers written.
708: * @return a boolean indicating if the response has been
709: * committed
710: * @see #setBufferSize
711: * @see #getBufferSize
712: * @see #flushBuffer
713: * @see #reset
714: */
715: public boolean isCommitted() {
716: if (output != null) {
717: return output.isCommitted();
718: } else {
719: return false;
720: }
721: }
722:
723: /**
724: * Clears any data that exists in the buffer as well as the status code and
725: * headers. If the response has been committed, this method throws an
726: * <code>IllegalStateException</code>.
727: * @exception IllegalStateException if the response has already been
728: * committed
729: * @see #setBufferSize
730: * @see #getBufferSize
731: * @see #flushBuffer
732: * @see #isCommitted
733: */
734: public void reset() {
735: // FIXME needs to clear headers and status
736: if (output != null) {
737: if (stream_state == STREAM_WRITER_USED) {
738: writer.flush();
739: }
740: output.reset();
741: }
742: }
743:
744: /**
745: * Clears any data that exists in the buffer but not status code and
746: * headers. If the response has been committed, this method throws an
747: * <code>IllegalStateException</code>.
748: * @exception IllegalStateException if the response has already been
749: * committed
750: * @see #setBufferSize
751: * @see #getBufferSize
752: * @see #flushBuffer
753: * @see #isCommitted
754: */
755: public void resetBuffer() {
756: if (output != null) {
757: if (stream_state == STREAM_WRITER_USED) {
758: writer.flush();
759: }
760: output.reset();
761: }
762: }
763:
764: /**
765: * Sets the locale of the response, setting the headers (including the
766: * Content-Type's charset) as appropriate. This method should be called
767: * before a call to {@link #getWriter}. By default, the response locale
768: * is the default locale for the server.
769: * @param loc the locale of the response
770: * @see #getLocale
771: */
772: public void setLocale(Locale locale) {
773: if (locale == null) {
774: return;
775: }
776: this .locale = locale;
777:
778: // content language
779: String lang = locale.getLanguage();
780: if (lang.length() > 0) {
781: String array[] = new String[1];
782: array[0] = lang;
783: reply.setContentLanguage(array);
784: }
785:
786: // content type (charset)
787: String charset = org.w3c.www.mime.Utils.getCharset(locale);
788: if (charset != null) {
789: MimeType contentType = reply.getContentType();
790: // override charset
791: contentType.setParameter(CHARSET_PARAMETER, charset);
792: }
793: }
794:
795: /**
796: * Returns the locale assigned to the response.
797: * @see #setLocale
798: */
799: public Locale getLocale() {
800: return locale;
801: }
802:
803: public void addHeader(String name, String value) {
804: String lname = name.toLowerCase();
805: HeaderValue hvalue = reply.getHeaderValue(lname);
806: //
807: // Horrible, Shame on us, I hate that
808: //
809: if (hvalue == null) {
810: setHeader(name, value);
811: } else if (hvalue instanceof HttpAcceptCharsetList) {
812: HttpAcceptCharsetList acl = (HttpAcceptCharsetList) hvalue;
813: acl.addCharset(HttpFactory.parseAcceptCharset(value));
814: } else if (hvalue instanceof HttpAcceptEncodingList) {
815: HttpAcceptEncodingList ael = (HttpAcceptEncodingList) hvalue;
816: ael.addEncoding(HttpFactory.parseAcceptEncoding(value));
817: } else if (hvalue instanceof HttpAcceptLanguageList) {
818: HttpAcceptLanguageList all = (HttpAcceptLanguageList) hvalue;
819: all.addLanguage(HttpFactory.parseAcceptLanguage(value));
820: } else if (hvalue instanceof HttpAcceptList) {
821: HttpAcceptList al = (HttpAcceptList) hvalue;
822: al.addAccept(HttpFactory.parseAccept(value));
823: } else if (hvalue instanceof HttpEntityTagList) {
824: HttpEntityTagList etl = (HttpEntityTagList) hvalue;
825: etl.addTag(HttpFactory.parseETag(value));
826: } else if (hvalue instanceof HttpExtList) {
827: HttpExtList el = (HttpExtList) hvalue;
828: el.addHttpExt(new HttpExt(value, false));
829: } else if (hvalue instanceof HttpCookieList) {
830: // shouldn't be used, but who knows?
831: HttpCookieList cl = (HttpCookieList) hvalue;
832: HttpCookieList ncl = HttpFactory.parseCookieList(value);
833: HttpCookie scookies[] = ncl.getCookies();
834: for (int i = 0; i < scookies.length; i++) {
835: HttpCookie cookie = scookies[i];
836: cl.addCookie(cookie.getName(), cookie.getValue());
837: }
838: } else if (hvalue instanceof HttpParamList) {
839: int idx = value.indexOf('=');
840: if (idx != -1) {
841: String pname = value.substring(0, idx);
842: String pvalue = value.substring(idx + 1);
843: HttpParamList pl = (HttpParamList) hvalue;
844: pl.setParameter(pname, pvalue);
845: }
846: } else if (hvalue instanceof HttpRangeList) {
847: HttpRangeList rl = (HttpRangeList) hvalue;
848: rl.addRange(HttpFactory.parseRange(value));
849: } else if (hvalue instanceof HttpSetCookieList) {
850: HttpSetCookieList scl = (HttpSetCookieList) hvalue;
851: HttpSetCookieList nscl = HttpFactory
852: .parseSetCookieList(value);
853: HttpSetCookie scookies[] = nscl.getSetCookies();
854: for (int i = 0; i < scookies.length; i++) {
855: scl.addSetCookie(scookies[i]);
856: }
857: } else if (hvalue instanceof HttpTokenList) {
858: ((HttpTokenList) hvalue).addToken(value, true);
859: } else if (hvalue instanceof HttpWarningList) {
860: HttpWarningList wl = (HttpWarningList) hvalue;
861: wl.addWarning(HttpFactory.parseWarning(value));
862: } else if (hvalue instanceof HttpString) {
863: // this is the default type for unkown header
864: // we don't know what it is, so just append
865: HttpString s = (HttpString) hvalue;
866: String string = (String) s.getValue();
867: s.setValue(string + ", " + value);
868: } else {
869: // not compliant with HTTP/1.1, override
870: setHeader(name, value);
871: }
872: }
873:
874: public void addDateHeader(String name, long date) {
875: addHeader(name, HttpFactory.makeDate(date).toExternalForm());
876: }
877:
878: public void addIntHeader(String name, int value) {
879: addHeader(name, String.valueOf(value));
880: }
881:
882: JigsawHttpServletResponse(Request request, Reply reply) {
883: this.request = request;
884: this.reply = reply;
885: this.buffer_size = DEFAULT_BUFFER_SIZE;
886: }
887:
888: }
|