/*
* (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;
import com.evrythng.api.wrapper.param.FilterQueryParamValue;
import com.evrythng.api.wrapper.param.IdsQueryParamValue;
import com.evrythng.commons.domain.SortOrder;
import com.evrythng.java.wrapper.core.api.AcceptedResourceResponse;
import com.evrythng.java.wrapper.core.api.ApiCommand;
import com.evrythng.java.wrapper.core.api.ApiCommandBuilder;
import com.evrythng.java.wrapper.core.api.QueryParamValue;
import com.evrythng.java.wrapper.core.api.TypedResponseWithEntity;
import com.evrythng.java.wrapper.core.api.UncheckedApiCommandBuilder;
import com.evrythng.java.wrapper.core.api.Utils;
import com.evrythng.java.wrapper.core.api.param.CallbackQueryParamValue;
import com.evrythng.java.wrapper.core.api.param.FromQueryParamValue;
import com.evrythng.java.wrapper.core.api.param.PageQueryParamValue;
import com.evrythng.java.wrapper.core.api.param.PerPageQueryParamValue;
import com.evrythng.java.wrapper.core.api.param.ProjectQueryParamValue;
import com.evrythng.java.wrapper.core.api.param.QSearchQueryParamValue;
import com.evrythng.java.wrapper.core.api.param.ScopeQueryParamValue;
import com.evrythng.java.wrapper.core.api.param.SortOrderQueryParamValue;
import com.evrythng.java.wrapper.core.api.param.ToQueryParamValue;
import com.evrythng.java.wrapper.core.api.param.UserScopeQueryParamValue;
import com.evrythng.java.wrapper.core.api.param.WithScopesQueryParamValue;
import com.evrythng.java.wrapper.core.http.HttpMethodBuilder;
import com.evrythng.java.wrapper.core.http.HttpMethodBuilder.Method;
import com.evrythng.java.wrapper.core.http.HttpMethodBuilder.MethodBuilder;
import com.evrythng.java.wrapper.core.http.Status;
import com.evrythng.java.wrapper.exception.EvrythngClientException;
import com.evrythng.java.wrapper.exception.EvrythngException;
import com.evrythng.thng.commons.config.ApiConfiguration;
import com.evrythng.thng.commons.config.ApiConfiguration.QueryKeyword;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.AbstractIterator;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.net.URLCodec;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.pcollections.PVector;
import org.pcollections.TreePVector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.InputStream;
import java.net.URI;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Entry-point command builder for the EVRYTHNG API.
*
* @author Pedro De Almeida (almeidap)
*/
public final class EvrythngApiBuilder {
private static final Logger logger = LoggerFactory.getLogger(EvrythngApiBuilder.class);
/**
* Protected constructor, use one of the static methods to obtain an
* instance.
*/
private EvrythngApiBuilder() {
}
/**
* Creates a {@link CheckedBuilder} for executing a {@code POST} request.
*
* @param apiKey the authorization token for accessing the EVRYTHNG API
* @param uri the {@link URI} holding the absolute URL
* @param data the content data that will be associated with the POST request
* @param responseStatus the expected {@link HttpResponse} status
* @param responseType the native type to which the {@link HttpResponse} will be
* mapped to
*
* @return an EVRYTHNG API-ready {@link CheckedBuilder}
*/
public static <T> Builder<T> post(final String apiKey, final URI uri, final Object data, final Status responseStatus, final TypeReference<T> responseType) {
return new CheckedBuilder<>(apiKey, HttpMethodBuilder.httpPost(data), uri, responseStatus, responseType);
}
/**
* Creates a {@link CheckedBuilder} for executing a file upload via a {@code POST}
* request.
*
* @param apiKey the authorization token for accessing the EVRYTHNG API
* @param uri the {@link URI} holding the absolute URL
* @param file file
* @param responseStatus the expected response {@link Status}
* @param responseType the native type to which the {@link HttpResponse} will be
* mapped to
*
* @return an EVRYTHNG API-ready {@link CheckedBuilder}
*/
public static <T> Builder<T> postMultipart(final String apiKey, final URI uri, final File file, final Status responseStatus, final TypeReference<T> responseType) {
return new CheckedBuilder<>(apiKey, HttpMethodBuilder.httpPostMultipart(file), uri, responseStatus, responseType, null);
}
/**
* Creates a {@link CheckedBuilder} for executing a file upload via a {@code PUT}
* request.
*
* @param apiKey the authorization token for accessing the EVRYTHNG API
* @param uri the {@link URI} holding the absolute URL
* @param file file
* @param responseStatus the expected response {@link Status}
* @param responseType the native type to which the {@link HttpResponse} will be
* mapped to
*
* @return an EVRYTHNG API-ready {@link CheckedBuilder}
*/
public static <T> Builder<T> putMultipart(final String apiKey, final URI uri, final File file, final Status responseStatus, final TypeReference<T> responseType) {
return new CheckedBuilder<>(apiKey, HttpMethodBuilder.httpPutMultipart(file), uri, responseStatus, responseType, null);
}
public static Builder<AcceptedResourceResponse> postAsynchronously(final String apiKey, final URI uri, final Object data, final Pattern extractor) {
return new CheckedBuilder<AcceptedResourceResponse>(apiKey, HttpMethodBuilder.httpPost(data), uri, Status.ACCEPTED, new TypeReference<AcceptedResourceResponse>() {}) {
@Override
public AcceptedResourceResponse execute() throws EvrythngException {
// Perform request (response status code will be automatically checked):
HttpResponse response = request();
String location = null;
Header header = response.getFirstHeader("location");
String id = null;
if (header != null) {
location = header.getValue();
if (location != null) {
Matcher match = extractor.matcher(location);
if (match.matches() && match.groupCount() > 0) {
id = match.group(1);
}
}
}
return new AcceptedResourceResponse(id, location);
}
};
}
/**
* Creates a {@link Builder} for executing a {@code GET} request.
*
* @param apiKey the authorization token for accessing the EVRYTHNG API
* @param uri the {@link URI} holding the absolute URL
* @param responseStatus the expected {@link HttpResponse} status
* @param returnType the native type to which the {@link HttpResponse} will be
* mapped to
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
public static <T> Builder<T> get(final String apiKey, final URI uri, final Status responseStatus, final TypeReference<T> returnType) {
return new CheckedBuilder<>(apiKey, HttpMethodBuilder.httpGet(), uri, responseStatus, returnType);
}
static <T> IteratorBuilder<T> iterate(final String apiKey, final URI uri, final Status responseStatus, final TypeReference<List<T>> pageType) {
return new IteratorBuilder<>(apiKey, HttpMethodBuilder.httpGet(), uri, responseStatus, pageType);
}
private static class IteratorBuilder<T> extends UncheckedBuilder<Iterator<PVector<T>>> {
private static final URLCodec CODEC = new URLCodec();
private String apiKey;
private final Status responseStatus;
private final TypeReference<List<T>> pageType;
private IteratorBuilder(final String apiKey, final MethodBuilder<?> methodBuilder, final URI uri, final Status responseStatus, final TypeReference<List<T>> pageType) {
super(apiKey, methodBuilder, uri, responseStatus, pageType);
this.apiKey = apiKey;
this.responseStatus = responseStatus;
this.pageType = pageType;
}
@Override
public UncheckedBuilder<Iterator<PVector<T>>> apiKey(final String apiKey) {
super.apiKey(apiKey);
this.apiKey = apiKey;
return this;
}
@Override
public final Iterator<PVector<T>> execute() throws EvrythngException {
return new RemotePagingIterator();
}
private final class RemotePagingIterator extends AbstractIterator<PVector<T>> {
private URI nextPageURI;
private RemotePagingIterator() {
this.nextPageURI = getCommand().uri();
}
@SuppressWarnings("ReturnOfCollectionOrArrayField")
@Override
protected final PVector<T> computeNext() {
if (nextPageURI == null) {
return endOfData();
}
TypedResponseWithEntity<List<T>> response = EvrythngApiBuilder.get(apiKey, nextPageURI, responseStatus, pageType).executeWithResponse();
nextPageURI = nextPageURI(response);
return response.entity() != null ? TreePVector.from(response.entity()) : TreePVector.<T>empty();
}
private URI nextPageURI(final TypedResponseWithEntity<List<T>> response) {
String nextPageLink = null;
Header link = response.response().getFirstHeader("Link");
if (link != null) {
for (HeaderElement linkValue : link.getElements()) {
NameValuePair rel = linkValue.getParameterByName("rel");
if (rel != null && "next".equals(rel.getValue())) {
nextPageLink = linkValue.getName();
nextPageLink = nextPageLink.substring(1, nextPageLink.length() - 1);
}
}
}
return nextPageLink != null ? toURI(nextPageLink) : null;
}
private URI toURI(final String unEncodedRawURI) {
try {
return URI.create(CODEC.decode(unEncodedRawURI));
} catch (DecoderException e) {
throw new IllegalStateException(e);
}
}
}
}
/**
* Creates a {@link Builder} for executing a {@code PUT} request.
*
* @param apiKey the authorization token for accessing the EVRYTHNG API
* @param uri the {@link URI} holding the absolute URL
* @param data the content data that will be associated with the POST request
* @param responseStatus the expected {@link HttpResponse} status
* @param returnType the native type to which the {@link HttpResponse} will be
* mapped to
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
public static <T> Builder<T> put(final String apiKey, final URI uri, final Object data, final Status responseStatus, final TypeReference<T> returnType) {
return new CheckedBuilder<>(apiKey, HttpMethodBuilder.httpPut(data), uri, responseStatus, returnType);
}
/**
* Create a {@link Builder} for executing a {@code PUT} request, expecting
* no result payload. But a
* {@link ApiConfiguration#HTTP_HEADER_RESULT_COUNT} header which
* contains the amount of document updated.
*
* @param apiKey the authorization token for accessing the EVRYTHNG API
* @param uri the {@link URI} holding the absolute URL
* @param data the content data that will be associated with the PUT request
* @param responseStatus the expected {@link HttpResponse} status. More likely 204 No
* Content
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
public static Builder<Long> putMultiple(final String apiKey, final URI uri, final Object data, final Status responseStatus) {
return new CheckedBuilder<Long>(apiKey, HttpMethodBuilder.httpPut(data), uri, responseStatus, new TypeReference<Long>() {}) {
@Override
public Long execute() throws EvrythngException {
// Perform request (response status code will be automatically checked):
HttpResponse response = request();
Header header = response.getFirstHeader(ApiConfiguration.HTTP_HEADER_RESULT_COUNT);
Long result = null;
if (header != null) {
try {
result = Long.parseLong(header.getValue());
} catch (NumberFormatException ex) {
logger.warn("Invalid numeric value in header {} : {}", ApiConfiguration.HTTP_HEADER_RESULT_COUNT, header.getValue());
}
}
return result;
}
};
}
/**
* Creates a {@link Builder} for executing a {@code DELETE} request.
*
* @param apiKey the authorization token for accessing the EVRYTHNG API
* @param uri the {@link URI} holding the absolute URL
* @param responseStatus the expected {@link HttpResponse} status
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
public static Builder<Boolean> delete(final String apiKey, final URI uri, final Status responseStatus) {
return new CheckedBuilder<Boolean>(apiKey, HttpMethodBuilder.httpDelete(), uri, responseStatus, new TypeReference<Boolean>() {}) {
/**
* {@code true} if the request has been successfully executed (i.e.
* returned response status code equals {@link Status#OK}),
* {@code false} otherwise
*/
@Override
public Boolean execute() throws EvrythngException {
// Perform request (response status code will be automatically checked):
return request() != null;
}
};
}
/**
* Creates a {@link Builder} for executing a bulk {@code DELETE} request,
* and wrap the X-result-count header as a integer response.
*
* @param apiKey the authorization token for accessing the EVRYTHNG API
* @param uri the {@link URI} holding the absolute URL
* @param responseStatus the expected {@link HttpResponse} status
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
public static Builder<Long> deleteMultiple(final String apiKey, final URI uri, final Status responseStatus) {
return new CheckedBuilder<Long>(apiKey, HttpMethodBuilder.httpDelete(), uri, responseStatus, new TypeReference<Long>() {}) {
@Override
public Long execute() throws EvrythngException {
// Perform request (response status code will be automatically checked):
HttpResponse response = request();
Header header = response.getFirstHeader(ApiConfiguration.HTTP_HEADER_RESULT_COUNT);
Long result = null;
if (header != null) {
try {
result = Long.parseLong(header.getValue());
} catch (NumberFormatException ex) {
logger.warn("Invalid numeric value in header {} : {}", ApiConfiguration.HTTP_HEADER_RESULT_COUNT, header.getValue());
}
}
return result;
}
};
}
// static <T> Builder<AcceptedResourceResponse> postAsynchronously(final String apiKey, final URI uri, final Object data, final Pattern pattern, final String mqttUrl, final String topic, final Class<T> notificationClass, final MqttCallback<T> callback) {
//
// return new MqttListenerBuilder<>(apiKey, HttpMethodBuilder.httpPost(data), uri, Status.ACCEPTED, pattern, mqttUrl, topic, notificationClass, callback);
// }
//
// public interface MqttCallback<T> {
//
// void apply(T notified);
// }
//
// private static final class MqttListenerBuilder<T> extends Builder<AcceptedResourceResponse> {
//
// private final Pattern idLocationExtractor;
// private final String topic;
// private final String mqttUrl;
// private final String apiKey;
// private final Class<T> notificationClass;
// private final MqttCallback<T> callback;
//
// private MqttListenerBuilder(final String apiKey, final MethodBuilder<?> methodBuilder, final URI uri, final Status responseStatus, final Pattern idLocationExtractor, final String mqttUrl, final String topic, final Class<T> notificationClass, final MqttCallback<T> callback) {
//
// super(apiKey, methodBuilder, uri, responseStatus, new TypeReference<AcceptedResourceResponse>() {});
// this.idLocationExtractor = idLocationExtractor;
// this.topic = topic;
// this.mqttUrl = mqttUrl;
// this.apiKey = apiKey;
// this.notificationClass = notificationClass;
// this.callback = callback;
// }
//
// @Override
// public AcceptedResourceResponse execute() throws EvrythngException {
//
// // Perform request (response status code will be automatically checked):
// // TODO _MS_ get project
// String projectId = null;
// HttpResponse response = request();
// String location = null;
// Header header = response.getFirstHeader("location");
// String id = null;
// if (header != null) {
// location = header.getValue();
// if (location != null) {
// Matcher match = idLocationExtractor.matcher(location);
// if (match.matches() && match.groupCount() > 0) {
// id = match.group(1);
// }
// }
// }
// try {
// final MqttClient client = mqttClient(mqttUrl, apiKey, projectId);
// client.subscribe(topic);
// // TODO _MS_ introduce type
// client.setCallback(new org.eclipse.paho.client.mqttv3.MqttCallback() {
//
// @Override
// public void connectionLost(final Throwable cause) {
//
// throw new RuntimeException("MQTT connection lost", cause);
// }
//
// @Override
// public void messageArrived(final String topic, final MqttMessage message) throws Exception {
//
// T notified = JSONUtils.OBJECT_MAPPER.readValue(message.getPayload(), notificationClass);
// client.unsubscribe(topic);
// client.disconnect();
// callback.apply(notified);
// }
//
// @Override
// public void deliveryComplete(final IMqttDeliveryToken token) {
//
// }
// });
// } catch (MqttException e) {
// // TODO _MS_ create exception type
// throw new RuntimeException("Failed to subscribe through MQTT", e);
// }
// return new AcceptedResourceResponse(id, location);
// }
// }
//
// private static MqttClient mqttClient(final String url, final String apiKey, final String projectId) throws MqttException {
//
// String clientId = MqttClient.generateClientId();
// String username = "authorization";
// MqttClient client = new EVTMqttClient(url, clientId, projectId);
// MqttConnectOptions options = new MqttConnectOptions();
// options.setUserName(username);
// options.setPassword(apiKey.toCharArray());
// options.setCleanSession(true);
// client.connect(options);
// return client;
// }
//
// private static final class EVTMqttClient extends MqttClient {
//
// private final String projectId;
//
// private EVTMqttClient(final String serverURI, final String clientId, final String projectId) throws MqttException {
//
// super(serverURI, clientId);
// this.projectId = projectId;
// }
//
// @Override
// public void subscribe(final String[] topicFilters, final int[] qos) throws MqttException {
//
// if (projectId != null) {
// for (int i = 0; i < topicFilters.length; i++) {
// topicFilters[i] += "?project=" + projectId;
// }
// }
// super.subscribe(topicFilters, qos);
// }
// }
/**
* Default command builder for the EVRYTHNG API.
*
* @author Pedro De Almeida (almeidap)
*/
public static class CheckedBuilder<T> extends ApiCommandBuilder<T, CheckedBuilder<T>> implements Builder<T> {
/**
* Private constructor, use {@link EvrythngApiBuilder} static methods
* for creating a {@link CheckedBuilder}.
*
* @param apiKey the authorization token for accessing the EVRYTHNG API
* @param methodBuilder the {@link MethodBuilder} used for creating the
* request
* @param uri the {@link URI} holding the absolute URL
* @param responseStatus the expected response {@link Status}
* @param responseType the native type to which the {@link HttpResponse} will be
* mapped to
*/
private CheckedBuilder(final String apiKey, final MethodBuilder<?> methodBuilder, final URI uri, final Status responseStatus, final TypeReference<T> responseType) {
this(apiKey, methodBuilder, uri, responseStatus, responseType, ApiConfiguration.HTTP_CONTENT_TYPE);
}
/**
* Private constructor, use {@link EvrythngApiBuilder} static methods
* for creating a {@link CheckedBuilder}.
*
* @param apiKey the authorization token for accessing the EVRYTHNG API
* @param methodBuilder the {@link MethodBuilder} used for creating the
* request
* @param uri the {@link URI} holding the absolute URL
* @param responseStatus the expected response {@link Status}
* @param responseType the native type to which the {@link HttpResponse} will be
* mapped to
* @param contentType content type
*/
private CheckedBuilder(final String apiKey, final MethodBuilder<?> methodBuilder, final URI uri, final Status responseStatus, final TypeReference<T> responseType, final String contentType) {
super(methodBuilder, uri, responseStatus, responseType);
// Define required API metainformation:
// header("Content-Type", ApiConfiguration.HTTP_CONTENT_TYPE);
if (contentType != null) {
header("Content-Type", contentType);
}
header("Accept", ApiConfiguration.HTTP_ACCEPT_TYPE);
apiKey(apiKey);
}
@Override
public CheckedBuilder<T> apiKey(final String apiKey) {
return header(ApiConfiguration.HTTP_HEADER_AUTHORIZATION, apiKey);
}
@Override
public CheckedBuilder<T> search(final String pattern) {
return queryParam(QSearchQueryParamValue.pattern(pattern));
}
@Override
public CheckedBuilder<T> withScopes(final boolean withScopes) {
return queryParam(WithScopesQueryParamValue.NAME, String.valueOf(withScopes));
}
@Override
public CheckedBuilder<T> withPagination(final int page, final int perPage) {
return page(page).perPage(perPage);
}
@Override
public CheckedBuilder<T> page(final int page) {
return queryParam(PageQueryParamValue.page(page));
}
@Override
public CheckedBuilder<T> perPage(final int perPage) {
return queryParam(PerPageQueryParamValue.perPage(perPage));
}
@Override
public CheckedBuilder<T> from(final long from) {
return queryParam(FromQueryParamValue.from(String.valueOf(from)));
}
@Override
public CheckedBuilder<T> from(final String from) {
return queryParam(FromQueryParamValue.from(from));
}
@Override
public CheckedBuilder<T> from(final QueryKeyword queryKeyword) {
return queryParam(FromQueryParamValue.from(queryKeyword.toString()));
}
@Override
public CheckedBuilder<T> to(final long to) {
return queryParam(ToQueryParamValue.to(String.valueOf(to)));
}
@Override
public CheckedBuilder<T> to(final String to) {
return queryParam(ToQueryParamValue.to(to));
}
@Override
public CheckedBuilder<T> to(final QueryKeyword queryKeyword) {
return queryParam(ToQueryParamValue.to(queryKeyword.toString()));
}
@Override
@Deprecated
public CheckedBuilder<T> app(final String appId) {
return this;
}
@Override
public CheckedBuilder<T> userScope(final Iterable<String> scope) {
return queryParam(UserScopeQueryParamValue.valueOf(StringUtils.join(scope, ',')));
}
@Override
public CheckedBuilder<T> userScopeAll() {
return queryParam(ScopeQueryParamValue.valueOf(QueryKeyword.ALL.toString()));
}
@Override
public CheckedBuilder<T> ids(final List<String> ids) {
return queryParamList(IdsQueryParamValue.NAME, ids);
}
@Override
public CheckedBuilder<T> filter(final String filter) {
return queryParam(FilterQueryParamValue.NAME, filter);
}
@Override
public CheckedBuilder<T> project(final String projectId) {
return queryParam(ProjectQueryParamValue.project(projectId));
}
@Override
@Deprecated
public int count() throws EvrythngException {
logger.debug("Counting total number of elements: [header={}]", ApiConfiguration.HTTP_HEADER_RESULT_COUNT);
Header xResultCountHeader = getCommand().head(ApiConfiguration.HTTP_HEADER_RESULT_COUNT);
return Integer.valueOf(xResultCountHeader.getValue());
}
@Override
public Result<T> list() throws EvrythngException {
if (getCommand().getMethod() != Method.GET) {
throw new EvrythngClientException("The list() method is only available for GET requests.");
}
logger.debug("Call list. For type : {}", getCommand().getResponseType().getType());
// Issue the command "manually" to get the response object.
TypedResponseWithEntity<T> bundle = getCommand().bundle();
HttpResponse response = bundle.response();
Utils.assertStatus(response, getCommand().getExpectedResponseStatus());
T ret = bundle.entity();
// Parse the total result count.
Header header = response.getFirstHeader(ApiConfiguration.HTTP_HEADER_RESULT_COUNT);
long n;
if (header == null) {
throw new EvrythngClientException("The response contains no " + ApiConfiguration.HTTP_HEADER_RESULT_COUNT + " header.");
}
try {
n = Long.parseLong(header.getValue());
} catch (NumberFormatException e) {
throw new EvrythngClientException("The response's " + ApiConfiguration.HTTP_HEADER_RESULT_COUNT + " header could not be parsed.");
}
logger.debug("Total number of items: {}", n);
return new Result<>(ret, n);
}
// TODO: check usefulness & validity of this!
@Override
public String jsonp(final String callback) throws EvrythngException {
// Add JSONP callback to query parameters list:
queryParam(CallbackQueryParamValue.callback(callback));
// Retrieve response entity content:
String jsonp = getCommand().content();
// Remove callback to avoid conflicts on future requests:
queryParam(CallbackQueryParamValue.empty());
return jsonp;
}
}
public interface Builder<TYPE> {
/**
* Class to hold the results and the total count.
*/
class Result<R> {
private R result;
private long totalCount;
public Result(final R result, final long totalCount) {
this.result = result;
this.totalCount = totalCount;
}
public R getResult() {
return result;
}
public long getTotalCount() {
return totalCount;
}
}
TypedResponseWithEntity<TYPE> executeWithResponse() throws EvrythngException;
/**
* Sets a query parameter or removes it if {@code value} equals {@code null}
*
* @param name the query parameter name
* @param value the query parameter value
*
* @return the current {@link Builder} instance
*/
Builder<TYPE> queryParam(String name, String value);
Builder<TYPE> placeHolder(Boolean placeHolder);
/**
* Sets a query parameter or removes it if the {@code value} equals
* {@code null}.
*
* @param qpv the name and the value of the query parameter.
*
* @return the current {@link Builder} instance
*/
Builder<TYPE> queryParam(QueryParamValue qpv);
/**
* Sets a multi-valued query parameter or removes it if {@code value} equals
* {@code null}.
*
* @param name parameter name
* @param value parameter values or {@code null}
*
* @return the current {@link Builder} instance
*/
Builder<TYPE> queryParam(String name, List<String> value);
/**
* Sets a multi-valued query parameter or removes it if {@code value} equals
* {@code null}.
*
* @param name parameter name
* @param values parameter values or {@code null}
*
* @return the current {@link Builder} instance
*/
Builder<TYPE> queryParamList(String name, List<String> values);
/**
* Sets a multi-valued query parameter or removes it if {@code value} equals
* {@code null}.
*
* @param name parameter name
* @param values parameter values or {@code null}
*
* @return the current {@link Builder} instance
*/
Builder<TYPE> queryParamList(String name, String... values);
/**
* Sets the provided query parametes.
*
* @param params a map name/value entries
*
* @return the current {@link Builder} instance
*
* @see #queryParam(String, String)
*/
Builder<TYPE> queryParams(Map<String, String> params);
/**
* Sets a request header or removes it if {@code value} equals {@code null}.
*
* @param name request header name
* @param value the request header value
*
* @return the current {@link Builder} instance
*/
Builder<TYPE> header(String name, String value);
/**
* Sets the value of the {@code Accept} HTTP header.
*
* @param mediaType a valid media type for the {@code Accept} HTTP header
*
* @return the current {@link Builder} instance
*/
Builder<TYPE> accept(String mediaType);
/**
* Executes the current command and maps the {@link HttpResponse} entity to
* {@code T} specified by {@link ApiCommand#responseType}.
*
* @return the {@link HttpResponse} entity mapped to {@code T}
*
* @see ApiCommand#execute()
*/
TYPE execute() throws EvrythngException;
/**
* Executes the current command and maps the {@link HttpResponse} entity to
* {@code T} specified by {@link ApiCommand#responseType}.
*
* @param retryOnConnectTimeout if true the connection will be attempted up to 5 times
* times when a connect timeout is encountered
*
* @return the {@link HttpResponse} entity mapped to {@code T}
*
* @see ApiCommand#execute()
*/
TYPE execute(final boolean retryOnConnectTimeout) throws EvrythngException;
/**
* Executes the current command and returns the {@link HttpResponse} entity
* content as {@link String}.
*
* @return the {@link HttpResponse} entity content as {@link String}
*
* @see ApiCommand#content()
*/
String content() throws EvrythngException;
/**
* Executes the current command and returns the native {@link HttpResponse}.
*
* @return the {@link HttpResponse} resulting from the request
*
* @see ApiCommand#request()
*/
HttpResponse request() throws EvrythngException;
/**
* Executes the current command and returns the {@link HttpResponse} content
* {@link InputStream}.
*
* @return the {@link InputStream} of the {@link HttpResponse}
*
* @see ApiCommand#stream()
*/
InputStream stream() throws EvrythngException;
/**
* @return {@link ApiCommand} instance
*/
ApiCommand<TYPE> getCommand();
/**
* @param apiKey the authorization token for accessing the EVRYTHNG API
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> apiKey(String apiKey);
/**
* @param pattern "{@value QSearchQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*
* @see QSearchQueryParamValue
*/
Builder<TYPE> search(String pattern);
/**
* @param sortOrder "{@value SortOrderQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*
* @see SortOrderQueryParamValue
*/
Builder<TYPE> sortOrder(SortOrder sortOrder);
/**
* @param withScopes "{@value WithScopesQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*
* @see WithScopesQueryParamValue
*/
Builder<TYPE> withScopes(boolean withScopes);
/**
* Combines page and perPage methods.
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> withPagination(int page, int perPage);
/**
* @param page "{@value PageQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> page(int page);
/**
* @param perPage "{@value PerPageQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> perPage(int perPage);
/**
* @param from "{@value FromQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> from(long from);
/**
* @param from "{@value FromQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> from(String from);
/**
* @param queryKeyword "{@value FromQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> from(QueryKeyword queryKeyword);
/**
* @param to "{@value ToQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> to(long to);
/**
* @param to "{@value ToQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> to(String to);
/**
* @param queryKeyword "{@value ToQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> to(QueryKeyword queryKeyword);
/**
* Does nothing. App query parameter is not used anymore.
*
* @return an EVRYTHNG API-ready {@link Builder}
*
* @deprecated Use {@value ProjectQueryParamValue#NAME} project to scope a request on a project.
*/
@Deprecated
// TODO _MS_ remove!
Builder<TYPE> app(String appId);
/**
* @param scope "{@value UserScopeQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> userScope(Iterable<String> scope);
/**
* Adds "{@value ScopeQueryParamValue#NAME}" query param with value {@link QueryKeyword#ALL}
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> userScopeAll();
/**
* @param ids "{@value IdsQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> ids(List<String> ids);
/**
* @param filter "{@value FilterQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> filter(String filter);
/**
* @param projectId "{@value ProjectQueryParamValue#NAME}" query parameter value
*
* @return an EVRYTHNG API-ready {@link Builder}
*/
Builder<TYPE> project(String projectId);
/**
* Counts the <strong>total</strong> number of elements if the current
* command was executed as a {@code HEAD} request.
*
* @return the <strong>total</strong> number of elements matching the
* current request
*
* @see ApiCommand#head(String)
*/
@Deprecated
// TODO _MS_ remove!
int count() throws EvrythngException;
/**
* Executes the requests and returns a result object. The result objects
* contains the actual object and the total count for the paginated
* list. This method can only be used in the context of a paginated
* result set, otherwise an exception is thrown.
*
* @return result object
*/
Result<TYPE> list() throws EvrythngException;
/**
* Executes the current command by requesting JSONP in the
* {@link HttpResponse} entity.
* <p>
*
* @param callback the name of the callback function
*
* @return the {@link HttpResponse} entity in the form of JSONP content
*/
// TODO: check usefulness & validity of this!
String jsonp(String callback) throws EvrythngException;
}
public static class UncheckedBuilder<T> extends UncheckedApiCommandBuilder<T, UncheckedBuilder<T>> {
/**
* Private constructor, use {@link EvrythngApiBuilder} static methods
* for creating a {@link Builder}.
*
* @param apiKey the authorization token for accessing the EVRYTHNG API
* @param methodBuilder the {@link MethodBuilder} used for creating the
* request
* @param uri the {@link URI} holding the absolute URL
* @param responseStatus the expected response {@link Status}
* @param responseType the native type to which the {@link HttpResponse} will be
* mapped to
*/
private <X> UncheckedBuilder(final String apiKey, final MethodBuilder<?> methodBuilder, final URI uri, final Status responseStatus, final TypeReference<X> responseType) {
this(apiKey, methodBuilder, uri, responseStatus, responseType, ApiConfiguration.HTTP_CONTENT_TYPE);
}
/**
* Private constructor, use {@link EvrythngApiBuilder} static methods
* for creating a {@link Builder}.
*
* @param apiKey the authorization token for accessing the EVRYTHNG API
* @param methodBuilder the {@link MethodBuilder} used for creating the
* request
* @param uri the {@link URI} holding the absolute URL
* @param responseStatus the expected response {@link Status}
* @param responseType the native type to which the {@link HttpResponse} will be
* mapped to
* @param contentType content type
*/
private <X> UncheckedBuilder(final String apiKey, final MethodBuilder<?> methodBuilder, final URI uri, final Status responseStatus, final TypeReference<X> responseType, final String contentType) {
super(methodBuilder, uri, responseStatus, responseType);
// Define required API metainformation:
// header("Content-Type", ApiConfiguration.HTTP_CONTENT_TYPE);
if (contentType != null) {
header("Content-Type", contentType);
}
header("Accept", ApiConfiguration.HTTP_ACCEPT_TYPE);
apiKey(apiKey);
}
public <X> TypedResponseWithEntity<X> executeWithResponse() throws EvrythngException {
return (TypedResponseWithEntity<X>) getCommand().bundle();
}
public UncheckedBuilder<T> apiKey(final String apiKey) {
return header(ApiConfiguration.HTTP_HEADER_AUTHORIZATION, apiKey);
}
public UncheckedBuilder<T> search(final String pattern) {
return queryParam(QSearchQueryParamValue.pattern(pattern));
}
public UncheckedBuilder<T> withScopes(final boolean withScopes) {
return queryParam(WithScopesQueryParamValue.NAME, String.valueOf(withScopes));
}
public UncheckedBuilder<T> withPagination(final int page, final int perPage) {
return page(page).perPage(perPage);
}
public UncheckedBuilder<T> page(final int page) {
return queryParam(PageQueryParamValue.page(page));
}
public UncheckedBuilder<T> perPage(final int perPage) {
return queryParam(PerPageQueryParamValue.perPage(perPage));
}
public UncheckedBuilder<T> from(final long from) {
return queryParam(FromQueryParamValue.from(String.valueOf(from)));
}
public UncheckedBuilder<T> from(final String from) {
return queryParam(FromQueryParamValue.from(from));
}
public UncheckedBuilder<T> from(final QueryKeyword queryKeyword) {
return queryParam(FromQueryParamValue.from(queryKeyword.toString()));
}
public UncheckedBuilder<T> to(final long to) {
return queryParam(ToQueryParamValue.to(String.valueOf(to)));
}
public UncheckedBuilder<T> to(final String to) {
return queryParam(ToQueryParamValue.to(to));
}
public UncheckedBuilder<T> to(final QueryKeyword queryKeyword) {
return queryParam(ToQueryParamValue.to(queryKeyword.toString()));
}
@Deprecated
public UncheckedBuilder<T> app(final String appId) {
return this;
}
public UncheckedBuilder<T> userScope(final Iterable<String> scope) {
return queryParam(UserScopeQueryParamValue.valueOf(StringUtils.join(scope, ',')));
}
public UncheckedBuilder<T> userScopeAll() {
return queryParam(ScopeQueryParamValue.valueOf(QueryKeyword.ALL.toString()));
}
public UncheckedBuilder<T> ids(final List<String> ids) {
return queryParamList(IdsQueryParamValue.NAME, ids);
}
public UncheckedBuilder<T> filter(final String filter) {
return queryParam(FilterQueryParamValue.NAME, filter);
}
public UncheckedBuilder<T> project(final String projectId) {
return queryParam(ProjectQueryParamValue.project(projectId));
}
@Deprecated
public int count() throws EvrythngException {
logger.debug("Counting total number of elements: [header={}]", ApiConfiguration.HTTP_HEADER_RESULT_COUNT);
Header xResultCountHeader = getCommand().head(ApiConfiguration.HTTP_HEADER_RESULT_COUNT);
return Integer.valueOf(xResultCountHeader.getValue());
}
public Builder.Result<T> list() throws EvrythngException {
if (getCommand().getMethod() != Method.GET) {
throw new EvrythngClientException("The list() method is only available for GET requests.");
}
logger.debug("Call list. For type : {}", getCommand().getResponseType().getType());
// Issue the command "manually" to get the response object.
ApiCommand<T> command = getCommand();
TypedResponseWithEntity<T> bundle = command.bundle();
HttpResponse response = bundle.response();
Utils.assertStatus(response, getCommand().getExpectedResponseStatus());
T ret = bundle.entity();
// Parse the total result count.
Header header = response.getFirstHeader(ApiConfiguration.HTTP_HEADER_RESULT_COUNT);
long n;
if (header == null) {
throw new EvrythngClientException("The response contains no " + ApiConfiguration.HTTP_HEADER_RESULT_COUNT + " header.");
}
try {
n = Long.parseLong(header.getValue());
} catch (NumberFormatException e) {
throw new EvrythngClientException("The response's " + ApiConfiguration.HTTP_HEADER_RESULT_COUNT + " header could not be parsed.");
}
logger.debug("Total number of items: {}", n);
return new Builder.Result<>(ret, n);
}
// TODO: check usefulness & validity of this!
public String jsonp(final String callback) throws EvrythngException {
// Add JSONP callback to query parameters list:
queryParam(CallbackQueryParamValue.callback(callback));
// Retrieve response entity content:
String jsonp = getCommand().content();
// Remove callback to avoid conflicts on future requests:
queryParam(CallbackQueryParamValue.empty());
return jsonp;
}
UncheckedBuilder<T> sortOrder(final SortOrder sortOrder) {
return queryParam(SortOrderQueryParamValue.NAME, sortOrder.direction().name());
}
}
}