Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
de.tsl2.nano.h5.ARESTDynamic Maven / Gradle / Ivy
package de.tsl2.nano.h5;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import de.tsl2.nano.action.IAction;
import de.tsl2.nano.bean.BeanContainer;
import de.tsl2.nano.bean.BeanUtil;
import de.tsl2.nano.bean.def.Bean;
import de.tsl2.nano.bean.def.BeanDefinition;
import de.tsl2.nano.bean.def.IAttributeDefinition;
import de.tsl2.nano.core.ENV;
import de.tsl2.nano.core.ISession;
import de.tsl2.nano.core.ManagedException;
import de.tsl2.nano.core.cls.PrimitiveUtil;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.util.MapUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;
import de.tsl2.nano.core.util.parser.JSon;
/**
* @author Thomas Schneider
* @param type of response depending on used HttpServer
*
*
* provides a cover for bean data access through rest. only JSON is allowed.
*
* example:
*
* POST:
* http://localhost:8067/rest/address/create
*
* {"city": "Buxdehude", "street": "Berliner Str.1"}
*
* GET:
* /rest/address/id/0815
* /rest/address/id/0815/city
* /rest/address/city/berlin
*
* PUT:
* /rest/address/id/0815/city/frankfurt
*
* DELETE:
* /rest/address/id/0815/delete
*
*
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public abstract class ARESTDynamic {
private static final Log LOG = LogFactory.getLog(ARESTDynamic.class);
static final String BODY = ENV.get("app.rest.payload.key", "postData");
public static String BASE_PATH = ENV.get("app.rest.basepath", "/rest");
static String USAGE = BASE_PATH + "/{entity}/{attribute-or-action}/{query}/{optional-output-attribute}/{PUT:value}";
static final String API_KEY = StringUtil.toHexString(StringUtil.cryptoHash(ENV.get("app.rest.apikey", "ein23einfacherrestdynamickey!")));
public static final String H5SESSION = "h5session";
enum Status {
OK(200), CREATED(201), BAD_REQUEST(400), UNAUTHORIZED(401), FORBIDDEN(403), NOT_FOUND(404), INTERNAL_ERROR(500);
int s; Status(int s) { this.s = s;} public int http() {return s;}
}
enum Methods {
GET, PUT, DELETE, POST, OPTIONS;
static String matchingExpression() {
StringBuilder buf = new StringBuilder();
Arrays.stream(values()).forEach(m -> buf.append("|" + m));
return buf.substring(1);
}
}
public static boolean canRest(String uri) {
return (uri.endsWith(BASE_PATH) || uri.contains(BASE_PATH + "/")) && ENV.get("app.rest.active", true);
}
RESPONSE serve(String url, String method, Map header) {
return serve(url, method, header, null);
}
RESPONSE serve(String url, String method, Map header, Map parms) {
return serve(url, method, header, parms, null);
}
RESPONSE serve(String url, String method, Map header, Map parms, Map payload) {
return serve(url, method, header, parms, payload, false);
}
RESPONSE serve(String url, String method, Map header, Map parms, Map payload, boolean internalCall) {
try {
if (url.equals(BASE_PATH) || method.equals("OPTIONS"))
return createResponse(Status.OK, printManual());
else if (url.equals(BASE_PATH + "/entities"))
return createResponse(Status.OK, printEntities());
else if (url.equals(BASE_PATH + "/entitiesjson"))
return createResponse(Status.OK, printEntitiesJSON());
if (!internalCall)
checkAuthentication(url, method, header);
checkMethod(method);
String beanName = get(url, BASE_PATH, "entity");
String actionOrAttribute = get(url, beanName, "attribute-or-action");
checkAuthorization(beanName, actionOrAttribute, header);
if (method.equals("POST"))
return doPost(url, method, payload, beanName, actionOrAttribute);
else
return doWithQuery(url, method, parms, beanName, actionOrAttribute);
} catch (SecurityException ex) {
LOG.error(ex);
return createResponse(Status.FORBIDDEN, ex.getMessage());
} catch (IllegalAccessException ex) {
LOG.error(ex);
return createResponse(Status.UNAUTHORIZED, ex.getMessage());
} catch (IllegalArgumentException ex) {
LOG.error(ex);
return createResponse(Status.BAD_REQUEST, ex.getMessage() + "\n" + printManual());
} catch (Exception ex) {
LOG.error(ex);
return createResponse(Status.INTERNAL_ERROR, ex.toString());
}
}
void checkAuthentication(String url, String method, Map header) throws SecurityException {
if (header == null)
throw new SecurityException("please provide a header map with Authorization");
String auth = header.get("authorization");
if (!Util.isEmpty(auth)) {
String[] split = auth.split("\\s");
String digest = split[split.length-1];
StringBuilder buf = new StringBuilder();
ISession session = (ISession) Util.untyped(header.get(H5SESSION));
if (session != null)
buf.append(session.getId().toString());
for (int i = 0; i < split.length-1; i++) {
buf.append(split[i]);
}
if (digest.equals(createDigest(url, method, buf.toString())))
return;
}
throw new SecurityException("not allowed!");
}
abstract void checkAuthorization(String beanName, String actionOrAttribute, Map header) throws IllegalAccessException;
abstract RESPONSE createResponse(Status status, String message);
RESPONSE doWithQuery(String url, String method, Map parms, String beanName, String actionOrAttribute) {
StringBuilder result = new StringBuilder();
Status status = Status.OK;
Collection instances = null;
String query = get(url, actionOrAttribute, "query");
instances = getBeans(beanName, actionOrAttribute, query);
String output;
if (!Util.isEmpty(instances)) {
for (Object instance : instances) {
switch (method) {
case "GET":
output = StringUtil.substring(url, query + "/", null, true, true);
if (output != null)
checkUrlEnd(url, output, "optional-output-attribute");
else
checkUrlEnd(url, query, "query");
result = result.append((output != null ? String.valueOf(Bean.getBean(instance).getAttribute(output).getValue())
: BeanUtil.toJSON(instance)) + ",");
break;
case "PUT":
output = get(url, query, "output-attribute");
String urlEnd = StringUtil.substring(url, query, null);
doPut(urlEnd, output, instance);
if (result.length() == 0)
result.append("changed (id): ");
result.append(Bean.getBean(instance).getId() + " ");
break;
case "DELETE":
checkUrlEnd(url, query, "query");
BeanContainer.instance().delete(instance);
if (result.length() == 0)
result.append("deleted (id): ");
result.append(Bean.getBean(instance).getId() + " ");
break;
default:
throw new IllegalArgumentException("unknown method");
}
}
} else {
status = Status.NOT_FOUND;
result.append(actionOrAttribute + ":" + query + " not found");
}
if (instances.size() > 0)
result.setLength(result.length()-1);
LOG.info("REST (" + method + ") " + url + " --> " + result);
return createResponse(status, result.toString());
}
void doPut(String url, String output, Object instance) {
String value = get(url, output, "value");
checkUrlEnd(url, value, "value");
Bean bean = Bean.getBean(instance);
bean.setValue(output, value);
bean.save();
}
RESPONSE doPost(String url, String method, Map payload, String beanName, String action) {
checkUrlEnd(url, action, "action");
String body = payload.get(BODY);
if (body == null)
throw new IllegalArgumentException("no body (app.rest.payload.key: '" + BODY + "') found in http parameters. must be present as payload!");
Status status = Status.CREATED;
Object result;
BeanDefinition def = getType(beanName);
Object instance = BeanUtil.fromJSON(def.getClazz(), body);
Bean bean = Bean.getBean(instance);
checkBean(bean);
action = action.equals("create") ? "save" : action;
if (bean.getActionByName(action) == null) {
status = Status.BAD_REQUEST;
List available = (List) bean.getActions().stream().map(a -> ((IAction)a).getShortDescription())
.collect(Collectors.toList());
result = "unknown post action '" + action + "'. available are: " + StringUtil.toString(available, -1);
} else {
result = bean.getActionByName(action).activate();
if (result != null && !PrimitiveUtil.isPrimitiveOrWrapper(result.getClass())) {
Bean resultBean = Bean.getBean(result);
result = "'" + action + "' successfull!\n\n" + new JSon().serialize(resultBean.toValueMap(null));
} else if (result == null) {
result = "'" + action + "' successfull!\n\n" + new JSon().serialize(bean.toValueMap(null));
}
}
LOG.info("REST (POST) " + url + " --> " + StringUtil.toString(result, 80));
return createResponse(status, String.valueOf(result));
}
void checkBean(Bean bean) {
if (bean.getAttributeNames().length == 0)
throw new IllegalStateException(bean.toString() + " has no attributes!");
if (!BeanContainer.instance().isPersistable(bean.getClazz()))
throw new IllegalStateException(bean.toString() + " is not persistable!");
}
String get(String url, String from, String name) {
String part = StringUtil.substring(url, from + "/", "/", false, false);
if (part == null)
throw new IllegalArgumentException("url part '" + name + "' must be present!");
return part;
}
void checkMethod(String method) {
ManagedException.assertion(method.matches(Methods.matchingExpression()), "http method must match one of {0}", Methods.matchingExpression());
}
void checkUrlEnd(String url, String from, String name) {
String part = StringUtil.substring(url, from, null, true, true);
if (!Util.isEmpty(part))
throw new IllegalArgumentException("url must end up with '" + name + "'!");
}
BeanDefinition getType(String beanName) {
BeanDefinition> beandef = BeanDefinition.getBeanDefinition(beanName);
if (beandef.isVirtual() && Util.isEmpty(beandef.getAttributeNames()))
throw new IllegalArgumentException("beanName is not a known entity!");
return beandef;
}
Collection getBeans(String beanName, String actionOrAttribute, String value) {
BeanDefinition type = getType(beanName);
Object instance = Bean.createInstance(type.getClazz());
Bean bean = Bean.getBean(instance);
checkBean(bean);
if (actionOrAttribute.equals("id") && bean.getAttribute(actionOrAttribute, false) == null)
actionOrAttribute = bean.getIdAttribute().getName();
bean.setParsedValue(actionOrAttribute, value);
return BeanContainer.instance().getBeansByExample(bean.getInstance());
}
public static String createDigest(String url, String method, String args) {
return StringUtil.toHexString(Util.cryptoHash((method + "+" + url + "+" + "+" + LocalDate.now() + args + "+" + API_KEY).getBytes()));
}
String printEntities() {
StringBuilder buf = new StringBuilder(
"\n-------- RESTDynamic available entities --- (back: " + ENV.get("service.url") + "/rest)---");
List beanTypes = ENV.get("service.loadedBeanTypes", new LinkedList());
BeanDefinition> beanDef;
for (Class b : beanTypes) {
beanDef = BeanDefinition.getBeanDefinition(b);
buf.append("\n" + beanDef.getName());
List> attributes = beanDef.getBeanAttributes();
for (IAttributeDefinition attr : attributes) {
buf.append("\n\t" + StringUtil.fixString(attr.getName(), 20) + ": " + attr.getDescription());
}
Collection actions = beanDef.getActions();
if (actions.size() > 0) {
buf.append("\n\tACTIONS");
for (IAction action : actions) {
buf.append("\n\t" + StringUtil.fixString(action.getShortDescription(), 20) + ": " + action.getLongDescription());
}
}
}
buf.append("\n---------------------------------------------------------------------------------\n");
return buf.toString();
}
String printEntitiesJSON() {
HashMap entityMap = new HashMap<>();
HashMap attributeMap = new HashMap<>();
HashMap actionMap = new HashMap<>();
List beanTypes = ENV.get("service.loadedBeanTypes", new LinkedList());
BeanDefinition> beanDef;
for (Class b : beanTypes) {
beanDef = BeanDefinition.getBeanDefinition(b);
attributeMap.clear();
List> attributes = beanDef.getBeanAttributes();
for (IAttributeDefinition attr : attributes) {
attributeMap.put(attr.getName(), attr.getType());
}
entityMap.put(beanDef.getName(), attributeMap);
Collection actions = beanDef.getActions();
for (IAction attr : actions) {
actionMap.put(attr.getShortDescription(), attr.getLongDescription());
}
attributeMap.put("actions", actionMap);
}
return MapUtil.toJSon(entityMap);
}
String printManual() {
return
"\n-------------------- RESTDynamic usage informations ------------------------------\n"
+ "\nREQUEST FORMAT: " + USAGE
+ "\n\t(login) : to login into a session, open: " + ENV.get("service.url")
+ "\n\tentities : metainfo as list of all available entities"
+ "\n\tentitiesjson : metainfo as json of all available entities"
+ "\n\t/restui : instead of 'rest' use 'restui' to do interaction with rest"
+ "\nGET,PUT,DELETE:"
+ "\n\tentity : simple class lower name of entity to be accessed"
+ "\n\tattribte-or-action : entities bean attribute name to be accessed"
+ "\n\tquery : query value for attribute"
+ "\n\toptional-output-attr: if only this attribute should be returned"
+ "\n\tPUT:value : value to be set on output-attribute"
+ "\n\texample-1 : GET:/rest/address/city/Buxde*"
+ "\n\texample-2 : GET:/rest/address/city/Buxdehude/street"
+ "\n\texample-3 : PUT:/rest/address/id/1/city/Berlin"
+ "\n\texample-4 : DELETE:/rest/address/id/1"
+ "\nPOST:"
+ "\n\tentity : simple class lower name of entity to be accessed"
+ "\n\tattribte-or-action : action 'create' or entity bean action name"
+ "\n\tPAYLOAD : 'postData' entry in payolad map - only JSON!"
+ "\n\texample-1 : POST:/rest/address/create"
+"\n\nHEADER:"
+ "\n\tauthorization : user date digest for basic authentication"
+ "\n\tuser : user for method authorization"
+ "\n\tpassword : password for method authorization"
+ "\n---------------------------------------------------------------------------------\n";
}
}