com.moesif.api.APIHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of moesifapi Show documentation
Show all versions of moesifapi Show documentation
Java API Library for Moesif
/*
* MoesifAPILib
*
*/
package com.moesif.api;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InvalidObjectException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import java.text.ParseException;
import java.text.ParsePosition;
import com.moesif.api.exceptions.APIException;
import com.mashape.unirest.http.Unirest;
import org.w3c.dom.Document;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
public class APIHelper {
/* used for async execution of API calls using a thread pool */
private static ExecutorService scheduler = null;
private static Object syncRoot = new Object();
/**
* Singleton access to the threadpool scheduler
* @return ExecutorService instance
*/
public static ExecutorService getScheduler() {
synchronized(syncRoot) {
if(null == scheduler) {
scheduler = Executors.newCachedThreadPool();
}
return scheduler;
}
}
/**
* Shutdown all the threads
*/
public static void shutdown() {
if(null != scheduler) {
scheduler.shutdown();
}
try {
Unirest.shutdown();
} catch (IOException e) {
//do nothing
}
}
/* used for deserialization of json data */
public static ObjectMapper mapper = new ObjectMapper()
{
private static final long serialVersionUID = -174113593500315394L;
{
configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
};
/**
* Parse a date from its string representation
* @param date ISO8601 encodede date string
* @return Parsed Date object
*/
public static Date parseDate(String date) throws ParseException
{
return com.fasterxml.jackson.databind.util.ISO8601Utils.parse(date, new ParsePosition(0));
}
/**
* Convert a date to an ISO8601 formatted string
* @param date Date object to format
* @return ISO8601 formatted date string
*/
public static String dateToString(Date date)
{
return com.fasterxml.jackson.databind.util.ISO8601Utils.format(date);
}
/**
* JSON Serialization of a given object.
* @param obj The object to serialize into JSON
* @return The serialized Json string representation of the given object
* @throws JsonProcessingException if error serializing JSON obj
*/
public static String serialize(Object obj)
throws JsonProcessingException {
if(null == obj)
return null;
return mapper.writeValueAsString(obj);
}
/**
* JSON Deserialization of the given json string.
* @param json The json string to deserialize
* @param The type of the object to deserialize into
* @param typeReference The TypeReference of the object to deserialize into
* @return The deserialized object
* @throws IOException if error deserializing
*/
public static T deserialize(String json, TypeReference typeReference)
throws IOException {
if (isNullOrWhiteSpace(json))
return null;
return mapper.readValue(json, typeReference);
}
/**
* JSON Deserialization of the given json string.
* @param json The json string to deserialize
* @param The type of the object to deserialize into
* @param typeReference The Class of the object to deserialize into
* @return The deserialized object
* @throws IOException if error deserializing
*/
public static T deserialize(String json, Class typeReference)
throws IOException {
if (isNullOrWhiteSpace(json))
return null;
return mapper.readValue(json, typeReference);
}
/**
* Populates an object of an APIException subclass with the required properties.
* @param json The json string to deserialize
* @param obj The object to populate
* @throws IOException if error deserializing
*/
public static void populate(String json, APIException obj)
throws IOException {
if (!isNullOrWhiteSpace(json))
mapper.readerForUpdating(obj).readValue(json);;
}
/**
* JSON Deserialization of the given json string.
* @param json The json string to deserialize
* @return The deserialized json as a Map
* @throws IOException if error deserializing
*/
public static LinkedHashMap deserialize(String json)
throws IOException {
if (isNullOrWhiteSpace(json))
return null;
TypeReference> typeRef
= new TypeReference>() {};
return deserialize(json, typeRef);
}
/**
* Replaces template parameters in the given url
* @param queryBuilder The query string builder to replace the template parameters
* @param parameters The parameters to replace in the url
*/
public static void appendUrlWithTemplateParameters(StringBuilder queryBuilder, Map parameters) {
//perform parameter validation
if (null == queryBuilder)
throw new IllegalArgumentException("Given value for parameter \"queryBuilder\" is invalid." );
if (null == parameters)
return;
//iterate and append parameters
for (Map.Entry pair : parameters.entrySet()) {
String replaceValue = "";
//load element value as string
if (null == pair.getValue())
replaceValue = "";
else if (pair.getValue() instanceof Collection>)
replaceValue = flattenCollection("", (Collection>) pair.getValue(), "%s%s%s", '/');
else if (pair.getValue() instanceof Date)
replaceValue = tryUrlEncode(dateToString((Date)pair.getValue()));
else
replaceValue = tryUrlEncode(pair.getValue().toString());
//find the template parameter and replace it with its value
replaceAll(queryBuilder, "{" + pair.getKey() + "}", replaceValue);
}
}
/**
* Appends the given set of parameters to the given query string
* @param queryBuilder The query url string to append the parameters
* @param parameters The parameters to append
*/
public static void appendUrlWithQueryParameters(StringBuilder queryBuilder, Map parameters) {
//perform parameter validation
if (null == queryBuilder)
throw new IllegalArgumentException("Given value for parameter \"queryBuilder\" is invalid.");
if (null == parameters)
return;
//does the query string already has parameters
boolean hasParams = (queryBuilder.indexOf("?") > 0) || (queryBuilder.indexOf("http") != 0);
queryBuilder.append((hasParams) ? '&' : '?');
encodeObjectAsQueryString("", parameters, queryBuilder);
}
/**
* Validates if the string is null, empty or whitespace
* @param s The string to validate
* @return The result of validation
*/
public static boolean isNullOrWhiteSpace(String s) {
if(s == null)
return true;
int length = s.length();
if (length > 0) {
for (int start = 0, middle = length / 2, end = length - 1; start <= middle; start++, end--) {
if (s.charAt(start) > ' ' || s.charAt(end) > ' ') {
return false;
}
}
return true;
}
return false;
}
/**
* Replaces all occurrences of the given string in the string builder
* @param stringBuilder The string builder to update with replaced strings
* @param toReplace The string to replace in the string builder
* @param replaceWith The string to replace with
*/
public static void replaceAll(StringBuilder stringBuilder, String toReplace, String replaceWith) {
int index = stringBuilder.indexOf(toReplace);
while (index != -1) {
stringBuilder.replace(index, index + toReplace.length(), replaceWith);
index += replaceWith.length(); // Move to the end of the replacement
index = stringBuilder.indexOf(toReplace, index);
}
}
/**
* Removes null values from the given map
* @param map the input map
*/
public static void removeNullValues(Map map) {
if(map == null)
return;
map.values().removeAll(Collections.singleton(null));
}
/**
* Validates and processes the given Url
* @param url The given Url to process
* @return Pre-process Url as string
*/
public static String cleanUrl(StringBuilder url)
{
//ensure that the urls are absolute
Pattern pattern = Pattern.compile("^(https?://[^/]+)");
Matcher matcher = pattern.matcher(url);
if (!matcher.find())
throw new IllegalArgumentException("Invalid Url format.");
//get the http protocol match
String protocol = matcher.group(1);
//remove redundant forward slashes
String query = url.substring(protocol.length());
query = query.replaceAll("//+", "/");
//return process url
return protocol.concat(query);
}
/**
* Prepares Array style form fields from a given array of values
* @param value Value for the form fields
* @return Dictionary of form fields created from array elements
*/
public static Map prepareFormFields(Object value) {
Map formFields = new LinkedHashMap();
if(value != null) {
try {
objectToMap("", value, formFields, new HashSet());
} catch (Exception ex) {
}
}
return formFields;
}
/**
* Get the current version of the moesifapi-java artifact
* @return Version string
*/
public synchronized static String getVersion() {
String mavenPackage = "com.moesif";
String mavenArtifact = "moesifapi-java";
// Try to get version number from pom.xml (available in Eclipse)
try {
String className = MoesifAPIClient.class.getName();
String classfileName = "/" + className.replace('.', '/') + ".class";
URL classfileResource = MoesifAPIClient.class.getResource(classfileName);
if (classfileResource != null) {
Path absolutePackagePath = Paths.get(classfileResource.toURI())
.getParent();
int packagePathSegments = className.length()
- className.replace(".", "").length();
// Remove package segments from path, plus two more levels
// for "target/classes", which is the standard location for
// classes in Eclipse.
Path path = absolutePackagePath;
for (int i = 0, segmentsToRemove = packagePathSegments + 2;
i < segmentsToRemove; i++) {
path = path.getParent();
}
Path pom = path.resolve("pom.xml");
InputStream is = Files.newInputStream(pom);
Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().parse(is);
doc.getDocumentElement().normalize();
String version = (String) XPathFactory.newInstance()
.newXPath().compile("/project/version")
.evaluate(doc, XPathConstants.STRING);
if (version != null) {
version = version.trim();
if (!version.isEmpty()) {
return version;
}
}
}
} catch (Exception e) {
// Ignore
}
// Try to get version number from maven properties in jar's META-INF
try {
InputStream is = MoesifAPIClient.class
.getResourceAsStream("/META-INF/maven/" + mavenPackage + "/"
+ mavenArtifact + "/pom.properties");
if (is != null) {
Properties p = new Properties();
p.load(is);
String version = p.getProperty("version", "").trim();
if (!version.isEmpty()) {
return version;
}
}
} catch (Exception e) {
// Ignore
}
// Fallback to using Java API to get version from MANIFEST.MF
String version = null;
Package pkg = MoesifAPIClient.class.getPackage();
if (pkg != null) {
version = pkg.getImplementationVersion();
if (version == null) {
version = pkg.getSpecificationVersion();
}
}
version = version == null ? "" : version.trim();
return version.isEmpty() ? "unknown" : version;
}
/**
* Encodes a given object to url encoded string
* @param name
* @param obj
* @param objBuilder
*/
private static void encodeObjectAsQueryString(String name, Object obj, StringBuilder objBuilder) {
try {
if(obj == null)
return;
Map objectMap = new LinkedHashMap();
objectToMap(name, obj, objectMap, new HashSet());
boolean hasParam = false;
for (Map.Entry pair : objectMap.entrySet()) {
String paramKeyValPair;
//ignore nulls
Object value = pair.getValue();
if(value == null)
continue;
hasParam = true;
//load element value as string
paramKeyValPair = String.format("%s=%s&", pair.getKey(), tryUrlEncode(value.toString()));
objBuilder.append(paramKeyValPair);
}
//remove the last &
if(hasParam) {
objBuilder.setLength(objBuilder.length() - 1);
}
} catch (Exception ex) {
}
}
/**
* Used for flattening a collection of objects into a string
* @param array Array of elements to flatten
* @param fmt Format string to use for array flattening
* @param separator Separator to use for string concat
* @return Representative string made up of array elements
*/
private static String flattenCollection(String elemName, Collection> array, String fmt, char separator) {
StringBuilder builder = new StringBuilder();
//append all elements in the array into a string
for (Object element : array) {
String elemValue = null;
//replace null values with empty string to maintain index order
if (null == element) {
elemValue = "";
} else if (element instanceof Date) {
elemValue = dateToString((Date)element);
} else {
elemValue = element.toString();
}
elemValue = tryUrlEncode(elemValue);
builder.append(String.format(fmt, elemName, elemValue, separator));
}
//remove the last separator, if appended
if ((builder.length() > 1) && (builder.charAt(builder.length() - 1) == separator))
builder.deleteCharAt(builder.length() - 1);
return builder.toString();
}
/**
* Tries Url encode using UTF-8
* @param value The value to url encode
* @return
*/
private static String tryUrlEncode(String value) {
try {
return URLEncoder.encode(value, "UTF-8");
}catch (Exception ex) {
return value;
}
}
/**
* Converts a given object to a form encoded map
* @param objName Name of the object
* @param obj The object to convert into a map
* @param objectMap The object map to populate
* @param processed List of objects hashCodes that are already parsed
* @throws InvalidObjectException
*/
private static void objectToMap(
String objName, Object obj, Map objectMap, HashSet processed)
throws InvalidObjectException {
//null values need not to be processed
if(obj == null)
return;
//wrapper types are autoboxed, so reference checking is not needed
if(!isWrapperType(obj.getClass())) {
//avoid infinite recursion
if(processed.contains(obj.hashCode()))
return;
processed.add(obj.hashCode());
}
//process arrays
if(obj instanceof Collection>) {
//process array
if((objName == null) ||(objName.isEmpty()))
throw new InvalidObjectException("Object name cannot be empty");
Collection> array = (Collection>) obj;
//append all elements in the array into a string
int index = 0;
for (Object element : array) {
//load key value pair
String key = String.format("%s[%d]", objName, index++);
loadKeyValuePairForEncoding(key, element, objectMap, processed);
}
} else if(obj.getClass().isArray()) {
//process array
if((objName == null) ||(objName.isEmpty()))
throw new InvalidObjectException("Object name cannot be empty");
Object[] array = (Object[]) obj;
//append all elements in the array into a string
int index = 0;
for (Object element : array) {
//load key value pair
String key = String.format("%s[%d]", objName, index++);
loadKeyValuePairForEncoding(key, element, objectMap, processed);
}
} else if(obj instanceof Map) {
//process map
Map, ?> map = (Map, ?>) obj;
//append all elements in the array into a string
for (Map.Entry, ?> pair : map.entrySet()) {
String attribName = pair.getKey().toString();
String key = attribName;
if((objName != null) && (!objName.isEmpty())) {
key = String.format("%s[%s]", objName, attribName);
}
loadKeyValuePairForEncoding(key, pair.getValue(), objectMap, processed);
}
} else if(obj instanceof UUID) {
String key = objName;
String value = obj.toString();
//UUIDs can be converted to string
loadKeyValuePairForEncoding(key, value, objectMap, processed);
} else if (obj instanceof Date) {
String key = objName;
String value = dateToString((Date)obj);
//UUIDs can be converted to string
loadKeyValuePairForEncoding(key, value, objectMap, processed);
} else {
//process objects
// invoke getter methods
Method[] methods = obj.getClass().getMethods();
for (Method method : methods) {
//is a getter?
if ((method.getParameterTypes().length != 0)
|| (!method.getName().startsWith("get")))
continue;
//get json attribute name
Annotation getterAnnotation = method.getAnnotation(JsonGetter.class);
if (getterAnnotation == null)
continue;
//load key name
String attribName = ((JsonGetter) getterAnnotation).value();
String key = attribName;
if ((objName != null) && (!objName.isEmpty())) {
key = String.format("%s[%s]", objName, attribName);
}
try {
//load key value pair
Object value = method.invoke(obj);
loadKeyValuePairForEncoding(key, value, objectMap, processed);
} catch (Exception ex) {
}
}
// load fields
Field[] fields = obj.getClass().getFields();
for (Field field : fields) {
//load key name
String attribName = field.getName();
String key = attribName;
if ((objName != null) && (!objName.isEmpty())) {
key = String.format("%s[%s]", objName, attribName);
}
try {
//load key value pair
Object value = field.get(obj);
loadKeyValuePairForEncoding(key, value, objectMap, processed);
} catch (Exception ex) { }
}
}
}
/**
* While processing objects to map, decides whether to perform recursion or load value
* @param key The key to used for creating key value pair
* @param value The value to process against the given key
* @param objectMap The object map to process with key value pair
* @param processed List of processed objects hashCodes
* @throws InvalidObjectException
*/
private static void loadKeyValuePairForEncoding(
String key, Object value, Map objectMap, HashSet processed)
throws InvalidObjectException {
if(value == null)
return;
if (isWrapperType(value.getClass()))
objectMap.put(key, value);
else
objectToMap(key, value, objectMap, processed);
}
/**
* List of classes that are wrapped directly. This information is need when
* traversing object trees for reference matching
*/
private static final Set WRAPPER_TYPES = new HashSet(Arrays.asList(
Boolean.class, Character.class, Byte.class, Short.class, String.class,
Integer.class, Long.class, Float.class, Double.class, Void.class, File.class));
/**
* Checks if the given class can be wrapped directly
* @param clazz The given class
* @return true if the given class is an autoboxed class e.g., Integer
*/
private static boolean isWrapperType(Class clazz) {
return WRAPPER_TYPES.contains(clazz) || clazz.isPrimitive() || clazz.isEnum();
}
}