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