/*
* (c) Copyright Reserved EVRYTHNG Limited 2016. All rights reserved.
* Use of this material is subject to license.
* Copying and unauthorised use of this material strictly prohibited.
*/
package com.evrythng.java.wrapper.core.api;
import com.evrythng.java.wrapper.core.http.Status;
import com.evrythng.java.wrapper.exception.BadRequestException;
import com.evrythng.java.wrapper.exception.ConflictException;
import com.evrythng.java.wrapper.exception.EvrythngClientException;
import com.evrythng.java.wrapper.exception.EvrythngException;
import com.evrythng.java.wrapper.exception.EvrythngUnexpectedException;
import com.evrythng.java.wrapper.exception.ForbiddenException;
import com.evrythng.java.wrapper.exception.InternalErrorException;
import com.evrythng.java.wrapper.exception.MethodNotAllowedException;
import com.evrythng.java.wrapper.exception.NotFoundException;
import com.evrythng.java.wrapper.exception.RequestEntityTooLargeException;
import com.evrythng.java.wrapper.exception.UnauthorizedException;
import com.evrythng.java.wrapper.util.JSONUtils;
import com.evrythng.thng.resource.model.exception.ErrorMessage;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.InputStream;
/**
* Class that contains static utility methods.
*
* @author Michel Yerly (my)
*/
public class Utils {
private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class);
private Utils() {
throw new IllegalStateException("This class is static.");
}
/**
* Converts http response into entity
*
* @param response {@link HttpResponse} instance
* @param type {@link TypeReference} instance
* @param <K> entity type
* @return entity
*/
@SuppressWarnings("unchecked")
public static <K> K convert(final HttpResponse response, final TypeReference<K> type) throws EvrythngException {
K result;
LOGGER.debug("Performing conversion: [type={}]", type.getType());
if (type.getType().equals(Void.class)) {
return null;
}
if (type.getType().equals(InputStream.class)) {
try {
// Retrieve response content stream and buffer data as connection may be closed before input is handled:
result = (K) IOUtils.toBufferedInputStream(entityStream(response));
} catch (Exception e) {
throw new EvrythngClientException(String.format("Unable to retrieve response content stream: [type=%s, cause=%s]", type.getType(), e.getMessage()), e);
}
} else {
if (type.getType().equals(HttpResponse.class)) {
// We already have a HttpResponse, let's return it:
result = (K) response;
} else {
// Retrieve response entity (as String so that it can be outputted in case of exception):
String entity = entityString(response);
if (type.getType().equals(String.class)) {
result = (K) entity;
} else {
try {
result = JSONUtils.read(entity, type);
} catch (Exception e) {
throw new EvrythngClientException(String.format("Unable to map response entity: [type=%s, entity=%s, cause=%s]", type.getType(), entity, e.getMessage()), e);
}
}
}
}
return result;
}
/**
* Reads entity content stream from the provided {@link HttpResponse}.
*
* @param response {@link HttpResponse} instance
* @return the {@link HttpResponse} entity content as {@link InputStream}
*/
private static InputStream entityStream(final HttpResponse response) throws EvrythngClientException {
LOGGER.debug("Reading response content stream...");
InputStream result;
try {
HttpEntity entity = response.getEntity();
result = entity.getContent();
} catch (Exception e) {
// Convert to custom exception:
throw new EvrythngClientException("Error while reading response content stream!", e);
}
return result;
}
/**
* Reads entity content from the provided {@link HttpResponse}.
*
* @param response {@link HttpResponse} instance
* @return the {@link HttpResponse} entity content as {@link String}
*/
private static String entityString(final HttpResponse response) throws EvrythngClientException {
LOGGER.debug("Reading response entity...");
String result;
try {
result = IOUtils.toString(entityStream(response));
} catch (Exception e) {
// Convert to custom exception:
throw new EvrythngClientException(String.format("Error while reading response entity! [type=%s]", String.class), e);
}
return result;
}
/**
* Asserts {@code expected} {@link Status} against the provided
* {@link HttpResponse}. If {@code actual} response {@link Status} does not
* match {@code expected} one, then response entity
* will be mapped to an {@link ErrorMessage} instance and an exception will
* be thrown.
*
* @param response the {@link HttpResponse} holding a valid status code
* @param expected the expected response status code
* @throws EvrythngException if provided {@code response} {@link Status} does not match
* {@code expected} one
*/
public static void assertStatus(final HttpResponse response, final Status expected) throws EvrythngException {
Status actual = Status.fromStatusCode(response.getStatusLine().getStatusCode());
if (actual == null) {
throw new EvrythngUnexpectedException(new ErrorMessage(Status.INTERNAL_SERVER_ERROR.getStatusCode(), "Unknown status code " + response.getStatusLine().getStatusCode()));
}
LOGGER.debug("Checking response status: [expected={}, actual={}]", expected.getStatusCode(), actual.getStatusCode());
if (actual != expected) {
LOGGER.debug("Unexpected response status!");
// Map entity to ErrorMessage:
String entity = entityString(response);
ErrorMessage message;
try {
LOGGER.debug("Mapping response to ErrorMessage: [entity={}]", entity);
// API should always return an ErrorMessage as JSON:
message = JSONUtils.read(entity, new TypeReference<ErrorMessage>() {
});
} catch (Exception e) {
StringBuilder builder = new StringBuilder();
builder.append("Unable to retrieve ErrorMessage from response! ");
builder.append(response.getStatusLine().getStatusCode());
builder.append(": ");
builder.append(response.getStatusLine().getReasonPhrase());
builder.append("; Entity String: [");
builder.append(entity);
builder.append("]");
throw new EvrythngClientException(builder.toString(), e);
}
// Handle unexpected status:
switch (actual.getFamily()) {
case CLIENT_ERROR:
switch (actual) {
case BAD_REQUEST:
throw new BadRequestException(message);
case UNAUTHORIZED:
throw new UnauthorizedException(message);
case FORBIDDEN:
throw new ForbiddenException(message);
case NOT_FOUND:
throw new NotFoundException(message);
case METHOD_NOT_ALLOWED:
throw new MethodNotAllowedException(message);
case CONFLICT:
throw new ConflictException(message);
case REQUEST_ENTITY_TOO_LARGE:
throw new RequestEntityTooLargeException(message);
default:
throw new EvrythngUnexpectedException(message);
}
case SERVER_ERROR:
throw new InternalErrorException(message);
default:
throw new EvrythngUnexpectedException(message);
}
}
}
}