001: /*
002: * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java,v 1.39 2004/07/03 14:27:03 olegk Exp $
003: * $Revision: 480424 $
004: * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
005: *
006: * ====================================================================
007: *
008: * Licensed to the Apache Software Foundation (ASF) under one or more
009: * contributor license agreements. See the NOTICE file distributed with
010: * this work for additional information regarding copyright ownership.
011: * The ASF licenses this file to You under the Apache License, Version 2.0
012: * (the "License"); you may not use this file except in compliance with
013: * the License. You may obtain a copy of the License at
014: *
015: * http://www.apache.org/licenses/LICENSE-2.0
016: *
017: * Unless required by applicable law or agreed to in writing, software
018: * distributed under the License is distributed on an "AS IS" BASIS,
019: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020: * See the License for the specific language governing permissions and
021: * limitations under the License.
022: * ====================================================================
023: *
024: * This software consists of voluntary contributions made by many
025: * individuals on behalf of the Apache Software Foundation. For more
026: * information on the Apache Software Foundation, please see
027: * <http://www.apache.org/>.
028: *
029: */
030:
031: package org.apache.commons.httpclient.methods;
032:
033: import java.io.IOException;
034: import java.io.InputStream;
035: import java.io.OutputStream;
036: import java.io.UnsupportedEncodingException;
037:
038: import org.apache.commons.httpclient.ChunkedOutputStream;
039: import org.apache.commons.httpclient.Header;
040: import org.apache.commons.httpclient.HttpConnection;
041: import org.apache.commons.httpclient.HttpException;
042: import org.apache.commons.httpclient.HttpState;
043: import org.apache.commons.httpclient.HttpVersion;
044: import org.apache.commons.httpclient.ProtocolException;
045: import org.apache.commons.logging.Log;
046: import org.apache.commons.logging.LogFactory;
047:
048: /**
049: * This abstract class serves as a foundation for all HTTP methods
050: * that can enclose an entity within requests
051: *
052: * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
053: * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
054: *
055: * @since 2.0beta1
056: * @version $Revision: 480424 $
057: */
058: public abstract class EntityEnclosingMethod extends
059: ExpectContinueMethod {
060:
061: // ----------------------------------------- Static variables/initializers
062:
063: /**
064: * The content length will be calculated automatically. This implies
065: * buffering of the content.
066: * @deprecated Use {@link InputStreamRequestEntity#CONTENT_LENGTH_AUTO}.
067: */
068: public static final long CONTENT_LENGTH_AUTO = InputStreamRequestEntity.CONTENT_LENGTH_AUTO;
069:
070: /**
071: * The request will use chunked transfer encoding. Content length is not
072: * calculated and the content is not buffered.<br>
073: * @deprecated Use {@link #setContentChunked(boolean)}.
074: */
075: public static final long CONTENT_LENGTH_CHUNKED = -1;
076:
077: /** LOG object for this class. */
078: private static final Log LOG = LogFactory
079: .getLog(EntityEnclosingMethod.class);
080:
081: /** The unbuffered request body, if any. */
082: private InputStream requestStream = null;
083:
084: /** The request body as string, if any. */
085: private String requestString = null;
086:
087: private RequestEntity requestEntity;
088:
089: /** Counts how often the request was sent to the server. */
090: private int repeatCount = 0;
091:
092: /** The content length of the <code>requestBodyStream</code> or one of
093: * <code>CONTENT_LENGTH_AUTO</code> and <code>CONTENT_LENGTH_CHUNKED</code>.
094: *
095: * @deprecated
096: */
097: private long requestContentLength = InputStreamRequestEntity.CONTENT_LENGTH_AUTO;
098:
099: private boolean chunked = false;
100:
101: // ----------------------------------------------------------- Constructors
102:
103: /**
104: * No-arg constructor.
105: *
106: * @since 2.0
107: */
108: public EntityEnclosingMethod() {
109: super ();
110: setFollowRedirects(false);
111: }
112:
113: /**
114: * Constructor specifying a URI.
115: *
116: * @param uri either an absolute or relative URI
117: *
118: * @since 2.0
119: */
120: public EntityEnclosingMethod(String uri) {
121: super (uri);
122: setFollowRedirects(false);
123: }
124:
125: /**
126: * Returns <tt>true</tt> if there is a request body to be sent.
127: *
128: * <P>This method must be overridden by sub-classes that implement
129: * alternative request content input methods
130: * </p>
131: *
132: * @return boolean
133: *
134: * @since 2.0beta1
135: */
136: protected boolean hasRequestContent() {
137: LOG.trace("enter EntityEnclosingMethod.hasRequestContent()");
138: return (this .requestEntity != null)
139: || (this .requestStream != null)
140: || (this .requestString != null);
141: }
142:
143: /**
144: * Clears the request body.
145: *
146: * <p>This method must be overridden by sub-classes that implement
147: * alternative request content input methods.</p>
148: *
149: * @since 2.0beta1
150: */
151: protected void clearRequestBody() {
152: LOG.trace("enter EntityEnclosingMethod.clearRequestBody()");
153: this .requestStream = null;
154: this .requestString = null;
155: this .requestEntity = null;
156: }
157:
158: /**
159: * Generates the request body.
160: *
161: * <p>This method must be overridden by sub-classes that implement
162: * alternative request content input methods.</p>
163: *
164: * @return request body as an array of bytes. If the request content
165: * has not been set, returns <tt>null</tt>.
166: *
167: * @since 2.0beta1
168: */
169: protected byte[] generateRequestBody() {
170: LOG.trace("enter EntityEnclosingMethod.renerateRequestBody()");
171: return null;
172: }
173:
174: protected RequestEntity generateRequestEntity() {
175:
176: byte[] requestBody = generateRequestBody();
177: if (requestBody != null) {
178: // use the request body, if it exists.
179: // this is just for backwards compatability
180: this .requestEntity = new ByteArrayRequestEntity(requestBody);
181: } else if (this .requestStream != null) {
182: this .requestEntity = new InputStreamRequestEntity(
183: requestStream, requestContentLength);
184: this .requestStream = null;
185: } else if (this .requestString != null) {
186: String charset = getRequestCharSet();
187: try {
188: this .requestEntity = new StringRequestEntity(
189: requestString, null, charset);
190: } catch (UnsupportedEncodingException e) {
191: if (LOG.isWarnEnabled()) {
192: LOG.warn(charset + " not supported");
193: }
194: try {
195: this .requestEntity = new StringRequestEntity(
196: requestString, null, null);
197: } catch (UnsupportedEncodingException ignore) {
198: }
199: }
200: }
201:
202: return this .requestEntity;
203: }
204:
205: /**
206: * Entity enclosing requests cannot be redirected without user intervention
207: * according to RFC 2616.
208: *
209: * @return <code>false</code>.
210: *
211: * @since 2.0
212: */
213: public boolean getFollowRedirects() {
214: return false;
215: }
216:
217: /**
218: * Entity enclosing requests cannot be redirected without user intervention
219: * according to RFC 2616.
220: *
221: * @param followRedirects must always be <code>false</code>
222: */
223: public void setFollowRedirects(boolean followRedirects) {
224: if (followRedirects == true) {
225: throw new IllegalArgumentException(
226: "Entity enclosing requests cannot be redirected without user intervention");
227: }
228: super .setFollowRedirects(false);
229: }
230:
231: /**
232: * Sets length information about the request body.
233: *
234: * <p>
235: * Note: If you specify a content length the request is unbuffered. This
236: * prevents redirection and automatic retry if a request fails the first
237: * time. This means that the HttpClient can not perform authorization
238: * automatically but will throw an Exception. You will have to set the
239: * necessary 'Authorization' or 'Proxy-Authorization' headers manually.
240: * </p>
241: *
242: * @param length size in bytes or any of CONTENT_LENGTH_AUTO,
243: * CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED
244: * is specified the content will not be buffered internally and the
245: * Content-Length header of the request will be used. In this case
246: * the user is responsible to supply the correct content length.
247: * If CONTENT_LENGTH_AUTO is specified the request will be buffered
248: * before it is sent over the network.
249: *
250: * @deprecated Use {@link #setContentChunked(boolean)} or
251: * {@link #setRequestEntity(RequestEntity)}
252: */
253: public void setRequestContentLength(int length) {
254: LOG
255: .trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
256: this .requestContentLength = length;
257: }
258:
259: /**
260: * Returns the request's charset. The charset is parsed from the request entity's
261: * content type, unless the content type header has been set manually.
262: *
263: * @see RequestEntity#getContentType()
264: *
265: * @since 3.0
266: */
267: public String getRequestCharSet() {
268: if (getRequestHeader("Content-Type") == null) {
269: // check the content type from request entity
270: // We can't call getRequestEntity() since it will probably call
271: // this method.
272: if (this .requestEntity != null) {
273: return getContentCharSet(new Header("Content-Type",
274: requestEntity.getContentType()));
275: } else {
276: return super .getRequestCharSet();
277: }
278: } else {
279: return super .getRequestCharSet();
280: }
281: }
282:
283: /**
284: * Sets length information about the request body.
285: *
286: * <p>
287: * Note: If you specify a content length the request is unbuffered. This
288: * prevents redirection and automatic retry if a request fails the first
289: * time. This means that the HttpClient can not perform authorization
290: * automatically but will throw an Exception. You will have to set the
291: * necessary 'Authorization' or 'Proxy-Authorization' headers manually.
292: * </p>
293: *
294: * @param length size in bytes or any of CONTENT_LENGTH_AUTO,
295: * CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED
296: * is specified the content will not be buffered internally and the
297: * Content-Length header of the request will be used. In this case
298: * the user is responsible to supply the correct content length.
299: * If CONTENT_LENGTH_AUTO is specified the request will be buffered
300: * before it is sent over the network.
301: *
302: * @deprecated Use {@link #setContentChunked(boolean)} or
303: * {@link #setRequestEntity(RequestEntity)}
304: */
305: public void setRequestContentLength(long length) {
306: LOG
307: .trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
308: this .requestContentLength = length;
309: }
310:
311: /**
312: * Sets whether or not the content should be chunked.
313: *
314: * @param chunked <code>true</code> if the content should be chunked
315: *
316: * @since 3.0
317: */
318: public void setContentChunked(boolean chunked) {
319: this .chunked = chunked;
320: }
321:
322: /**
323: * Returns the length of the request body.
324: *
325: * @return number of bytes in the request body
326: */
327: protected long getRequestContentLength() {
328: LOG
329: .trace("enter EntityEnclosingMethod.getRequestContentLength()");
330:
331: if (!hasRequestContent()) {
332: return 0;
333: }
334: if (this .chunked) {
335: return -1;
336: }
337: if (this .requestEntity == null) {
338: this .requestEntity = generateRequestEntity();
339: }
340: return (this .requestEntity == null) ? 0 : this .requestEntity
341: .getContentLength();
342: }
343:
344: /**
345: * Populates the request headers map to with additional
346: * {@link org.apache.commons.httpclient.Header headers} to be submitted to
347: * the given {@link HttpConnection}.
348: *
349: * <p>
350: * This implementation adds tt>Content-Length</tt> or <tt>Transfer-Encoding</tt>
351: * headers.
352: * </p>
353: *
354: * <p>
355: * Subclasses may want to override this method to to add additional
356: * headers, and may choose to invoke this implementation (via
357: * <tt>super</tt>) to add the "standard" headers.
358: * </p>
359: *
360: * @param state the {@link HttpState state} information associated with this method
361: * @param conn the {@link HttpConnection connection} used to execute
362: * this HTTP method
363: *
364: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
365: * can be recovered from.
366: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
367: * cannot be recovered from.
368: *
369: * @see #writeRequestHeaders
370: *
371: * @since 3.0
372: */
373: protected void addRequestHeaders(HttpState state,
374: HttpConnection conn) throws IOException, HttpException {
375: LOG
376: .trace("enter EntityEnclosingMethod.addRequestHeaders(HttpState, "
377: + "HttpConnection)");
378:
379: super .addRequestHeaders(state, conn);
380: addContentLengthRequestHeader(state, conn);
381:
382: // only use the content type of the request entity if it has not already been
383: // set manually
384: if (getRequestHeader("Content-Type") == null) {
385: RequestEntity requestEntity = getRequestEntity();
386: if (requestEntity != null
387: && requestEntity.getContentType() != null) {
388: setRequestHeader("Content-Type", requestEntity
389: .getContentType());
390: }
391: }
392: }
393:
394: /**
395: * Generates <tt>Content-Length</tt> or <tt>Transfer-Encoding: Chunked</tt>
396: * request header, as long as no <tt>Content-Length</tt> request header
397: * already exists.
398: *
399: * @param state current state of http requests
400: * @param conn the connection to use for I/O
401: *
402: * @throws IOException when errors occur reading or writing to/from the
403: * connection
404: * @throws HttpException when a recoverable error occurs
405: */
406: protected void addContentLengthRequestHeader(HttpState state,
407: HttpConnection conn) throws IOException, HttpException {
408: LOG
409: .trace("enter EntityEnclosingMethod.addContentLengthRequestHeader("
410: + "HttpState, HttpConnection)");
411:
412: if ((getRequestHeader("content-length") == null)
413: && (getRequestHeader("Transfer-Encoding") == null)) {
414: long len = getRequestContentLength();
415: if (len < 0) {
416: if (getEffectiveVersion().greaterEquals(
417: HttpVersion.HTTP_1_1)) {
418: addRequestHeader("Transfer-Encoding", "chunked");
419: } else {
420: throw new ProtocolException(getEffectiveVersion()
421: + " does not support chunk encoding");
422: }
423: } else {
424: addRequestHeader("Content-Length", String.valueOf(len));
425: }
426: }
427: }
428:
429: /**
430: * Sets the request body to be the specified inputstream.
431: *
432: * @param body Request body content as {@link java.io.InputStream}
433: *
434: * @deprecated use {@link #setRequestEntity(RequestEntity)}
435: */
436: public void setRequestBody(InputStream body) {
437: LOG
438: .trace("enter EntityEnclosingMethod.setRequestBody(InputStream)");
439: clearRequestBody();
440: this .requestStream = body;
441: }
442:
443: /**
444: * Sets the request body to be the specified string.
445: * The string will be submitted, using the encoding
446: * specified in the Content-Type request header.<br>
447: * Example: <code>setRequestHeader("Content-type", "text/xml; charset=UTF-8");</code><br>
448: * Would use the UTF-8 encoding.
449: * If no charset is specified, the
450: * {@link org.apache.commons.httpclient.HttpConstants#DEFAULT_CONTENT_CHARSET default}
451: * content encoding is used (ISO-8859-1).
452: *
453: * @param body Request body content as a string
454: *
455: * @deprecated use {@link #setRequestEntity(RequestEntity)}
456: */
457: public void setRequestBody(String body) {
458: LOG.trace("enter EntityEnclosingMethod.setRequestBody(String)");
459: clearRequestBody();
460: this .requestString = body;
461: }
462:
463: /**
464: * Writes the request body to the given {@link HttpConnection connection}.
465: *
466: * @param state the {@link HttpState state} information associated with this method
467: * @param conn the {@link HttpConnection connection} used to execute
468: * this HTTP method
469: *
470: * @return <tt>true</tt>
471: *
472: * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
473: * can be recovered from.
474: * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
475: * cannot be recovered from.
476: */
477: protected boolean writeRequestBody(HttpState state,
478: HttpConnection conn) throws IOException, HttpException {
479: LOG
480: .trace("enter EntityEnclosingMethod.writeRequestBody(HttpState, HttpConnection)");
481:
482: if (!hasRequestContent()) {
483: LOG.debug("Request body has not been specified");
484: return true;
485: }
486: if (this .requestEntity == null) {
487: this .requestEntity = generateRequestEntity();
488: }
489: if (requestEntity == null) {
490: LOG.debug("Request body is empty");
491: return true;
492: }
493:
494: long contentLength = getRequestContentLength();
495:
496: if ((this .repeatCount > 0) && !requestEntity.isRepeatable()) {
497: throw new ProtocolException(
498: "Unbuffered entity enclosing request can not be repeated.");
499: }
500:
501: this .repeatCount++;
502:
503: OutputStream outstream = conn.getRequestOutputStream();
504:
505: if (contentLength < 0) {
506: outstream = new ChunkedOutputStream(outstream);
507: }
508:
509: requestEntity.writeRequest(outstream);
510:
511: // This is hardly the most elegant solution to closing chunked stream
512: if (outstream instanceof ChunkedOutputStream) {
513: ((ChunkedOutputStream) outstream).finish();
514: }
515:
516: outstream.flush();
517:
518: LOG.debug("Request body sent");
519: return true;
520: }
521:
522: /**
523: * Recycles the HTTP method so that it can be used again.
524: * Note that all of the instance variables will be reset
525: * once this method has been called. This method will also
526: * release the connection being used by this HTTP method.
527: *
528: * @see #releaseConnection()
529: *
530: * @deprecated no longer supported and will be removed in the future
531: * version of HttpClient
532: */
533: public void recycle() {
534: LOG.trace("enter EntityEnclosingMethod.recycle()");
535: clearRequestBody();
536: this .requestContentLength = InputStreamRequestEntity.CONTENT_LENGTH_AUTO;
537: this .repeatCount = 0;
538: this .chunked = false;
539: super .recycle();
540: }
541:
542: /**
543: * @return Returns the requestEntity.
544: *
545: * @since 3.0
546: */
547: public RequestEntity getRequestEntity() {
548: return generateRequestEntity();
549: }
550:
551: /**
552: * @param requestEntity The requestEntity to set.
553: *
554: * @since 3.0
555: */
556: public void setRequestEntity(RequestEntity requestEntity) {
557: clearRequestBody();
558: this.requestEntity = requestEntity;
559: }
560:
561: }
|