initial commit
diff --git a/src/.DS_Store b/src/.DS_Store
new file mode 100644
index 0000000..16a96aa
--- /dev/null
+++ b/src/.DS_Store
Binary files differ
diff --git a/src/main/.DS_Store b/src/main/.DS_Store
new file mode 100644
index 0000000..d94e183
--- /dev/null
+++ b/src/main/.DS_Store
Binary files differ
diff --git a/src/main/java/.DS_Store b/src/main/java/.DS_Store
new file mode 100644
index 0000000..e282944
--- /dev/null
+++ b/src/main/java/.DS_Store
Binary files differ
diff --git a/src/main/java/de/.DS_Store b/src/main/java/de/.DS_Store
new file mode 100644
index 0000000..b276992
--- /dev/null
+++ b/src/main/java/de/.DS_Store
Binary files differ
diff --git a/src/main/java/de/ids_mannheim/.DS_Store b/src/main/java/de/ids_mannheim/.DS_Store
new file mode 100644
index 0000000..07256f1
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/.DS_Store
Binary files differ
diff --git a/src/main/java/de/ids_mannheim/korap/.DS_Store b/src/main/java/de/ids_mannheim/korap/.DS_Store
new file mode 100644
index 0000000..e540d83
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/.DS_Store
Binary files differ
diff --git a/src/main/java/de/ids_mannheim/korap/auditing/AuditRecord.java b/src/main/java/de/ids_mannheim/korap/auditing/AuditRecord.java
new file mode 100644
index 0000000..28f915d
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/auditing/AuditRecord.java
@@ -0,0 +1,157 @@
+package de.ids_mannheim.korap.auditing;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.util.Arrays;
+
+/**
+ * @author hanl
+ *         <p/>
+ *         Record holder for auditing requests. Holds the data until it can be persisted to a database
+ */
+@Getter
+@Setter
+public class AuditRecord {
+
+    // fixme: handle via status codes
+    @Deprecated
+    public enum Operation {
+        GET, INSERT, UPDATE, DELETE, CREATE
+    }
+
+    public enum CATEGORY {
+        SECURITY, DATABASE, RESOURCE, QUERY, SERVICE
+    }
+
+    @JsonIgnore
+    private Integer id;
+    //security access describes changes in user authorities and access control permissions of resources
+    private String userid;
+    private String target;
+
+    //fixme: replace with more specific error codes
+    private CATEGORY category;
+    private String loc;
+    private Long timestamp;
+    private Integer status = -1;
+    private String args;
+    private String field_1 = "None";
+
+    private AuditRecord() {
+        this.timestamp = TimeUtils.getNow().getMillis();
+    }
+
+    public AuditRecord(CATEGORY category) {
+        this();
+        this.category = category;
+    }
+
+    public AuditRecord(CATEGORY cat, Object userID, Integer status) {
+        this(cat);
+        this.status = status;
+        if (userID != null) {
+            //todo: client info!
+            //            this.loc = clientInfoToString(user.getTokenContext().getHostAddress(),
+            //                    user.getTokenContext().getUserAgent());
+            this.loc = clientInfoToString("null", "null");
+            userid = String.valueOf(userID);
+        }else {
+            this.loc = clientInfoToString("null", "null");
+            userid = "-1";
+        }
+    }
+
+    public static AuditRecord serviceRecord(Object user, Integer status,
+            String... args) {
+        AuditRecord r = new AuditRecord(CATEGORY.SERVICE);
+        r.setArgs(Arrays.asList(args).toString());
+        r.setUserid(String.valueOf(user));
+        r.setStatus(status);
+        return r;
+    }
+
+    public static AuditRecord dbRecord(Object user, Integer status,
+            String... args) {
+        AuditRecord r = new AuditRecord(CATEGORY.DATABASE);
+        r.setArgs(Arrays.asList(args).toString());
+        r.setUserid(String.valueOf(user));
+        r.setStatus(status);
+        return r;
+    }
+
+    public AuditRecord fromJson(String json) {
+        JsonNode n = JsonUtils.readTree(json);
+        AuditRecord r = new AuditRecord();
+        r.setCategory(CATEGORY.valueOf(n.path("category").asText()));
+        r.setTarget(n.path("target").asText());
+        r.setField_1(n.path("field_1").asText());
+        r.setUserid(n.path("account").asText());
+        r.setStatus(n.path("status").asInt());
+        r.setLoc(n.path("loc").asText());
+        return r;
+    }
+
+    private String clientInfoToString(String IP, String userAgent) {
+        return userAgent + "@" + IP;
+    }
+
+    @Override
+    public String toString() {
+        return "Record{" +
+                "account='" + userid + '\'' +
+                ", category=" + category +
+                ", loc='" + loc + '\'' +
+                ", timestamp=" + timestamp +
+                ", status='" + status + '\'' +
+                ", field_1='" + field_1 + '\'' +
+                '}';
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        AuditRecord that = (AuditRecord) o;
+
+        if (userid != null ? !userid.equals(that.userid) : that.userid != null)
+            return false;
+        if (category != that.category)
+            return false;
+        if (status != null ? !status.equals(that.status) : that.status != null)
+            return false;
+        if (field_1 != null ?
+                !field_1.equals(that.field_1) :
+                that.field_1 != null)
+            return false;
+        if (loc != null ? !loc.equals(that.loc) : that.loc != null)
+            return false;
+        if (target != null ? !target.equals(that.target) : that.target != null)
+            return false;
+        if (timestamp != null ?
+                !timestamp.equals(that.timestamp) :
+                that.timestamp != null)
+            return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = userid != null ? userid.hashCode() : 0;
+        result = 31 * result + (target != null ? target.hashCode() : 0);
+        result = 31 * result + category.hashCode();
+        result = 31 * result + (loc != null ? loc.hashCode() : 0);
+        result = 31 * result + (timestamp != null ? timestamp.hashCode() : 0);
+        result = 31 * result + (status != null ? status.hashCode() : 0);
+        result = 31 * result + (field_1 != null ? field_1.hashCode() : 0);
+        return result;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/config/BeanConfiguration.java b/src/main/java/de/ids_mannheim/korap/config/BeanConfiguration.java
new file mode 100644
index 0000000..f4b72b1
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/config/BeanConfiguration.java
@@ -0,0 +1,69 @@
+package de.ids_mannheim.korap.config;
+
+import de.ids_mannheim.korap.interfaces.AuditingIface;
+import de.ids_mannheim.korap.plugins.PluginManager;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.context.support.FileSystemXmlApplicationContext;
+
+import java.net.URL;
+
+/**
+ * User: hanl
+ * Date: 10/9/13
+ * Time: 11:20 AM
+ */
+public class BeanConfiguration {
+
+    private static final String config_file = "default-config.xml";
+
+    private static ApplicationContext context = null;
+    private static PluginManager plugins;
+
+    private static void loadPlugins() {
+        plugins = new PluginManager();
+        plugins.loadPluginInterfaces();
+    }
+
+    public static void loadContext() {
+        URL url = BeanConfiguration.class.getClassLoader()
+                .getResource(config_file);
+        if (url != null && context == null)
+            context = new ClassPathXmlApplicationContext(config_file);
+    }
+
+    public static void loadContext(String filepath) {
+        if (filepath == null)
+            loadContext();
+        else {
+            if (context == null)
+                context = new FileSystemXmlApplicationContext(
+                        "file:" + filepath);
+        }
+    }
+
+    public static <T extends KustvaktConfiguration> T getConfiguration() {
+        return (T) getBean("config");
+    }
+
+    public static <T extends KustvaktConfiguration> T getConfiguration(
+            Class<T> clazz) {
+        return getBean(clazz);
+    }
+
+    public static boolean hasContext() {
+        return context != null;
+    }
+
+    protected static <T> T getBean(Class<T> clazz) {
+        return context.getBean(clazz);
+    }
+
+    protected static <T> T getBean(String name) {
+        return (T) context.getBean(name);
+    }
+
+    public static AuditingIface getAuditingProvider() {
+        return (AuditingIface) context.getBean("auditingProvider");
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/config/KustvaktClassLoader.java b/src/main/java/de/ids_mannheim/korap/config/KustvaktClassLoader.java
new file mode 100644
index 0000000..de4748e
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/config/KustvaktClassLoader.java
@@ -0,0 +1,26 @@
+package de.ids_mannheim.korap.config;
+
+import org.reflections.Reflections;
+
+import java.util.Set;
+
+/**
+ * @author hanl
+ * @date 10/06/2015
+ */
+public class KustvaktClassLoader {
+
+    private static final Reflections reflections = new Reflections(
+            "de.ids_mannheim.korap");
+
+    /**
+     * loads interface implementations in current classpath
+     *
+     * @param iface
+     * @param <T>
+     * @return
+     */
+    public static <T> Set<Class<? extends T>> load(Class<T> iface) {
+        return reflections.getSubTypesOf(iface);
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java b/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java
new file mode 100644
index 0000000..52ac3c7
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/config/KustvaktConfiguration.java
@@ -0,0 +1,116 @@
+package de.ids_mannheim.korap.config;
+
+import de.ids_mannheim.korap.utils.KorAPLogger;
+import lombok.Getter;
+import org.apache.log4j.PropertyConfigurator;
+import org.slf4j.Logger;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * if configuration class is extended, load method should be overriden
+ * @author hanl
+ * @date 05/02/2014
+ */
+
+@Getter
+public class KustvaktConfiguration {
+
+    private final Logger jlog = KorAPLogger
+            .initiate(KustvaktConfiguration.class);
+
+    // deprec?!
+    private final BACKENDS DEFAULT_ENGINE = BACKENDS.LUCENE;
+    private int maxhits;
+
+    private int port;
+    private int returnhits;
+    private String serverHost;
+    private String indexDir;
+
+    private List<String> queryLanguages;
+    private String host;
+
+    private URL issuer;
+
+    /**
+     * loading of the properties and mapping to parameter variables
+     *
+     * @param korap
+     * @return
+     */
+    protected Properties load(Properties korap) {
+        String log4jconfig = korap
+                .getProperty("log4jconfig", "log4j.properties");
+        loadLog4jLogger(log4jconfig);
+        maxhits = new Integer(korap.getProperty("maxhits", "50000"));
+        returnhits = new Integer(korap.getProperty("returnhits", "50000"));
+        indexDir = korap.getProperty("lucene.indexDir", "");
+        port = new Integer(korap.getProperty("server.port", "8080"));
+        // server options
+        serverHost = String
+                .valueOf(korap.getProperty("server.host", "localhost"));
+        String queries = korap.getProperty("korap.ql", "");
+        String[] qls = queries.split(",");
+        queryLanguages = new ArrayList<>();
+        for (String querylang : qls)
+            queryLanguages.add(querylang.trim().toUpperCase());
+        //        issuer = new URL(korap.getProperty("korap.issuer", ""));
+        return korap;
+    }
+
+    public void setProperties(Properties props) {
+        this.load(props);
+    }
+
+    public BACKENDS chooseBackend(String value) {
+        if (value == null || value.equals("null"))
+            return DEFAULT_ENGINE;
+        else
+            return Enum.valueOf(BACKENDS.class, value.toUpperCase());
+    }
+
+    private void loadLog4jLogger(String log4jconfig) {
+        /** load log4j configuration file programmatically */
+        Properties log4j = new Properties();
+        try {
+            if (!log4jconfig.equals("")) {
+                log4j.load(new FileInputStream(log4jconfig));
+                PropertyConfigurator.configure(log4j);
+                jlog.info(
+                        "using local logging properties file ({}) to configure logging system",
+                        log4jconfig);
+            }else
+                loadClassLogger();
+        }catch (Exception e) {
+            loadClassLogger();
+        }
+    }
+
+    public void loadClassLogger() {
+        Properties log4j = new Properties();
+        jlog.info(
+                "using class path logging properties file to configure logging system");
+
+        try {
+            log4j.load(KustvaktConfiguration.class.getClassLoader()
+                    .getResourceAsStream("log4j.properties"));
+        }catch (IOException e) {
+            // do nothing
+        }
+
+        PropertyConfigurator.configure(log4j);
+        jlog.warn(
+                "No logger properties detected. Using default logger properties");
+    }
+
+    public enum BACKENDS {
+        NEO4J, LUCENE
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/exceptions/BaseException.java b/src/main/java/de/ids_mannheim/korap/exceptions/BaseException.java
new file mode 100644
index 0000000..408f591
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/exceptions/BaseException.java
@@ -0,0 +1,47 @@
+package de.ids_mannheim.korap.exceptions;
+
+import de.ids_mannheim.korap.auditing.AuditRecord;
+import lombok.Getter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author hanl
+ * @date 29/01/2014
+ */
+@Getter
+public abstract class BaseException extends Exception {
+
+    protected List<AuditRecord> records = new ArrayList<>();
+    private Integer statusCode;
+    private String entity;
+
+
+    public BaseException(int code) {
+        this.statusCode = code;
+    }
+
+    public BaseException(int status, String message, String entity) {
+        super(message);
+        this.statusCode = status;
+        this.entity = entity;
+    }
+
+    public BaseException(int status, String entity) {
+        this(status);
+        this.entity = entity;
+    }
+
+    public BaseException(Throwable cause, int status) {
+        super(cause);
+        this.statusCode = status;
+
+    }
+
+    public BaseException(String message, Throwable cause, int status) {
+        super(message, cause);
+        this.statusCode = status;
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/exceptions/EmptyResultException.java b/src/main/java/de/ids_mannheim/korap/exceptions/EmptyResultException.java
new file mode 100644
index 0000000..5541695
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/exceptions/EmptyResultException.java
@@ -0,0 +1,13 @@
+package de.ids_mannheim.korap.exceptions;
+
+/**
+ * @author hanl
+ * @date 25/03/2014
+ */
+public class EmptyResultException extends BaseException {
+
+    public EmptyResultException(String entity) {
+        super(StatusCodes.EMPTY_RESULTS, entity);
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/exceptions/KorAPException.java b/src/main/java/de/ids_mannheim/korap/exceptions/KorAPException.java
new file mode 100644
index 0000000..a050770
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/exceptions/KorAPException.java
@@ -0,0 +1,52 @@
+package de.ids_mannheim.korap.exceptions;
+
+import lombok.Getter;
+import lombok.Setter;
+
+/**
+ * @author hanl
+ * @date 11/12/2013
+ */
+@Setter
+@Getter
+public class KorAPException extends BaseException {
+
+    private String userid;
+
+    public KorAPException(Integer status) {
+        super(status);
+    }
+
+    public KorAPException(Object userid, Integer status) {
+        this(status);
+        this.userid = String.valueOf(userid);
+    }
+
+    public KorAPException(Object userid, Integer status, String message,
+            String entity) {
+        super(status, message, entity);
+        this.userid = String.valueOf(userid);
+    }
+
+    public KorAPException(Integer status, String message, String entity) {
+        super(status, message, entity);
+    }
+
+    public KorAPException(Throwable cause, Integer status) {
+        super(cause, status);
+    }
+
+    public KorAPException(String message, Throwable cause, Integer status) {
+        super(message, cause, status);
+    }
+
+    @Override
+    public String toString() {
+        return "Excpt{" +
+                "status=" + getStatusCode() +
+                ", message=" + getMessage() +
+                ", args=" + getEntity() +
+                ", userid=" + userid +
+                '}';
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/exceptions/Message.java b/src/main/java/de/ids_mannheim/korap/exceptions/Message.java
new file mode 100644
index 0000000..f9021b4
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/exceptions/Message.java
@@ -0,0 +1,109 @@
+package de.ids_mannheim.korap.exceptions;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * @author hanl
+ * @date 03/12/2014
+ */
+public class Message implements Cloneable {
+
+    ObjectMapper mapper = new ObjectMapper();
+    private String msg;
+    private int code = 0;
+    private LinkedList<String> parameters;
+
+    public Message(int code, String msg) {
+        this.code = code;
+        this.msg = msg;
+    }
+
+    public Message() {
+    }
+
+    @JsonIgnore
+    public Message setMessage(String msg) {
+        this.msg = msg;
+        return this;
+    }
+
+    @JsonIgnore
+    public String getMessage() {
+        return this.msg;
+    }
+
+    @JsonIgnore
+    public Message setCode(int code) {
+        this.code = code;
+        return this;
+    }
+
+    @JsonIgnore
+    public int getCode() {
+        return this.code;
+    }
+
+    public Message addParameter(String param) {
+        if (this.parameters == null) {
+            this.parameters = new LinkedList();
+        }
+
+        this.parameters.add(param);
+        return this;
+    }
+
+    public Object clone() throws CloneNotSupportedException {
+        Message clone = new Message();
+        if (this.msg != null) {
+            clone.msg = this.msg;
+        }
+
+        clone.code = this.code;
+        if (this.parameters != null) {
+            Iterator i$ = this.parameters.iterator();
+
+            while (i$.hasNext()) {
+                String p = (String) i$.next();
+                clone.addParameter(p);
+            }
+        }
+
+        return clone;
+    }
+
+    public JsonNode toJSONnode() {
+        ArrayNode message = this.mapper.createArrayNode();
+        if (this.code != 0) {
+            message.add(this.getCode());
+        }
+
+        message.add(this.getMessage());
+        if (this.parameters != null) {
+            Iterator i$ = this.parameters.iterator();
+
+            while (i$.hasNext()) {
+                String p = (String) i$.next();
+                message.add(p);
+            }
+        }
+
+        return message;
+    }
+
+    public String toJSON() {
+        String msg = "";
+
+        try {
+            return this.mapper.writeValueAsString(this.toJSONnode());
+        } catch (Exception var3) {
+            msg = ", \"" + var3.getLocalizedMessage() + "\"";
+            return "[620, \"Unable to generate JSON\"" + msg + "]";
+        }
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/exceptions/Messages.java b/src/main/java/de/ids_mannheim/korap/exceptions/Messages.java
new file mode 100644
index 0000000..0729448
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/exceptions/Messages.java
@@ -0,0 +1,167 @@
+package de.ids_mannheim.korap.exceptions;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * @author hanl
+ * @date 03/12/2014
+ */
+public class Messages implements Cloneable, Iterable<Message> {
+
+    private ArrayList<Message> messages = new ArrayList(3);
+
+    public Messages() {
+    }
+
+    public Iterator<Message> iterator() {
+        return new Messages.MessageIterator();
+    }
+
+    public Message add(int code, String message, String... terms) {
+        Message newMsg = new Message(code, message);
+        this.messages.add(newMsg);
+        if (terms != null) {
+            String[] arr$ = terms;
+            int len$ = terms.length;
+
+            for (int i$ = 0; i$ < len$; ++i$) {
+                String t = arr$[i$];
+                newMsg.addParameter(t);
+            }
+        }
+
+        return newMsg;
+    }
+
+    public Message add(Message msg) {
+        try {
+            Message e = (Message) msg.clone();
+            this.messages.add(e);
+            return e;
+        } catch (CloneNotSupportedException var3) {
+            return (Message) null;
+        }
+    }
+
+    public Message add(JsonNode msg) throws KorAPException {
+        if (msg.isArray() && msg.has(0)) {
+            Message newMsg = new Message();
+            short i = 1;
+            if (msg.get(0).isNumber()) {
+                newMsg.setCode(msg.get(0).asInt());
+                if (!msg.has(1)) {
+                    throw new KorAPException(750, "Passed notifications are not well formed", null);
+                }
+
+                newMsg.setMessage(msg.get(1).asText());
+                ++i;
+            } else {
+                newMsg.setMessage(msg.get(0).asText());
+            }
+
+            while (msg.has(i)) {
+                newMsg.addParameter(msg.get(i++).asText());
+            }
+
+            this.add((Message) newMsg);
+            return newMsg;
+        } else {
+            throw new KorAPException(750, "Passed notifications are not well formed", null);
+        }
+    }
+
+    public Messages add(Messages msgs) {
+        try {
+            Iterator e = msgs.getMessages().iterator();
+
+            while (e.hasNext()) {
+                Message msg = (Message) e.next();
+                this.add((Message) ((Message) msg.clone()));
+            }
+        } catch (CloneNotSupportedException var4) {
+            ;
+        }
+
+        return this;
+    }
+
+    public Messages clear() {
+        this.messages.clear();
+        return this;
+    }
+
+    public int size() {
+        return this.messages.size();
+    }
+
+    @JsonIgnore
+    public Message get(int index) {
+        return index >= this.size() ? (Message) null : (Message) this.messages.get(index);
+    }
+
+    @JsonIgnore
+    public List<Message> getMessages() {
+        return this.messages;
+    }
+
+    public Object clone() throws CloneNotSupportedException {
+        Messages clone = new Messages();
+        Iterator i$ = this.messages.iterator();
+
+        while (i$.hasNext()) {
+            Message m = (Message) i$.next();
+            clone.add((Message) ((Message) m.clone()));
+        }
+
+        return clone;
+    }
+
+    public JsonNode toJSONnode() {
+        ArrayNode messageArray = JsonUtils.createArrayNode();
+        Iterator i$ = this.messages.iterator();
+
+        while (i$.hasNext()) {
+            Message msg = (Message) i$.next();
+            messageArray.add(msg.toJSONnode());
+        }
+
+        return messageArray;
+    }
+
+    public String toJSON() {
+        String msg = "";
+
+        try {
+            return JsonUtils.toJSON(this.toJSONnode());
+        } catch (Exception var3) {
+            msg = ", \"" + var3.getLocalizedMessage() + "\"";
+            return "[620, \"Unable to generate JSON\"" + msg + "]";
+        }
+    }
+
+    private class MessageIterator implements Iterator<Message> {
+        int index = 0;
+
+        public MessageIterator() {
+        }
+
+        public boolean hasNext() {
+            return this.index < Messages.this.messages.size();
+        }
+
+        public Message next() {
+            return (Message) Messages.this.messages.get(this.index++);
+        }
+
+        public void remove() {
+            Messages.this.messages.remove(this.index);
+        }
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/exceptions/NotAuthorizedException.java b/src/main/java/de/ids_mannheim/korap/exceptions/NotAuthorizedException.java
new file mode 100644
index 0000000..2b499c7
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/exceptions/NotAuthorizedException.java
@@ -0,0 +1,33 @@
+package de.ids_mannheim.korap.exceptions;
+
+import lombok.Data;
+
+/**
+ * @author hanl
+ * @date 11/12/2013
+ */
+// a security table registers all these exceptions (failed authentication, failed access to a resource, etc.)
+@Data
+@Deprecated
+public class NotAuthorizedException extends BaseException {
+
+    public NotAuthorizedException(int status) {
+        super(status);
+    }
+
+    public NotAuthorizedException(int status, String entity) {
+        super(status, "", entity);
+    }
+
+    public NotAuthorizedException(int status, String message, String entity) {
+        super(status, message, entity);
+    }
+
+    public NotAuthorizedException(Throwable cause, int status) {
+        super(cause, status);
+    }
+
+    public NotAuthorizedException(String message, Throwable cause, int status) {
+        super(message, cause, status);
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java b/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
new file mode 100644
index 0000000..6db7b0e
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
@@ -0,0 +1,103 @@
+package de.ids_mannheim.korap.exceptions;
+
+/**
+ * @author hanl
+ * @date 07/09/2014
+ */
+public class StatusCodes {
+
+    /**
+     * 100 status codes for standard system errors
+     */
+    public static final Integer EMPTY_RESULTS = 100;
+    public static final Integer REQUEST_INVALID = 101;
+    //fixme: redundancy?!
+    public static final Integer ENTRY_EXISTS = 102;
+    public static final Integer STATUS_OK = 103;
+    public static final Integer UNSUPPORTED_OPERATION = 104;
+    public static final Integer ILLEGAL_ARGUMENT = 105;
+    public static final Integer CONNECTION_ERROR = 106;
+    public static final Integer NOTHING_CHANGED = 107;
+    public static final Integer PARAMETER_VALIDATION_ERROR = 108;
+    public static final Integer DEFAULT_ERROR = 109;
+    public static final Integer NOT_SUPPORTED = 110;
+
+    /**
+     * 400 status codes for account/authentication relevant components
+     */
+
+    public static final Integer ACCOUNT_DEACTIVATED = 200;
+    public static final Integer ACCOUNT_CONFIRMATION_FAILED = 201;
+    public static final Integer ALREADY_LOGGED_IN = 202;
+    public static final Integer EXPIRED = 204;
+    public static final Integer BAD_CREDENTIALS = 205;
+    public static final Integer UNCONFIRMED_ACCOUNT = 206;
+    public static final Integer NAME_EXISTS = 207;
+    public static final Integer PASSWORD_RESET_FAILED = 208;
+    // fixme: ?!
+    public static final Integer AUTHENTICATION_DENIED = 209;
+
+    public static final Integer LOGIN_SUCCESSFUL = 210;
+    public static final Integer LOGIN_FAILED = 211;
+    public static final Integer LOGOUT_SUCCESSFUL = 212;
+    public static final Integer LOGOUT_FAILED = 213;
+
+    public static final Integer CLIENT_REGISTRATION_FAILURE = 214;
+    public static final Integer CLIENT_REMOVAL_FAILURE = 215;
+    public static final Integer CLIENT_AUTHORIZATION_FAILURE = 216;
+
+
+    /**
+     * 500 status codes for access control related components (also policy rewrite)
+     */
+    public static final Integer PERMISSION_DENIED = 401;
+    public static final Integer UNSUPPORTED_RESOURCE = 402;
+    public static final Integer UNSUPPORTED_FOUNDRY = 403;
+    public static final Integer UNSUPPORTED_CORPUS = 404;
+    public static final Integer UNSUPPORTED_LAYER = 405;
+    // make a distinction between no and invalid vc?
+    public static final Integer UNSUPPORTED_VIRTUALCOLLECTION = 406;
+    public static final Integer CORPUS_REWRITE = 407;
+    public static final Integer FOUNDRY_REWRITE = 408;
+    public static final Integer FOUNDRY_INJECTION = 409;
+    public static final Integer MISSING_ARGUMENTS = 410;
+    public static final Integer MISSING_VIRTUALCOLLECTION = 411;
+    public static final Integer MISSING_POLICY_TARGET = 412;
+    public static final Integer MISSING_POLICY_CONDITIONS = 413;
+    public static final Integer MISSING_POLICY_PERMISSION = 414;
+
+    // todo: extend according to policy rewrite possible!
+    // policy errors
+    public static final Integer POLICY_ERROR_DEFAULT = 500;
+    public static final Integer POLICY_CREATE_ERROR = 501;
+    public static final Integer NO_POLICIES = 502;
+
+    // database codes
+    public static final Integer DB_GET_FAILED = 601;
+    public static final Integer DB_INSERT_FAILED = 602;
+    public static final Integer DB_DELETE_FAILED = 603;
+    public static final Integer DB_UPDATE_FAILED = 604;
+
+    public static final Integer DB_GET_SUCCESSFUL = 605;
+    public static final Integer DB_INSERT_SUCCESSFUL = 606;
+    public static final Integer DB_DELETE_SUCCESSFUL = 607;
+    public static final Integer DB_UPDATE_SUCCESSFUL = 608;
+
+    // service status codes
+    public static final Integer CREATE_ACCOUNT_SUCCESSFUL = 700;
+    public static final Integer CREATE_ACCOUNT_FAILED = 701;
+    public static final Integer DELETE_ACCOUNT_SUCCESSFUL = 702;
+    public static final Integer DELETE_ACCOUNT_FAILED = 703;
+    public static final Integer UPDATE_ACCOUNT_SUCCESSFUL = 704;
+    public static final Integer UPDATE_ACCOUNT_FAILED = 705;
+
+    public static final Integer GET_ACCOUNT_SUCCESSFUL = 706;
+    public static final Integer GET_ACCOUNT_FAILED = 707;
+
+    /**
+     * 300 status codes for query language and serialization
+     */
+
+    public static final Integer NO_QUERY = 301;
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/exceptions/WrappedException.java b/src/main/java/de/ids_mannheim/korap/exceptions/WrappedException.java
new file mode 100644
index 0000000..c180f1b
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/exceptions/WrappedException.java
@@ -0,0 +1,35 @@
+package de.ids_mannheim.korap.exceptions;
+
+import de.ids_mannheim.korap.auditing.AuditRecord;
+
+import java.util.Arrays;
+
+/**
+ * @author hanl
+ * @date 08/04/2015
+ */
+// should be a http exception that responds to a service point
+// is the extension of the notauthorized exception!
+public class WrappedException extends KorAPException {
+
+    private WrappedException(Object userid, Integer status, String message,
+            String args) {
+        super(String.valueOf(userid), status, message, args);
+    }
+
+    public WrappedException(Object userid, Integer status, String... args) {
+        this(userid, status, "", Arrays.asList(args).toString());
+        AuditRecord record = AuditRecord.serviceRecord(userid, status, args);
+        this.records.add(record);
+    }
+
+    public WrappedException(KorAPException e, Integer status, String... args) {
+        this(e.getUserid(), e.getStatusCode(), e.getMessage(), e.getEntity());
+        AuditRecord record = AuditRecord
+                .serviceRecord(e.getUserid(), status, args);
+        record.setField_1(e.toString());
+        this.records.addAll(e.getRecords());
+        this.records.add(record);
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/exceptions/dbException.java b/src/main/java/de/ids_mannheim/korap/exceptions/dbException.java
new file mode 100644
index 0000000..d505e13
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/exceptions/dbException.java
@@ -0,0 +1,48 @@
+package de.ids_mannheim.korap.exceptions;
+
+import de.ids_mannheim.korap.auditing.AuditRecord;
+
+import java.util.Arrays;
+
+/**
+ * @author hanl
+ * @date 08/04/2015
+ */
+public class dbException extends KorAPException {
+
+    private dbException(Object userid, Integer status, String message,
+            String args) {
+        super(String.valueOf(userid), status, message, args);
+    }
+
+    public dbException(Object userid, String target, Integer status,
+            String... args) {
+        this(userid, status, "",
+                Arrays.asList(args).toString());
+        AuditRecord record = new AuditRecord(AuditRecord.CATEGORY.DATABASE);
+        record.setUserid(String.valueOf(userid));
+        record.setStatus(status);
+        record.setTarget(target);
+        record.setArgs(this.getEntity());
+        this.records.add(record);
+    }
+
+    public dbException(KorAPException e, Integer status, String... args) {
+        this(e.getUserid(), e.getStatusCode(), e.getMessage(), e.getEntity());
+        AuditRecord record = AuditRecord
+                .dbRecord(e.getUserid(), status, args);
+        record.setField_1(e.toString());
+        this.records.addAll(e.getRecords());
+        this.records.add(record);
+    }
+
+    @Override
+    public String toString() {
+        return "DBExcpt{" +
+                "status=" + getStatusCode() +
+                ", message=" + getMessage() +
+                ", args=" + getEntity() +
+                ", userid=" + this.getUserid() +
+                '}';
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/AuditingIface.java b/src/main/java/de/ids_mannheim/korap/interfaces/AuditingIface.java
new file mode 100644
index 0000000..82bdd55
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/AuditingIface.java
@@ -0,0 +1,56 @@
+package de.ids_mannheim.korap.interfaces;
+
+import de.ids_mannheim.korap.auditing.AuditRecord;
+import de.ids_mannheim.korap.user.User;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * User: hanl
+ * Date: 8/20/13
+ * Time: 10:45 AM
+ */
+//fixme: move table to different database!
+public abstract class AuditingIface implements Runnable {
+
+    protected static int BATCH_SIZE = 15;
+    protected final List<AuditRecord> records = new ArrayList<>(BATCH_SIZE + 5);
+    private final List<AuditRecord> buffer = new ArrayList<>(BATCH_SIZE + 5);
+
+    public abstract <T extends AuditRecord> List<T> retrieveRecords(
+            AuditRecord.CATEGORY category, DateTime day, DateTime until,
+            boolean exact, int limit);
+
+    public abstract <T extends AuditRecord> List<T> retrieveRecords(
+            AuditRecord.CATEGORY category, User user, int limit);
+
+    public abstract <T extends AuditRecord> List<T> retrieveRecords(
+            LocalDate day, int hitMax);
+
+    public abstract <T extends AuditRecord> List<T> retrieveRecords(
+            String userID, LocalDate start, LocalDate end, int hitMax);
+
+    private void addAndRun(AuditRecord record) {
+        if (buffer.size() > BATCH_SIZE) {
+            records.clear();
+            records.addAll(buffer);
+            run();
+            buffer.clear();
+        }
+        if (buffer.size() <= BATCH_SIZE)
+            buffer.add(record);
+    }
+
+    public <T extends AuditRecord> void audit(T request) {
+        addAndRun(request);
+    }
+
+    public <T extends AuditRecord> void audit(List<T> requests) {
+        for (T rec : requests)
+            addAndRun(rec);
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/AuthenticationIface.java b/src/main/java/de/ids_mannheim/korap/interfaces/AuthenticationIface.java
new file mode 100644
index 0000000..e02933e
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/AuthenticationIface.java
@@ -0,0 +1,25 @@
+package de.ids_mannheim.korap.interfaces;
+
+import de.ids_mannheim.korap.exceptions.KorAPException;
+import de.ids_mannheim.korap.ext.security.AccessLevel;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.user.User;
+
+import java.util.Map;
+
+public abstract class AuthenticationIface {
+
+    public abstract TokenContext getUserStatus(String authToken)
+            throws KorAPException;
+
+    public abstract TokenContext createUserSession(User user,
+            Map<String, Object> attr) throws KorAPException;
+
+    public abstract void removeUserSession(String token) throws KorAPException;
+
+    public abstract AccessLevel[] retrieveLevelAccess(String authToken)
+            throws KorAPException;
+
+    public abstract String getIdentifier();
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/EncryptionIface.java b/src/main/java/de/ids_mannheim/korap/interfaces/EncryptionIface.java
new file mode 100644
index 0000000..0b00b31
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/EncryptionIface.java
@@ -0,0 +1,78 @@
+package de.ids_mannheim.korap.interfaces;
+
+import de.ids_mannheim.korap.exceptions.KorAPException;
+import de.ids_mannheim.korap.user.User;
+
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Map;
+
+public interface EncryptionIface {
+
+    public enum Encryption {
+        SIMPLE, ESAPICYPHER, BCRYPT
+    }
+
+    /**
+     * One-way hashing of String input. Used to canonicalize
+     *
+     * @param input
+     * @param salt
+     * @return
+     * @throws java.security.NoSuchAlgorithmException
+     * @throws java.io.UnsupportedEncodingException
+     */
+    public String produceSecureHash(String input, String salt)
+            throws NoSuchAlgorithmException, UnsupportedEncodingException,
+            KorAPException;
+
+    public String produceSecureHash(String input)
+            throws NoSuchAlgorithmException, UnsupportedEncodingException,
+            KorAPException;
+
+    public String hash(String value);
+
+    /**
+     * @param plain
+     * @param hash
+     * @param salt
+     * @return
+     */
+    public boolean checkHash(String plain, String hash, String salt);
+
+    public boolean checkHash(String plain, String hash);
+
+    public String getSalt(User user);
+
+    /**
+     * create random String to be used as authentication token
+     *
+     * @return
+     */
+    public String createToken(boolean hash, Object... obj);
+
+    public String createToken();
+
+    /**
+     * create a random Integer to be used as ID for databases
+     *
+     * @return
+     */
+    public String createID(Object... obj);
+
+    public String encodeBase();
+
+    public String validateIPAddress(String ipaddress) throws KorAPException;
+
+    public String validateEmail(String email) throws KorAPException;
+
+    public Map<String, Object> validateMap(Map<String, Object> map)
+            throws KorAPException;
+
+    public String validateString(String input) throws KorAPException;
+
+    public void validate(Object instance) throws KorAPException;
+
+    public String validatePassphrase(String pw) throws KorAPException;
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/EntityHandlerIface.java b/src/main/java/de/ids_mannheim/korap/interfaces/EntityHandlerIface.java
new file mode 100644
index 0000000..01cab35
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/EntityHandlerIface.java
@@ -0,0 +1,44 @@
+package de.ids_mannheim.korap.interfaces;
+
+import de.ids_mannheim.korap.exceptions.EmptyResultException;
+import de.ids_mannheim.korap.exceptions.KorAPException;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.user.UserDetails;
+import de.ids_mannheim.korap.user.UserSettings;
+
+/**
+ * User: hanl
+ * Date: 8/19/13
+ * Time: 11:04 AM
+ */
+public interface EntityHandlerIface {
+    public UserSettings getUserSettings(Integer userid) throws KorAPException;
+
+    public int updateSettings(UserSettings settings) throws KorAPException;
+
+    public UserDetails getUserDetails(Integer userid) throws KorAPException;
+
+    public int updateUserDetails(UserDetails details) throws KorAPException;
+
+    //    public List<UserQuery> getUserQueries(User user) throws KorAPException;
+
+    //    public UserQuery getUserQuery(String id) throws KorAPException;
+
+    //    public void updateUserQueries(User user, List<UserQuery> newOnes) throws KorAPException;
+
+    public User getAccount(String username) throws
+            EmptyResultException, KorAPException;
+
+    public int updateAccount(User user) throws KorAPException;
+
+    public int createAccount(User user) throws KorAPException;
+
+    public int deleteAccount(Integer userid) throws KorAPException;
+
+    public int resetPassphrase(String username, String uriToken,
+            String passphrase) throws KorAPException;
+
+    public int activateAccount(String username, String uriToken)
+            throws KorAPException;
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/UserControllerIface.java b/src/main/java/de/ids_mannheim/korap/interfaces/UserControllerIface.java
new file mode 100644
index 0000000..67d6545
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/UserControllerIface.java
@@ -0,0 +1,59 @@
+package de.ids_mannheim.korap.interfaces;
+
+import de.ids_mannheim.korap.exceptions.KorAPException;
+import de.ids_mannheim.korap.user.*;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author hanl
+ * @date 15/06/2015
+ */
+public abstract class UserControllerIface {
+
+    private Map<String, AuthenticationIface> providers;
+
+    //todo: test if constr actually called
+    public UserControllerIface() {
+        this.providers = new HashMap<>();
+    }
+
+    public void setProviders(Set<AuthenticationIface> providers) {
+        for (AuthenticationIface i : providers)
+            this.providers.put(i.getIdentifier(), i);
+    }
+
+    protected AuthenticationIface getProvider(String key) {
+        AuthenticationIface iface;
+        if (key == null)
+            iface = this.providers.get(Attributes.API_AUTHENTICATION);
+        else
+            iface = this.providers.get(key.toUpperCase());
+        return iface;
+    }
+
+    public abstract User authenticate(int type, String username,
+            String password, Map<String, Object> attributes)
+            throws KorAPException;
+
+    public abstract void logout(TokenContext context) throws KorAPException;
+
+    public abstract void lockAccount(User user) throws KorAPException;
+
+    public abstract boolean updateAccount(User user) throws KorAPException;
+
+    public abstract boolean deleteAccount(User user) throws KorAPException;
+
+    public abstract UserDetails getUserDetails(User user) throws KorAPException;
+
+    public abstract UserSettings getUserSettings(User user)
+            throws KorAPException;
+
+    public abstract void updateUserDetails(User user, UserDetails details)
+            throws KorAPException;
+
+    public abstract void updateUserSettings(User user, UserSettings settings)
+            throws KorAPException;
+}
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/defaults/DefaultAuditing.java b/src/main/java/de/ids_mannheim/korap/interfaces/defaults/DefaultAuditing.java
new file mode 100644
index 0000000..cb0eef7
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/defaults/DefaultAuditing.java
@@ -0,0 +1,50 @@
+package de.ids_mannheim.korap.interfaces.defaults;
+
+import de.ids_mannheim.korap.auditing.AuditRecord;
+import de.ids_mannheim.korap.interfaces.AuditingIface;
+import de.ids_mannheim.korap.user.User;
+import org.joda.time.DateTime;
+import org.joda.time.LocalDate;
+
+import java.util.List;
+
+/**
+ * @author hanl
+ * @date 05/06/2015
+ */
+public class DefaultAuditing extends AuditingIface {
+
+    public DefaultAuditing() {
+
+    }
+
+    @Override
+    public <T extends AuditRecord> List<T> retrieveRecords(
+            AuditRecord.CATEGORY category, DateTime day, DateTime until,
+            boolean exact, int limit) {
+        return null;
+    }
+
+    @Override
+    public <T extends AuditRecord> List<T> retrieveRecords(
+            AuditRecord.CATEGORY category, User user, int limit) {
+        return null;
+    }
+
+    @Override
+    public <T extends AuditRecord> List<T> retrieveRecords(LocalDate day,
+            int hitMax) {
+        return null;
+    }
+
+    @Override
+    public <T extends AuditRecord> List<T> retrieveRecords(String userID,
+            LocalDate start, LocalDate end, int hitMax) {
+        return null;
+    }
+
+    @Override
+    public void run() {
+        //todo: append to logging file or other auditing file
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/defaults/DefaultEncryption.java b/src/main/java/de/ids_mannheim/korap/interfaces/defaults/DefaultEncryption.java
new file mode 100644
index 0000000..8c787db
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/defaults/DefaultEncryption.java
@@ -0,0 +1,110 @@
+package de.ids_mannheim.korap.interfaces.defaults;
+
+import de.ids_mannheim.korap.exceptions.KorAPException;
+import de.ids_mannheim.korap.interfaces.EncryptionIface;
+import de.ids_mannheim.korap.user.User;
+
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Map;
+
+/**
+ * @author hanl
+ * @date 05/06/2015
+ */
+public class DefaultEncryption implements EncryptionIface {
+
+    private SecureRandom randomizer;
+
+    public DefaultEncryption() {
+        randomizer = new SecureRandom();
+    }
+
+    @Override
+    public String produceSecureHash(String input, String salt)
+            throws NoSuchAlgorithmException, UnsupportedEncodingException,
+            KorAPException {
+        return null;
+    }
+
+    @Override
+    public String produceSecureHash(String input)
+            throws NoSuchAlgorithmException, UnsupportedEncodingException,
+            KorAPException {
+        return null;
+    }
+
+    @Override
+    public String hash(String value) {
+        return null;
+    }
+
+    @Override
+    public boolean checkHash(String plain, String hash, String salt) {
+        return false;
+    }
+
+    @Override
+    public boolean checkHash(String plain, String hash) {
+        return false;
+    }
+
+    @Override
+    public String getSalt(User user) {
+        return null;
+    }
+
+    @Override
+    public String createToken(boolean hash, Object... obj) {
+        return createToken();
+
+    }
+
+    @Override
+    public String createToken() {
+        return new BigInteger(100, randomizer).toString(20);
+    }
+
+    @Override
+    public String createID(Object... obj) {
+        return createToken();
+    }
+
+    @Override
+    public String encodeBase() {
+        return null;
+    }
+
+    @Override
+    public String validateIPAddress(String ipaddress) throws KorAPException {
+        return null;
+    }
+
+    @Override
+    public String validateEmail(String email) throws KorAPException {
+        return null;
+    }
+
+    @Override
+    public Map<String, Object> validateMap(Map<String, Object> map)
+            throws KorAPException {
+        return null;
+    }
+
+    @Override
+    public String validateString(String input) throws KorAPException {
+        return null;
+    }
+
+    @Override
+    public void validate(Object instance) throws KorAPException {
+
+    }
+
+    @Override
+    public String validatePassphrase(String pw) throws KorAPException {
+        return null;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/plugins/PluginManager.java b/src/main/java/de/ids_mannheim/korap/plugins/PluginManager.java
new file mode 100644
index 0000000..262985f
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/plugins/PluginManager.java
@@ -0,0 +1,31 @@
+package de.ids_mannheim.korap.plugins;
+
+import de.ids_mannheim.korap.interfaces.EntityHandlerIface;
+import de.ids_mannheim.korap.interfaces.UserControllerIface;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author hanl
+ * @date 15/06/2015
+ */
+// via spring a list of implementations is inserted, for which there will be default constructors loaded
+public class PluginManager {
+
+    private Map<String, Class> plugins;
+
+    public PluginManager() {
+        plugins = new HashMap<>();
+    }
+
+    public void loadPluginInterfaces() {
+        plugins.put("userdb", EntityHandlerIface.class);
+        plugins.put("usercontroller", UserControllerIface.class);
+        plugins.put("encrytion", EntityHandlerIface.class);
+    }
+
+    public void register(String key, Class cl) {
+        plugins.put(key, cl);
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/user/Attributes.java b/src/main/java/de/ids_mannheim/korap/user/Attributes.java
new file mode 100644
index 0000000..0bba5b7
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/user/Attributes.java
@@ -0,0 +1,143 @@
+package de.ids_mannheim.korap.user;
+
+public class Attributes {
+
+    public static final String AUTHORIZATION = "Authorization";
+    public static final String SESSION_AUTHENTICATION = "session_token";
+    public static final String API_AUTHENTICATION = "api_token";
+    public static final String OAUTH2_AUTHORIZATION = "bearer";
+    public static final String OPENID_AUTHENTICATION = "id_token";
+    public static final String BASIC_AUTHENTICATION = "basic";
+
+    public static final String CLIENT_ID = "client_id";
+    public static final String CLIENT_SECRET = "client_secret";
+    public static final String SCOPES = "scopes";
+
+    public static final String SERVICE_ACCESS = "service_access";
+    public static final String USER = "KorapUser";
+    public static final String SHIBUSER = "ShibUser";
+    public static final String DEMO_DISPLAY = "Anonymous";
+    public static final String DEMOUSER_PASSWORD = "demo";
+
+    public static final String SETTINGS = "LocalSettings";
+    //    public static final String STORAGE_SETTINGS = "StorageSettings";
+
+    public static final String QUERY_ABBREVIATION = "Q";
+    public static final String LAYER = "layer";
+
+    public static final String TYPE = "type";
+
+    public static final String UID = "accountID";
+    public static final String USERNAME = "username";
+    public static final String PASSWORD = "password";
+    public static final String GENDER = "gender";
+    public static final String FIRSTNAME = "firstName";
+    public static final String LASTNAME = "lastName";
+    public static final String PHONE = "phone";
+    public static final String INSTITUTION = "institution";
+    public static final String EMAIL = "email";
+    public static final String ADDRESS = "address";
+    public static final String COUNTRY = "country";
+    public static final String ACCOUNT_CREATION = "accountCreation";
+    public static final String ACCOUNTLOCK = "accountLock";
+    public static final String ACCOUNTLINK = "accountLink";
+    public static final String URI = "uri";
+    public static final String URI_FRAGMENT = "URIFragment";
+    public static final String URI_EXPIRATION = "uriExpiration";
+    public static final String PRIVATE_USAGE = "privateUsage";
+
+    /**
+     * token context
+     */
+    public static final String TOKEN = "authToken";
+    public static final String TOKEN_EXPIRATION = "tokenExpires";
+    public static final String TOKEN_CREATION = "tokenCreated";
+    public static final String USER_AGENT = "User-Agent";
+    public static final String HOST = "userIP";
+
+    public static final String QUERY_PARAM_URI = "uri";
+    public static final String QUERY_PARAM_USER = "user";
+
+    /**
+     * shibboleth attribute names
+     */
+    public static final String EPPN = "eppn";
+    public static final String COMMON_NAME = "cn";
+    public static final String SURNAME = "sn";
+
+    public static final String EDUPERSON = "eduPersonPrincipalName";
+    public static final String CN = "cn";
+    public static final String MAIL = "mail";
+    public static final String EDU_AFFIL = "eduPersonScopedAffiliation";
+
+    /**
+     * resource mappings
+     */
+
+    public static final String RID = "id";
+    public static final String OWNER = "owner";
+    public static final String NAME = "name";
+    public static final String DESCRIPTION = "description";
+
+    public static final String REF_CORPUS = "refCorpus";
+    public static final String QUERY = "query";
+    public static final String CACHE = "cache";
+    public static final String DOCIDS = "docIDs";
+    public static final String FOUNDRIES = "foundries";
+    public static final String DEFAULT_VALUE = "defaultColl";
+
+    public static final String FILE_FORMAT_FOR_EXPORT = "fileFormatForExport";
+    public static final String FILENAME_FOR_EXPORT = "fileNameForExport";
+    @Deprecated
+    public static final String ITEM_FOR_SIMPLE_ANNOTATION = "itemForSimpleAnnotation";
+    public static final String LEFT_CONTEXT_ITEM_FOR_EXPORT = "leftContextItemForExport";
+    public static final String LEFT_CONTEXT_SIZE_FOR_EXPORT = "leftContextSizeForExport";
+    public static final String LOCALE = "locale";
+    public static final String LEFT_CONTEXT_ITEM = "leftContextItem";
+    public static final String LEFT_CONTEXT_SIZE = "leftContextSize";
+    public static final String RIGHT_CONTEXT_ITEM = "rightContextItem";
+    public static final String RIGHT_CONTEXT_ITEM_FOR_EXPORT = "rightContextItemForExport";
+    public static final String RIGHT_CONTEXT_SIZE = "rightContextSize";
+    public static final String RIGHT_CONTEXT_SIZE_FOR_EXPORT = "rightContextSizeForExport";
+    public static final String SELECTED_COLLECTION = "selectedCollection";
+    public static final String QUERY_LANGUAGE = "queryLanguage";
+    public static final String PAGE_LENGTH = "pageLength";
+    public static final String METADATA_QUERY_EXPERT_MODUS = "metadataQueryExpertModus";
+    @Deprecated
+    public static final String SEARCH_SETTINGS_TAB = "searchSettingsTab";
+    @Deprecated
+    public static final String SELECTED_BROWSER_PROPERTY = "selectedBrowserProperty";
+    @Deprecated
+    public static final String SELECTED_CONTEXT_ITEM = "selectedContextItem";
+    @Deprecated
+    public static final String SELECTED_GRAPH_TYPE = "selectedGraphType";
+    @Deprecated
+    public static final String SELECTED_SORT_TYPE = "selectedSortType";
+    @Deprecated
+    public static final String SELECTED_VIEW_FOR_SEARCH_RESULTS = "selectedViewForSearchResults";
+    public static final String COLLECT_AUDITING_DATA = "collectData";
+
+    /**
+     * default layers
+     */
+    public static final String DEFAULT_POS_FOUNDRY = "POSFoundry";
+    public static final String DEFAULT_LEMMA_FOUNDRY = "lemmaFoundry";
+    public static final String DEFAULT_CONST_FOUNDRY = "constFoundry";
+    public static final String DEFAULT_REL_FOUNDRY = "relFoundry";
+
+    /**
+     * db column keys
+     */
+
+    public static final String SELF_REF = "self";
+
+    public static final String SYM_USE = "sym_use";
+    public static final String COMMERCIAL = "commercial";
+    public static final String LICENCE = "licence";
+    public static final String QUERY_ONLY = "query_only";
+    public static final String EXPORT = "export";
+    public static final String TIME_SPANS = "spans";
+    public static final String RANGE = "range";
+
+}
+
diff --git a/src/main/java/de/ids_mannheim/korap/user/DemoUser.java b/src/main/java/de/ids_mannheim/korap/user/DemoUser.java
new file mode 100644
index 0000000..c88f5e1
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/user/DemoUser.java
@@ -0,0 +1,29 @@
+package de.ids_mannheim.korap.user;
+
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.Serializable;
+
+@Getter
+@Setter
+public class DemoUser extends User implements Serializable {
+    private static final long serialVersionUID = -5015206272520970500L;
+    public static final String DEMOUSER_NAME = "demo";
+    public static final Integer DEMOUSER_ID = 1654234534;
+    private static final long ACCOUNT_CREATED = 1377102171202L;
+    public static final String PASSPHRASE = "$2a$15$rGPvLWm5JJ1iYj0V61e5guYIGmSo.rjdBkAVIU1vWS/xdybmABxRa";
+
+    protected DemoUser() {
+        super(DEMOUSER_NAME, 2);
+        this.setDetails(new UserDetails());
+        this.setSettings(new UserSettings());
+        this.setAccountCreation(ACCOUNT_CREATED);
+        this.setQueries(UserQuery.demoUserQueries());
+    }
+
+    protected User clone() {
+        return new DemoUser();
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/user/KorAPUser.java b/src/main/java/de/ids_mannheim/korap/user/KorAPUser.java
new file mode 100644
index 0000000..5eb6932
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/user/KorAPUser.java
@@ -0,0 +1,76 @@
+package de.ids_mannheim.korap.user;
+
+import de.ids_mannheim.korap.utils.KorAPLogger;
+import lombok.Getter;
+import lombok.Setter;
+import org.slf4j.Logger;
+
+@Getter
+@Setter
+public class KorAPUser extends User {
+    private static Logger jlog = KorAPLogger.initiate(KorAPUser.class);
+    private static final long serialVersionUID = -7108308497625884584L;
+
+    //fixme: accountlink to shibboleth account
+    private String accountLink;
+
+    private String password;
+    private String URIFragment;
+    private Long URIExpiration;
+
+    protected KorAPUser(String username) {
+        super(username, 0);
+        this.URIFragment = "";
+        this.URIExpiration = 0L;
+    }
+
+    public KorAPUser(Integer id, String username) {
+        this(username);
+        this.setId(id);
+    }
+
+    public KorAPUser() {
+        super();
+    }
+
+    @Override
+    protected User clone() {
+        KorAPUser user = new KorAPUser(this.getUsername());
+        user.setUsername(this.getUsername());
+        user.setAccountCreation(this.getAccountCreation());
+        return user;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = super.hashCode();
+        result = 31 * result + (jlog != null ? jlog.hashCode() : 0);
+        result = 31 * result + (password != null ? password.hashCode() : 0);
+        result = 31 * result + (URIFragment != null ?
+                URIFragment.hashCode() :
+                0);
+        result = 31 * result + (URIExpiration != null ?
+                URIExpiration.hashCode() :
+                0);
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (!(o instanceof KorAPUser))
+            return false;
+        if (!super.equals(o))
+            return false;
+
+        KorAPUser korAPUser = (KorAPUser) o;
+        if (URIExpiration != korAPUser.URIExpiration)
+            return false;
+        if (URIFragment != null ?
+                !URIFragment.equals(korAPUser.URIFragment) :
+                korAPUser.URIFragment != null)
+            return false;
+        return true;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/user/ShibUser.java b/src/main/java/de/ids_mannheim/korap/user/ShibUser.java
new file mode 100644
index 0000000..f3f60b1
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/user/ShibUser.java
@@ -0,0 +1,48 @@
+package de.ids_mannheim.korap.user;
+
+import lombok.Data;
+
+/**
+ * User: hanl
+ * Date: 10/16/13
+ * Time: 2:02 PM
+ */
+@Data
+public class ShibUser extends User {
+
+    private String mail;
+    private String affiliation;
+    private String cn;
+
+    protected ShibUser() {
+        super(1);
+    }
+
+    private ShibUser(String eduPersonID, String mail, String cn, String affiliation) {
+        this(eduPersonID);
+        this.setUsername(eduPersonID);
+        this.mail = mail;
+        this.affiliation = affiliation;
+        this.cn = cn;
+    }
+
+    public ShibUser(String username) {
+        super(username, 1);
+
+    }
+
+    @Override
+    public String toString() {
+        final StringBuffer sb = new StringBuffer("ShibUser{");
+        sb.append(", mail='").append(mail).append('\'');
+        sb.append(", affiliation='").append(affiliation).append('\'');
+        sb.append(", cn='").append(cn).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    @Override
+    protected User clone() {
+        return new ShibUser(this.getUsername(), this.getMail(), this.getCn(), this.getAffiliation());
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/user/TokenContext.java b/src/main/java/de/ids_mannheim/korap/user/TokenContext.java
new file mode 100644
index 0000000..8f244d3
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/user/TokenContext.java
@@ -0,0 +1,116 @@
+package de.ids_mannheim.korap.user;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import lombok.Data;
+import org.joda.time.DateTime;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author hanl
+ * @date 27/01/2014
+ */
+@Data
+public class TokenContext implements java.security.Principal {
+
+    /**
+     * session relevant data. Are never persisted into a database
+     */
+    private String username;
+    private Date expirationTime;
+    // either "session_token " / "api_token
+    private String tokenType;
+    private String token;
+
+    private boolean secureRequired;
+
+    private Map<String, Object> parameters;
+    private String hostAddress;
+    private String userAgent;
+
+    public TokenContext(String username) {
+        this();
+        this.username = username;
+    }
+
+    private TokenContext() {
+        this.parameters = new HashMap<>();
+        this.setUsername("");
+        this.setToken("");
+        this.setSecureRequired(false);
+    }
+
+    private Map statusMap() {
+        Map m = new HashMap();
+        if (username != null && !username.isEmpty())
+            m.put(Attributes.USERNAME, username);
+        m.put(Attributes.TOKEN_EXPIRATION,
+                new DateTime(expirationTime).toString());
+        m.put(Attributes.TOKEN, this.token);
+        return m;
+    }
+
+    public boolean match(TokenContext other) {
+        if (other.getToken().equals(this.token))
+            if (this.getHostAddress().equals(this.hostAddress))
+                // user agent should be irrelvant -- what about os system version?
+                //                if (other.getUserAgent().equals(this.userAgent))
+                return true;
+        return false;
+    }
+
+    public void addContextParameter(String key, String value) {
+        this.parameters.put(key, value);
+    }
+
+    public void removeContextParameter(String key) {
+        this.parameters.remove(key);
+    }
+
+    public void setExpirationTime(long date) {
+        this.expirationTime = new Date(date);
+    }
+
+    public static TokenContext fromJSON(String s) {
+        JsonNode node = JsonUtils.readTree(s);
+        TokenContext c = new TokenContext(
+                node.path(Attributes.USERNAME).asText());
+        c.setToken(node.path(Attributes.TOKEN).asText());
+        return c;
+    }
+
+    public static TokenContext fromOAuth(String s) {
+        JsonNode node = JsonUtils.readTree(s);
+        TokenContext c = new TokenContext();
+        c.setToken(node.path("token").asText());
+        c.setTokenType(node.path("token_type").asText());
+        c.setExpirationTime(node.path("expires_in").asLong());
+        return c;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public String toJSON() {
+        return JsonUtils.toJSON(this.statusMap());
+    }
+
+    public String toResponse() {
+        ObjectNode node = JsonUtils.createObjectNode();
+        node.put("token", this.getToken());
+        node.put("expires", this.getExpirationTime().getTime());
+        node.put("token_type", this.getTokenType());
+        return JsonUtils.toJSON(node);
+    }
+
+    @Override
+    public String getName() {
+        return this.getUsername();
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/user/User.java b/src/main/java/de/ids_mannheim/korap/user/User.java
new file mode 100644
index 0000000..2b28edd
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/user/User.java
@@ -0,0 +1,214 @@
+package de.ids_mannheim.korap.user;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import lombok.Data;
+import org.joda.time.DateTime;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Data
+public abstract class User implements Serializable {
+
+    public static final int ADMINISTRATOR_ID = 34349733;
+    public static final String ADMINISTRATOR_NAME = "admin";
+
+    private Integer id;
+    // in local its username, in shib it's edupersonPrincipalName
+    private String username;
+    private Long accountCreation;
+    private boolean isAccountLocked;
+    private int type;
+    private Map<String, Object> fields;
+    private UserSettings settings;
+    private UserDetails details;
+    private List<UserQuery> queries;
+
+    protected User() {
+        this.accountCreation = TimeUtils.getNow().getMillis();
+        this.isAccountLocked = false;
+        this.username = "";
+        this.fields = new HashMap<>();
+        this.id = -1;
+    }
+
+    protected User(int type) {
+        this();
+        this.type = type;
+    }
+
+    protected User(String username, int type) {
+        this(type);
+        this.username = username;
+    }
+
+    public void setField(String key, Object value) {
+        this.fields.put(key, value);
+    }
+
+    public Object getField(String key) {
+        return this.fields.get(key);
+    }
+
+    //todo: repair transfer
+    public void transfer(User user) {
+        this.setSettings(user.getSettings());
+        this.setDetails(user.getDetails());
+        //        this.setQueries(user.getQueries());
+        if (this instanceof KorAPUser) {
+            this.getSettings().setUserID(this.id);
+            this.getDetails().setUserID(this.id);
+            //            for (UserQuery q : this.getQueries())
+            //                q.setOwner(this.accountID);
+        }
+    }
+
+    public void setDetails(UserDetails details) {
+        if (details != null)
+            details.setUserID(this.id);
+        this.details = details;
+    }
+
+    public void setSettings(UserSettings settings) {
+        if (settings != null)
+            settings.setUserID(this.id);
+        this.settings = settings;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+        if (this.settings != null)
+            this.settings.setUserID(this.id);
+        if (this.details != null)
+            this.details.setUserID(this.id);
+    }
+
+    public Map<String, Object> toMap() {
+        Map map = new HashMap();
+        map.put(Attributes.USERNAME, this.username);
+        //TimeUtils.format(new DateTime(this.accountCreation))
+        map.put(Attributes.ACCOUNT_CREATION, this.accountCreation);
+
+        if (this.getDetails() != null)
+            map.putAll(this.getDetails().toMap());
+        return map;
+    }
+
+    public String toJson() {
+        return JsonUtils.toJSON(this.toMap());
+    }
+
+    public Map toCache() {
+        Map map = new HashMap();
+        map.put(Attributes.UID, this.id);
+        map.put(Attributes.USERNAME, this.username);
+        map.put(Attributes.ACCOUNT_CREATION,
+                TimeUtils.format(new DateTime(this.accountCreation)));
+        map.put(Attributes.ACCOUNTLOCK, this.isAccountLocked);
+        map.put(Attributes.TYPE, this.type);
+        return map;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (!(o instanceof User))
+            return false;
+        User user = (User) o;
+        if (!username.equals(user.username))
+            return false;
+        return true;
+    }
+
+    public boolean isDemo() {
+        return this.getUsername().equalsIgnoreCase(DemoUser.DEMOUSER_NAME);
+    }
+
+    public boolean isAdmin() {
+        return this.getUsername().equals(ADMINISTRATOR_ID);
+    }
+
+    protected abstract User clone();
+
+    @Override
+    public String toString() {
+        final StringBuffer sb = new StringBuffer();
+        sb.append("id='").append(id).append('\'');
+        sb.append(", username='").append(username).append('\'');
+        return sb.toString();
+    }
+
+    public static class UserFactory {
+
+        public static KorAPUser getUser(String username) {
+            return new KorAPUser(username);
+        }
+
+        public static KorAPUser getAdmin() {
+            return new KorAPUser(ADMINISTRATOR_ID, ADMINISTRATOR_NAME);
+        }
+
+        public static DemoUser getDemoUser() {
+            return new DemoUser();
+        }
+
+        public static DemoUser getDemoUser(Integer id) {
+            DemoUser demo = new DemoUser();
+            demo.setId(id);
+            return demo;
+        }
+
+        public static ShibUser getShibInstance(String eduPersonID, String mail,
+                String cn) {
+            ShibUser u = new ShibUser(eduPersonID);
+            u.setAffiliation("");
+            u.setMail(mail);
+            u.setUsername(eduPersonID);
+            u.setCn(cn);
+            return u;
+        }
+
+        public static User toUser(Map map) {
+            int type = map.get(Attributes.TYPE) == null ?
+                    0 :
+                    (int) map.get(Attributes.TYPE);
+            User user;
+            DateTime dateTime = DateTime
+                    .parse((String) map.get(Attributes.ACCOUNT_CREATION));
+            switch (type) {
+                case 0:
+                    user = UserFactory
+                            .getUser((String) map.get(Attributes.USERNAME));
+                    user.setId((Integer) map.get(Attributes.UID));
+                    user.setAccountLocked(
+                            (Boolean) map.get(Attributes.ACCOUNTLOCK));
+                    user.setAccountCreation(dateTime.getMillis());
+                    break;
+                default:
+                    user = UserFactory
+                            .getDemoUser((Integer) map.get(Attributes.UID));
+                    user.setAccountCreation(dateTime.getMillis());
+            }
+            return user;
+        }
+
+        public static KorAPUser toUser(String value) {
+            JsonNode node = JsonUtils.readTree(value);
+            KorAPUser user = UserFactory
+                    .getUser(node.path(Attributes.USERNAME).asText());
+            user.setAccountLocked(
+                    node.path(Attributes.ACCOUNTLOCK).asBoolean());
+            user.setAccountLink(node.path(Attributes.ACCOUNTLINK).asText());
+            user.setAccountCreation(
+                    node.path(Attributes.ACCOUNT_CREATION).asLong());
+            user.setPassword(node.path(Attributes.PASSWORD).asText());
+            return user;
+        }
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/user/UserDetails.java b/src/main/java/de/ids_mannheim/korap/user/UserDetails.java
new file mode 100644
index 0000000..6b2ffdb
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/user/UserDetails.java
@@ -0,0 +1,111 @@
+package de.ids_mannheim.korap.user;
+
+import lombok.Data;
+import org.apache.commons.collections.map.CaseInsensitiveMap;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * User: hanl
+ * Date: 8/14/13
+ * Time: 10:32 AM
+ */
+
+@Data
+public class UserDetails {
+
+    private Integer Id;
+    private Integer userID;
+    private String firstName;
+    private String lastName;
+    private String gender;
+    private String phone;
+    private String institution;
+    private String email;
+    private String address;
+    private String country;
+    private boolean privateUsage;
+
+    public UserDetails() {
+        setFirstName("");
+        setLastName("");
+        setPhone("");
+        setEmail("");
+        setGender("");
+        setAddress("");
+        setCountry("");
+        setInstitution("");
+        setPrivateUsage(true);
+    }
+
+    public static UserDetails newDetailsIterator(Map<String, Object> d) {
+        UserDetails details = new UserDetails();
+        Map<String, Object> detailMap = new CaseInsensitiveMap(d);
+
+        if (!detailMap.isEmpty()) {
+            details.setFirstName((String) detailMap.get(Attributes.FIRSTNAME));
+            details.setLastName((String) detailMap.get(Attributes.LASTNAME));
+            details.setPhone((String) detailMap.get(Attributes.PHONE));
+            details.setEmail((String) detailMap.get(Attributes.EMAIL));
+            details.setGender((String) detailMap.get(Attributes.GENDER));
+            details.setAddress((String) detailMap.get(Attributes.ADDRESS));
+            details.setCountry((String) detailMap.get(Attributes.COUNTRY));
+            details.setInstitution(
+                    (String) detailMap.get(Attributes.INSTITUTION));
+            details.setPrivateUsage(
+                    detailMap.get(Attributes.PRIVATE_USAGE) == null ?
+                            true :
+                            (Boolean) detailMap.get(Attributes.PRIVATE_USAGE));
+        }
+        return details;
+    }
+
+    public void updateDetails(Map<String, String> d) {
+        Map<String, String> detailMap = new CaseInsensitiveMap(d);
+
+        if (!detailMap.isEmpty()) {
+            if (detailMap.containsKey(Attributes.FIRSTNAME))
+                this.setFirstName(detailMap.get(Attributes.FIRSTNAME));
+            if (detailMap.containsKey(Attributes.LASTNAME))
+                this.setLastName(detailMap.get(Attributes.LASTNAME));
+            if (detailMap.containsKey(Attributes.PHONE))
+                this.setPhone(detailMap.get(Attributes.PHONE));
+            if (detailMap.containsKey(Attributes.EMAIL))
+                this.setEmail(detailMap.get(Attributes.EMAIL));
+            if (detailMap.containsKey(Attributes.GENDER))
+                this.setGender(detailMap.get(Attributes.GENDER));
+            if (detailMap.containsKey(Attributes.ADDRESS))
+                this.setAddress(detailMap.get(Attributes.ADDRESS));
+            if (detailMap.containsKey(Attributes.COUNTRY))
+                this.setCountry(detailMap.get(Attributes.COUNTRY));
+            if (detailMap.containsKey(Attributes.INSTITUTION))
+                this.setInstitution(detailMap.get(Attributes.INSTITUTION));
+            this.setPrivateUsage(
+                    Boolean.valueOf(detailMap.get(Attributes.PRIVATE_USAGE)));
+        }
+    }
+
+    public Map<String, Object> toMap() {
+        Map<String, Object> details = new HashMap<>();
+        // shouldnt there be a mechanism that prevents the retrieval of all information if no scopes are given?
+        // and if so, are the access_tokens specific to the scopes then?
+        details.put(Attributes.EMAIL, this.email);
+        details.put(Attributes.FIRSTNAME, this.firstName);
+        details.put(Attributes.LASTNAME, this.lastName);
+        details.put(Attributes.GENDER, this.gender);
+        details.put(Attributes.PHONE, this.phone);
+        details.put(Attributes.INSTITUTION, this.institution);
+        details.put(Attributes.ADDRESS, this.address);
+        details.put(Attributes.COUNTRY, this.country);
+        details.put(Attributes.PRIVATE_USAGE, this.privateUsage);
+
+        for (Map.Entry<String, Object> pair : details.entrySet()) {
+            if (pair.getValue() == null || pair.getValue().equals("null"))
+                pair.setValue("");
+        }
+        return details;
+    }
+
+}
+
diff --git a/src/main/java/de/ids_mannheim/korap/user/UserQuery.java b/src/main/java/de/ids_mannheim/korap/user/UserQuery.java
new file mode 100644
index 0000000..0e53aa0
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/user/UserQuery.java
@@ -0,0 +1,136 @@
+package de.ids_mannheim.korap.user;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * User: hanl
+ * Date: 9/16/13
+ * Time: 4:38 PM
+ */
+@Data
+public class UserQuery {
+
+    private Integer id;
+    private String queryLanguage;
+    private String query;
+    private String name;
+    private String description;
+    private Integer creator;
+
+    public UserQuery(Integer id, int creator) {
+        setId(id);
+        setCreator(creator);
+        setName("");
+        setDescription("");
+        setQuery("");
+        setQueryLanguage("");
+    }
+
+    public UserQuery(String ql, String query, String description) {
+        setDescription(description);
+        setQuery(query);
+        setQueryLanguage(ql);
+    }
+
+    public UserQuery() {
+        setDescription("");
+        setQuery("");
+        setQueryLanguage("");
+        setName("");
+    }
+
+    public void setQuery(String query) {
+        this.query = query;
+        setName("Query: " + query
+                .substring(0, query.length() > 20 ? 20 : query.length()));
+    }
+
+    // todo: use example queries or store in database
+    public static List<UserQuery> demoUserQueries() {
+
+        List<UserQuery> queries = new ArrayList<>();
+        UserQuery q1 = new UserQuery();
+        q1.setQueryLanguage("COSMAS2");
+        q1.setQuery("$wegen #IN(L) <s>");
+        q1.setDescription(
+                "Findet 'wegen' an Satzanfängen. Berücksichtigt auch Groß- und Kleinschreibung");
+
+        //todo: change query
+        UserQuery q2 = new UserQuery();
+        q2.setQueryLanguage("COSMAS2");
+        q2.setQuery("base/cons:Buchstabe base/aggr:Buchstabe");
+
+        UserQuery q3 = new UserQuery();
+        q3.setQueryLanguage("COSMAS2");
+        q3.setDescription("Regular Expression Search");
+        q3.setQuery("id:/WPD_AAA.*/ AND textClass:sport");
+
+        UserQuery q4 = new UserQuery();
+        q4.setQueryLanguage("COSMAS2");
+        q4.setQuery("mpt/syntax_pos:@CC\\|und");
+
+        UserQuery q5 = new UserQuery();
+        q5.setQueryLanguage("COSMAS2");
+        q5.setQuery("VVINF\\|.*en");
+
+        queries.add(q1);
+        //        queries.add(q2);
+        //        queries.add(q3);
+        queries.add(q4);
+        queries.add(q5);
+        return queries;
+    }
+
+    //id is irrevelant, since data was coming
+    // from frontend and thus this object does not contain a id that could be compared!
+    // same with the userAccount. Not set yet!
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (!(o instanceof UserQuery))
+            return false;
+        UserQuery userQuery = (UserQuery) o;
+        if (!query.equals(userQuery.query))
+            return false;
+        if (!queryLanguage.equals(userQuery.queryLanguage))
+            return false;
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = getId() != null ? getId().hashCode() : 0;
+        result = 31 * result + (queryLanguage != null ?
+                queryLanguage.hashCode() :
+                0);
+        result = 31 * result + (query != null ? query.hashCode() : 0);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuffer sb = new StringBuffer("UserQuery{");
+        sb.append("id=").append(getId());
+        //        sb.append(", owner=").append(getOwner());
+        sb.append(", queryLanguage='").append(queryLanguage).append('\'');
+        sb.append(", query='").append(query).append('\'');
+        sb.append(", description='").append(getDescription()).append('\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    public Map toMap() {
+        Map map = new HashMap();
+        map.put("name", this.name);
+        map.put("description", this.description);
+        map.put("query", this.query);
+        map.put("queryLanguage", this.queryLanguage);
+        return map;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/user/UserSettings.java b/src/main/java/de/ids_mannheim/korap/user/UserSettings.java
new file mode 100644
index 0000000..365e69a
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/user/UserSettings.java
@@ -0,0 +1,467 @@
+package de.ids_mannheim.korap.user;
+
+import de.ids_mannheim.korap.utils.BooleanUtils;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * User: hanl
+ * Date: 8/14/13
+ * Time: 10:26 AM
+ */
+
+@Getter
+@Setter
+public class UserSettings {
+
+    private Integer id;
+    private Integer userID;
+    private String fileNameForExport;
+    //    @Deprecated
+    //    private Integer itemForSimpleAnnotation;
+    private String leftContextItemForExport;
+    private Integer leftContextSizeForExport;
+    private String locale;
+    private String leftContextItem;
+    private Integer leftContextSize;
+    private String rightContextItem;
+    private String rightContextItemForExport;
+    private Integer rightContextSize;
+    private Integer rightContextSizeForExport;
+    private String selectedCollection;
+    private String queryLanguage;
+    private Integer pageLength;
+    private boolean metadataQueryExpertModus;
+    //    @Deprecated
+    //    private Integer searchSettingsTab;
+    //    @Deprecated
+    //    private Integer selectedGraphType;
+    //    @Deprecated
+    //    private String selectedSortType;
+    //    @Deprecated
+    //    private String selectedViewForSearchResults;
+
+    /**
+     * default values for foundry specification! of structure ff/lay
+     */
+    private String defaultPOSfoundry;
+    private String defaultLemmafoundry;
+    //default foundry for constituent information (syntax trees) --> there is no actual layer for this information
+    private String defaultConstfoundry;
+    private String defaultRelfoundry;
+
+    //todo: refactor to anonymous -- since data is collected either way!
+    private boolean collectData;
+
+    /**
+     * creates an instance of this object with default values, mapped from a database/configuration file
+     */
+    public UserSettings() {
+        setupDefaultSettings();
+    }
+
+    public static UserSettings fromObjectMap(Map<String, Object> m) {
+        UserSettings s = new UserSettings();
+        s.setFileNameForExport((String) m.get(Attributes.FILENAME_FOR_EXPORT));
+        //        s.setItemForSimpleAnnotation(
+        //                (Integer) m.get(Attributes.ITEM_FOR_SIMPLE_ANNOTATION));
+        s.setLeftContextItemForExport(
+                (String) m.get(Attributes.LEFT_CONTEXT_ITEM_FOR_EXPORT));
+        s.setLeftContextSizeForExport(
+                (Integer) m.get(Attributes.LEFT_CONTEXT_SIZE_FOR_EXPORT));
+        s.setLocale((String) m.get(Attributes.LOCALE));
+        s.setLeftContextItem((String) m.get(Attributes.LEFT_CONTEXT_ITEM));
+        s.setLeftContextSize((Integer) m.get(Attributes.LEFT_CONTEXT_SIZE));
+        s.setRightContextItem((String) m.get(Attributes.RIGHT_CONTEXT_ITEM));
+        s.setRightContextItemForExport(
+                (String) m.get(Attributes.RIGHT_CONTEXT_ITEM_FOR_EXPORT));
+        s.setRightContextSize((Integer) m.get(Attributes.RIGHT_CONTEXT_SIZE));
+        s.setRightContextSizeForExport(
+                (Integer) m.get(Attributes.RIGHT_CONTEXT_SIZE_FOR_EXPORT));
+        s.setSelectedCollection((String) m.get(Attributes.SELECTED_COLLECTION));
+        s.setQueryLanguage((String) m.get(Attributes.QUERY_LANGUAGE));
+        s.setPageLength((Integer) m.get(Attributes.PAGE_LENGTH));
+        s.setMetadataQueryExpertModus((Boolean) BooleanUtils
+                .getBoolean(m.get(Attributes.METADATA_QUERY_EXPERT_MODUS)));
+        //        s.setSearchSettingsTab((Integer) m.get(Attributes.SEARCH_SETTINGS_TAB));
+        //        s.setSelectedGraphType((Integer) m.get(Attributes.SELECTED_GRAPH_TYPE));
+        //        s.setSelectedSortType((String) m.get(Attributes.SELECTED_SORT_TYPE));
+        //        s.setSelectedViewForSearchResults(
+        //                (String) m.get(Attributes.SELECTED_VIEW_FOR_SEARCH_RESULTS));
+        s.setCollectData((Boolean) BooleanUtils
+                .getBoolean(m.get(Attributes.COLLECT_AUDITING_DATA)));
+
+        s.setDefaultConstfoundry(
+                (String) m.get(Attributes.DEFAULT_CONST_FOUNDRY));
+        s.setDefaultRelfoundry((String) m.get(Attributes.DEFAULT_REL_FOUNDRY));
+        s.setDefaultPOSfoundry((String) m.get(Attributes.DEFAULT_POS_FOUNDRY));
+        s.setDefaultLemmafoundry(
+                (String) m.get(Attributes.DEFAULT_LEMMA_FOUNDRY));
+
+        s.setId((Integer) m.get("Id"));
+        s.setUserID((Integer) m.get("userID"));
+        return s;
+    }
+
+    public static UserSettings fromMap(Map<String, String> m) {
+        UserSettings s = new UserSettings();
+        s.setFileNameForExport(m.get(Attributes.FILENAME_FOR_EXPORT));
+        //        s.setItemForSimpleAnnotation(
+        //                Integer.valueOf(m.get(Attributes.ITEM_FOR_SIMPLE_ANNOTATION)));
+        s.setLeftContextItemForExport(
+                m.get(Attributes.LEFT_CONTEXT_ITEM_FOR_EXPORT));
+        s.setLeftContextSizeForExport(Integer.valueOf(
+                m.get(Attributes.LEFT_CONTEXT_SIZE_FOR_EXPORT)));
+        s.setLocale(m.get(Attributes.LOCALE));
+        s.setLeftContextItem(m.get(Attributes.LEFT_CONTEXT_ITEM));
+        s.setLeftContextSize(
+                Integer.valueOf(m.get(Attributes.LEFT_CONTEXT_SIZE)));
+        s.setRightContextItem(m.get(Attributes.RIGHT_CONTEXT_ITEM));
+        s.setRightContextItemForExport(
+                m.get(Attributes.RIGHT_CONTEXT_ITEM_FOR_EXPORT));
+        s.setRightContextSize(
+                Integer.valueOf(m.get(Attributes.RIGHT_CONTEXT_SIZE)));
+        s.setRightContextSizeForExport(Integer.valueOf(
+                m.get(Attributes.RIGHT_CONTEXT_SIZE_FOR_EXPORT)));
+        s.setSelectedCollection(m.get(Attributes.SELECTED_COLLECTION));
+        s.setQueryLanguage(m.get(Attributes.QUERY_LANGUAGE));
+        s.setPageLength(Integer.valueOf(m.get(Attributes.PAGE_LENGTH)));
+        s.setMetadataQueryExpertModus(
+                Boolean.valueOf(m.get(Attributes.METADATA_QUERY_EXPERT_MODUS)));
+        //        s.setSearchSettingsTab(
+        //                Integer.valueOf(m.get(Attributes.SEARCH_SETTINGS_TAB)));
+        //        s.setSelectedGraphType(
+        //                Integer.valueOf(m.get(Attributes.SELECTED_GRAPH_TYPE)));
+        //        s.setSelectedSortType(m.get(Attributes.SELECTED_SORT_TYPE));
+        //        s.setSelectedViewForSearchResults(
+        //                m.get(Attributes.SELECTED_VIEW_FOR_SEARCH_RESULTS));
+
+        s.setCollectData(
+                Boolean.valueOf(m.get(Attributes.COLLECT_AUDITING_DATA)));
+        s.setDefaultConstfoundry(m.get(Attributes.DEFAULT_CONST_FOUNDRY));
+        s.setDefaultRelfoundry(m.get(Attributes.DEFAULT_REL_FOUNDRY));
+        s.setDefaultPOSfoundry(m.get(Attributes.DEFAULT_POS_FOUNDRY));
+        s.setDefaultLemmafoundry(m.get(Attributes.DEFAULT_LEMMA_FOUNDRY));
+        return s;
+    }
+
+    public static UserSettings newSettingsIterator(Map<String, String> m) {
+        UserSettings s = new UserSettings();
+        if (m.isEmpty())
+            return s;
+
+        s.setFileNameForExport(m.get(Attributes.FILENAME_FOR_EXPORT));
+        //        s.setItemForSimpleAnnotation(
+        //                Integer.valueOf(m.get(Attributes.ITEM_FOR_SIMPLE_ANNOTATION)));
+        s.setLeftContextItemForExport(
+                m.get(Attributes.LEFT_CONTEXT_ITEM_FOR_EXPORT));
+        s.setLeftContextSizeForExport(Integer.valueOf(
+                m.get(Attributes.LEFT_CONTEXT_SIZE_FOR_EXPORT)));
+        s.setLocale(m.get(Attributes.LOCALE));
+        s.setLeftContextItem(m.get(Attributes.LEFT_CONTEXT_ITEM));
+        s.setLeftContextSize(
+                Integer.valueOf(m.get(Attributes.LEFT_CONTEXT_SIZE)));
+        s.setRightContextItem(m.get(Attributes.RIGHT_CONTEXT_ITEM));
+        s.setRightContextItemForExport(
+                m.get(Attributes.RIGHT_CONTEXT_ITEM_FOR_EXPORT));
+        s.setRightContextSize(
+                Integer.valueOf(m.get(Attributes.RIGHT_CONTEXT_SIZE)));
+        s.setRightContextSizeForExport(Integer.valueOf(
+                m.get(Attributes.RIGHT_CONTEXT_SIZE_FOR_EXPORT)));
+        s.setSelectedCollection(m.get(Attributes.SELECTED_COLLECTION));
+        s.setQueryLanguage(m.get(Attributes.QUERY_LANGUAGE));
+        s.setPageLength(Integer.valueOf(m.get(Attributes.PAGE_LENGTH)));
+        s.setMetadataQueryExpertModus(
+                Boolean.valueOf(m.get(Attributes.METADATA_QUERY_EXPERT_MODUS)));
+        //        s.setSearchSettingsTab(
+        //                Integer.valueOf(m.get(Attributes.SEARCH_SETTINGS_TAB)));
+        //        s.setSelectedGraphType(
+        //                Integer.valueOf(m.get(Attributes.SELECTED_GRAPH_TYPE)));
+        //        s.setSelectedSortType(m.get(Attributes.SELECTED_SORT_TYPE));
+        //        s.setSelectedViewForSearchResults(
+        //                m.get(Attributes.SELECTED_VIEW_FOR_SEARCH_RESULTS));
+
+        s.setCollectData(
+                Boolean.valueOf(m.get(Attributes.COLLECT_AUDITING_DATA)));
+        s.setDefaultConstfoundry(m.get(Attributes.DEFAULT_CONST_FOUNDRY));
+        s.setDefaultRelfoundry(m.get(Attributes.DEFAULT_REL_FOUNDRY));
+        s.setDefaultPOSfoundry(m.get(Attributes.DEFAULT_POS_FOUNDRY));
+        s.setDefaultLemmafoundry(m.get(Attributes.DEFAULT_LEMMA_FOUNDRY));
+        return s;
+    }
+
+    public void updateStringSettings(Map<String, String> m) {
+        this.setFileNameForExport(m.get(Attributes.FILENAME_FOR_EXPORT));
+        //        this.setItemForSimpleAnnotation(
+        //                Integer.valueOf(m.get(Attributes.ITEM_FOR_SIMPLE_ANNOTATION)));
+        this.setLeftContextItemForExport(
+                m.get(Attributes.LEFT_CONTEXT_ITEM_FOR_EXPORT));
+        this.setLeftContextSizeForExport(Integer.valueOf(
+                m.get(Attributes.LEFT_CONTEXT_SIZE_FOR_EXPORT)));
+        this.setLocale(m.get(Attributes.LOCALE));
+        this.setLeftContextItem(m.get(Attributes.LEFT_CONTEXT_ITEM));
+        this.setLeftContextSize(
+                Integer.valueOf(m.get(Attributes.LEFT_CONTEXT_SIZE)));
+        this.setRightContextItem(m.get(Attributes.RIGHT_CONTEXT_ITEM));
+        this.setRightContextItemForExport(
+                m.get(Attributes.RIGHT_CONTEXT_ITEM_FOR_EXPORT));
+        this.setRightContextSize(
+                Integer.valueOf(m.get(Attributes.RIGHT_CONTEXT_SIZE)));
+        this.setRightContextSizeForExport(Integer.valueOf(
+                m.get(Attributes.RIGHT_CONTEXT_SIZE_FOR_EXPORT)));
+        this.setSelectedCollection(m.get(Attributes.SELECTED_COLLECTION));
+        this.setQueryLanguage(m.get(Attributes.QUERY_LANGUAGE));
+        this.setPageLength(Integer.valueOf(m.get(Attributes.PAGE_LENGTH)));
+        this.setMetadataQueryExpertModus(
+                Boolean.valueOf(m.get(Attributes.METADATA_QUERY_EXPERT_MODUS)));
+        //        this.setSearchSettingsTab(
+        //                Integer.valueOf(m.get(Attributes.SEARCH_SETTINGS_TAB)));
+        //        this.setSelectedGraphType(
+        //                Integer.valueOf(m.get(Attributes.SELECTED_GRAPH_TYPE)));
+        //        this.setSelectedSortType(m.get(Attributes.SELECTED_SORT_TYPE));
+        //        this.setSelectedViewForSearchResults(
+        //                m.get(Attributes.SELECTED_VIEW_FOR_SEARCH_RESULTS));
+
+        this.setCollectData(
+                Boolean.valueOf(m.get(Attributes.COLLECT_AUDITING_DATA)));
+        this.setDefaultPOSfoundry(m.get(Attributes.DEFAULT_POS_FOUNDRY));
+        this.setDefaultLemmafoundry(m.get(Attributes.DEFAULT_LEMMA_FOUNDRY));
+        this.setDefaultConstfoundry(m.get(Attributes.DEFAULT_CONST_FOUNDRY));
+        this.setDefaultRelfoundry(m.get(Attributes.DEFAULT_REL_FOUNDRY));
+    }
+
+    public void updateObjectSettings(Map<String, Object> m) {
+        this.setFileNameForExport(
+                (String) m.get(Attributes.FILENAME_FOR_EXPORT));
+        //        this.setItemForSimpleAnnotation(
+        //                (Integer) m.get(Attributes.ITEM_FOR_SIMPLE_ANNOTATION));
+        this.setLeftContextItemForExport(
+                (String) m.get(Attributes.LEFT_CONTEXT_ITEM_FOR_EXPORT));
+        this.setLeftContextSizeForExport(
+                (Integer) m.get(Attributes.LEFT_CONTEXT_SIZE_FOR_EXPORT));
+        this.setLocale((String) m.get(Attributes.LOCALE));
+        this.setLeftContextItem((String) m.get(Attributes.LEFT_CONTEXT_ITEM));
+        this.setLeftContextSize((Integer) m.get(Attributes.LEFT_CONTEXT_SIZE));
+        this.setRightContextItem((String) m.get(Attributes.RIGHT_CONTEXT_ITEM));
+        this.setRightContextItemForExport(
+                (String) m.get(Attributes.RIGHT_CONTEXT_ITEM_FOR_EXPORT));
+        this.setRightContextSize(
+                (Integer) m.get(Attributes.RIGHT_CONTEXT_SIZE));
+        this.setRightContextSizeForExport(
+                (Integer) m.get(Attributes.RIGHT_CONTEXT_SIZE_FOR_EXPORT));
+        this.setSelectedCollection(
+                (String) m.get(Attributes.SELECTED_COLLECTION));
+        this.setQueryLanguage((String) m.get(Attributes.QUERY_LANGUAGE));
+        this.setPageLength((Integer) m.get(Attributes.PAGE_LENGTH));
+        this.setMetadataQueryExpertModus(
+                (Boolean) m.get(Attributes.METADATA_QUERY_EXPERT_MODUS));
+        //        this.setSearchSettingsTab(
+        //                (Integer) m.get(Attributes.SEARCH_SETTINGS_TAB));
+        //        this.setSelectedGraphType(
+        //                (Integer) m.get(Attributes.SELECTED_GRAPH_TYPE));
+        //        this.setSelectedSortType((String) m.get(Attributes.SELECTED_SORT_TYPE));
+        //        this.setSelectedViewForSearchResults(
+        //                (String) m.get(Attributes.SELECTED_VIEW_FOR_SEARCH_RESULTS));
+
+        this.setCollectData((Boolean) m.get(Attributes.COLLECT_AUDITING_DATA));
+        this.setDefaultPOSfoundry(
+                (String) m.get(Attributes.DEFAULT_POS_FOUNDRY));
+        this.setDefaultLemmafoundry(
+                (String) m.get(Attributes.DEFAULT_LEMMA_FOUNDRY));
+        this.setDefaultConstfoundry(
+                (String) m.get(Attributes.DEFAULT_CONST_FOUNDRY));
+        this.setDefaultRelfoundry(
+                (String) m.get(Attributes.DEFAULT_REL_FOUNDRY));
+    }
+
+    //load from configuration?
+    private void setupDefaultSettings() {
+        this.setFileNameForExport("export");
+        //        this.setItemForSimpleAnnotation(0);
+        this.setLocale("de");
+        this.setLeftContextItemForExport("char");
+        this.setLeftContextSizeForExport(100);
+        this.setLeftContextItem("char");
+        this.setLeftContextSize(200);
+        this.setRightContextItem("char");
+        this.setRightContextItemForExport("char");
+        this.setRightContextSize(200);
+        this.setRightContextSizeForExport(100);
+        // persistent id for wikipedia!
+        this.setSelectedCollection(
+                "ZGU0ZTllNTFkYzc3M2VhZmViYzdkYWE2ODI5NDc3NTk4NGQ1YThhOTMwOTNhOWYxNWMwN2M3Y2YyZmE3N2RlNQ==");
+        this.setQueryLanguage("COSMAS2");
+        this.setPageLength(25);
+        this.setMetadataQueryExpertModus(true);
+        //        this.setSearchSettingsTab(0);
+        //        this.setSelectedGraphType(1);
+        //        this.setSelectedSortType("FIFO");
+        //        this.setSelectedViewForSearchResults("KWIC");
+
+        this.setCollectData(true);
+        this.setDefaultConstfoundry("mate");
+        this.setDefaultRelfoundry("mate");
+        this.setDefaultPOSfoundry("tt");
+        this.setDefaultLemmafoundry("tt");
+    }
+
+    public Map toStringMap() {
+        Map<String, String> m = new HashMap<>();
+        m.put(Attributes.FILENAME_FOR_EXPORT, this.getFileNameForExport());
+        //        m.put(Attributes.ITEM_FOR_SIMPLE_ANNOTATION,
+        //                String.valueOf(this.getItemForSimpleAnnotation()));
+        m.put(Attributes.LEFT_CONTEXT_SIZE,
+                String.valueOf(this.getLeftContextSize()));
+        m.put(Attributes.LEFT_CONTEXT_ITEM,
+                String.valueOf(this.getLeftContextItem()));
+        m.put(Attributes.LOCALE, this.getLocale());
+        m.put(Attributes.LEFT_CONTEXT_SIZE_FOR_EXPORT,
+                String.valueOf(this.getLeftContextSizeForExport()));
+        m.put(Attributes.LEFT_CONTEXT_ITEM_FOR_EXPORT,
+                String.valueOf(this.getLeftContextItemForExport()));
+        m.put(Attributes.RIGHT_CONTEXT_ITEM,
+                String.valueOf(this.getRightContextItem()));
+        m.put(Attributes.RIGHT_CONTEXT_SIZE,
+                String.valueOf(this.getRightContextSize()));
+        m.put(Attributes.RIGHT_CONTEXT_ITEM_FOR_EXPORT,
+                String.valueOf(this.getRightContextItemForExport()));
+        m.put(Attributes.RIGHT_CONTEXT_SIZE_FOR_EXPORT,
+                String.valueOf(this.getRightContextSizeForExport()));
+        m.put(Attributes.SELECTED_COLLECTION, this.getSelectedCollection());
+        m.put(Attributes.QUERY_LANGUAGE, this.getQueryLanguage());
+        m.put(Attributes.PAGE_LENGTH, String.valueOf(this.getPageLength()));
+        m.put(Attributes.METADATA_QUERY_EXPERT_MODUS,
+                String.valueOf(this.isMetadataQueryExpertModus()));
+        //        m.put(Attributes.SEARCH_SETTINGS_TAB,
+        //                String.valueOf(this.getSearchSettingsTab()));
+        //        m.put(Attributes.SELECTED_GRAPH_TYPE,
+        //                String.valueOf(this.getSelectedGraphType()));
+        //        m.put(Attributes.SELECTED_SORT_TYPE, this.getSelectedSortType());
+        //        m.put(Attributes.SELECTED_VIEW_FOR_SEARCH_RESULTS,
+        //                this.getSelectedViewForSearchResults());
+        m.put(Attributes.DEFAULT_POS_FOUNDRY, this.getDefaultPOSfoundry());
+        m.put(Attributes.DEFAULT_LEMMA_FOUNDRY, this.getDefaultLemmafoundry());
+        m.put(Attributes.DEFAULT_CONST_FOUNDRY, this.getDefaultConstfoundry());
+        m.put(Attributes.DEFAULT_REL_FOUNDRY, this.getDefaultRelfoundry());
+        m.put(Attributes.COLLECT_AUDITING_DATA,
+                String.valueOf(this.isCollectData()));
+
+        for (Map.Entry pair : m.entrySet()) {
+            if (pair.getValue() == null)
+                pair.setValue("");
+        }
+        return m;
+    }
+
+    public Map toObjectMap() {
+        Map<String, Object> m = new HashMap<>();
+        m.put(Attributes.FILENAME_FOR_EXPORT, this.getFileNameForExport());
+        //        m.put(Attributes.ITEM_FOR_SIMPLE_ANNOTATION,
+        //                this.getItemForSimpleAnnotation());
+        m.put(Attributes.LEFT_CONTEXT_SIZE, this.getLeftContextSize());
+        m.put(Attributes.LEFT_CONTEXT_ITEM, this.getLeftContextItem());
+        m.put(Attributes.LOCALE, this.getLocale());
+        m.put(Attributes.LEFT_CONTEXT_SIZE_FOR_EXPORT,
+                this.getLeftContextSizeForExport());
+        m.put(Attributes.LEFT_CONTEXT_ITEM_FOR_EXPORT,
+                this.getLeftContextItemForExport());
+        m.put(Attributes.RIGHT_CONTEXT_ITEM, this.getRightContextItem());
+        m.put(Attributes.RIGHT_CONTEXT_SIZE, this.getRightContextSize());
+        m.put(Attributes.RIGHT_CONTEXT_ITEM_FOR_EXPORT,
+                this.getRightContextItemForExport());
+        m.put(Attributes.RIGHT_CONTEXT_SIZE_FOR_EXPORT,
+                this.getRightContextSizeForExport());
+        m.put(Attributes.SELECTED_COLLECTION, this.getSelectedCollection());
+        m.put(Attributes.QUERY_LANGUAGE, this.getQueryLanguage());
+        m.put(Attributes.PAGE_LENGTH, this.getPageLength());
+        m.put(Attributes.METADATA_QUERY_EXPERT_MODUS,
+                this.isMetadataQueryExpertModus());
+        //        m.put(Attributes.SEARCH_SETTINGS_TAB, this.getSearchSettingsTab());
+        //        m.put(Attributes.SELECTED_GRAPH_TYPE, this.getSelectedGraphType());
+        //        m.put(Attributes.SELECTED_SORT_TYPE, this.getSelectedSortType());
+        //        m.put(Attributes.SELECTED_VIEW_FOR_SEARCH_RESULTS,
+        //                this.getSelectedViewForSearchResults());
+        m.put(Attributes.DEFAULT_POS_FOUNDRY, this.getDefaultPOSfoundry());
+        m.put(Attributes.DEFAULT_LEMMA_FOUNDRY, this.getDefaultLemmafoundry());
+        m.put(Attributes.DEFAULT_CONST_FOUNDRY, this.getDefaultConstfoundry());
+        m.put(Attributes.DEFAULT_REL_FOUNDRY, this.getDefaultRelfoundry());
+        m.put(Attributes.COLLECT_AUDITING_DATA, this.isCollectData());
+        for (Map.Entry pair : m.entrySet()) {
+            if (pair.getValue() == null)
+                pair.setValue("");
+        }
+        return m;
+    }
+
+    @Override
+    public String toString() {
+        return "UserSettings{" +
+                "id=" + id +
+                ", userid=" + userID +
+                ", fileNameForExport='" + fileNameForExport + '\'' +
+                ", leftContextItemForExport='" + leftContextItemForExport + '\''
+                +
+                ", leftContextSizeForExport=" + leftContextSizeForExport +
+                ", locale='" + locale + '\'' +
+                ", leftContextItem='" + leftContextItem + '\'' +
+                ", leftContextSize=" + leftContextSize +
+                ", rightContextItem='" + rightContextItem + '\'' +
+                ", rightContextItemForExport='" + rightContextItemForExport
+                + '\'' +
+                ", rightContextSize=" + rightContextSize +
+                ", rightContextSizeForExport=" + rightContextSizeForExport +
+                ", selectedCollection='" + selectedCollection + '\'' +
+                ", queryLanguage='" + queryLanguage + '\'' +
+                ", pageLength=" + pageLength +
+                ", metadataQueryExpertModus=" + metadataQueryExpertModus +
+                ", defaultPOSfoundry='" + defaultPOSfoundry + '\'' +
+                ", defaultLemmafoundry='" + defaultLemmafoundry + '\'' +
+                ", defaultConstfoundry='" + defaultConstfoundry + '\'' +
+                ", defaultRelfoundry='" + defaultRelfoundry + '\'' +
+                ", collectData='" + collectData + "\'" +
+                '}';
+    }
+
+    public static UserSettings fromString(String value) {
+        Map<String, Object> map;
+        try {
+            map = JsonUtils.read(value, Map.class);
+        }catch (IOException e) {
+            return new UserSettings();
+        }
+        return UserSettings.fromObjectMap(map);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o)
+            return true;
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        UserSettings that = (UserSettings) o;
+
+        if (userID != null ? userID != that.userID : that.userID != null)
+            return false;
+        if (id != null ? !id.equals(that.id) : that.id != null)
+            return false;
+
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = id != null ? id.hashCode() : 0;
+        result = 31 * result + (userID != null ? userID.hashCode() : 0);
+        return result;
+    }
+
+    public String toJSON() {
+        return JsonUtils.toJSON(this);
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/Benchmark.java b/src/main/java/de/ids_mannheim/korap/utils/Benchmark.java
new file mode 100644
index 0000000..1f06897
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/Benchmark.java
@@ -0,0 +1,15 @@
+package de.ids_mannheim.korap.utils;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author hanl
+ * @date 29/04/2014
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface Benchmark {
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/BooleanUtils.java b/src/main/java/de/ids_mannheim/korap/utils/BooleanUtils.java
new file mode 100644
index 0000000..2e767ed
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/BooleanUtils.java
@@ -0,0 +1,23 @@
+package de.ids_mannheim.korap.utils;
+
+/**
+ * @author hanl
+ * @date 19/02/2014
+ */
+public class BooleanUtils {
+
+    public static String dbname;
+
+    public static Object getBoolean(Object val) {
+        if (val == null) val = false;
+        if (dbname != null
+                && dbname.equalsIgnoreCase("sqlite")) {
+            if (val instanceof Boolean) {
+                return (val == true) ? 1 : 0;
+            } else if (val instanceof Integer) {
+                return ((Integer) val == 1);
+            }
+        }
+        return val;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder.java b/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder.java
new file mode 100644
index 0000000..b68feac
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder.java
@@ -0,0 +1,319 @@
+package de.ids_mannheim.korap.utils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Multiset;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author hanl
+ * @date 06/12/2013
+ */
+@Deprecated
+public class CollectionQueryBuilder {
+
+    private enum Relation {
+        OR, AND
+    }
+
+    private CollectionTypes types;
+    private List<Map> rq;
+    private Multimap<String, String> mfilter;
+    private Multimap<String, String> mextension;
+    private Relation simpleFilterRel = Relation.OR;
+    private Relation simpleExtendRel = Relation.OR;
+
+
+    public CollectionQueryBuilder() {
+        this.rq = new ArrayList<>();
+        this.mfilter = ArrayListMultimap.create();
+        this.mextension = ArrayListMultimap.create();
+        this.types = new CollectionTypes();
+    }
+
+    public CollectionQueryBuilder addResource(String query) {
+        try {
+            List v = JsonUtils.read(query, LinkedList.class);
+            this.rq.addAll(v);
+        } catch (IOException e) {
+            throw new IllegalArgumentException("Conversion went wrong!");
+        }
+        return this;
+    }
+
+    public CollectionQueryBuilder addResources(List<String> queries) {
+        for (String query : queries)
+            addResource(query);
+        return this;
+    }
+
+    public CollectionQueryBuilder addMetaFilter(String key, String value) {
+        this.mfilter.put(key, value);
+        return this;
+    }
+
+
+    public CollectionQueryBuilder addMetaFilterQuery(String queries) {
+        this.mfilter.putAll(resRel(queries));
+        return this;
+    }
+
+    public CollectionQueryBuilder addMetaExtend(String key, String value) {
+        this.mextension.put(key, value);
+        return this;
+    }
+
+
+    public CollectionQueryBuilder setFilterAttributeRelation(Relation rel) {
+        simpleFilterRel = rel;
+        return this;
+    }
+
+
+    public CollectionQueryBuilder setExtendAttributeRelation(Relation rel) {
+        simpleExtendRel = rel;
+        return this;
+    }
+
+    public CollectionQueryBuilder addMetaExtendQuery(String queries) {
+        this.mextension.putAll(resRel(queries));
+        return this;
+    }
+
+
+    @Deprecated
+    private List<Map> createFilter(Relation rel) {
+        String relation = rel == Relation.AND ? "and" : "or";
+        List<Map> mfil = new ArrayList<>();
+        boolean multypes = this.mfilter.keySet().size() > 1;
+        String def_key = null;
+
+        if (!multypes) {
+            Multiset<String> keys = this.mfilter.keys();
+            def_key = keys.toArray(new String[keys.size()])[0];
+        }
+
+        List value = this.createValue(this.mfilter);
+
+        if (mfilter.values().size() == 1)
+            Collections.addAll(mfil, types.createMetaFilter((Map) value.get(0)));
+        else {
+            Map group;
+            if (!multypes)
+                group = types.createGroup(relation, def_key, value);
+            else
+                group = types.createGroup(relation, null, value);
+            Collections.addAll(mfil, types.createMetaFilter(group));
+        }
+        return mfil;
+    }
+
+    @Deprecated
+    private List<Map> createExtender(Relation rel) {
+        String relation = rel == Relation.AND ? "and" : "or";
+        List<Map> mex = new ArrayList();
+        boolean multypes = this.mextension.keys().size() > 1;
+        String def_key = null;
+
+        if (!multypes)
+            def_key = this.mextension.keys().toArray(new String[0])[0];
+
+        List value = this.createValue(this.mextension);
+        // todo: missing: - takes only one resource, but resources can be chained!
+        if (this.mextension.values().size() == 1)
+            Collections.addAll(mex, types.createMetaExtend((Map) value.get(0)));
+        else {
+            Map group;
+            if (!multypes)
+                group = types.createGroup(relation, def_key, value);
+            else
+                group = types.createGroup(relation, null, value);
+            Collections.addAll(mex, types.createMetaExtend(group));
+        }
+        return mex;
+    }
+
+    private List<Map> join() {
+        List<Map> cursor = new ArrayList<>(this.rq);
+        if (!this.mfilter.isEmpty())
+            cursor.addAll(this.createFilter(simpleFilterRel));
+        if (!this.mextension.isEmpty())
+            cursor.addAll(this.createExtender(simpleExtendRel));
+        return cursor;
+    }
+
+    private List createValue(Multimap<String, String> map) {
+        List value = new ArrayList<>();
+        String[] dates = new String[3];
+        for (String key : map.keySet()) {
+            if (key.equals("pubDate")) {
+                dates = processDates((List<String>) map.get(key));
+                continue;
+            }
+
+            if (map.get(key).size() == 1) {
+                Map term = types.createTerm(key, null,
+                        map.get(key).toArray(new String[0])[0], null);
+                value.add(term);
+            } else {
+                boolean multypes = map.keySet().size() > 1;
+                List g = new ArrayList();
+                for (String v : map.get(key))
+                    g.add(types.createTerm(null, v, null));
+
+                if (multypes) {
+                    Map group = types.createGroup("and", key, g);
+                    value.add(group);
+                } else
+                    value.addAll(g);
+
+            }
+        }
+
+        int idx = 3;
+        if (dates[0] != null && dates[0].equals("r")) {
+            Map term1 = types.createTerm(null, dates[1], "korap:date");
+            Map term2 = types.createTerm(null, dates[2], "korap:date");
+            Map group = types.createGroup("between", "pubDate", Arrays.asList(term1, term2));
+            value.add(group);
+        } else if (dates[1] != null) {
+            Map term1 = types.createTerm(null, dates[1], "korap:date");
+            Map group = types.createGroup("since", "pubDate", Arrays.asList(term1));
+            value.add(group);
+        } else if (dates[2] != null) {
+            Map term1 = types.createTerm(null, dates[2], "korap:date");
+            Map group = types.createGroup("until", "pubDate", Arrays.asList(term1));
+            value.add(group);
+        }
+
+        for (int i = idx; i < dates.length; i++) {
+            if (dates[i] != null) {
+                Map term1 = types.createTerm(dates[i], "korap:date");
+                Map group = types.createGroup("exact", "pubDate", Arrays.asList(term1));
+                value.add(group);
+            }
+        }
+        return value;
+    }
+
+    private String[] processDates(List<String> dates) {
+        if (dates.isEmpty())
+            return new String[3];
+        String[] el = new String[dates.size() + 3];
+        int idx = 3;
+        for (String value : dates) {
+            if (value.contains("<")) {
+                String[] sp = value.split("<");
+                el[1] = sp[1];
+            } else if (value.contains(">")) {
+                String[] sp = value.split(">");
+                el[2] = sp[1];
+            } else {
+                el[idx] = value;
+                idx++;
+            }
+        }
+        if (el[1] != null && el[2] != null)
+            el[0] = "r";
+        return el;
+    }
+
+    public List<Map> raw() {
+        return join();
+    }
+
+    public String toCollections() {
+        Map meta = new LinkedHashMap();
+        meta.put("collections", join());
+        return JsonUtils.toJSON(meta);
+    }
+
+    /**
+     * returns all references to parents and meta query as string representation
+     *
+     * @return
+     */
+    public JsonNode toNode() {
+        return JsonUtils.valueToTree(join());
+    }
+
+    public String toJSON() {
+        return JsonUtils.toJSON(join());
+    }
+
+
+    /**
+     * resolves all queries as equal (hierarchy) AND/OR relations
+     * grouping is not supported!
+     *
+     * @param queries
+     * @return
+     */
+    private Multimap<String, String> resRel(String queries) {
+        Multimap<String, String> qmap = ArrayListMultimap.create();
+        String op = null;
+        if (queries.contains("AND") | queries.contains("OR"))
+            op = queries.contains("AND") ? "AND" : "OR";
+        else if (queries.contains("&") | queries.contains("|"))
+            op = queries.contains("&") ? "&" : "|";
+
+        if (op == null)
+            return qmap;
+
+        String[] spl = queries.trim().split(op);
+        for (String query : spl) {
+            String[] q = query.split("=");
+            if (q.length > 1) {
+                String attr = q[0].trim();
+                String val = q[1].trim();
+                qmap.put(attr, val);
+            }
+            // todo: return error when query not well-formed
+        }
+        return qmap;
+    }
+
+    /**
+     * resolve relations and allow grouping of attributes: (tc1 and tc1) or (tc3)
+     *
+     * @param queries
+     * @param filter  flag if either filter or extend collection
+     * @return
+     */
+    private void resRelation(String queries, boolean filter) {
+        Pattern p = Pattern.compile("\\(([\\w\\s:]+)\\)");
+        List _fill = new ArrayList();
+        Matcher m = p.matcher(queries);
+        while (m.find()) {
+            String gr = m.group(1);
+            _fill.add(gr);
+            String whole = "(" + gr + ")";
+            int fin = queries.lastIndexOf(whole);
+            String sub = queries.substring(queries.indexOf(whole), queries.lastIndexOf(whole));
+            queries.replace(whole, "");
+        }
+    }
+
+    private void v(String queries, boolean filter) {
+        // and exclude sub-groups?? : ((tc=121))
+        Pattern p = Pattern.compile("\\(([\\w\\s=]+)\\)");
+        List _fill = new ArrayList();
+        Matcher m = p.matcher(queries);
+        while (m.find()) {
+            String gr = m.group(1);
+
+        }
+
+    }
+
+    public void clear() {
+        this.rq.clear();
+        this.mfilter.clear();
+        this.mextension.clear();
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder2.java b/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder2.java
new file mode 100644
index 0000000..1058f5b
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder2.java
@@ -0,0 +1,86 @@
+package de.ids_mannheim.korap.utils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * @author hanl
+ * @date 13/05/2014
+ */
+@Deprecated
+public class CollectionQueryBuilder2 {
+
+    private List<Map> rq;
+    private Map groups;
+    private CollectionTypes types;
+    private boolean verbose;
+
+    public CollectionQueryBuilder2() {
+        this.verbose = false;
+        this.rq = new ArrayList<>();
+        this.groups = new HashMap();
+        this.types = new CollectionTypes();
+    }
+
+    public CollectionQueryBuilder2(boolean verbose) {
+        this();
+        this.verbose = verbose;
+    }
+
+
+    public CollectionQueryBuilder2 addResource(String collections) {
+        try {
+            List v = JsonUtils.read(collections, LinkedList.class);
+            this.rq.addAll(v);
+        } catch (IOException e) {
+            throw new IllegalArgumentException("Conversion went wrong!");
+        }
+        return this;
+    }
+
+    public CollectionQueryBuilder2 addResources(List<String> queries) {
+        for (String query : queries)
+            addResource(query);
+        return this;
+    }
+
+//    public CollectionQueryBuilder2 setQuery(String query) {
+//        CollectionQueryProcessor tree = new CollectionQueryProcessor();
+//        tree.process(query);
+//        this.groups = tree.getRequestMap();
+//        return this;
+//    }
+
+    public List raw() {
+        List list = new ArrayList(this.rq);
+        list.add(types.createMetaFilter(this.groups));
+        return list;
+    }
+
+    private Object raw2() {
+        return this.groups;
+    }
+
+    public String toCollections() {
+        Map value = new HashMap();
+        value.put("collections", raw2());
+        return JsonUtils.toJSON(value);
+    }
+
+    public JsonNode toNode() {
+        return JsonUtils.valueToTree(raw2());
+    }
+
+    public String toJSON() {
+        return JsonUtils.toJSON(raw2());
+    }
+
+
+    // add public filter to original query
+    private void addToGroup() {
+        Map first = this.rq.get(0);
+
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder3.java b/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder3.java
new file mode 100644
index 0000000..1217085
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder3.java
@@ -0,0 +1,79 @@
+package de.ids_mannheim.korap.utils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * convenience builder class for collection query -- new one
+ *
+ * @author hanl
+ * @date 16/09/2014
+ */
+public class CollectionQueryBuilder3 {
+
+    private boolean verbose;
+    private List<Map> rq;
+    private StringBuilder builder;
+
+    public CollectionQueryBuilder3() {
+        this(false);
+    }
+
+    public CollectionQueryBuilder3(boolean verbose) {
+        this.verbose = verbose;
+        this.builder = new StringBuilder();
+        this.rq = new LinkedList<>();
+    }
+
+
+    public CollectionQueryBuilder3 addSegment(String field, String value) {
+        String f = field + "=" + value;
+        this.builder.append(f);
+        return this;
+    }
+
+    public CollectionQueryBuilder3 add(String query) {
+        this.builder.append(query);
+        return this;
+    }
+
+    public CollectionQueryBuilder3 and() {
+        this.builder.append(" & ");
+        return this;
+    }
+
+    public CollectionQueryBuilder3 or() {
+        this.builder.append(" | ");
+        return this;
+    }
+
+    public CollectionQueryBuilder3 addResource(String collection) {
+        try {
+            List v = JsonUtils.read(collection, LinkedList.class);
+            this.rq.addAll(v);
+        } catch (IOException e) {
+            throw new IllegalArgumentException("Conversion went wrong!");
+        }
+        return this;
+    }
+
+    public List getRequest() {
+        List list = new ArrayList();
+        if (!this.rq.isEmpty())
+            list.addAll(this.rq);
+        System.out.println("RAW QUERY " + this.builder.toString());
+//        CollectionQueryProcessor tree = new CollectionQueryProcessor(this.verbose);
+//        tree.process(this.builder.toString());
+//        list.add(tree.getRequestMap());
+        return list;
+    }
+
+    public String toJSON() {
+        return JsonUtils.toJSON(getRequest());
+    }
+
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/CollectionTypes.java b/src/main/java/de/ids_mannheim/korap/utils/CollectionTypes.java
new file mode 100644
index 0000000..2f90f57
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/CollectionTypes.java
@@ -0,0 +1,95 @@
+package de.ids_mannheim.korap.utils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author hanl
+ * @date 04/12/2013
+ */
+public class CollectionTypes {
+
+    private ObjectMapper mapper;
+
+    public CollectionTypes() {
+        this.mapper = new ObjectMapper();
+    }
+
+    public Map createGroup(String relation, String field, List terms) {
+        if (relation == null)
+            return null;
+
+        Map kgroup = new LinkedHashMap<>();
+        kgroup.put("@type", "korap:group");
+        if (field != null)
+            kgroup.put("@field", "korap:field#" + field);
+        kgroup.put("relation", relation);
+        kgroup.put("operands", terms);
+        return kgroup;
+    }
+
+    public Map createTerm(String field, String subtype, String value, String type) {
+        Map term = new LinkedHashMap<>();
+        if (type == null)
+            type = "korap:term";
+        term.put("@type", type);
+        if (field != null)
+            term.put("@field", "korap:field#" + field);
+        if (subtype != null)
+            term.put("@subtype", "korap:value#" + subtype);
+        term.put("@value", value);
+        return term;
+    }
+
+    public Map createTerm(String field, String value, String type) {
+        return createTerm(field, null, value, type);
+    }
+
+    public Map createTerm(String field, String value) {
+        return createTerm(field, value, null);
+    }
+
+    public Map createResourceFilter(String resource, Map value) {
+        Map meta = new LinkedHashMap();
+        meta.put("@type", "korap:meta-filter");
+        meta.put("@id", "korap-filter#" + resource);
+        meta.put("@value", value);
+        return meta;
+    }
+
+    public Map createResourceFilter(String resource, String value) throws IOException {
+        return createResourceFilter(resource, mapify(value));
+    }
+
+    public Map createResourceExtend(String resource, Map value) {
+        Map meta = new LinkedHashMap();
+        meta.put("@type", "korap:meta-extend");
+        meta.put("@id", "korap-filter#" + resource);
+        meta.put("@value", value);
+        return meta;
+    }
+
+    public Map createMetaFilter(Map value) {
+        Map meta = new LinkedHashMap();
+        meta.put("@type", "korap:meta-filter");
+        meta.put("@value", value);
+        return meta;
+    }
+
+    public Map createMetaExtend(Map value) {
+        Map meta = new LinkedHashMap();
+        meta.put("@type", "korap:meta-extend");
+        meta.put("@value", value);
+        return meta;
+    }
+
+
+    public Map mapify(String s) throws IOException {
+        return mapper.readValue(s, Map.class);
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/ConcurrentMultiMap.java b/src/main/java/de/ids_mannheim/korap/utils/ConcurrentMultiMap.java
new file mode 100644
index 0000000..5bb811e
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/ConcurrentMultiMap.java
@@ -0,0 +1,132 @@
+package de.ids_mannheim.korap.utils;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.MapMaker;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentMap;
+
+import com.google.common.collect.Sets;
+
+import java.util.Collection;
+import java.util.Set;
+
+
+/**
+ * A general purpose Multimap implementation for delayed processing and concurrent insertion/deletes.
+ * This code is based on an implementation by Guido Medina!
+ *
+ * @param <K> A comparable Key
+ * @param <V> A comparable Value
+ */
+
+/**
+ * User: hanl
+ * Date: 8/27/13
+ * Time: 11:18 AM
+ */
+
+public class ConcurrentMultiMap<K extends Comparable, V extends Comparable> {
+
+    private final int initialCapacity;
+    private final LockMap<K> locks;
+    private final ConcurrentMap<K, List<V>> cache;
+
+    public ConcurrentMultiMap() {
+        this(16, 64);
+    }
+
+    public ConcurrentMultiMap(final int concurrencyLevel) {
+        this(concurrencyLevel, 64);
+    }
+
+    public ConcurrentMultiMap(final int concurrencyLevel, final int initialCapacity) {
+        this.initialCapacity = initialCapacity;
+        cache = new MapMaker().concurrencyLevel(concurrencyLevel).initialCapacity(initialCapacity).makeMap();
+        locks = new LockMap<K>(concurrencyLevel, initialCapacity);
+    }
+
+    public void put(final K key, final V value) {
+        synchronized (locks.getLock(key)) {
+            List<V> set = cache.get(key);
+            if (set == null) {
+                set = Lists.newArrayListWithExpectedSize(initialCapacity);
+                cache.put(key, set);
+            }
+            set.add(value);
+        }
+    }
+
+    public void putAll(final K key, final Collection<V> values) {
+        synchronized (locks.getLock(key)) {
+            List<V> set = cache.get(key);
+            if (set == null) {
+                set = Lists.newArrayListWithExpectedSize(initialCapacity);
+                cache.put(key, set);
+            }
+            set.addAll(values);
+        }
+    }
+
+    public List<V> remove(final K key) {
+        synchronized (locks.getLock(key)) {
+            return cache.remove(key);
+        }
+    }
+
+
+    public void remove(final K key, final V value) {
+        List<V> values = cache.get(key);
+        synchronized (locks.getLock(key)) {
+            values.remove(value);
+        }
+    }
+
+
+    public Set<K> getKeySet() {
+        return cache.keySet();
+    }
+
+    public int size() {
+        return cache.size();
+    }
+
+    public boolean containsKey(K key) {
+        return cache.containsKey(key);
+    }
+
+    public List<V> get(K key) {
+        return cache.get(key);
+    }
+
+
+    public class LockMap<K extends Comparable> {
+        private final ConcurrentMap<K, Object> locks;
+
+        public LockMap() {
+            this(16, 64);
+        }
+
+        public LockMap(final int concurrencyLevel) {
+            this(concurrencyLevel, 64);
+        }
+
+        public LockMap(final int concurrencyLevel, final int initialCapacity) {
+            locks = new MapMaker().concurrencyLevel(concurrencyLevel).initialCapacity(initialCapacity).weakValues().makeMap();
+        }
+
+        public Object getLock(final K key) {
+            final Object object = new Object();
+            Object lock = locks.putIfAbsent(key, object);
+            return lock == null ? object : lock;
+        }
+
+    }
+
+
+    public String toString() {
+        return cache.toString();
+    }
+
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/JsonUtils.java b/src/main/java/de/ids_mannheim/korap/utils/JsonUtils.java
new file mode 100644
index 0000000..85c7e3e
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/JsonUtils.java
@@ -0,0 +1,93 @@
+package de.ids_mannheim.korap.utils;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author hanl
+ * @date 28/01/2014
+ */
+public class JsonUtils {
+    private static ObjectMapper mapper = new ObjectMapper();
+
+    private JsonUtils() {
+    }
+
+    public static String toJSON(Object values) {
+        try {
+            return mapper.writeValueAsString(values);
+        }catch (JsonProcessingException e) {
+            return "";
+        }
+    }
+
+    public static JsonNode readTree(String s) {
+        try {
+            return mapper.readTree(s);
+        }catch (IOException e) {
+            return null;
+        }
+    }
+
+    public static ObjectNode createObjectNode() {
+        return mapper.createObjectNode();
+    }
+
+    public static ArrayNode createArrayNode() {
+        return mapper.createArrayNode();
+    }
+
+    public static JsonNode valueToTree(Object value) {
+        return mapper.valueToTree(value);
+    }
+
+    public static <T> T read(String json, Class<T> cl) throws IOException {
+        return mapper.readValue(json, cl);
+    }
+
+    public static <T> T readFile(String path, Class<T> clazz)
+            throws IOException {
+        return mapper.readValue(new File(path), clazz);
+    }
+
+    public static void writeFile(String path, String content) throws IOException {
+        mapper.writeValue(new File(path), content);
+    }
+
+    public static <T> T readSimple(String json, Class<T> cl) {
+        try {
+            return mapper.readValue(json, cl);
+        }catch (IOException e) {
+            return null;
+        }
+    }
+
+    public static List<Map<String, Object>> convertToList(String json)
+            throws JsonProcessingException {
+        List d = new ArrayList();
+        JsonNode node = JsonUtils.readTree(json);
+        if (node.isArray()) {
+            Iterator<JsonNode> nodes = node.iterator();
+            while (nodes.hasNext()) {
+                Map<String, Object> map = mapper
+                        .treeToValue(nodes.next(), Map.class);
+                d.add(map);
+            }
+        }else if (node.isObject()) {
+            Map<String, Object> map = mapper.treeToValue(node, Map.class);
+            d.add(map);
+        }
+        return d;
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/KorAPLogger.java b/src/main/java/de/ids_mannheim/korap/utils/KorAPLogger.java
new file mode 100644
index 0000000..5fc876f
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/KorAPLogger.java
@@ -0,0 +1,373 @@
+package de.ids_mannheim.korap.utils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Marker;
+
+/**
+ * @author hanl
+ * @date 28/03/2014
+ */
+
+public class KorAPLogger implements Logger {
+    // debugging flag, info, warn, error will always be logged though!
+    public static boolean DEBUG = false;
+
+    public static final String ERROR_LOG = "errorLog";
+    public static final String SECURITY_LOG = "securityLog";
+
+    //fixme:
+    public static final KorAPLogger ERROR_LOGGER = KorAPLogger
+            .initiate(ERROR_LOG);
+    public static final KorAPLogger QUERY_LOGGER = KorAPLogger.initiate("ql");
+
+    @Deprecated
+    public static final KorAPLogger SECURITY_LOGGER = KorAPLogger
+            .initiate("security");
+    private Logger log;
+
+    public static KorAPLogger initiate(Class cl) {
+        KorAPLogger l = new KorAPLogger();
+        l.log = LoggerFactory.getLogger(cl);
+        return l;
+    }
+
+    public static KorAPLogger initiate(String name) {
+        KorAPLogger l = new KorAPLogger();
+        l.log = LoggerFactory.getLogger(name);
+        return l;
+    }
+
+    private KorAPLogger() {
+    }
+
+    @Override
+    public String getName() {
+        return log.getName();
+    }
+
+    @Override
+    public boolean isTraceEnabled() {
+        return log.isTraceEnabled();
+    }
+
+    @Override
+    public void trace(String s) {
+        if (DEBUG)
+            this.log.trace(s);
+
+    }
+
+    @Override
+    public void trace(String s, Object o) {
+        if (DEBUG)
+            this.log.trace(s, o);
+    }
+
+    @Override
+    public void trace(String s, Object o, Object o2) {
+        if (DEBUG)
+            this.log.trace(s, o, o2);
+    }
+
+    @Override
+    public void trace(String s, Object... objects) {
+        if (DEBUG)
+            this.log.trace(s, objects);
+    }
+
+    @Override
+    public void trace(String s, Throwable throwable) {
+        if (DEBUG)
+            this.log.trace(s, throwable);
+    }
+
+    @Override
+    public boolean isTraceEnabled(Marker marker) {
+        if (DEBUG)
+            return true;
+        return false;
+    }
+
+    @Override
+    public void trace(Marker marker, String s) {
+        if (DEBUG)
+            this.log.trace(marker, s);
+    }
+
+    @Override
+    public void trace(Marker marker, String s, Object o) {
+        if (DEBUG)
+            this.log.trace(marker, s, o);
+    }
+
+    @Override
+    public void trace(Marker marker, String s, Object o, Object o2) {
+        if (DEBUG)
+            this.log.trace(marker, s, o, o2);
+    }
+
+    @Override
+    public void trace(Marker marker, String s, Object... objects) {
+        if (DEBUG)
+            this.log.trace(marker, s, objects);
+    }
+
+    @Override
+    public void trace(Marker marker, String s, Throwable throwable) {
+        if (DEBUG)
+            this.log.trace(marker, s, throwable);
+    }
+
+    @Override
+    public boolean isDebugEnabled() {
+        if (DEBUG)
+            return true;
+        return false;
+    }
+
+    @Override
+    public void debug(String s) {
+        if (DEBUG)
+            this.log.debug(s);
+    }
+
+    @Override
+    public void debug(String s, Object o) {
+        if (DEBUG)
+            this.log.debug(s, o);
+    }
+
+    @Override
+    public void debug(String s, Object o, Object o2) {
+        if (DEBUG)
+            this.log.debug(s, o, o2);
+    }
+
+    @Override
+    public void debug(String s, Object... objects) {
+        if (DEBUG)
+            this.log.debug(s);
+    }
+
+    @Override
+    public void debug(String s, Throwable throwable) {
+        if (DEBUG)
+            this.log.debug(s, throwable);
+    }
+
+    @Override
+    public boolean isDebugEnabled(Marker marker) {
+        return this.log.isDebugEnabled(marker);
+    }
+
+    @Override
+    public void debug(Marker marker, String s) {
+        if (DEBUG)
+            this.log.debug(marker, s);
+    }
+
+    @Override
+    public void debug(Marker marker, String s, Object o) {
+        if (DEBUG)
+            this.log.debug(marker, s, o);
+    }
+
+    @Override
+    public void debug(Marker marker, String s, Object o, Object o2) {
+        if (DEBUG)
+            this.log.debug(marker, s, o, o2);
+    }
+
+    @Override
+    public void debug(Marker marker, String s, Object... objects) {
+        if (DEBUG)
+            this.log.debug(marker, s, objects);
+    }
+
+    @Override
+    public void debug(Marker marker, String s, Throwable throwable) {
+        if (DEBUG)
+            this.log.debug(marker, s, throwable);
+    }
+
+    @Override
+    public boolean isInfoEnabled() {
+        return this.log.isInfoEnabled();
+    }
+
+    @Override
+    public void info(String s) {
+        this.log.info(s);
+    }
+
+    @Override
+    public void info(String s, Object o) {
+        this.log.info(s, o);
+    }
+
+    @Override
+    public void info(String s, Object o, Object o2) {
+        this.log.info(s, o, o2);
+    }
+
+    @Override
+    public void info(String s, Object... objects) {
+        this.log.info(s, objects);
+    }
+
+    @Override
+    public void info(String s, Throwable throwable) {
+        this.log.info(s, throwable);
+    }
+
+    @Override
+    public boolean isInfoEnabled(Marker marker) {
+        return this.log.isInfoEnabled(marker);
+    }
+
+    @Override
+    public void info(Marker marker, String s) {
+        this.log.info(marker, s);
+    }
+
+    @Override
+    public void info(Marker marker, String s, Object o) {
+        this.log.info(marker, s, o);
+    }
+
+    @Override
+    public void info(Marker marker, String s, Object o, Object o2) {
+        this.log.info(marker, s, o, o2);
+    }
+
+    @Override
+    public void info(Marker marker, String s, Object... objects) {
+        this.log.info(marker, s, objects);
+    }
+
+    @Override
+    public void info(Marker marker, String s, Throwable throwable) {
+        this.log.info(marker, s, throwable);
+    }
+
+    @Override
+    public boolean isWarnEnabled() {
+        return this.log.isWarnEnabled();
+    }
+
+    @Override
+    public void warn(String s) {
+        this.log.warn(s);
+    }
+
+    @Override
+    public void warn(String s, Object o) {
+        this.log.warn(s, o);
+    }
+
+    @Override
+    public void warn(String s, Object... objects) {
+        this.log.warn(s, objects);
+    }
+
+    @Override
+    public void warn(String s, Object o, Object o2) {
+        this.log.warn(s, o, o2);
+    }
+
+    @Override
+    public void warn(String s, Throwable throwable) {
+        this.log.warn(s, throwable);
+    }
+
+    @Override
+    public boolean isWarnEnabled(Marker marker) {
+        return this.log.isTraceEnabled(marker);
+    }
+
+    @Override
+    public void warn(Marker marker, String s) {
+        this.log.warn(marker, s);
+    }
+
+    @Override
+    public void warn(Marker marker, String s, Object o) {
+        this.log.warn(marker, s, o);
+    }
+
+    @Override
+    public void warn(Marker marker, String s, Object o, Object o2) {
+        this.log.warn(marker, s, o, o2);
+    }
+
+    @Override
+    public void warn(Marker marker, String s, Object... objects) {
+        this.log.warn(marker, s, objects);
+    }
+
+    @Override
+    public void warn(Marker marker, String s, Throwable throwable) {
+        this.log.warn(marker, s, throwable);
+    }
+
+    @Override
+    public boolean isErrorEnabled() {
+        return this.log.isErrorEnabled();
+    }
+
+    @Override
+    public void error(String s) {
+        this.log.error(s);
+    }
+
+    @Override
+    public void error(String s, Object o) {
+        this.log.error(s, o);
+    }
+
+    @Override
+    public void error(String s, Object o, Object o2) {
+        this.log.error(s, o, o2);
+    }
+
+    @Override
+    public void error(String s, Object... objects) {
+        this.log.error(s, objects);
+    }
+
+    @Override
+    public void error(String s, Throwable throwable) {
+        this.log.error(s, throwable);
+    }
+
+    @Override
+    public boolean isErrorEnabled(Marker marker) {
+        return this.log.isErrorEnabled(marker);
+    }
+
+    @Override
+    public void error(Marker marker, String s) {
+        this.log.error(marker, s);
+    }
+
+    @Override
+    public void error(Marker marker, String s, Object o) {
+        this.log.error(marker, s, o);
+    }
+
+    @Override
+    public void error(Marker marker, String s, Object o, Object o2) {
+        this.log.error(marker, s, o, o2);
+    }
+
+    @Override
+    public void error(Marker marker, String s, Object... objects) {
+        this.log.error(marker, s, objects);
+    }
+
+    @Override
+    public void error(Marker marker, String s, Throwable throwable) {
+        this.log.error(marker, s, throwable);
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/PrefixTreeMap.java b/src/main/java/de/ids_mannheim/korap/utils/PrefixTreeMap.java
new file mode 100644
index 0000000..493a574
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/PrefixTreeMap.java
@@ -0,0 +1,46 @@
+package de.ids_mannheim.korap.utils;
+
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * @author hanl
+ * @date 01/07/2014
+ */
+public class PrefixTreeMap<V> extends TreeMap<String, V> {
+
+
+    public SortedMap<String, V> getPrefixSubMap(String prefix) {
+        if (prefix.length() > 0) {
+            SortedMap d = this.subMap(prefix, getEnd(prefix));
+            if (d.isEmpty())
+                return null;
+            return d;
+        }
+        return null;
+    }
+
+    private String getEnd(String prefix) {
+        char nextLetter = (char) (prefix.charAt(prefix.length() - 1) + 1);
+        return prefix.substring(0, prefix.length() - 1) + nextLetter;
+
+    }
+
+    public V getFirstValue(String prefix) {
+        if (prefix.length() > 0) {
+            String first = this.subMap(prefix, getEnd(prefix)).firstKey();
+            return this.get(first);
+        }
+        return null;
+    }
+
+    public V getLastValue(String prefix) {
+        if (prefix.length() > 0) {
+            String last = this.subMap(prefix, getEnd(prefix)).lastKey();
+            return this.get(last);
+        }
+        return null;
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/StringUtils.java b/src/main/java/de/ids_mannheim/korap/utils/StringUtils.java
new file mode 100644
index 0000000..a7120aa
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/StringUtils.java
@@ -0,0 +1,191 @@
+package de.ids_mannheim.korap.utils;
+
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.slf4j.Logger;
+
+import java.util.*;
+
+public class StringUtils {
+    private final static Logger jlog = KorAPLogger.initiate(StringUtils.class);
+
+    private static final String SLASH = "/";
+    private static final String SEP = ";";
+
+    public static Collection<UUID> stringToUUIDList(String s) {
+        String[] array = s.split(SEP);
+        List<UUID> list = new LinkedList<>();
+        for (String att : array) {
+            list.add(UUID.fromString(att));
+        }
+        return list;
+    }
+
+    public static List<String> toList(String values) {
+        List<String> list = new ArrayList<>();
+        StringTokenizer tokenizer = new StringTokenizer(values, SEP);
+        while (tokenizer.hasMoreTokens())
+            list.add(tokenizer.nextToken());
+        return list;
+    }
+
+    public static Set<String> toSet(String values, String sep) {
+        Set<String> set = new HashSet<>();
+        StringTokenizer tokenizer = new StringTokenizer(values, sep);
+        while (tokenizer.hasMoreTokens())
+            set.add(tokenizer.nextToken());
+        return set;
+    }
+
+    public static Set<String> toSet(String values) {
+        return toSet(values, SEP);
+    }
+
+    public static String toString(Collection<String> values) {
+        return StringUtils.toString(values, SEP);
+    }
+
+    public static String toString(Collection<String> values, String sep) {
+        StringBuffer b = new StringBuffer();
+        for (String s : values)
+            b.append(s).append(sep);
+
+        if (b.length() > 0)
+            b.deleteCharAt(b.length() - 1);
+        return b.toString();
+    }
+
+    public static String orderedToString(Collection<String> hash) {
+        Set<String> orderedSet = new TreeSet<>();
+        orderedSet.addAll(hash);
+        if (orderedSet.isEmpty()) {
+            return "";
+        }else {
+            StringBuilder builder = new StringBuilder();
+            for (String s : orderedSet) {
+                builder.append(s);
+                builder.append(SEP);
+            }
+            builder.deleteCharAt(builder.length() - 1);
+            return builder.toString();
+        }
+    }
+
+    public static String UUIDsetToString(Collection<UUID> hash) {
+        Set<UUID> orderedSet = new TreeSet<>();
+        orderedSet.addAll(hash);
+        if (orderedSet.isEmpty()) {
+            return "";
+        }else {
+            StringBuilder builder = new StringBuilder();
+            for (UUID s : orderedSet) {
+                builder.append(s);
+                builder.append(SEP);
+            }
+            builder.deleteCharAt(builder.length() - 1);
+            return builder.toString();
+        }
+    }
+
+    public static String buildSQLRegex(String path) {
+        StringBuilder b = new StringBuilder();
+        String[] match = path.split("/");
+        b.append(match[0]);
+        b.append("(" + "/" + match[1] + ")");
+        b.append("*$");
+        return b.toString();
+    }
+
+    public static Collection<String> joinStringSet(Collection<String> source,
+            String other) {
+        Set<String> set = new HashSet<>(source);
+        set.add(other);
+        return set;
+    }
+
+    public static Collection<UUID> joinUUIDSet(Collection<UUID> source,
+            UUID other) {
+        Set<UUID> set = new HashSet<>(source);
+        set.add(other);
+        return set;
+    }
+
+    public static String joinResources(String first, String second) {
+        String res;
+        if (first != null && !first.isEmpty())
+            res = first + SLASH + second;
+        else
+            res = second;
+        return res.replaceAll("\\s", "");
+    }
+
+    public static String[] splitAnnotations(String joined) {
+        String[] spl = joined.split(SLASH);
+        if (spl.length == 2)
+            return spl;
+        else
+            return null;
+    }
+
+    public static String stripTokenType(String token) {
+        int idx = token.lastIndexOf(" ");
+        if (idx == -1)
+            return token;
+        return token.substring(idx).replaceAll("\\s", "");
+    }
+
+    public static String getTokenType(String token) {
+        return token.substring(0, token.lastIndexOf(" ")).replaceAll("\\s", "")
+                .toLowerCase();
+    }
+
+    public static boolean isInteger(String value) {
+        try {
+            Integer.valueOf(value);
+            return true;
+        }catch (IllegalArgumentException e) {
+            // do nothing!
+            return false;
+        }
+    }
+
+    public static String normalize(String value) {
+        return value.trim().toLowerCase();
+    }
+
+    public static String normalizeHTML(String value) {
+        return StringEscapeUtils.escapeHtml4(value);
+    }
+
+    public static String decodeHTML(String value) {
+        return StringEscapeUtils.unescapeHtml4(value);
+    }
+
+    /**
+     * constructs a lucene query from query string and corpus parameters as set
+     *
+     * @param query
+     * @param corpusIDs
+     * @return
+     */
+    public static String queryBuilder(String query,
+            Collection<String> corpusIDs) {
+        String completeQuery; // holds original query and corpus
+        // selection
+        /**
+         * find documents with metadataquery TODO: does not intercept with
+         * parameters foundries and corpusIDs
+         */
+
+        /* add corpus ids to corpus query */
+        StringBuilder corpusQuery = new StringBuilder("corpus:/(");
+        for (String corpusId : corpusIDs) {
+            corpusQuery.append(corpusId + "|");
+        }
+        corpusQuery.deleteCharAt(corpusQuery.length() - 1);
+        corpusQuery.append(")/");
+        completeQuery = "(" + query + ") AND " + corpusQuery.toString();
+        jlog.debug("Searching documents matching '" + completeQuery + "'.");
+        return completeQuery;
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/TimeUtils.java b/src/main/java/de/ids_mannheim/korap/utils/TimeUtils.java
new file mode 100644
index 0000000..b4f1d30
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/TimeUtils.java
@@ -0,0 +1,207 @@
+package de.ids_mannheim.korap.utils;
+
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.LocalDate;
+import org.joda.time.format.DateTimeFormatter;
+import org.joda.time.format.ISODateTimeFormat;
+import org.slf4j.Logger;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * @author hanl
+ *         <p/>
+ *         calculates current, expiration and inactive time for security
+ *         purposes.
+ * @return
+ */
+public class TimeUtils {
+
+    private static DecimalFormat df = new DecimalFormat("#.#############");
+    private static final DateTimeZone dtz = DateTimeZone.forID("Europe/Berlin");
+    private static Logger jlog = KorAPLogger.initiate(TimeUtils.class);
+
+    public static int convertTimeToSeconds(String expirationVal) {
+        expirationVal = expirationVal.trim();
+        int finIndex = expirationVal.length() - 1;
+        char entity = expirationVal.charAt(finIndex);
+        int returnSec = Integer.valueOf(expirationVal.substring(0, finIndex));
+        jlog.debug("setting time value to {} with time in {}", returnSec,
+                entity);
+        switch (entity) {
+            case 'D':
+                return returnSec * 60 * 60 * 24;
+            case 'H':
+                return returnSec * 60 * 60;
+            case 'M':
+                return returnSec * 60;
+            case 'S':
+                return returnSec;
+            default:
+                jlog.debug(
+                        "no time unit specified. Trying to read from default (minutes)");
+                return Integer.valueOf(expirationVal) * 60;
+        }
+
+    }
+
+    //todo: time zone is wrong!
+    public static DateTime getNow() {
+        return DateTime.now().withZone(dtz);
+    }
+
+    //returns difference in milliseconds
+    public static long calcDiff(DateTime now, DateTime future) {
+        long diff = (future.withZone(dtz).getMillis() - now.withZone(dtz)
+                .getMillis());
+        return diff;
+    }
+
+    public static boolean isPassed(long time) {
+        return getNow().isAfter(time);
+
+    }
+
+    public static boolean isPassed(DateTime time) {
+        return isPassed(time.getMillis());
+    }
+
+    // returns difference in seconds in floating number
+    public static float floating(DateTime past, DateTime now) {
+        long diff = (now.withZone(dtz).getMillis() - past.withZone(dtz)
+                .getMillis());
+        double fin = diff / 1000.0;
+        BigDecimal bd = new BigDecimal(fin).setScale(8, RoundingMode.HALF_EVEN);
+        return bd.floatValue();
+    }
+
+    public static DateTime fromCosmas(String date) {
+        int idx = date.length();
+        try {
+            Integer sec = Integer.valueOf(
+                    date.substring((idx = idx - 2), date.length()).trim());
+            Integer min = Integer
+                    .valueOf(date.substring((idx = idx - 2), idx + 2).trim());
+            Integer hours = Integer
+                    .valueOf(date.substring((idx = idx - 2), idx + 2).trim());
+            Integer day = Integer
+                    .valueOf(date.substring((idx = idx - 2), idx + 2).trim());
+            Integer month = Integer
+                    .valueOf(date.substring((idx = idx - 2), idx + 2).trim());
+            Integer year = Integer
+                    .valueOf(date.substring((idx = idx - 4), idx + 4).trim());
+            return new DateTime(year, month, day, hours, min, sec);
+        }catch (NumberFormatException e) {
+            return getNow().toDateTime();
+        }
+    }
+
+    public static String formatDiff(DateTime now, DateTime after) {
+        return df.format(calcDiff(now, after));
+    }
+
+    /**
+     * converts time to the ISO8601 standard.
+     *
+     * @param time
+     * @return
+     */
+    public static String format(DateTime time) {
+        DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
+        return fmt.print(time);
+    }
+
+    public static String format(long time) {
+        DateTimeFormatter fmt = ISODateTimeFormat.dateTime();
+        return fmt.print(time);
+    }
+
+    /**
+     * calculate expiration time
+     *
+     * @param creation
+     * @param plus     time in seconds
+     * @return
+     */
+    public static DateTime plusSeconds(long creation, int plus) {
+        return new DateTime(creation).withZone(dtz).plusSeconds(plus);
+    }
+
+    public static DateTime getExpiration(long now, int exp) {
+        return new DateTime(now).withZone(dtz).plusSeconds(exp);
+    }
+
+    /**
+     * @param plus
+     * @return
+     */
+    public static DateTime plusSeconds(int plus) {
+        return getNow().withZone(dtz).plusSeconds(plus);
+    }
+
+    public static DateTime plusHours(int hours) {
+        return getNow().withZone(dtz).plusHours(hours);
+    }
+
+    public static DateTime plusMinutes(int minutes) {
+        return getNow().withZone(dtz).plusMinutes(minutes);
+    }
+
+    /**
+     * create time stamp from long value
+     *
+     * @param t time
+     * @return Timestamp
+     */
+    public static LocalDate getTimeStamp(long t) {
+        return new DateTime(t).withZone(dtz).toLocalDate();
+    }
+
+    public static DateTime getDate(int day, int month, int year) {
+        DateTime date = new DateTime().withZone(dtz);
+        return date.withDate(year, month, day);
+    }
+
+    public static String toString(long val, Locale locale) {
+        if (locale == Locale.GERMAN)
+            return new DateTime(val)
+                    .toString("dd. MMMM yyyy, HH:mm", Locale.GERMAN);
+        else
+            return new DateTime(val)
+                    .toString("MM-dd-yyyy, hh:mm", Locale.ENGLISH);
+
+    }
+
+    public static String dateToString(long val, int i) {
+        switch (i) {
+            case 1:
+                return new DateTime(val).toString("yyyy-MM");
+            case 2:
+                return new DateTime(val).toString("yyyy-MM-dd");
+            default:
+                return new DateTime(val).toString("yyyy");
+        }
+    }
+
+    private static final List<DateTime> times = new ArrayList<>();
+
+    @Deprecated
+    public static float benchmark(boolean getFinal) {
+        float value = 0;
+        times.add(getNow());
+        if (getFinal && times.size() > 1) {
+            value = floating(times.get(0), times.get(times.size() - 1));
+            times.clear();
+        }else if (times.size() > 1)
+            value = floating(times.get(times.size() - 2),
+                    times.get(times.size() - 1));
+        return value;
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/.DS_Store b/src/main/java/de/ids_mannheim/korap/web/.DS_Store
new file mode 100644
index 0000000..32f11f9
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/.DS_Store
Binary files differ
diff --git a/src/main/java/de/ids_mannheim/korap/web/ClientsHandler.java b/src/main/java/de/ids_mannheim/korap/web/ClientsHandler.java
new file mode 100644
index 0000000..8b846eb
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/ClientsHandler.java
@@ -0,0 +1,52 @@
+package de.ids_mannheim.korap.web;
+
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.UniformInterfaceException;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.client.config.ClientConfig;
+import com.sun.jersey.api.client.config.DefaultClientConfig;
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+import de.ids_mannheim.korap.exceptions.KorAPException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+
+import javax.ws.rs.core.MultivaluedMap;
+import java.net.URI;
+
+/**
+ * @author hanl
+ * @date 10/12/2013
+ */
+// use for Piotr Ps. rest api connection
+public class ClientsHandler {
+
+    private WebResource service;
+
+    public ClientsHandler(URI address) {
+        ClientConfig config = new DefaultClientConfig();
+        Client client = Client.create(config);
+        this.service = client.resource(address);
+    }
+
+    public String getResponse(String path, String key, Object value) throws KorAPException {
+        MultivaluedMap map = new MultivaluedMapImpl();
+        map.add(key, value);
+        try {
+            return service.path(path).queryParams(map).get(String.class);
+        } catch (UniformInterfaceException e) {
+            throw new KorAPException(StatusCodes.REQUEST_INVALID);
+        }
+    }
+
+    public String getResponse(MultivaluedMap map, String... paths) throws KorAPException {
+        try {
+            WebResource resource = service;
+            for (String p : paths)
+                resource = resource.path(p);
+            resource = resource.queryParams(map);
+            return resource.get(String.class);
+        } catch (UniformInterfaceException e) {
+            throw new KorAPException(StatusCodes.REQUEST_INVALID);
+        }
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/Kustvakt.java b/src/main/java/de/ids_mannheim/korap/web/Kustvakt.java
new file mode 100644
index 0000000..f9349e6
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/Kustvakt.java
@@ -0,0 +1,54 @@
+package de.ids_mannheim.korap.web;
+
+import com.sun.grizzly.http.embed.GrizzlyWebServer;
+import com.sun.grizzly.http.servlet.ServletAdapter;
+import com.sun.jersey.spi.container.servlet.ServletContainer;
+import de.ids_mannheim.korap.config.BeanConfiguration;
+import de.ids_mannheim.korap.utils.KorAPLogger;
+
+import java.io.IOException;
+
+/**
+ * @author hanl
+ * @date 01/06/2015
+ */
+public class Kustvakt {
+
+    private static Integer PORT = -1;
+
+    public static void main(String[] args) throws Exception {
+        attributes(args);
+        BeanConfiguration.loadContext();
+        grizzlyServer(PORT);
+    }
+
+    public static void grizzlyServer(int port) throws IOException {
+        if (port == -1)
+            port = BeanConfiguration.getConfiguration().getPort();
+        System.out.println("Starting grizzly on port " + port + " ...");
+        GrizzlyWebServer gws = new GrizzlyWebServer(port);
+        ServletAdapter jerseyAdapter = new ServletAdapter();
+        jerseyAdapter
+                .addInitParameter("com.sun.jersey.config.property.packages",
+                        "de.ids_mannheim.korap.web.service");
+        jerseyAdapter.setContextPath("/api");
+        jerseyAdapter.setServletInstance(new ServletContainer());
+
+        gws.addGrizzlyAdapter(jerseyAdapter, new String[] { "/api" });
+        gws.start();
+    }
+
+    private static void attributes(String[] args) {
+        for (int i = 0; i < args.length; i++) {
+            switch ((args[i])) {
+                case "--debug":
+                    KorAPLogger.DEBUG = true;
+                    break;
+                case "--port":
+                    PORT = Integer.valueOf(args[i + 1]);
+                    break;
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/SearchLucene.java b/src/main/java/de/ids_mannheim/korap/web/SearchLucene.java
new file mode 100644
index 0000000..be19540
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/SearchLucene.java
@@ -0,0 +1,313 @@
+// Connector to the Lucene Backend
+package de.ids_mannheim.korap.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.Krill;
+import de.ids_mannheim.korap.KrillCollection;
+import de.ids_mannheim.korap.KrillIndex;
+import de.ids_mannheim.korap.response.Match;
+import de.ids_mannheim.korap.response.Result;
+import de.ids_mannheim.korap.util.QueryException;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.KorAPLogger;
+import org.apache.lucene.store.MMapDirectory;
+import org.slf4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * The SearchLucene class allows for searching in the Lucene backend
+ * by JSON-LD serialized queries.
+ * It supports span queries, virtual collections, and paging.
+ *
+ * @author Nils Diewald
+ */
+public class SearchLucene {
+    private final static Logger qlog = KorAPLogger.initiate("queryLogger");
+    private final static Logger log = KorAPLogger.initiate(SearchLucene.class);
+    // Temporary
+    String indexDir = "/data/prep_corpus/index/";
+    String i = "/Users/hanl/Projects/prep_corpus";
+    String klinux10 = "/vol/work/hanl/indices";
+
+    private KrillIndex index;
+
+    /**
+     * Constructor
+     */
+    // todo: use korap.config to get index location
+    public SearchLucene(String path) {
+        try {
+            File f = new File(path);
+            log.info("Loading index from " + path);
+            if (!f.exists()) {
+                KorAPLogger.ERROR_LOGGER.error("Index not found!");
+                System.exit(-1);
+            }
+            this.index = new KrillIndex(new MMapDirectory(new File(path)));
+        }catch (IOException e) {
+            KorAPLogger.ERROR_LOGGER
+                    .error("Unable to load index: {}", e.getMessage());
+        }
+    }
+
+    /**
+     * Search in the Lucene index.
+     *
+     * @param json JSON-LD string with search and potential meta filters.
+     */
+    public String search(String json) {
+        qlog.trace(json);
+        if (this.index != null)
+            return new Krill(json).setIndex(this.index).toJsonString();
+
+        Result kr = new Result();
+        //        kr.setError("Index not found");
+        return kr.toJsonString();
+    }
+
+    ;
+
+    /**
+     * Search in the Lucene index and return matches as token lists.
+     *
+     * @param json JSON-LD string with search and potential meta filters.
+     */
+    public String searchTokenList(String json) {
+        qlog.trace(json);
+        if (this.index != null)
+            return new Krill(json).setIndex(this.index).toJsonString();
+
+        Result kr = new Result();
+        //        kr.setError("Index not found");
+        return kr.toJsonString();
+    }
+
+    ;
+
+    /**
+     * Get info on a match - by means of a richly annotated html snippet.
+     *
+     * @param id match id
+     */
+    public String getMatch(String id) {
+
+        if (this.index != null) {
+            try {
+                return this.index.getMatch(id).toJsonString();
+            }catch (QueryException qe) {
+                Match km = new Match();
+                km.setError(qe.getMessage());
+                return km.toJsonString();
+            }
+        }
+
+        Match km = new Match();
+        km.setError("Index not found");
+        return km.toJsonString();
+    }
+
+    public String getMatch(String id, List<String> foundries,
+            List<String> layers, boolean includeSpans,
+            boolean includeHighlights, boolean sentenceExpansion) {
+        if (this.index != null) {
+            try {
+
+                return this.index
+                        .getMatchInfo(id, "tokens", true, foundries, layers,
+                                includeSpans, includeHighlights,
+                                sentenceExpansion).toJsonString();
+            }catch (QueryException qe) {
+                Match km = new Match();
+                km.setError(qe.getMessage());
+                return km.toJsonString();
+            }
+        }
+
+        Match km = new Match();
+        km.setError("Index not found");
+        return km.toJsonString();
+
+    }
+
+    /**
+     * Get info on a match - by means of a richly annotated html snippet.
+     *
+     * @param id                match id
+     * @param foundry           the foundry of interest - may be null
+     * @param layer             the layer of interest - may be null
+     * @param includeSpans      Should spans be included (or only token infos)?
+     * @param includeHighlights Should highlight markup be included?
+     */
+    public String getMatch(String id, String foundry, String layer,
+            boolean includeSpans, boolean includeHighlights,
+            boolean sentenceExpansion) {
+
+        if (this.index != null) {
+            try {
+
+		/*
+          For multiple foundries/layers use
+		  String idString,
+		  "tokens",
+		  true,
+		  ArrayList<String> foundry,
+		  ArrayList<String> layer,
+		  boolean includeSpans,
+		  boolean includeHighlights,
+		  boolean extendToSentence
+		 */
+
+                return this.index.getMatchInfo(id, "tokens", foundry, layer,
+                        includeSpans, includeHighlights, sentenceExpansion)
+                        .toJsonString();
+            }catch (QueryException qe) {
+                Match km = new Match();
+                km.setError(qe.getMessage());
+                return km.toJsonString();
+            }
+        }
+
+        Match km = new Match();
+        km.setError("Index not found");
+        return km.toJsonString();
+    }
+
+    /**
+     * Get statistics on (virtual) collections.
+     *
+     * @param json JSON-LD string with potential meta filters.
+     */
+    public String getStatisticsLegacy(JsonNode json) throws QueryException {
+        qlog.trace(JsonUtils.toJSON(json));
+        System.out.println("THE NODE BEFORE GETTING STATISTICS " + json);
+
+        if (this.index == null) {
+            return "{\"documents\" : -1, error\" : \"No index given\" }";
+        }
+
+        // Create Virtula VCollection from json search
+        KrillCollection kc = new KrillCollection();
+        kc.fromJsonLegacy(json);
+
+        // Set index
+        kc.setIndex(this.index);
+
+        long docs = 0,
+                tokens = 0,
+                sentences = 0,
+                paragraphs = 0;
+
+        // Get numbers from index (currently slow)
+        try {
+            docs = kc.numberOf("documents");
+            tokens = kc.numberOf("tokens");
+            sentences = kc.numberOf("sentences");
+            paragraphs = kc.numberOf("paragraphs");
+        }catch (IOException e) {
+            e.printStackTrace();
+        }
+
+
+	/*
+    KorAPLogger.ERROR_LOGGER.error("Unable to retrieve statistics: {}", e.getMessage());
+	*/
+
+        // Build json response
+        StringBuilder sb = new StringBuilder("{");
+        sb.append("\"documents\":").append(docs).append(",\"tokens\":")
+                .append(tokens).append(",\"sentences\":").append(sentences)
+                .append(",\"paragraphs\":").append(paragraphs).append("}");
+        return sb.toString();
+    }
+
+    /**
+     * Get statistics on (virtual) collections.
+     *
+     * @param json JSON-LD string with potential meta filters.
+     */
+    public String getStatistics(String json) {
+        qlog.trace(json);
+
+        if (this.index == null) {
+            return "{\"documents\" : -1, error\" : \"No index given\" }";
+        }
+
+        // Create Virtual collection from json search
+        KrillCollection kc = new KrillCollection(json);
+
+        // Set index
+        kc.setIndex(this.index);
+
+        long docs = 0,
+                tokens = 0,
+                sentences = 0,
+                paragraphs = 0;
+
+        // Get numbers from index (currently slow)
+        try {
+            docs = kc.numberOf("documents");
+            tokens = kc.numberOf("tokens");
+            sentences = kc.numberOf("sentences");
+            paragraphs = kc.numberOf("paragraphs");
+        }catch (IOException e) {
+            e.printStackTrace();
+        }
+
+
+	/*
+    KorAPLogger.ERROR_LOGGER.error("Unable to retrieve statistics: {}", e.getMessage());
+	*/
+
+        // Build json response
+        StringBuilder sb = new StringBuilder("{");
+        sb.append("\"documents\":").append(docs).append(",\"tokens\":")
+                .append(tokens).append(",\"sentences\":").append(sentences)
+                .append(",\"paragraphs\":").append(paragraphs).append("}");
+        return sb.toString();
+    }
+
+    /**
+     * Get set relations on field terms of (virtual) collections.
+     *
+     * @param json JSON-LD string with potential meta filters.
+     */
+    public String getTermRelation(String json, String field) {
+        qlog.trace(json);
+
+        if (this.index == null) {
+            return "{\"documents\" : -1, \"error\" : \"No index given\" }";
+        }
+
+        // Create Virtula VCollection from json search
+        KrillCollection kc = new KrillCollection(json);
+
+        // Set index
+        kc.setIndex(this.index);
+        long v = 0L;
+        try {
+            v = kc.numberOf("documents");
+        }catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        try {
+            // Get term relations as a json string
+            return kc.getTermRelationJSON(field);
+        }catch (IOException e) {
+            KorAPLogger.ERROR_LOGGER
+                    .error("Unable to retrieve term relations: {}",
+                            e.getMessage());
+            return "{\"documents\" : -1, \"error\" : \"IO error\" }";
+        }
+    }
+
+    public String getMatchId(String type, String docid, String tofrom) {
+        return new StringBuilder().append("contains-").append(type).append("!")
+                .append(type).append("_").append(docid).append("-")
+                .append(tofrom).toString();
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/TRACE.java b/src/main/java/de/ids_mannheim/korap/web/TRACE.java
new file mode 100644
index 0000000..e33089d
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/TRACE.java
@@ -0,0 +1,17 @@
+package de.ids_mannheim.korap.web;
+
+import javax.ws.rs.HttpMethod;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @author hanl
+ * @date 03/07/2014
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+@HttpMethod("TRACE")
+public @interface TRACE {
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/service/LightService.java b/src/main/java/de/ids_mannheim/korap/web/service/LightService.java
new file mode 100644
index 0000000..bd1be22
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/service/LightService.java
@@ -0,0 +1,346 @@
+package de.ids_mannheim.korap.web.service;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+import de.ids_mannheim.korap.config.BeanConfiguration;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.exceptions.KorAPException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.graphdb.collo.ColloQuery;
+import de.ids_mannheim.korap.graphdb.collo.Dependency;
+import de.ids_mannheim.korap.query.serialize.IdWriter;
+import de.ids_mannheim.korap.query.serialize.MetaQueryBuilder;
+import de.ids_mannheim.korap.query.serialize.QuerySerializer;
+import de.ids_mannheim.korap.utils.CollectionQueryBuilder;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.KorAPLogger;
+import de.ids_mannheim.korap.utils.StringUtils;
+import de.ids_mannheim.korap.web.ClientsHandler;
+import de.ids_mannheim.korap.web.SearchLucene;
+import de.ids_mannheim.korap.web.TRACE;
+import de.ids_mannheim.korap.web.utils.KustvaktResponseHandler;
+import org.slf4j.Logger;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.*;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author hanl
+ * @date 29/01/2014
+ */
+@Path("v0.1" + "/")
+@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
+public class LightService {
+
+    private static Logger jlog = KorAPLogger.initiate(LightService.class);
+
+    private SearchLucene searchLucene;
+    private ClientsHandler graphDBhandler;
+
+    public LightService() {
+        this.searchLucene = new SearchLucene(
+                BeanConfiguration.getConfiguration().getIndexDir());
+        UriBuilder builder = UriBuilder.fromUri("http://10.0.10.13").port(9997);
+        this.graphDBhandler = new ClientsHandler(builder.build());
+    }
+
+    /**
+     * @param properties
+     * @param sfs
+     * @param limit
+     * @param query
+     * @param ql
+     * @param context
+     * @param foundry
+     * @param wPaths
+     * @return
+     */
+    //todo: add entry point where a json collection can be injected as well
+    @GET
+    @Path("colloc")
+    public Response getCollocationsAll(@QueryParam("props") String properties,
+            @QueryParam("sfskip") Integer sfs,
+            @QueryParam("sflimit") Integer limit, @QueryParam("q") String query,
+            @QueryParam("ql") String ql, @QueryParam("context") Integer context,
+            @QueryParam("foundry") String foundry,
+            @QueryParam("paths") Boolean wPaths) {
+        ColloQuery.ColloQueryBuilder builder;
+        String result;
+        try {
+            builder = new ColloQuery.ColloQueryBuilder();
+            QuerySerializer s = new QuerySerializer().setQuery(query, ql);
+            //todo: fix collectionbuilder
+            //        s.setDeprCollection(builder);
+            IdWriter writer = new IdWriter(s.toJSON()).process();
+            List collprops = JsonUtils.convertToList(properties);
+
+            if (context != null)
+                builder.context(context).collocateProps(collprops);
+            if (limit != null)
+                builder.surfaceLimit(limit);
+            if (sfs != null)
+                builder.surfaceSkip(sfs);
+            if (foundry != null)
+                builder.foundry(foundry);
+            builder.nodeQueryJS(writer.toJSON())
+                    .dep(new ArrayList<Dependency>()).withPaths(wPaths);
+
+            result = graphDBhandler
+                    .getResponse("distCollo", "q", builder.build().toJSON());
+        }catch (KorAPException e) {
+            throw KustvaktResponseHandler.throwit(e);
+        }catch (JsonProcessingException e) {
+            throw KustvaktResponseHandler.throwit(StatusCodes.ILLEGAL_ARGUMENT);
+        }
+        return Response.ok(result).build();
+    }
+
+    @POST
+    @Path("colloc")
+    public Response getCollocatioBase(@QueryParam("q") String query) {
+        String result;
+        try {
+            result = graphDBhandler.getResponse("distCollo", "q", query);
+        }catch (KorAPException e) {
+            throw KustvaktResponseHandler.throwit(e);
+        }
+        return Response.ok(result).build();
+    }
+
+    public Response postMatchFavorite() {
+        return Response.ok().build();
+    }
+
+    @TRACE
+    @Path("search")
+    public Response buildQuery(@QueryParam("q") String q,
+            @QueryParam("ql") String ql, @QueryParam("v") String v,
+            @QueryParam("context") String context,
+            @QueryParam("cutoff") Boolean cutoff,
+            @QueryParam("count") Integer pageLength,
+            @QueryParam("offset") Integer pageIndex,
+            @QueryParam("page") Integer startPage,
+            @QueryParam("ref") String reference, @QueryParam("cq") String cq) {
+        QuerySerializer ss = new QuerySerializer().setQuery(q, ql, v);
+
+        MetaQueryBuilder meta = new MetaQueryBuilder();
+        if (pageIndex != null)
+            meta.addEntry("startIndex", pageIndex);
+        if (pageIndex == null && startPage != null)
+            meta.addEntry("startPage", startPage);
+        if (pageLength != null)
+            meta.addEntry("count", pageLength);
+        if (context != null)
+            meta.setSpanContext(context);
+        meta.addEntry("cutOff", cutoff);
+        ss.addMeta(meta);
+        //todo: test this
+        ss.setCollection(cq);
+        return Response.ok(ss.toJSON()).build();
+    }
+
+    @POST
+    @Path("search")
+    public Response queryRaw(@QueryParam("engine") String engine,
+            String jsonld) {
+        KustvaktConfiguration.BACKENDS eng = BeanConfiguration
+                .getConfiguration().chooseBackend(engine);
+
+        // todo: should be possible to add the meta part to the query serialization
+        jlog.info("Serialized search: {}", jsonld);
+
+        // todo: Security Parsing and rewrite
+        // fixme: to use the systemarchitecture pointcut thingis, searchlucene must be injected via
+        String result = searchLucene.search(jsonld);
+        KorAPLogger.QUERY_LOGGER.trace("The result set: {}", result);
+        return Response.ok(result).build();
+    }
+
+    @GET
+    @Path("search")
+    public Response searchbyNameAll(@Context SecurityContext securityContext,
+            @QueryParam("q") String q, @QueryParam("ql") String ql,
+            @QueryParam("v") String v, @QueryParam("context") String ctx,
+            @QueryParam("cutoff") Boolean cutoff,
+            @QueryParam("count") Integer pageLength,
+            @QueryParam("offset") Integer pageIndex,
+            @QueryParam("page") Integer pageInteger,
+            @QueryParam("cq") String cq, @QueryParam("engine") String engine) {
+        KustvaktConfiguration.BACKENDS eng = BeanConfiguration
+                .getConfiguration().chooseBackend(engine);
+
+        String result;
+        QuerySerializer serializer = new QuerySerializer().setQuery(q, ql, v);
+        MetaQueryBuilder meta = new MetaQueryBuilder();
+        meta.fillMeta(pageIndex, pageInteger, pageLength, ctx, cutoff);
+        // fixme: should only apply to CQL queries per default!
+        //        meta.addEntry("itemsPerResource", 1);
+        serializer.addMeta(meta);
+
+        // policy rewrite!
+        String query = serializer.toJSON();
+        jlog.info("the serialized query {}", query);
+
+        if (eng.equals(KustvaktConfiguration.BACKENDS.NEO4J)) {
+            MultivaluedMap map = new MultivaluedMapImpl();
+            map.add("q", query);
+            map.add("count", String.valueOf(pageLength));
+            map.add("lctxs",
+                    String.valueOf(meta.getSpanContext().getLeft_size()));
+            map.add("rctxs",
+                    String.valueOf(meta.getSpanContext().getRight_size()));
+            try {
+                result = this.graphDBhandler.getResponse(map, "distKwic");
+            }catch (KorAPException e) {
+                throw KustvaktResponseHandler.throwit(e);
+            }
+        }else
+            result = searchLucene.search(query);
+        KorAPLogger.QUERY_LOGGER.trace("The result set: {}", result);
+        return Response.ok(result).build();
+    }
+
+    /**
+     * param context will be like this: context: "3-t,2-c"
+     * <p/>
+     * id does not have to be an integer. name is also possible, in which case a type reference is required
+     *
+     * @param query
+     * @param ql
+     * @param v
+     * @param ctx
+     * @param cutoff
+     * @param pageLength
+     * @param pageIndex
+     * @param pageInteger
+     * @param id
+     * @param type
+     * @param cq
+     * @param raw
+     * @param engine
+     * @return
+     */
+    //fixme: does not use policyrewrite!
+    @GET
+    @Path("/{type}/{id}/search")
+    public Response searchbyName(@QueryParam("q") String query,
+            @QueryParam("ql") String ql, @QueryParam("v") String v,
+            @QueryParam("context") String ctx,
+            @QueryParam("cutoff") Boolean cutoff,
+            @QueryParam("count") Integer pageLength,
+            @QueryParam("offset") Integer pageIndex,
+            @QueryParam("page") Integer pageInteger, @PathParam("id") String id,
+            @PathParam("type") String type, @QueryParam("cq") String cq,
+            @QueryParam("raw") Boolean raw,
+            @QueryParam("engine") String engine) {
+        // ref is a virtual collection id!
+        KustvaktConfiguration.BACKENDS eng = BeanConfiguration
+                .getConfiguration().chooseBackend(engine);
+        raw = raw == null ? false : raw;
+        MetaQueryBuilder meta = new MetaQueryBuilder();
+        if (!raw) {
+            QuerySerializer s = new QuerySerializer().setQuery(query, ql, v);
+            meta.fillMeta(pageIndex, pageInteger, pageLength, ctx, cutoff);
+            // should only apply to CQL queries
+            //                meta.addEntry("itemsPerResource", 1);
+            s.addMeta(meta);
+            query = s.toJSON();
+        }
+        String result;
+        try {
+            if (eng.equals(KustvaktConfiguration.BACKENDS.NEO4J)) {
+                if (raw)
+                    throw KustvaktResponseHandler
+                            .throwit(StatusCodes.ILLEGAL_ARGUMENT,
+                                    "raw not supported!", null);
+                MultivaluedMap map = new MultivaluedMapImpl();
+                map.add("q", query);
+                map.add("count", String.valueOf(pageLength));
+                map.add("lctxs",
+                        String.valueOf(meta.getSpanContext().getLeft_size()));
+                map.add("rctxs",
+                        String.valueOf(meta.getSpanContext().getRight_size()));
+                result = this.graphDBhandler.getResponse(map, "distKwic");
+            }else
+                result = searchLucene.search(query);
+
+        }catch (Exception e) {
+            KorAPLogger.ERROR_LOGGER
+                    .error("Exception for serialized query: " + query, e);
+            throw KustvaktResponseHandler.throwit(500, e.getMessage(), null);
+        }
+
+        KorAPLogger.QUERY_LOGGER.trace("The result set: {}", result);
+        return Response.ok(result).build();
+    }
+
+    //todo: switch to new serialization
+    @POST
+    @Path("stats")
+    public Response getStats(String json) {
+        CollectionQueryBuilder builder = new CollectionQueryBuilder();
+        builder.addResource(json);
+
+        String stats = searchLucene.getStatistics(builder.toCollections());
+        if (stats.contains("-1"))
+            throw KustvaktResponseHandler.throwit(StatusCodes.EMPTY_RESULTS);
+
+        return Response.ok(stats).build();
+    }
+
+    //fixme: only allowed for corpus?!
+    @GET
+    @Path("/corpus/{id}/{docid}/{rest}/matchInfo")
+    public Response getMatchInfo(@PathParam("id") String id,
+            @PathParam("docid") String docid, @PathParam("rest") String rest,
+            @QueryParam("foundry") Set<String> foundries,
+            @QueryParam("layer") Set<String> layers,
+            @QueryParam("spans") Boolean spans) {
+        spans = spans != null ? spans : false;
+        String matchid = searchLucene.getMatchId(id, docid, rest);
+
+        if (layers == null || layers.isEmpty())
+            layers = new HashSet<>();
+
+        boolean match_only = foundries == null || foundries.isEmpty();
+
+        String results;
+        // fixme: checks for policy matching
+        // fixme: currently disabled, due to mishab in foundry/layer spec
+        // fixme:
+        if (foundries != null && foundries.size() > 1000) {
+            Set<String> f_list = new HashSet<>();
+            Set<String> l_list = new HashSet<>();
+
+            for (String spl : new ArrayList<>(foundries)) {
+
+                String[] sep = StringUtils.splitAnnotations(spl);
+                if (spl != null) {
+                    f_list.add(sep[0]);
+                    l_list.add(sep[1]);
+                }
+                results = searchLucene
+                        .getMatch(matchid, new ArrayList<>(f_list),
+                                new ArrayList<>(l_list), spans, false, true);
+
+            }
+        }
+        try {
+            if (!match_only)
+                results = searchLucene
+                        .getMatch(matchid, new ArrayList<>(foundries),
+                                new ArrayList<>(layers), spans, false, true);
+            else
+                results = searchLucene.getMatch(matchid);
+        }catch (Exception e) {
+            KorAPLogger.ERROR_LOGGER.error("Exception encountered!", e);
+            throw KustvaktResponseHandler
+                    .throwit(StatusCodes.ILLEGAL_ARGUMENT, e.getMessage(), "");
+        }
+        return Response.ok(results).build();
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/utils/FormWrapper.java b/src/main/java/de/ids_mannheim/korap/web/utils/FormWrapper.java
new file mode 100644
index 0000000..b1dad85
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/utils/FormWrapper.java
@@ -0,0 +1,35 @@
+package de.ids_mannheim.korap.web.utils;
+
+import javax.ws.rs.core.MultivaluedMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author hanl
+ * @date 18/05/2015
+ * Helper class to wrapp multivaluedmap into a hashmap. Depending on the strict parameter,
+ * list values are retained in the resulting wrapper map.
+ */
+public class FormWrapper extends HashMap<String, Object> {
+
+    public FormWrapper(MultivaluedMap form, boolean strict) {
+        super(toMap(form, strict));
+    }
+
+    public FormWrapper(MultivaluedMap form) {
+        super(toMap(form, true));
+    }
+
+    private static HashMap<String, Object> toMap(MultivaluedMap<String, Object> form,
+            boolean strict) {
+        HashMap<String, Object> map = new HashMap<>();
+        for (Map.Entry<String, List<Object>> e : form.entrySet()) {
+            if (e.getValue().size() == 1)
+                map.put(e.getKey(), e.getValue().get(0));
+            else if (!strict)
+                map.put(e.getKey(), e.getValue());
+        }
+        return map;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/utils/HTMLBuilder.java b/src/main/java/de/ids_mannheim/korap/web/utils/HTMLBuilder.java
new file mode 100644
index 0000000..f6da418
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/utils/HTMLBuilder.java
@@ -0,0 +1,56 @@
+package de.ids_mannheim.korap.web.utils;
+
+/**
+ * @author hanl
+ * @date 12/04/2014
+ */
+public class HTMLBuilder {
+
+    private StringBuilder html;
+    private StringBuilder body;
+    private String bodyAttr;
+
+    public HTMLBuilder() {
+        html = new StringBuilder();
+        body = new StringBuilder();
+        bodyAttr = "";
+        html.append("<html>");
+    }
+
+
+    public void addHeader(String header, int h) {
+        html.append("<h" + h + ">");
+        html.append(header);
+        html.append("</h" + h + ">");
+    }
+
+    public void addToBody(String body) {
+        this.body.append(body);
+    }
+
+    public void addToBody(String body, String attributes) {
+        this.body.append(body);
+        bodyAttr = attributes;
+    }
+
+    public String build() {
+        if (bodyAttr.isEmpty())
+            html.append("<body>");
+        else {
+            html.append("<body ");
+            html.append(bodyAttr);
+            html.append(">");
+        }
+
+        html.append(body);
+        html.append("</body>");
+        html.append("</html>");
+        return html.toString();
+    }
+
+
+    @Override
+    public String toString() {
+        return build();
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/utils/KustvaktResponseHandler.java b/src/main/java/de/ids_mannheim/korap/web/utils/KustvaktResponseHandler.java
new file mode 100644
index 0000000..f99a520
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/utils/KustvaktResponseHandler.java
@@ -0,0 +1,77 @@
+package de.ids_mannheim.korap.web.utils;
+
+import de.ids_mannheim.korap.auditing.AuditRecord;
+import de.ids_mannheim.korap.config.BeanConfiguration;
+import de.ids_mannheim.korap.exceptions.BaseException;
+import de.ids_mannheim.korap.exceptions.KorAPException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.interfaces.AuditingIface;
+import de.ids_mannheim.korap.response.Notifications;
+
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+/**
+ * @author hanl
+ * @date 29/01/2014
+ */
+public class KustvaktResponseHandler {
+
+    private static AuditingIface auditing = BeanConfiguration
+            .getAuditingProvider();
+
+    private static void register(List<AuditRecord> records) {
+        if (auditing != null && !records.isEmpty())
+            auditing.audit(records);
+    }
+
+    public static WebApplicationException throwit(BaseException e) {
+        //fixme: ??!
+        e.printStackTrace();
+        return new WebApplicationException(
+                Response.status(Response.Status.BAD_REQUEST)
+                        .entity(buildNotification(e)).build());
+    }
+
+    @Deprecated
+    public static WebApplicationException throwit(int code) {
+        KorAPException e = new KorAPException(code);
+        return new WebApplicationException(
+                Response.status(Response.Status.OK).entity(buildNotification(e))
+                        .build());
+    }
+
+    @Deprecated
+    public static WebApplicationException throwit(int code, String message,
+            String entity) {
+        KorAPException e = new KorAPException(code, message, entity);
+        return new WebApplicationException(
+                Response.status(Response.Status.OK).entity(buildNotification(e))
+                        .build());
+    }
+
+    private static String buildNotification(BaseException e) {
+        KustvaktResponseHandler.register(e.getRecords());
+        return buildNotification(e.getStatusCode(), e.getMessage(),
+                e.getEntity());
+    }
+
+    private static String buildNotification(int code, String message,
+            String entity) {
+        Notifications notif = new Notifications();
+        notif.addError(code, message, entity);
+        return notif.toJsonString() + "\n";
+    }
+
+    public static WebApplicationException throwAuthenticationException() {
+        KorAPException e = new KorAPException(StatusCodes.BAD_CREDENTIALS);
+        return new WebApplicationException(
+                Response.status(Response.Status.UNAUTHORIZED)
+                        .header(HttpHeaders.WWW_AUTHENTICATE,
+                                "Basic realm=Kustvakt Authentication Service")
+                        .entity(buildNotification(e)).build());
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/utils/LocaleProvider.java b/src/main/java/de/ids_mannheim/korap/web/utils/LocaleProvider.java
new file mode 100644
index 0000000..47e8e03
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/utils/LocaleProvider.java
@@ -0,0 +1,45 @@
+package de.ids_mannheim.korap.web.utils;
+
+import com.sun.jersey.api.core.HttpContext;
+import com.sun.jersey.core.spi.component.ComponentContext;
+import com.sun.jersey.core.spi.component.ComponentScope;
+import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable;
+import com.sun.jersey.spi.inject.Injectable;
+import com.sun.jersey.spi.inject.InjectableProvider;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.ext.Provider;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * @author hanl
+ * @date 04/02/2014
+ */
+@Provider
+public class LocaleProvider
+        extends AbstractHttpContextInjectable<Locale>
+        implements InjectableProvider<Context, Type> {
+
+    @Override
+    public Locale getValue(HttpContext httpContext) {
+        final List<Locale> locales = httpContext.getRequest().getAcceptableLanguages();
+        if (locales.isEmpty())
+            return Locale.US;
+        return locales.get(0);
+    }
+
+    @Override
+    public ComponentScope getScope() {
+        return ComponentScope.PerRequest;
+    }
+
+    @Override
+    public Injectable getInjectable(ComponentContext ic, Context context, Type type) {
+        if (type.equals(Locale.class))
+            return this;
+        return null;
+    }
+}
+
diff --git a/src/main/resources/.DS_Store b/src/main/resources/.DS_Store
new file mode 100644
index 0000000..5008ddf
--- /dev/null
+++ b/src/main/resources/.DS_Store
Binary files differ
diff --git a/src/main/resources/changelog b/src/main/resources/changelog
new file mode 100644
index 0000000..50f47a7
--- /dev/null
+++ b/src/main/resources/changelog
@@ -0,0 +1,6 @@
+=== CHANGELOG FILE ===
+
+05/05/2015
+    - ADD: rest test suite for user service
+    - MOD: setup parameter modification
+    - ADD: oauth2 client unique constraint
diff --git a/src/main/resources/default-config.xml b/src/main/resources/default-config.xml
new file mode 100644
index 0000000..7e3c557
--- /dev/null
+++ b/src/main/resources/default-config.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xmlns="http://www.springframework.org/schema/beans"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                            http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
+                            http://www.springframework.org/schema/util
+                            http://www.springframework.org/schema/util/spring-util-4.0.xsd">
+
+
+    <!--<context:property-placeholder location="classpath:kustvakt.conf"/>-->
+
+    <util:properties id="props" location="classpath:kustvakt.conf"/>
+
+    <bean id="auditingProvider"
+          class="de.ids_mannheim.korap.interfaces.defaults.DefaultAuditing">
+    </bean>
+
+    <bean id="config"
+          class="de.ids_mannheim.korap.config.KustvaktConfiguration">
+        <property name="properties" ref="props"/>
+    </bean>
+
+    <bean name="encryption"
+          class="de.ids_mannheim.korap.interfaces.defaults.DefaultEncryption">
+    </bean>
+</beans>
\ No newline at end of file
diff --git a/src/test/.DS_Store b/src/test/.DS_Store
new file mode 100644
index 0000000..c7f4961
--- /dev/null
+++ b/src/test/.DS_Store
Binary files differ
diff --git a/src/test/java/.DS_Store b/src/test/java/.DS_Store
new file mode 100644
index 0000000..9462fb8
--- /dev/null
+++ b/src/test/java/.DS_Store
Binary files differ