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 }