KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > httpclient > methods > EntityEnclosingMethod


1 /*
2  * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java,v 1.18.2.5 2004/06/13 20:24:49 olegk Exp $
3  * $Revision: 1.18.2.5 $
4  * $Date: 2004/06/13 20:24:49 $
5  *
6  * ====================================================================
7  *
8  * Copyright 2003-2004 The Apache Software Foundation
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  * ====================================================================
22  *
23  * This software consists of voluntary contributions made by many
24  * individuals on behalf of the Apache Software Foundation. For more
25  * information on the Apache Software Foundation, please see
26  * <http://www.apache.org/>.
27  *
28  * [Additional notices, if required by prior licensing conditions]
29  *
30  */

31
32 package org.apache.commons.httpclient.methods;
33
34 import java.io.ByteArrayInputStream JavaDoc;
35 import java.io.ByteArrayOutputStream JavaDoc;
36 import java.io.IOException JavaDoc;
37 import java.io.InputStream JavaDoc;
38 import java.io.OutputStream JavaDoc;
39
40 import org.apache.commons.httpclient.ChunkedOutputStream;
41 import org.apache.commons.httpclient.ContentLengthInputStream;
42 import org.apache.commons.httpclient.HttpConnection;
43 import org.apache.commons.httpclient.HttpConstants;
44 import org.apache.commons.httpclient.HttpException;
45 import org.apache.commons.httpclient.HttpState;
46 import org.apache.commons.logging.Log;
47 import org.apache.commons.logging.LogFactory;
48
49 /**
50  * This abstract class serves as a foundation for all HTTP methods
51  * that can enclose an entity within requests
52  *
53  * @author <a HREF="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
54  * @author <a HREF="mailto:jsdever@apache.org">Jeff Dever</a>
55  *
56  * @since 2.0beta1
57  * @version $Revision: 1.18.2.5 $
58  */

59 public abstract class EntityEnclosingMethod extends ExpectContinueMethod {
60
61     // ----------------------------------------- Static variables/initializers
62

63     /**
64      * The content length will be calculated automatically. This implies
65      * buffering of the content.
66      */

67     public static final int CONTENT_LENGTH_AUTO = -2;
68
69     /**
70      * The request will use chunked transfer encoding. Content length is not
71      * calculated and the content is not buffered.<br>
72      */

73     public static final int CONTENT_LENGTH_CHUNKED = -1;
74
75     /** LOG object for this class. */
76     private static final Log LOG = LogFactory.getLog(EntityEnclosingMethod.class);
77
78     /** The buffered request body, if any. */
79     private byte[] buffer = null;
80
81     /** The unbuffered request body, if any. */
82     private InputStream JavaDoc requestStream = null;
83
84     /** The request body as string, if any. */
85     private String JavaDoc requestString = null;
86
87     /** for optimization purpose, the generated request body may be
88      * cached when the method is being executed.
89      */

90     private byte[] contentCache = null;
91     
92     /** Counts how often the request was sent to the server. */
93     private int repeatCount = 0;
94
95     /** The content length of the <code>requestBodyStream</code> or one of
96      * <code>CONTENT_LENGTH_AUTO</code> and <code>CONTENT_LENGTH_CHUNKED</code>.
97      */

98     private int requestContentLength = CONTENT_LENGTH_AUTO;
99
100     // ----------------------------------------------------------- Constructors
101

102     /**
103      * No-arg constructor.
104      *
105      * @since 2.0
106      */

107     public EntityEnclosingMethod() {
108         super();
109         setFollowRedirects(false);
110     }
111
112     /**
113      * Constructor specifying a URI.
114      *
115      * @param uri either an absolute or relative URI
116      *
117      * @since 2.0
118      */

119     public EntityEnclosingMethod(String JavaDoc uri) {
120         super(uri);
121         setFollowRedirects(false);
122     }
123
124     /**
125      * Constructor specifying a URI and a tempDir.
126      *
127      * @param uri either an absolute or relative URI
128      * @param tempDir directory to store temp files in
129      *
130      * @deprecated the client is responsible for disk I/O
131      * @since 2.0
132      */

133     public EntityEnclosingMethod(String JavaDoc uri, String JavaDoc tempDir) {
134         super(uri, tempDir);
135         setFollowRedirects(false);
136     }
137
138     /**
139      * Constructor specifying a URI, tempDir and tempFile.
140      *
141      * @param uri either an absolute or relative URI
142      * @param tempDir directory to store temp files in
143      * @param tempFile file to store temporary data in
144      *
145      * @deprecated the client is responsible for disk I/O
146      * @since 2.0
147      */

148     public EntityEnclosingMethod(String JavaDoc uri, String JavaDoc tempDir, String JavaDoc tempFile) {
149         super(uri, tempDir, tempFile);
150         setFollowRedirects(false);
151     }
152
153     /**
154      * Returns <tt>true</tt> if there is a request body to be sent.
155      *
156      * <P>This method must be overridden by sub-classes that implement
157      * alternative request content input methods
158      * </p>
159      *
160      * @return boolean
161      *
162      * @since 2.0beta1
163      */

164     protected boolean hasRequestContent() {
165         LOG.trace("enter EntityEnclosingMethod.hasRequestContent()");
166         return (this.buffer != null)
167             || (this.requestStream != null)
168             || (this.requestString != null);
169     }
170
171     /**
172      * Clears request body.
173      *
174      * <p>This method must be overridden by sub-classes that implement
175      * alternative request content input methods.</p>
176      *
177      * @since 2.0beta1
178      */

179     protected void clearRequestBody() {
180         LOG.trace("enter EntityEnclosingMethod.clearRequestBody()");
181         this.requestStream = null;
182         this.requestString = null;
183         this.buffer = null;
184         this.contentCache = null;
185     }
186
187     /**
188      * Generates request body.
189      *
190      * <p>This method must be overridden by sub-classes that implement
191      * alternative request content input methods.</p>
192      *
193      * @return request body as an array of bytes. If the request content
194      * has not been set, returns <tt>null</tt>.
195      *
196      * @since 2.0beta1
197      */

198     protected byte[] generateRequestBody() {
199         LOG.trace("enter EntityEnclosingMethod.renerateRequestBody()");
200         if (this.requestStream != null) {
201             bufferContent();
202         }
203          
204         if (this.buffer != null) {
205             return this.buffer;
206         } else if (this.requestString != null) {
207             return HttpConstants.getContentBytes(this.requestString, getRequestCharSet());
208         } else {
209             return null;
210         }
211     }
212
213     /**
214      * Entity enclosing requests cannot be redirected without user intervention
215      * according to RFC 2616.
216      *
217      * @return <code>false</code>.
218      *
219      * @since 2.0
220      */

221     public boolean getFollowRedirects() {
222         return false;
223     }
224
225
226     /**
227      * Entity enclosing requests cannot be redirected without user intervention
228      * according to RFC 2616.
229      *
230      * @param followRedirects must always be <code>false</code>
231      */

232     public void setFollowRedirects(boolean followRedirects) {
233         if (followRedirects == true) {
234             // TODO: EntityEnclosingMethod should inherit from HttpMethodBase rather than GetMethod
235
// Enable exception once the inheritence is fixed
236
//throw new IllegalArgumentException(
237
// "Entity enclosing requests cannot be redirected without user intervention");
238
}
239         super.setFollowRedirects(false);
240     }
241
242     /**
243      * Sets length information about the request body.
244      *
245      * <p>
246      * Note: If you specify a content length the request is unbuffered. This
247      * prevents redirection and automatic retry if a request fails the first
248      * time. This means that the HttpClient can not perform authorization
249      * automatically but will throw an Exception. You will have to set the
250      * necessary 'Authorization' or 'Proxy-Authorization' headers manually.
251      * </p>
252      *
253      * @param length size in bytes or any of CONTENT_LENGTH_AUTO,
254      * CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED
255      * is specified the content will not be buffered internally and the
256      * Content-Length header of the request will be used. In this case
257      * the user is responsible to supply the correct content length.
258      * If CONTENT_LENGTH_AUTO is specified the request will be buffered
259      * before it is sent over the network.
260      *
261      */

262     public void setRequestContentLength(int length) {
263         LOG.trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
264         this.requestContentLength = length;
265     }
266
267     /**
268      * Overrides method of {@link org.apache.commons.httpclient.HttpMethodBase}
269      * to return the length of the request body.
270      *
271      * @return number of bytes in the request body
272      */

273     protected int getRequestContentLength() {
274         LOG.trace("enter EntityEnclosingMethod.getRequestContentLength()");
275
276         if (!hasRequestContent()) {
277             return 0;
278         }
279         if (this.requestContentLength != CONTENT_LENGTH_AUTO) {
280             return this.requestContentLength;
281         }
282         if (this.contentCache == null) {
283             this.contentCache = generateRequestBody();
284         }
285         return (this.contentCache == null) ? 0 : this.contentCache.length;
286     }
287
288     /**
289      * Generates <tt>Content-Length</tt> or <tt>Transfer-Encoding: Chunked</tt>
290      * request header, as long as no <tt>Content-Length</tt> request header
291      * already exists.
292      *
293      * @param state the {@link HttpState state} information associated with this method
294      * @param conn the {@link HttpConnection connection} used to execute
295      * this HTTP method
296      *
297      * @throws IOException if an I/O (transport) error occurs
298      * @throws HttpException if a protocol exception occurs.
299      * @throws HttpRecoverableException if a recoverable transport error occurs.
300      * Usually this kind of exceptions can be recovered from by
301      * retrying the HTTP method
302      */

303     protected void addContentLengthRequestHeader(HttpState state,
304                                                  HttpConnection conn)
305     throws IOException JavaDoc, HttpException {
306         LOG.trace("enter HttpMethodBase.addContentLengthRequestHeader("
307                   + "HttpState, HttpConnection)");
308
309         if ((getRequestHeader("content-length") == null)
310             && (getRequestHeader("Transfer-Encoding") == null)) {
311             int len = getRequestContentLength();
312             if (len >= 0) {
313                 addRequestHeader("Content-Length", String.valueOf(len));
314             } else if ((len == CONTENT_LENGTH_CHUNKED) && (isHttp11())) {
315                 addRequestHeader("Transfer-Encoding", "chunked");
316             }
317         }
318     }
319
320     /**
321      * Sets the request body to be the specified inputstream.
322      *
323      * @param body Request body content as {@link java.io.InputStream}
324      */

325     public void setRequestBody(InputStream JavaDoc body) {
326         LOG.trace("enter EntityEnclosingMethod.setRequestBody(InputStream)");
327         clearRequestBody();
328         this.requestStream = body;
329     }
330
331     /**
332      * Returns the request body as a {@link java.io.InputStream}.
333      * Calling this method will cause the content to be buffered.
334      *
335      * @return The request body as a {@link java.io.InputStream} if it has been set.
336      */

337     public InputStream JavaDoc getRequestBody() {
338         LOG.trace("enter EntityEnclosingMethod.getRequestBody()");
339         byte [] content = generateRequestBody();
340         if (content != null) {
341             return new ByteArrayInputStream JavaDoc(content);
342         } else {
343             return new ByteArrayInputStream JavaDoc(new byte[] {});
344         }
345     }
346
347     /**
348      * Sets the request body to be the specified string.
349      * The string will be submitted, using the encoding
350      * specified in the Content-Type request header.<br>
351      * Example: <code>setRequestHeader("Content-type", "text/xml; charset=UTF-8");</code><br>
352      * Would use the UTF-8 encoding.
353      * If no charset is specified, the
354      * {@link org.apache.commons.httpclient.HttpConstants#DEFAULT_CONTENT_CHARSET default}
355      * content encoding is used (ISO-8859-1).
356      *
357      * @param body Request body content as a string
358      */

359     public void setRequestBody(String JavaDoc body) {
360         LOG.trace("enter EntityEnclosingMethod.setRequestBody(String)");
361         clearRequestBody();
362         this.requestString = body;
363     }
364
365     /**
366      * Returns the request body as a {@link java.lang.String}.
367      * Calling this method will cause the content to be buffered.
368      *
369      * @return the request body as a {@link java.lang.String}
370      *
371      * @throws IOException when i/o error occurs while reading the request
372      */

373     public String JavaDoc getRequestBodyAsString() throws IOException JavaDoc {
374         LOG.trace("enter EntityEnclosingMethod.getRequestBodyAsString()");
375         byte [] content = generateRequestBody();
376         if (content != null) {
377             return HttpConstants.getContentString(content, getRequestCharSet());
378         } else {
379             return null;
380         }
381     }
382
383
384     /**
385      * Writes the request body to the given {@link HttpConnection connection}.
386      *
387      * @param state the {@link HttpState state} information associated with this method
388      * @param conn the {@link HttpConnection connection} used to execute
389      * this HTTP method
390      *
391      * @return <tt>true</tt>
392      *
393      * @throws IOException if an I/O (transport) error occurs
394      * @throws HttpException if a protocol exception occurs.
395      * @throws HttpRecoverableException if a recoverable transport error occurs.
396      * Usually this kind of exceptions can be recovered from by
397      * retrying the HTTP method
398      */

399     protected boolean writeRequestBody(HttpState state, HttpConnection conn)
400     throws IOException JavaDoc, HttpException {
401         LOG.trace(
402             "enter EntityEnclosingMethod.writeRequestBody(HttpState, HttpConnection)");
403         
404         if (!hasRequestContent()) {
405             LOG.debug("Request body has not been specified");
406             return true;
407         }
408
409         int contentLength = getRequestContentLength();
410
411         if ((contentLength == CONTENT_LENGTH_CHUNKED) && !isHttp11()) {
412             throw new HttpException(
413                 "Chunked transfer encoding not allowed for HTTP/1.0");
414         }
415         
416         InputStream JavaDoc instream = null;
417         if (this.requestStream != null) {
418             LOG.debug("Using unbuffered request body");
419             instream = this.requestStream;
420         } else {
421             if (this.contentCache == null) {
422                 this.contentCache = generateRequestBody();
423             }
424             if (this.contentCache != null) {
425                 LOG.debug("Using buffered request body");
426                 instream = new ByteArrayInputStream JavaDoc(this.contentCache);
427             }
428         }
429
430         if (instream == null) {
431             LOG.debug("Request body is empty");
432             return true;
433         }
434
435         if ((this.repeatCount > 0) && (this.contentCache == null)) {
436             throw new HttpException(
437                 "Unbuffered entity enclosing request can not be repeated.");
438         }
439
440         this.repeatCount++;
441
442         OutputStream JavaDoc outstream = conn.getRequestOutputStream();
443
444         if (contentLength == CONTENT_LENGTH_CHUNKED) {
445             outstream = new ChunkedOutputStream(outstream);
446         }
447         if (contentLength >= 0) {
448             // don't need a watcher here - we're reading from something local,
449
// not server-side.
450
instream = new ContentLengthInputStream(instream, contentLength);
451         }
452
453         byte[] tmp = new byte[4096];
454         int total = 0;
455         int i = 0;
456         while ((i = instream.read(tmp)) >= 0) {
457             outstream.write(tmp, 0, i);
458             total += i;
459         }
460         // This is hardly the most elegant solution to closing chunked stream
461
if (outstream instanceof ChunkedOutputStream) {
462             ((ChunkedOutputStream) outstream).writeClosingChunk();
463         }
464         if ((contentLength > 0) && (total < contentLength)) {
465             throw new IOException JavaDoc("Unexpected end of input stream after "
466                 + total + " bytes (expected " + contentLength + " bytes)");
467         }
468         LOG.debug("Request body sent");
469         return true;
470     }
471
472     /**
473      * Recycles the HTTP method so that it can be used again.
474      * Note that all of the instance variables will be reset
475      * once this method has been called. This method will also
476      * release the connection being used by this HTTP method.
477      *
478      * @see #releaseConnection()
479      *
480      * @deprecated no longer supported and will be removed in the future
481      * version of HttpClient
482      */

483     public void recycle() {
484         LOG.trace("enter EntityEnclosingMethod.recycle()");
485         clearRequestBody();
486         this.requestContentLength = CONTENT_LENGTH_AUTO;
487         this.repeatCount = 0;
488         super.recycle();
489     }
490
491     /**
492      * Buffers request body input stream.
493      */

494     private void bufferContent() {
495         LOG.trace("enter EntityEnclosingMethod.bufferContent()");
496
497         if (this.buffer != null) {
498             // Already been buffered
499
return;
500         }
501         if (this.requestStream != null) {
502             try {
503                 ByteArrayOutputStream JavaDoc tmp = new ByteArrayOutputStream JavaDoc();
504                 byte[] data = new byte[4096];
505                 int l = 0;
506                 while ((l = this.requestStream.read(data)) >= 0) {
507                     tmp.write(data, 0, l);
508                 }
509                 this.buffer = tmp.toByteArray();
510                 this.requestStream = null;
511             } catch (IOException JavaDoc e) {
512                 LOG.error(e.getMessage(), e);
513                 this.buffer = null;
514                 this.requestStream = null;
515             }
516         }
517     }
518 }
519
Popular Tags