
com.viaoa.web.servlet.OARestServlet Maven / Gradle / Ivy
package com.viaoa.web.servlet;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.bind.JAXBContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.viaoa.annotation.OAClassFilter;
import com.viaoa.context.OAContext;
import com.viaoa.context.OAUserAccess;
import com.viaoa.datasource.OASelect;
import com.viaoa.filter.OAAndFilter;
import com.viaoa.hub.CustomHubFilter;
import com.viaoa.hub.Hub;
import com.viaoa.json.OAJson;
import com.viaoa.object.OAFinder;
import com.viaoa.object.OAObject;
import com.viaoa.object.OAObjectCallbackDelegate;
import com.viaoa.object.OAObjectInfo;
import com.viaoa.object.OAObjectInfoDelegate;
import com.viaoa.object.OAPropertyInfo;
import com.viaoa.object.OAThreadLocalDelegate;
import com.viaoa.util.OAConv;
import com.viaoa.util.OAFilter;
import com.viaoa.util.OAPropertyPath;
import com.viaoa.util.OAReflect;
import com.viaoa.util.OAString;
import com.viaoa.web.filter.OAUserAccessFilter;
import com.viaoa.web.servlet.exception.OAServletException;
/*
?query=
?pp=
?ppx=
?from={class}&id={id}[&path={path}]
?filter=
?useRefId[=bool]
*/
/**
* Servlet that allows REST API calls using HTTP for the OAModel. Has support for user access to object graph using property paths.
*
* GET ===========
* 1) read object using name and Id
* http://localhost/oarest/{className/{id}
* http://localhost/oarest/{className/{id}?pp={x}&pp1={x}&ppN={x} pp = PropertyPath(s) from the ClassName of reference data to send.
* http://localhost/oarest/client/1
* http://localhost/oarest/clients?fromClass=company&fromId=1&fromPath=clients&pp=products
* http://localhost/oarest/order/1?pp=lines.item.vendor&pp=client.contacts
*
* 2) get objects using a property path from a base object
* http://localhost/oarest/{pluralClassName}?fromClass={fromClass&fromId={id}?pp={x}&pp1={x}&ppN={x}
* http://localhost:/oarest/clients?fromClass=company&fromId=1&fromPath=clients&pp=products
*
* 3) query objects using where clause
* http://localhost/oarest/{pluralClassName}?query={whereClause}&pp1={x}&ppN={x}
* http://localhost/oarest/clients?query=company.name like 'AAA*'&pp=products
* http://localhost/oarest/clients?query=company.name like 'AAA*'&orderBy=id&max=5&pp=products
* http://localhost/oarest/clients?query=company.id>?'&queryParam=1&orderBy=id&pp=products
*
* 4) filter support
* http://localhost/oarest/campaigns?filter=unassigned
*
* Also:
* useRefId[=bool] can be used if client supports managing refId in json response/body.
*
* POST ===========
* create new object
* http://localhost/oarest/{className}
*
* PUT ===========
* update an existing object
* http://localhost/oarest/{className}[/{id}]
*
* call a Remote method from a registered object:
* oarest/oaremote?remoteClassName=employeedelegate&remoteClassMethod=giveRaise
*
* Call a method on an Object:
* oarest/employee?objectMethodName=giveRaise
*
* DELETE ===========
* delete existing object
* http://localhost/oarest/{className}/{id}
*
*
* @author vvia
*/
public class OARestServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private static Logger LOG = Logger.getLogger(OARestServlet.class.getName());
private String packageName;
private ServletConfig servletConfig;
private String contextPath;
private HashMap hmClassName = new HashMap();
private HashMap hmClassPluralName = new HashMap();
private String httpCORS; // "*" for all
private boolean bJaxbIncludeOwnedReferences = true;
protected HashMap hmRemoteObject = new HashMap<>();
/** default setting for JAXB marshalling to use refIds */
private boolean bJaxbUseReferences;
public OARestServlet(String packageName) {
LOG.fine("Started packageName=" + packageName);
this.packageName = packageName;
setupMappings();
}
public void setJaxbUseReferences(boolean b) {
this.bJaxbUseReferences = b;
}
public boolean getJaxbUseReferences() {
return bJaxbUseReferences;
}
public void setJaxbIncludeOwnedReferences(boolean b) {
this.bJaxbIncludeOwnedReferences = b;
}
public boolean getJaxbIncludeOwnedReferences() {
return bJaxbIncludeOwnedReferences;
}
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
this.servletConfig = config;
contextPath = config.getServletContext().getContextPath();
try {
//qqqqqqqqqqqqqqqqqqqqq needs to include package names for other oaobjects ... search, inputs, etc qqqqqqqqqqqqqqqq
String[] fnames = OAReflect.getClasses(packageName);
for (String fn : fnames) {
Class c = Class.forName(packageName + "." + fn);
OAObjectInfo oi = OAObjectInfoDelegate.getObjectInfo(c);
hmClassName.put(fn.toLowerCase(), c);
String s = oi.getPluralName().toLowerCase();
hmClassPluralName.put(s, c);
if (!s.equals(fn.toLowerCase() + "s")) {
hmClassPluralName.put(fn.toLowerCase() + "s", c);
s = ", (also: " + c.getName() + "s)";
} else {
s = "";
}
LOG.fine("adding class=" + c.getName() + ", plural name=" + oi.getPluralName() + s);
}
} catch (Exception e) {
ServletException se = new ServletException("Exception getting class infos for package", e);
LOG.log(Level.WARNING, "Could not initialize REST Servlet", se);
throw se;
}
LOG.fine("init called, contextPath=" + contextPath);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
process(req, resp);
}
@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
process(req, resp);
}
@Override
public void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
process(req, resp);
}
@Override
public void doDelete(HttpServletRequest req, HttpServletResponse resp) throws IOException {
process(req, resp);
}
@Override
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String s2 = req.getHeader("Access-Control-Request-Headers");
if (OAString.isNotEmpty(s2)) {
resp.setHeader("Access-Control-Allow-Headers", s2);
}
s2 = req.getHeader("Access-Control-Request-Method");
if (OAString.isNotEmpty(s2)) {
resp.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
}
/*qqqqqqqqqqqqqqqqq finish CORS, let it be configured ... create model object "RESTServlet"
https://dev.to/effingkay/cors-preflighted-requests--options-method-3024
header('Access-Control-Allow-Origin: *');
header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept");
header('Access-Control-Allow-Methods: GET, POST, PUT');
https://developer.mozilla.org/en-US/docs/Glossary/preflight_request
ex: request
OPTIONS /resource/foo
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: origin, x-requested-with
Origin: https://foo.bar.org
HTTP/1.1 204 No Content
Connection: keep-alive
Access-Control-Allow-Origin: https://foo.bar.org
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
Access-Control-Allow-Headers: Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since
Access-Control-Max-Age: 86400
*/
process(req, resp);
}
/**
* Set the HTTP Header "Access-Control-Allow-Origin" (ex: "http://localhost:4200"), default = "*" (for now)
*/
public String getHttpCORS() {
return httpCORS;
}
public void setHttpCORS(String val) {
this.httpCORS = val;
}
/*qqqqqqqqq
private AppUser appUser;
protected AppUser getRestAppUser() {
if (appUser != null) return appUser;
AppUser au = (AppUser) ModelDelegate.getAppUsers().find(AppUserPP.loginId(), "restapi");
if (au == null) {
au = new AppUser();
au.setLoginId("restapi");
au.setPassword("restapi#3");
au.setAdmin(false);
ModelDelegate.getAppUsers().add(au);
appUser = au;
}
OAContext.setContext(this, appUser);
return appUser;
}
**/
private OAUserAccess userAccess;
protected OAUserAccess getUserAccess() {
if (userAccess != null) {
return userAccess;
}
// http://localhost:8088/pi/api/clients
userAccess = new OAUserAccess(true, true);
/*
userAccess = new OAUserAccess(false, true);
userAccess.setValidPackage(Campaign.class.getPackage()); // only for PI project
OAContext.setContextUserAccess(this, userAccess);
Class[] classes = new Class[] {
AppUser.class,
AppUserLogin.class,
AppServer.class,
CompanyPlatformAccess.class,
ClientPlatformInfo.class
};
for (Class c : classes) {
userAccess.addNotVisible(c);
userAccess.addNotEnabled(c);
}
//qqqqqqqqqq this needs to use users Company ... and store in concurrentHM by company
Company company = ModelDelegate.getCompanies().find(CompanyPP.name(), "*dent*");
OAUserAccess userAccess2 = new OAUserAccess();
userAccess.addUserAccess(userAccess2);
// userAccess2.addVisible(company, CompanyPP.clients().products().campaigns().campaignLinks().platformCampaign().platformVendor().pp);
userAccess2.addEnabled(company, CompanyPP.clients().products().campaigns().pp, null, true);
userAccess2.addEnabled(company, CompanyPP.clients().products().campaigns().campaignLinks().pp, null, true);
userAccess2.addEnabled(company, CompanyPP.clients().products().campaigns().campaignLinks().platformCampaign().pp, null, true);
//qqqqqqqqq : check for servlet session, add HTTP basic auth
*/
return userAccess;
}
/**
* Process the http request
*/
protected void process(final HttpServletRequest req, final HttpServletResponse resp) {
HttpSession httpSession = req.getSession();
System.out.println("Session => " + httpSession);
//qqqqqqqqq check basic auth
String s = getHttpCORS();
if (OAString.isNotEmpty(s)) {
resp.addHeader("Access-Control-Allow-Origin", s); // ex: "http://localhost:4200");
}
try {
//qqqq getRestAppUser(); // make sure that it's initialized
getUserAccess(); // " "
//qqqq OAThreadLocalDelegate.setContext(this);
resp.setCharacterEncoding("UTF-8");
resp.setContentType("application/json");
int status = _process(req, resp);
resp.setStatus(status);
} catch (OAServletException ex) {
LOG.log(Level.FINE, "OAServletException OAREST", ex);
resp.setStatus(ex.getHttpStatusCode());
onException(resp, ex);
} catch (Exception ex) {
LOG.log(Level.WARNING, "error processing OAREST", ex);
resp.setStatus(resp.SC_INTERNAL_SERVER_ERROR);
onException(resp, ex);
} finally {
OAThreadLocalDelegate.setContext(null);
}
}
protected void onException(final HttpServletResponse resp, final Exception exception) {
if (resp == null) {
return;
}
OutputStream out;
try {
out = resp.getOutputStream();
} catch (Exception ex) {
LOG.log(Level.WARNING, "error getting outputstream while processing REST", ex);
resp.setStatus(resp.SC_INTERNAL_SERVER_ERROR);
return;
}
PrintWriter pw = new PrintWriter(out);
resp.setContentType("application/json");
pw.print("{");
pw.print(" \"Exception\": {");
pw.print(" \"message\": \"" + OAString.convert(exception.toString(), "\"", "") + "\"");
pw.print(" }");
pw.print("}");
pw.flush(); // but dont close
}
/**
* used to MAP a REST endpoint to an OAModel API call.
*/
static public class Mapping {
String description;
String pathInfo; // "/ui/campaigns"
// value from query to match
String name; // "filter"
String value; // "open", "closed"
boolean bDefault;
String methodType; // get,post,put,etc
String className;
final ArrayList alPropertyPath = new ArrayList<>();
// String cors;
String query; // null=no query, a "" (blank) is to select all.
OAFilter filter;
}
private ArrayList alMapping = new ArrayList();
public void addMapping(Mapping mapping) {
alMapping.add(mapping);
}
/**
* Create custom REST endpoints for FrontEnd. todo: move to controller to allow configuration.
*/
public void setupMappings() {
/*qqqqqqqqqqq
Mapping mapx = new Mapping();
mapx.description = "array of unassigned campaigns";
mapx.methodType = "get";
mapx.pathInfo = "/ui/campaigns";
mapx.name = "type";
mapx.value = "unassigned";
mapx.bDefault = true;
mapx.className = "campaigns";
mapx.alPropertyPath.add("product");
mapx.alPropertyPath.add("client.company");
mapx.alPropertyPath.add("campaignLinks.platformCampaign.platformVendor");
mapx.alPropertyPath.add("buyer");
mapx.query = ""; // all
mapx.filter = new OAFilter() {
@Override
public boolean isUsed(Object obj) {
Campaign camp = (Campaign) obj;
if (camp == null) return false;
return camp.getCampaignLinks().size() == 0;
}
};
addMapping(mapx);
mapx = new Mapping();
mapx.description = "array of assigned campaigns";
mapx.methodType = "get";
mapx.pathInfo = "/ui/campaigns";
mapx.name = "type";
mapx.value = "assigned";
mapx.bDefault = false;
mapx.className = "campaigns";
mapx.alPropertyPath.add("product");
mapx.alPropertyPath.add("client.company");
mapx.alPropertyPath.add("campaignLinks.platformCampaign.platformVendor");
mapx.alPropertyPath.add("buyer");
mapx.query = ""; // all
mapx.filter = new OAFilter() {
@Override
public boolean isUsed(Object obj) {
Campaign camp = (Campaign) obj;
if (camp == null) return false;
return camp.getCampaignLinks().size() > 0;
}
};
addMapping(mapx);
mapx = new Mapping();
mapx.description = "campaign and detail";
mapx.methodType = "get";
mapx.pathInfo = "/ui/campaignDetail";
mapx.className = "campaigns";
mapx.alPropertyPath.add("product");
mapx.alPropertyPath.add("client.company");
mapx.alPropertyPath.add("campaignLinks.platformCampaign.platformVendor");
mapx.alPropertyPath.add("buyer");
mapx.query = ""; // all
mapx.filter = new OAFilter() {
@Override
public boolean isUsed(Object obj) {
Campaign camp = (Campaign) obj;
if (camp == null) return false;
return camp.getCampaignLinks().size() > 0;
}
};
addMapping(mapx);
//
mapx = new Mapping();
mapx.description = "unassigned platform campaigns";
mapx.methodType = "get";
mapx.pathInfo = "/ui/unassignedPlatformCampaigns";
mapx.className = "platformCampaigns";
mapx.alPropertyPath.add(PlatformCampaignPP.platformVendor().pp);
mapx.query = ""; // all
mapx.filter = new OAFilter() {
@Override
public boolean isUsed(Object obj) {
PlatformCampaign camp = (PlatformCampaign) obj;
if (camp == null) return false;
OADate date = new OADate();
for (CampaignLink cl : camp.getCampaignLinks()) {
if (date.between(cl.getStartDate(), cl.getEndDate())) return false;
}
return true;
}
};
addMapping(mapx);
*/
}
protected int _process(final HttpServletRequest req, final HttpServletResponse resp) throws Exception {
int httpStatus = HttpServletResponse.SC_OK;
final String methodType = req.getMethod();
final String contextPath = req.getContextPath(); // "/pi"
final String requestUrl = req.getRequestURL().toString(); // "http://localhost:8088/pi/api/ui/campaigns/123"
final String url = req.getRequestURI(); // "/pi/api/ui/campaigns/123"
String httpQueryString = req.getQueryString(); // "filter=open&max=3&flag"
final String authType = req.getAuthType();
final String pathInfo = req.getPathInfo(); // "/ui/campaigns/123"
final String contentType = OAString.notNull(req.getContentType()).toLowerCase();
final OutputStream out = resp.getOutputStream();
final PrintWriter pw = new PrintWriter(out);
String className = OAString.field(pathInfo, "/", 2);
//qqqqqqqqqqqqqqqqqqqqqqq
final boolean bOARemote = "oaremote".equalsIgnoreCase(className);
if (bOARemote) {
className = null;
}
final String originalRequest = requestUrl + (OAString.isEmpty(httpQueryString) ? "" : ("?" + httpQueryString)); // "http://localhost:8088/pi/api/ui/campaigns/123?filter=open&max=3&flag"
LOG.fine(String.format("Method=%s, url=%s, query=%s", methodType, requestUrl, httpQueryString));
Enumeration enumx = req.getHeaderNames();
for (; enumx.hasMoreElements();) {
String key = (String) enumx.nextElement();
LOG.finer("Header: " + key + ", " + req.getHeader(key));
}
enumx = req.getParameterNames();
HashMap hmParam = new HashMap<>();
for (; enumx.hasMoreElements();) {
String key = (String) enumx.nextElement();
hmParam.put(key.toLowerCase(), req.getParameter(key));
}
boolean bUseRefId = bJaxbUseReferences;
boolean bUseOwned = getJaxbIncludeOwnedReferences();
// get list of extra propertyPaths to include, for OAJaxb
ArrayList alPropertyPath = new ArrayList<>();
enumx = req.getParameterNames();
for (; enumx.hasMoreElements();) {
String key = (String) enumx.nextElement();
if (!key.toLowerCase().startsWith("pp")) {
if (key.equalsIgnoreCase("useRefId")) {
String s = req.getParameter(key);
bUseRefId = (s == null) || OAConv.toBoolean(s);
}
if (key.equalsIgnoreCase("owned")) {
String s = req.getParameter(key);
bUseOwned = (s == null) || OAConv.toBoolean(s);
}
continue;
}
if (key.length() > 2) {
String s = key.substring(2);
if (!OAString.isInteger(s)) {
continue;
}
}
String[] ss = req.getParameterValues(key);
if (ss == null) {
continue;
}
for (String s : ss) {
if (s != null) {
alPropertyPath.add(s);
}
}
}
// check to see if there is a custom Mapping for this URL
OAFilter filterQuery = null;
String description = originalRequest;
String query = hmParam.get("query");
String orderBy = hmParam.get("orderBy");
String max = hmParam.get("max");
final String[] queryParams = req.getParameterValues("queryParam");
for (Mapping map : alMapping) {
if (!methodType.equalsIgnoreCase(map.methodType)) {
continue;
}
if (pathInfo == null || map.pathInfo == null) {
continue;
}
if (!pathInfo.equalsIgnoreCase(map.pathInfo)) {
continue;
}
if (map.name != null) {
String s = hmParam.get(map.name);
if (map.value != null) {
if (!map.value.equalsIgnoreCase(s)) {
continue;
}
} else {
if (!map.bDefault) {
continue;
}
}
}
for (String s : map.alPropertyPath) {
alPropertyPath.add(s);
}
description = map.description;
className = map.className;
query = map.query;
filterQuery = map.filter;
break;
}
LOG.fine(String.format("description=%s, query=%s", description, query));
boolean bIsMany = false;
Class clazz = null;
if (className != null) {
clazz = hmClassName.get(className.toLowerCase());
if (clazz == null) {
hmClassName.get("detail" + className.toLowerCase());
}
if (clazz == null) {
clazz = hmClassPluralName.get(className.toLowerCase());
if (clazz == null) {
hmClassPluralName.get("detail" + className.toLowerCase());
}
bIsMany = true;
}
}
if (clazz == null && !bOARemote) {
onException(resp, new Exception("Class not found, name=" + className));
httpStatus = HttpServletResponse.SC_BAD_REQUEST;
return httpStatus;
}
OAJson oaj = null;
if (!bOARemote) {
oaj = new OAJson();
oaj.addPropertyPaths(alPropertyPath);
oaj.setIncludeOwned(bUseOwned);
LOG.fine("clazz=" + clazz.getName());
}
/*was
OAJaxb jaxb = null;
if (!bOARemote) {
LOG.fine("clazz=" + clazz.getName());
jaxb = new OAJaxb<>(clazz);
jaxb.setUseReferences(bUseRefId);
jaxb.setIncludeGuids(false);
jaxb.setIncludeOwned(bUseOwned);
jaxb.setIncludeNewChangedDeletedFlags(true);
for (String s : alPropertyPath) {
jaxb.addPropertyPath(s);
}
}
*/
String jsonInput = null;
byte[] bsInput = null;
if ("application/octet-stream".equalsIgnoreCase(req.getContentType())) {
int length = req.getContentLength();
bsInput = new byte[length];
req.getInputStream().read(bsInput);
} else {
StringBuilder sb = new StringBuilder();
BufferedReader reader = req.getReader();
try {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append('\n');
}
} finally {
reader.close();
}
jsonInput = sb.toString();
}
String jsonOutput = null;
// name in method to call
final String objectMethodName = hmParam.get("objectMethodName".toLowerCase());
if (bOARemote) {
// ../oaremote?remoteClass=asdfa&remoteMethod=asdfs&pp=asd.ewr.wers
String remoteClassName = hmParam.get("remoteClassName".toLowerCase());
String remoteMethodName = hmParam.get("remoteMethodName".toLowerCase());
Object objResult = callRemoteMethod(remoteClassName, remoteMethodName, jsonInput, bsInput);
/*was
jaxb = null;
if (objResult instanceof OAObject) {
jaxb = new OAJaxb<>(objResult.getClass());
} else if (objResult instanceof Hub) {
jaxb = new OAJaxb<>(((Hub) objResult).getObjectClass());
}
*/
if (oaj != null) {
if (objResult instanceof OAObject) {
jsonOutput = oaj.write((OAObject) objResult);
} else if (objResult instanceof Hub) {
jsonOutput = oaj.write(objResult);
} else {
jsonOutput = oaj.write((Hub) objResult);
}
}
/*was:
if (jaxb != null) {
jaxb.setIncludeNewChangedDeletedFlags(true);
jaxb.setUseReferences(bUseRefId);
jaxb.setIncludeGuids(false);
jaxb.setIncludeOwned(bUseOwned);
for (String s : alPropertyPath) {
jaxb.addPropertyPath(s);
}
if (objResult instanceof OAObject) {
jsonOutput = jaxb.convertToJSON((OAObject) objResult);
} else {
jsonOutput = jaxb.convertToJSON((Hub) objResult);
}
} else {
jsonOutput = OAJsonMapper.convertObjectToJson(objResult);
}
*/
} else if ("put".equalsIgnoreCase(methodType) && !bIsMany) {
// ========== PUT ===========
/*was: qqqq need to do this
jaxb.setLoadingMode(OAJaxb.LoadingMode.UpdateRootOnly);
OAObject obj = (OAObject) jaxb.convertFromJSON(jsonInput);
*/
OAObject obj = (OAObject) oaj.readObject(jsonInput, clazz, true);
if (obj != null) {
obj.save();
jsonOutput = oaj.write((OAObject) obj);
// /was: jsonOutput = jaxb.convertToJSON((OAObject) obj);
} else {
httpStatus = HttpServletResponse.SC_NOT_FOUND;
}
} else if (objectMethodName != null) {
Object objResult = callObjectMethod(clazz, pathInfo, objectMethodName, jsonInput);
jsonOutput = oaj.write(objResult);
//was: jsonOutput = OAJsonMapper.convertObjectToJson(objResult);
} else if ("post".equalsIgnoreCase(methodType) && !bIsMany) {
// ========== POST ===========
//was: qqqqq ??need to do this: jaxb.setLoadingMode(OAJaxb.LoadingMode.CreateNewRootOnly);
OAObject obj = (OAObject) oaj.readObject(jsonInput, clazz, true);
//was: OAObject obj = (OAObject) jaxb.convertFromJSON(jsonInput);
if (obj != null) {
obj.save();
}
jsonOutput = oaj.write((OAObject) obj);
//was: jsonOutput = jaxb.convertToJSON((OAObject) obj);
} else if ("delete".equalsIgnoreCase(methodType) && !bIsMany) {
// ========== DELETE =========== qqqqqqqqqqq todo qqqqqqqqqq
// call objectCallback to validate qqqqqqqqq
} else if ("get".equalsIgnoreCase(methodType)) {
// ========== GET ===========
final String id = OAString.field(pathInfo, "/", 3);
if (!bIsMany || OAString.isNotEmpty(id)) {
Object obj = null;
// might be multipart id
OAObjectInfo oi = OAObjectInfoDelegate.getOAObjectInfo(clazz);
String sql = "";
ArrayList al = new ArrayList();
for (String idName : oi.getIdProperties()) {
OAPropertyInfo pi = oi.getPropertyInfo(idName);
String s = OAString.field(pathInfo, "/", 3 + al.size());
if (OAString.isEmpty(s)) {
if (al.size() > 0) {
s = al.get(0); // see if it's one field separated by '-'
s = OAString.field(s, "-", al.size() + 1);
}
if (OAString.isEmpty(s)) {
sql = null;
break;
}
}
al.add(s);
if (OAString.isNotEmpty(sql)) {
sql += " AND ";
}
sql += pi.getName() + " = ?";
}
if (OAString.isNotEmpty(sql)) {
OASelect sel = new OASelect(clazz);
sel.select(sql, al.toArray(new String[0]));
obj = sel.next();
}
if (obj != null) {
if (!OAObjectCallbackDelegate.getAllowVisible(null, (OAObject) obj, null)) {
httpStatus = HttpServletResponse.SC_UNAUTHORIZED;
} else {
jsonOutput = oaj.write((OAObject) obj);
//was: jsonOutput = jaxb.convertToJSON((OAObject) obj);
}
}
} else {
Hub h = new Hub(clazz);
if (OAString.isNotEmpty(query)) {
if (query.equals("\"\"") || query.equals("''")) {
query = null;
}
if (query != null && query.length() > 2 && (query.charAt(0) == '\"' || query.charAt(0) == '\'')) {
query = query.substring(1, query.length() - 1); // remove quotes from quoted string
}
}
String fromClass = hmParam.get("fromclass");
if (OAString.isEmpty(fromClass)) {
fromClass = hmParam.get("from");
}
String fromId = hmParam.get("fromid");
if (OAString.isEmpty(fromId)) {
fromId = hmParam.get("id");
}
String fromPath = hmParam.get("frompath");
if (OAString.isEmpty(fromPath)) {
fromPath = hmParam.get("path");
if (OAString.isEmpty(fromPath)) {
fromPath = className;
}
}
String filterName = hmParam.get("filter");
if (OAString.isNotEmpty(filterName)) {
OAPropertyPath propertyPath = new OAPropertyPath(clazz, ":" + filterName);
// match filters
String[] names = propertyPath.getFilterNames();
Object[] values = propertyPath.getFilterParamValues();
Constructor[] constructors = propertyPath.getFilterConstructors();
if (constructors.length != 1 || constructors[0] == null) {
throw new Exception("Filter not found, name=" + filterName);
}
try {
if (values[0] == null) {
// propertyPath will use the constructor (hub,hub), need to change to no params constructor
Parameter[] params = constructors[0].getParameters();
if (params != null && params.length == 2 && params[0].getType().equals(Hub.class)
&& params[1].getType().equals(Hub.class)) {
constructors[0] = constructors[0].getDeclaringClass().getConstructor(null);
}
}
CustomHubFilter customHubFilter;
if (values[0] == null) {
customHubFilter = (CustomHubFilter) constructors[0].newInstance(null);
} else {
customHubFilter = (CustomHubFilter) constructors[0].newInstance(values[0]);
}
OAFilter filter = new OAFilter() {
@Override
public boolean isUsed(Object obj) {
return customHubFilter.isUsed(obj);
}
};
OAClassFilter classFilter = customHubFilter.getClass().getAnnotation(OAClassFilter.class);
String s = classFilter.query();
if (OAString.isNotEmpty(s)) {
if (OAString.isNotEmpty(query)) {
query += " AND ";
}
query += s;
}
if (filterQuery == null) {
filterQuery = filter;
} else {
filterQuery = new OAAndFilter(filterQuery, filter);
}
} catch (Exception e) {
throw new IllegalArgumentException("Filter " + names[0] + " can not be created", e);
}
}
OASelect select;
if (OAString.isNotEmpty(query)) {
select = new OASelect(clazz);
select.setWhere(query);
select.setParams(queryParams);
select.setFilter(filterQuery);
select.setOrder(orderBy);
if (OAString.isNotEmpty(max)) {
select.setMax(OAConv.toInt(max));
}
h.select(select);
} else if (OAString.isEmpty(fromClass)) {
select = new OASelect(clazz);
select.setFilter(filterQuery);
if (OAString.isNotEmpty(max)) {
select.setMax(OAConv.toInt(max));
}
h.select(select);
} else {
clazz = hmClassName.get(fromClass.toLowerCase());
if (clazz == null) {
onException(resp, new Exception("Class not found, name=" + className));
return httpStatus;
}
select = new OASelect(clazz);
select.select("id == ?", new Object[] { fromId });
Object obj = select.next();
if (obj != null) {
if (fromPath.indexOf(".") < 0) {
obj = ((OAObject) obj).getProperty(fromPath);
if (obj instanceof Hub) {
h = (Hub) obj;
} else {
h.add(obj);
}
} else {
OAFinder finder = new OAFinder((OAObject) obj, fromPath);
ArrayList al = finder.find();
for (Object objx : al) {
h.add(objx);
}
}
}
}
boolean b = true;
for (Object obj : h) {
if (!OAObjectCallbackDelegate.getAllowVisible(null, (OAObject) obj, null)) {
b = false;
httpStatus = HttpServletResponse.SC_UNAUTHORIZED;
break;
}
}
if (b) {
jsonOutput = oaj.write(h);
//was: jsonOutput = jaxb.convertToJSON(h);
}
}
}
if (jsonOutput == null) {
if (httpStatus == HttpServletResponse.SC_OK) {
httpStatus = HttpServletResponse.SC_NOT_FOUND;
}
} else {
pw.write(jsonOutput);
}
resp.setStatus(httpStatus);
pw.flush(); // but dont close
return httpStatus;
}
// not used
// todo: qqqqqqq get code for Multipart and x-www-form-urlencoded
protected int _process(final String methodName, final String url, String contentType, final Map mapParam,
final PrintWriter printWriter, final HttpServletRequest req, final HttpServletResponse resp) throws Exception {
if (contentType == null) {
contentType = "";
} else {
contentType = contentType.toLowerCase();
}
//qqqqqqqqqqqqqqqqqqqqqqq
// req.getReader().;
//qqqqqqqqqqqqqq payload, only used if not using params
//qqqqqqqqq https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
// resp.addHeader("Access-Control-Allow-Origin", "http://localhost:4200");
boolean bUseParams = false;
boolean bUseReader = false;
boolean bUseMultipart = false;
boolean bUseInputStream = false;
boolean bUseXML = false;
boolean bUseJSON = false;
//qqqqqqqqqq get User, could be null
// need to know if req.getParameter should be used, or InputStream/Reader
if ("GET".equalsIgnoreCase(methodName)) {
bUseParams = true;
// parse URL
} else {
// But only if the POST data is encoded as key-value pairs of
// content type: "application/x-www-form-urlencoded" like when you use a standard HTML form.
if (contentType.indexOf("x-www-form-urlencoded") >= 0) {
bUseParams = true;
} else if (contentType.indexOf("multipart/form-data") >= 0) {
bUseParams = true;
bUseMultipart = true;
} else {
bUseInputStream = true;
}
}
/* if JSON, XML, text
else allow inputStream to be sent to method
could be multi-part ????
multipart/form-data
*/
if (contentType.indexOf("xml") >= 0) {
bUseXML = true;
if (bUseInputStream) {
bUseInputStream = false;
if (!bUseParams) {
bUseReader = true;
}
}
} else if (contentType.indexOf("json") >= 0) {
bUseJSON = true;
if (bUseInputStream) {
bUseInputStream = false;
if (!bUseParams) {
bUseReader = true;
}
}
} else if (contentType.indexOf("text") >= 0) {
if (bUseInputStream) {
bUseInputStream = false;
if (!bUseParams) {
bUseReader = true;
}
}
}
LOG.fine(String.format("request methodName=%s, url=%s, contentType=%s, bUseParams=%b", methodName, url, contentType, bUseParams));
String ignoreName = null;
if (req != null) {
ignoreName = req.getServletContext().getContextPath();
String s = req.getServletPath();
if (OAString.isNotEmpty(s)) {
if (!s.startsWith("/")) {
ignoreName += "/";
}
ignoreName += s;
}
if (OAString.isNotEmpty(s) && !s.endsWith("/")) {
ignoreName += "/";
}
}
if (ignoreName == null) {
ignoreName = "/vendor-api/";
}
String s = url.substring(ignoreName.length());
String requestName = OAString.field(s, "/", 1);
LOG.fine("requestName=" + requestName);
String argsURI = null;
if (requestName.indexOf("?") > 0) {
requestName = OAString.field(requestName, "?", 1);
} else {
argsURI = OAString.field(s, "/", 2, 99);
}
final int dcntURI = OAString.dcount(argsURI, "/");
JAXBContext jaxbContext = null;
String valString = null;
int result = HttpServletResponse.SC_OK;
// result = HttpServletResponse.SC_ACCEPTED;
// result = HttpServletResponse.SC_NO_CONTENT;
// resp.setContentType("text/plain");
/*
if (jaxbContext == null) {
// if (allXMLPackages == null) jaxbContext = JAXBContext.newInstance(objResult.getClass());
jaxbContext = JAXBContext.newInstance(allXMLPackages);
}
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
// https://timjansen.github.io/jarfiller/guide/jaxb/xmlfragments.xhtml
// marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
StringWriter sw = new StringWriter();
*/
/*
String qname = objResult.getClass().getSimpleName();
qname = convertXMLQName(qname);
JAXBElement jele = new JAXBElement(new QName(qname), objResult.getClass(), objResult);
marshaller.marshal(jele, sw);
objResult = sw.toString();
if (allXMLPackages == null) jaxbContext = null; // dont reuse
*/
// printWriter.print(objResult + "");
return result;
}
protected String convertXMLQName(String qname) {
if (qname != null && qname.toUpperCase().endsWith("TYPE")) {
qname = qname.substring(0, qname.length() - 4);
}
return qname;
}
private String allXMLPackages;
/**
* List of packages (name spaces) used for XML.
*
* @param allXMLPackages separated by ":"
*/
public void setAllXMLPackageNames(String allXMLPackages) {
this.allXMLPackages = allXMLPackages;
}
/**
* This is always called first.
*/
@Override
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
final HttpSession session = request.getSession(true);
// verify that user has been set
OAObject user = (OAObject) session.getAttribute(OAUserAccessFilter.KEY_OAWebUser);
if (user == null) {
throw new RuntimeException(
"Session web user (" + OAUserAccessFilter.KEY_OAWebUser + ") is not defined");
}
OAObject user2 = OAContext.getContextObject();
if (user != user2) {
throw new RuntimeException("Session context user (" + OAUserAccessFilter.KEY_OAContextUser + ") is not defined");
}
super.service(request, response);
}
protected Object callObjectMethod(final Class clazz, final String pathInfo, final String methodName, final String jsonBody)
throws Exception {
// id could be multipart with "-" seperator
final String id = OAString.field(pathInfo, "/", 3);
Object obj = null;
// might be multipart id
OAObjectInfo oi = OAObjectInfoDelegate.getOAObjectInfo(clazz);
String sql = "";
ArrayList al = new ArrayList();
for (OAPropertyInfo pi : oi.getPropertyInfos()) {
if (!pi.getId()) {
continue;
}
String s = OAString.field(pathInfo, "/", 3 + al.size());
if (OAString.isEmpty(s)) {
if (al.size() > 0) {
s = al.get(0); // see if it's one field separated by '-'
s = OAString.field(s, "-", al.size() + 1);
}
if (OAString.isEmpty(s)) {
sql = null;
break;
}
}
al.add(s);
if (OAString.isNotEmpty(sql)) {
sql += " AND ";
}
sql += pi.getName() + " = ?";
}
if (OAString.isNotEmpty(sql)) {
OASelect sel = new OASelect(clazz);
sel.select(sql, al.toArray(new String[0]));
obj = sel.next();
}
int httpStatus;
if (obj == null) {
String s = String.format( "Object not found, class=%s, method=%s, id=%s, sql to find it=%s",
clazz.getSimpleName(), methodName, id, sql);
throw new OAServletException(s, HttpServletResponse.SC_NOT_FOUND, null);
}
if (!OAObjectCallbackDelegate.getAllowVisible(null, (OAObject) obj, methodName)) {
String s = String.format( "method not authorized (visible=false), class=%s, method=%s, id=%s",
clazz.getSimpleName(), methodName, id);
throw new OAServletException(s, HttpServletResponse.SC_UNAUTHORIZED, null);
}
Method method = OAObjectInfoDelegate.getMethod(oi, methodName);
if (method == null) {
throw new RuntimeException("method " + methodName + " not found in class " + clazz.getSimpleName());
}
Object[] args = OAJson.convertJsonToMethodArguments(jsonBody, method);
Object objResult = method.invoke(obj, args);
return objResult;
}
protected Object callRemoteMethod(final String className, final String methodName, final String jsonBody, final byte[] bsBody)
throws Exception {
Object obj = hmRemoteObject.get(className.toUpperCase());
if (obj == null) {
throw new OAServletException("remote object for className=" + className + " not found", HttpServletResponse.SC_NOT_FOUND);
}
Method method = OAReflect.getMethod(obj.getClass(), methodName);
int[] iSkip = null;
//qqqqqqqqqqqqqqqqqqqq
/* todo: find out which method params to skip qqqqqqqqqqq
Parameter[] ps = method.getParameters();
if (ps != null) {
for (Parameter p : ps) {
if (s.equals(p.getType()))
}
}
*/
// OAJson oajson;
// OAJsonArrayNode nodeArray;
OAJson oaj;
ArrayNode nodeArray;
if (OAString.isNotEmpty(jsonBody)) {
oaj = new OAJson();
ObjectMapper om = oaj.getObjectMapper();
nodeArray = om.createArrayNode();
/*
oajson = new OAJson();
nodeArray = oajson.loadArray(jsonBody);
*/
} else {
oaj = null;
nodeArray = null;
}
Object[] objs;
if (bsBody != null) {
Parameter[] mps = method.getParameters();
if (mps == null) {
return null;
}
objs = new Object[mps.length];
int i = -1;
for (Parameter param : mps) {
i++;
Class paramClass = param.getType();
if (!paramClass.isArray() || !paramClass.getComponentType().equals(byte.class)) {
continue;
}
objs[i] = bsBody;
break;
}
} else {
objs = OAJson.convertJsonToMethodArguments(nodeArray, method, iSkip);
}
Object result = method.invoke(obj, objs);
return result;
}
public void registerRemoteObject(Object obj) throws Exception {
//qqqqqqqqqq need to load meta data ... *Info classes ... to get which method params are used qqqqqqqqqqqqq
if (obj == null) {
throw new NullPointerException("remote is required");
}
hmRemoteObject.put(obj.getClass().getSimpleName().toUpperCase(), obj);
Class[] cs = obj.getClass().getInterfaces();
if (cs != null) {
for (Class c : cs) {
hmRemoteObject.put(c.getSimpleName().toUpperCase(), obj);
}
}
}
}