Coverage Report - groovyx.net.http.EncoderRegistry
 
Classes in this File Line Coverage Branch Coverage Complexity
EncoderRegistry
19%
15/77
13%
4/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.NameValuePair;
 50  
 import org.apache.http.client.entity.UrlEncodedFormEntity;
 51  
 import org.apache.http.entity.ByteArrayEntity;
 52  
 import org.apache.http.entity.InputStreamEntity;
 53  
 import org.apache.http.message.BasicNameValuePair;
 54  
 import org.codehaus.groovy.runtime.MethodClosure;
 55  
 
 56  
 
 57  
 /**
 58  
  * This factory (or registry) handles request body "encoding." This is not
 59  
  * to be confused with HTTP content-encoding header.  When a 
 60  
  * {@link SendDelegate#setBody(Object) body} is set from the builder, it is 
 61  
  * processed based on the request content-type.  For instance, if the body
 62  
  * is set to a map and the request cotnent-type is JSON, the map will be 
 63  
  * transformed to a JSON Object.  
 64  
  * 
 65  
  * Most default encoders can handle a closure as a request body.  In this 
 66  
  * case, the closure is executed and a suitable 'builder' passed to the 
 67  
  * closure that is  used for constructing the content.  In the case of 
 68  
  * binary encoding this would be an OutputStream; for TEXT encoding it would
 69  
  * be a PrintWriter, and for XML it would be an already-bound 
 70  
  * {@link StreamingMarkupBuilder}. 
 71  
  */
 72  15
 public class EncoderRegistry {
 73  
         
 74  15
         Charset charset = Charset.defaultCharset();
 75  
         public void setCharset( String charset ) { 
 76  0
                 this.charset = Charset.forName(charset);
 77  0
         }
 78  
         
 79  
         /**
 80  
          * Default request encoder for a binary stream.  Acceptable argument 
 81  
          * types are:
 82  
          * <ul>
 83  
          *   <li>InputStream</li>
 84  
          *   <li>ByteArrayOutputStream</li>
 85  
          *   <li>Closure</li>
 86  
          * </ul>
 87  
          * If a closure is given, it is executed with an OutputStream passed
 88  
          * as the single closure argument.  Any data sent to the stream from the 
 89  
          * body of the closure is used as the request content body.
 90  
          * @param data
 91  
          * @return
 92  
          * @throws UnsupportedEncodingException
 93  
          */
 94  
         public HttpEntity encodeStream( Object data ) throws UnsupportedEncodingException {
 95  0
                 if ( data instanceof InputStream ) {
 96  0
                         return new InputStreamEntity( (InputStream)data, -1 );
 97  
                 }
 98  0
                 else if ( data instanceof ByteArrayInputStream ) {
 99  0
                         ByteArrayInputStream in = ((ByteArrayInputStream)data);
 100  0
                         return new InputStreamEntity( in, in.available() );
 101  
                 }
 102  0
                 else if ( data instanceof ByteArrayOutputStream ) {
 103  0
                         ByteArrayOutputStream out = ((ByteArrayOutputStream)data); 
 104  0
                         return new InputStreamEntity( new ByteArrayInputStream(
 105  
                                         out.toByteArray()), out.size() );
 106  
                 }
 107  0
                 else if ( data instanceof Closure ) {
 108  0
                         ByteArrayOutputStream out = new ByteArrayOutputStream();
 109  0
                         ((Closure)data).call( out ); // data is written to out
 110  0
                         return new InputStreamEntity( new ByteArrayInputStream(out.toByteArray()), out.size() );
 111  
                 }
 112  0
                 throw new IllegalArgumentException( "Don't know how to encode " + data + " as a byte stream" );
 113  
         }
 114  
         
 115  
         /**
 116  
          * Default handler used for a text content-type.  Acceptable argument
 117  
          * types are:
 118  
          * <ul>
 119  
          *   <li>Closure</li>
 120  
          *   <li>Writable</li>
 121  
          *   <li>Reader</li>
 122  
          * </ul>
 123  
          * For Closure argument, a {@link PrintWriter} is passed as the single 
 124  
          * argument to the closure.  Any data sent to the writer from the 
 125  
          * closure will be sent to the request content body.
 126  
          * @param data
 127  
          * @return
 128  
          * @throws IOException
 129  
          */
 130  
         public HttpEntity encodeText( Object data ) throws IOException {
 131  0
                 if ( data instanceof Closure ) {
 132  0
                         StringWriter out = new StringWriter();
 133  0
                         PrintWriter writer = new PrintWriter( out );
 134  0
                         ((Closure)data).call( writer );
 135  0
                         writer.close();
 136  0
                         out.flush();
 137  0
                         data = out;
 138  0
                 }
 139  0
                 else if ( data instanceof Writable ) {
 140  0
                         StringWriter out = new StringWriter();
 141  0
                         ((Writable)data).writeTo(out);
 142  0
                         out.flush();
 143  0
                         data = out;
 144  0
                 }
 145  0
                 else if ( data instanceof Reader && ! (data instanceof BufferedReader) )
 146  0
                         data = new BufferedReader( (Reader)data );
 147  0
                 if ( data instanceof BufferedReader ) {
 148  0
                         StringBuilder sb = new StringBuilder();
 149  0
                         BufferedReader reader = (BufferedReader)data;
 150  0
                         String line = null;
 151  0
                         while( (line = reader.readLine()) != null )
 152  0
                                 sb.append( line );
 153  
                         
 154  0
                         data = sb;
 155  
                 }
 156  
                 // if data is a String, we are already covered.
 157  0
                 return createEntity( ContentType.TEXT, data.toString() );
 158  
         }
 159  
         
 160  
         /**
 161  
          * Set the request body as a url-encoded list of parameters.  This is 
 162  
          * typically used to simulate a HTTP form POST. 
 163  
          * @param params
 164  
          * @return
 165  
          * @throws UnsupportedEncodingException
 166  
          */
 167  
         public HttpEntity encodeForm( Map<String,Object> params ) throws UnsupportedEncodingException {
 168  0
                 List<NameValuePair> paramList = new ArrayList<NameValuePair>();
 169  
 
 170  0
                 for ( Map.Entry<String, Object> entry : params.entrySet() ) 
 171  0
                         paramList.add( new BasicNameValuePair(entry.getKey(), 
 172  
                                         entry.getValue().toString()) );
 173  
                         
 174  0
                 return new UrlEncodedFormEntity( paramList, charset.name() );
 175  
         }
 176  
         
 177  
         /**
 178  
          * Executes the given closure and passes a bound {@link StreamingMarkupBuilder}.
 179  
          * @param xmlBuilder
 180  
          * @return
 181  
          * @throws UnsupportedEncodingException
 182  
          */
 183  
         public HttpEntity encodeXML( Closure xmlBuilder ) throws UnsupportedEncodingException {
 184  0
                 StreamingMarkupBuilder smb = new StreamingMarkupBuilder();
 185  0
                 String markup = smb.bind( xmlBuilder ).toString();
 186  0
                 return createEntity( ContentType.XML, markup);
 187  
         }
 188  
         
 189  
         /**
 190  
          * Accepts a Map or a JavaBean object which is converted to JSON.  If
 191  
          * a Closure is passed, it will be executed with a 
 192  
          * {@link JsonGroovyBuilder} as the closure's delegate.  The closure 
 193  
          * must return the result of the outermost builder method call. 
 194  
          * @param model
 195  
          * @return
 196  
          * @throws UnsupportedEncodingException
 197  
          */
 198  
         @SuppressWarnings("unchecked")
 199  
         public HttpEntity encodeJSON( Object model ) throws UnsupportedEncodingException {
 200  
                 JSON json;
 201  
                 
 202  0
                 if ( model instanceof Map ) {
 203  0
                         json = new JSONObject();
 204  0
                         ((JSONObject)json).putAll( (Map)model );
 205  
                 }
 206  0
                 else if ( model instanceof Closure ) {
 207  0
                         Closure closure = (Closure)model;
 208  0
                         closure.setDelegate( new JsonGroovyBuilder() );
 209  0
                         json = (JSONObject)closure.call();
 210  0
                 }
 211  0
                 else json = JSONObject.fromObject( model ); // Assume object is a JavaBean
 212  
                 
 213  0
                 return this.createEntity( ContentType.JSON, json.toString() );
 214  
         }
 215  
         
 216  
         protected HttpEntity createEntity( ContentType ct, String data ) throws UnsupportedEncodingException {
 217  0
                 ByteArrayEntity entity = new ByteArrayEntity( data.getBytes( charset ) );
 218  0
                 entity.setContentType( ct.toString() + "; charset=" + charset.name() );
 219  0
                 return entity;
 220  
         }
 221  
         
 222  15
         protected Map<String,Closure> registeredEncoders = buildDefaultEncoderMap();
 223  
 
 224  
         /** 
 225  
          * Used to set an additional encoder for the given content type.  The 
 226  
          * Closure must return an {@link HttpEntity}.  It will also usually 
 227  
          * accept a single argument, which will be the value given in  
 228  
          * {@link SendDelegate#setBody(Object)}.
 229  
          * @param contentType
 230  
          * @param closure
 231  
          */
 232  
         public void register( String contentType, Closure closure ) {
 233  0
                 registeredEncoders.put( contentType, closure );
 234  0
         }
 235  
         
 236  
         /* Get the encoder for the given content-type.  Not usually called 
 237  
          * by the end-user.  The HTTPBuilder will get the appropriate encoder 
 238  
          * automatically in order to encode the request body data.
 239  
          * @param contentType
 240  
          * @return the encoder closure, or <code>null</code> if no encoder is
 241  
          * registered.
 242  
          */
 243  0
         Closure get( String contentType ) { return registeredEncoders.get(contentType); }
 244  
         
 245  
         /**
 246  
          * Returns a map of default encoders.  Override this method to change 
 247  
          * what encoders are registered by default.  You can of course call
 248  
          * <code>super.buildDefaultEncoderMap()</code> and then add or remove 
 249  
          * from that result as well.
 250  
          */
 251  
         protected Map<String,Closure> buildDefaultEncoderMap() {
 252  15
                 Map<String,Closure> encoders = new HashMap<String,Closure>();
 253  
                 
 254  15
                 encoders.put( ContentType.BINARY.toString(), new MethodClosure(this,"encodeStream") );
 255  15
                 encoders.put( ContentType.TEXT.toString(), new MethodClosure( this, "encodeText" ) );
 256  15
                 encoders.put( ContentType.URLENC.toString(), new MethodClosure( this, "encodeForm" ) );
 257  
                 
 258  15
                 Closure encClosure = new MethodClosure(this,"encodeXML");
 259  60
                 for ( String ct : ContentType.XML.getContentTypeStrings() )
 260  45
                         encoders.put( ct, encClosure );
 261  15
                 encoders.put( ContentType.HTML.toString(), encClosure );
 262  
                 
 263  15
                 encClosure = new MethodClosure(this,"encodeJSON");
 264  60
                 for ( String ct : ContentType.JSON.getContentTypeStrings() )
 265  45
                         encoders.put( ct, encClosure );
 266  
                 
 267  15
                 return encoders;
 268  
         }
 269  
 }