001package org.apache.maven.wagon.providers.webdav; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.commons.httpclient.Credentials; 023import org.apache.commons.httpclient.Header; 024import org.apache.commons.httpclient.HostConfiguration; 025import org.apache.commons.httpclient.HttpClient; 026import org.apache.commons.httpclient.HttpConnectionManager; 027import org.apache.commons.httpclient.HttpMethod; 028import org.apache.commons.httpclient.HttpStatus; 029import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; 030import org.apache.commons.httpclient.NTCredentials; 031import org.apache.commons.httpclient.UsernamePasswordCredentials; 032import org.apache.commons.httpclient.auth.AuthScope; 033import org.apache.commons.httpclient.cookie.CookiePolicy; 034import org.apache.commons.httpclient.methods.EntityEnclosingMethod; 035import org.apache.commons.httpclient.methods.GetMethod; 036import org.apache.commons.httpclient.methods.HeadMethod; 037import org.apache.commons.httpclient.methods.PutMethod; 038import org.apache.commons.httpclient.methods.RequestEntity; 039import org.apache.commons.httpclient.params.HttpMethodParams; 040import org.apache.commons.httpclient.util.DateParseException; 041import org.apache.commons.httpclient.util.DateUtil; 042import org.apache.commons.io.IOUtils; 043import org.apache.commons.lang.StringUtils; 044import org.apache.maven.wagon.InputData; 045import org.apache.maven.wagon.OutputData; 046import org.apache.maven.wagon.PathUtils; 047import org.apache.maven.wagon.ResourceDoesNotExistException; 048import org.apache.maven.wagon.StreamWagon; 049import org.apache.maven.wagon.TransferFailedException; 050import org.apache.maven.wagon.Wagon; 051import org.apache.maven.wagon.authorization.AuthorizationException; 052import org.apache.maven.wagon.events.TransferEvent; 053import org.apache.maven.wagon.proxy.ProxyInfo; 054import org.apache.maven.wagon.repository.Repository; 055import org.apache.maven.wagon.resource.Resource; 056import org.apache.maven.wagon.shared.http.EncodingUtil; 057 058import java.io.ByteArrayInputStream; 059import java.io.File; 060import java.io.FileInputStream; 061import java.io.IOException; 062import java.io.InputStream; 063import java.io.OutputStream; 064import java.nio.ByteBuffer; 065import java.text.SimpleDateFormat; 066import java.util.Date; 067import java.util.Locale; 068import java.util.Properties; 069import java.util.TimeZone; 070import java.util.zip.GZIPInputStream; 071 072/** 073 * @author <a href="michal.maczka@dimatics.com">Michal Maczka</a> 074 * @author <a href="mailto:james@atlassian.com">James William Dumay</a> 075 */ 076public abstract class AbstractHttpClientWagon 077 extends StreamWagon 078{ 079 private final class RequestEntityImplementation 080 implements RequestEntity 081 { 082 private final Resource resource; 083 084 private final Wagon wagon; 085 086 private File source; 087 088 private ByteBuffer byteBuffer; 089 090 private RequestEntityImplementation( final InputStream stream, final Resource resource, final Wagon wagon, 091 final File source ) 092 throws TransferFailedException 093 { 094 if ( source != null ) 095 { 096 this.source = source; 097 } 098 else 099 { 100 try 101 { 102 byte[] bytes = IOUtils.toByteArray( stream ); 103 this.byteBuffer = ByteBuffer.allocate( bytes.length ); 104 this.byteBuffer.put( bytes ); 105 } 106 catch ( IOException e ) 107 { 108 throw new TransferFailedException( e.getMessage(), e ); 109 } 110 } 111 112 this.resource = resource; 113 this.wagon = wagon; 114 } 115 116 public long getContentLength() 117 { 118 return resource.getContentLength(); 119 } 120 121 public String getContentType() 122 { 123 return null; 124 } 125 126 public boolean isRepeatable() 127 { 128 return true; 129 } 130 131 public void writeRequest( OutputStream output ) 132 throws IOException 133 { 134 byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; 135 136 TransferEvent transferEvent = 137 new TransferEvent( wagon, resource, TransferEvent.TRANSFER_PROGRESS, TransferEvent.REQUEST_PUT ); 138 transferEvent.setTimestamp( System.currentTimeMillis() ); 139 140 InputStream fin = null; 141 try 142 { 143 fin = this.source != null 144 ? new FileInputStream( source ) 145 : new ByteArrayInputStream( this.byteBuffer.array() ); 146 int remaining = Integer.MAX_VALUE; 147 while ( remaining > 0 ) 148 { 149 int n = fin.read( buffer, 0, Math.min( buffer.length, remaining ) ); 150 151 if ( n == -1 ) 152 { 153 break; 154 } 155 156 fireTransferProgress( transferEvent, buffer, n ); 157 158 output.write( buffer, 0, n ); 159 160 remaining -= n; 161 } 162 } 163 finally 164 { 165 IOUtils.closeQuietly( fin ); 166 } 167 168 output.flush(); 169 } 170 } 171 172 protected static final int SC_NULL = -1; 173 174 protected static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone( "GMT" ); 175 176 private HttpClient client; 177 178 protected HttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager(); 179 180 /** 181 * @deprecated Use httpConfiguration instead. 182 */ 183 private Properties httpHeaders; 184 185 /** 186 * @since 1.0-beta-6 187 */ 188 private HttpConfiguration httpConfiguration; 189 190 private HttpMethod getMethod; 191 192 public void openConnectionInternal() 193 { 194 repository.setUrl( getURL( repository ) ); 195 client = new HttpClient( connectionManager ); 196 197 // WAGON-273: default the cookie-policy to browser compatible 198 client.getParams().setCookiePolicy( CookiePolicy.BROWSER_COMPATIBILITY ); 199 200 String username = null; 201 String password = null; 202 String domain = null; 203 204 if ( authenticationInfo != null ) 205 { 206 username = authenticationInfo.getUserName(); 207 208 if ( StringUtils.contains( username, "\\" ) ) 209 { 210 String[] domainAndUsername = username.split( "\\\\" ); 211 domain = domainAndUsername[0]; 212 username = domainAndUsername[1]; 213 } 214 215 password = authenticationInfo.getPassword(); 216 217 218 } 219 220 String host = getRepository().getHost(); 221 222 if ( StringUtils.isNotEmpty( username ) && StringUtils.isNotEmpty( password ) ) 223 { 224 Credentials creds; 225 if ( domain != null ) 226 { 227 creds = new NTCredentials( username, password, host, domain ); 228 } 229 else 230 { 231 creds = new UsernamePasswordCredentials( username, password ); 232 } 233 234 int port = getRepository().getPort() > -1 ? getRepository().getPort() : AuthScope.ANY_PORT; 235 236 AuthScope scope = new AuthScope( host, port ); 237 client.getState().setCredentials( scope, creds ); 238 } 239 240 HostConfiguration hc = new HostConfiguration(); 241 242 ProxyInfo proxyInfo = getProxyInfo( getRepository().getProtocol(), getRepository().getHost() ); 243 if ( proxyInfo != null ) 244 { 245 String proxyUsername = proxyInfo.getUserName(); 246 String proxyPassword = proxyInfo.getPassword(); 247 String proxyHost = proxyInfo.getHost(); 248 int proxyPort = proxyInfo.getPort(); 249 String proxyNtlmHost = proxyInfo.getNtlmHost(); 250 String proxyNtlmDomain = proxyInfo.getNtlmDomain(); 251 if ( proxyHost != null ) 252 { 253 hc.setProxy( proxyHost, proxyPort ); 254 255 if ( proxyUsername != null && proxyPassword != null ) 256 { 257 Credentials creds; 258 if ( proxyNtlmHost != null || proxyNtlmDomain != null ) 259 { 260 creds = new NTCredentials( proxyUsername, proxyPassword, proxyNtlmHost, proxyNtlmDomain ); 261 } 262 else 263 { 264 creds = new UsernamePasswordCredentials( proxyUsername, proxyPassword ); 265 } 266 267 int port = proxyInfo.getPort() > -1 ? proxyInfo.getPort() : AuthScope.ANY_PORT; 268 269 AuthScope scope = new AuthScope( proxyHost, port ); 270 client.getState().setProxyCredentials( scope, creds ); 271 } 272 } 273 } 274 275 hc.setHost( host ); 276 277 //start a session with the webserver 278 client.setHostConfiguration( hc ); 279 } 280 281 public void closeConnection() 282 { 283 if ( connectionManager instanceof MultiThreadedHttpConnectionManager ) 284 { 285 ( (MultiThreadedHttpConnectionManager) connectionManager ).shutdown(); 286 } 287 } 288 289 public void put( File source, String resourceName ) 290 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 291 { 292 Resource resource = new Resource( resourceName ); 293 294 firePutInitiated( resource, source ); 295 296 resource.setContentLength( source.length() ); 297 298 resource.setLastModified( source.lastModified() ); 299 300 put( null, resource, source ); 301 } 302 303 public void putFromStream( final InputStream stream, String destination, long contentLength, long lastModified ) 304 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 305 { 306 Resource resource = new Resource( destination ); 307 308 firePutInitiated( resource, null ); 309 310 resource.setContentLength( contentLength ); 311 312 resource.setLastModified( lastModified ); 313 314 put( stream, resource, null ); 315 } 316 317 private void put( final InputStream stream, Resource resource, File source ) 318 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException 319 { 320 RequestEntityImplementation requestEntityImplementation = 321 new RequestEntityImplementation( stream, resource, this, source ); 322 323 put( resource, source, requestEntityImplementation, buildUrl( resource ) ); 324 325 } 326 327 /** 328 * Builds a complete URL string from the repository URL and the relative path of the resource passed. 329 * 330 * @param resource the resource to extract the relative path from. 331 * @return the complete URL 332 */ 333 private String buildUrl( Resource resource ) 334 { 335 return EncodingUtil.encodeURLToString( getRepository().getUrl(), resource.getName() ); 336 } 337 338 private void put( Resource resource, File source, RequestEntityImplementation requestEntityImplementation, 339 String url ) 340 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException 341 { 342 // preemptive true for put 343 client.getParams().setAuthenticationPreemptive( true ); 344 345 //Parent directories need to be created before posting 346 try 347 { 348 mkdirs( PathUtils.dirname( resource.getName() ) ); 349 } 350 catch ( IOException e ) 351 { 352 fireTransferError( resource, e, TransferEvent.REQUEST_GET ); 353 } 354 355 PutMethod putMethod = new PutMethod( url ); 356 357 firePutStarted( resource, source ); 358 359 try 360 { 361 putMethod.setRequestEntity( requestEntityImplementation ); 362 363 int statusCode; 364 try 365 { 366 statusCode = execute( putMethod ); 367 368 } 369 catch ( IOException e ) 370 { 371 fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); 372 373 throw new TransferFailedException( e.getMessage(), e ); 374 } 375 376 fireTransferDebug( url + " - Status code: " + statusCode ); 377 378 // Check that we didn't run out of retries. 379 switch ( statusCode ) 380 { 381 // Success Codes 382 case HttpStatus.SC_OK: // 200 383 case HttpStatus.SC_CREATED: // 201 384 case HttpStatus.SC_ACCEPTED: // 202 385 case HttpStatus.SC_NO_CONTENT: // 204 386 break; 387 388 // 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" 389 case HttpStatus.SC_MOVED_PERMANENTLY: // 301 390 case HttpStatus.SC_MOVED_TEMPORARILY: // 302 391 case HttpStatus.SC_SEE_OTHER: // 303 392 String relocatedUrl = calculateRelocatedUrl( putMethod ); 393 fireTransferDebug( "relocate to " + relocatedUrl ); 394 put( resource, source, requestEntityImplementation, relocatedUrl ); 395 return; 396 397 case SC_NULL: 398 { 399 TransferFailedException e = new TransferFailedException( "Failed to transfer file: " + url ); 400 fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); 401 throw e; 402 } 403 404 case HttpStatus.SC_FORBIDDEN: 405 fireSessionConnectionRefused(); 406 throw new AuthorizationException( "Access denied to: " + url ); 407 408 case HttpStatus.SC_NOT_FOUND: 409 throw new ResourceDoesNotExistException( "File: " + url + " does not exist" ); 410 411 //add more entries here 412 default: 413 { 414 TransferFailedException e = new TransferFailedException( 415 "Failed to transfer file: " + url + ". Return code is: " + statusCode ); 416 fireTransferError( resource, e, TransferEvent.REQUEST_PUT ); 417 throw e; 418 } 419 } 420 421 firePutCompleted( resource, source ); 422 } 423 finally 424 { 425 putMethod.releaseConnection(); 426 } 427 } 428 429 protected String calculateRelocatedUrl( EntityEnclosingMethod method ) 430 { 431 Header locationHeader = method.getResponseHeader( "Location" ); 432 String locationField = locationHeader.getValue(); 433 // is it a relative Location or a full ? 434 return locationField.startsWith( "http" ) ? locationField : getURL( getRepository() ) + '/' + locationField; 435 } 436 437 protected void mkdirs( String dirname ) 438 throws IOException 439 { 440 // do nothing as default. 441 } 442 443 public boolean resourceExists( String resourceName ) 444 throws TransferFailedException, AuthorizationException 445 { 446 StringBuilder url = new StringBuilder( getRepository().getUrl() ); 447 if ( !url.toString().endsWith( "/" ) ) 448 { 449 url.append( '/' ); 450 } 451 url.append( resourceName ); 452 HeadMethod headMethod = new HeadMethod( url.toString() ); 453 454 int statusCode; 455 try 456 { 457 statusCode = execute( headMethod ); 458 } 459 catch ( IOException e ) 460 { 461 throw new TransferFailedException( e.getMessage(), e ); 462 } 463 try 464 { 465 switch ( statusCode ) 466 { 467 case HttpStatus.SC_OK: 468 return true; 469 470 case HttpStatus.SC_NOT_MODIFIED: 471 return true; 472 473 case SC_NULL: 474 throw new TransferFailedException( "Failed to transfer file: " + url ); 475 476 case HttpStatus.SC_FORBIDDEN: 477 throw new AuthorizationException( "Access denied to: " + url ); 478 479 case HttpStatus.SC_UNAUTHORIZED: 480 throw new AuthorizationException( "Not authorized." ); 481 482 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: 483 throw new AuthorizationException( "Not authorized by proxy." ); 484 485 case HttpStatus.SC_NOT_FOUND: 486 return false; 487 488 //add more entries here 489 default: 490 throw new TransferFailedException( 491 "Failed to transfer file: " + url + ". Return code is: " + statusCode ); 492 } 493 } 494 finally 495 { 496 headMethod.releaseConnection(); 497 } 498 } 499 500 protected int execute( HttpMethod httpMethod ) 501 throws IOException 502 { 503 int statusCode; 504 505 setParameters( httpMethod ); 506 setHeaders( httpMethod ); 507 508 statusCode = client.executeMethod( httpMethod ); 509 return statusCode; 510 } 511 512 protected void setParameters( HttpMethod method ) 513 { 514 HttpMethodConfiguration config = 515 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method ); 516 if ( config != null ) 517 { 518 HttpMethodParams params = config.asMethodParams( method.getParams() ); 519 if ( params != null ) 520 { 521 method.setParams( params ); 522 } 523 } 524 525 if ( config == null || config.getConnectionTimeout() == HttpMethodConfiguration.DEFAULT_CONNECTION_TIMEOUT ) 526 { 527 method.getParams().setSoTimeout( getTimeout() ); 528 } 529 } 530 531 protected void setHeaders( HttpMethod method ) 532 { 533 HttpMethodConfiguration config = 534 httpConfiguration == null ? null : httpConfiguration.getMethodConfiguration( method ); 535 if ( config == null || config.isUseDefaultHeaders() ) 536 { 537 // TODO: merge with the other headers and have some better defaults, unify with lightweight headers 538 method.addRequestHeader( "Cache-control", "no-cache" ); 539 method.addRequestHeader( "Cache-store", "no-store" ); 540 method.addRequestHeader( "Pragma", "no-cache" ); 541 method.addRequestHeader( "Expires", "0" ); 542 method.addRequestHeader( "Accept-Encoding", "gzip" ); 543 } 544 545 if ( httpHeaders != null ) 546 { 547 for ( Object header : httpHeaders.keySet() ) 548 { 549 method.addRequestHeader( (String) header, httpHeaders.getProperty( (String) header ) ); 550 } 551 } 552 553 Header[] headers = config == null ? null : config.asRequestHeaders(); 554 if ( headers != null ) 555 { 556 for ( Header header : headers ) 557 { 558 method.addRequestHeader( header ); 559 } 560 } 561 } 562 563 /** 564 * getUrl 565 * Implementors can override this to remove unwanted parts of the url such as role-hints 566 * 567 * @param repository 568 * @return 569 */ 570 protected String getURL( Repository repository ) 571 { 572 return repository.getUrl(); 573 } 574 575 protected HttpClient getClient() 576 { 577 return client; 578 } 579 580 public void setConnectionManager( HttpConnectionManager connectionManager ) 581 { 582 this.connectionManager = connectionManager; 583 } 584 585 public Properties getHttpHeaders() 586 { 587 return httpHeaders; 588 } 589 590 public void setHttpHeaders( Properties httpHeaders ) 591 { 592 this.httpHeaders = httpHeaders; 593 } 594 595 public HttpConfiguration getHttpConfiguration() 596 { 597 return httpConfiguration; 598 } 599 600 public void setHttpConfiguration( HttpConfiguration httpConfiguration ) 601 { 602 this.httpConfiguration = httpConfiguration; 603 } 604 605 public void fillInputData( InputData inputData ) 606 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 607 { 608 Resource resource = inputData.getResource(); 609 610 StringBuilder url = new StringBuilder( getRepository().getUrl() ); 611 if ( !url.toString().endsWith( "/" ) ) 612 { 613 url.append( '/' ); 614 } 615 url.append( resource.getName() ); 616 617 getMethod = new GetMethod( url.toString() ); 618 619 long timestamp = resource.getLastModified(); 620 if ( timestamp > 0 ) 621 { 622 SimpleDateFormat fmt = new SimpleDateFormat( "EEE, dd-MMM-yy HH:mm:ss zzz", Locale.US ); 623 fmt.setTimeZone( GMT_TIME_ZONE ); 624 Header hdr = new Header( "If-Modified-Since", fmt.format( new Date( timestamp ) ) ); 625 fireTransferDebug( "sending ==> " + hdr + "(" + timestamp + ")" ); 626 getMethod.addRequestHeader( hdr ); 627 } 628 629 int statusCode; 630 try 631 { 632 statusCode = execute( getMethod ); 633 } 634 catch ( IOException e ) 635 { 636 fireTransferError( resource, e, TransferEvent.REQUEST_GET ); 637 638 throw new TransferFailedException( e.getMessage(), e ); 639 } 640 641 fireTransferDebug( url + " - Status code: " + statusCode ); 642 643 // TODO [BP]: according to httpclient docs, really should swallow the output on error. verify if that is 644 // required 645 switch ( statusCode ) 646 { 647 case HttpStatus.SC_OK: 648 break; 649 650 case HttpStatus.SC_NOT_MODIFIED: 651 // return, leaving last modified set to original value so getIfNewer should return unmodified 652 return; 653 654 case SC_NULL: 655 { 656 TransferFailedException e = new TransferFailedException( "Failed to transfer file: " + url ); 657 fireTransferError( resource, e, TransferEvent.REQUEST_GET ); 658 throw e; 659 } 660 661 case HttpStatus.SC_FORBIDDEN: 662 fireSessionConnectionRefused(); 663 throw new AuthorizationException( "Access denied to: " + url ); 664 665 case HttpStatus.SC_UNAUTHORIZED: 666 fireSessionConnectionRefused(); 667 throw new AuthorizationException( "Not authorized." ); 668 669 case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED: 670 fireSessionConnectionRefused(); 671 throw new AuthorizationException( "Not authorized by proxy." ); 672 673 case HttpStatus.SC_NOT_FOUND: 674 throw new ResourceDoesNotExistException( "File: " + url + " does not exist" ); 675 676 // add more entries here 677 default: 678 { 679 cleanupGetTransfer( resource ); 680 TransferFailedException e = new TransferFailedException( 681 "Failed to transfer file: " + url + ". Return code is: " + statusCode ); 682 fireTransferError( resource, e, TransferEvent.REQUEST_GET ); 683 throw e; 684 } 685 } 686 687 InputStream is = null; 688 689 Header contentLengthHeader = getMethod.getResponseHeader( "Content-Length" ); 690 691 if ( contentLengthHeader != null ) 692 { 693 try 694 { 695 long contentLength = Integer.valueOf( contentLengthHeader.getValue() ).intValue(); 696 697 resource.setContentLength( contentLength ); 698 } 699 catch ( NumberFormatException e ) 700 { 701 fireTransferDebug( 702 "error parsing content length header '" + contentLengthHeader.getValue() + "' " + e ); 703 } 704 } 705 706 Header lastModifiedHeader = getMethod.getResponseHeader( "Last-Modified" ); 707 708 long lastModified = 0; 709 710 if ( lastModifiedHeader != null ) 711 { 712 try 713 { 714 lastModified = DateUtil.parseDate( lastModifiedHeader.getValue() ).getTime(); 715 716 resource.setLastModified( lastModified ); 717 } 718 catch ( DateParseException e ) 719 { 720 fireTransferDebug( "Unable to parse last modified header" ); 721 } 722 723 fireTransferDebug( "last-modified = " + lastModifiedHeader.getValue() + " (" + lastModified + ")" ); 724 } 725 726 Header contentEncoding = getMethod.getResponseHeader( "Content-Encoding" ); 727 boolean isGZipped = contentEncoding != null && "gzip".equalsIgnoreCase( contentEncoding.getValue() ); 728 729 try 730 { 731 is = getMethod.getResponseBodyAsStream(); 732 if ( isGZipped ) 733 { 734 is = new GZIPInputStream( is ); 735 } 736 } 737 catch ( IOException e ) 738 { 739 fireTransferError( resource, e, TransferEvent.REQUEST_GET ); 740 741 String msg = 742 "Error occurred while retrieving from remote repository:" + getRepository() + ": " + e.getMessage(); 743 744 throw new TransferFailedException( msg, e ); 745 } 746 747 inputData.setInputStream( is ); 748 } 749 750 protected void cleanupGetTransfer( Resource resource ) 751 { 752 if ( getMethod != null ) 753 { 754 getMethod.releaseConnection(); 755 } 756 } 757 758 @Override 759 public void putFromStream( InputStream stream, String destination ) 760 throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException 761 { 762 putFromStream( stream, destination, -1, -1 ); 763 } 764 765 @Override 766 protected void putFromStream( InputStream stream, Resource resource ) 767 throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException 768 { 769 putFromStream( stream, resource.getName(), -1, -1 ); 770 } 771 772 @Override 773 public void fillOutputData( OutputData outputData ) 774 throws TransferFailedException 775 { 776 // no needed in this implementation but throw an Exception if used 777 throw new IllegalStateException( "this wagon http client must not use fillOutputData" ); 778 } 779}