KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > go > teaservlet > ApplicationResponseImpl


1 /* ====================================================================
2  * TeaServlet - Copyright (c) 1999-2000 Walt Disney Internet Group
3  * ====================================================================
4  * The Tea Software License, Version 1.1
5  *
6  * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  * notice, this list of conditions and the following disclaimer in
17  * the documentation and/or other materials provided with the
18  * distribution.
19  *
20  * 3. The end-user documentation included with the redistribution,
21  * if any, must include the following acknowledgment:
22  * "This product includes software developed by the
23  * Walt Disney Internet Group (http://opensource.go.com/)."
24  * Alternately, this acknowledgment may appear in the software itself,
25  * if and wherever such third-party acknowledgments normally appear.
26  *
27  * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must
28  * not be used to endorse or promote products derived from this
29  * software without prior written permission. For written
30  * permission, please contact opensource@dig.com.
31  *
32  * 5. Products derived from this software may not be called "Tea",
33  * "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet",
34  * "Kettle", "Trove" or "BeanDoc" appear in their name, without prior
35  * written permission of the Walt Disney Internet Group.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE WALT DISNEY INTERNET GROUP OR ITS
41  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
42  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
43  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
44  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
45  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
47  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48  * ====================================================================
49  *
50  * For more information about Tea, please see http://opensource.go.com/.
51  */

52
53 package com.go.teaservlet;
54
55 import java.io.IOException JavaDoc;
56 import java.io.OutputStream JavaDoc;
57 import java.io.Writer JavaDoc;
58 import java.io.PrintWriter JavaDoc;
59 import java.io.UnsupportedEncodingException JavaDoc;
60 import java.util.Locale JavaDoc;
61 import java.util.zip.CRC32 JavaDoc;
62 import javax.servlet.ServletOutputStream JavaDoc;
63 import javax.servlet.http.HttpServletResponse JavaDoc;
64 import com.go.teaservlet.util.FilteredHttpServletResponse;
65 import com.go.teaservlet.io.CharToByteBuffer;
66 import com.go.teaservlet.io.InternedCharToByteBuffer;
67 import com.go.trove.io.ByteData;
68 import com.go.trove.io.ByteBuffer;
69 import com.go.trove.io.DefaultByteBuffer;
70 import com.go.trove.io.ByteBufferOutputStream;
71 import com.go.trove.io.CharToByteBufferWriter;
72 import com.go.trove.log.Log;
73 import com.go.trove.util.Deflater;
74 import com.go.trove.util.DeflaterPool;
75 import com.go.trove.util.DeflaterOutputStream;
76 import com.go.tea.runtime.OutputReceiver;
77 import com.go.tea.runtime.Substitution;
78
79 /******************************************************************************
80  * A HttpServletResponse wrapper that tracks whether a redirect or error will
81  * be sent to the client.
82  *
83  * @author Reece Wilton, Brian S O'Neill
84  * @version
85  * <!--$$Revision:--> 22 <!-- $-->, <!--$$JustDate:--> 01/07/12 <!-- $-->
86  */

87 class ApplicationResponseImpl extends FilteredHttpServletResponse
88     implements ApplicationResponse
89 {
90     private static final byte[] GZIP_HEADER = {
91         (byte)0x1f, (byte)0x8b, // Magic number
92
(byte)Deflater.DEFLATED, // Compression method
93
0, // Flags
94
0, 0, 0, 0, // Modification time
95
0, // Extra flags
96
0, // Operating system
97
};
98
99     private static final byte[] FINAL_BLANK_HEADER = {
100         1, 0, 0, (byte)0xff, (byte)0xff
101     };
102
103     // Writes a short in Intel byte order.
104
static void writeShort(OutputStream JavaDoc out, int i) throws IOException JavaDoc {
105         out.write((byte)i);
106         out.write((byte)(i >> 8));
107     }
108
109     // Writes an int in Intel byte order.
110
static void writeInt(OutputStream JavaDoc out, int i) throws IOException JavaDoc {
111         out.write((byte)i);
112         out.write((byte)(i >> 8));
113         out.write((byte)(i >> 16));
114         out.write((byte)(i >> 24));
115     }
116
117     protected final Log mLog;
118     protected final CharToByteBuffer mBuffer;
119
120     // Bit 0: When set, response redirect or error.
121
// Bit 1: When set, response finished.
122
protected int mState;
123
124     private HttpContext mHttpContext;
125     private MergedApplication mApp;
126     private ApplicationRequest mRequest;
127     private ServletOutputStream JavaDoc mOut;
128     private PrintWriter JavaDoc mWriter;
129
130     private int mCompressedSegments;
131
132     ApplicationResponseImpl(HttpServletResponse JavaDoc response, Log log)
133         throws IOException JavaDoc
134     {
135         this(response, log, new DefaultByteBuffer());
136     }
137
138     ApplicationResponseImpl(HttpServletResponse JavaDoc response, Log log,
139                             ByteBuffer bb)
140         throws IOException JavaDoc
141     {
142         super(response);
143         mLog = log;
144         String JavaDoc encoding = response.getCharacterEncoding();
145         if (encoding == null) {
146             encoding = "ISO-8859-1";
147         }
148         CharToByteBuffer buffer = new FastCharToByteBuffer(bb, encoding);
149         mBuffer = new InternedCharToByteBuffer(buffer);
150     }
151     
152     public ServletOutputStream JavaDoc getOutputStream() {
153         if (mOut == null) {
154             final OutputStream JavaDoc out = new ByteBufferOutputStream(mBuffer);
155             mOut = new ServletOutputStream JavaDoc() {
156                 public void write(int b) throws IOException JavaDoc {
157                     out.write(b);
158                 }
159                 public void write(byte[] buf) throws IOException JavaDoc {
160                     out.write(buf);
161                 }
162                 public void write(byte[] buf, int off, int len)
163                     throws IOException JavaDoc
164                 {
165                     out.write(buf, off, len);
166                 }
167             };
168         }
169         return mOut;
170     }
171     
172     public PrintWriter JavaDoc getWriter() throws IOException JavaDoc {
173         if (mWriter == null) {
174             Writer JavaDoc w = new CharToByteBufferWriter(mBuffer);
175             mWriter = new PrintWriter JavaDoc(w, false);
176         }
177         return mWriter;
178     }
179
180     public void setContentLength(int len) {
181         // Ignore.
182
}
183     
184     public void setContentType(String JavaDoc type) {
185         super.setContentType(type);
186         try {
187             mBuffer.setEncoding(getCharacterEncoding());
188         }
189         catch (IOException JavaDoc e) {
190             Thread JavaDoc t = Thread.currentThread();
191             t.getThreadGroup().uncaughtException(t, e);
192         }
193     }
194
195     public void setBufferSize(int size) {
196         // Ignore.
197
}
198     
199     public int getBufferSize() {
200         return Integer.MAX_VALUE;
201     }
202
203     public void flushBuffer() {
204         // Ignore.
205
}
206
207     public void resetBuffer() {
208         // Ignore.
209
}
210
211     public boolean isCommitted() {
212         return false;
213     }
214
215     public void reset() {
216         super.reset();
217         resetBuffer();
218     }
219
220     public void setLocale(Locale JavaDoc locale) {
221         super.setLocale(locale);
222         mHttpContext.setLocale(locale);
223     }
224
225     public void sendError(int statusCode, String JavaDoc msg) throws IOException JavaDoc {
226         mState |= 1;
227         super.sendError(statusCode, msg);
228     }
229
230     public void sendError(int statusCode) throws IOException JavaDoc {
231         mState |= 1;
232         super.sendError(statusCode);
233     }
234
235     public void sendRedirect(String JavaDoc location) throws IOException JavaDoc {
236         mState |= 1;
237         super.sendRedirect(location);
238     }
239
240     public boolean isRedirectOrError() {
241         return (mState & 1) != 0;
242     }
243
244     public CharToByteBuffer getResponseBuffer() {
245         return mBuffer;
246     }
247
248     public HttpContext getHttpContext() {
249         return mHttpContext;
250     }
251
252     public void stealOutput(Substitution s, OutputReceiver receiver)
253         throws Exception JavaDoc
254     {
255         ((HttpContextImpl)mHttpContext).stealOutput(s, receiver);
256     }
257
258     public DetachedData execDetached(Substitution s) throws Exception JavaDoc {
259         DetachedResponseImpl response =
260             new DetachedResponseImpl(mResponse, mLog);
261         HttpContext newContext =
262             (HttpContext)mApp.createContext(mRequest, response);
263
264         HttpContext thisContext = mHttpContext;
265
266         newContext.setLocale(thisContext.getLocale());
267         newContext.nullFormat(thisContext.getNullFormat());
268         newContext.dateFormat(thisContext.getDateFormat(),
269                               thisContext.getDateFormatTimeZone());
270         newContext.numberFormat(thisContext.getNumberFormat(),
271                                 thisContext.getNumberFormatInfinity(),
272                                 thisContext.getNumberFormatNaN());
273
274         try {
275             s.detach().substitute(newContext);
276         }
277         catch (AbortTemplateException e) {
278         }
279         return response.getData();
280     }
281
282     public DetachedData execDetached(Command command) throws Exception JavaDoc {
283         DetachedResponseImpl response =
284             new DetachedResponseImpl(mResponse, mLog);
285         HttpContext newContext =
286             (HttpContext)mApp.createContext(mRequest, response);
287
288         HttpContext thisContext = mHttpContext;
289
290         newContext.setLocale(thisContext.getLocale());
291         newContext.nullFormat(thisContext.getNullFormat());
292         newContext.dateFormat(thisContext.getDateFormat(),
293                               thisContext.getDateFormatTimeZone());
294         newContext.numberFormat(thisContext.getNumberFormat(),
295                                 thisContext.getNumberFormatInfinity(),
296                                 thisContext.getNumberFormatNaN());
297
298         command.execute(mRequest, response);
299         return response.getData();
300     }
301
302     public boolean insertCommand(Command command) throws Exception JavaDoc {
303         return false;
304     }
305
306     public void finish() throws IOException JavaDoc {
307         if (mState != 0) {
308             return;
309         }
310
311         mState |= 2;
312
313         ByteData bytes = mBuffer;
314         OutputStream JavaDoc out = super.getOutputStream();
315         long length = bytes.getByteCount();
316
317         try {
318             if (mCompressedSegments == 0 || length > 0xffffffffL) {
319                 if (length <= Integer.MAX_VALUE) {
320                     super.setContentLength((int)length);
321                 }
322                 bytes.writeTo(out);
323                 return;
324             }
325             
326             // Write out response using GZIP compressed encoding.
327

328             setHeader("Content-Encoding", "gzip");
329             
330             LengthComputer lc =
331                 new LengthComputer(mCompressedSegments * 2 + 1);
332             bytes.writeTo(lc);
333             lc.nextSegment(false);
334
335             // Add overhead of GZIP header, final blank block, and GZIP footer
336
// to computed length.
337
int contentLength = lc.getLength() + 10 + 5 + 8;
338             super.setContentLength(contentLength);
339
340             // Write GZIP header.
341
out.write(GZIP_HEADER);
342
343             // Write out GZIP blocks.
344
bytes.writeTo(new FinalOut(out, lc.mSegments));
345
346             // Write final blank block header.
347
out.write(FINAL_BLANK_HEADER);
348
349             // Write GZIP footer.
350
// CRC-32 of uncompressed bytes.
351
writeInt(out, computeCRC(bytes));
352             // Count of uncompressed bytes.
353
writeInt(out, (int)length);
354         }
355         finally {
356             try {
357                 bytes.reset();
358             }
359             catch (IOException JavaDoc e) {
360                 mLog.warn(e);
361             }
362         }
363     }
364
365     // Critical callback from MergedApplication in order for execDetached to
366
// work properly.
367
void setAppAndRequest(MergedApplication app, ApplicationRequest request) {
368         mApp = app;
369         mRequest = request;
370     }
371
372     // Critical callback from MergedApplication in order for getHttpContext and
373
// stealOutput to work properly.
374
void setHttpContext(HttpContext context) {
375         mHttpContext = context;
376     }
377
378     // Called from DetachedResponseImpl.
379
void appendCompressed(ByteData compressed, ByteData original)
380         throws IOException JavaDoc
381     {
382         mCompressedSegments++;
383         mBuffer.appendSurrogate(new CompressedData(compressed, original));
384     }
385
386     private int computeCRC(ByteData bytes) throws IOException JavaDoc {
387         final CRC32 JavaDoc crc = new CRC32 JavaDoc();
388
389         OutputStream JavaDoc out = new OutputStream JavaDoc() {
390             public void write(int b) {
391                 crc.update(b);
392             }
393                 
394             public void write(byte[] b) {
395                 crc.update(b, 0, b.length);
396             }
397
398             public void write(byte[] b, int off, int len) {
399                 crc.update(b, off, len);
400             }
401         };
402
403         bytes.writeTo(out);
404         return (int)crc.getValue();
405     }
406
407     private static class CompressedData implements ByteData {
408         private final ByteData mCompressed;
409         private final ByteData mOriginal;
410
411         CompressedData(ByteData compressed, ByteData original) {
412             mCompressed = compressed;
413             mOriginal = original;
414         }
415
416         public long getByteCount() throws IOException JavaDoc {
417             return mOriginal.getByteCount();
418         }
419
420         public void writeTo(OutputStream JavaDoc out) throws IOException JavaDoc {
421             if (out instanceof Segmented) {
422                 ((Segmented)out).nextSegment(true);
423                 mCompressed.writeTo(out);
424                 ((Segmented)out).nextSegment(false);
425             }
426             else {
427                 mOriginal.writeTo(out);
428             }
429         }
430
431         public void reset() throws IOException JavaDoc {
432             mOriginal.reset();
433         }
434     }
435
436     private interface Segmented {
437         void nextSegment(boolean preCompressed);
438     }
439
440     // Output is discarded, but is used to compute final content length of
441
// compressed response.
442
private static class LengthComputer extends OutputStream JavaDoc
443         implements Segmented
444     {
445         int[] mSegments;
446         int mSegCount;
447
448         // If negative, is pre-compressed.
449
private int mCurrentSegment;
450         // If -1, is accepting pre-compressed bytes.
451
private int mSign = 1;
452
453         LengthComputer(int segmentCount) {
454             mSegments = new int[segmentCount];
455         }
456
457         public void write(int b) {
458             mCurrentSegment += mSign;
459         }
460
461         public void write(byte[] b) {
462             mCurrentSegment += b.length * mSign;
463         }
464
465         public void write(byte[] b, int off, int len) {
466             mCurrentSegment += len * mSign;
467         }
468
469         public void nextSegment(boolean preCompressed) {
470             mSegments[mSegCount++] = mCurrentSegment;
471             mSign = preCompressed ? -1 : 1;
472             mCurrentSegment = 0;
473         }
474
475         int getLength() {
476             int length = 0;
477             for (int i=0; i<mSegCount; i++) {
478                 int segLen = mSegments[i];
479                 if (segLen < 0) {
480                     length += -segLen;
481                 }
482                 else {
483                     // Length must include 5 byte header for each block of
484
// 65535 maximum bytes.
485
length += segLen + ((65534 + segLen) / 65535) * 5;
486                 }
487             }
488             return length;
489         }
490     }
491
492     // Writes uncompressed and pre-compressed data joined together.
493
// Uncompressed data is formed into blocks.
494
private static class FinalOut extends OutputStream JavaDoc implements Segmented {
495         private OutputStream JavaDoc mOut;
496         private int[] mSegments;
497         private int mCursor;
498
499         // Remaining space in current block. If negative, is accepting
500
// pre-compressed blocks.
501
private int mBlockLen;
502
503         FinalOut(OutputStream JavaDoc out, int[] segments) {
504             mOut = out;
505             mSegments = segments;
506         }
507
508         public void write(int b) throws IOException JavaDoc {
509             if (mBlockLen < 0) {
510                 mOut.write(b);
511                 return;
512             }
513             if (nextBlock(1) > 0) {
514                 mOut.write(b);
515                 mBlockLen--;
516             }
517         }
518
519         public void write(byte[] b) throws IOException JavaDoc {
520             write(b, 0, b.length);
521         }
522
523         public void write(byte[] b, int off, int len) throws IOException JavaDoc {
524             if (mBlockLen < 0) {
525                 mOut.write(b, off, len);
526                 return;
527             }
528             while (len > 0) {
529                 int amt = nextBlock(len);
530                 if (amt <= 0) {
531                     break;
532                 }
533                 mOut.write(b, off, amt);
534                 len -= amt;
535                 off += amt;
536                 mBlockLen -= amt;
537             }
538         }
539
540         public void nextSegment(boolean preCompressed) {
541             mCursor++;
542             mBlockLen = preCompressed ? -1 : 0;
543         }
544
545         // Returns amount that can be written to block.
546
private int nextBlock(int needed) throws IOException JavaDoc {
547             if (mBlockLen > 0) {
548                 return mBlockLen <= needed ? mBlockLen : needed;
549             }
550
551             // Write header for next block.
552
int blockLen = mSegments[mCursor];
553
554             if (blockLen > 65535) {
555                 blockLen = 65535;
556             }
557
558             mSegments[mCursor] -= blockLen;
559
560             // Write deflater block header.
561
mOut.write(0);
562             writeShort(mOut, blockLen);
563             writeShort(mOut, 65535 - blockLen);
564
565             mBlockLen = blockLen;
566
567             return blockLen < needed ? blockLen : needed;
568         }
569     }
570 }
571
Popular Tags