package org.smartly.commons.remoting.rest.wrapper;
import org.json.JSONObject;
import org.smartly.IConstants;
import org.smartly.Smartly;
import org.smartly.commons.cryptograph.MD5;
import org.smartly.commons.io.BinaryData;
import org.smartly.commons.io.serialization.json.JsonBean;
import org.smartly.commons.util.*;
import org.smartly.commons.remoting.rest.IRESTCons;
import org.smartly.commons.remoting.rest.annotations.*;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
public class MethodWrapper {
private static final String CHARSET = Smartly.getCharset();
private final Object _instance;
private final Method _method;
// fields
private String _id;
private String _http_method;
private String _path;
private String _path_id;
private String[] _path_params; // array of {param} in path
private String _type_output;
public MethodWrapper(final Object instance, final Method m) throws Exception {
try {
_instance = instance;
_method = m;
_type_output = IRESTCons.TYPE_JSON;
this.init(_method);
} catch (Throwable t) {
throw new Exception(FormatUtils.format("Error wrapping method '{0}': {1}", m.getName(), t), t);
}
}
@Override
public boolean equals(final Object obj) {
if (obj instanceof MethodWrapper) {
final MethodWrapper mobj = (MethodWrapper) obj;
return super.equals(obj) ||
(this.getId().equalsIgnoreCase(mobj.getId()));
}
return false;
}
@Override
public int hashCode() {
return 9 + this.getId().hashCode();
//(null!=_http_method?_http_method.hashCode():0) +
//(null!=_path?_path.hashCode():0);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(this.getClass().getName()).append("{");
sb.append("id=").append(this.getId());
sb.append(", ");
sb.append("path=").append(_path);
sb.append(", ");
sb.append("pathId=").append(_path_id);
sb.append(", ");
sb.append("httpMethod=").append(_http_method);
sb.append(", ");
sb.append("type=").append(_type_output);
sb.append(", ");
sb.append("name=").append(null != _method ? _method.getName() : "");
sb.append(", ");
sb.append("class=").append(null != _instance ? _instance.getClass().getSimpleName() : "");
sb.append("}");
return sb.toString();
}
public String getId() {
return _id;
}
public String getPath() {
return _path;
}
public String getPathId() {
return _path_id;
}
public String[] getPathParams() {
return _path_params;
}
public String getHttpMethod() {
return _http_method;
}
public String getTypeOutput() {
return _type_output;
}
public boolean match(final String httpMethod, final String url) {
if (_http_method.equalsIgnoreCase(httpMethod)) {
// remove service name, the root
final String path = removeRoot(url);
return this.match(path);
}
return false;
}
public byte[] execute(final String url, final Map<String, Object> formParams) throws IOException {
return this.execute(url, new JSONObject(formParams));
}
public byte[] execute(final String url, final JSONObject formParams) throws IOException {
Object result = null;
try {
// remove service name, the root
final String path = removeRoot(url);
final Map<String, String> urlParams = pluckParams(path, this.getPathParams());
result = this.execute(urlParams, formParams);
} catch (Throwable t) {
result = t;
}
return serialize(_type_output, result);
}
// ------------------------------------------------------------------------
// p r i v a t e
// ------------------------------------------------------------------------
private void init(final Method m) {
final Annotation[] annotations = m.getDeclaredAnnotations();
for (final Annotation a : annotations) {
if (a instanceof GET) {
_http_method = "GET";
} else if (a instanceof POST) {
_http_method = "POST";
} else if (a instanceof PUT) {
_http_method = "PUT";
} else if (a instanceof DELETE) {
_http_method = "DELETE";
} else if (a instanceof Path) {
final String value = ((Path) a).value();
_path = StringUtils.hasText(value) ? value : IRESTCons.DEFAULT_PATH;
} else if (a instanceof Produces) {
final String value = ((Produces) a).value();
if (null != value && value.length() > 0) {
_type_output = value;
} else {
_type_output = IRESTCons.TYPE_JSON;
}
}
}
// set path id
_path_id = getPathId(_path);
// count parameters in path
_path_params = getPathParams(_path);
// set method id
_id = MD5.encode((null != _path_id ? _path_id : "") + (null != _http_method ? _http_method : ""));
}
private boolean match(final String check) {
final String[] check_tokens = StringUtils.split(check, "/");
final String[] path_tokens = StringUtils.split(_path, "/");
if (check_tokens.length != path_tokens.length) {
return false;
}
for (int i = 0; i < check_tokens.length; i++) {
if (!check_tokens[i].equalsIgnoreCase(path_tokens[i]) && !isParam(path_tokens[i])) {
return false;
}
}
return true;
}
private Object execute(final Map<String, String> urlParams, final JSONObject formParams) throws IOException {
Object result = null;
try {
if (urlParams.isEmpty() && formParams.length() == 0) {
result = this.execute();
} else {
final Annotation[][] aparams = _method.getParameterAnnotations();
if (null != aparams && aparams.length > 0) {
final Object[] params = new Object[aparams.length];
Arrays.fill(params, "");
for (int i = 0; i < aparams.length; i++) {
final Annotation[] ap = aparams[i];
for (final Annotation a : ap) {
if (a instanceof PathParam) {
final String key = ((PathParam) a).value(); // getValue(a);
if (urlParams.containsKey(key)) {
params[i] = urlParams.get(key);
}
} else if (a instanceof FormParam) {
final String key = ((FormParam) a).value(); // getValue(a);
if (formParams.has(key)) {
params[i] = formParams.get(key).toString();
}
}
}
}
// now I should have an ordered array of parameters
result = this.execute(params);
} else {
result = this.execute();
}
}
} catch (Throwable t) {
result = t;
}
return result;
}
private Object execute(final Object... args) throws IOException {
Object result = null;
try {
result = _method.invoke(_instance, args);
} catch (InvocationTargetException ite) {
result = ite.getTargetException();
} catch (Throwable t) {
result = t;
}
return result;
}
// --------------------------------------------------------------------
// S T A T I C
// --------------------------------------------------------------------
private static byte[] serialize(final String type, final Object data) throws IOException {
if (!StringUtils.isNULL(data)) {
if (IRESTCons.TYPE_JSON.equalsIgnoreCase(type)) {
/*
if (StringUtils.isJSON(data)) {
final JsonBean json = new JsonBean(data);
return json.asJSONObject().toString().getBytes(CHARSET);
} else {
return ResponseWrapper.wrapToJSONResponse(data.toString()).toString().getBytes(CHARSET);
} */
return ResponseWrapper.wrapToJSONString(data).getBytes(CHARSET);
} else {
if (data instanceof BinaryData) {
final BinaryData bin_data = (BinaryData) data;
return bin_data.getBytes();
} else if (ByteUtils.isByteArray(data)) {
return (byte[]) data;
} else if (data instanceof String) {
return ((String) data).getBytes(Smartly.getCharset());
} else if (data instanceof InputStream) {
return ByteUtils.getBytes((InputStream) data);
} else {
return data.toString().getBytes(CHARSET);
}
}
} else {
if (IRESTCons.TYPE_JSON.equalsIgnoreCase(type)) {
return ResponseWrapper.wrapToJSONResponse(IConstants.NULL).toString().getBytes(CHARSET);
}
}
return IConstants.NULL.getBytes(CHARSET);
}
private static String getPathId(final String path) {
final StringBuilder sb = new StringBuilder();
final String[] tokens = StringUtils.split(path, "/");
for (final String t : tokens) {
if (sb.length() > 0) {
sb.append(".");
}
if (isParam(t)) {
sb.append("*");
} else {
sb.append(t);
}
}
return sb.toString();
}
private static String removeRoot(final String url) {
return PathUtils.splitPathRoot(url);
}
private static boolean isParam(final String path_token) {
return (path_token.trim().startsWith("{"));
}
private static String[] getPathParams(final String path) {
final List<String> result = new LinkedList<String>();
final String[] tokens = StringUtils.split(path, "/");
for (final String t : tokens) {
if (isParam(t)) {
result.add(t.substring(t.indexOf("{") + 1, t.indexOf("}")));
} else {
result.add(""); // empty space
}
}
return result.toArray(new String[result.size()]);
}
private static Map<String, String> pluckParams(final String path, final String[] path_params) {
final String[] tokens = StringUtils.split(path, "/");
final Map<String, String> result = new HashMap<String, String>();
if (tokens.length > 0 && tokens.length == path_params.length) {
for (int i = 0; i < path_params.length; i++) {
if (StringUtils.hasText(path_params[i])) {
result.put(path_params[i], tokens[i]);
}
}
}
return result;
}
private static String getValue(final Annotation annotation) {
try {
return (String) annotation.annotationType().getMethod("value").invoke(annotation);
} catch (Throwable ignored) {
}
return "";
}
}