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.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.TicketAuthentication; import de.hsadmin.core.model.Transaction; import de.hsadmin.core.model.onetier.TicketValidator; /** * 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> componentmap; private Map 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>(); componentDescriptions = new HashMap(); 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 set, ModuleInterface module) { Iterator 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.getInstance(); 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 */ private String buildQuery(Class eType, Map where, ArrayList oids) { String rval = ""; boolean first = true; Iterator 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; } } 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 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 callSearch(Class cls, Map where, ArrayList oids, ModuleInterface module) { try { Method m = module.getClass().getMethod("search", Class.class, String.class, String.class); return (List) 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 list, List oids) { List oidsNotFound = new ArrayList(); 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 callUpdate(Class cls, Map where, ArrayList oids, Map 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 list = (List) 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 where, ArrayList 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 list = (List) 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 callModule(String moduleName, String function, Map where, Map set, ArrayList oids, ModuleInterface module) throws FunctionNotKnownException, UnknownModuleException { List rval = new ArrayList(); // 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 r = callSearch(cls, where, oids, module); if (r != null) return r; } else if (function.equals("update")) { List 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 { if (TicketAuthentication.getInstance().login(login, ticket)) { // login successful tx = new Transaction(login); module = new GenericModuleImpl(tx); // read arguments BufferedReader read = req.getReader(); String tmpbuf; ArrayList arguments = new ArrayList(); 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(); } } }