001    /*
002     * Copyright 1999,2004 The Apache Software Foundation.
003     * 
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     *      http://www.apache.org/licenses/LICENSE-2.0
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    
018    package org.apache.commons.messagelet.impl;
019    
020    
021    import java.io.IOException;
022    import java.text.SimpleDateFormat;
023    import java.util.ArrayList;
024    import java.util.Date;
025    import java.util.HashMap;
026    import java.util.Locale;
027    import java.util.TimeZone;
028    
029    import javax.servlet.http.Cookie;
030    import javax.servlet.http.HttpServletResponse;
031    
032    
033    /**
034     * Based on the HttpRequestBase code from Catalina.
035     *
036     * @author Craig R. McClanahan
037     * @author James Strachan
038     * @version $Revision: 155459 $ $Date: 2005-02-26 13:24:44 +0000 (Sat, 26 Feb 2005) $
039     */
040    
041    public class HttpServletResponseImpl extends ServletResponseImpl implements HttpServletResponse {
042    
043        /**
044         * The set of Cookies associated with this Response.
045         */
046        protected ArrayList cookies = new ArrayList();
047    
048    
049        /**
050         * The date format we will use for creating date headers.
051         */
052        protected static final SimpleDateFormat format =
053            new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz",Locale.US);
054        static {
055            format.setTimeZone(TimeZone.getTimeZone("GMT"));
056        };
057    
058    
059        /**
060         * The HTTP headers explicitly added via addHeader(), but not including
061         * those to be added with setContentLength(), setContentType(), and so on.
062         * This collection is keyed by the header name, and the elements are
063         * ArrayLists containing the associated values that have been set.
064         */
065        protected HashMap headers = new HashMap();
066    
067    
068    
069        /**
070         * The error message set by <code>sendError()</code>.
071         */
072        protected String message = getStatusMessage(HttpServletResponse.SC_OK);
073    
074    
075        /**
076         * The HTTP status code associated with this Response.
077         */
078        protected int status = HttpServletResponse.SC_OK;
079    
080    
081        /**
082         * The time zone with which to construct date headers.
083         */
084        protected static final TimeZone zone = TimeZone.getTimeZone("GMT");
085    
086    
087    
088        // --------------------------------------------------------- Public Methods
089    
090    
091        /**
092         * Return an array of all cookies set for this response, or
093         * a zero-length array if no cookies have been set.
094         */
095        public Cookie[] getCookies() {
096    
097            synchronized (cookies) {
098                return ((Cookie[]) cookies.toArray(new Cookie[cookies.size()]));
099            }
100    
101        }
102    
103    
104        /**
105         * Return the value for the specified header, or <code>null</code> if this
106         * header has not been set.  If more than one value was added for this
107         * name, only the first is returned; use getHeaderValues() to retrieve all
108         * of them.
109         *
110         * @param name Header name to look up
111         */
112        public String getHeader(String name) {
113    
114            ArrayList values = null;
115            synchronized (headers) {
116                values = (ArrayList) headers.get(name);
117            }
118            if (values != null)
119                return ((String) values.get(0));
120            else
121                return (null);
122    
123        }
124    
125    
126        /**
127         * Return an array of all the header names set for this response, or
128         * a zero-length array if no headers have been set.
129         */
130        public String[] getHeaderNames() {
131    
132            synchronized (headers) {
133                String results[] = new String[headers.size()];
134                return ((String[]) headers.keySet().toArray(results));
135            }
136    
137        }
138    
139    
140        /**
141         * Return an array of all the header values associated with the
142         * specified header name, or an zero-length array if there are no such
143         * header values.
144         *
145         * @param name Header name to look up
146         */
147        public String[] getHeaderValues(String name) {
148    
149            ArrayList values = null;
150            synchronized (headers) {
151                values = (ArrayList) headers.get(name);
152            }
153            if (values == null)
154                return (new String[0]);
155            String results[] = new String[values.size()];
156            return ((String[]) values.toArray(results));
157    
158        }
159    
160    
161        /**
162         * Return the error message that was set with <code>sendError()</code>
163         * for this Response.
164         */
165        public String getMessage() {
166    
167            return (this.message);
168    
169        }
170    
171    
172        /**
173         * Return the HTTP status code associated with this Response.
174         */
175        public int getStatus() {
176    
177            return (this.status);
178    
179        }
180    
181    
182        /**
183         * Release all object references, and initialize instance variables, in
184         * preparation for reuse of this object.
185         */
186        public void recycle() {
187            //super.recycle();
188            cookies.clear();
189            headers.clear();
190            message = getStatusMessage(HttpServletResponse.SC_OK);
191            status = HttpServletResponse.SC_OK;
192    
193        }
194    
195    
196        /**
197         * Reset this response, and specify the values for the HTTP status code
198         * and corresponding message.
199         *
200         * @exception IllegalStateException if this response has already been
201         *  committed
202         */
203        public void reset(int status, String message) {
204    
205            reset();
206            setStatus(status, message);
207    
208        }
209    
210    
211        // ------------------------------------------------------ Protected Methods
212    
213    
214        /**
215         * Returns a default status message for the specified HTTP status code.
216         *
217         * @param status The status code for which a message is desired
218         */
219        protected String getStatusMessage(int status) {
220    
221            switch (status) {
222            case SC_OK:
223                return ("OK");
224            case SC_ACCEPTED:
225                return ("Accepted");
226            case SC_BAD_GATEWAY:
227                return ("Bad Gateway");
228            case SC_BAD_REQUEST:
229                return ("Bad Request");
230            case SC_CONFLICT:
231                return ("Conflict");
232            case SC_CONTINUE:
233                return ("Continue");
234            case SC_CREATED:
235                return ("Created");
236            case SC_EXPECTATION_FAILED:
237                return ("Expectation Failed");
238            case SC_FORBIDDEN:
239                return ("Forbidden");
240            case SC_GATEWAY_TIMEOUT:
241                return ("Gateway Timeout");
242            case SC_GONE:
243                return ("Gone");
244            case SC_HTTP_VERSION_NOT_SUPPORTED:
245                return ("HTTP Version Not Supported");
246            case SC_INTERNAL_SERVER_ERROR:
247                return ("Internal Server Error");
248            case SC_LENGTH_REQUIRED:
249                return ("Length Required");
250            case SC_METHOD_NOT_ALLOWED:
251                return ("Method Not Allowed");
252            case SC_MOVED_PERMANENTLY:
253                return ("Moved Permanently");
254            case SC_MOVED_TEMPORARILY:
255                return ("Moved Temporarily");
256            case SC_MULTIPLE_CHOICES:
257                return ("Multiple Choices");
258            case SC_NO_CONTENT:
259                return ("No Content");
260            case SC_NON_AUTHORITATIVE_INFORMATION:
261                return ("Non-Authoritative Information");
262            case SC_NOT_ACCEPTABLE:
263                return ("Not Acceptable");
264            case SC_NOT_FOUND:
265                return ("Not Found");
266            case SC_NOT_IMPLEMENTED:
267                return ("Not Implemented");
268            case SC_NOT_MODIFIED:
269                return ("Not Modified");
270            case SC_PARTIAL_CONTENT:
271                return ("Partial Content");
272            case SC_PAYMENT_REQUIRED:
273                return ("Payment Required");
274            case SC_PRECONDITION_FAILED:
275                return ("Precondition Failed");
276            case SC_PROXY_AUTHENTICATION_REQUIRED:
277                return ("Proxy Authentication Required");
278            case SC_REQUEST_ENTITY_TOO_LARGE:
279                return ("Request Entity Too Large");
280            case SC_REQUEST_TIMEOUT:
281                return ("Request Timeout");
282            case SC_REQUEST_URI_TOO_LONG:
283                return ("Request URI Too Long");
284            case SC_REQUESTED_RANGE_NOT_SATISFIABLE:
285                return ("Requested Range Not Satisfiable");
286            case SC_RESET_CONTENT:
287                return ("Reset Content");
288            case SC_SEE_OTHER:
289                return ("See Other");
290            case SC_SERVICE_UNAVAILABLE:
291                return ("Service Unavailable");
292            case SC_SWITCHING_PROTOCOLS:
293                return ("Switching Protocols");
294            case SC_UNAUTHORIZED:
295                return ("Unauthorized");
296            case SC_UNSUPPORTED_MEDIA_TYPE:
297                return ("Unsupported Media Type");
298            case SC_USE_PROXY:
299                return ("Use Proxy");
300            case 207:       // WebDAV
301                return ("Multi-Status");
302            case 422:       // WebDAV
303                return ("Unprocessable Entity");
304            case 423:       // WebDAV
305                return ("Locked");
306            case 507:       // WebDAV
307                return ("Insufficient Storage");
308            default:
309                return ("HTTP Response Status " + status);
310            }
311    
312        }
313    
314    
315    
316        // ------------------------------------------------ ServletResponse Methods
317    
318    
319        /**
320         * Flush the buffer and commit this response.  If this is the first output,
321         * send the HTTP headers prior to the user data.
322         *
323         * @exception IOException if an input/output error occurs
324         */
325        public void flushBuffer() throws IOException {
326    /*
327            if (!isCommitted()) {
328                sendHeaders();
329            }
330    */
331            super.flushBuffer();
332    
333        }
334    
335    
336        /**
337         * Clear any content written to the buffer.  In addition, all cookies
338         * and headers are cleared, and the status is reset.
339         *
340         * @exception IllegalStateException if this response has already
341         *  been committed
342         */
343        public void reset() {
344    
345            if (included)
346                return;     // Ignore any call from an included servlet
347    
348            super.reset();
349            cookies.clear();
350            headers.clear();
351            message = null;
352            status = HttpServletResponse.SC_OK;
353    
354        }
355    
356    
357        /**
358         * Set the content length (in bytes) for this Response.
359         *
360         * @param length The new content length
361         */
362        public void setContentLength(int length) {
363    
364            if (isCommitted())
365                return;
366    
367            if (included)
368                return;     // Ignore any call from an included servlet
369    
370            super.setContentLength(length);
371    
372        }
373    
374    
375    
376        /**
377         * Set the content type for this Response.
378         *
379         * @param type The new content type
380         */
381        public void setContentType(String type) {
382    
383            if (isCommitted())
384                return;
385    
386            if (included)
387                return;     // Ignore any call from an included servlet
388    
389            super.setContentType(type);
390    
391        }
392    
393    
394        /**
395         * Set the Locale that is appropriate for this response, including
396         * setting the appropriate character encoding.
397         *
398         * @param locale The new locale
399         */
400        public void setLocale(Locale locale) {
401    
402            if (isCommitted())
403                return;
404    
405            if (included)
406                return;     // Ignore any call from an included servlet
407    
408            super.setLocale(locale);
409            String language = locale.getLanguage();
410            if ((language != null) && (language.length() > 0)) {
411                String country = locale.getCountry();
412                StringBuffer value = new StringBuffer(language);
413                if ((country != null) && (country.length() > 0)) {
414                    value.append('-');
415                    value.append(country);
416                }
417                setHeader("Content-Language", value.toString());
418            }
419    
420        }
421    
422    
423        // -------------------------------------------- HttpServletResponse Methods
424    
425    
426        /**
427         * Add the specified Cookie to those that will be included with
428         * this Response.
429         *
430         * @param cookie Cookie to be added
431         */
432        public void addCookie(Cookie cookie) {
433    
434            if (isCommitted())
435                return;
436    
437            if (included)
438                return;     // Ignore any call from an included servlet
439    
440            synchronized (cookies) {
441                cookies.add(cookie);
442            }
443    
444        }
445    
446    
447        /**
448         * Add the specified date header to the specified value.
449         *
450         * @param name Name of the header to set
451         * @param value Date value to be set
452         */
453        public void addDateHeader(String name, long value) {
454    
455            if (isCommitted())
456                return;
457    
458            if (included)
459                return;     // Ignore any call from an included servlet
460    
461            addHeader(name, format.format(new Date(value)));
462    
463        }
464    
465    
466        /**
467         * Add the specified header to the specified value.
468         *
469         * @param name Name of the header to set
470         * @param value Value to be set
471         */
472        public void addHeader(String name, String value) {
473    
474            if (isCommitted())
475                return;
476    
477            if (included)
478                return;     // Ignore any call from an included servlet
479    
480            synchronized (headers) {
481                ArrayList values = (ArrayList) headers.get(name);
482                if (values == null) {
483                    values = new ArrayList();
484                    headers.put(name, values);
485                }
486                values.add(value);
487            }
488    
489        }
490    
491    
492        /**
493         * Add the specified integer header to the specified value.
494         *
495         * @param name Name of the header to set
496         * @param value Integer value to be set
497         */
498        public void addIntHeader(String name, int value) {
499    
500            if (isCommitted())
501                return;
502    
503            if (included)
504                return;     // Ignore any call from an included servlet
505    
506            addHeader(name, "" + value);
507    
508        }
509    
510    
511        /**
512         * Has the specified header been set already in this response?
513         *
514         * @param name Name of the header to check
515         */
516        public boolean containsHeader(String name) {
517    
518            synchronized (headers) {
519                return (headers.get(name) != null);
520            }
521    
522        }
523    
524    
525        /**
526         * Encode the session identifier associated with this response
527         * into the specified redirect URL, if necessary.
528         *
529         * @param url URL to be encoded
530         */
531        public String encodeRedirectURL(String url) {
532    
533            return (url);
534    
535        }
536    
537    
538        /**
539         * Encode the session identifier associated with this response
540         * into the specified redirect URL, if necessary.
541         *
542         * @param url URL to be encoded
543         *
544         * @deprecated As of Version 2.1 of the Java Servlet API, use
545         *  <code>encodeRedirectURL()</code> instead.
546         */
547        public String encodeRedirectUrl(String url) {
548    
549            return (encodeRedirectURL(url));
550    
551        }
552    
553    
554        /**
555         * Encode the session identifier associated with this response
556         * into the specified URL, if necessary.
557         *
558         * @param url URL to be encoded
559         */
560        public String encodeURL(String url) {
561            
562            return (url);
563        }
564    
565    
566        /**
567         * Encode the session identifier associated with this response
568         * into the specified URL, if necessary.
569         *
570         * @param url URL to be encoded
571         *
572         * @deprecated As of Version 2.1 of the Java Servlet API, use
573         *  <code>encodeURL()</code> instead.
574         */
575        public String encodeUrl(String url) {
576    
577            return (encodeURL(url));
578    
579        }
580    
581    
582        /**
583         * Send an error response with the specified status and a
584         * default message.
585         *
586         * @param status HTTP status code to send
587         *
588         * @exception IllegalStateException if this response has
589         *  already been committed
590         * @exception IOException if an input/output error occurs
591         */
592        public void sendError(int status) throws IOException {
593    
594            sendError(status, getStatusMessage(status));
595    
596        }
597    
598    
599        /**
600         * Send an error response with the specified status and message.
601         *
602         * @param status HTTP status code to send
603         * @param message Corresponding message to send
604         *
605         * @exception IllegalStateException if this response has
606         *  already been committed
607         * @exception IOException if an input/output error occurs
608         */
609        public void sendError(int status, String message) throws IOException {
610    
611            if (isCommitted()) {
612                throw new IllegalStateException( "Cannot send error, already committed" );
613            }
614    
615            if (included) {
616                return;     // Ignore any call from an included servlet
617            }
618    
619            //setError();
620    
621            // Record the status code and message.
622            this.status = status;
623            this.message = message;
624    
625            // Clear any data content that has been buffered
626            resetBuffer();
627    
628            // Cause the response to be committed
629            /* Per spec clarification, no default content type is set
630            String contentType = getContentType();
631            if ((contentType == null) || "text/plain".equals(contentType))
632                setContentType("text/html");
633            */
634    
635            // Temporarily comment out the following flush so that
636            // default error reports can still set the content type
637            // FIXME - this stuff needs to be refactored
638            /*
639            try {
640                flushBuffer();
641            } catch (IOException e) {
642                ;
643            }
644            */
645    
646        }
647    
648    
649        /**
650         * Send a temporary redirect to the specified redirect location URL.
651         *
652         * @param location Location URL to redirect to
653         *
654         * @exception IllegalStateException if this response has
655         *  already been committed
656         * @exception IOException if an input/output error occurs
657         */
658        public void sendRedirect(String location) throws IOException {
659    
660            if (isCommitted()) {
661                throw new IllegalStateException( "Cannot send error, already committed" );
662            }
663            if (included)
664                return;     // Ignore any call from an included servlet
665    
666            // Clear any data content that has been buffered
667            resetBuffer();
668    
669            // Generate a temporary redirect to the specified location
670            //String absolute = toAbsolute(location);
671            String absolute = location;
672            setStatus(SC_MOVED_TEMPORARILY);
673            setHeader("Location", absolute);
674    
675        }
676    
677    
678        /**
679         * Set the specified date header to the specified value.
680         *
681         * @param name Name of the header to set
682         * @param value Date value to be set
683         */
684        public void setDateHeader(String name, long value) {
685    
686            if (isCommitted())
687                return;
688    
689            if (included)
690                return;     // Ignore any call from an included servlet
691    
692            setHeader(name, format.format(new Date(value)));
693    
694        }
695    
696    
697        /**
698         * Set the specified header to the specified value.
699         *
700         * @param name Name of the header to set
701         * @param value Value to be set
702         */
703        public void setHeader(String name, String value) {
704    
705            if (isCommitted())
706                return;
707    
708            if (included)
709                return;     // Ignore any call from an included servlet
710    
711            ArrayList values = new ArrayList();
712            values.add(value);
713            synchronized (headers) {
714                headers.put(name, values);
715            }
716    
717            String match = name.toLowerCase();
718            if (match.equals("content-length")) {
719                int contentLength = -1;
720                try {
721                    contentLength = Integer.parseInt(value);
722                } catch (NumberFormatException e) {
723                    ;
724                }
725                if (contentLength >= 0)
726                    setContentLength(contentLength);
727            } else if (match.equals("content-type")) {
728                setContentType(value);
729            }
730    
731        }
732    
733    
734        /**
735         * Set the specified integer header to the specified value.
736         *
737         * @param name Name of the header to set
738         * @param value Integer value to be set
739         */
740        public void setIntHeader(String name, int value) {
741    
742            if (isCommitted())
743                return;
744    
745            if (included)
746                return;     // Ignore any call from an included servlet
747    
748            setHeader(name, "" + value);
749    
750        }
751    
752    
753        /**
754         * Set the HTTP status to be returned with this response.
755         *
756         * @param status The new HTTP status
757         */
758        public void setStatus(int status) {
759    
760            setStatus(status, getStatusMessage(status));
761    
762        }
763    
764    
765        /**
766         * Set the HTTP status and message to be returned with this response.
767         *
768         * @param status The new HTTP status
769         * @param message The associated text message
770         *
771         * @deprecated As of Version 2.1 of the Java Servlet API, this method
772         *  has been deprecated due to the ambiguous meaning of the message
773         *  parameter.
774         */
775        public void setStatus(int status, String message) {
776    
777            if (included)
778                return;     // Ignore any call from an included servlet
779    
780            this.status = status;
781            this.message = message;
782    
783        }
784    
785    
786    }