/*
* (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.commons.domain.SortOrder;
import com.evrythng.java.wrapper.ApiManager;
import com.evrythng.java.wrapper.core.EvrythngApiBuilder.Builder;
import com.evrythng.java.wrapper.core.api.AcceptedResourceResponse;
import com.evrythng.java.wrapper.core.api.ApiCommand;
import com.evrythng.java.wrapper.core.api.QueryParamValue;
import com.evrythng.java.wrapper.core.api.TypedResponseWithEntity;
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.java.wrapper.util.URIBuilder;
import com.evrythng.thng.commons.config.ApiConfiguration;
import com.fasterxml.jackson.core.type.TypeReference;
import org.apache.commons.codec.binary.Base64InputStream;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.pcollections.PVector;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
* Base definition for EVRYTHNG API services.
*/
public class EvrythngServiceBase {
private static final boolean BASE64_ENCODE = true;
private static final int BASE64_ENCODE_NO_LINE_LENGTH = 0;
private static final byte[] BASE64_ENCODE_NO_LINE_SEPARATOR = new byte[0];
private final ApiManager api;
private final ApiConfiguration config;
/**
* Creates a new instance of {@link EvrythngServiceBase} using the provided
* {@link ApiManager}.
*
* @param api the {@link ApiManager} for accessing the API service.
*/
public EvrythngServiceBase(final ApiManager api) {
this.api = api;
this.config = api.getConfig();
}
protected final <T> Builder<Iterator<PVector<T>>> iterator(final String relativePath, final TypeReference<List<T>> pageType) throws EvrythngClientException {
EvrythngApiBuilder.UncheckedBuilder<Iterator<PVector<T>>> builder = EvrythngApiBuilder.iterate(config.getKey(), absoluteUri(relativePath), Status.OK, pageType);
Builder<Iterator<PVector<T>>> adapter = new BuilderUncheckedAdapter<>(builder);
onBuilderCreated(adapter);
return adapter;
}
private static final class BuilderUncheckedAdapter<TYPE> implements Builder<TYPE> {
private final EvrythngApiBuilder.UncheckedBuilder<TYPE> internal;
private BuilderUncheckedAdapter(final EvrythngApiBuilder.UncheckedBuilder<TYPE> internal) {
this.internal = internal;
}
@Override
public final TypedResponseWithEntity<TYPE> executeWithResponse() throws EvrythngException {
return internal.executeWithResponse();
}
@Override
public final Builder<TYPE> queryParam(final String name, final String value) {
internal.queryParam(name, value);
return this;
}
@Override
public final Builder<TYPE> placeHolder(final Boolean placeHolder) {
internal.placeHolder(placeHolder);
return this;
}
@Override
public final Builder<TYPE> queryParam(final QueryParamValue qpv) {
internal.queryParam(qpv);
return this;
}
@Override
public final Builder<TYPE> queryParam(final String name, final List<String> values) {
internal.queryParam(name, values);
return this;
}
@Override
public final Builder<TYPE> queryParamList(final String name, final List<String> values) {
internal.queryParamList(name, values);
return this;
}
@Override
public final Builder<TYPE> queryParamList(final String name, final String... values) {
internal.queryParamList(name, values);
return this;
}
@Override
public final Builder<TYPE> queryParams(final Map<String, String> params) {
internal.queryParams(params);
return this;
}
@Override
public final Builder<TYPE> header(final String name, final String value) {
internal.header(name, value);
return this;
}
@Override
public final Builder<TYPE> accept(final String mediaType) {
internal.accept(mediaType);
return this;
}
@Override
public final TYPE execute() throws EvrythngException {
return internal.execute();
}
@Override
public final TYPE execute(final boolean retryOnConnectTimeout) throws EvrythngException {
return internal.execute(retryOnConnectTimeout);
}
@Override
public final String content() throws EvrythngException {
return internal.content();
}
@Override
public final HttpResponse request() throws EvrythngException {
return internal.request();
}
@Override
public final InputStream stream() throws EvrythngException {
return internal.stream();
}
@Override
public final ApiCommand<TYPE> getCommand() {
return internal.getCommand();
}
@Override
public final Builder<TYPE> apiKey(final String apiKey) {
internal.apiKey(apiKey);
return this;
}
@Override
public final Builder<TYPE> search(final String pattern) {
internal.search(pattern);
return this;
}
@Override
public Builder<TYPE> sortOrder(final SortOrder sortOrder) {
internal.sortOrder(sortOrder);
return this;
}
@Override
public final Builder<TYPE> withScopes(final boolean withScopes) {
internal.withScopes(withScopes);
return this;
}
@Override
public final Builder<TYPE> withPagination(final int page, final int perPage) {
internal.withPagination(page, perPage);
return this;
}
// TODO _MS_ remove it!
@Override
@Deprecated
public final Builder<TYPE> page(final int page) {
internal.page(page);
return this;
}
@Override
public final Builder<TYPE> perPage(final int perPage) {
internal.perPage(perPage);
return this;
}
@Override
public final Builder<TYPE> from(final long from) {
internal.from(from);
return this;
}
@Override
public final Builder<TYPE> from(final String from) {
internal.from(from);
return this;
}
@Override
public final Builder<TYPE> from(final ApiConfiguration.QueryKeyword queryKeyword) {
internal.from(queryKeyword);
return this;
}
@Override
public final Builder<TYPE> to(final long to) {
internal.to(to);
return this;
}
@Override
public final Builder<TYPE> to(final String to) {
internal.to(to);
return this;
}
@Override
public final Builder<TYPE> to(final ApiConfiguration.QueryKeyword queryKeyword) {
internal.to(queryKeyword);
return this;
}
@Override
@Deprecated
public final Builder<TYPE> app(final String appId) {
internal.app(appId);
return this;
}
@Override
public final Builder<TYPE> userScope(final Iterable<String> scope) {
internal.userScope(scope);
return this;
}
@Override
public final Builder<TYPE> userScopeAll() {
internal.userScopeAll();
return this;
}
@Override
public final Builder<TYPE> ids(final List<String> ids) {
internal.ids(ids);
return this;
}
@Override
public final Builder<TYPE> filter(final String filter) {
internal.filter(filter);
return this;
}
@Override
public final Builder<TYPE> project(final String projectId) {
internal.project(projectId);
return this;
}
@Override
@Deprecated
public final int count() throws EvrythngException {
return internal.count();
}
@Override
public final Result<TYPE> list() throws EvrythngException {
return internal.list();
}
@Override
public final String jsonp(final String callback) throws EvrythngException {
return internal.jsonp(callback);
}
}
/**
* Returns a preconfigured {@link Builder} for executing POST requests.
* Expects
* 201 (created) return code
*
* @param relativePath the relative path of the API endpoint. It will be appended to
* {@link ApiConfiguration#getUrl()} in
* order to build the absolute endpoint URL.
* @param data the content data that will be associated with the POST request
* @param type the {@link TypeReference} for mapping the {@link HttpResponse}
* entity.
* @return a preconfigured {@link Builder} for executing POST requests
* @see EvrythngApiBuilder#post(String, URI, Object, Status, TypeReference)
*/
public <T> Builder<T> post(final String relativePath, final Object data, final TypeReference<T> type) throws EvrythngClientException {
return post(relativePath, data, Status.CREATED, type);
}
/**
* Returns a preconfigured {@link Builder} for executing POST requests.
*
* @param relativePath the relative path of the API endpoint. It will be appended to
* {@link ApiConfiguration#getUrl()} in
* order to build the absolute endpoint URL.
* @param data the content data that will be associated with the POST request
* @param expected The expected return status
* @param type the {@link TypeReference} for mapping the {@link HttpResponse}
* entity.
* @return a preconfigured {@link Builder} for executing POST requests
* @see EvrythngApiBuilder#post(String, URI, Object, Status, TypeReference)
*/
public <T> Builder<T> post(final String relativePath, final Object data, final Status expected, final TypeReference<T> type) throws EvrythngClientException {
Builder<T> builder = EvrythngApiBuilder.post(config.getKey(), absoluteUri(relativePath), data, expected, type);
onBuilderCreated(builder);
return builder;
}
/**
* Returns a preconfigured {@link Builder} for uploading file via POST requests
*
* @param relativePath the relative path of the API endpoint. It will be appended to
* {@link ApiConfiguration#getUrl()} in
* order to build the absolute endpoint URL.
* @param file file
* @param type the {@link TypeReference} for mapping the {@link HttpResponse} entity
* @return a preconfigured {@link Builder} for executing POST requests
* @see #postMultipart(String, File, Status, TypeReference)
*/
public <T> Builder<T> postMultipart(final String relativePath, final File file, final TypeReference<T> type) throws EvrythngClientException {
return postMultipart(relativePath, file, Status.CREATED, type);
}
/**
* Returns a preconfigured {@link Builder} for uploading file via POST
* requests
*
* @param relativePath the relative path of the API endpoint. It will be appended to
* {@link ApiConfiguration#getUrl()} in
* order to build the absolute endpoint URL.
* @param file file
* @param expected expected return {@link Status}
* @param type the {@link TypeReference} for mapping the {@link HttpResponse} entity
* @return a preconfigured {@link Builder} for executing POST requests
*/
public <T> Builder<T> postMultipart(final String relativePath, final File file, final Status expected, final TypeReference<T> type) throws EvrythngClientException {
Builder<T> builder = EvrythngApiBuilder.postMultipart(config.getKey(), absoluteUri(relativePath), file, expected, type);
onBuilderCreated(builder);
return builder;
}
/**
* Returns a preconfigured {@link Builder} for uploading file via PUT requests
*
* @param relativePath the relative path of the API endpoint. It will be appended to
* {@link ApiConfiguration#getUrl()} in
* order to build the absolute endpoint URL.
* @param file file
* @param type the {@link TypeReference} for mapping the {@link HttpResponse} entity
* @return a preconfigured {@link Builder} for executing PUT requests
* @see #postMultipart(String, File, Status, TypeReference)
*/
public <T> Builder<T> putMultipart(final String relativePath, final File file, final TypeReference<T> type) throws EvrythngClientException {
return putMultipart(relativePath, file, Status.OK, type);
}
/**
* Returns a preconfigured {@link Builder} for uploading file via PUT
* requests
*
* @param relativePath the relative path of the API endpoint. It will be appended to
* {@link ApiConfiguration#getUrl()} in
* order to build the absolute endpoint URL.
* @param file file
* @param expected expected return {@link Status}
* @param type the {@link TypeReference} for mapping the {@link HttpResponse} entity
* @return a preconfigured {@link Builder} for executing PUT requests
*/
public <T> Builder<T> putMultipart(final String relativePath, final File file, final Status expected, final TypeReference<T> type) throws EvrythngClientException {
Builder<T> builder = EvrythngApiBuilder.putMultipart(config.getKey(), absoluteUri(relativePath), file, expected, type);
onBuilderCreated(builder);
return builder;
}
public Builder<AcceptedResourceResponse> postAsynchronously(final String relativePath, Object data, Pattern pattern) throws EvrythngClientException {
Builder<AcceptedResourceResponse> builder = EvrythngApiBuilder.postAsynchronously(config.getKey(), absoluteUri(relativePath), data, pattern);
onBuilderCreated(builder);
return builder;
}
// public <T> Builder<AcceptedResourceResponse> postAsynchronouslyWithCallback(final String relativePath, final Object data, final Pattern pattern, final String topic, final Class<T> notificationClass, final EvrythngApiBuilder.MqttCallback<T> callback) throws EvrythngClientException {
//
// Builder<AcceptedResourceResponse> builder = EvrythngApiBuilder.postAsynchronously(config.getKey(), absoluteUri(relativePath), data, pattern, config.getMqttUrl(), topic, notificationClass, callback);
// onBuilderCreated(builder);
// return builder;
// }
/**
* Returns a preconfigured {@link Builder} for executing GET requests.
*
* @param relativePath the relative path of the API endpoint. It will be appended to
* {@link ApiConfiguration#getUrl()} in
* order to build the absolute endpoint URL.
* @param type the {@link TypeReference} for mapping the {@link HttpResponse}
* entity.
* @return a preconfigured {@link Builder} for executing GET requests
* @see EvrythngApiBuilder#get(String, URI, Status, TypeReference)
*/
public <T> Builder<T> get(final String relativePath, final TypeReference<T> type) throws EvrythngClientException {
Builder<T> builder = EvrythngApiBuilder.get(config.getKey(), absoluteUri(relativePath), Status.OK, type);
onBuilderCreated(builder);
return builder;
}
/**
* Returns a preconfigured {@link Builder} for executing PUT requests.
*
* @param relativePath the relative path of the API endpoint. It will be appended to
* {@link ApiConfiguration#getUrl()} in
* order to build the absolute endpoint URL.
* @param data the content data that will be associated with the PUT request
* @param expected the expected Status code of the future request. Will be
* checked.
* @param type the {@link TypeReference} for mapping the {@link HttpResponse}
* entity.
* @return a preconfigured {@link Builder} for executing PUT requests
* @see EvrythngApiBuilder#post(String, URI, Object, Status, TypeReference)
*/
public <T> Builder<T> put(final String relativePath, final Object data, final Status expected, final TypeReference<T> type) throws EvrythngClientException {
Builder<T> builder = EvrythngApiBuilder.put(config.getKey(), absoluteUri(relativePath), data, expected, type);
onBuilderCreated(builder);
return builder;
}
/**
* Returns a preconfigured {@link Builder} for executing PUT requests.
*
* @param relativePath the relative path of the API endpoint. It will be appended to
* {@link ApiConfiguration#getUrl()} in
* order to build the absolute endpoint URL.
* @param data the content data that will be associated with the PUT request
* @param type the {@link TypeReference} for mapping the {@link HttpResponse}
* entity.
* @return a preconfigured {@link Builder} for executing PUT requests
* @see EvrythngApiBuilder#put(String, URI, Object, Status, TypeReference)
*/
public <T> Builder<T> put(final String relativePath, final Object data, final TypeReference<T> type) throws EvrythngClientException {
return put(relativePath, data, Status.OK, type);
}
/**
* Returns a preconfigured {@link Builder} for executing PUT requests.
* The reference return type is Long, and will contain the amount of updated
* documents.
*
* @param relativePath the relative path of the API endpoint. It will be appended to
* {@link ApiConfiguration#getUrl()} in
* order to build the absolute endpoint URL.
* @param data the content data that will be associated with the PUT request
* @return a preconfigured {@link Builder} for executing PUT requests
* @see EvrythngApiBuilder#putMultiple(String, URI, Object, Status)
*/
public Builder<Long> putMultiple(final String relativePath, final Object data) throws EvrythngClientException {
Builder<Long> builder = EvrythngApiBuilder.putMultiple(config.getKey(), absoluteUri(relativePath), data, Status.NO_CONTENT);
onBuilderCreated(builder);
return builder;
}
/**
* Returns a preconfigured {@link Builder} for executing DELETE requests.
*
* @param relativePath the relative path of the API endpoint. It will be appended to
* {@link ApiConfiguration#getUrl()} in
* order to build the absolute endpoint URL.
* @return a preconfigured {@link Builder} for executing DELETE requests
* @see EvrythngApiBuilder#post(String, URI, Object, Status, TypeReference)
*/
public Builder<Boolean> delete(final String relativePath) throws EvrythngClientException {
Builder<Boolean> builder = EvrythngApiBuilder.delete(config.getKey(), absoluteUri(relativePath), Status.OK);
onBuilderCreated(builder);
return builder;
}
/**
* Returns a preconfigured {@link Builder} for executing bulk DELETE
* requests.
*
* @param relativePath the relative path of the API endpoint. It will be appended to
* {@link ApiConfiguration#getUrl()} in
* order to build the absolute endpoint URL.
* @return a preconfigured {@link Builder} for executing DELETE requests
* @see EvrythngApiBuilder#deleteMultiple(String, URI, Status)
*/
public Builder<Long> deleteMultiple(final String relativePath) throws EvrythngClientException {
Builder<Long> builder = EvrythngApiBuilder.deleteMultiple(config.getKey(), absoluteUri(relativePath), Status.OK);
onBuilderCreated(builder);
return builder;
}
/**
* Builds an absolute {@link URI} using the provided {@code relativePath}
* and the predefined {@link ApiConfiguration#getUrl()}.
*
* @param relativePath the relative path that will be appended to
* {@link ApiConfiguration#getUrl()} in order to build the
* absolute endpoint URL.
* @return the absolute endpoint {@link URI}
*/
protected URI absoluteUri(final String relativePath) throws EvrythngClientException {
String path = relativePath.startsWith("/") ? relativePath : String.format("/%s", relativePath);
return URIBuilder.fromUri(String.format("%s%s", config.getUrl(), path)).build();
}
protected String mqttUrl() throws EvrythngClientException {
return config.getMqttUrl();
}
public ApiConfiguration getConfig() {
return config;
}
protected void onBuilderCreated(final Builder<?> builder) {
api.onBuilderCreated(builder);
}
protected static String urlEncodePathPart(final String s) {
try {
return URLEncoder.encode(s, "UTF-8").replace("+", "%20");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
protected String encodeBase64(final InputStream image, final String mime) throws IOException {
Base64InputStream b64is = null;
StringWriter sw = null;
try {
// Base64 encoding WITHOUT line separators
b64is = new Base64InputStream(image, BASE64_ENCODE, BASE64_ENCODE_NO_LINE_LENGTH, BASE64_ENCODE_NO_LINE_SEPARATOR);
sw = new StringWriter();
IOUtils.copy(b64is, sw);
return mime + "," + sw.toString();
} finally {
IOUtils.closeQuietly(b64is);
IOUtils.closeQuietly(sw);
}
}
}