com.mercadopago.core.MPBase Maven / Gradle / Ivy
package com.mercadopago.core;
import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.mercadopago.*;
import com.mercadopago.core.annotations.idempotent.Idempotent;
import com.mercadopago.core.annotations.rest.*;
import com.mercadopago.core.annotations.rest.*;
import com.mercadopago.exceptions.MPException;
import com.mercadopago.net.HttpMethod;
import com.mercadopago.net.MPRestClient;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Header;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.*;
/**
* Mercado Pago MercadoPago
* Core MPBase class
*
* Created by Eduardo Paoletta on 11/4/16.
*/
public abstract class MPBase {
private transient static final List ALLOWED_METHODS = Arrays.asList("findById", "save", "update", "delete");
private transient static final List ALLOWED_BULK_METHODS = Arrays.asList("all", "search");
private transient static final List METHODS_TO_VALIDATE = Arrays.asList("save", "update");
public transient static final Boolean WITHOUT_CACHE = Boolean.FALSE;
public transient static final Boolean WITH_CACHE = Boolean.TRUE;
protected transient JsonObject _lastKnownJson = null;
private transient String idempotenceKey = null;
protected transient MPApiResponse lastApiResponse;
public MPBase() {
if (admitIdempotenceKey()) {
this.idempotenceKey = UUID.randomUUID().toString();
}
}
public String getIdempotenceKey() {
return this.idempotenceKey;
}
public T setIdempotenceKey(String idempotenceKey) throws MPException {
if (!admitIdempotenceKey()) {
throw new MPException(this.getClass().getSimpleName() + " does not admit an idempotence key");
}
this.idempotenceKey = idempotenceKey;
return (T) this;
}
public MPApiResponse getLastApiResponse() {
return this.lastApiResponse;
}
/**
* Checks if the class is marked as an idempotent resource
* @return a boolean
*/
private boolean admitIdempotenceKey() {
return (this.getClass().getAnnotation(Idempotent.class) != null);
}
/**
* Process the method to call the api, usually used for create, update and delete methods
*
* @param methodName a String with the decorated method to be processed
* @param useCache a Boolean flag that indicates if the cache must be used
* @return a resourse obj fill with the api response
* @throws MPException
*/
protected T processMethod(String methodName, Boolean useCache) throws MPException {
HashMap mapParams = null;
T resource = processMethod(this.getClass(), (T)this, methodName, mapParams, useCache);
fillResource(resource, this);
return (T)this;
}
/**
* Process the method to call the api, usually used for load methods
*
* @param clazz a MPBase extended class
* @param methodName a String with the decorated method to be processed
* @param param1 a String with the arg passed in the call of the method
* @param useCache a Boolean flag that indicates if the cache must be used
* @return a resourse obj fill with the api response
* @throws MPException
*/
protected static T processMethod(Class clazz, String methodName, String param1, Boolean useCache) throws MPException {
HashMap mapParams = new HashMap();
mapParams.put("param1", param1);
return processMethod(clazz, null, methodName, mapParams, useCache);
}
/**
* Process the method to call the api, usually used for load methods
*
* @param clazz a MPBase extended class
* @param methodName a String with the decorated method to be processed
* @param param1 a String with the arg passed in the call of the method
* @param param2 a String with the arg passed in the call of the method
* @param useCache a Boolean flag that indicates if the cache must be used
* @return a resourse obj fill with the api response
* @throws MPException
*/
protected static T processMethod(Class clazz, String methodName, String param1, String param2, Boolean useCache) throws MPException {
HashMap mapParams = new HashMap();
mapParams.put("param1", param1);
mapParams.put("param2", param2);
return processMethod(clazz, null, methodName, mapParams, useCache);
}
/**
* Process the method to call the api
*
* @param clazz a MPBase extended class
* @param resource an instance of the MPBase extended class, if its called from a non static method
* @param methodName a String with the decorated method to be processed
* @param mapParams a hashmap with the args passed in the call of the method
* @param useCache a Boolean flag that indicates if the cache must be used
* @return a resourse obj fill with the api response
* @throws MPException
*/
protected static T processMethod(Class clazz, T resource, String methodName, HashMap mapParams, Boolean useCache) throws MPException {
if (resource == null) {
try {
resource = (T) clazz.newInstance();
} catch (Exception ex) {
throw new MPException(ex);
}
}
//Validates the method executed
if (!ALLOWED_METHODS.contains(methodName)) {
throw new MPException("Method \"" + methodName + "\" not allowed");
}
AnnotatedElement annotatedMethod = getAnnotatedMethod(clazz, methodName);
HashMap hashAnnotation = getRestInformation(annotatedMethod);
HttpMethod httpMethod = (HttpMethod)hashAnnotation.get("method");
String path = parsePath(hashAnnotation.get("path").toString(), mapParams, resource);
int retries = Integer.valueOf(hashAnnotation.get("retries").toString());
int connectionTimeout = Integer.valueOf(hashAnnotation.get("connectionTimeout").toString());
int socketTimeout = Integer.valueOf(hashAnnotation.get("socketTimeout").toString());
if (METHODS_TO_VALIDATE.contains(methodName)) {
// Validator will throw an MPValidatorException, there is no need to do a conditional
MPValidator.validate(resource);
}
PayloadType payloadType = (PayloadType) hashAnnotation.get("payloadType");
JsonObject payload = generatePayload(httpMethod, resource);
Collection colHeaders = getStandardHeaders();
if (StringUtils.isNotEmpty(resource.getIdempotenceKey())) {
colHeaders.add(new BasicHeader("x-idempotency-key", resource.getIdempotenceKey()));
}
MPApiResponse response = callApi(httpMethod, path, payloadType, payload, colHeaders, retries, connectionTimeout, socketTimeout, useCache);
if (response.getStatusCode() >= 200 &&
response.getStatusCode() < 300) {
if (httpMethod != HttpMethod.DELETE) {
resource = fillResourceWithResponseData(resource, response);
} else {
resource = cleanResource(resource);
}
}
resource.lastApiResponse = response;
return resource;
}
/**
* Process method to call the api, usually used for loadAll and search methods
*
* @param clazz a MPBase extended class
* @param methodName a String with the decorated method to be processed
* @param useCache a Boolean flag that indicates if the cache must be used
* @return
* @throws MPException
*/
protected static MPResourceArray processMethodBulk(Class clazz, String methodName, Boolean useCache) throws MPException {
HashMap mapParams = null;
return processMethodBulk(clazz, methodName, mapParams, useCache);
}
/**
* Process method to call the api, usually used for loadAll and search methods
*
* @param clazz a MPBase extended class
* @param methodName a String with the decorated method to be processed
* @param param1 a String with the arg passed in the call of the method
* @param useCache a Boolean flag that indicates if the cache must be used
* @return
* @throws MPException
*/
protected static MPResourceArray processMethodBulk(Class clazz, String methodName, String param1, Boolean useCache) throws MPException {
HashMap mapParams = new HashMap();
mapParams.put("param1", param1);
return processMethodBulk(clazz, methodName, mapParams, useCache);
}
/**
* Process method to call the api, usually used for loadAll and search methods
*
* @param clazz a MPBase extended class
* @param methodName a String with the decorated method to be processed
* @param param1 a String with the arg passed in the call of the method
* @param param2 a String with the arg passed in the call of the method
* @param useCache a Boolean flag that indicates if the cache must be used
* @return
* @throws MPException
*/
protected static MPResourceArray processMethodBulk(Class clazz, String methodName, String param1, String param2, Boolean useCache) throws MPException {
HashMap mapParams = new HashMap();
mapParams.put("param1", param1);
mapParams.put("param2", param2);
return processMethodBulk(clazz, methodName, mapParams, useCache);
}
/**
* Process the method to call the api
*
* @param clazz a MPBase extended class
* @param methodName a String with the decorated method to be processed
* @param mapParams a hashmap with the args passed in the call of the method
* @param useCache a Boolean flag that indicates if the cache must be used
* @return a resourse obj fill with the api response
* @throws MPException
*/
protected static MPResourceArray processMethodBulk(Class clazz, String methodName, HashMap mapParams, Boolean useCache) throws MPException {
//Validates the method executed
if (!ALLOWED_BULK_METHODS.contains(methodName)) {
throw new MPException("Method \"" + methodName + "\" not allowed");
}
AnnotatedElement annotatedMethod = getAnnotatedMethod(clazz, methodName);
HashMap hashAnnotation = getRestInformation(annotatedMethod);
HttpMethod httpMethod = (HttpMethod)hashAnnotation.get("method");
String path = parsePath(hashAnnotation.get("path").toString(), mapParams, null);
int retries = Integer.valueOf(hashAnnotation.get("retries").toString());
int connectionTimeout = Integer.valueOf(hashAnnotation.get("connectionTimeout").toString());
int socketTimeout = Integer.valueOf(hashAnnotation.get("socketTimeout").toString());
PayloadType payloadType = (PayloadType) hashAnnotation.get("payloadType");
Collection colHeaders = getStandardHeaders();
MPApiResponse response = callApi(httpMethod, path, payloadType, null, colHeaders, retries, connectionTimeout, socketTimeout, useCache);
MPResourceArray resourceArray = new MPResourceArray();
if (response.getStatusCode() >= 200 &&
response.getStatusCode() < 300) {
resourceArray._resourceArray = fillArrayWithResponseData(clazz, response);
}
resourceArray.lastApiResponse = response;
return resourceArray;
}
/**
* Calls the api and returns an MPApiResponse.
*
* @param httpMethod the http method to be processed
* @param path a String with the full url of the endpoint
* @param payloadType a PayloadType obj
* @param payload a JsonObject with the content of the payload
* @param colHeaders a collection of headers
* @param retries int with nrs of retries
* @param connectionTimeout int with the connection timeout
* @param socketTimeout int with the socket timeout
* @param useCache a Boolean flag that indicates if the cache must be used
* @return
* @throws MPException
*/
private static MPApiResponse callApi(
HttpMethod httpMethod,
String path,
PayloadType payloadType,
JsonObject payload,
Collection colHeaders,
int retries,
int connectionTimeout,
int socketTimeout,
Boolean useCache) throws MPException {
String cacheKey = httpMethod.toString() + "_" + path;
MPApiResponse response = null;
if (useCache) {
response = MPCache.getFromCache(cacheKey);
}
if (response == null) {
response = new MPRestClient().executeRequest(
httpMethod,
path,
payloadType,
payload,
colHeaders,
retries,
connectionTimeout,
socketTimeout);
if (useCache) {
MPCache.addToCache(cacheKey, response);
} else {
MPCache.removeFromCache(cacheKey);
}
}
return response;
}
/**
* It fills all the attributes members of the Resource obj.
* Used when a Get or a Put request is called
*
* @param response Response of the request
* @throws MPException
*/
protected static T fillResourceWithResponseData(T resource, MPApiResponse response) throws MPException {
if (response.getJsonElementResponse() != null &&
response.getJsonElementResponse().isJsonObject()) {
JsonObject jsonObject = (JsonObject) response.getJsonElementResponse();
T resourceObject = MPCoreUtils.getResourceFromJson(resource.getClass(), jsonObject);
resource = fillResource(resourceObject, resource);
resource._lastKnownJson = MPCoreUtils.getJsonFromResource(resource);
}
return resource;
}
/**
* It fills an array with the resource objects from the api response
*
* @param clazz a MPBase extended class
* @param response MPApiResponse obj.
* @param
* @return
* @throws MPException
*/
protected static ArrayList fillArrayWithResponseData(Class clazz, MPApiResponse response) throws MPException {
ArrayList resourceArray = new ArrayList();
if (response.getJsonElementResponse() != null) {
JsonArray jsonArray = MPCoreUtils.getArrayFromJsonElement(response.getJsonElementResponse());
if (jsonArray != null) {
for (int i = 0; i < jsonArray.size(); i++) {
T resource = MPCoreUtils.getResourceFromJson(clazz, (JsonObject) jsonArray.get(i));
resource._lastKnownJson = MPCoreUtils.getJsonFromResource(resource);
resourceArray.add(resource);
}
}
}
return resourceArray;
}
/**
* Copies the atributes from an obj to a destination obj
*
* @param sourceResource source resource obj
* @param destinationResource destination resource obj
* @param
* @return
* @throws MPException
*/
private static T fillResource(T sourceResource, T destinationResource) throws MPException {
Field[] declaredFields = destinationResource.getClass().getDeclaredFields();
for (Field field : declaredFields) {
try {
Field originField = sourceResource.getClass().getDeclaredField(field.getName());
field.setAccessible(true);
originField.setAccessible(true);
field.set(destinationResource, originField.get(sourceResource));
} catch (Exception ex) {
throw new MPException(ex);
}
}
return destinationResource;
}
/**
* Removes all data from the attributes members of the Resource obj.
* Used when a delete request is called
*
* @throws MPException
*/
private static T cleanResource(T resource) throws MPException {
Field[] declaredFields = resource.getClass().getDeclaredFields();
for (Field field : declaredFields) {
try {
field.setAccessible(true);
field.set(resource, null);
} catch (Exception ex) {
throw new MPException(ex);
}
}
return resource;
}
/**
* Returns standard headers for all the requests
*
* @return a collection with headers objects
*/
private static Collection getStandardHeaders() {
Collection colHeaders = new Vector();
colHeaders.add(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
colHeaders.add(new BasicHeader(HTTP.USER_AGENT, "MercadoPago Java MercadoPago v1.0.1"));
return colHeaders;
}
/**
* Evaluates the path of the resourse and use the args or the attributes members of the instance to complete it.
* @param path a String with the path as stated in the declaration of the method caller
* @param mapParams a HashMap with the args passed in the call of the method
* @return a String with the final path to call the API
* @throws MPException
*/
private static String parsePath(String path, HashMap mapParams, T resource) throws MPException {
StringBuilder processedPath = new StringBuilder();
if (path.contains(":")) {
int paramIterator = 0;
while (path.contains(":")) {
paramIterator++;
processedPath.append(path.substring(0, path.indexOf(":")));
path = path.substring(path.indexOf(":") + 1);
String param = path;
if (path.contains("/")) {
param = path.substring(0, path.indexOf("/"));
}
String value = null;
if (paramIterator <= 2 &&
mapParams != null &&
StringUtils.isNotEmpty(mapParams.get("param" + String.valueOf(paramIterator)))) {
value = mapParams.get("param" + String.valueOf(paramIterator));
} else if (mapParams != null &&
StringUtils.isNotEmpty(mapParams.get(param))) {
value = mapParams.get(param);
} else {
if (resource != null) {
JsonObject json = MPCoreUtils.getJsonFromResource(resource);
if (json.get(param) != null) {
value = json.get(param).getAsString();
}
}
}
if (StringUtils.isEmpty(value)) {
throw new MPException("No argument supplied/found for method path");
}
processedPath.append(value);
if (path.contains("/")) {
path = path.substring(path.indexOf("/"));
} else {
path = "";
}
}
if (StringUtils.isNotEmpty(path)) {
processedPath.append(path);
}
} else {
processedPath.append(path);
}
// URL
processedPath.insert(0, MercadoPago.SDK.getBaseUrl());
// Token
String accessToken;
if (StringUtils.isNotEmpty(MercadoPago.SDK.getUserToken())) {
accessToken = MercadoPago.SDK.getUserToken();
} else {
accessToken = MercadoPago.SDK.getAccessToken();
}
processedPath
.append("?access_token=")
.append(accessToken);
if (!MPCoreUtils.validateUrl(processedPath.toString())) {
throw new MPException("Processed URL not valid: " + processedPath.toString());
}
return processedPath.toString();
}
/**
* Transforms all attributes members of the instance in a JSON String. Only for POST and PUT methods.
* POST gets the full object in a JSON object.
* PUT gets only the differences with the last known state of the object.
*
* @return a JSON Object with the attributes members of the instance. Null for GET and DELETE methods
*/
private static JsonObject generatePayload(HttpMethod httpMethod, T resource) {
JsonObject payload = null;
if (httpMethod.equals(HttpMethod.POST) ||
(httpMethod.equals(HttpMethod.PUT) && resource._lastKnownJson == null)) {
payload = MPCoreUtils.getJsonFromResource(resource);
} else if (httpMethod.equals(HttpMethod.PUT)) {
JsonObject actualJson = MPCoreUtils.getJsonFromResource(resource);
Type mapType = new TypeToken
© 2015 - 2025 Weber Informatics LLC | Privacy Policy