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