View Javadoc

1   package org.apache.tomcat.maven.common.deployer;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.commons.codec.binary.Base64;
23  import org.apache.commons.io.IOUtils;
24  import org.apache.commons.lang.StringUtils;
25  import org.apache.http.HttpHost;
26  import org.apache.http.HttpResponse;
27  import org.apache.http.auth.AuthScope;
28  import org.apache.http.auth.Credentials;
29  import org.apache.http.auth.UsernamePasswordCredentials;
30  import org.apache.http.client.AuthCache;
31  import org.apache.http.client.methods.HttpGet;
32  import org.apache.http.client.methods.HttpPut;
33  import org.apache.http.client.methods.HttpRequestBase;
34  import org.apache.http.client.protocol.ClientContext;
35  import org.apache.http.entity.AbstractHttpEntity;
36  import org.apache.http.impl.auth.BasicScheme;
37  import org.apache.http.impl.client.BasicAuthCache;
38  import org.apache.http.impl.client.DefaultHttpClient;
39  import org.apache.http.impl.conn.BasicClientConnectionManager;
40  import org.apache.http.protocol.BasicHttpContext;
41  import org.apache.maven.plugin.logging.Log;
42  
43  import java.io.IOException;
44  import java.io.InputStream;
45  import java.io.OutputStream;
46  import java.io.PrintStream;
47  import java.net.URL;
48  import java.net.URLEncoder;
49  import java.text.DecimalFormat;
50  import java.text.DecimalFormatSymbols;
51  import java.util.Locale;
52  
53  /**
54   * FIXME http connection tru a proxy
55   * A Tomcat manager webapp invocation wrapper.
56   *
57   * @author Mark Hobson <markhobson@gmail.com>
58   * @version $Id: TomcatManager.html 1305321 2012-03-26 12:01:12Z olamy $
59   */
60  public class TomcatManager
61  {
62      // ----------------------------------------------------------------------
63      // Constants
64      // ----------------------------------------------------------------------
65  
66      /**
67       * The charset to use when decoding Tomcat manager responses.
68       */
69      private static final String MANAGER_CHARSET = "UTF-8";
70  
71      // ----------------------------------------------------------------------
72      // Fields
73      // ----------------------------------------------------------------------
74  
75      /**
76       * The full URL of the Tomcat manager instance to use.
77       */
78      private URL url;
79  
80      /**
81       * The username to use when authenticating with Tomcat manager.
82       */
83      private String username;
84  
85      /**
86       * The password to use when authenticating with Tomcat manager.
87       */
88      private String password;
89  
90      /**
91       * The URL encoding charset to use when communicating with Tomcat manager.
92       */
93      private String charset;
94  
95      /**
96       * The user agent name to use when communicating with Tomcat manager.
97       */
98      private String userAgent;
99  
100     /**
101      * @since 2.0
102      */
103     private DefaultHttpClient httpClient;
104 
105     /**
106      * @since 2.0
107      */
108     private BasicHttpContext localContext;
109 
110     // ----------------------------------------------------------------------
111     // Constructors
112     // ----------------------------------------------------------------------
113 
114     /**
115      * Creates a Tomcat manager wrapper for the specified URL that uses a username of <code>admin</code>, an empty
116      * password and ISO-8859-1 URL encoding.
117      *
118      * @param url the full URL of the Tomcat manager instance to use
119      */
120     public TomcatManager( URL url )
121     {
122         this( url, "admin" );
123     }
124 
125     /**
126      * Creates a Tomcat manager wrapper for the specified URL and username that uses an empty password and ISO-8859-1
127      * URL encoding.
128      *
129      * @param url      the full URL of the Tomcat manager instance to use
130      * @param username the username to use when authenticating with Tomcat manager
131      */
132     public TomcatManager( URL url, String username )
133     {
134         this( url, username, "" );
135     }
136 
137     /**
138      * Creates a Tomcat manager wrapper for the specified URL, username and password that uses ISO-8859-1 URL encoding.
139      *
140      * @param url      the full URL of the Tomcat manager instance to use
141      * @param username the username to use when authenticating with Tomcat manager
142      * @param password the password to use when authenticating with Tomcat manager
143      */
144     public TomcatManager( URL url, String username, String password )
145     {
146         this( url, username, password, "ISO-8859-1" );
147     }
148 
149 
150     /**
151      * Creates a Tomcat manager wrapper for the specified URL, username, password and URL encoding.
152      *
153      * @param url      the full URL of the Tomcat manager instance to use
154      * @param username the username to use when authenticating with Tomcat manager
155      * @param password the password to use when authenticating with Tomcat manager
156      * @param charset  the URL encoding charset to use when communicating with Tomcat manager
157      */
158     public TomcatManager( URL url, String username, String password, String charset )
159     {
160         this.url = url;
161         this.username = username;
162         this.password = password;
163         this.charset = charset;
164 
165         this.httpClient = new DefaultHttpClient( new BasicClientConnectionManager() );
166         if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) )
167         {
168             Credentials creds = new UsernamePasswordCredentials( username, password );
169 
170             String host = url.getHost();
171             int port = url.getPort() > -1 ? url.getPort() : AuthScope.ANY_PORT;
172 
173             httpClient.getCredentialsProvider().setCredentials( new AuthScope( host, port ), creds );
174 
175             AuthCache authCache = new BasicAuthCache();
176             BasicScheme basicAuth = new BasicScheme();
177             HttpHost targetHost = new HttpHost( url.getHost(), url.getPort(), url.getProtocol() );
178             authCache.put( targetHost, basicAuth );
179 
180             localContext = new BasicHttpContext();
181             localContext.setAttribute( ClientContext.AUTH_CACHE, authCache );
182         }
183     }
184 
185     // ----------------------------------------------------------------------
186     // Public Methods
187     // ----------------------------------------------------------------------
188 
189     /**
190      * Gets the full URL of the Tomcat manager instance.
191      *
192      * @return the full URL of the Tomcat manager instance
193      */
194     public URL getURL()
195     {
196         return url;
197     }
198 
199     /**
200      * Gets the username to use when authenticating with Tomcat manager.
201      *
202      * @return the username to use when authenticating with Tomcat manager
203      */
204     public String getUserName()
205     {
206         return username;
207     }
208 
209     /**
210      * Gets the password to use when authenticating with Tomcat manager.
211      *
212      * @return the password to use when authenticating with Tomcat manager
213      */
214     public String getPassword()
215     {
216         return password;
217     }
218 
219     /**
220      * Gets the URL encoding charset to use when communicating with Tomcat manager.
221      *
222      * @return the URL encoding charset to use when communicating with Tomcat manager
223      */
224     public String getCharset()
225     {
226         return charset;
227     }
228 
229     /**
230      * Gets the user agent name to use when communicating with Tomcat manager.
231      *
232      * @return the user agent name to use when communicating with Tomcat manager
233      */
234     public String getUserAgent()
235     {
236         return userAgent;
237     }
238 
239     /**
240      * Sets the user agent name to use when communicating with Tomcat manager.
241      *
242      * @param userAgent the user agent name to use when communicating with Tomcat manager
243      */
244     public void setUserAgent( String userAgent )
245     {
246         this.userAgent = userAgent;
247     }
248 
249     /**
250      * Deploys the specified WAR as a URL to the specified context path.
251      *
252      * @param path the webapp context path to deploy to
253      * @param war  the URL of the WAR to deploy
254      * @return the Tomcat manager response
255      * @throws TomcatManagerException if the Tomcat manager request fails
256      * @throws IOException            if an i/o error occurs
257      */
258     public TomcatManagerResponse deploy( String path, URL war )
259         throws TomcatManagerException, IOException
260     {
261         return deploy( path, war, false );
262     }
263 
264     /**
265      * Deploys the specified WAR as a URL to the specified context path, optionally undeploying the webapp if it already
266      * exists.
267      *
268      * @param path   the webapp context path to deploy to
269      * @param war    the URL of the WAR to deploy
270      * @param update whether to first undeploy the webapp if it already exists
271      * @return the Tomcat manager response
272      * @throws TomcatManagerException if the Tomcat manager request fails
273      * @throws IOException            if an i/o error occurs
274      */
275     public TomcatManagerResponse deploy( String path, URL war, boolean update )
276         throws TomcatManagerException, IOException
277     {
278         return deploy( path, war, update, null );
279     }
280 
281     /**
282      * Deploys the specified WAR as a URL to the specified context path, optionally undeploying the webapp if it already
283      * exists and using the specified tag name.
284      *
285      * @param path   the webapp context path to deploy to
286      * @param war    the URL of the WAR to deploy
287      * @param update whether to first undeploy the webapp if it already exists
288      * @param tag    the tag name to use
289      * @return the Tomcat manager response
290      * @throws TomcatManagerException if the Tomcat manager request fails
291      * @throws IOException            if an i/o error occurs
292      */
293     public TomcatManagerResponse deploy( String path, URL war, boolean update, String tag )
294         throws TomcatManagerException, IOException
295     {
296         return deployImpl( path, null, war, null, update, tag );
297     }
298 
299     /**
300      * Deploys the specified WAR as a HTTP PUT to the specified context path.
301      *
302      * @param path the webapp context path to deploy to
303      * @param war  an input stream to the WAR to deploy
304      * @return the Tomcat manager response
305      * @throws TomcatManagerException if the Tomcat manager request fails
306      * @throws IOException            if an i/o error occurs
307      */
308     public TomcatManagerResponse deploy( String path, InputStream war )
309         throws TomcatManagerException, IOException
310     {
311         return deploy( path, war, false );
312     }
313 
314     /**
315      * Deploys the specified WAR as a HTTP PUT to the specified context path, optionally undeploying the webapp if it
316      * already exists.
317      *
318      * @param path   the webapp context path to deploy to
319      * @param war    an input stream to the WAR to deploy
320      * @param update whether to first undeploy the webapp if it already exists
321      * @return the Tomcat manager response
322      * @throws TomcatManagerException if the Tomcat manager request fails
323      * @throws IOException            if an i/o error occurs
324      */
325     public TomcatManagerResponse deploy( String path, InputStream war, boolean update )
326         throws TomcatManagerException, IOException
327     {
328         return deploy( path, war, update, null );
329     }
330 
331     /**
332      * Deploys the specified WAR as a HTTP PUT to the specified context path, optionally undeploying the webapp if it
333      * already exists and using the specified tag name.
334      *
335      * @param path   the webapp context path to deploy to
336      * @param war    an input stream to the WAR to deploy
337      * @param update whether to first undeploy the webapp if it already exists
338      * @param tag    the tag name to use
339      * @return the Tomcat manager response
340      * @throws TomcatManagerException if the Tomcat manager request fails
341      * @throws IOException            if an i/o error occurs
342      */
343     public TomcatManagerResponse deploy( String path, InputStream war, boolean update, String tag )
344         throws TomcatManagerException, IOException
345     {
346         return deployImpl( path, null, null, war, update, tag );
347     }
348 
349     /**
350      * @param path
351      * @param war
352      * @param update
353      * @param tag
354      * @param length
355      * @return
356      * @throws TomcatManagerException
357      * @throws IOException
358      * @since 2.0
359      */
360     public TomcatManagerResponse deploy( String path, InputStream war, boolean update, String tag, long length )
361         throws TomcatManagerException, IOException
362     {
363         return deployImpl( path, null, null, war, update, tag, length );
364     }
365 
366     /**
367      * Deploys the specified context XML configuration to the specified context path.
368      *
369      * @param path   the webapp context path to deploy to
370      * @param config the URL of the context XML configuration to deploy
371      * @return the Tomcat manager response
372      * @throws TomcatManagerException if the Tomcat manager request fails
373      * @throws IOException            if an i/o error occurs
374      */
375     public TomcatManagerResponse deployContext( String path, URL config )
376         throws TomcatManagerException, IOException
377     {
378         return deployContext( path, config, false );
379     }
380 
381     /**
382      * Deploys the specified context XML configuration to the specified context path, optionally undeploying the webapp
383      * if it already exists.
384      *
385      * @param path   the webapp context path to deploy to
386      * @param config the URL of the context XML configuration to deploy
387      * @param update whether to first undeploy the webapp if it already exists
388      * @return the Tomcat manager response
389      * @throws TomcatManagerException if the Tomcat manager request fails
390      * @throws IOException            if an i/o error occurs
391      */
392     public TomcatManagerResponse deployContext( String path, URL config, boolean update )
393         throws TomcatManagerException, IOException
394     {
395         return deployContext( path, config, update, null );
396     }
397 
398     /**
399      * Deploys the specified context XML configuration to the specified context path, optionally undeploying the webapp
400      * if it already exists and using the specified tag name.
401      *
402      * @param path   the webapp context path to deploy to
403      * @param config the URL of the context XML configuration to deploy
404      * @param update whether to first undeploy the webapp if it already exists
405      * @param tag    the tag name to use
406      * @return the Tomcat manager response
407      * @throws TomcatManagerException if the Tomcat manager request fails
408      * @throws IOException            if an i/o error occurs
409      */
410     public TomcatManagerResponse deployContext( String path, URL config, boolean update, String tag )
411         throws TomcatManagerException, IOException
412     {
413         return deployContext( path, config, null, update, tag );
414     }
415 
416     /**
417      * Deploys the specified context XML configuration and WAR as a URL to the specified context path.
418      *
419      * @param path   the webapp context path to deploy to
420      * @param config the URL of the context XML configuration to deploy
421      * @param war    the URL of the WAR to deploy
422      * @return the Tomcat manager response
423      * @throws TomcatManagerException if the Tomcat manager request fails
424      * @throws IOException            if an i/o error occurs
425      */
426     public TomcatManagerResponse deployContext( String path, URL config, URL war )
427         throws TomcatManagerException, IOException
428     {
429         return deployContext( path, config, war, false );
430     }
431 
432     /**
433      * Deploys the specified context XML configuration and WAR as a URL to the specified context path, optionally
434      * undeploying the webapp if it already exists.
435      *
436      * @param path   the webapp context path to deploy to
437      * @param config the URL of the context XML configuration to deploy
438      * @param war    the URL of the WAR to deploy
439      * @param update whether to first undeploy the webapp if it already exists
440      * @return the Tomcat manager response
441      * @throws TomcatManagerException if the Tomcat manager request fails
442      * @throws IOException            if an i/o error occurs
443      */
444     public TomcatManagerResponse deployContext( String path, URL config, URL war, boolean update )
445         throws TomcatManagerException, IOException
446     {
447         return deployContext( path, config, war, update, null );
448     }
449 
450     /**
451      * Deploys the specified context XML configuration and WAR as a URL to the specified context path, optionally
452      * undeploying the webapp if it already exists and using the specified tag name.
453      *
454      * @param path   the webapp context path to deploy to
455      * @param config the URL of the context XML configuration to deploy
456      * @param war    the URL of the WAR to deploy
457      * @param update whether to first undeploy the webapp if it already exists
458      * @param tag    the tag name to use
459      * @return the Tomcat manager response
460      * @throws TomcatManagerException if the Tomcat manager request fails
461      * @throws IOException            if an i/o error occurs
462      */
463     public TomcatManagerResponse deployContext( String path, URL config, URL war, boolean update, String tag )
464         throws TomcatManagerException, IOException
465     {
466         return deployImpl( path, config, war, null, update, tag );
467     }
468 
469     /**
470      * Undeploys the webapp at the specified context path.
471      *
472      * @param path the webapp context path to undeploy
473      * @return the Tomcat manager response
474      * @throws TomcatManagerException if the Tomcat manager request fails
475      * @throws IOException            if an i/o error occurs
476      */
477     public TomcatManagerResponse undeploy( String path )
478         throws TomcatManagerException, IOException
479     {
480         return invoke( "/undeploy?path=" + URLEncoder.encode( path, charset ) );
481     }
482 
483     /**
484      * Reloads the webapp at the specified context path.
485      *
486      * @param path the webapp context path to reload
487      * @return the Tomcat manager response
488      * @throws TomcatManagerException if the Tomcat manager request fails
489      * @throws IOException            if an i/o error occurs
490      */
491     public TomcatManagerResponse reload( String path )
492         throws TomcatManagerException, IOException
493     {
494         return invoke( "/reload?path=" + URLEncoder.encode( path, charset ) );
495     }
496 
497     /**
498      * Starts the webapp at the specified context path.
499      *
500      * @param path the webapp context path to start
501      * @return the Tomcat manager response
502      * @throws TomcatManagerException if the Tomcat manager request fails
503      * @throws IOException            if an i/o error occurs
504      */
505     public TomcatManagerResponse start( String path )
506         throws TomcatManagerException, IOException
507     {
508         return invoke( "/start?path=" + URLEncoder.encode( path, charset ) );
509     }
510 
511     /**
512      * Stops the webapp at the specified context path.
513      *
514      * @param path the webapp context path to stop
515      * @return the Tomcat manager response
516      * @throws TomcatManagerException if the Tomcat manager request fails
517      * @throws IOException            if an i/o error occurs
518      */
519     public TomcatManagerResponse stop( String path )
520         throws TomcatManagerException, IOException
521     {
522         return invoke( "/stop?path=" + URLEncoder.encode( path, charset ) );
523     }
524 
525     /**
526      * Lists all the currently deployed web applications.
527      *
528      * @return the list of currently deployed applications
529      * @throws TomcatManagerException if the Tomcat manager request fails
530      * @throws IOException            if an i/o error occurs
531      */
532     public TomcatManagerResponse list()
533         throws TomcatManagerException, IOException
534     {
535         return invoke( "/list" );
536     }
537 
538     /**
539      * Lists information about the Tomcat version, OS, and JVM properties.
540      *
541      * @return the server information
542      * @throws TomcatManagerException if the Tomcat manager request fails
543      * @throws IOException            if an i/o error occurs
544      */
545     public TomcatManagerResponse getServerInfo()
546         throws TomcatManagerException, IOException
547     {
548         return invoke( "/serverinfo" );
549     }
550 
551     /**
552      * Lists all of the global JNDI resources.
553      *
554      * @return the list of all global JNDI resources
555      * @throws TomcatManagerException if the Tomcat manager request fails
556      * @throws IOException            if an i/o error occurs
557      */
558     public TomcatManagerResponse getResources()
559         throws TomcatManagerException, IOException
560     {
561         return getResources( null );
562     }
563 
564     /**
565      * Lists the global JNDI resources of the given type.
566      *
567      * @param type the class name of the resources to list, or <code>null</code> for all
568      * @return the list of global JNDI resources of the given type
569      * @throws TomcatManagerException if the Tomcat manager request fails
570      * @throws IOException            if an i/o error occurs
571      */
572     public TomcatManagerResponse getResources( String type )
573         throws TomcatManagerException, IOException
574     {
575         StringBuffer buffer = new StringBuffer();
576         buffer.append( "/resources" );
577 
578         if ( type != null )
579         {
580             buffer.append( "?type=" + URLEncoder.encode( type, charset ) );
581         }
582         return invoke( buffer.toString() );
583     }
584 
585     /**
586      * Lists the security role names and corresponding descriptions that are available.
587      *
588      * @return the list of security role names and corresponding descriptions
589      * @throws TomcatManagerException if the Tomcat manager request fails
590      * @throws IOException            if an i/o error occurs
591      */
592     public TomcatManagerResponse getRoles()
593         throws TomcatManagerException, IOException
594     {
595         return invoke( "/roles" );
596     }
597 
598     /**
599      * Lists the default session timeout and the number of currently active sessions for the given context path.
600      *
601      * @param path the context path to list session information for
602      * @return the default session timeout and the number of currently active sessions
603      * @throws TomcatManagerException if the Tomcat manager request fails
604      * @throws IOException            if an i/o error occurs
605      */
606     public TomcatManagerResponse getSessions( String path )
607         throws TomcatManagerException, IOException
608     {
609         return invoke( "/sessions?path=" + URLEncoder.encode( path, charset ) );
610     }
611 
612     // ----------------------------------------------------------------------
613     // Protected Methods
614     // ----------------------------------------------------------------------
615 
616     /**
617      * Invokes Tomcat manager with the specified command.
618      *
619      * @param path the Tomcat manager command to invoke
620      * @return the Tomcat manager response
621      * @throws TomcatManagerException if the Tomcat manager request fails
622      * @throws IOException            if an i/o error occurs
623      */
624     protected TomcatManagerResponse invoke( String path )
625         throws TomcatManagerException, IOException
626     {
627         return invoke( path, null, -1 );
628     }
629 
630     // ----------------------------------------------------------------------
631     // Private Methods
632     // ----------------------------------------------------------------------
633 
634     private TomcatManagerResponse deployImpl( String path, URL config, URL war, InputStream data, boolean update,
635                                               String tag )
636         throws TomcatManagerException, IOException
637     {
638         return deployImpl( path, config, war, data, update, tag, -1 );
639     }
640 
641     /**
642      * Deploys the specified WAR.
643      *
644      * @param path   the webapp context path to deploy to
645      * @param config the URL of the context XML configuration to deploy, or null for none
646      * @param war    the URL of the WAR to deploy, or null to use <code>data</code>
647      * @param data   an input stream to the WAR to deploy, or null to use <code>war</code>
648      * @param update whether to first undeploy the webapp if it already exists
649      * @param tag    the tag name to use
650      * @return the Tomcat manager response
651      * @throws TomcatManagerException if the Tomcat manager request fails
652      * @throws IOException            if an i/o error occurs
653      */
654     private TomcatManagerResponse deployImpl( String path, URL config, URL war, InputStream data, boolean update,
655                                               String tag, long length )
656         throws TomcatManagerException, IOException
657     {
658         StringBuilder buffer = new StringBuilder( "/deploy" );
659         buffer.append( "?path=" ).append( URLEncoder.encode( path, charset ) );
660 
661         if ( config != null )
662         {
663             buffer.append( "&config=" ).append( URLEncoder.encode( config.toString(), charset ) );
664         }
665 
666         if ( war != null )
667         {
668             buffer.append( "&war=" ).append( URLEncoder.encode( war.toString(), charset ) );
669         }
670 
671         if ( update )
672         {
673             buffer.append( "&update=true" );
674         }
675 
676         if ( tag != null )
677         {
678             buffer.append( "&tag=" ).append( URLEncoder.encode( tag, charset ) );
679         }
680 
681         return invoke( buffer.toString(), data, length );
682     }
683 
684 
685     /**
686      * Invokes Tomcat manager with the specified command and content data.
687      *
688      * @param path the Tomcat manager command to invoke
689      * @param data an input stream to the content data
690      * @return the Tomcat manager response
691      * @throws TomcatManagerException if the Tomcat manager request fails
692      * @throws IOException            if an i/o error occurs
693      */
694     protected TomcatManagerResponse invoke( String path, InputStream data, long length )
695         throws TomcatManagerException, IOException
696     {
697 
698         HttpRequestBase httpRequestBase = null;
699         if ( data == null )
700         {
701             httpRequestBase = new HttpGet( url + path );
702         }
703         else
704         {
705             HttpPut httpPut = new HttpPut( url + path );
706 
707             httpPut.setEntity( new RequestEntityImplementation( data, length, url + path ) );
708 
709             httpRequestBase = httpPut;
710 
711         }
712 
713         if ( userAgent != null )
714         {
715             httpRequestBase.setHeader( "User-Agent", userAgent );
716         }
717 
718         HttpResponse response = httpClient.execute( httpRequestBase, localContext );
719 
720         return new TomcatManagerResponse().setStatusCode( response.getStatusLine().getStatusCode() ).setReasonPhrase(
721             response.getStatusLine().getReasonPhrase() ).setHttpResponseBody(
722             IOUtils.toString( response.getEntity().getContent() ) );
723 
724     }
725 
726 
727     /**
728      * Gets the HTTP Basic Authorization header value for the supplied username and password.
729      *
730      * @param username the username to use for authentication
731      * @param password the password to use for authentication
732      * @return the HTTP Basic Authorization header value
733      */
734     private String toAuthorization( String username, String password )
735     {
736         StringBuffer buffer = new StringBuffer();
737         buffer.append( username ).append( ':' );
738         if ( password != null )
739         {
740             buffer.append( password );
741         }
742         return "Basic " + new String( Base64.encodeBase64( buffer.toString().getBytes() ) );
743     }
744 
745     private final class RequestEntityImplementation
746         extends AbstractHttpEntity
747     {
748 
749         private final static int BUFFER_SIZE = 2048;
750 
751         private InputStream stream;
752 
753         PrintStream out = System.out;
754 
755         private long length = -1;
756 
757         private int lastLength;
758 
759         private String url;
760 
761         private long startTime;
762 
763         private RequestEntityImplementation( final InputStream stream, long length, String url )
764         {
765             this.stream = stream;
766             this.length = length;
767             this.url = url;
768         }
769 
770         public long getContentLength()
771         {
772             return length >= 0 ? length : -1;
773         }
774 
775 
776         public InputStream getContent()
777             throws IOException, IllegalStateException
778         {
779             return this.stream;
780         }
781 
782         public boolean isRepeatable()
783         {
784             return false;
785         }
786 
787 
788         public void writeTo( final OutputStream outstream )
789             throws IOException
790         {
791             long completed = 0;
792             if ( outstream == null )
793             {
794                 throw new IllegalArgumentException( "Output stream may not be null" );
795             }
796             transferInitiated( this.url );
797             this.startTime = System.currentTimeMillis();
798             try
799             {
800                 byte[] buffer = new byte[BUFFER_SIZE];
801                 int l;
802                 if ( this.length < 0 )
803                 {
804                     // until EOF
805                     while ( ( l = stream.read( buffer ) ) != -1 )
806                     {
807                         transferProgressed( completed += buffer.length, -1 );
808                         outstream.write( buffer, 0, l );
809                     }
810                 }
811                 else
812                 {
813                     // no need to consume more than length
814                     long remaining = this.length;
815                     while ( remaining > 0 )
816                     {
817                         int transferSize = (int) Math.min( BUFFER_SIZE, remaining );
818                         completed += transferSize;
819                         l = stream.read( buffer, 0, transferSize );
820                         if ( l == -1 )
821                         {
822                             break;
823                         }
824 
825                         outstream.write( buffer, 0, l );
826                         remaining -= l;
827                         transferProgressed( completed, this.length );
828                     }
829                 }
830                 transferSucceeded( completed );
831             }
832             finally
833             {
834                 stream.close();
835                 out.println();
836             }
837             // end transfer
838         }
839 
840         public boolean isStreaming()
841         {
842             return true;
843         }
844 
845 
846         public void transferInitiated( String url )
847         {
848             String message = "Uploading";
849 
850             out.println( message + ": " + url );
851         }
852 
853         public void transferProgressed( long completedSize, long totalSize )
854         {
855 
856             StringBuilder buffer = new StringBuilder( 64 );
857 
858             buffer.append( getStatus( completedSize, totalSize ) ).append( "  " );
859             lastLength = buffer.length();
860             buffer.append( '\r' );
861 
862             out.print( buffer );
863         }
864 
865         public void transferSucceeded( long contentLength )
866         {
867 
868             if ( contentLength >= 0 )
869             {
870                 String type = "Uploaded";
871                 String len = contentLength >= 1024 ? toKB( contentLength ) + " KB" : contentLength + " B";
872 
873                 String throughput = "";
874                 long duration = System.currentTimeMillis() - startTime;
875                 if ( duration > 0 )
876                 {
877                     DecimalFormat format = new DecimalFormat( "0.0", new DecimalFormatSymbols( Locale.ENGLISH ) );
878                     double kbPerSec = ( contentLength / 1024.0 ) / ( duration / 1000.0 );
879                     throughput = " at " + format.format( kbPerSec ) + " KB/sec";
880                 }
881 
882                 out.println( type + ": " + url + " (" + len + throughput + ")" );
883             }
884         }
885 
886         private String getStatus( long complete, long total )
887         {
888             if ( total >= 1024 )
889             {
890                 return toKB( complete ) + "/" + toKB( total ) + " KB ";
891             }
892             else if ( total >= 0 )
893             {
894                 return complete + "/" + total + " B ";
895             }
896             else if ( complete >= 1024 )
897             {
898                 return toKB( complete ) + " KB ";
899             }
900             else
901             {
902                 return complete + " B ";
903             }
904         }
905 
906         private long toKB( long bytes )
907         {
908             return ( bytes + 1023 ) / 1024;
909         }
910 
911     }
912 }