HSAdmin Backend Domains, E-Mail, Datenbanken
Peter Hormanns
2010-10-01 4dd9894b867a8272525d75c67d252800bfc6bea6
Schlankes Backend ohne JSF-Code
69 files added
7534 ■■■■■ changed files
hsarback/src/de/hsadmin/cliClientConnector/ArgumentParser.java 357 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/cliClientConnector/Base64.java 244 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/cliClientConnector/BusinessException.java 18 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/cliClientConnector/CLIClientConnectorServlet.java 490 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/cliClientConnector/ModuleModel.java 19 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/cliClientConnector/OidsNotFoundException.java 25 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/cliClientConnector/TechnicalException.java 50 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/cliClientConnector/VersionModel.java 14 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/AbstractModuleImpl.java 361 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/AuthenticationException.java 15 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/AuthorisationException.java 56 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/Entity.java 249 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/EntityInfo.java 12 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/EntitySessionHelper.java 41 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/GenericModuleImpl.java 99 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/HSAdminException.java 19 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/ModuleImpl.java 10 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/ModuleInterface.java 50 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/SearchFilter.java 12 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/SecureDefaultModuleImpl.java 105 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/TicketAuthentication.java 17 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/Transaction.java 179 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/onetier/PersistenceManager.java 43 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/model/onetier/TicketValidator.java 96 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/AbstractProcessor.java 17 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/CommandShell.java 162 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/CompoundProcessor.java 30 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/CopyFileProcessor.java 22 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/CreateFileProcessor.java 25 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/EntityProcessorFactory.java 22 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/JDBCProcessor.java 89 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/MailerProcessor.java 41 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/MailerShell.java 41 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/Processor.java 22 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/ProcessorException.java 21 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/QueueClient.java 80 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/QueueCommons.java 14 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/QueueServer.java 254 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/QueueStatusReceiverServlet.java 141 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/QueueTask.java 232 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/ShellException.java 19 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/ShellProcessor.java 76 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/TemplateProcessor.java 57 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/qserv/WaitingTasksProcessor.java 64 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/core/util/Config.java 53 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/cust/BankAccount.java 164 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/cust/Contact.java 282 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/cust/Customer.java 267 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/cust/CustomersTariff.java 188 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/pac/BaseComponent.java 108 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/pac/BasePac.java 141 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/pac/Component.java 117 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/pac/ComponentId.java 22 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/pac/Hive.java 109 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/pac/INetAddress.java 87 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/pac/Pac.java 280 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/pac/PacComponent.java 99 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/pac/PacComponentId.java 23 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/qstat/QTaskModuleImpl.java 38 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/user/UnixUser.java 323 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/user/UnixUserModuleImpl.java 279 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/mods/user/UnixUserProcessorFactory.java 105 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/remote/AbstractRemote.java 229 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/remote/IRemote.java 35 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/remote/QueueTaskRemote.java 49 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/remote/UnixUserRemote.java 81 ●●●●● patch | view | raw | blame | history
hsarback/src/net/sf/jtpl/Jtpl.java 307 ●●●●● patch | view | raw | blame | history
hsarback/src/net/sf/jtpl/Template.java 136 ●●●●● patch | view | raw | blame | history
hsarback/src/org/apache/xmlrpc/webserver/XmlRpcServlet.properties 2 ●●●●● patch | view | raw | blame | history
hsarback/src/de/hsadmin/cliClientConnector/ArgumentParser.java
New file
@@ -0,0 +1,357 @@
package de.hsadmin.cliClientConnector;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import de.hsadmin.cliClientConnector.CLIClientConnectorServlet.FunctionNotKnownException;
import de.hsadmin.cliClientConnector.CLIClientConnectorServlet.UnknownModuleException;
import de.hsadmin.core.model.Entity;
import de.hsadmin.core.model.ModuleInterface;
/**
 * Parses Arguments for the CLI Client Connector Servlet
 *
 * @author Christof Donat
 *
 */
public class ArgumentParser {
    /// I am working for this Servlet instance
    private CLIClientConnectorServlet master;
    private DateFormat df = new SimpleDateFormat( "yyyy-MM-dd");
    public ArgumentParser(CLIClientConnectorServlet master) {
        this.master = master;
    }
    private String getUsageString() {
        return    "     [ (-W name=value|--globalWhere:name=value) ...]\n"+
                "     [ (-S name=value|--globalSet:name=value) ...]\n"+
                "     [ (-D displayspec|--globalDisplay=displayspec) ...]\n"+
                "     [ (-c module.function|--call=module.function)\n" +
                "         [ (-w name=value|--where:name=value) ...]\n"+
                "         [ (-s name=value|--set:name=value) ...]\n"+
                "         [ (-d displayspec|--display:displayspec) ...]\n"+
                "         [ oids ...] ] ]\n"+
                "\n"+
                "(" + CLIClientConnectorServlet.version + ")\n";
    }
    private ArrayList<Method> getMethodList(Object o) {
        Method[] meths = o.getClass().getMethods();
        ArrayList<Method> methodlist = new ArrayList<Method>();
        for( int i = 0; i < meths.length; i++ ) {
            Method m = meths[i];
            String n = m.getName();
            if( n.startsWith("get") && !n.equals("getNew") ) {
                String fn = m.getName().substring(3).toLowerCase();
                if( fn.equals("class") ) continue;
                if (m.getParameterTypes().length == 0) {
                    methodlist.add(m);
                }
            }
        }
        return methodlist;
    }
    private Hashtable<String,String> getValues(Object o, ArrayList<Method> methodlist, ArrayList<String>fieldNames, boolean deep, ArrayList<Object> foundObjects) {
        Hashtable<String,String> row = new Hashtable<String,String>();
        int i, j;
        if( foundObjects == null ) foundObjects = new ArrayList<Object>();
        for( i = 0; i < methodlist.size(); i++ ) {
            Method m = methodlist.get(i);
            try {
                String name = fieldNames.get(i);
                String type = m.getReturnType().getCanonicalName();
                String val = "";
                Object value = null;
                try {
                    value = m.invoke(o);
                } catch( Exception e ) {
                    e.printStackTrace();
                }
                if( value == null )
                    val = "";
                else if( type.equals("java.lang.String") )
                    val = (String)value;
                else if( type.equals("java.lang.Integer") )
                    val = String.valueOf((Integer)value);
                else if( type.equals("java.lang.Long") )
                    val = String.valueOf((Long)value);
                else if( type.equals("java.lang.Boolean") )
                    val = String.valueOf((Boolean)value);
                else if( type.equals("java.util.Date") ) {
                    val = df.format((Date)value);
                } else if( type.equals("java.util.Set") ) {
                    val = "<Set>";
                } else try {
                    Entity v = (Entity)value;
                    val = v.createStringKey();
                    if( deep && !foundObjects.contains(v) ) {
                        foundObjects.add(v);
                        ArrayList<String> fieldNamesDeep = new ArrayList<String>();
                        ArrayList<Method> methodlistDeep = getMethodList(v);
                        for( j = 0; j < methodlistDeep.size(); j++ ) {
                            fieldNamesDeep.add(methodlistDeep.get(j).getName().substring(3).toLowerCase());
                        }
                        Hashtable<String,String> tmp = getValues(v,methodlistDeep,fieldNamesDeep,deep,foundObjects);
                        Enumeration<String> keys = tmp.keys();
                        while(keys.hasMoreElements()) {
                            try {
                                String k = (String)keys.nextElement();
                                row.put(name+"."+k, tmp.get(k));
                            } catch( Exception e ) {
                                e.printStackTrace();
                            }
                        }
                    }
                } catch(ClassCastException e) {
                    val = value.toString();
                }
                if (val != null) row.put(name, val);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return row;
    }
    private String formatObjectsWithoutDisplay(int j, ArrayList<String> fieldNames, ArrayList<Integer>columnWidths, ArrayList<Hashtable<String,String> > rows) {
        int i;
        StringBuffer rval = new StringBuffer();
        for( i = 0; i < fieldNames.size(); i++ ) {
            StringBuffer name =  new StringBuffer(fieldNames.get(i));
            int fieldwidth = columnWidths.get(i);
            while( name.length() < fieldwidth )
                name.append(" ");
            rval.append(name.toString()+((i < fieldNames.size()-1)?" | ":""));
        }
        rval.append("\n");
        for( i = 0; i < j; i++ )
            rval.append("-");
        rval.append("\n");
        for( j = 0; j < rows.size(); j++ ) {
            for( i = 0; i < fieldNames.size(); i++ ) {
                StringBuffer value =  new StringBuffer(rows.get(j).get(fieldNames.get(i)));
                int fieldwidth = columnWidths.get(i);
                while( value.length() < fieldwidth )
                    value.append(" ");
                rval.append(value.toString()+((i < fieldNames.size()-1)?" | ":""));
            }
            rval.append("\n");
        }
        return rval.toString();
    }
    private String formatObjectsWithDisplay(ArrayList<Hashtable<String,String> > rows, String displayDef) {
        StringBuffer rval = new StringBuffer();
        for( int j = 0; j < rows.size(); j++) {
            String rv = displayDef;
            Enumeration<String> fNames = rows.get(j).keys();
            while( fNames.hasMoreElements() ) {
                String f = (String)fNames.nextElement();
                rv = rv.replaceAll("\\$\\{"+f+"\\}", rows.get(j).get(f));
            }
            rv = rv.replaceAll("\\\\n", "\n");
            rv = rv.replaceAll("\\\\t", "\t");
            rv = rv.replaceAll("\\\\(.)?", "$1");
            rval.append(rv);
        }
        return rval.toString();
    }
    /**
     * format Objects for the output as a Human readable table
     *
     * @param objects
     *  format these objects
     *
     * @return humanReadableString
     *  a string with the human readable representation of the objects
     */
    public String formatObjects(List<?> objects, String displayDef) {
        if( objects.size() == 0 ) return "";
        if( objects.get(0) == null ) return "";
        ArrayList<Method> methodlist = getMethodList(objects.get(0));
        ArrayList<Integer>columnWidths = new ArrayList<Integer>();
        ArrayList<String>fieldNames = new ArrayList<String>();
        ArrayList<Hashtable<String,String> > rows =
            new ArrayList<Hashtable<String,String> >();
        int i, j;
        for( i = 0; i < methodlist.size(); i++ ) {
            Method m = methodlist.get(i);
            String n = m.getName();
            String fn = n.substring(3).toLowerCase();
            fieldNames.add(fn);
            columnWidths.add(n.length()+3);
        }
        for( j = 0; j < objects.size(); j++ ) {
            Object o = objects.get(j);
            Hashtable<String,String> row = getValues(o,methodlist,fieldNames,(displayDef != null), null);
            for( i = 0; i < fieldNames.size(); i++ ) {
                String val = row.get(fieldNames.get(i));
                if( val != null && i < columnWidths.size() && val.length()+3 > columnWidths.get(i) )
                    columnWidths.set(i, val.length()+3);
            }
            rows.add(row);
        }
        if( displayDef == null ) {
            j = 0;
            for( i = 0; i < columnWidths.size(); i++ ) {
                j += columnWidths.get(i)+2;
            }
            j -= 2;
            return formatObjectsWithoutDisplay(j, fieldNames, columnWidths, rows);
        } else
            return formatObjectsWithDisplay(rows, displayDef);
    }
    /**
     * The main parser function. Parses the Parameters, uses the master to call
     * the functions and returns a formatet output.
     *
     * @param arguments
     * @return humanReadableObjectsString
     */
    public String parse(List<String> arguments, ModuleInterface module) {
        String rval = "";
        String currentModule = null;
        String currentFunction = null;
        Map<String,String> currentWhere = new Hashtable<String,String>();
        Map<String,String> currentSet = new Hashtable<String,String>();
        ArrayList<String> currentOIDs = new ArrayList<String>();
        Map<String,String> globalWhere = new Hashtable<String,String>();
        Map<String,String> globalSet = new Hashtable<String,String>();
        String display = null;
        String globalDisplay = null;
        for( int i = 0; i < arguments.size(); i++ ) {
            String arg = arguments.get(i);
            if( arg.equals("-c") || arg.startsWith("--call:") ) {
                // call
                if( currentModule != null ) {
                    try {
                        // execute the last call now
                        rval += formatObjects(master.callModule(
                                currentModule,
                                currentFunction,
                                currentWhere,
                                currentSet,
                                currentOIDs,
                                module),(display==null)?globalDisplay:display);
                    } catch (FunctionNotKnownException e) {
                        rval += "Function unknown: "+currentModule+'.'+currentFunction+"\n";
                    } catch (UnknownModuleException e) {
                        rval += "Module unknown: "+currentModule+"\n";
                    }
                }
                // reset parameters for next call
                currentWhere = new HashMap<String, String>();
                currentWhere.putAll(globalWhere);
                currentSet = new HashMap<String, String>();
                currentSet.putAll(globalSet);
                currentOIDs  = new ArrayList<String>();
                display      = null;
                // set the new call
                boolean isShortParam = arg.equals("-c");
                String calldef = isShortParam?arguments.get(i+1):arg.substring(7);
                if( calldef != null ) {
                    String[] split = calldef.split("[.]", 2);
                    currentModule   = split[0];
                    currentFunction = split[1];
                }
                if( isShortParam ) i++;
            } else if( arg.equals("-w") || arg.startsWith("--where:") ) {
                // where
                boolean isShortParam = arg.equals("-w");
                String wheredef = isShortParam?arguments.get(i+1):arg.substring(8);
                if( wheredef != null ) {
                    String[] split = wheredef.split("[=]", 2);
                    currentWhere.put(split[0],split[1]);
                }
                if( isShortParam ) i++;
            } else if( arg.equals("-W") || arg.startsWith("--globalWhere:") ) {
                // global where
                boolean isShortParam = arg.equals("-W");
                String gwheredef = isShortParam?arguments.get(i+1):arg.substring(14);
                if( gwheredef != null ) {
                    String[] split = gwheredef.split("[=]", 2);
                    globalWhere.put(split[0],split[1]);
                }
                if( isShortParam ) i++;
            } else if( arg.equals("-s") || arg.startsWith("--set:") ) {
                // set
                boolean isShortParam = arg.equals("-s");
                String setdef = isShortParam?arguments.get(i+1):arg.substring(6);
                if( setdef != null ) {
                    String[] split = setdef.split("[=]", 2);
                    currentSet.put(split[0],split[1]);
                }
                if( isShortParam ) i++;
            } else if( arg.equals("-S") || arg.startsWith("--globalSet:") ) {
                // global set
                boolean isShortParam = arg.equals("-S");
                String gsetdef = isShortParam?arguments.get(i+1):arg.substring(12);
                if( gsetdef != null ) {
                    String[] split = gsetdef.split("[=]", 2);
                    globalSet.put(split[0],split[1]);
                }
                if( isShortParam ) i++;
            } else if( arg.equals("-d") || arg.startsWith("--display:") ) {
                // display
                boolean isShortParam = arg.equals("-d");
                display = isShortParam?arguments.get(i+1):arg.substring(10);
                if( isShortParam ) i++;
            } else if( arg.equals("-D") || arg.startsWith("--globalDisplay:") ) {
                // global display
                boolean isShortParam = arg.equals("-D");
                globalDisplay = isShortParam?arguments.get(i+1):arg.substring(16);
                if( isShortParam ) i++;
            } else if( arg.equals("-h") || arg.equals("--help") ) {
                return getUsageString();
            } else if( arg.startsWith("-") ) {
                return "unknown option '"+arg+"'\n"+getUsageString();
            } else currentOIDs.add(arg);
        }
        if( currentModule != null ) {
            try {
                rval += formatObjects(master.callModule(
                        currentModule,
                        currentFunction,
                        currentWhere,
                        currentSet,
                        currentOIDs,
                        module),(display==null)?globalDisplay:display);
            } catch (FunctionNotKnownException e) {
                rval += "Function unknown: "+currentModule+'.'+currentFunction+"\n";
            } catch (UnknownModuleException e) {
                rval += "Module unknown: "+currentModule+"\n";
            }
        }
        return rval;
    }
}
hsarback/src/de/hsadmin/cliClientConnector/Base64.java
New file
@@ -0,0 +1,244 @@
/*
 * @(#)Base64.java    1.5 03/12/19
 *
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package de.hsadmin.cliClientConnector;
/**
 * Static methods for translating Base64 encoded strings to byte arrays
 * and vice-versa.
 *
 * @author  Josh Bloch
 * @version 1.5, 12/19/03
 * @see     Preferences
 * @since   1.4
 */
class Base64 {
    /**
     * Translates the specified byte array into a Base64 string as per
     * Preferences.put(byte[]).
     */
    static String byteArrayToBase64(byte[] a) {
        return byteArrayToBase64(a, false);
    }
    /**
     * Translates the specified byte array into an "aternate representation"
     * Base64 string.  This non-standard variant uses an alphabet that does
     * not contain the uppercase alphabetic characters, which makes it
     * suitable for use in situations where case-folding occurs.
     */
    static String byteArrayToAltBase64(byte[] a) {
        return byteArrayToBase64(a, true);
    }
    private static String byteArrayToBase64(byte[] a, boolean alternate) {
        int aLen = a.length;
        int numFullGroups = aLen/3;
        int numBytesInPartialGroup = aLen - 3*numFullGroups;
        int resultLen = 4*((aLen + 2)/3);
        StringBuffer result = new StringBuffer(resultLen);
        char[] intToAlpha = (alternate ? intToAltBase64 : intToBase64);
        // Translate all full groups from byte array elements to Base64
        int inCursor = 0;
        for (int i=0; i<numFullGroups; i++) {
            int byte0 = a[inCursor++] & 0xff;
            int byte1 = a[inCursor++] & 0xff;
            int byte2 = a[inCursor++] & 0xff;
            result.append(intToAlpha[byte0 >> 2]);
            result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]);
            result.append(intToAlpha[(byte1 << 2)&0x3f | (byte2 >> 6)]);
            result.append(intToAlpha[byte2 & 0x3f]);
        }
        // Translate partial group if present
        if (numBytesInPartialGroup != 0) {
            int byte0 = a[inCursor++] & 0xff;
            result.append(intToAlpha[byte0 >> 2]);
            if (numBytesInPartialGroup == 1) {
                result.append(intToAlpha[(byte0 << 4) & 0x3f]);
                result.append("==");
            } else {
                // assert numBytesInPartialGroup == 2;
                int byte1 = a[inCursor++] & 0xff;
                result.append(intToAlpha[(byte0 << 4)&0x3f | (byte1 >> 4)]);
                result.append(intToAlpha[(byte1 << 2)&0x3f]);
                result.append('=');
            }
        }
        // assert inCursor == a.length;
        // assert result.length() == resultLen;
        return result.toString();
    }
    /**
     * This array is a lookup table that translates 6-bit positive integer
     * index values into their "Base64 Alphabet" equivalents as specified
     * in Table 1 of RFC 2045.
     */
    private static final char intToBase64[] = {
        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
        'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
        'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
    };
    /**
     * This array is a lookup table that translates 6-bit positive integer
     * index values into their "Alternate Base64 Alphabet" equivalents.
     * This is NOT the real Base64 Alphabet as per in Table 1 of RFC 2045.
     * This alternate alphabet does not use the capital letters.  It is
     * designed for use in environments where "case folding" occurs.
     */
    private static final char intToAltBase64[] = {
        '!', '"', '#', '$', '%', '&', '\'', '(', ')', ',', '-', '.', ':',
        ';', '<', '>', '@', '[', ']', '^',  '`', '_', '{', '|', '}', '~',
        'a', 'b', 'c', 'd', 'e', 'f', 'g',  'h', 'i', 'j', 'k', 'l', 'm',
        'n', 'o', 'p', 'q', 'r', 's', 't',  'u', 'v', 'w', 'x', 'y', 'z',
        '0', '1', '2', '3', '4', '5', '6',  '7', '8', '9', '+', '?'
    };
    /**
     * Translates the specified Base64 string (as per Preferences.get(byte[]))
     * into a byte array.
     *
     * @throw IllegalArgumentException if <tt>s</tt> is not a valid Base64
     *        string.
     */
    static byte[] base64ToByteArray(String s) {
        return base64ToByteArray(s, false);
    }
    /**
     * Translates the specified "aternate representation" Base64 string
     * into a byte array.
     *
     * @throw IllegalArgumentException or ArrayOutOfBoundsException
     *        if <tt>s</tt> is not a valid alternate representation
     *        Base64 string.
     */
    static byte[] altBase64ToByteArray(String s) {
        return base64ToByteArray(s, true);
    }
    private static byte[] base64ToByteArray(String s, boolean alternate) {
        byte[] alphaToInt = (alternate ?  altBase64ToInt : base64ToInt);
        int sLen = s.length();
        int numGroups = sLen/4;
        if (4*numGroups != sLen)
            throw new IllegalArgumentException(
                "String length must be a multiple of four.");
        int missingBytesInLastGroup = 0;
        int numFullGroups = numGroups;
        if (sLen != 0) {
            if (s.charAt(sLen-1) == '=') {
                missingBytesInLastGroup++;
                numFullGroups--;
            }
            if (s.charAt(sLen-2) == '=')
                missingBytesInLastGroup++;
        }
        byte[] result = new byte[3*numGroups - missingBytesInLastGroup];
        // Translate all full groups from base64 to byte array elements
        int inCursor = 0, outCursor = 0;
        for (int i=0; i<numFullGroups; i++) {
            int ch0 = base64toInt(s.charAt(inCursor++), alphaToInt);
            int ch1 = base64toInt(s.charAt(inCursor++), alphaToInt);
            int ch2 = base64toInt(s.charAt(inCursor++), alphaToInt);
            int ch3 = base64toInt(s.charAt(inCursor++), alphaToInt);
            result[outCursor++] = (byte) ((ch0 << 2) | (ch1 >> 4));
            result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2));
            result[outCursor++] = (byte) ((ch2 << 6) | ch3);
        }
        // Translate partial group, if present
        if (missingBytesInLastGroup != 0) {
            int ch0 = base64toInt(s.charAt(inCursor++), alphaToInt);
            int ch1 = base64toInt(s.charAt(inCursor++), alphaToInt);
            result[outCursor++] = (byte) ((ch0 << 2) | (ch1 >> 4));
            if (missingBytesInLastGroup == 1) {
                int ch2 = base64toInt(s.charAt(inCursor++), alphaToInt);
                result[outCursor++] = (byte) ((ch1 << 4) | (ch2 >> 2));
            }
        }
        // assert inCursor == s.length()-missingBytesInLastGroup;
        // assert outCursor == result.length;
        return result;
    }
    /**
     * Translates the specified character, which is assumed to be in the
     * "Base 64 Alphabet" into its equivalent 6-bit positive integer.
     *
     * @throw IllegalArgumentException or ArrayOutOfBoundsException if
     *        c is not in the Base64 Alphabet.
     */
    private static int base64toInt(char c, byte[] alphaToInt) {
        int result = alphaToInt[c];
        if (result < 0)
            throw new IllegalArgumentException("Illegal character " + c);
        return result;
    }
    /**
     * This array is a lookup table that translates unicode characters
     * drawn from the "Base64 Alphabet" (as specified in Table 1 of RFC 2045)
     * into their 6-bit positive integer equivalents.  Characters that
     * are not in the Base64 alphabet but fall within the bounds of the
     * array are translated to -1.
     */
    private static final byte base64ToInt[] = {
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54,
        55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
        5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
        24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34,
        35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
    };
    /**
     * This array is the analogue of base64ToInt, but for the nonstandard
     * variant that avoids the use of uppercase alphabetic characters.
     */
    private static final byte altBase64ToInt[] = {
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 0, 1,
        2, 3, 4, 5, 6, 7, 8, -1, 62, 9, 10, 11, -1 , 52, 53, 54, 55, 56, 57,
        58, 59, 60, 61, 12, 13, 14, -1, 15, 63, 16, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, 17, -1, 18, 19, 21, 20, 26, 27, 28, 29, 30, 31, 32, 33,
        34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
        51, 22, 23, 24, 25
    };
    public static void main(String args[]) {
        int numRuns  = Integer.parseInt(args[0]);
        int numBytes = Integer.parseInt(args[1]);
        java.util.Random rnd = new java.util.Random();
        for (int i=0; i<numRuns; i++) {
            for (int j=0; j<numBytes; j++) {
                byte[] arr = new byte[j];
                for (int k=0; k<j; k++)
                    arr[k] = (byte)rnd.nextInt();
                String s = byteArrayToBase64(arr);
                byte [] b = base64ToByteArray(s);
                if (!java.util.Arrays.equals(arr, b))
                    System.out.println("Dismal failure!");
                s = byteArrayToAltBase64(arr);
                b = altBase64ToByteArray(s);
                if (!java.util.Arrays.equals(arr, b))
                    System.out.println("Alternate dismal failure!");
            }
        }
    }
}
hsarback/src/de/hsadmin/cliClientConnector/BusinessException.java
New file
@@ -0,0 +1,18 @@
package de.hsadmin.cliClientConnector;
@SuppressWarnings("serial")
public class BusinessException
        extends RuntimeException
{
    public BusinessException(String msg)
    {
        super(msg);
    }
    public BusinessException(Exception exc)
    {
        super(exc.getMessage());
    }
}
hsarback/src/de/hsadmin/cliClientConnector/CLIClientConnectorServlet.java
New file
@@ -0,0 +1,490 @@
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 de.hsadmin.core.model.Entity;
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 = "1.0.8 (2009/May/27 18:22)";
    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(o, value);
                    else if (type.equals("java.lang.Integer"))
                        m[j].invoke(o, Integer.parseInt(value));
                    else if (type.equals("java.lang.Long"))
                        m[j].invoke(o, Long.parseLong(value));
                    else if (type.equals("java.lang.Boolean"))
                        m[j].invoke(o, Boolean.valueOf(value));
                    else if (type.equals("java.util.Date")) {
                        DateFormat df = DateFormat.getInstance();
                        m[j].invoke(o, 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(o, 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<String, String> where,
            ArrayList<String> oids) {
        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." + Entity.escapeString(kname) + " = '" + Entity.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<String, String> set, ModuleInterface module) {
        Transaction transaction = module.getTransaction();
        transaction.beginTransaction();
        try {
            Method m = module.getClass().getMethod("add", Entity.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<Entity> list, List<String> oids) {
        List<String> oidsNotFound = new ArrayList<String>();
        for (String id : oids) {
            boolean found = false;
            for (Entity 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<Entity> list = (List<Entity>) m.invoke(module, cls,
                    buildQuery(cls, where, oids), null);
            checkOids(list, oids);
            Method m2 = module.getClass().getMethod("update", Entity.class);
            for (int i = 0; i < list.size(); i++) {
                Entity 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<Entity> list =
                (List<Entity>) m.invoke(module, cls, buildQuery(cls, where, oids), null);
            checkOids(list, oids);
            Method m2 = module.getClass().getMethod("delete", Entity.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;
            // 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.base64ToByteArray(ticket.trim());
                StringBuffer s = new StringBuffer();
                for (int i = 0; i < decoded.length; i++)
                    s.append((char) decoded[i]);
                a = s.toString().split(":", 2);
                // try to log in
                String login = a[0];
                ticket = a[1];
                module = null;
                try {
                    if (TicketAuthentication.getInstance().login(login, ticket)) {
                        // login successful
                        module = new GenericModuleImpl(new Transaction(login));
                        // 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 (module != null && module.getTransaction() != null) {
                        module.getTransaction().close();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
hsarback/src/de/hsadmin/cliClientConnector/ModuleModel.java
New file
@@ -0,0 +1,19 @@
package de.hsadmin.cliClientConnector;
public class ModuleModel {
    private String name;
    private String description;
    public ModuleModel(String name, String description) {
        this.name = name;
        this.description = description;
    }
    public String getName() {
        return this.name;
    }
    public String getDescription() {
        return this.description;
    }
}
hsarback/src/de/hsadmin/cliClientConnector/OidsNotFoundException.java
New file
@@ -0,0 +1,25 @@
package de.hsadmin.cliClientConnector;
import java.util.List;
@SuppressWarnings("serial")
public class OidsNotFoundException
        extends RuntimeException
{
    static private String oidsAsString(List<String> oids)
    {
        StringBuilder oidsBuilder = new StringBuilder();
        for ( String id: oids )
            oidsBuilder.append(", " + id);
        if ( oidsBuilder.length() > 0 )
            return oidsBuilder.substring(2);
        throw new RuntimeException( "an empty list of missing OIDS does not make sense" );
    }
    public OidsNotFoundException(List<String> oids)
    {
        super("OIDS not found: " + oidsAsString(oids));
    }
}
hsarback/src/de/hsadmin/cliClientConnector/TechnicalException.java
New file
@@ -0,0 +1,50 @@
package de.hsadmin.cliClientConnector;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import javax.persistence.RollbackException;
public class TechnicalException extends RuntimeException {
    private static final long serialVersionUID = 1L;
    public TechnicalException(Throwable e) {
        super(extractCauseMessage(e));
    }
    private static String extractCauseMessage(Throwable e) {
        if (e.getMessage() != null && !(e instanceof RollbackException)) {
            return e.getMessage();
        }
        else if (e instanceof InvocationTargetException || e instanceof RollbackException) {
            String sqlState = null;
            Throwable cause = e.getCause();
            Throwable prev = null;
            while (cause != null && cause != prev) {
                prev = cause;
                cause = prev.getCause();
                if ((cause == null || cause == prev)
                        && prev instanceof SQLException) {
                    SQLException sqlExc = (SQLException) prev;
                    sqlState = sqlExc.getSQLState();
                    cause = sqlExc.getNextException();
                }
            }
            if (cause == null)
                cause = prev;
            if (cause != null)
                return composeMessage(sqlState, cause.getMessage());
            if (e instanceof InvocationTargetException) {
                return composeMessage(sqlState, ((InvocationTargetException) e).getTargetException().getMessage());
            }
        }
        return e.getClass() + " without detail message";
    }
    private static String composeMessage(String sqlState, String message) {
        if (sqlState != null) return "SQLSTATE[" + sqlState + "] " + message;
        return message;
    }
}
hsarback/src/de/hsadmin/cliClientConnector/VersionModel.java
New file
@@ -0,0 +1,14 @@
package de.hsadmin.cliClientConnector;
public class VersionModel
{
    private String version;
    public VersionModel( String version) {
        this.version = version;
    }
    public String getVersion() {
        return this.version;
    }
}
hsarback/src/de/hsadmin/core/model/AbstractModuleImpl.java
New file
@@ -0,0 +1,361 @@
package de.hsadmin.core.model;
import java.util.LinkedList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import de.hsadmin.core.qserv.EntityProcessorFactory;
import de.hsadmin.core.qserv.Processor;
import de.hsadmin.core.qserv.QueueTask;
import de.hsadmin.mods.user.UnixUser;
/**
 * Generic implementation of EntitySession interface.
 *
 * @author peter
 */
public abstract class AbstractModuleImpl implements ModuleInterface {
    private static final long serialVersionUID = 2693948730004920437L;
    private static Log log = LogFactory.getLog(AbstractModuleImpl.class);
    private UnixUser loginUser;
    private Transaction transaction;
    public void construct(Transaction tx) {
        transaction = tx;
    }
    /**
     *  apply access restriction to JPA-QL condition.
     * @param entityClass
     * @param loginUser
     * @param condition
     * @return
     */
    private String restrict(Class<?> entityClass, UnixUser loginUser, String condition) {
        String restriction = Entity.restriction(entityClass, loginUser);
        if (restriction == null)
            return condition;
        if (condition != null && condition.length() > 0)
            condition = "(" + condition + ") AND (" + restriction + ")";
        else
            condition = restriction;
        return condition;
    }
    public Transaction getTransaction() {
        return transaction;
    }
    public Entity initialize(Entity newEntity)
            throws AuthorisationException {
        newEntity.initialize(transaction.getEntityManager(), getLoginUser());
        return newEntity;
    }
    public Entity add(Entity newEntity) throws HSAdminException {
        // get the user who is logged in
        UnixUser loginUser = getLoginUser();
        // create record in database
        log.debug("merging?");
        newEntity.complete(transaction.getEntityManager(), loginUser);
        try {
            transaction.getEntityManager().persist(newEntity);
        } catch (Throwable exc) {
            log.error("exception: " + exc);
        } finally {
            log.debug("finally");
        }
        log.debug("merged");
        // check rights
        if (!newEntity.isWriteAllowedFor(loginUser))
            throw new AuthorisationException(loginUser, "add", newEntity);
        // generically create the processor
        EntityProcessorFactory procFact = createProcessorFactory(newEntity.getClass());
        if (procFact == null) {
            log.debug("no procFact found :-(");
            return newEntity;
        }
        log.debug("procFact found :-)");
        Processor proc = procFact.createCreateProcessor(transaction.getEntityManager(), newEntity);
        // queue the processor
        queueProcessor(proc, loginUser, newEntity, "hinzugefuegt");
        // return the added entity
        return newEntity;
    }
    public Entity find(Class<? extends Entity> entityClass, Object key) throws HSAdminException {
        Entity entity = transaction.getEntityManager().find(entityClass, key);
        // check rights
        UnixUser loginUser = getLoginUser();
        if (!entity.isReadAllowedFor(loginUser))
            throw new AuthorisationException(loginUser, "add", entity);
        return entity;
    }
    public Entity findByString(Class<? extends Entity> entityClass, String key) throws HSAdminException {
        // find a static method which creates the query
        java.lang.reflect.Method method = null;
        try {
            method = entityClass.getDeclaredMethod("createQueryFromStringKey", String.class);
        } catch (SecurityException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            method = null;
        }
        Entity entity;
        if (method == null)
            entity = transaction.getEntityManager().find(entityClass, key);
        else {
            // get the query expression from the static method (query part after
            // WHERE)
            String query;
            try {
                query = (String) method.invoke(null, key);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            // perform the query
            List<Entity> result = search(entityClass, query, null);
            if (result.size() > 1)
                throw new javax.persistence.NonUniqueResultException();
            if (result.size() == 0)
                return null;
            entity = result.get(0);
            // this was maybe thought as a fallback
            // but is wrong when the above result is empty due to accessibility
            // entity = em.find(entityClass, key);
        }
        // return (checking rights already done in search)
        return entity;
    }
    public List<Entity> search(Class<? extends Entity> entityClass, String condition, String orderBy) throws HSAdminException {
        // restrict query
        UnixUser loginUser = getLoginUser();
        condition = restrict(entityClass, loginUser, condition);
        // get the entities name (query part from FROM to WHERE)
        // TODO: beware SQL injections!!!
        javax.persistence.Entity entityAnnot = entityClass.getAnnotation(javax.persistence.Entity.class);
        String queryString = "SELECT obj FROM " + entityAnnot.name() + " obj";
        if (condition != null && condition.length() > 0)
            queryString += " WHERE " + condition;
        // Fix problem with queries WHERE .. AND (FALSE) -- pe
        if (condition != null && condition.contains("AND (FALSE)")) {
            return new LinkedList<Entity>();
        }
        if (orderBy != null) {
            queryString += " ";
            queryString += orderBy;
        }
        // set parameters
        EntityManager entityManager = transaction.getEntityManager();
        entityManager.clear();
        Query query = entityManager.createQuery(queryString);
        setQueryParameter(query, queryString, "loginUser", loginUser);
        setQueryParameter(query, queryString, "loginUserName", loginUser.getName());
        setQueryParameter(query, queryString, "loginUserPac", loginUser.getPac());
        // do query
        try {
            List<?> res = query.getResultList();
            List<Entity> ret = new LinkedList<Entity>();
            // remove entities where login user has no access rights
            for (Object entity : res) {
                if (entity instanceof Entity) {
                    Entity returnedEntity = (Entity) entity;
                    if (returnedEntity.isReadAllowedFor(getLoginUser())) {
                        ret.add(returnedEntity);
                    }
                }
            }
            // return clean result
            return ret;
        } catch (Exception ex) {
            return null;
        }
    }
    public Entity update(Entity existingEntity) throws HSAdminException {
        // get the user who is logged in
        UnixUser loginUser = getLoginUser();
        // update record in database
        log.debug("merging:");
        try {
            existingEntity = existingEntity.merge(transaction.getEntityManager(), loginUser);
        } catch (Throwable exc) {
            log.error("exception: " + exc);
            throw new RuntimeException(exc);
        } finally {
            log.debug("finally");
        }
        log.debug("merged!");
        // check rights
        if (!existingEntity.isWriteAllowedFor(loginUser))
            throw new AuthorisationException(loginUser, "update",
                    existingEntity);
        // generically create the processor
        EntityProcessorFactory procFact =
            createProcessorFactory(existingEntity.getClass());
        if (procFact != null) {
            log.debug("creating processor");
            Processor proc = procFact.createUpdateProcessor(transaction.getEntityManager(), existingEntity);
            // queue the processor
            queueProcessor(proc, loginUser, existingEntity, "aktualisiert");
        }
        // return the merged entity
        return existingEntity;
    }
    public void delete(Entity existingEntity) throws HSAdminException {
        // get the user who is logged in
        UnixUser user = getLoginUser();
        // re-attach the entity
        log.debug("merging:");
        try {
            existingEntity = transaction.getEntityManager().find(existingEntity.getClass(), existingEntity.id());
        } catch (Throwable exc) {
            log.error("exception: " + exc);
            throw new RuntimeException(exc);
        } finally {
            log.debug("finally");
        }
        log.debug("merged!");
        // check rights
        if (!existingEntity.isWriteAllowedFor(loginUser))
            throw new AuthorisationException(loginUser, "add", existingEntity);
        // delete record in database
        log.debug("deleting:");
        try {
            transaction.getEntityManager().remove(existingEntity);
        } catch (Throwable exc) {
            log.error("exception: " + exc);
        } finally {
            log.debug("finally");
        }
        log.debug("deleted!");
        // generically create the processor
        EntityProcessorFactory procFact = createProcessorFactory(existingEntity.getClass());
        if (procFact == null) {
            log.debug("no procFact found :-(");
            return;
        }
        log.debug("procFact found :-)");
        Processor proc = procFact.createDeleteProcessor(transaction.getEntityManager(), existingEntity);
        // queue the processor
        queueProcessor(proc, user, existingEntity, "geloescht");
    }
    public EntityProcessorFactory createProcessorFactory(Class<? extends Entity> entityClass) {
        try {
            String procFactName = entityClass.getCanonicalName()
                    + "ProcessorFactory";
            Class<?> procFactClass = Class.forName(procFactName);
            if (procFactClass == null)
                return null;
            Object procFact = procFactClass.newInstance();
            return (EntityProcessorFactory) procFact;
        } catch (Exception exc) {
            log.error("exception creating instance: " + exc);
            return null;
        }
    }
    /**
     *  get current login user from session context.
     * @return
     */
    public UnixUser getLoginUser() {
        if (loginUser == null) {
            log.debug("getting login user");
            Query userQuery = transaction.getEntityManager().createQuery("SELECT u FROM UnixUsers u WHERE u.name=:name");
            userQuery.setParameter("name", transaction.getLoginName());
            loginUser = (UnixUser) userQuery.getSingleResult();
            log.debug("found login user: " + loginUser.getName());
        }
        return loginUser;
    }
    public void queueProcessor(Processor proc, UnixUser user, Entity entity, String action) {
        log.debug("queueing processor for user " + user.getId() + "/"
                + user.getUserId() + "/" + user.getName());
        EntityInfo entityInfo =
            entity.getClass().getAnnotation(EntityInfo.class);
        String entityTypeName =
            entityInfo != null ? entityInfo.name() : entity.getClass().getSimpleName();
        StringBuilder details = new StringBuilder();
        // TODO: add properties of entity to details
        String title =
            entityTypeName + " (" + entity.createStringKey() + ") " + action;
        QueueTask task = new QueueTask(user, title, details.toString(), proc);
        transaction.getEntityManager().persist(task);
        transaction.enqueue(entity.getHiveName(), task);
        log.debug("processor queued");
    }
    public String toString(StackTraceElement[] stackTrace) {
        StringBuilder stack = new StringBuilder();
        for (StackTraceElement e : stackTrace)
            stack.append(e.getFileName() + ":" + e.getLineNumber() + "\n");
        return stack.toString();
    }
    /**
     *  determines if the login user is a hostmaster.
     * @return
     */
    public boolean isHostmaster() {
        // TODO: dummy
        return false;
    }
    public static void setQueryParameter(Query query, String queryString,
            String argName, Object argValue) {
        int argLen = argName.length();
        int iMax = queryString.length();
        int i = 0;
        while ((i = queryString.indexOf(argName, i)) >= 0) {
            if ((i + argLen) >= iMax || queryString.charAt(i + argLen) < 'A') {
                query.setParameter(argName, argValue);
                break;
            }
            ++i;
        }
    }
}
hsarback/src/de/hsadmin/core/model/AuthenticationException.java
New file
@@ -0,0 +1,15 @@
package de.hsadmin.core.model;
public class AuthenticationException extends HSAdminException {
    private static final long serialVersionUID = 6242824365822822456L;
    public AuthenticationException(Exception e) {
        super(e);
    }
    public AuthenticationException(String msg) {
        super(msg);
    }
}
hsarback/src/de/hsadmin/core/model/AuthorisationException.java
New file
@@ -0,0 +1,56 @@
package de.hsadmin.core.model;
import de.hsadmin.mods.user.UnixUser;
public class AuthorisationException extends HSAdminException {
    private static final long serialVersionUID = -8125905071037488732L;
    private UnixUser user;
    private String method;
    private Entity entity;
    private String field;
    public UnixUser getUser() {
        return user;
    }
    public String getMethod() {
        return method;
    }
    public Entity getEntity() {
        return entity;
    }
    public String getField() {
        return field;
    }
    public AuthorisationException(UnixUser user, String method) {
        super("nicht authorisiert fuer " + method + "()");
        this.user = user;
        this.method = method;
    }
    public AuthorisationException(UnixUser user, String method, Entity entity) {
        super("nicht authorisiert fuer " + method + "("
                + entity.createStringKey() + ")");
        this.user = user;
        this.method = method;
        this.entity = entity;
    }
    public AuthorisationException(UnixUser user, String method, Entity entity,
            String field) {
        super("nicht authorisiert fuer " + method + "("
                + entity.createStringKey() + "." + field + ")");
        this.user = user;
        this.method = method;
        this.entity = entity;
        this.field = field;
    }
}
hsarback/src/de/hsadmin/core/model/Entity.java
New file
@@ -0,0 +1,249 @@
package de.hsadmin.core.model;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import javax.persistence.EntityManager;
import de.hsadmin.mods.user.UnixUser;
public abstract class Entity {
    /**
     * trims whitespace from both ends, but a null remains null.
     * @param field
     * @return
     */
    public static String trim(String field) {
        if (field == null)
            return field;
        return field.trim();
    }
    /**
     *  trims whitespace from both ends, returns null for a resulting empty
     *  string or null.
     * @param field
     * @return
     */
    public static String trimToNull(String field) {
        if (field == null)
            return field;
        field = field.trim();
        if (field.length() == 0)
            return null;
        return field;
    }
    /**
     *  trims whitespace from both ends, returns an empty string for null
     * @param field
     * @return
     */
    public static String trimToEmpty(String field) {
        if (field == null)
            return "";
        field = field.trim();
        return field;
    }
    /**
     * Creates a JPA-QL query string from a human readable string representing a
     * key.
     *
     * Such a static method is to be implemented by every entity class which has
     * a human readable key different from "id".
     *
     * @param humanKey
     *            A human readable key representing the entity. E.g.
     *            "info@example.com" for an E-Mail-Address.
     *
     * @return jpaQLQuery the matching JPA-QL query
     */
    public static String createQueryFromStringKey(String humanKey) throws HSAdminException {
        return "id=" + humanKey;
    }
    /**
     * escapes a String so that it can be used for SQL statements
     * @param str a sting that should be usable as a String in an SQLStatement
     * @return the escapedString
     */
    public static String escapeString(String str) {
        str = str.replaceAll("\"", "\\\"");
        str = str.replaceAll("\'", "\\'");
        return str;
    }
    /**
     * Calls the static method of the Entity subclass to get a query
     * restriction.
     *
     * @return a restricting JPA-QL expression to limit access to entities
     */
    public static String restriction(Class<?> entityClass, UnixUser loginUser) {
        // hostmasters don't get any restriction
        if (loginUser.hasHostmasterRole())
            return null;
        // find a static method which creates the query
        java.lang.reflect.Method method = null;
        try {
            method = entityClass.getDeclaredMethod("restriction");
        } catch (SecurityException e) {
            throw new RuntimeException(
                    "unexpected SecurityException in restriction()");
        } catch (NoSuchMethodException e) {
            // don't grant any access
            return "FALSE";
        }
        // get the restriction from a static method of the Entity class
        String restriction = "FALSE";
        try {
            restriction = (String) method.invoke(null);
        } catch (Exception e) {
            throw new RuntimeException(
                    "unexpected Exception in Entity-restriction() implementation");
        }
        return restriction;
    }
    /**
     * Creates a human readable string representing a key to this entity.
     *
     * @return humanReadableString A human readable string which can be used as
     *         a key to this entity. This string can also be used with the
     *         static method createQueryFromStringKey which has to be supported
     *         by all entity
     */
    public abstract String createStringKey();
    /**
     * returns true if entity is not yet stored to the database, false otherwise.
     * @return
     */
    public abstract boolean isNew();
    /**
     * returns the unique database if
     */
    public abstract long id();
    /**
     * returns true if the argument entity has the same database id
     * @param entity
     * @return
     */
    public boolean sameIdAs(Entity entity) {
        if (entity == null)
            return false;
        if (getClass() != entity.getClass())
            return false;
        return id() == entity.id();
    }
    /**
     * returns true if entity is not yet stored to the database, false otherwise.
     * @return
     */
    @javax.persistence.Transient
    public boolean getNew() {
        return isNew();
    }
    /**
     * Initializes a newly created entity.
     *
     * This method makes it possible to initialize fields of entities using the
     * login user and database access.
     * @param em
     * @param loginUser
     */
    public void initialize(EntityManager em, UnixUser loginUser) {
    }
    /**
     *  completes the (new) entity before it gets stored to the database
     * @param em
     * @param loginUser
     */
    public void complete(EntityManager em, UnixUser loginUser) {
    }
    /**
     * merges this entity with the database, keeping transient members which are
     * otherwise nulled.
     *
     * Usually subclasses load the attached entity with same id and update all
     * fields to which the login user has access, but no other fields. Then the
     * attached entity is returned instead of this entity.
     * @param em
     * @param loginUser
     */
    public Entity merge(EntityManager em, UnixUser loginUser) {
        return em.merge(this);
    }
    /**
     * determines whether the given user has full read access on all merged
     * fields of this entity
     * @param loginUser
     * @return
     */
    public boolean isReadAllowedFor(UnixUser loginUser) {
        return loginUser.hasHostmasterRole();
    }
    /**
     * determines whether the given user has full write access on all merged
     * fields of this entity
     * @param loginUser
     * @return
     */
    public boolean isWriteAllowedFor(UnixUser loginUser) {
        return loginUser.hasHostmasterRole();
    }
    /**
     * returns the hive name in which this entity resists or null if entity
     * is new or does not need a hive.
     * @return
     */
    public String getHiveName() {
        return null;
    }
    /**
     * returns the UnixUser who owns this entity (and thus has broad access to it
     * @param em
     * @return
     */
    public abstract UnixUser owningUser(EntityManager em);
    /**
     * Clonable::clone did result in javasisst errors (duplicate method) since
     * JBoss 5.1.
     * This is just for testing, it does not use the proper class loader!
     */
    public <T extends Entity> T replicate() {
        try {
            ByteArrayOutputStream baOut = new ByteArrayOutputStream();
            ObjectOutputStream oOut = new ObjectOutputStream(baOut);
            oOut.writeObject(this);
            byte[] buffer = baOut.toByteArray();
            ByteArrayInputStream baIn = new ByteArrayInputStream(buffer);
            ObjectInputStream oIn = new ObjectInputStream(baIn);
            @SuppressWarnings("unchecked")
            T clone = (T) oIn.readObject();
            return clone;
        } catch (Exception exc) {
            throw new RuntimeException(exc);
        }
    }
}
hsarback/src/de/hsadmin/core/model/EntityInfo.java
New file
@@ -0,0 +1,12 @@
package de.hsadmin.core.model;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(value=RetentionPolicy.RUNTIME)
public @interface EntityInfo
{
    /// human readable identifier of the entity
    String name();
}
hsarback/src/de/hsadmin/core/model/EntitySessionHelper.java
New file
@@ -0,0 +1,41 @@
package de.hsadmin.core.model;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class EntitySessionHelper {
    private static Log log = LogFactory.getLog(EntitySessionHelper.class);
    /**
     *  helper method to create a wrapper for checking the rights according to
     *  the entity class.
     * @param entityClass
     * @return
     */
    public static AbstractModuleImpl createEntitySessionWrapper(Class<? extends Entity> entityClass) {
        // get in instance
        AbstractModuleImpl impl = null;
        try {
            // determine wrapper class
            ModuleImpl wrapperAnnot = entityClass.getAnnotation(ModuleImpl.class);
            Class<?> wrapperClass = null;
            if (wrapperAnnot != null) {
                wrapperClass = wrapperAnnot.value();
            } else {
                wrapperClass = Class.forName(entityClass.getCanonicalName() + "ModuleImpl");
            }
            // instantiate wrapper
            impl = (AbstractModuleImpl) wrapperClass.newInstance();
        } catch (ClassNotFoundException exc) {
            log.info("entity class '"
                            + entityClass.getCanonicalName()
                            + "' has no session wrapper => using restrictive default access rights");
            impl = new SecureDefaultModuleImpl();
        } catch (Exception exc) {
            throw new RuntimeException(exc);
        }
        return impl;
    }
}
hsarback/src/de/hsadmin/core/model/GenericModuleImpl.java
New file
@@ -0,0 +1,99 @@
package de.hsadmin.core.model;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class GenericModuleImpl implements ModuleInterface {
    private static final Log log = LogFactory.getLog(GenericModuleImpl.class);
    private Transaction tx;
    public GenericModuleImpl(Transaction transaction) {
        tx = transaction;
    }
    public Transaction getTransaction() {
        return tx;
    }
    @Override
    public Entity initialize(Entity newEntity) throws HSAdminException {
        log.trace("initialize(" + newEntity + ")");
        AbstractModuleImpl wrapper =
            EntitySessionHelper.createEntitySessionWrapper(newEntity.getClass());
        wrapper.construct(tx);
        return wrapper.initialize(newEntity);
    }
    @Override
    public Entity add(Entity newEntity) throws HSAdminException {
        if (!newEntity.isNew())
            throw new HSAdminException("cannot add an already persistent entity");
        log.trace("add(" + newEntity + ")");
        AbstractModuleImpl wrapper =
            EntitySessionHelper.createEntitySessionWrapper(newEntity.getClass());
        wrapper.construct(tx);
        Entity result = null;
        try {
            result = wrapper.add(newEntity);
            return result;
        } catch (Exception e) {
            throw new HSAdminException(e);
        }
    }
    @Override
    public Entity find(Class<? extends Entity> entityClass, Object key) throws HSAdminException {
        log.trace("find(" + entityClass + ", " + key + ")");
        AbstractModuleImpl wrapper = EntitySessionHelper.createEntitySessionWrapper(entityClass);
        wrapper.construct(tx);
        return wrapper.find(entityClass, key);
    }
    @Override
    public Entity findByString(Class<? extends Entity> entityClass, String key) throws HSAdminException {
        log.trace("find(" + entityClass + ", " + key + ")");
        AbstractModuleImpl wrapper = EntitySessionHelper.createEntitySessionWrapper(entityClass);
        wrapper.construct(tx);
        return wrapper.findByString(entityClass, key);
    }
    @Override
    public List<Entity> search(Class<? extends Entity> entityClass, String condition, String orderBy) throws HSAdminException {
        log.trace("search(" + entityClass + ", " + condition + ")");
        AbstractModuleImpl wrapper = EntitySessionHelper.createEntitySessionWrapper(entityClass);
        wrapper.construct(tx);
        return wrapper.search(entityClass, condition, orderBy);
    }
    @Override
    public Entity update(Entity existingEntity) throws HSAdminException {
        if (existingEntity.isNew())
            return add(existingEntity);
        log.debug("update(" + existingEntity + ")");
        AbstractModuleImpl wrapper = EntitySessionHelper.createEntitySessionWrapper(existingEntity.getClass());
        wrapper.construct(tx);
        try {
            return wrapper.update(existingEntity);
        } catch (Exception e) {
            throw new HSAdminException(e);
        }
    }
    @Override
    public void delete(Entity existingEntity) throws HSAdminException {
        log.trace("delete(" + existingEntity + ")");
        AbstractModuleImpl wrapper =
            EntitySessionHelper.createEntitySessionWrapper(existingEntity.getClass());
        wrapper.construct(tx);
        try {
            wrapper.delete(existingEntity);
        } catch (Exception e) {
            throw new HSAdminException(e);
        }
    }
}
hsarback/src/de/hsadmin/core/model/HSAdminException.java
New file
@@ -0,0 +1,19 @@
package de.hsadmin.core.model;
public class HSAdminException extends Exception {
    private static final long serialVersionUID = -5082179267383474532L;
    public HSAdminException(String message) {
        super(message);
    }
    public HSAdminException(Exception e) {
        super(e);
    }
    public HSAdminException(String message, Exception aExc) {
        super(message, aExc);
    }
}
hsarback/src/de/hsadmin/core/model/ModuleImpl.java
New file
@@ -0,0 +1,10 @@
package de.hsadmin.core.model;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface ModuleImpl
{
    Class<?> value();
}
hsarback/src/de/hsadmin/core/model/ModuleInterface.java
New file
@@ -0,0 +1,50 @@
package de.hsadmin.core.model;
import java.util.List;
/**
 * Represents a CRUD (Create, Retrieve, Update, Delete) interface for generic
 * entity instances.
 * @author peter
 */
public interface ModuleInterface {
    public Transaction getTransaction();
    /**
     * initializes a newly created entity.
     */
    public Entity initialize(Entity newEntity) throws HSAdminException;
    /**
     * Adds a new entity instance to the model.
     */
    public Entity add(Entity newEntity) throws HSAdminException;
    /**
     * Finds an entity instance in the model, using its primary key.
     */
    public Entity find(Class<? extends Entity> entityClass, Object key) throws HSAdminException;
    /**
     * Finds an entity instance in the model, using its primary key in a String
     * representation.
     */
    public Entity findByString(Class<? extends Entity> entityClass, String key) throws HSAdminException;
    /**
     * Searches entity instances in the model, using a simplified JPA-QL query.
     */
    public List<Entity> search(Class<? extends Entity> entityClass, String query, String orderBy) throws HSAdminException;
    /**
     * Updates an existing entity in the model.
     */
    public Entity update(Entity existingEntity) throws HSAdminException;
    /**
     * Deletes an existing entity from the model.
     */
    public void delete(Entity existingEntity) throws HSAdminException;
}
hsarback/src/de/hsadmin/core/model/SearchFilter.java
New file
@@ -0,0 +1,12 @@
package de.hsadmin.core.model;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/// specifies pre-filters (JPAQL) for several roles
@Retention(value=RetentionPolicy.RUNTIME)
public @interface SearchFilter
{
    /// an additional JPA condition to add to the WHERE clause
    String value();
}
hsarback/src/de/hsadmin/core/model/SecureDefaultModuleImpl.java
New file
@@ -0,0 +1,105 @@
package de.hsadmin.core.model;
import java.util.LinkedList;
import java.util.List;
/**
 *  allows access only for hostmasters, used as fallback wrapper.
 */
public class SecureDefaultModuleImpl extends AbstractModuleImpl {
    private static final long serialVersionUID = 4567381515459292565L;
    @Override
    public Entity initialize(Entity newEntity) throws AuthorisationException {
        return super.initialize(newEntity);
    }
    @Override
    public Entity find(Class<? extends Entity> entityClass, Object key) throws HSAdminException {
        Entity entity = super.find(entityClass, key);
        if (entity != null && !entity.isReadAllowedFor(getLoginUser()))
            throw new AuthorisationException(getLoginUser(), "find");
        return entity;
    }
    @Override
    public Entity findByString(Class<? extends Entity> entityClass, String key) throws HSAdminException {
        Entity entity = super.findByString(entityClass, key);
        if (entity != null && !entity.isReadAllowedFor(getLoginUser()))
            throw new AuthorisationException(getLoginUser(), "findByString");
        return entity;
    }
    @Override
    public List<Entity> search(Class<? extends Entity> entityClass, String condition, String orderBy)
            throws HSAdminException {
        // restrict query to entities where the loginUser could have rights on
        SearchFilter filterAnnot;
        if (!getLoginUser().hasHostmasterRole()
                && (filterAnnot = getSecurityFilterAnnotation(entityClass)) != null) {
            String securityCondition = filterAnnot.value();
            if (condition != null && condition.length() > 0)
                condition = "(" + condition + ")" + " AND ( "
                        + securityCondition + ")";
            else
                condition = securityCondition;
        }
        // do query
        List<Entity> res = super.search(entityClass, condition, orderBy);
        List<Entity> ret = new LinkedList<Entity>();
        // remove entities where login user has no access rights
        if (res != null) {
            for (Entity entity : res) {
                Entity returnedEntity = entity;
                if (returnedEntity.isReadAllowedFor(getLoginUser()))
                    ret.add(returnedEntity);
            }
        }
        // return clean result
        return ret;
    }
    // helper method to get annotation from class or superclass
    private SearchFilter getSecurityFilterAnnotation(Class<?> entityClass) {
        SearchFilter ret;
        while (entityClass != null) {
            ret = (SearchFilter) entityClass.getAnnotation(SearchFilter.class);
            if (ret != null)
                return ret;
            entityClass = entityClass.getSuperclass();
        }
        return null;
    }
    @Override
    public Entity add(Entity newEntity) throws HSAdminException {
        // access rights checking is done by base class
        return super.add(newEntity);
    }
    @Override
    public Entity update(Entity existingEntity) throws HSAdminException {
        // access rights checking is done by base class
        return super.update(existingEntity);
    }
    @Override
    public void delete(Entity detachedEntity) throws HSAdminException {
        // get the entity from the database
        Entity attachedEntity = getTransaction().getEntityManager().find(detachedEntity.getClass(),
                detachedEntity.id());
        // does the login user have the right to delete?
        if (!attachedEntity.isWriteAllowedFor(getLoginUser()))
            throw new AuthorisationException(getLoginUser(), "delete",
                    detachedEntity);
        super.delete(attachedEntity);
    }
}
hsarback/src/de/hsadmin/core/model/TicketAuthentication.java
New file
@@ -0,0 +1,17 @@
package de.hsadmin.core.model;
import de.hsadmin.core.model.onetier.TicketValidator;
public class TicketAuthentication {
    private static TicketAuthentication auth = new TicketAuthentication();
    public static TicketAuthentication getInstance() {
        return auth;
    }
    public boolean login(String login, String ticket) throws AuthenticationException {
        return TicketValidator.getInstance().validateTicket(login, ticket);
    }
}
hsarback/src/de/hsadmin/core/model/Transaction.java
New file
@@ -0,0 +1,179 @@
package de.hsadmin.core.model;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.jms.Queue;
import javax.jms.QueueConnectionFactory;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
//import org.hibernate.Session;
import de.hsadmin.cliClientConnector.TechnicalException;
import de.hsadmin.core.model.onetier.PersistenceManager;
import de.hsadmin.core.qserv.QueueClient;
import de.hsadmin.core.qserv.QueueTask;
public class Transaction {
    private static final Log log = LogFactory.getLog(Transaction.class);
    private EntityManager entityManager;
    private QueueConnectionFactory queueConnectionFactory;
    private String loginName;
    private Map<String, QueueTaskStore> taskStores;
    private boolean transactionActive;
    private InitialContext ctx;
    public Transaction(String loginName) {
        transactionActive = false;
        this.entityManager = PersistenceManager.getEntityManager("hsadmin");
        this.loginName = loginName;
        taskStores = new HashMap<String, QueueTaskStore>();
        try {
            ctx = new InitialContext();
            Context env = (Context) ctx.lookup("java:comp/env");
            queueConnectionFactory = (QueueConnectionFactory) env.lookup("jms/QueueCF");
        } catch (NamingException e) {
            log.fatal(e);
            e.printStackTrace();
            throw new TechnicalException(e);
        }
    }
    public EntityManager getEntityManager() {
        return entityManager;
    }
    public QueueConnectionFactory getQueueConnectionFactory() {
        return queueConnectionFactory;
    }
    public Queue lookupJMSQueue(String queueName) {
        if (ctx != null) {
            try {
                Context env = (Context) ctx.lookup("java:comp/env");
                return (Queue) env.lookup("jms/" + queueName);
            } catch (NamingException e) {
                log.fatal(e);
                e.printStackTrace();
                return null;
            }
        }
        return null;
    }
    public String getLoginName() {
        if (loginName != null) return loginName;
        return null;
    }
    public void enqueue(String hiveName, QueueTask task) {
        QueueTaskStore taskStore = taskStores.get(hiveName);
        if (taskStore == null) {
            taskStore = new QueueTaskStore();
            taskStores.put(hiveName, taskStore);
        }
        taskStore.add(task);
    }
    private void sendAll(EntityTransaction transaction) {
        boolean firstHive = true;
        for (String hive : taskStores.keySet()) {
            QueueTaskStore store = taskStores.get(hive);
            String queueName = "hsadminSystem-" + hive;
            Queue jmsSystemQueue = lookupJMSQueue(queueName);
            QueueClient qClient = null;
            try {
                qClient = new QueueClient(queueConnectionFactory, jmsSystemQueue);
                if (firstHive) {
                    firstHive = false;
                    transaction.commit();
                }
                for (QueueTask task : store.getTasks()) {
                    qClient.send(task);
                }
            } catch (Exception e) {
                throw new TechnicalException(e);
            } finally {
                store.clear();
                if (qClient != null) qClient.close();
            }
        }
        if (firstHive) {
            try {
                transaction.commit();
            } catch (Exception e) {
                throw new TechnicalException(e);
            }
        }
    }
    public void beginTransaction() {
        transactionActive = true;
        entityManager.getTransaction().begin();
    }
    public void commitTransaction() {
        sendAll(entityManager.getTransaction());
        transactionActive = false;
    }
    public void rollbackTransaction() {
        taskStores.clear();
        transactionActive = false;
        try {
            entityManager.getTransaction().rollback();
        } catch (IllegalStateException e) {
            log.info(e);
        }
    }
    public void close() {
        if (transactionActive) {
            rollbackTransaction();
        }
        entityManager.close();
    }
    /**
     * Detach entities from hibernate session.
     * Used to detach entities before update. Makes it possible to compare
     * old and new attribute values.
     */
    public void detach(Entity entity) {
        // TODO: replace hibernate specific implmentation
//        org.hibernate.Session hSession = (Session) entityManager.getDelegate();
//        hSession.evict(entity);
        if (entityManager instanceof OpenJPAEntityManager) {
            OpenJPAEntityManager openjpaEM = (OpenJPAEntityManager) entityManager;
            openjpaEM.detach(entity);
        }
    }
    class QueueTaskStore {
        private List<QueueTask> taskList;
        QueueTaskStore() {
            taskList = new ArrayList<QueueTask>();
        }
        public void clear() {
            taskList = new ArrayList<QueueTask>();
        }
        void add(QueueTask t) {
            taskList.add(t);
        }
        Iterable<QueueTask> getTasks() {
            return taskList;
        }
    }
}
hsarback/src/de/hsadmin/core/model/onetier/PersistenceManager.java
New file
@@ -0,0 +1,43 @@
package de.hsadmin.core.model.onetier;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class PersistenceManager {
    private static PersistenceManager instance;
    private Map<String, EntityManagerFactory> persistenceUnits;
    private PersistenceManager() {
        persistenceUnits = new HashMap<String, EntityManagerFactory>();
    }
    private EntityManagerFactory getEMF(String persistUnitName) {
        EntityManagerFactory emf = persistenceUnits.get(persistUnitName);
        if (emf == null) {
            emf = Persistence.createEntityManagerFactory(persistUnitName);
            persistenceUnits.put(persistUnitName, emf);
        }
        return emf;
    }
    public static EntityManager getEntityManager(String persistUnitName) {
        PersistenceManager pm = PersistenceManager.getInstance();
        EntityManagerFactory emf = pm.getEMF(persistUnitName);
        return emf.createEntityManager();
    }
    private static PersistenceManager getInstance() {
        if (instance == null) {
            instance = new PersistenceManager();
        }
        return instance;
    }
}
hsarback/src/de/hsadmin/core/model/onetier/TicketValidator.java
New file
@@ -0,0 +1,96 @@
package de.hsadmin.core.model.onetier;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import de.hsadmin.core.model.AuthenticationException;
public class TicketValidator {
    private static final Log log = LogFactory.getLog(TicketValidator.class);
    private static TicketValidator instance;
    private String proxyValidateURL;
    private String proxyServiceURL;
    private TicketValidator() {
        proxyServiceURL = null;
        proxyValidateURL = null;
    }
    public static TicketValidator getInstance() {
        if (instance == null) {
            instance = new TicketValidator();
        }
        return instance;
    }
    public void initialize(String validateURL, String serviceURL) {
        proxyServiceURL = serviceURL;
        proxyValidateURL = validateURL;
    }
    public boolean validateTicket(String runAsUser, String ticket) throws AuthenticationException {
        String ticketUser = validateTicket(ticket);
        if (runAsUser != null &&
                (runAsUser.equals(ticketUser)        // user himself
                    || (ticketUser.length() == 5 && runAsUser.startsWith(ticketUser))
                                                    // pac-admin
                    || ticketUser.length() == 2)    // hostmaster
                    // TODO: add test for member-account
                ) {
            return true;
        } else {
            throw new AuthenticationException("User " + ticketUser + " is not allowed to run as " + runAsUser);
        }
    }
    public String validateTicket(String ticket) throws AuthenticationException {
        if (proxyServiceURL == null || proxyServiceURL == null) {
            log.fatal("TicketValidator is not initialized.");
            throw new RuntimeException("TicketValidator is not initialized.");
        }
        try {
            URL url = new URL(proxyValidateURL + "?service=" + proxyServiceURL + "&ticket=" + ticket);
            URLConnection httpConnection = url.openConnection();
            httpConnection.connect();
            InputStream inputStream = httpConnection.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String nextLine = reader.readLine();
            while (nextLine != null) {
                if (nextLine.contains("<cas:user>")) {
                    String user = extractUser(nextLine);
                    inputStream.close();
                    return user;
                }
                nextLine = reader.readLine();
            }
            inputStream.close();
            log.debug("Ticket validation failed: " + ticket);
            throw new AuthenticationException("Invalid Ticket: " + ticket);
        } catch (MalformedURLException e) {
            log.fatal(e);
            throw new AuthenticationException(e.getMessage());
        } catch (IOException e) {
            log.fatal(e);
            throw new AuthenticationException(e.getMessage());
        }
    }
    private String extractUser(String nextLine) {
        int start = nextLine.indexOf("<cas:user>");
        int end = nextLine.indexOf("</cas:user>");
        String user = nextLine.substring(start + 10, end);
        return user;
    }
}
hsarback/src/de/hsadmin/core/qserv/AbstractProcessor.java
New file
@@ -0,0 +1,17 @@
package de.hsadmin.core.qserv;
import de.hsadmin.core.model.Transaction;
abstract public class AbstractProcessor implements Processor {
    private static final long serialVersionUID = -2401718681212973276L;
    @Override
    abstract public Object process() throws ProcessorException;
    @Override
    public void finalize(Transaction transaction, QueueTask task) throws ProcessorException {
        task.done();
    }
}
hsarback/src/de/hsadmin/core/qserv/CommandShell.java
New file
@@ -0,0 +1,162 @@
package de.hsadmin.core.qserv;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
public class CommandShell
{
    private static boolean bExecute = true; // really execute or just store command and stdin?
    private static String executedCommands; // stored command and stdin
    private static String[] aEnvironment; // stored environment
    /** Set mode of real execution or just storing the command and stdin.
     *
     *     @param bExec
     *         specifies whether shell commands should really be executed (true) or not (false)
     */
    public static void setExecute( boolean bExec )
    {
        bExecute = bExec;
    }
    /** Returns and clears the last command which should have been executed.
     *
     * @return
     *         Last command, plus "<<EOF\n" + stdin + "EOF" if stdin was given.
     */
    public static String popLastCommand()
    {
        String aLastCommand = executedCommands;
        executedCommands = null;
        return aLastCommand != null ? aLastCommand.trim() : null;
    }
    /** Returns and clears the environment which ist currently used for commands.
     *
     * @return
     *         Most recent command environment.
     */
    public static String[] popEnvironment()
    {
        String[] aLastEnvironment = aEnvironment;
        aEnvironment = null;
        return aLastEnvironment;
    }
    public static void setEnvironment( String[] env )
    {
        aEnvironment = env;
    }
    /** Execute the given command by a shell.
     *
     *     @throws ShellException
     *         if an execution error has occured.
     */
    public static String execute( String command )
        throws ShellException
    {
        return execute( command, null );
    }
    /** Execute the given command by a shell including stdin.
     *
     *     @throws ShellException
     *         if an execution error has occured.
     */
    public static String execute( String command, String stdInput )
        throws ShellException
    {
        Process backend = null;
        String callOutput = null;
        int exitCode = 0;
        try
        {
            String logCommand = command;
            if ( stdInput != null ) //&& stdInput.length() > 0 )
                logCommand += "<<EOF\n" + stdInput + "EOF";
            logCommand += "\n";
            // add to command buffer (used in tests)
            if ( executedCommands == null )
                executedCommands = logCommand;
            else
                executedCommands += logCommand;
            if ( bExecute )
            {
                // TODO logging
                System.out.println("--- shell command: ---" );
                System.out.println( logCommand );
                String[] cmdArray = { "/bin/bash", "-c", command };
                backend = Runtime.getRuntime().exec(cmdArray, aEnvironment);
                if ( stdInput != null )
                {
                    OutputStream stdInputStream = backend.getOutputStream();
                    PrintWriter stdInputWriter = new PrintWriter(stdInputStream);
                    stdInputWriter.print(stdInput);
                    stdInputWriter.close();
                    stdInputStream.close();
                }
                callOutput = readProcessStream(backend.getInputStream());
                exitCode = backend.waitFor();
                // TODO logging
                System.out.println( "RET: " + exitCode );
                if (exitCode != 0)
                {
                    String aErr = readProcessStream(backend.getErrorStream());
                    // TODO logging
                    System.out.println( "ERR: " + aErr  );
                    System.out.println("--- done. ---" );
                    throw new ShellException( exitCode, aErr );
                }
                // TODO logging
                System.out.println("--- done. ---" );
            }
        }
        catch (IOException e)
        {
            throw new ShellException();
        }
        catch (InterruptedException e)
        {
            throw new ShellException();
        }
        if ( callOutput != null )
        {
            // int nLen = callOutput.length();
            // if ( nLen > 0 && callOutput.charAt(nLen-1) == '\n' )
            // callOutput = callOutput.substring(0, nLen-1);
            return callOutput.trim();
        }
        return null;
    }
    private static String readProcessStream(InputStream stream) throws IOException
    {
        BufferedReader reader = new BufferedReader(
                new InputStreamReader(stream));
        StringBuffer textBuff = new StringBuffer();
        String textLine = reader.readLine();
        while (textLine != null)
        {
            textBuff.append(textLine);
            textBuff.append('\n');
            textLine = reader.readLine();
        }
        reader.close();
        return textBuff.toString();
    }
}
hsarback/src/de/hsadmin/core/qserv/CompoundProcessor.java
New file
@@ -0,0 +1,30 @@
package de.hsadmin.core.qserv;
import java.util.ArrayList;
import java.util.List;
public class CompoundProcessor extends AbstractProcessor {
    private static final long serialVersionUID = 5718623579674305929L;
    private List<Processor> aProcessors;
    public CompoundProcessor(Processor... aProcessor) {
        aProcessors = new ArrayList<Processor>();
        for (Processor aP : aProcessor) {
            aProcessors.add(aP);
        }
    }
    public Object process() throws ProcessorException {
        for (Processor aProcessor : aProcessors) {
            aProcessor.process();
        }
        return null;
    }
    public void appendProcessor(Processor aProcessor) {
        aProcessors.add(aProcessor);
    }
}
hsarback/src/de/hsadmin/core/qserv/CopyFileProcessor.java
New file
@@ -0,0 +1,22 @@
package de.hsadmin.core.qserv;
public class CopyFileProcessor extends AbstractProcessor {
    private static final long serialVersionUID = 1741419610410712714L;
    private Processor internal;
    public CopyFileProcessor(String source, String target, String owner, String group, String modeMask) {
        internal = new ShellProcessor(
            "cp " + source + " " + target + " && " +
            "chown " + owner + ":" + group + " " + target + " && " +
            "chmod " + modeMask + " " + target
        );
    }
    @Override
    public Object process() throws ProcessorException {
        return internal.process();
    }
}
hsarback/src/de/hsadmin/core/qserv/CreateFileProcessor.java
New file
@@ -0,0 +1,25 @@
package de.hsadmin.core.qserv;
import java.util.Map;
public class CreateFileProcessor extends AbstractProcessor {
    private static final long serialVersionUID = -2947609532975712444L;
    private CompoundProcessor compoundProcessor;
    public CreateFileProcessor(String templateName, Map<String, String> templateValues, String targetPath, String owner, String group, String modeMask) throws ProcessorException {
        TemplateProcessor templateProcessor = new TemplateProcessor(templateName, templateValues, targetPath, false);
        ShellProcessor shellProcessor =
            new ShellProcessor(
                "chown " + owner + ":" + group + " " + targetPath + " && " +
                "chmod " + modeMask + " " + targetPath);
        compoundProcessor = new CompoundProcessor(templateProcessor, shellProcessor);
    }
    @Override
    public Object process() throws ProcessorException {
        return compoundProcessor.process();
    }
}
hsarback/src/de/hsadmin/core/qserv/EntityProcessorFactory.java
New file
@@ -0,0 +1,22 @@
package de.hsadmin.core.qserv;
import javax.persistence.EntityManager;
import de.hsadmin.core.model.Entity;
/**
 * Most processor factories need only these methods.
 *
 * @author peter
 */
public interface EntityProcessorFactory {
    public <T extends Entity> Processor createCreateProcessor(EntityManager em,
            T entity) throws ProcessorException;
    public <T extends Entity> Processor createUpdateProcessor(EntityManager em,
            T newEntity) throws ProcessorException;
    public <T extends Entity> Processor createDeleteProcessor(EntityManager em,
            T entity) throws ProcessorException;
}
hsarback/src/de/hsadmin/core/qserv/JDBCProcessor.java
New file
@@ -0,0 +1,89 @@
package de.hsadmin.core.qserv;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import de.hsadmin.core.qserv.AbstractProcessor;
import de.hsadmin.core.qserv.ProcessorException;
import de.hsadmin.core.util.Config;
public class JDBCProcessor extends AbstractProcessor {
    private static final long serialVersionUID = 7702753017749022325L;
    private String driver;
    private String url;
    private String user;
    private String password;
    private List<String> sql;
    public JDBCProcessor(String driver, String url, String user, String password) {
        this.driver = driver;
        this.url = url;
        this.user = user;
        this.password = password;
    }
    public JDBCProcessor(String driver, String url) {
        this.driver = driver;
        this.url = url;
    }
    public void addSQL(String sqlStatement) {
        if (sql == null)
            sql = new ArrayList<String>();
        sql.add(sqlStatement);
    }
    public Object process() throws ProcessorException {
        Connection c = null;
        Config config = Config.getInstance();
        if ("com.mysql.jdbc.Driver".equals(driver)) {
            if (user == null)
                user = config.getProperty("mysqladmin.user", "root");
            if (password == null)
                password = config.getProperty("mysqladmin.password");
        }
        if ("org.postgresql.Driver".equals(driver)) {
            if (user == null)
                user = config.getProperty("pgsqladmin.user", "postgres");
            if (password == null)
                password = config.getProperty("pgsqladmin.password");
        }
        if (user == null || password == null) {
            throw new ProcessorException("database admin-user configuration failed");
        }
        try {
            Class.forName(driver);
            c = DriverManager.getConnection(url, user, password);
            if (c == null)
                throw new ProcessorException("cannot connect to '" + url + "'");
            Statement s = c.createStatement();
            for (String sqlStatement : sql) {
                s.addBatch(sqlStatement);
                System.out.println("SQL: " + sqlStatement);
            }
            return s.executeBatch();
        } catch (SQLException aSqlExc) {
            Exception exc = aSqlExc.getNextException();
            if (exc == null)
                exc = aSqlExc;
            System.out.println("ERR: " + exc.getMessage());
            throw new ProcessorException(aSqlExc.getMessage() + ", reason: "
                    + exc.getMessage());
        } catch (Exception aExc) {
            throw new ProcessorException(aExc.getMessage());
        } finally {
            if (c != null) {
                try {
                    c.close();
                } catch (Exception exc) {
                }
            }
        }
    }
}
hsarback/src/de/hsadmin/core/qserv/MailerProcessor.java
New file
@@ -0,0 +1,41 @@
package de.hsadmin.core.qserv;
import java.util.Properties;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
public class MailerProcessor extends AbstractProcessor {
    private static final long serialVersionUID = -8900943664576420041L;
    private String aTo;
    private String aSubject;
    private String aBody;
    public MailerProcessor(String aTo, String aSubject, String aBody) {
        this.aTo = aTo;
        this.aSubject = aSubject;
        this.aBody = aBody;
    }
    public Object process() throws ProcessorException {
        Properties aProps = new Properties();
        aProps.setProperty("mail.smtp.host", "localhost");
        Session aSess = Session.getInstance(aProps);
        MimeMessage aMsg = new MimeMessage(aSess);
        try {
            aMsg.setFrom(new InternetAddress("robot@hostsharing.net"));
            aMsg.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress(aTo));
            aMsg.setSubject(aSubject);
            aMsg.setText(aBody);
            MailerShell.send(aMsg);
            return null;
        } catch (MessagingException aExc) {
            throw new ProcessorException(aExc);
        }
    }
}
hsarback/src/de/hsadmin/core/qserv/MailerShell.java
New file
@@ -0,0 +1,41 @@
package de.hsadmin.core.qserv;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
public class MailerShell {
    private static boolean bExecute = true; // really execute or just store
                                            // command and stdin?
    private static MimeMessage aMessage; // stored message
    /**
     * Set mode of real mailer or just storing the messages for testing
     * purposes.
     *
     * @param bExec
     *            specifies whether messages are really send (true) or just
     *            stored (false)
     */
    public static void setExecute(boolean bExec) {
        bExecute = bExec;
    }
    /**
     * Returns and clears the last command which should have been executed.
     *
     * @return Last command, plus "<<EOF\n" + stdin + "EOF" if stdin was given.
     */
    public static MimeMessage popLastMessage() {
        MimeMessage aLastMessage = aMessage;
        aMessage = null;
        return aLastMessage;
    }
    public static void send(MimeMessage aMsg) throws MessagingException {
        if (bExecute)
            javax.mail.Transport.send(aMsg);
        aMessage = aMsg;
    }
}
hsarback/src/de/hsadmin/core/qserv/Processor.java
New file
@@ -0,0 +1,22 @@
package de.hsadmin.core.qserv;
import java.io.Serializable;
import de.hsadmin.core.model.Transaction;
/**
 * A Processor encapsulates some code to be prepared by a client and to be
 * executed on a server.
 */
public interface Processor extends Serializable {
    /**
     * Executes the encapsulated code, optionally returning a result.
     * @return
     * @throws ProcessorException
     */
    public abstract Object process() throws ProcessorException;
    public abstract void finalize(Transaction transaction, QueueTask task) throws ProcessorException;
}
hsarback/src/de/hsadmin/core/qserv/ProcessorException.java
New file
@@ -0,0 +1,21 @@
package de.hsadmin.core.qserv;
import de.hsadmin.core.model.HSAdminException;
public class ProcessorException extends HSAdminException {
    private static final long serialVersionUID = -1893688504714037106L;
    public ProcessorException(String message, Exception aExc) {
        super(message, aExc);
    }
    public ProcessorException(Exception aExc) {
        super(aExc);
    }
    public ProcessorException(String message) {
        super(message);
    }
}
hsarback/src/de/hsadmin/core/qserv/QueueClient.java
New file
@@ -0,0 +1,80 @@
package de.hsadmin.core.qserv;
import javax.jms.JMSException;
import javax.jms.JMSSecurityException;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import de.hsadmin.core.util.Config;
/**
 * An instance of this class is used to send Processor instances to the
 * QueueServer.
 *
 * @author mi
 */
public class QueueClient extends QueueCommons {
    private QueueConnectionFactory jmsConnectionFactory;
    private Queue jmsSystemQueue;
    private QueueConnection jmsConnection;
    private QueueSession jmsSession;
    private QueueSender jmsSender;
    public QueueClient(QueueConnectionFactory jmsConFact, Queue jmsSysQueue)
            throws JMSException {
        this.jmsConnectionFactory = jmsConFact;
        this.jmsSystemQueue = jmsSysQueue;
        Config config = Config.getInstance();
        String jmsUser = config.getProperty("hsadmin.jms.username", "hsadmin");
        String jmsPass = config.getProperty("hsadmin.jms.password", "hsadmin-pw");
        jmsConnection = jmsConnectionFactory.createQueueConnection(jmsUser, jmsPass);
        jmsSession = jmsConnection.createQueueSession(DEFAULT, Session.AUTO_ACKNOWLEDGE);
        jmsSender = jmsSession.createSender(jmsSystemQueue);
    }
    // Query a single value from the QueueServer which will process it as
    // root.
    public void send(QueueTask task) throws ProcessorException {
        try {
            ObjectMessage jmsMessage = jmsSession.createObjectMessage(task);
            jmsSender.send(jmsMessage);
        } catch (JMSSecurityException secExc) {
            secExc.printStackTrace();
            throw new ProcessorException("Not allowed to send to queue "
                    + queueName(jmsSystemQueue) + " failed", secExc);
        } catch (Exception exc) {
            exc.printStackTrace();
            throw new ProcessorException("Error while sending to queue "
                    + queueName(jmsSystemQueue) + " failed", exc);
        }
    }
    private String queueName(Queue jmsSystemQueue2) {
        try {
            return jmsSystemQueue.getQueueName();
        } catch (Exception exc) {
            return "<unknown>";
        }
    }
    // frees all ressources
    public void close() {
        try {
            if (jmsSender != null) jmsSender.close();
        } catch (Exception exc) { }
        try {
            if (jmsSession != null) jmsSession.close();
        } catch (Exception exc) { }
        try {
            if (jmsConnection != null) jmsConnection.close();
        } catch (Exception exc) { }
    }
}
hsarback/src/de/hsadmin/core/qserv/QueueCommons.java
New file
@@ -0,0 +1,14 @@
package de.hsadmin.core.qserv;
public class QueueCommons
{
    protected static final boolean TRANSACTED = true;
    protected static final boolean IMMEDIATE = false;
    protected static final boolean DEFAULT = IMMEDIATE;
    public QueueCommons()
    {
        super();
    }
}
hsarback/src/de/hsadmin/core/qserv/QueueServer.java
New file
@@ -0,0 +1,254 @@
package de.hsadmin.core.qserv;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.Properties;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
public class QueueServer extends QueueCommons implements
        MessageListener, ExceptionListener {
    private static final String VERSION_NO = "1.0";
    private int nMessagesProcessed = 0;
    private QueueConnection conn;
    private QueueSession queueSession;
    private String jmsStatusQueue;
    private String jmsPassWord;
    private String jmsUserName;
    private String jmsSystemQueue;
    private String jmsFactory;
    /** Runs the QueueServer, using the arguments as ConnectionFactory
     *  and Topic names.
     */
    public static void main(String[] args) throws Exception {
        File propFile = new File(System.getProperty("user.dir"),
                "conf/qserv.properties");
        if (args.length == 1)
            propFile = new File(args[0]);
        else if (args.length != 0) {
            throw new Exception(
                    "Wrong number of arguments.\n"
                            + "With no arguments '"
                            + propFile
                            + "' will be used as config file.\n"
                            + "Or give a properties file as single argument.\n\n"
                            + "Example config file:\n\n"
                            + "hsadmin.jms.factory=QueueCF\n"
                            + "hsadmin.jms.system-queue=hive-h01\n"
                            + "hsadmin.jms.status-queue=queue/hsadminStatus\n"
                            + "hsadmin.jms.username=hive-h01\n"
                            + "hsadmin.jms.password=geheimeskennwort\n");
        }
        FileInputStream propStream = new FileInputStream(propFile);
        Properties props = new Properties(System.getProperties());
        props.load(propStream);
        propStream.close();
        String stdout = System.getProperty("hsadmin.stdout");
        if (stdout != null && stdout.length() > 0)
            System.setOut(new PrintStream(new FileOutputStream(stdout)));
        String stderr = System.getProperty("hsadmin.stderr");
        if (stderr != null && stderr.length() > 0)
            System.setErr(new PrintStream(new FileOutputStream(stderr)));
        final QueueServer qServ = new QueueServer();
        qServ.setJmsFactory(props.getProperty("hsadmin.jms.factory"));
        qServ.setJmsSystemQueue(props.getProperty("hsadmin.jms.system-queue"));
        qServ.setJmsStatusQueue(props.getProperty("hsadmin.jms.status-queue"));
        qServ.setJmsUserName(props.getProperty("hsadmin.jms.username"));
        qServ.setJmsPassWord(props.getProperty("hsadmin.jms.password"));
        System.out.println("==================================================");
        System.out.println("hsadmin-qserv " + VERSION_NO +
                " started using:");
        System.out.println("queue server: "
                + props.getProperty("hsadmin.jms.factory"));
        System.out.println("system queue: "
                + props.getProperty("hsadmin.jms.system-queue"));
        System.out.println("status queue: "
                + props.getProperty("hsadmin.jms.status-queue"));
        System.out.println("queue user:   "
                + props.getProperty("hsadmin.jms.username"));
        System.out.println("==================================================");
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                qServ.close();
            }
        });
        while (!qServ.connect()) {
            Thread.sleep(10000);
        }
        while (true) {
            Thread.sleep(10000);
        }
    }
    private boolean connect() {
        // create JMS connection and session
        try {
            Context ctx = new InitialContext();
            QueueConnectionFactory connectionFactory =
                (QueueConnectionFactory) ctx.lookup(jmsFactory);
            conn = connectionFactory.createQueueConnection(jmsUserName, jmsPassWord);
            conn.setExceptionListener(this);
            queueSession = conn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
            Queue queue = (Queue) ctx.lookup(jmsSystemQueue);
            conn.start();
            QueueReceiver receiver = queueSession.createReceiver(queue);
            receiver.setMessageListener(this);
            return true;
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return false;
        }
    }
    // / Is called when a message arrives; processes Processor within message.
    public synchronized void onMessage(Message jmsMessage) {
        // TODO logging
        System.out.println("\nonMessage(" + jmsMessage);
        QueueTask task = null;
        try {
            // expect ObjectMessage
            ObjectMessage jmsObjectMessage = (ObjectMessage) jmsMessage;
            // get QueueTask
            task = (QueueTask) jmsObjectMessage.getObject();
            // TODO: logging
            System.out.println("processing (" + task.getTitle() + " | started("
                    + task.getStarted() + ") |" + task.getDetails() + "|"
                    + task.getProcessor() + ")");
            System.out.println("with " + task.getProcessor());
            // execute processor within the message
            task.getProcessor().process();
            System.out.println("done");
            // mark done
            sendStatus(task);
        } catch (Exception receiveException) {
            System.err.println("exception " + receiveException); // TODO:
                                                                    // logging
            receiveException.printStackTrace(System.err);
            if (receiveException.getCause() != null) {
                System.err.println("caused by exception "
                        + receiveException.getCause()); // TODO: logging
                receiveException.getCause().printStackTrace(System.err);
            } else
                System.err.println("no further cause available");
            task.setException(receiveException);
            sendStatus(task);
        } catch (Throwable t) {
            System.err.println("severe exception " + t); // TODO: logging
        } finally {
            setNMessagesProcessed(getNMessagesProcessed() + 1);
            notifyAll();
        }
    }
    @Override
    public void onException(JMSException e) {
        System.out.println("Exception: " + e.getMessage());
        close();
        while (!connect()) {
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e1) { }
        }
    }
    public void close() {
        if (queueSession != null) {
            try {
                queueSession.close();
            } catch (JMSException e1) { }
        }
        if (conn != null) {
            try {
                conn.close();
            } catch (JMSException e1) { }
        }
    }
    // / send the status information back to the hsadmin server
    protected void sendStatus(QueueTask queueMessage) {
        MessageProducer producer = null;
        Session statusSession = null;
        Connection statusConnection = null;
        try {
            System.out.println("sendStatus( " + queueMessage + ")");
            Context ctx = new InitialContext();
            System.out.println("Created InitialContext");
            ConnectionFactory connectionFactory = (ConnectionFactory) ctx.lookup(jmsFactory);
            System.out.println("connectionFactory= "
                    + connectionFactory.toString());
            Destination queue = (Destination) ctx.lookup(jmsStatusQueue);
            statusConnection = connectionFactory.createConnection(jmsUserName, jmsPassWord);
            statusSession = statusConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            producer = statusSession.createProducer(queue);
            ObjectMessage statusMessage = statusSession.createObjectMessage(queueMessage);
            System.out.println("send( " + statusMessage + ")");
            producer.send(statusMessage);
        } catch (Exception statusException) {
            // TODO: log exception
            System.out.println("failed");
            statusException.printStackTrace(System.err);
        } finally {
            // close JMS
            try { producer.close(); } catch (Exception exc) { }
            try { statusSession.close(); } catch (Exception exc) { }
            try { statusConnection.close(); } catch (Exception exc) { }
        }
    }
    public void setNMessagesProcessed(int nMessagesProcessed) {
        this.nMessagesProcessed = nMessagesProcessed;
    }
    public int getNMessagesProcessed() {
        return nMessagesProcessed;
    }
    public void setJmsStatusQueue(String property) {
        jmsStatusQueue = property;
    }
    public void setJmsPassWord(String property) {
        jmsPassWord = property;
    }
    public void setJmsUserName(String property) {
        jmsUserName = property;
    }
    public void setJmsSystemQueue(String property) {
        jmsSystemQueue = property;
    }
    public void setJmsFactory(String property) {
        jmsFactory = property;
    }
}
hsarback/src/de/hsadmin/core/qserv/QueueStatusReceiverServlet.java
New file
@@ -0,0 +1,141 @@
package de.hsadmin.core.qserv;
import java.io.IOException;
import java.io.PrintWriter;
import javax.jms.ExceptionListener;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueReceiver;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.EntityManager;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import de.hsadmin.cliClientConnector.TechnicalException;
import de.hsadmin.core.model.Transaction;
import de.hsadmin.core.util.Config;
public class QueueStatusReceiverServlet extends HttpServlet
    implements MessageListener, ExceptionListener {
    private static final long serialVersionUID = -5701350884034782083L;
    private String jmsUser;
    private String jmsPass;
    private QueueConnectionFactory queueConnectionFactory;
    private QueueConnection queueConnection;
    private QueueSession queueSession;
    private boolean isConnected;
    private int messageCount;
    private int errorCount;
    @Override
    public void init() throws ServletException {
        isConnected = false;
        messageCount = 0;
        errorCount = 0;
        try {
            connect();
        } catch (NamingException e) {
            throw new ServletException(e);
        }
    }
    private void connect() throws NamingException {
        Config config = Config.getInstance();
        jmsUser = config.getProperty("hsadmin.jms.username", "hsadmin");
        jmsPass = config.getProperty("hsadmin.jms.password", "hsadmin-pw");
        InitialContext ctx = new InitialContext();
        Context env = (Context) ctx.lookup("java:comp/env");
        queueConnectionFactory = (QueueConnectionFactory) env.lookup("jms/QueueCF");
        while (!isConnected) {
            try {
                queueConnection = queueConnectionFactory.createQueueConnection(jmsUser, jmsPass);
                queueConnection.setExceptionListener(this);
                queueSession = queueConnection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
                Queue queue = (Queue) env.lookup("jms/hsadminStatus");
                queueConnection.start();
                QueueReceiver receiver = queueSession.createReceiver(queue);
                receiver.setMessageListener(this);
                isConnected = true;
            } catch (JMSException e) {
                close();
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e1) { }
            }
        }
    }
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        resp.setContentType("text/plain");
        PrintWriter writer = resp.getWriter();
        writer.println("Verbindungsstatus: " + (isConnected ? "OK" : "ERROR"));
        writer.println("Verarbeitete Nachrichten: " + messageCount);
        writer.println("Verarbeitungsfehler: " + errorCount);
        writer.close();
    }
    @Override
    public void destroy() {
        close();
    }
    private void close() {
        try { if (queueSession != null) queueSession.close(); } catch (JMSException e) { }
        try { if (queueConnection != null) queueConnection.close(); } catch (JMSException e) { }
        isConnected = false;
    }
    @Override
    public void onMessage(Message jmsMessage) {
        Transaction transaction = null;
        messageCount++;
        try {
            ObjectMessage objMessage = (ObjectMessage) jmsMessage;
            QueueTask detachedQT = (QueueTask) objMessage.getObject();
            transaction = new Transaction("statusreceiver");
            transaction.beginTransaction();
            EntityManager em = transaction.getEntityManager();
            // merging detachedQT directly makes Hibernate run into an endless recursion (stack overflow)
            em.clear();
            QueueTask persistentQT = em.find(QueueTask.class, detachedQT.getId());
            persistentQT.assign(detachedQT);
            Processor processor = persistentQT.getProcessor();
            if (processor != null) {
                processor.finalize(transaction, persistentQT);
            }
            em.persist(persistentQT);
            em.flush();
            transaction.commitTransaction();
        } catch (Exception e) {
            errorCount++;
            if (transaction != null) transaction.rollbackTransaction();
            throw new TechnicalException(e);
        } finally {
            if (transaction != null) transaction.close();
        }
    }
    @Override
    public void onException(JMSException exception) {
        close();
        try { Thread.sleep(10000); } catch (InterruptedException e) { }
        try { connect(); } catch (NamingException e) { }
    }
}
hsarback/src/de/hsadmin/core/qserv/QueueTask.java
New file
@@ -0,0 +1,232 @@
package de.hsadmin.core.qserv;
import java.io.Serializable;
import java.util.Date;
import javax.persistence.EntityManager;
import javax.persistence.FetchType;
import javax.persistence.GenerationType;
import de.hsadmin.core.model.Entity;
import de.hsadmin.core.model.EntityInfo;
import de.hsadmin.core.model.ModuleImpl;
import de.hsadmin.mods.qstat.QTaskModuleImpl;
import de.hsadmin.mods.user.UnixUser;
@javax.persistence.Entity(name = "QueueTasks")
@javax.persistence.Table(name = "queue_task")
@javax.persistence.SequenceGenerator(name = "QueueTaskSeqGen", sequenceName = "queue_task_id_seq")
@EntityInfo(name = "Systemauftrag")
@ModuleImpl(QTaskModuleImpl.class)
public class QueueTask extends Entity implements Serializable {
    private static final long serialVersionUID = 2171870783383767875L;
    @javax.persistence.Id
    @javax.persistence.GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "QueueTaskSeqGen")
    @javax.persistence.Column(name = "task_id", columnDefinition = "integer")
    private long id;
    @javax.persistence.JoinColumn(name="user_id", columnDefinition="integer", nullable=true)
    @javax.persistence.ManyToOne(fetch=FetchType.EAGER)
    private UnixUser user;
    @javax.persistence.Column(name = "started", columnDefinition = "date")
    @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP)
    private Date started;
    @javax.persistence.Column(name = "finished", columnDefinition = "date", nullable = true)
    @javax.persistence.Temporal(javax.persistence.TemporalType.TIMESTAMP)
    private Date finished;
    @javax.persistence.Column(name = "title", columnDefinition = "character varying(192)")
    private String title;
    @javax.persistence.Column(name = "details", columnDefinition = "text", nullable = true)
    private String details;
    private Processor proc;
    @javax.persistence.Column(name = "exception", columnDefinition = "text", nullable = true)
    private String exception;
    public QueueTask() {
    }
    public QueueTask(UnixUser user, String title, String details, Processor proc) {
        this.user = user;
        this.title = title;
        this.details = details;
        this.started = new Date();
        this.finished = null;
        this.proc = proc;
        this.exception = null;
    }
    @Override
    public UnixUser owningUser(EntityManager em) {
        return user;
    }
    /**
     *  determines whether the given user has full read access
     *  on all merged fields of this entity
     */
    @Override
    public boolean isReadAllowedFor(UnixUser loginUser) {
        return loginUser.hasPacAdminRoleFor(getUser().getPac())
                || loginUser.id() == getUser().id();
    }
    /**
     *  JPA-Query restriction for these entities
     * @return
     */
    public static String restriction() {
        return
            // customers can see all entries of all users of their pacs
            "obj.user.pac.customer.name=:loginUserName OR " +
            // packet admins can access all entries of their users
            // TODO: Hostsharing convention
            "obj.user.pac.name=:loginUserName OR " +
            // otherwise only their own tasks
            "obj.user=:loginUser";
    }
    @Override
    public String createStringKey() {
        return Long.toString(id);
    }
    @Override
    public long id() {
        return id;
    }
    public long getId() {
        return id;
    }
    protected void setId(long id) {
        this.id = id;
    }
    public UnixUser getUser() {
        return user;
    }
    public void setUser(UnixUser user) {
        this.user = user;
    }
    public Date getStarted() {
        return started;
    }
    public void setStarted(Date started) {
        this.finished = started;
    }
    public Date getFinished() {
        return finished;
    }
    /**
     * virtual attribute done
     * @return
     */
    @javax.persistence.Transient
    public boolean isDone() {
        return finished != null;
    }
    public void done() {
        finished = new java.util.Date();
    }
    /**
     * virtual attribute status
     * @return
     */
    @javax.persistence.Transient
    public QueueTaskStatus getStatus() {
        return isDone() ? (exception == null ? QueueTaskStatus.DONE
                : QueueTaskStatus.ERROR) : QueueTaskStatus.PENDING;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getDetails() {
        return details;
    }
    public void setDetails(String details) {
        this.details = details;
    }
    public Processor getProcessor() {
        return proc;
    }
    public void setProcessor(Processor proc) {
        this.proc = proc;
    }
    public String getException() {
        return exception;
    };
    public void setException(String exception) {
        this.exception = exception;
    }
    public void setException(Exception exception) {
        this.exception = exception.toString();
        // TODO: stack+cause
    }
    @Override
    public boolean isNew() {
        return id == 0;
    }
    /**
     * assigns all data field of qt to this instance, with
     * the exception of the id
     */
    public void assign(QueueTask qt) {
        this.user = qt.user;
        this.title = qt.title;
        this.details = qt.details;
        this.started = qt.started;
        this.finished = qt.finished;
        this.proc = qt.proc;
        this.exception = qt.exception;
    }
    @Override
    public String getHiveName() {
        return null;
    }
    public enum QueueTaskStatus {
        PENDING("pending"), ERROR("error"), DONE("done");
        private final String color;
        QueueTaskStatus(final String color) {
            this.color = color;
        }
        public String toString() {
            return color;
        }
    }
}
hsarback/src/de/hsadmin/core/qserv/ShellException.java
New file
@@ -0,0 +1,19 @@
package de.hsadmin.core.qserv;
public class ShellException
        extends Exception
{
    private static final long serialVersionUID = 8335020360721047849L;
    int nExitCode;
    public ShellException()
    {
    }
    public ShellException( int exitCode, String message )
    {
        super( message );
        nExitCode = exitCode;
    }
}
hsarback/src/de/hsadmin/core/qserv/ShellProcessor.java
New file
@@ -0,0 +1,76 @@
package de.hsadmin.core.qserv;
/**
 * A ShellProcessor encapsulates a shell command as a Processor.
 *
 * As such, a client can prepare shell commands to be processed on a server.
 *
 * @author mi
 */
public class ShellProcessor extends AbstractProcessor {
    private static final long serialVersionUID = -649045174380048818L;
    private String aSystemCall;
    private String[] aEnv;
    private String aInput;
    private String aOutput;
    private String aErrors;
    /**
     * Constructor for a queue entry which executes a system call.
     *
     * @param aSystemCall
     *            the system call to be executed
     */
    public ShellProcessor(String aSystemCall) {
        this(aSystemCall, null, null);
    }
    /**
     * Constructor for a queue entry which executes a system call with stdin
     * data.
     *
     * @param aSystemCall
     *            the system call to be executed
     * @param aInput
     *            data for stdin of the system call
     */
    public ShellProcessor(String aSystemCall, String aInput) {
        this.aSystemCall = aSystemCall;
        this.aInput = aInput;
    }
    public ShellProcessor(String aSystemCall, String[] aEnv, String aInput) {
        this.aSystemCall = aSystemCall;
        this.aEnv = aEnv;
        this.aInput = aInput;
    }
    public Object process() throws ProcessorException {
        try {
            CommandShell.setEnvironment(aEnv);
            aOutput = CommandShell.execute(aSystemCall, aInput);
            return aOutput;
        } catch (ShellException aExc) {
            aExc.printStackTrace(System.err); // Logging
            throw new ProcessorException(aExc);
        }
    }
    /**
     * @return Returns stderr as a String.
     */
    public String getErrors() {
        return aErrors;
    }
    /**
     * @return Returns stdout as a String.
     */
    public String getOutput() {
        return aOutput;
    }
}
hsarback/src/de/hsadmin/core/qserv/TemplateProcessor.java
New file
@@ -0,0 +1,57 @@
package de.hsadmin.core.qserv;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStrea