/**
* 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);
}
}