Coverage Report - groovyx.net.http.EncoderRegistry
 
Classes in this File Line Coverage Branch Coverage Complexity
EncoderRegistry
26%
20/77
20%
6/30
0
 
 1  
 /*
 2  
  * Copyright 2003-2008 the original author or authors.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  *
 16  
  * You are receiving this code free of charge, which represents many hours of
 17  
  * effort from other individuals and corporations.  As a responsible member 
 18  
  * of the community, you are asked (but not required) to donate any 
 19  
  * enhancements or improvements back to the community under a similar open 
 20  
  * source license.  Thank you. -TMN
 21  
  */
 22  
 package groovyx.net.http;
 23  
 
 24  
 import groovy.lang.Closure;
 25  
 import groovy.lang.Writable;
 26  
 import groovy.xml.StreamingMarkupBuilder;
 27  
 import groovyx.net.http.HTTPBuilder.SendDelegate;
 28  
 
 29  
 import java.io.BufferedReader;
 30  
 import java.io.ByteArrayInputStream;
 31  
 import java.io.ByteArrayOutputStream;
 32  
 import java.io.IOException;
 33  
 import java.io.InputStream;
 34  
 import java.io.PrintWriter;
 35  
 import java.io.Reader;
 36  
 import java.io.StringWriter;
 37  
 import java.io.UnsupportedEncodingException;
 38  
 import java.nio.charset.Charset;
 39  
 import java.util.ArrayList;
 40  
 import java.util.HashMap;
 41  
 import java.util.List;
 42  
 import java.util.Map;
 43  
 
 44  
 import net.sf.json.JSON;
 45  
 import net.sf.json.JSONObject;
 46  
 import net.sf.json.groovy.JsonGroovyBuilder;
 47  
 
 48  
 import org.apache.http.HttpEntity;
 49  
 import org.apache.http.HttpEntityEnclosingRequest;
 50  
 import org.apache.http.NameValuePair;
 51  
 import org.apache.http.client.entity.UrlEncodedFormEntity;
 52  
 import org.apache.http.entity.InputStreamEntity;
 53  
 import org.apache.http.entity.StringEntity;
 54  
 import org.apache.http.message.BasicNameValuePair;
 55  
 import org.codehaus.groovy.runtime.MethodClosure;
 56  
 
 57  
 
 58  
 /**
 59  
  * <p>This factory (or registry) handles request body "encoding." This is not
 60  
  * to be confused with HTTP content-encoding header.  When a 
 61  
  * {@link SendDelegate#setBody(Object) body} is set from the builder, it is 
 62  
  * processed based on the request content-type.  For instance, if the body
 63  
  * is set to a map and the request content-type is JSON, the map will be 
 64  
  * transformed to a JSON Object.  </p>
 65  
  * 
 66  
  * <p>Most default encoders can handle a closure as a request body.  In this 
 67  
  * case, the closure is executed and a suitable 'builder' passed to the 
 68  
  * closure that is  used for constructing the content.  In the case of 
 69  
  * binary encoding this would be an OutputStream; for TEXT encoding it would
 70  
  * be a PrintWriter, and for XML it would be an already-bound 
 71  
  * {@link StreamingMarkupBuilder}. </p>
 72  
  */
 73  36
 public class EncoderRegistry {
 74  
         
 75  36
         Charset charset = Charset.defaultCharset(); // 1.5
 76  
         
 77  
         /**
 78  
          * Set the charset used in the content-type header of all requests that send
 79  
          * textual data.  This must be a chaset supported by the Java platform
 80  
          * @see Charset#forName(String)
 81  
          * @param charset 
 82  
          */
 83  
         public void setCharset( String charset ) { 
 84  0
                 this.charset = Charset.forName(charset);
 85  0
         }
 86  
         
 87  
         /**
 88  
          * Default request encoder for a binary stream.  Acceptable argument 
 89  
          * types are:
 90  
          * <ul>
 91  
          *   <li>InputStream</li>
 92  
          *   <li>ByteArrayOutputStream</li>
 93  
          *   <li>Closure</li>
 94  
          * </ul>
 95  
          * If a closure is given, it is executed with an OutputStream passed
 96  
          * as the single closure argument.  Any data sent to the stream from the 
 97  
          * body of the closure is used as the request content body.
 98  
          * @param data
 99  
          * @return an {@link HttpEntity} encapsulating this request data
 100  
          * @throws UnsupportedEncodingException
 101  
          */
 102  
         public InputStreamEntity encodeStream( Object data ) throws UnsupportedEncodingException {
 103  0
                 if ( data instanceof InputStream ) {
 104  0
                         return new InputStreamEntity( (InputStream)data, -1 );
 105  
                 }
 106  0
                 else if ( data instanceof ByteArrayInputStream ) {
 107  0
                         ByteArrayInputStream in = ((ByteArrayInputStream)data);
 108  0
                         return new InputStreamEntity( in, in.available() );
 109  
                 }
 110  0
                 else if ( data instanceof ByteArrayOutputStream ) {
 111  0
                         ByteArrayOutputStream out = ((ByteArrayOutputStream)data); 
 112  0
                         return new InputStreamEntity( new ByteArrayInputStream(
 113  
                                         out.toByteArray()), out.size() );
 114  
                 }
 115  0
                 else if ( data instanceof Closure ) {
 116  0
                         ByteArrayOutputStream out = new ByteArrayOutputStream();
 117  0
                         ((Closure)data).call( out ); // data is written to out
 118  0
                         return new InputStreamEntity( new ByteArrayInputStream(out.toByteArray()), out.size() );
 119  
                 }
 120  0
                 throw new IllegalArgumentException( "Don't know how to encode " + data + " as a byte stream" );
 121  
         }
 122  
         
 123  
         /**
 124  
          * Default handler used for a plain text content-type.  Acceptable argument
 125  
          * types are:
 126  
          * <ul>
 127  
          *   <li>Closure</li>
 128  
          *   <li>Writable</li>
 129  
          *   <li>Reader</li>
 130  
          * </ul>
 131  
          * For Closure argument, a {@link PrintWriter} is passed as the single 
 132  
          * argument to the closure.  Any data sent to the writer from the 
 133  
          * closure will be sent to the request content body.
 134  
          * @param data
 135  
          * @return an {@link HttpEntity} encapsulating this request data
 136  
          * @throws IOException
 137  
          */
 138  
         public HttpEntity encodeText( Object data ) throws IOException {
 139  0
                 if ( data instanceof Closure ) {
 140  0
                         StringWriter out = new StringWriter();
 141  0
                         PrintWriter writer = new PrintWriter( out );
 142  0
                         ((Closure)data).call( writer );
 143  0
                         writer.close();
 144  0
                         out.flush();
 145  0
                         data = out;
 146  0
                 }
 147  0
                 else if ( data instanceof Writable ) {
 148  0
                         StringWriter out = new StringWriter();
 149  0
                         ((Writable)data).writeTo(out);
 150  0
                         out.flush();
 151  0
                         data = out;
 152  0
                 }
 153  0
                 else if ( data instanceof Reader && ! (data instanceof BufferedReader) )
 154  0
                         data = new BufferedReader( (Reader)data );
 155  0
                 if ( data instanceof BufferedReader ) {
 156  0
                         StringBuilder sb = new StringBuilder();
 157  0
                         BufferedReader reader = (BufferedReader)data;
 158  0
                         String line = null;
 159  0
                         while( (line = reader.readLine()) != null )
 160  0
                                 sb.append( line );
 161  
                         
 162  0
                         data = sb;
 163  
                 }
 164  
                 // if data is a String, we are already covered.
 165  0
                 return createEntity( ContentType.TEXT, data.toString() );
 166  
         }
 167  
         
 168  
         /**
 169  
          * Set the request body as a url-encoded list of parameters.  This is 
 170  
          * typically used to simulate a HTTP form POST. 
 171  
          * @param params
 172  
          * @return an {@link HttpEntity} encapsulating this request data
 173  
          * @throws UnsupportedEncodingException
 174  
          */
 175  
         public UrlEncodedFormEntity encodeForm( Map<String,Object> params ) 
 176  
                         throws UnsupportedEncodingException {
 177  4
                 List<NameValuePair> paramList = new ArrayList<NameValuePair>();
 178  
 
 179  4
                 for ( Map.Entry<String, Object> entry : params.entrySet() ) 
 180  4
                         paramList.add( new BasicNameValuePair(entry.getKey(), 
 181  
                                         entry.getValue().toString()) );
 182  
                         
 183  4
                 return new UrlEncodedFormEntity( paramList, charset.name() );
 184  
         }
 185  
         
 186  
         /**
 187  
          * Executes the given closure and passes a bound {@link StreamingMarkupBuilder}.
 188  
          * @param xmlBuilder
 189  
          * @return an {@link HttpEntity} encapsulating this request data
 190  
          * @throws UnsupportedEncodingException
 191  
          */
 192  
         public HttpEntity encodeXML( Closure xmlBuilder ) throws UnsupportedEncodingException {
 193  0
                 StreamingMarkupBuilder smb = new StreamingMarkupBuilder();
 194  0
                 String markup = smb.bind( xmlBuilder ).toString();
 195  0
                 return createEntity( ContentType.XML, markup);
 196  
         }
 197  
         
 198  
         /**
 199  
          * Accepts a Map or a JavaBean object which is converted to JSON.  If
 200  
          * a Closure is passed, it will be executed with a 
 201  
          * {@link JsonGroovyBuilder} as the closure's delegate.  The closure 
 202  
          * must return the result of the outermost builder method call. 
 203  
          * @param model data to be converted to JSON, as specified above.
 204  
          * @return an {@link HttpEntity} encapsulating this request data
 205  
          * @throws UnsupportedEncodingException
 206  
          */
 207  
         @SuppressWarnings("unchecked")
 208  
         public HttpEntity encodeJSON( Object model ) throws UnsupportedEncodingException {
 209  
                 JSON json;
 210  
                 
 211  0
                 if ( model instanceof Map ) {
 212  0
                         json = new JSONObject();
 213  0
                         ((JSONObject)json).putAll( (Map)model );
 214  
                 }
 215  0
                 else if ( model instanceof Closure ) {
 216  0
                         Closure closure = (Closure)model;
 217  0
                         closure.setDelegate( new JsonGroovyBuilder() );
 218  0
                         json = (JSONObject)closure.call();
 219  0
                 }
 220  0
                 else json = JSONObject.fromObject( model ); // Assume object is a JavaBean
 221  
                 
 222  0
                 return this.createEntity( ContentType.JSON, json.toString() );
 223  
         }
 224  
         
 225  
         /**
 226  
          * Helper method used by encoder methods to creates an {@link HttpEntity} 
 227  
          * instance that encapsulates the request data.  This may be used by any 
 228  
          * non-streaming encoder that needs to send textual data.  It also sets the 
 229  
          * {@link #setCharset(String) charset} portion of the content-type header. 
 230  
          * 
 231  
          * @param ct content-type of the data
 232  
          * @param data textual request data to be encoded 
 233  
          * @return an instance to be used for the 
 234  
          *  {@link HttpEntityEnclosingRequest#setEntity(HttpEntity) request content} 
 235  
          * @throws UnsupportedEncodingException
 236  
          */
 237  
         protected StringEntity createEntity( ContentType ct, String data ) 
 238  
                         throws UnsupportedEncodingException {
 239  0
                 StringEntity entity = new StringEntity( data, charset.toString() );
 240  0
                 entity.setContentType( ct.toString() );
 241  0
                 return entity;
 242  
         }
 243  
         
 244  36
         protected Map<String,Closure> registeredEncoders = buildDefaultEncoderMap();
 245  
 
 246  
         /** 
 247  
          * Used to set an additional encoder for the given content type.  The 
 248  
          * Closure must return an {@link HttpEntity}.  It will also usually 
 249  
          * accept a single argument, which will be the value given in  
 250  
          * {@link SendDelegate#setBody(Object)}.
 251  
          * @param contentType
 252  
          * @param closure
 253  
          */
 254  
         public void register( String contentType, Closure closure ) {
 255  0
                 registeredEncoders.put( contentType, closure );
 256  0
         }
 257  
         
 258  
         /* Get the encoder for the given content-type.  Not usually called 
 259  
          * by the end-user.  The HTTPBuilder will get the appropriate encoder 
 260  
          * automatically in order to encode the request body data.
 261  
          * @param contentType
 262  
          * @return the encoder closure, or <code>null</code> if no encoder is
 263  
          * registered.
 264  
          */
 265  4
         Closure get( String contentType ) { return registeredEncoders.get(contentType); }
 266  
         
 267  
         /**
 268  
          * Returns a map of default encoders.  Override this method to change 
 269  
          * what encoders are registered by default.  You can of course call
 270  
          * <code>super.buildDefaultEncoderMap()</code> and then add or remove 
 271  
          * from that result as well.
 272  
          */
 273  
         protected Map<String,Closure> buildDefaultEncoderMap() {
 274  36
                 Map<String,Closure> encoders = new HashMap<String,Closure>();
 275  
                 
 276  36
                 encoders.put( ContentType.BINARY.toString(), new MethodClosure(this,"encodeStream") );
 277  36
                 encoders.put( ContentType.TEXT.toString(), new MethodClosure( this, "encodeText" ) );
 278  36
                 encoders.put( ContentType.URLENC.toString(), new MethodClosure( this, "encodeForm" ) );
 279  
                 
 280  36
                 Closure encClosure = new MethodClosure(this,"encodeXML");
 281  144
                 for ( String ct : ContentType.XML.getContentTypeStrings() )
 282  108
                         encoders.put( ct, encClosure );
 283  36
                 encoders.put( ContentType.HTML.toString(), encClosure );
 284  
                 
 285  36
                 encClosure = new MethodClosure(this,"encodeJSON");
 286  144
                 for ( String ct : ContentType.JSON.getContentTypeStrings() )
 287  108
                         encoders.put( ct, encClosure );
 288  
                 
 289  36
                 return encoders;
 290  
         }
 291  
 }