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