/**
* Copyright (C) 2008 Abiquo Holdings S.L.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.abiquo.apiclient;
import static com.abiquo.apiclient.domain.PageIterator.flatten;
import static com.abiquo.apiclient.domain.options.BaseOptions.urlEncode;
import static com.abiquo.apiclient.util.LogUtils.logRequest;
import static com.abiquo.apiclient.util.LogUtils.logResponse;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Maps.transformValues;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
import com.abiquo.apiclient.ApiClient.SSLConfiguration;
import com.abiquo.apiclient.auth.Authentication;
import com.abiquo.apiclient.domain.exception.AbiquoException;
import com.abiquo.apiclient.domain.exception.AuthorizationException;
import com.abiquo.apiclient.domain.exception.HttpException;
import com.abiquo.apiclient.interceptors.AuthenticationInterceptor;
import com.abiquo.apiclient.json.Json;
import com.abiquo.model.rest.RESTLink;
import com.abiquo.model.transport.AcceptedRequestDto;
import com.abiquo.model.transport.SingleResourceTransportDto;
import com.abiquo.model.transport.WrapperDto;
import com.abiquo.model.transport.error.ErrorsDto;
import com.abiquo.server.core.cloud.VirtualApplianceDto;
import com.abiquo.server.core.cloud.VirtualApplianceState;
import com.abiquo.server.core.cloud.VirtualMachineDto;
import com.abiquo.server.core.cloud.VirtualMachineState;
import com.abiquo.server.core.task.TaskDto;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.net.HttpHeaders;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.Uninterruptibles;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.MultipartBuilder;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
public class RestClient
{
private final OkHttpClient client;
private final Json json;
private final String baseURL;
private final String apiVersion;
// Package protected. To be used only by the ApiClient
RestClient(final Authentication authentication, final String baseURL, final String apiVersion,
final SSLConfiguration sslConfiguration)
{
this.json = new Json();
this.baseURL = checkNotNull(baseURL, "baseURL cannot be null");
this.apiVersion = checkNotNull(apiVersion, "apiVersion cannot be null");
client = new OkHttpClient();
client.setReadTimeout(0, TimeUnit.MILLISECONDS);
client.networkInterceptors().add(new AuthenticationInterceptor(authentication));
if (sslConfiguration != null)
{
client.setHostnameVerifier(sslConfiguration.hostnameVerifier());
client.setSslSocketFactory(sslConfiguration.sslContext().getSocketFactory());
}
}
/**
* Changes made to the returned client will affect all the subsequent requests.
* <p>
* If you want to create a new client without affecting the existing one, use the
* {@link OkHttpClient#clone()} method on the returned client.
*/
public OkHttpClient rawClient()
{
return client;
}
/**
* Returns the {@link Json} instance used to serialize and deserialize the request and response
* bodies.
*/
public Json json()
{
return json;
}
public <T extends SingleResourceTransportDto> T edit(final T dto)
{
RESTLink link =
checkNotNull(dto.getEditLink(), "The given object does not have an edit link");
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) dto.getClass();
return put(link.getHref(), link.getType(), link.getType(), dto, clazz);
}
public void delete(final SingleResourceTransportDto dto)
{
RESTLink link =
checkNotNull(dto.getEditLink(), "The given object does not have an edit link");
delete(link.getHref());
}
public <T extends SingleResourceTransportDto> T refresh(final T dto)
{
RESTLink link = dto.getEditLink();
if (link == null)
{
link = dto.searchLink("self");
}
@SuppressWarnings("unchecked")
Class<T> clazz = (Class<T>) dto.getClass();
checkNotNull(link, "The given object does not have an edit/self link");
return get(link.getHref(), link.getType(), clazz);
}
public <T extends SingleResourceTransportDto, W extends WrapperDto<T>> Iterable<T> list(
final RESTLink link, final Class<W> clazz)
{
return flatten(this, get(link, clazz));
}
public <T extends SingleResourceTransportDto, W extends WrapperDto<T>> Iterable<T> list(
final String uri, final String accept, final Class<W> returnClass)
{
return flatten(this, get(uri, accept, returnClass));
}
public <T extends SingleResourceTransportDto, W extends WrapperDto<T>> Iterable<T> list(
final String uri, final String accept, final TypeToken<W> returnType)
{
return flatten(this, get(uri, accept, returnType));
}
public <T extends SingleResourceTransportDto, W extends WrapperDto<T>> Iterable<T> list(
final String uri, final Map<String, Object> queryParams, final String accept,
final Class<W> returnClass)
{
return flatten(this, get(uri, queryParams, accept, returnClass));
}
public <T extends SingleResourceTransportDto, W extends WrapperDto<T>> Iterable<T> list(
final String uri, final Map<String, Object> queryParams, final String accept,
final TypeToken<W> returnType)
{
return flatten(this, get(uri, queryParams, accept, returnType));
}
public <T extends SingleResourceTransportDto> T get(final RESTLink link, final Class<T> clazz)
{
return get(link.getHref(), link.getType(), clazz);
}
public <T extends SingleResourceTransportDto> T get(final String uri, final String accept,
final Class<T> returnClass)
{
try
{
Request request =
new Request.Builder().url(absolute(uri))
.addHeader(HttpHeaders.ACCEPT, withVersion(accept)).get().build();
return execute(request, returnClass);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public <T extends SingleResourceTransportDto> T get(final String uri, final String accept,
final TypeToken<T> returnType)
{
try
{
Request request =
new Request.Builder().url(absolute(uri))
.addHeader(HttpHeaders.ACCEPT, withVersion(accept)).get().build();
return execute(request, returnType);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public <T extends SingleResourceTransportDto> T get(final String uri,
final Map<String, Object> queryParams, final String accept, final Class<T> returnClass)
{
try
{
Request request =
new Request.Builder().url(absolute(uri) + "?" + queryLine(queryParams))
.addHeader(HttpHeaders.ACCEPT, withVersion(accept)).get().build();
return execute(request, returnClass);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public <T extends SingleResourceTransportDto> T get(final String uri,
final Map<String, Object> queryParams, final String accept, final TypeToken<T> returnType)
{
try
{
Request request =
new Request.Builder().url(absolute(uri) + "?" + queryLine(queryParams))
.addHeader(HttpHeaders.ACCEPT, withVersion(accept)).get().build();
return execute(request, returnType);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public void delete(final String uri)
{
try
{
Request request = new Request.Builder().url(absolute(uri)).delete().build();
execute(request, (Class< ? >) null);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public <T extends SingleResourceTransportDto> T post(final String uri, final String accept,
final String contentType, final SingleResourceTransportDto body, final Class<T> returnClass)
{
try
{
RequestBody requestBody =
RequestBody.create(MediaType.parse(withVersion(contentType)), json.write(body));
Request request =
new Request.Builder().url(absolute(uri))
.addHeader(HttpHeaders.ACCEPT, withVersion(accept)).post(requestBody).build();
return execute(request, returnClass);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public <T extends SingleResourceTransportDto> T post(final String uri, final String accept,
final String contentType, final SingleResourceTransportDto body,
final TypeToken<T> returnType)
{
try
{
RequestBody requestBody =
RequestBody.create(MediaType.parse(withVersion(contentType)), json.write(body));
Request request =
new Request.Builder().url(absolute(uri))
.addHeader(HttpHeaders.ACCEPT, withVersion(accept)).post(requestBody).build();
return execute(request, returnType);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public <T extends SingleResourceTransportDto> T post(final String uri, final String accept,
final Class<T> returnClass)
{
try
{
Request request =
new Request.Builder().url(absolute(uri))
.addHeader(HttpHeaders.ACCEPT, withVersion(accept)).post(null).build();
return execute(request, returnClass);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public <T extends SingleResourceTransportDto> T post(final String uri, final String accept,
final String contentType, final String body, final Class<T> returnClass)
{
try
{
RequestBody requestBody =
RequestBody.create(MediaType.parse(withVersion(contentType)), body);
Request request =
new Request.Builder().url(absolute(uri))
.addHeader(HttpHeaders.ACCEPT, withVersion(accept)).post(requestBody).build();
return execute(request, returnClass);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public Response multipartPost(final String url, final String bodyName, final String body,
final String fileName, final File file)
{
OkHttpClient multipartClient = client.clone();
try
{
RequestBody jsonPart =
RequestBody.create(MediaType.parse("text/plain; charset=UTF-8"), body);
RequestBody filePart =
RequestBody.create(MediaType.parse("application/octet-stream; charset=ISO-8859-1"),
file);
RequestBody requestBody =
new MultipartBuilder()
.type(MultipartBuilder.FORM)
.addPart(
Headers.of("Content-Disposition", "form-data; name=\"" + bodyName + "\""),
jsonPart) //
.addFormDataPart(fileName, file.getName(), filePart) //
.build();
Request request = new Request.Builder().url(absolute(url)).post(requestBody) //
.build();
Response response = multipartClient.newCall(request).execute();
checkResponse(request, response, response.body().string());
return response;
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public <T extends SingleResourceTransportDto> T post(final String uri, final String accept,
final TypeToken<T> returnType)
{
try
{
Request request =
new Request.Builder().url(absolute(uri))
.addHeader(HttpHeaders.ACCEPT, withVersion(accept)).post(null).build();
return execute(request, returnType);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public <T extends SingleResourceTransportDto> T put(final String uri, final String accept,
final String contentType, final SingleResourceTransportDto body, final Class<T> returnClass)
{
try
{
RequestBody requestBody =
RequestBody.create(MediaType.parse(withVersion(contentType)), json.write(body));
Request request =
new Request.Builder().url(absolute(uri))
.addHeader(HttpHeaders.ACCEPT, withVersion(accept)).put(requestBody).build();
return execute(request, returnClass);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public <T extends SingleResourceTransportDto> T put(final String uri, final String accept,
final Class<T> returnClass)
{
try
{
Request request =
new Request.Builder().url(absolute(uri))
.addHeader(HttpHeaders.ACCEPT, withVersion(accept)).put(null).build();
return execute(request, returnClass);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public <T extends SingleResourceTransportDto> T put(final String uri, final String accept,
final TypeToken<T> returnType)
{
try
{
Request request =
new Request.Builder().url(absolute(uri))
.addHeader(HttpHeaders.ACCEPT, withVersion(accept)).put(null).build();
return execute(request, returnType);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public <T extends SingleResourceTransportDto> T put(final String uri, final String accept,
final String contentType, final SingleResourceTransportDto body,
final TypeToken<T> returnType)
{
try
{
RequestBody requestBody =
RequestBody.create(MediaType.parse(withVersion(contentType)), json.write(body));
Request request =
new Request.Builder().url(absolute(uri))
.addHeader(HttpHeaders.ACCEPT, withVersion(accept)).put(requestBody).build();
return execute(request, returnType);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
public void put(final String uri, final String accept, final String contentType,
final SingleResourceTransportDto body)
{
try
{
String rawBody = json.write(body);
RequestBody requestBody =
RequestBody.create(MediaType.parse(withVersion(contentType)), rawBody);
Request request =
new Request.Builder().url(absolute(uri))
.addHeader(HttpHeaders.ACCEPT, withVersion(accept)).put(requestBody).build();
execute(request, (Class< ? >) null);
}
catch (IOException ex)
{
throw Throwables.propagate(ex);
}
}
private String absolute(final String path)
{
try
{
new URL(path);
}
catch (MalformedURLException e)
{
return baseURL + (path.startsWith("/") ? path : "/" + path);
}
return path;
}
private String withVersion(final String mediaType)
{
return mediaType.contains("version=") ? mediaType : mediaType + "; version=" + apiVersion;
}
private String queryLine(final Map<String, Object> queryParams)
{
Map<String, Object> queryParamsSorted = new TreeMap<String, Object>(queryParams);
return Joiner.on('&').withKeyValueSeparator("=")
.join(transformValues(queryParamsSorted, new Function<Object, String>()
{
@Override
public String apply(final Object input)
{
return urlEncode(input.toString());
}
}));
}
public TaskDto waitForTask(final AcceptedRequestDto< ? > acceptedRequest,
final int pollInterval, final int maxWait, final TimeUnit timeUnit)
{
RESTLink status = acceptedRequest.getStatusLink();
return waitForTask(status, pollInterval, maxWait, timeUnit);
}
public TaskDto waitForTask(final TaskDto taskDto, final int pollInterval, final int maxWait,
final TimeUnit timeUnit)
{
return waitForTask(taskDto.searchLink("self"), pollInterval, maxWait, timeUnit);
}
private TaskDto waitForTask(final RESTLink restLink, final int pollInterval, final int maxWait,
final TimeUnit timeUnit)
{
final Stopwatch watch = Stopwatch.createStarted();
while (watch.elapsed(timeUnit) < maxWait)
{
TaskDto updatedTask = get(restLink.getHref(), TaskDto.MEDIA_TYPE, TaskDto.class);
switch (updatedTask.getState())
{
case FINISHED_SUCCESSFULLY:
case FINISHED_UNSUCCESSFULLY:
case ABORTED:
case ACK_ERROR:
case CANCELLED:
return updatedTask;
case PENDING:
case QUEUEING:
case STARTED:
// Do nothing and keep waiting
break;
}
Uninterruptibles.sleepUninterruptibly(pollInterval, timeUnit);
}
throw new RuntimeException("Task did not complete in the configured timeout");
}
public VirtualMachineDto waitUntilUnlocked(final VirtualMachineDto vm, final int pollInterval,
final int maxWait, final TimeUnit timeUnit)
{
Stopwatch watch = Stopwatch.createStarted();
while (watch.elapsed(timeUnit) < maxWait)
{
VirtualMachineDto refreshed = refresh(vm);
if (!VirtualMachineState.LOCKED.equals(refreshed.getState()))
{
return refreshed;
}
Uninterruptibles.sleepUninterruptibly(pollInterval, timeUnit);
}
throw new RuntimeException("Virtual machine did not reach the desired state in the configured timeout");
}
public VirtualApplianceDto waitUntilUnlocked(final VirtualApplianceDto vapp,
final int pollInterval, final int maxWait, final TimeUnit timeUnit)
{
Stopwatch watch = Stopwatch.createStarted();
while (watch.elapsed(timeUnit) < maxWait)
{
VirtualApplianceDto refreshed = refresh(vapp);
if (!VirtualApplianceState.LOCKED.equals(refreshed.getState()))
{
return refreshed;
}
Uninterruptibles.sleepUninterruptibly(pollInterval, timeUnit);
}
throw new RuntimeException("Virtual appliance did not reach the desired state in the configured timeout");
}
private <T> T execute(final Request request, final Class<T> resultClass) throws IOException
{
logRequest(request);
Response response = client.newCall(request).execute();
String responseBody = response.body().string();
logResponse(response, responseBody);
checkResponse(request, response, responseBody);
return !Strings.isNullOrEmpty(responseBody) && resultClass != null ? json.read(
responseBody, resultClass) : null;
}
private <T> T execute(final Request request, final TypeToken<T> returnType) throws IOException
{
logRequest(request);
Response response = client.newCall(request).execute();
String responseBody = response.body().string();
logResponse(response, responseBody);
checkResponse(request, response, responseBody);
return !Strings.isNullOrEmpty(responseBody) && returnType != null ? json.read(responseBody,
returnType) : null;
}
private void checkResponse(final Request request, final Response response,
final String responseBody) throws IOException
{
int responseCode = response.code();
if (responseCode == 401 || responseCode == 403)
{
throw new AuthorizationException(responseCode, response.message());
}
else if (responseCode >= 400)
{
if (responseBody == null)
{
throw new HttpException(responseCode, response.message());
}
try
{
ErrorsDto errors = json.read(responseBody, ErrorsDto.class);
throw new AbiquoException(responseCode, errors);
}
catch (Exception ex)
{
Throwables.propagateIfInstanceOf(ex, AbiquoException.class);
throw new HttpException(responseCode, response.message() + ". Body: "
+ responseBody);
}
}
}
}