/** * Copyright (c) 2012 to original author or authors * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package io.takari.aether.wagon; import io.takari.aether.client.AetherClient; import io.takari.aether.client.AetherClientAuthentication; import io.takari.aether.client.AetherClientConfig; import io.takari.aether.client.AetherClientProxy; import io.takari.aether.client.Response; import io.takari.aether.client.RetryableSource; import io.takari.aether.okhttp.OkHttpAetherClient; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Properties; import javax.inject.Named; import org.apache.maven.wagon.ConnectionException; import org.apache.maven.wagon.InputData; import org.apache.maven.wagon.OutputData; import org.apache.maven.wagon.ResourceDoesNotExistException; import org.apache.maven.wagon.StreamWagon; import org.apache.maven.wagon.TransferFailedException; import org.apache.maven.wagon.authentication.AuthenticationException; import org.apache.maven.wagon.authorization.AuthorizationException; import org.apache.maven.wagon.events.TransferEvent; import org.apache.maven.wagon.resource.Resource; import org.eclipse.aether.ConfigurationProperties; import com.google.common.io.Closer; @Named("http") public class OkHttpWagon extends StreamWagon { private Map<String,String> httpHeaders; private AetherClient client; @Override public void fillInputData(InputData inputData) throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { Resource resource = inputData.getResource(); String url = buildUrl(resource.getName()); try { Response response = client.get(url); final int statusCode = response.getStatusCode(); switch (statusCode) { case HttpURLConnection.HTTP_OK: break; case HttpURLConnection.HTTP_FORBIDDEN: throw new AuthorizationException("Access denied to: " + url); case HttpURLConnection.HTTP_NOT_FOUND: throw new ResourceDoesNotExistException("Resource not found: " + url); case HttpURLConnection.HTTP_UNAUTHORIZED: throw new AuthorizationException("Unauthorized to access: " + url); case HttpURLConnection.HTTP_PROXY_AUTH: throw new AuthorizationException("Proxy authentication required to access: " + url); default: throw new TransferFailedException("Failed to transfer file: " + url + ". Return code is: " + statusCode); } inputData.setInputStream(response.getInputStream()); } catch (IOException e) { StringBuilder message = new StringBuilder("Error transferring file: "); message.append(e.getMessage()); message.append(" from " + url); if (getProxyInfo() != null && getProxyInfo().getHost() != null) { message.append(" with proxyInfo ").append(getProxyInfo().toString()); } throw new TransferFailedException(message.toString(), e); } } @Override public void fillOutputData(OutputData outputData) throws TransferFailedException { throw new UnsupportedOperationException(); } @Override protected OutputStream getOutputStream(Resource resource) throws TransferFailedException { throw new UnsupportedOperationException(); } abstract class RetryableResource implements RetryableSource { private final Resource resource; protected RetryableResource(Resource resource) { this.resource = resource; } protected void copy(InputStream is, OutputStream os) throws IOException { TransferEvent transferEvent = new TransferEvent(OkHttpWagon.this, resource, TransferEvent.TRANSFER_PROGRESS, TransferEvent.REQUEST_PUT); transferEvent.setTimestamp(System.currentTimeMillis()); Closer closer = Closer.create(); try { closer.register(is); closer.register(os); int n = 0; final byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; while (-1 != (n = is.read(buffer))) { write(os, n, buffer); fireTransferProgress(transferEvent, buffer, n); } } finally { closer.close(); } } protected void write(OutputStream os, int n, final byte[] buffer) throws IOException { os.write(buffer, 0, n); } } class FileSource extends RetryableResource { private final File file; public FileSource(Resource resource, File file) { super( resource); this.file = file; } @Override public void copyTo(OutputStream os) throws IOException { copy(new FileInputStream(file), os); } @Override public long length() { return file.length(); } } class InputStreamSource extends RetryableResource { private final InputStream is; public InputStreamSource(Resource resource, InputStream is) { super(resource); this.is = is; } @Override public void copyTo(OutputStream os) throws IOException { copy(is, os); } @Override protected void write(OutputStream os, int n, byte[] buffer) throws IOException { super.write(os, n, buffer); } @Override public long length() { return -1; // unknown } } @Override public void put(File file, String resourceName) throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException { Resource resource = new Resource(resourceName); firePutInitiated(resource, file); resource.setContentLength(file.length()); resource.setLastModified(file.lastModified()); RetryableSource source = new FileSource(resource, file); put(source, file, resource); } @Override protected void putFromStream(InputStream stream, Resource resource) throws TransferFailedException, AuthorizationException, ResourceDoesNotExistException { put(new InputStreamSource(resource, stream), null, resource); } private void put(RetryableSource source, File file, Resource resource) throws TransferFailedException { firePutStarted( resource, file ); String url = buildUrl(resource.getName()); try { Response response = client.put(url, source); // TODO do I need to worry about response status? } catch (FileNotFoundException e) { fireTransferError(resource, e, TransferEvent.REQUEST_PUT); throw new TransferFailedException("Specified source file does not exist: " + source, e); } catch (IOException e) { fireTransferError(resource, e, TransferEvent.REQUEST_PUT); String msg = "PUT request to: " + resource.getName() + " in " + repository.getName() + " failed"; throw new TransferFailedException(msg, e); } firePutCompleted( resource, file ); } @Override public void closeConnection() throws ConnectionException { if (client != null) { try { client.close(); } catch (IOException e) { throw new ConnectionException(e.getMessage(), e); } } } @Override protected void openConnectionInternal() throws ConnectionException, AuthenticationException { AetherClientConfig config = new AetherClientConfig(); config.setUserAgent("Maven-Wagon/1.0"); // headers if (httpHeaders != null) { config.setHeaders(httpHeaders); } if (getProxyInfo() != null) { AetherClientProxy proxy = new AetherClientProxy(); proxy.setHost(getProxyInfo().getHost()); proxy.setPort(getProxyInfo().getPort()); // // Proxy authorization // if (getProxyInfo().getUserName() != null && getProxyInfo().getPassword() != null) { String username = getProxyInfo().getUserName(); String password = getProxyInfo().getPassword(); proxy.setAuthentication(new AetherClientAuthentication(username, password)); } config.setProxy(proxy); } // // Authorization // if (getAuthenticationInfo() != null) { String username = getAuthenticationInfo().getUserName(); String password = getAuthenticationInfo().getPassword(); AetherClientAuthentication authentication = new AetherClientAuthentication(username, password); config.setAuthentication(authentication); } config.setConnectionTimeout(ConfigurationProperties.DEFAULT_CONNECT_TIMEOUT); config.setRequestTimeout(ConfigurationProperties.DEFAULT_REQUEST_TIMEOUT); client = new OkHttpAetherClient(config); } @Override public boolean resourceExists(String resourceName) throws TransferFailedException, AuthorizationException { String url = buildUrl(resourceName); try { Response response = client.head(url); final int statusCode = response.getStatusCode(); switch (statusCode) { case HttpURLConnection.HTTP_OK: return true; case HttpURLConnection.HTTP_FORBIDDEN: throw new AuthorizationException("Access denied to: " + url); case HttpURLConnection.HTTP_NOT_FOUND: return false; case HttpURLConnection.HTTP_UNAUTHORIZED: throw new AuthorizationException("Unauthorized to access: " + url); case HttpURLConnection.HTTP_PROXY_AUTH: throw new AuthorizationException("Proxy authentication required to access: " + url); default: throw new TransferFailedException("Failed to look for file: " + url + ". Return code is: " + statusCode); } } catch (IOException e) { throw new TransferFailedException("Error transferring file: " + e.getMessage(), e); } } private String buildUrl(String path) { String repoUrl = getRepository().getUrl(); path = path.replace(' ', '+'); if (repoUrl.charAt(repoUrl.length() - 1) != '/') { return repoUrl + '/' + path; } return repoUrl + path; } void setSystemProperty(String key, String value) { if (value != null) { System.setProperty(key, value); } else { System.getProperties().remove(key); } } public void setHttpHeaders(Properties httpHeaders) { Map<String,String> map = new HashMap<String, String>(); for (Map.Entry<Object,Object> header : httpHeaders.entrySet()) { if (header.getKey() instanceof String && header.getValue() instanceof String) { map.put((String) header.getKey(), (String) header.getValue()); } } this.httpHeaders = Collections.unmodifiableMap(map); } }