495 lines
16 KiB
Java
495 lines
16 KiB
Java
package de.hsadmin.cliClientConnector;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.IOException;
|
|
import java.lang.reflect.Method;
|
|
import java.text.DateFormat;
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Locale;
|
|
import java.util.Map;
|
|
|
|
import javax.servlet.ServletConfig;
|
|
import javax.servlet.ServletException;
|
|
import javax.servlet.http.HttpServlet;
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
|
import org.apache.commons.codec.binary.Base64;
|
|
|
|
import de.hsadmin.core.model.AbstractEntity;
|
|
import de.hsadmin.core.model.GenericModuleImpl;
|
|
import de.hsadmin.core.model.ModuleInterface;
|
|
import de.hsadmin.core.model.TicketValidator;
|
|
import de.hsadmin.core.model.Transaction;
|
|
|
|
/**
|
|
* actually this is the core of the CLI-Client. The other CLI-Client is just a
|
|
* rather simple HTTP Client that calls this Servlet.
|
|
*
|
|
* @author Christof Donat
|
|
*
|
|
*/
|
|
public class CLIClientConnectorServlet extends HttpServlet {
|
|
|
|
private static final long serialVersionUID = 7150004719303750077L;
|
|
public static final String version = "CLI Servlet 2.0.0 (2011/May/21 09:00 MEST)";
|
|
|
|
private Map<String, Class<?>> componentmap;
|
|
private Map<String, String> componentDescriptions;
|
|
private ArgumentParser parser;
|
|
|
|
/**
|
|
* Servlet initialization
|
|
*/
|
|
public void init(ServletConfig cfg) {
|
|
// init ticket validator
|
|
String validateURL = cfg.getInitParameter("proxyValidateUrl");
|
|
String serviceURL = cfg.getInitParameter("proxyServiceUrl");
|
|
TicketValidator.getInstance().initialize(validateURL, serviceURL);
|
|
// find components
|
|
String cstring = cfg.getInitParameter("Components");
|
|
String[] components = cstring.split(",");
|
|
|
|
componentmap = new HashMap<String, Class<?>>();
|
|
componentDescriptions = new HashMap<String, String>();
|
|
for (int i = 0; i < components.length; i++) {
|
|
// get everything for this component and create an entry.
|
|
try {
|
|
// component class
|
|
String classname = cfg.getInitParameter("ComponentClass_"
|
|
+ components[i]);
|
|
if (classname == null)
|
|
throw new NullPointerException(
|
|
"no class name found for Component "
|
|
+ components[i]);
|
|
Class<?> cls = Class.forName(classname);
|
|
componentmap.put(components[i], cls);
|
|
// description
|
|
String descr = cfg.getInitParameter("ComponentDescription_"
|
|
+ components[i]);
|
|
if (descr != null)
|
|
componentDescriptions.put(components[i], descr);
|
|
else
|
|
componentDescriptions.put(components[i], "");
|
|
} catch (ClassNotFoundException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
// TODO: get username, password from http session
|
|
parser = new ArgumentParser(this);
|
|
}
|
|
|
|
/**
|
|
* set values to a given entity Object
|
|
*
|
|
* @param o
|
|
* the entity Object
|
|
* @param set
|
|
* Hashtable with names and values that sould be changed in o
|
|
*/
|
|
private void setValues(Object o, Map<String, String> set, ModuleInterface module) {
|
|
Iterator<String> keys = set.keySet().iterator();
|
|
while (keys.hasNext()) {
|
|
String key = keys.next();
|
|
String[] ns = key.split("[.]", 2);
|
|
Object realO = o;
|
|
for (int i = 0; i < ns.length - 1; i++) {
|
|
Method[] m = realO.getClass().getMethods();
|
|
boolean oFound = false;
|
|
for (int j = 0; j < m.length; j++) {
|
|
if (m[j].getName().toLowerCase().equals(
|
|
"get" + ns[i].toLowerCase())) {
|
|
try {
|
|
realO = m[j].invoke(realO, (Object[]) null);
|
|
oFound = (realO != null);
|
|
break;
|
|
} catch (Exception e) {
|
|
new TechnicalException(e);
|
|
}
|
|
}
|
|
}
|
|
if (!oFound)
|
|
break;
|
|
}
|
|
Method[] m = realO.getClass().getMethods();
|
|
String value = set.get(key);
|
|
for (int j = 0; j < m.length; j++) {
|
|
if (!m[j].getName().toLowerCase().equals(
|
|
"set" + ns[ns.length - 1].toLowerCase()))
|
|
continue;
|
|
String type = m[j].getParameterTypes()[0].getCanonicalName();
|
|
try {
|
|
if (type.equals("java.lang.String"))
|
|
m[j].invoke(realO, value);
|
|
else if (type.equals("java.lang.Integer") || type.equals("int"))
|
|
m[j].invoke(realO, Integer.parseInt(value));
|
|
else if (type.equals("java.lang.Long") || type.equals("long"))
|
|
m[j].invoke(realO, Long.parseLong(value));
|
|
else if (type.equals("java.lang.Boolean") || type.equals("boolean"))
|
|
m[j].invoke(realO, Boolean.valueOf(value));
|
|
else if (type.equals("java.util.Date")) {
|
|
DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, Locale.GERMANY);
|
|
m[j].invoke(realO, df.parse(value));
|
|
} else {
|
|
Method m2 = module.getClass().getMethod(
|
|
"findByString", Class.class, String.class);
|
|
Object entity =
|
|
m2.invoke(module, m[j].getParameterTypes()[0], value);
|
|
if (entity != null)
|
|
m[j].invoke(realO, entity);
|
|
else
|
|
throw new BusinessException(
|
|
"not object found for '" + value + "'");
|
|
}
|
|
} catch (Exception e) {
|
|
throw new TechnicalException(e); // TODO: this needs to be
|
|
// more specific for
|
|
// some cases
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private String hasGetter(Class<?> eType, String name) {
|
|
String rval = null;
|
|
String[] ns = name.split("[.]", 2);
|
|
String n1 = ns[0];
|
|
Method meth = null;
|
|
|
|
for (Method m : eType.getMethods()) {
|
|
String n = m.getName();
|
|
if (n.startsWith("get")) {
|
|
String fn = m.getName().substring(3).toLowerCase();
|
|
if (fn != "class" && fn.equals(n1)) {
|
|
meth = m;
|
|
rval = fn;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (meth != null) {
|
|
Class<?> returnType = meth.getReturnType();
|
|
if (rval != null && ns.length > 1 && meth != null)
|
|
return hasGetter(returnType, ns[1]);
|
|
if (returnType.getCanonicalName().startsWith("de.hsadmin.mods")) {
|
|
try {
|
|
if (returnType.getMethod("getName") != null) {
|
|
return rval + ".name";
|
|
}
|
|
} catch (Exception e) {
|
|
// no method found
|
|
}
|
|
}
|
|
}
|
|
return rval;
|
|
}
|
|
|
|
/**
|
|
* builds a query from a where clause and some objectIDs.
|
|
*
|
|
* @param eType
|
|
* The class of the Entity Object for which the string should be
|
|
* built. We need this to call the static method
|
|
* "createQueryFromStringKey"
|
|
* @param where
|
|
* Hashtable with where parameters. Only objects which match all
|
|
* where parameters are found
|
|
* @param oids
|
|
* Only objects with one of these object IDs are found
|
|
*
|
|
* @return queryString a query string that can be used to select the
|
|
* required Objects
|
|
* @throws ServletException
|
|
*/
|
|
private String buildQuery(Class<?> eType, Map<String, String> where,
|
|
ArrayList<String> oids) throws ServletException {
|
|
String rval = "";
|
|
|
|
boolean first = true;
|
|
Iterator<String> wkeys = where.keySet().iterator();
|
|
while (wkeys.hasNext()) {
|
|
String k = (String) wkeys.next();
|
|
String kname = hasGetter(eType, k);
|
|
if (kname != null) {
|
|
rval += (first ? "" : " and ")
|
|
+ "(obj." + AbstractEntity.escapeString(kname) + " = '" + AbstractEntity.escapeString(where.get(k)) + "')";
|
|
first = false;
|
|
} else {
|
|
throw new ServletException("illegal input (unknown field: " + k + ")");
|
|
}
|
|
}
|
|
|
|
String rv = "";
|
|
if (oids != null)
|
|
try {
|
|
Method m;
|
|
m = eType.getMethod("createQueryFromStringKey", String.class);
|
|
|
|
first = true;
|
|
for (String s : oids) {
|
|
rv += (first ? "" : " or ") + "("
|
|
+ (String) m.invoke(eType, s) + ")";
|
|
first = false;
|
|
}
|
|
if (rv != "" && rval != "")
|
|
rval = rval + " and (" + rv + ")";
|
|
else if (rv != "")
|
|
rval = rv;
|
|
} catch (Exception e) {
|
|
throw new TechnicalException(e);
|
|
}
|
|
|
|
return (rval == "") ? null : rval;
|
|
}
|
|
|
|
public class FunctionNotKnownException extends Exception {
|
|
private static final long serialVersionUID = -6330015688609717838L;
|
|
}
|
|
|
|
public class UnknownModuleException extends Exception {
|
|
private static final long serialVersionUID = 696641072107896601L;
|
|
}
|
|
|
|
private Object callAdd(Class<?> cls, Map<String, String> set, ModuleInterface module) {
|
|
Transaction transaction = module.getTransaction();
|
|
transaction.beginTransaction();
|
|
try {
|
|
Method m = module.getClass().getMethod("add", AbstractEntity.class);
|
|
Object o = cls.newInstance();
|
|
setValues(o, set, module);
|
|
m.invoke(module, o);
|
|
transaction.commitTransaction();
|
|
return null;
|
|
} catch (Exception e) {
|
|
transaction.rollbackTransaction();
|
|
// TODO: this needs to be more specific, but how?
|
|
throw new TechnicalException(e);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private List<Object> callSearch(Class<?> cls, Map<String, String> where,
|
|
ArrayList<String> oids, ModuleInterface module) {
|
|
try {
|
|
Method m = module.getClass().getMethod("search", Class.class,
|
|
String.class, String.class);
|
|
return (List<Object>) m.invoke(module, cls,
|
|
buildQuery(cls, where, oids), null);
|
|
} catch (Exception e) {
|
|
throw new TechnicalException(e); // TODO: this needs to be more
|
|
// specific, but how?
|
|
}
|
|
}
|
|
|
|
// / checks wheather all 'oids' are in 'list'
|
|
private void checkOids(List<AbstractEntity> list, List<String> oids) {
|
|
List<String> oidsNotFound = new ArrayList<String>();
|
|
for (String id : oids) {
|
|
boolean found = false;
|
|
for (AbstractEntity e : list) {
|
|
String foundKey = e.createStringKey();
|
|
if (foundKey.equals(id)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
oidsNotFound.add(id);
|
|
}
|
|
if (oidsNotFound.size() > 0) {
|
|
throw new OidsNotFoundException(oids);
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private List<Object> callUpdate(Class<?> cls, Map<String, String> where,
|
|
ArrayList<String> oids, Map<String, String> set,
|
|
ModuleInterface module) {
|
|
// better safe than sorry - alsd hso same behavior as UNIX rm
|
|
if (where.size() == 0 && oids.size() == 0)
|
|
throw new BusinessException(
|
|
"better safe than sorry - 'update' needs a -w/--where-query or object id");
|
|
Transaction tx = module.getTransaction();
|
|
tx.beginTransaction();
|
|
try {
|
|
Method m = module.getClass().getMethod("search", Class.class,
|
|
String.class, String.class);
|
|
List<AbstractEntity> list = (List<AbstractEntity>) m.invoke(module, cls,
|
|
buildQuery(cls, where, oids), null);
|
|
checkOids(list, oids);
|
|
Method m2 = module.getClass().getMethod("update", AbstractEntity.class);
|
|
for (int i = 0; i < list.size(); i++) {
|
|
AbstractEntity entity = list.get(i);
|
|
tx.detach(entity);
|
|
setValues(entity, set, module);
|
|
m2.invoke(module, entity);
|
|
}
|
|
tx.commitTransaction();
|
|
} catch (Exception e) {
|
|
tx.rollbackTransaction();
|
|
// TODO: this needs to be more specific, but how?
|
|
throw new TechnicalException(e);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private void callDelete(Class<?> cls, Map<String, String> where,
|
|
ArrayList<String> oids, ModuleInterface module) {
|
|
// better safe than sorry - also same behavior as UNIX rm
|
|
if (where.size() == 0 && oids.size() == 0)
|
|
throw new BusinessException(
|
|
"better safe than sorry - 'update' needs a -w/--where-query or object id");
|
|
Transaction tx = module.getTransaction();
|
|
tx.beginTransaction();
|
|
try {
|
|
Method m =
|
|
module.getClass().getMethod("search", Class.class, String.class, String.class);
|
|
List<AbstractEntity> list =
|
|
(List<AbstractEntity>) m.invoke(module, cls, buildQuery(cls, where, oids), null);
|
|
checkOids(list, oids);
|
|
Method m2 = module.getClass().getMethod("delete", AbstractEntity.class);
|
|
for (int i = 0; i < list.size(); i++) {
|
|
Object o = list.get(i);
|
|
m2.invoke(module, o);
|
|
}
|
|
tx.commitTransaction();
|
|
} catch (Exception e) {
|
|
tx.rollbackTransaction();
|
|
// TODO: this needs to be more specific, but how?
|
|
throw new TechnicalException(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Call one of the EntitySessions methods
|
|
*
|
|
* @param moduleName
|
|
* Defines the Entity class that will be given to the
|
|
* EntitySession
|
|
* @param function
|
|
* Defines the method that will be called. The function can be
|
|
* "add", "search", "update" and "delete".
|
|
* @param where
|
|
* Reduces the set of Entity objects which the function operates
|
|
* on to those matched by the where parameters. Only for
|
|
* "search", "update" and "delete". Will be ignored otherwise.
|
|
* @param set
|
|
* Set these values on all Objects - only for "add" and "update".
|
|
* Will be ignored otherwise.
|
|
* @param oids
|
|
* Works on Objects with these IDs. Only for "search", "update"
|
|
* and "delete". Will be ignored for "add".
|
|
* @return foundObjects
|
|
* @throws FunctionNotKnownException
|
|
*/
|
|
public List<Object> callModule(String moduleName, String function,
|
|
Map<String, String> where, Map<String, String> set,
|
|
ArrayList<String> oids, ModuleInterface module)
|
|
throws FunctionNotKnownException, UnknownModuleException {
|
|
List<Object> rval = new ArrayList<Object>();
|
|
|
|
// handle calls to the virtual module "modules"
|
|
if (moduleName.equals("modules")) {
|
|
// only search, no manipulation is possible
|
|
if (function.equals("search")) {
|
|
Iterator<?> m = componentDescriptions.keySet().iterator();
|
|
while (m.hasNext()) {
|
|
String mn = (String) m.next();
|
|
rval.add(new ModuleModel(mn, componentDescriptions.get(mn)));
|
|
}
|
|
} else if (function.equals("version")) {
|
|
rval.add(new VersionModel(version));
|
|
} else {
|
|
throw new FunctionNotKnownException();
|
|
}
|
|
return rval;
|
|
}
|
|
|
|
// find the component for the named module
|
|
Class<?> cls = componentmap.get(moduleName);
|
|
if (cls == null)
|
|
throw (new UnknownModuleException());
|
|
|
|
// call the appropriate methods
|
|
if (function.equals("add"))
|
|
rval.add(callAdd(cls, set, module));
|
|
else if (function.equals("search")) {
|
|
List<Object> r = callSearch(cls, where, oids, module);
|
|
if (r != null)
|
|
return r;
|
|
} else if (function.equals("update")) {
|
|
List<Object> r = callUpdate(cls, where, oids, set, module);
|
|
if (r != null)
|
|
return callUpdate(cls, where, oids, set, module);
|
|
} else if (function.equals("delete")) {
|
|
callDelete(cls, where, oids, module);
|
|
} else
|
|
throw (new FunctionNotKnownException());
|
|
return rval;
|
|
}
|
|
|
|
/**
|
|
* handle put method
|
|
*/
|
|
public void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
|
|
try {
|
|
ModuleInterface module = null;
|
|
Transaction tx = null;
|
|
// check login
|
|
String auth = req.getHeader("authorization");
|
|
if (auth == null) {
|
|
// no login information at all - get it
|
|
resp.setHeader("WWW-Authenticate",
|
|
"Basic realm=\"CLIClientConnector\"");
|
|
resp.getOutputStream().println("login Failed.");
|
|
resp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
|
return;
|
|
} else {
|
|
// parse login information
|
|
String[] a = auth.split(" ", 2);
|
|
String ticket = a[1];
|
|
byte[] decoded = Base64.decodeBase64(ticket.trim().getBytes());
|
|
String s = new String(decoded);
|
|
a = s.split(":", 2);
|
|
// try to log in
|
|
String login = a[0];
|
|
ticket = a[1];
|
|
try {
|
|
tx = new Transaction(login);
|
|
if (tx.login(login, ticket)) {
|
|
// login successful
|
|
module = new GenericModuleImpl(tx);
|
|
|
|
// read arguments
|
|
BufferedReader read = req.getReader();
|
|
|
|
String tmpbuf;
|
|
ArrayList<String> arguments = new ArrayList<String>();
|
|
while ((tmpbuf = read.readLine()) != null) {
|
|
arguments.add(tmpbuf);
|
|
}
|
|
|
|
// actually handle the request and write result to output
|
|
String output = parser.parse(arguments, module);
|
|
resp.getWriter().write(output);
|
|
} else {
|
|
resp.addHeader("X-hsadmin-error", "authentication failed");
|
|
}
|
|
} catch (Exception e) {
|
|
resp.addHeader("X-hsadmin-error", "exception: " + e.getMessage());
|
|
} finally {
|
|
if (tx != null) {
|
|
tx.close();
|
|
}
|
|
}
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|