added services and filters
diff --git a/src/main/java/de/ids_mannheim/korap/config/Scopes.java b/src/main/java/de/ids_mannheim/korap/config/Scopes.java
new file mode 100644
index 0000000..9c4ef7a
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/config/Scopes.java
@@ -0,0 +1,70 @@
+package de.ids_mannheim.korap.config;
+
+import de.ids_mannheim.korap.user.Attributes;
+import de.ids_mannheim.korap.user.UserDetails;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author hanl
+ * @date 09/12/2014
+ */
+public class Scopes {
+
+    public enum Scope {
+        openid, profile, email, queries, account, preferences, search
+
+    }
+
+    private static final String[] profile = { Attributes.EMAIL,
+            Attributes.FIRSTNAME, Attributes.LASTNAME, Attributes.INSTITUTION,
+            Attributes.ADDRESS, Attributes.PHONE, Attributes.GENDER,
+            Attributes.COUNTRY };
+
+    private static final String[] OPENID_CONNECT = { Scope.profile.toString() };
+
+    private static final Enum[] SERVICE_DEFAULTS = { Scope.account,
+            Scope.preferences, Scope.search, Scope.queries };
+
+    public static Map<String, Object> getProfileScopes(
+            Map<String, Object> values) {
+        Map<String, Object> r = new HashMap<>();
+        for (String key : profile) {
+            Object v = values.get(key);
+            if (v != null)
+                r.put(key, v);
+        }
+        return r;
+    }
+
+    /**
+     * expects space separated values
+     *
+     * @param scopes
+     * @return
+     */
+    //todo: test
+    public static Enum[] mapScopes(String scopes) {
+        List<Enum> s = new ArrayList<>();
+        for (String value : scopes.split(" "))
+            s.add(Scope.valueOf(value.toLowerCase()));
+        return (Enum[]) s.toArray(new Enum[s.size()]);
+    }
+
+    public static Map<String, Object> mapOpenIDConnectScopes(String scopes,
+            UserDetails details) {
+        Map<String, Object> m = new HashMap<>();
+        if (scopes != null && !scopes.isEmpty()) {
+            scopes = scopes.toLowerCase();
+            if (scopes.contains(Scope.email.toString()))
+                m.put(Attributes.EMAIL, details.getEmail());
+            if (scopes.contains(Scope.profile.toString()))
+                m.putAll(Scopes.getProfileScopes(details.toMap()));
+        }
+        return m;
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/PolicyHandlerIface.java b/src/main/java/de/ids_mannheim/korap/interfaces/PolicyHandlerIface.java
new file mode 100644
index 0000000..122970f
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/PolicyHandlerIface.java
@@ -0,0 +1,175 @@
+package de.ids_mannheim.korap.interfaces;
+
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.resources.KustvaktResource;
+import de.ids_mannheim.korap.security.Parameter;
+import de.ids_mannheim.korap.security.PolicyCondition;
+import de.ids_mannheim.korap.security.SecurityPolicy;
+import de.ids_mannheim.korap.user.User;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * User: hanl
+ * Date: 10/31/13
+ * Time: 3:01 PM
+ */
+// todo: this still applicable?
+@Transactional
+public interface PolicyHandlerIface {
+
+    /**
+     * @param policy
+     * @param user
+     * @throws KustvaktException
+     */
+    int createPolicy(SecurityPolicy policy, User user) throws KustvaktException;
+
+    /**
+     * @param target
+     * @param user
+     * @param perm
+     * @return
+     */
+    List<SecurityPolicy>[] getPolicies(Integer target, User user, Byte perm);
+
+    /**
+     * @param policy
+     * @return
+     * @throws KustvaktException
+     */
+    void mapConstraints(SecurityPolicy policy) throws KustvaktException;
+
+    /**
+     * @param target
+     * @param user
+     * @param perm
+     * @return
+     */
+    List<SecurityPolicy>[] getPolicies(String target, User user, Byte perm);
+
+    /**
+     * @param path
+     * @param user
+     * @param perm
+     * @return
+     */
+    List<SecurityPolicy>[] findPolicies(String path, User user, Byte perm);
+
+    /**
+     * @param path
+     * @param user
+     * @param b
+     * @param clazz
+     * @return
+     * @throws KustvaktException
+     */
+    List<KustvaktResource.Container> getDescending(String path, User user,
+            Byte b, Class<? extends KustvaktResource> clazz)
+            throws KustvaktException;
+
+    /**
+     * @param path
+     * @param user
+     * @param b
+     * @param clazz
+     * @return
+     * @throws KustvaktException
+     */
+    List<KustvaktResource.Container> getAscending(String path, User user,
+            Byte b, Class<? extends KustvaktResource> clazz)
+            throws KustvaktException;
+
+    /**
+     * @param id
+     * @param user
+     */
+    //todo: test
+    void deleteResourcePolicies(String id, User user) throws KustvaktException;
+
+    /**
+     * @param policy
+     * @param user
+     * @return
+     * @throws KustvaktException
+     */
+    int deletePolicy(SecurityPolicy policy, User user) throws KustvaktException;
+
+    /**
+     * @param policy
+     * @param user
+     * @return
+     * @throws KustvaktException
+     */
+    int updatePolicy(SecurityPolicy policy, User user) throws KustvaktException;
+
+    /**
+     * checks if a similar policy already exists
+     *
+     * @param policy
+     * @return
+     * @throws KustvaktException
+     */
+    int checkPolicy(SecurityPolicy policy, User user) throws KustvaktException;
+
+    /**
+     * @param user
+     * @param name
+     * @param owner
+     * @return
+     * @throws KustvaktException
+     */
+    int matchCondition(User user, String name, boolean owner)
+            throws KustvaktException;
+
+    /**
+     * @param username
+     * @param condition
+     * @param admin
+     * @return
+     * @throws KustvaktException
+     */
+    int addToCondition(String username, PolicyCondition condition,
+            boolean admin) throws KustvaktException;
+
+    /**
+     * @param usernames
+     * @param condition
+     * @param status
+     * @throws KustvaktException
+     */
+
+    //todo: add a handler user id, to skip the matching step in the corpusmanagement segment!
+    int[] addToCondition(List<String> usernames, PolicyCondition condition,
+            boolean status) throws KustvaktException;
+
+    /**
+     * @param usernames
+     * @param condition
+     * @throws KustvaktException
+     */
+    void removeFromCondition(List<String> usernames, PolicyCondition condition)
+            throws KustvaktException;
+
+    /**
+     * @param param
+     * @throws KustvaktException
+     */
+    void createParamBinding(Parameter param) throws KustvaktException;
+
+    /**
+     * @param condition
+     * @return
+     * @throws KustvaktException
+     */
+    List<String> getUsersFromCondition(PolicyCondition condition)
+            throws KustvaktException;
+
+    /**
+     * @param policy
+     * @throws KustvaktException
+     */
+    void removeParamBinding(SecurityPolicy policy) throws KustvaktException;
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteNode.java b/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteNode.java
deleted file mode 100644
index cb0a06c..0000000
--- a/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteNode.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package de.ids_mannheim.korap.resource.rewrite;
-
-/**
- * node rewrites get injected typically object nodes that are subject to altering.
- * Be aware that node rewrites are processed before query rewrites. Thus query rewrite may override previous node rewrites
- *
- * {@link de.ids_mannheim.korap.resource.rewrite.RewriteNode} rewrite support the deletion of the respective node by simply setting the node invalid in KoralNode
- *
- * @author hanl
- * @date 03/07/2015
- */
-public abstract class RewriteNode extends RewriteTask {
-
-    public RewriteNode() {
-        super();
-    }
-
-
-
-}
diff --git a/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteQuery.java b/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteQuery.java
deleted file mode 100644
index 99bfb34..0000000
--- a/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteQuery.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package de.ids_mannheim.korap.resource.rewrite;
-
-/**
- * query rewrites get injected the entire query from root containing all child nodes
- * <p/>
- * {@link de.ids_mannheim.korap.resource.rewrite.RewriteQuery} does not allow the deletion of the root node or subnode through KoralNode.
- * The {@link de.ids_mannheim.korap.resource.rewrite.RewriteHandler} will igonore respecitve invalid requests
- *
- * @author hanl
- * @date 03/07/2015
- */
-public abstract class RewriteQuery extends RewriteTask {
-
-    public RewriteQuery() {
-        super();
-    }
-
-}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder2.java b/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder2.java
deleted file mode 100644
index 1058f5b..0000000
--- a/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder2.java
+++ /dev/null
@@ -1,86 +0,0 @@
-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/PropertyReader.java b/src/main/java/de/ids_mannheim/korap/utils/PropertyReader.java
new file mode 100644
index 0000000..292b019
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/PropertyReader.java
@@ -0,0 +1,36 @@
+package de.ids_mannheim.korap.utils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author hanl
+ * @date 27/09/2014
+ */
+public abstract class PropertyReader {
+
+
+    protected Map<String, Properties> read(String path) throws IOException {
+        Map<String, Properties> res = new HashMap<>();
+        Properties s = new Properties();
+        s.load(new FileInputStream(new File(path)));
+        for (Map.Entry<Object, Object> e : s.entrySet()) {
+            String key = e.getKey().toString().split("\\.")[0];
+            Properties in = res.get(key);
+            if (in == null) {
+                in = new Properties();
+                res.put(key, in);
+            }
+            in.setProperty(e.getKey().toString(), e.getValue().toString());
+        }
+        return res;
+    }
+
+
+    public abstract void load();
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/UserPropertyReader.java b/src/main/java/de/ids_mannheim/korap/utils/UserPropertyReader.java
new file mode 100644
index 0000000..b506d22
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/UserPropertyReader.java
@@ -0,0 +1,111 @@
+package de.ids_mannheim.korap.utils;
+
+import de.ids_mannheim.korap.config.BeanConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.interfaces.EncryptionIface;
+import de.ids_mannheim.korap.interfaces.EntityHandlerIface;
+import de.ids_mannheim.korap.user.KorAPUser;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.user.UserDetails;
+import de.ids_mannheim.korap.user.UserSettings;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author hanl
+ * @date 30/09/2014
+ */
+public class UserPropertyReader extends PropertyReader {
+
+    private Map<String, Properties> props;
+    private String path;
+    private EntityHandlerIface iface;
+    private EncryptionIface crypto;
+    private static Logger jlog = KustvaktLogger
+            .initiate(UserPropertyReader.class);
+
+    public UserPropertyReader(String path) {
+        this.path = path;
+        this.iface = BeanConfiguration.getBeans().getUserDBHandler();
+        this.crypto = BeanConfiguration.getBeans().getEncryption();
+    }
+
+    @Override
+    public void load() {
+        try {
+            props = super.read(this.path);
+            for (Map.Entry<String, Properties> e : props.entrySet()) {
+                try {
+                    createUser(e.getKey(), e.getValue());
+                }catch (KustvaktException ex) {
+                    jlog.error("KorAP-Exception: {} for user {}",
+                            ex.getStatusCode(), e.getKey());
+                }
+            }
+            iface.createAccount(User.UserFactory.getDemoUser());
+        }catch (IOException e) {
+            jlog.error("Could not read from path {}", path);
+        }catch (KustvaktException e) {
+            jlog.error("KorAP-Exception: {}", e.getStatusCode());
+        }
+    }
+
+    private User createUser(String username, Properties p)
+            throws KustvaktException {
+        KorAPUser user;
+        if (username.equals(User.ADMINISTRATOR_NAME)) {
+            user = User.UserFactory.getAdmin();
+
+            String pass = p.getProperty(username + ".password", null);
+            if (pass == null)
+                throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT);
+
+            try {
+                pass = crypto.produceSecureHash(pass);
+            }catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+                throw new KustvaktException(StatusCodes.REQUEST_INVALID);
+            }
+            user.setPassword(pass);
+        }else {
+            user = User.UserFactory.getUser(username);
+            Map<String, Object> vals = new HashMap<>();
+            for (Map.Entry e : p.entrySet()) {
+                String key = e.getKey().toString().split("\\.", 2)[1];
+                vals.put(key, e.getValue().toString());
+            }
+            String pass = p.getProperty(username + ".password", null);
+            if (pass == null)
+                throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT);
+
+            try {
+                pass = crypto.produceSecureHash(pass);
+            }catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+                throw new KustvaktException(StatusCodes.REQUEST_INVALID);
+            }
+
+            user.setPassword(pass);
+            user.setAccountLocked(Boolean.valueOf(
+                    p.getProperty(username + ".lock", "false")));
+            user.setAccountCreation(TimeUtils.getNow().getMillis());
+
+            //todo: make sure uri is set to 0, so sql queries work with the null value
+//            user.setURIExpiration(0L);
+
+            UserDetails det = UserDetails.newDetailsIterator(vals);
+
+            user.setDetails(det);
+            user.setSettings(new UserSettings());
+        }
+        iface.createAccount(user);
+        jlog.info("successfully created account for user {}",
+                user.getUsername());
+        return user;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/filter/AuthFilter.java b/src/main/java/de/ids_mannheim/korap/web/filter/AuthFilter.java
new file mode 100644
index 0000000..8346e1f
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/filter/AuthFilter.java
@@ -0,0 +1,63 @@
+package de.ids_mannheim.korap.web.filter;
+
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerRequestFilter;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+import com.sun.jersey.spi.container.ResourceFilter;
+import de.ids_mannheim.korap.config.BeanConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.web.utils.KorAPContext;
+
+import javax.ws.rs.ext.Provider;
+
+/**
+ * @author hanl
+ * @date 28/01/2014
+ */
+@Provider
+public class AuthFilter implements ContainerRequestFilter, ResourceFilter {
+
+    private AuthenticationManagerIface userController;
+
+    public AuthFilter() {
+        this.userController = BeanConfiguration.getBeans()
+                .getAuthenticationManager();
+    }
+
+    @Override
+    public ContainerRequest filter(ContainerRequest request) {
+        String host = request.getHeaderValue(ContainerRequest.HOST);
+        String ua = request.getHeaderValue(ContainerRequest.USER_AGENT);
+
+        String authentication = request
+                .getHeaderValue(ContainerRequest.AUTHORIZATION);
+        if (authentication != null && !authentication.isEmpty()) {
+            TokenContext context;
+            try {
+                context = userController
+                        .getTokenStatus(authentication, host, ua);
+
+            }catch (KustvaktException e) {
+                throw BeanConfiguration.getResponseHandler().throwit(e);
+            }
+
+            if (context != null && (
+                    (context.isSecureRequired() && request.isSecure())
+                            | !context.isSecureRequired()))
+                request.setSecurityContext(new KorAPContext(context));
+        }
+        return request;
+    }
+
+    @Override
+    public ContainerRequestFilter getRequestFilter() {
+        return this;
+    }
+
+    @Override
+    public ContainerResponseFilter getResponseFilter() {
+        return null;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/filter/DefaultFilter.java b/src/main/java/de/ids_mannheim/korap/web/filter/DefaultFilter.java
new file mode 100644
index 0000000..a8ffe89
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/filter/DefaultFilter.java
@@ -0,0 +1,60 @@
+package de.ids_mannheim.korap.web.filter;
+
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerRequestFilter;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+import com.sun.jersey.spi.container.ResourceFilter;
+import de.ids_mannheim.korap.config.BeanConfiguration;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import de.ids_mannheim.korap.web.utils.KorAPContext;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Created by hanl on 7/15/14.
+ */
+@Provider
+public class DefaultFilter implements ContainerRequestFilter, ResourceFilter {
+
+    @Context
+    UriInfo info;
+
+    @Override
+    public ContainerRequest filter(ContainerRequest request) {
+        String host = request.getHeaderValue(ContainerRequest.HOST);
+        String ua = request.getHeaderValue(ContainerRequest.USER_AGENT);
+        String authentication = request
+                .getHeaderValue(ContainerRequest.AUTHORIZATION);
+
+        // means that this is the public service
+        if (authentication == null)
+            request.setSecurityContext(
+                    new KorAPContext(createShorterToken(host, ua)));
+        return request;
+    }
+
+    private TokenContext createShorterToken(String host, String agent) {
+        User demo = User.UserFactory.getDemoUser();
+        TokenContext c = new TokenContext(demo.getUsername());
+        c.setHostAddress(host);
+        c.setUserAgent(agent);
+        c.setExpirationTime(TimeUtils.plusSeconds(
+                BeanConfiguration.getBeans().getConfiguration()
+                        .getShortTokenTTL()).getMillis());
+        return c;
+    }
+
+    @Override
+    public ContainerRequestFilter getRequestFilter() {
+        return this;
+    }
+
+    @Override
+    public ContainerResponseFilter getResponseFilter() {
+        return null;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/service/AuthService.java b/src/main/java/de/ids_mannheim/korap/web/service/AuthService.java
new file mode 100644
index 0000000..5e76016
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/service/AuthService.java
@@ -0,0 +1,222 @@
+package de.ids_mannheim.korap.web.service;
+
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.config.BeanConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
+import de.ids_mannheim.korap.security.auth.BasicHttpAuth;
+import de.ids_mannheim.korap.user.Attributes;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.user.UserSettings;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.KustvaktLogger;
+import de.ids_mannheim.korap.utils.ServiceVersion;
+import de.ids_mannheim.korap.web.KustvaktServer;
+import de.ids_mannheim.korap.web.filter.AuthFilter;
+import de.ids_mannheim.korap.web.filter.DefaultFilter;
+import de.ids_mannheim.korap.web.filter.PiwikFilter;
+import org.slf4j.Logger;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+//import com.sun.xml.internal.messaging.saaj.util.Base64;
+
+/**
+ * @author hanl
+ * @date 24/01/2014
+ */
+@Path(KustvaktServer.API_VERSION + "/auth")
+@ResourceFilters({ PiwikFilter.class })
+@Produces(MediaType.TEXT_HTML + ";charset=utf-8")
+public class AuthService {
+
+    //todo: bootstrap function to transmit certain default configuration settings and examples (example user queries,
+    // default usersettings, etc.)
+    private static Logger jlog = KustvaktLogger.initiate(AuthService.class);
+
+    private AuthenticationManagerIface controller;
+    //    private SendMail mail;
+
+    public AuthService() {
+        this.controller = BeanConfiguration.getBeans()
+                .getAuthenticationManager();
+        //todo: replace with real property values
+        //        this.mail = new SendMail(ExtConfiguration.getMailProperties());
+    }
+
+    /**
+     * represents json string with data. All GUI clients can access this method to get certain default values
+     * --> security checks?
+     *
+     * @return String
+     */
+    @GET
+    @Path("bootstrap")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response bootstrap() {
+        Map m = new HashMap();
+        m.put("settings", new UserSettings().toObjectMap());
+        m.put("ql", BeanConfiguration.getBeans().getConfiguration()
+                .getQueryLanguages());
+        m.put("SortTypes", null); // types of sorting that are supported!
+        m.put("version", ServiceVersion.getAPIVersion());
+        return Response.ok(JsonUtils.toJSON(m)).build();
+    }
+
+    @GET
+    @Path("status")
+    @ResourceFilters({ AuthFilter.class, DefaultFilter.class })
+    public Response getStatus(@Context SecurityContext context,
+            @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+            @HeaderParam(ContainerRequest.HOST) String host,
+            @Context Locale locale) {
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        return Response.ok(ctx.toJSON()).build();
+    }
+
+    @GET
+    @Path("apiToken")
+    public Response requestAPIToken(@Context HttpHeaders headers,
+            @Context Locale locale,
+            @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+            @HeaderParam(ContainerRequest.HOST) String host,
+            @HeaderParam("referer-url") String referer,
+            @QueryParam("scope") String scope) {
+        List<String> auth = headers
+                .getRequestHeader(ContainerRequest.AUTHORIZATION);
+
+        if (auth == null)
+            throw BeanConfiguration.getResponseHandler()
+                    .throwit(StatusCodes.PERMISSION_DENIED);
+        String[] values = BasicHttpAuth.decode(auth.get(0));
+
+        // "Invalid syntax for username and password"
+        if (values == null)
+            throw BeanConfiguration.getResponseHandler()
+                    .throwit(StatusCodes.PERMISSION_DENIED);
+
+        if (values[0].equalsIgnoreCase("null") | values[1]
+                .equalsIgnoreCase("null"))
+            // is actual an invalid request
+            throw BeanConfiguration.getResponseHandler()
+                    .throwit(StatusCodes.REQUEST_INVALID);
+
+        Map<String, Object> attr = new HashMap<>();
+        if (scope != null && !scope.isEmpty())
+            attr.put(Attributes.SCOPES, scope);
+        attr.put(Attributes.HOST, host);
+        attr.put(Attributes.USER_AGENT, agent);
+        TokenContext context;
+        try {
+            User user = controller.authenticate(0, values[0], values[1], attr);
+            this.controller.getUserDetails(user);
+            context = controller.createTokenContext(user, attr,
+                    Attributes.API_AUTHENTICATION);
+        }catch (KustvaktException e) {
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+
+        return Response.ok(context.toResponse()).build();
+    }
+
+    // todo:
+    @Deprecated
+    @GET
+    @Path("refresh")
+    public Response refresh(@Context SecurityContext context,
+            @Context Locale locale) {
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        TokenContext newContext;
+
+        //        try {
+        //            newContext = controller.refresh(ctx);
+        //        }catch (KorAPException e) {
+        //            KorAPLogger.ERROR_LOGGER.error("Exception encountered!", e);
+        //            throw BeanConfiguration.getResponseHandler().throwit(e);
+        //        }
+        //        return Response.ok().entity(newContext.getToken()).build();
+        return null;
+    }
+
+    @GET
+    @Path("sessionToken")
+    public Response requestSession(@Context HttpHeaders headers,
+            @Context Locale locale,
+            @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+            @HeaderParam(ContainerRequest.HOST) String host) {
+        List<String> auth = headers
+                .getRequestHeader(ContainerRequest.AUTHORIZATION);
+
+        if (auth == null)
+            throw BeanConfiguration.getResponseHandler()
+                    .throwit(StatusCodes.PERMISSION_DENIED);
+
+        String[] values = BasicHttpAuth.decode(auth.get(0));
+        //        authentication = StringUtils.stripTokenType(authentication);
+        //        String[] values = new String(
+        //                DatatypeConverter.parseBase64Binary(authentication)).split(":");
+        //        String[] values = Base64.base64Decode(authentication).split(":");
+
+        // "Invalid syntax for username and password"
+        if (values == null)
+            throw BeanConfiguration.getResponseHandler()
+                    .throwit(StatusCodes.PERMISSION_DENIED);
+
+        if (values[0].equalsIgnoreCase("null") | values[1]
+                .equalsIgnoreCase("null"))
+            throw BeanConfiguration.getResponseHandler()
+                    .throwit(StatusCodes.REQUEST_INVALID);
+
+        Map<String, Object> attr = new HashMap<>();
+        attr.put(Attributes.HOST, host);
+        attr.put(Attributes.USER_AGENT, agent);
+        TokenContext context;
+        try {
+            User user = controller.authenticate(0, values[0], values[1], attr);
+            context = controller.createTokenContext(user, attr,
+                    Attributes.SESSION_AUTHENTICATION);
+        }catch (KustvaktException e) {
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+        return Response.ok().entity(context.toJSON()).build();
+    }
+
+    // fixme: security issues: setup shibboleth compatible authentication system
+    // todo: will be purged with token authentication --> shib is client side
+    @POST
+    @Consumes("application/x-www-form-urlencoded")
+    @Produces("application/json")
+    @Path("shibboleth")
+    public Response loginshib(@Context HttpHeaders headers,
+            @Context Locale locale,
+            @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+            @HeaderParam(ContainerRequest.HOST) String host,
+            @QueryParam("client_id") String client_id) {
+
+        // the shibfilter decrypted the values
+        // define default provider for returned access token strategy?!
+
+        Map<String, Object> attr = new HashMap<>();
+        attr.put(Attributes.HOST, host);
+        attr.put(Attributes.USER_AGENT, agent);
+
+        TokenContext context;
+
+        try {
+            // todo: distinguish type KorAP/Shibusers
+            User user = controller.authenticate(1, null, null, attr);
+            context = controller.createTokenContext(user, attr, null);
+        }catch (KustvaktException e) {
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+        return Response.ok().entity(context.toJSON()).build();
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/service/OAuthService.java b/src/main/java/de/ids_mannheim/korap/web/service/OAuthService.java
new file mode 100644
index 0000000..525eca9
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/service/OAuthService.java
@@ -0,0 +1,551 @@
+package de.ids_mannheim.korap.web.service;
+
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.config.*;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.handlers.OAuth2Handler;
+import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
+import de.ids_mannheim.korap.interfaces.EncryptionIface;
+import de.ids_mannheim.korap.user.Attributes;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.StringUtils;
+import de.ids_mannheim.korap.web.KustvaktServer;
+import de.ids_mannheim.korap.web.filter.AuthFilter;
+import de.ids_mannheim.korap.web.filter.BlockFilter;
+import de.ids_mannheim.korap.web.filter.DefaultFilter;
+import de.ids_mannheim.korap.web.filter.PiwikFilter;
+import de.ids_mannheim.korap.web.utils.FormRequestWrapper;
+import org.apache.oltu.oauth2.as.issuer.MD5Generator;
+import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
+import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
+import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
+import org.apache.oltu.oauth2.as.request.OAuthTokenRequest;
+import org.apache.oltu.oauth2.as.response.OAuthASResponse;
+import org.apache.oltu.oauth2.common.OAuth;
+import org.apache.oltu.oauth2.common.error.OAuthError;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
+import org.apache.oltu.oauth2.common.message.OAuthResponse;
+import org.apache.oltu.oauth2.common.message.types.GrantType;
+import org.apache.oltu.oauth2.common.message.types.ResponseType;
+import org.apache.oltu.oauth2.common.message.types.TokenType;
+import org.apache.oltu.oauth2.common.utils.OAuthUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author hanl
+ * @date 07/06/2014
+ */
+//todo: only allow oauth2 access_token requests GET methods?
+//todo: allow refresh tokens
+@Path(KustvaktServer.API_VERSION + "/oauth2")
+//@ResourceFilters({ AccessLevelFilter.class, PiwikFilter.class })
+public class OAuthService {
+
+    private OAuth2Handler handler;
+    private AuthenticationManagerIface controller;
+    private EncryptionIface crypto;
+    private KustvaktConfiguration config;
+
+    public OAuthService() {
+        this.handler = new OAuth2Handler(
+                BeanConfiguration.getBeans().getPersistenceClient());
+        this.controller = BeanConfiguration.getBeans()
+                .getAuthenticationManager();
+        this.crypto = BeanConfiguration.getBeans().getEncryption();
+        this.config = BeanConfiguration.getBeans().getConfiguration();
+    }
+
+    @POST
+    @Path("unregister")
+    @ResourceFilters({ AuthFilter.class, BlockFilter.class })
+    public Response unregisterClient(@Context SecurityContext context,
+            @HeaderParam("Host") String host,
+            @QueryParam("client_secret") String secret,
+            @QueryParam("client_id") String client_id) {
+        ClientInfo info = new ClientInfo(client_id, secret);
+        info.setUrl(host);
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        try {
+            this.handler.removeClient(info,
+                    this.controller.getUser(ctx.getUsername()));
+        }catch (KustvaktException e) {
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+        return Response.ok().build();
+    }
+
+    @POST
+    @Path("register")
+    @ResourceFilters({ AuthFilter.class, BlockFilter.class })
+    public Response registerClient(@Context SecurityContext context,
+            @HeaderParam("Host") String host,
+            @QueryParam("redirect_url") String rurl) {
+        ClientInfo info = new ClientInfo(crypto.createID(),
+                crypto.createToken());
+        info.setUrl(host);
+        if (rurl == null)
+            throw BeanConfiguration.getResponseHandler()
+                    .throwit(StatusCodes.ILLEGAL_ARGUMENT, "Missing parameter!",
+                            "redirect_url");
+        info.setRedirect_uri(rurl);
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        try {
+            this.handler.registerClient(info,
+                    this.controller.getUser(ctx.getUsername()));
+        }catch (KustvaktException e) {
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+        return Response.ok(info.toJSON()).build();
+    }
+
+    @GET
+    @Path("info")
+    @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+            PiwikFilter.class })
+    public Response getStatus(@Context SecurityContext context,
+            @QueryParam("scope") String scopes) {
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        User user;
+        try {
+            user = this.controller.getUser(ctx.getUsername());
+            this.controller.getUserDetails(user);
+            Set<String> base_scope = StringUtils
+                    .toSet((String) ctx.getParameters().get(Attributes.SCOPES),
+                            " ");
+            base_scope.retainAll(StringUtils.toSet(scopes));
+            scopes = StringUtils.toString(base_scope);
+        }catch (KustvaktException e) {
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+        // json format with scope callback parameter
+        return Response.ok(JsonUtils.toJSON(Scopes
+                .mapOpenIDConnectScopes(scopes, user.getDetails()))).build();
+    }
+
+    @GET
+    @Path("authorizations")
+    @ResourceFilters({ AuthFilter.class, BlockFilter.class })
+    public Response getAuthorizations(@Context SecurityContext context,
+            @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+            @HeaderParam(ContainerRequest.HOST) String host) {
+        // works on all tokens, but access to native apps cannot be revoked!
+        // check secret and id and retrieve access tokens
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        try {
+            User user = this.controller.getUser(ctx.getUsername());
+            Collection auths = this.handler.getAuthorizedClients(user.getId());
+            if (auths.isEmpty())
+                return Response.noContent().build();
+            return Response.ok(JsonUtils.toJSON(auths)).build();
+        }catch (KustvaktException e) {
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+    }
+
+    // todo: scopes for access_token are defined here
+    // todo: if user already has an access token registered for client and application, then redirect to token endpoint to retrive that token
+    // todo: demo account should be disabled for this function --> if authentication failed, client must redirect to login url (little login window)
+    @POST
+    @Path("authorize")
+    @Consumes("application/x-www-form-urlencoded")
+    @Produces("application/json")
+    @ResourceFilters({ BlockFilter.class })
+    public Response authorize(@Context HttpServletRequest request,
+            @Context SecurityContext context,
+            @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+            @HeaderParam(ContainerRequest.HOST) String host,
+            MultivaluedMap<String, String> form)
+            throws OAuthSystemException, URISyntaxException {
+        // user needs to be authenticated to this service!
+        TokenContext c = (TokenContext) context.getUserPrincipal();
+
+        try {
+            OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(
+                    new FormRequestWrapper(request, form));
+            OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(
+                    new MD5Generator());
+            User user;
+
+            Map<String, Object> attr = new HashMap<>();
+            attr.put(Attributes.HOST, host);
+            attr.put(Attributes.USER_AGENT, agent);
+            attr.put(Attributes.USERNAME, c.getUsername());
+            // also extractable via authorization header
+            attr.put(Attributes.CLIENT_ID, oauthRequest.getClientId());
+            attr.put(Attributes.CLIENT_SECRET, oauthRequest.getClientSecret());
+            StringBuilder scopes = new StringBuilder();
+            for (String scope : oauthRequest.getScopes())
+                scopes.append(scope + " ");
+            attr.put(Attributes.SCOPES, scopes.toString());
+
+            try {
+                user = controller.getUser(c.getUsername());
+                controller.getUserDetails(user);
+            }catch (KustvaktException e) {
+                throw BeanConfiguration.getResponseHandler().throwit(e);
+            }
+
+            // register response according to response_type
+            String responseType = oauthRequest
+                    .getParam(OAuth.OAUTH_RESPONSE_TYPE);
+
+            OAuthASResponse.OAuthAuthorizationResponseBuilder builder = OAuthASResponse
+                    .authorizationResponse(request,
+                            HttpServletResponse.SC_FOUND);
+
+            final String authorizationCode = oauthIssuerImpl
+                    .authorizationCode();
+            ClientInfo info = this.handler
+                    .getClient(oauthRequest.getClientId());
+
+            if (info == null || !info.getClient_secret()
+                    .equals(oauthRequest.getClientSecret())) {
+                OAuthResponse res = OAuthASResponse
+                        .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
+                        .setError(OAuthError.CodeResponse.UNAUTHORIZED_CLIENT)
+                        .setErrorDescription("Unauthorized client!\n")
+                        .buildJSONMessage();
+                return Response.status(res.getResponseStatus())
+                        .entity(res.getBody()).build();
+            }
+
+            if (!info.getRedirect_uri()
+                    .contains(oauthRequest.getRedirectURI())) {
+                OAuthResponse res = OAuthASResponse
+                        .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
+                        .setError(OAuthError.CodeResponse.INVALID_REQUEST)
+                        .setErrorDescription("Unauthorized redirect!\n")
+                        .buildJSONMessage();
+                return Response.status(res.getResponseStatus())
+                        .entity(res.getBody()).build();
+            }
+
+            String accessToken = this.handler
+                    .getToken(oauthRequest.getClientId(), user.getId());
+            //todo: test this with parameters
+            if (accessToken != null) {
+                final OAuthResponse response = builder.location("/oauth2/token")
+                        .setParam(OAuth.OAUTH_CLIENT_ID,
+                                oauthRequest.getClientId())
+                        .setParam(OAuth.OAUTH_CLIENT_SECRET,
+                                oauthRequest.getClientSecret())
+                        .buildQueryMessage();
+                return Response.status(response.getResponseStatus())
+                        .location(new URI(response.getLocationUri())).build();
+            }
+
+            if (responseType.equals(ResponseType.CODE.toString())) {
+                try {
+                    AuthCodeInfo codeInfo = new AuthCodeInfo(
+                            info.getClient_id(), authorizationCode);
+                    codeInfo.setScopes(StringUtils
+                            .toString(oauthRequest.getScopes(), " "));
+                    this.handler.authorize(codeInfo, user);
+                }catch (KustvaktException e) {
+                    throw BeanConfiguration.getResponseHandler().throwit(e);
+                }
+                builder.setParam(OAuth.OAUTH_RESPONSE_TYPE,
+                        ResponseType.CODE.toString());
+                builder.setCode(authorizationCode);
+
+            }else if (responseType.contains(ResponseType.TOKEN.toString())) {
+                try {
+                    AuthCodeInfo codeInfo = new AuthCodeInfo(
+                            info.getClient_id(), authorizationCode);
+                    codeInfo.setScopes(StringUtils
+                            .toString(oauthRequest.getScopes(), " "));
+                    this.handler.authorize(codeInfo, user);
+                }catch (KustvaktException e) {
+                    throw BeanConfiguration.getResponseHandler().throwit(e);
+                }
+
+                builder.setParam(OAuth.OAUTH_RESPONSE_TYPE,
+                        ResponseType.TOKEN.toString());
+                builder.setCode(authorizationCode);
+
+                String token = oauthIssuerImpl.accessToken();
+                this.handler.addToken(authorizationCode, token,
+                        config.getLongTokenTTL());
+                builder.setAccessToken(token);
+                builder.setExpiresIn((long) config.getLongTokenTTL());
+
+                // skips authorization code type and returns id_token and access token directly
+                if (oauthRequest.getScopes().contains("openid")) {
+                    try {
+                        TokenContext new_context = this.controller
+                                .createTokenContext(user, attr, null);
+                        builder.setParam(new_context.getTokenType(),
+                                new_context.getToken());
+                    }catch (KustvaktException e) {
+                        throw BeanConfiguration.getResponseHandler().throwit(e);
+                    }
+                }
+            }else {
+                OAuthResponse res = OAuthASResponse
+                        .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
+                        .setError(
+                                OAuthError.CodeResponse.UNSUPPORTED_RESPONSE_TYPE)
+                        .setErrorDescription("Unsupported Response type!\n")
+                        .buildJSONMessage();
+                return Response.status(res.getResponseStatus())
+                        .entity(res.getBody()).build();
+            }
+
+            String redirectURI = oauthRequest.getRedirectURI();
+
+            // enables state parameter to disable cross-site scripting attacks
+            final OAuthResponse response = builder.location(redirectURI)
+                    .buildQueryMessage();
+            if (OAuthUtils.isEmpty(redirectURI)) {
+                throw new WebApplicationException(
+                        Response.status(HttpServletResponse.SC_BAD_REQUEST)
+                                .entity("OAuth callback url needs to be provided by client!!!\n")
+                                .build());
+            }
+
+            return Response.status(response.getResponseStatus())
+                    .location(new URI(response.getLocationUri())).build();
+        }catch (OAuthProblemException e) {
+            e.printStackTrace();
+            final Response.ResponseBuilder responseBuilder = Response
+                    .status(HttpServletResponse.SC_BAD_REQUEST);
+            String redirectUri = e.getRedirectUri();
+
+            if (OAuthUtils.isEmpty(redirectUri))
+                throw new WebApplicationException(responseBuilder
+                        .entity("OAuth callback url needs to be provided by client!!!\n")
+                        .build());
+
+            final OAuthResponse response = OAuthASResponse
+                    .errorResponse(HttpServletResponse.SC_BAD_REQUEST).error(e)
+                    .location(redirectUri).buildQueryMessage();
+            final URI location = new URI(response.getLocationUri());
+            return responseBuilder.location(location).build();
+        }catch (OAuthSystemException | URISyntaxException | KustvaktException e) {
+            e.printStackTrace();
+        }
+        return Response.noContent().build();
+    }
+
+    @POST
+    @Path("revoke")
+    public Response revokeToken(@Context HttpServletRequest request,
+            @Context SecurityContext context,
+            @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+            @HeaderParam(ContainerRequest.HOST) String host)
+            throws OAuthSystemException, URISyntaxException {
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        try {
+
+            if (!this.handler.revokeToken(ctx.getToken())) {
+                OAuthResponse res = OAuthASResponse
+                        .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
+                        .setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
+                        .setErrorDescription("Invalid access token!\n")
+                        .buildJSONMessage();
+                return Response.status(res.getResponseStatus())
+                        .entity(res.getBody()).build();
+            }
+
+        }catch (KustvaktException e) {
+            e.printStackTrace();
+            // fixme: do something
+            /**
+             * final Response.ResponseBuilder responseBuilder = Response
+             .status(HttpServletResponse.SC_FOUND);
+             String redirectUri = e.getRedirectUri();
+
+             final OAuthResponse response = OAuthASResponse
+             .errorResponse(HttpServletResponse.SC_FOUND).error(e)
+             .location(redirectUri).buildQueryMessage();
+             final URI location = new URI(response.getLocationUri());
+             return responseBuilder.location(location).build();
+             */
+        }
+
+        return Response.ok().build();
+    }
+
+    @POST
+    @Consumes("application/x-www-form-urlencoded")
+    @Produces("application/json")
+    @Path("token")
+    public Response requestToken(@Context HttpServletRequest request,
+            @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+            @HeaderParam(ContainerRequest.HOST) String host,
+            MultivaluedMap form) throws OAuthSystemException {
+        boolean openid_valid = false;
+        User user = null;
+        OAuthTokenRequest oauthRequest;
+        OAuthASResponse.OAuthTokenResponseBuilder builder = OAuthASResponse
+                .tokenResponse(HttpServletResponse.SC_OK);
+
+        OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
+        ClientInfo info;
+        try {
+            oauthRequest = new OAuthTokenRequest(
+                    new FormRequestWrapper(request, form));
+
+            if ((info = this.handler.getClient(oauthRequest.getClientId()))
+                    == null) {
+                OAuthResponse res = OAuthASResponse
+                        .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
+                        .setError(OAuthError.TokenResponse.INVALID_CLIENT)
+                        .setErrorDescription("Invalid client id!\n")
+                        .buildJSONMessage();
+                return Response.status(res.getResponseStatus())
+                        .entity(res.getBody()).build();
+            }else if (!info.getClient_secret()
+                    .equals(oauthRequest.getClientSecret())) {
+                OAuthResponse res = OAuthASResponse
+                        .errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
+                        .setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
+                        .setErrorDescription("Invalid client secret!\n")
+                        .buildJSONMessage();
+                return Response.status(res.getResponseStatus())
+                        .entity(res.getBody()).build();
+            }
+
+            Map<String, Object> attr = new HashMap<>();
+            attr.put(Attributes.HOST, host);
+            attr.put(Attributes.USER_AGENT, agent);
+            attr.put(Attributes.SCOPES,
+                    StringUtils.toString(oauthRequest.getScopes(), " "));
+
+            // support code (for external clients only) and password grant type
+            // password grant at this point is only allowed with trusted clients (korap frontend)
+            if (oauthRequest.getGrantType().equalsIgnoreCase(
+                    GrantType.AUTHORIZATION_CODE.toString())) {
+                // validate auth code
+                AuthCodeInfo codeInfo;
+                try {
+                    //can this be joined with the simple retrieval of access tokens?
+                    // partially yes: auth code can be valid, even though no access token exists
+                    // --> zero result set
+                    codeInfo = this.handler
+                            .getAuthorization(oauthRequest.getCode());
+                    if (codeInfo == null) {
+                        OAuthResponse res = OAuthASResponse.errorResponse(
+                                HttpServletResponse.SC_UNAUTHORIZED).setError(
+                                OAuthError.TokenResponse.INVALID_REQUEST)
+                                .setErrorDescription(
+                                        "Invalid authorization code\n")
+                                .buildJSONMessage();
+                        return Response.status(res.getResponseStatus())
+                                .entity(res.getBody()).build();
+                    }else {
+                        openid_valid = codeInfo.getScopes().contains("openid");
+                        String accessToken = oauthIssuerImpl.accessToken();
+                        // auth code posesses the user reference. native apps access_tokens are directly associated with the user
+                        this.handler
+                                .addToken(oauthRequest.getCode(), accessToken,
+                                        config.getTokenTTL());
+
+                        builder.setTokenType(TokenType.BEARER.toString());
+                        builder.setExpiresIn(
+                                String.valueOf(config.getLongTokenTTL()));
+                        builder.setAccessToken(accessToken);
+                    }
+                }catch (KustvaktException e) {
+                    throw BeanConfiguration.getResponseHandler().throwit(e);
+                }
+                // todo: errors for invalid scopes or different scopes then during authorization request?
+                //todo ??
+                attr.put(Attributes.SCOPES, codeInfo.getScopes());
+
+            }else if (oauthRequest.getGrantType()
+                    .equalsIgnoreCase(GrantType.PASSWORD.toString())) {
+                //fixme: via https; as basic auth header and only if client is native!
+                if (!info.isConfidential()) {
+                    OAuthResponse res = OAuthASResponse
+                            .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
+                            .setError(
+                                    OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
+                            .setErrorDescription(
+                                    "Grant type not supported for client!\n")
+                            .buildJSONMessage();
+                    return Response.status(res.getResponseStatus())
+                            .entity(res.getBody()).build();
+                }
+
+                openid_valid = true;
+                try {
+                    user = controller
+                            .authenticate(0, oauthRequest.getUsername(),
+                                    oauthRequest.getPassword(), attr);
+                }catch (KustvaktException e) {
+                    throw BeanConfiguration.getResponseHandler().throwit(e);
+                }
+
+                try {
+                    String accessToken = this.handler
+                            .getToken(oauthRequest.getClientId(), user.getId());
+                    if (accessToken == null) {
+                        accessToken = oauthIssuerImpl.accessToken();
+                        this.handler.addToken(accessToken, user.getId(),
+                                oauthRequest.getClientId(), StringUtils
+                                        .toString(oauthRequest.getScopes(),
+                                                " "), config.getLongTokenTTL());
+                    }
+                    builder.setTokenType(TokenType.BEARER.toString());
+                    builder.setExpiresIn(
+                            String.valueOf(config.getLongTokenTTL()));
+                    builder.setAccessToken(accessToken);
+
+                }catch (KustvaktException e) {
+                    throw BeanConfiguration.getResponseHandler().throwit(e);
+                }
+            }
+
+            if (openid_valid && oauthRequest.getScopes()
+                    .contains(Scopes.Scope.openid.toString())) {
+                try {
+                    if (user == null)
+                        user = controller
+                                .authenticate(0, oauthRequest.getUsername(),
+                                        oauthRequest.getPassword(), attr);
+                    controller.getUserDetails(user);
+                    attr.put(Attributes.CLIENT_SECRET,
+                            oauthRequest.getClientSecret());
+                    TokenContext c = controller.createTokenContext(user, attr,
+                            Attributes.OPENID_AUTHENTICATION);
+                    builder.setParam(c.getTokenType(), c.getToken());
+                }catch (KustvaktException e) {
+                    throw BeanConfiguration.getResponseHandler().throwit(e);
+                }
+            }
+
+            OAuthResponse r = builder.buildJSONMessage();
+            return Response.status(r.getResponseStatus()).entity(r.getBody())
+                    .build();
+        }catch (OAuthProblemException ex) {
+            OAuthResponse r = OAuthResponse.errorResponse(401).error(ex)
+                    .buildJSONMessage();
+            return Response.status(r.getResponseStatus()).entity(r.getBody())
+                    .build();
+        }catch (OAuthSystemException e) {
+            e.printStackTrace();
+            // todo: throw error
+        }
+        return Response.noContent().build();
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/service/UserService.java b/src/main/java/de/ids_mannheim/korap/web/service/UserService.java
new file mode 100644
index 0000000..befa736
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/service/UserService.java
@@ -0,0 +1,456 @@
+package de.ids_mannheim.korap.web.service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.config.BeanConfiguration;
+import de.ids_mannheim.korap.config.Scopes;
+import de.ids_mannheim.korap.config.URIParam;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
+import de.ids_mannheim.korap.security.ac.ResourceHandler;
+import de.ids_mannheim.korap.user.*;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.KustvaktLogger;
+import de.ids_mannheim.korap.utils.StringUtils;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import de.ids_mannheim.korap.web.KustvaktServer;
+import de.ids_mannheim.korap.web.filter.AuthFilter;
+import de.ids_mannheim.korap.web.filter.DefaultFilter;
+import de.ids_mannheim.korap.web.filter.PiwikFilter;
+import de.ids_mannheim.korap.web.utils.FormWrapper;
+import org.slf4j.Logger;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.*;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * @author hanl
+ * @date 29/01/2014
+ */
+@Path(KustvaktServer.API_VERSION + "/user")
+@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
+@ResourceFilters({ PiwikFilter.class })
+public class UserService {
+
+    private static Logger error = KustvaktLogger
+            .initiate(KustvaktLogger.ERROR_LOG);
+    private static Logger jlog = KustvaktLogger
+            .initiate(KustvaktLogger.SECURITY_LOG);
+    private AuthenticationManagerIface controller;
+    private ResourceHandler resourceHandler;
+
+    private
+    @Context
+    UriInfo info;
+
+    public UserService() {
+        this.controller = BeanConfiguration.getBeans()
+                .getAuthenticationManager();
+        //        this.resourceHandler = BeanConfiguration.getResourceHandler();
+    }
+
+    // fixme: json contains password in clear text. Encrypt request?
+    // fixme: should also collect service exception, not just db exception!
+    @POST
+    @Path("register")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response signUp(
+            @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+            @HeaderParam(ContainerRequest.HOST) String host,
+            @Context Locale locale, MultivaluedMap form_values) {
+
+        FormWrapper wrapper = new FormWrapper(form_values);
+
+        wrapper.put(Attributes.HOST, host);
+        wrapper.put(Attributes.USER_AGENT, agent);
+        UriBuilder uriBuilder;
+        User user;
+        if (wrapper.get(Attributes.EMAIL) == null)
+            throw BeanConfiguration.getResponseHandler()
+                    .throwit(StatusCodes.ILLEGAL_ARGUMENT, "parameter missing",
+                            "email");
+
+        try {
+            uriBuilder = info.getBaseUriBuilder();
+            uriBuilder.path(KustvaktServer.API_VERSION).path("user")
+                    .path("confirm");
+
+            user = controller.createUserAccount(wrapper);
+
+        }catch (KustvaktException e) {
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+        URIParam uri = user.getField(URIParam.class);
+        if (uri.hasValues()) {
+            uriBuilder.queryParam(Attributes.QUERY_PARAM_URI,
+                    uri.getUriFragment())
+                    .queryParam(Attributes.QUERY_PARAM_USER,
+                            user.getUsername());
+            jlog.info("registration was successful for user '{}'",
+                    form_values.get(Attributes.USERNAME));
+            Map object = new HashMap();
+            object.put("confirm_uri", uriBuilder.build());
+            object.put("uri_expiration",
+                    TimeUtils.format(uri.getUriExpiration()));
+            return Response.ok(JsonUtils.toJSON(object)).build();
+        }else {
+            // todo: return error or warning
+            return null;
+        }
+
+    }
+
+    //todo: password update in special function?
+    @POST
+    @Path("update")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+            PiwikFilter.class })
+    public Response updateAccount(@Context SecurityContext ctx, String json) {
+        TokenContext context = (TokenContext) ctx.getUserPrincipal();
+        try {
+            User user = controller.getUser(context.getUsername());
+
+            JsonNode node = JsonUtils.readTree(json);
+            KorAPUser ident = (KorAPUser) user;
+            KorAPUser values = User.UserFactory.toUser(json);
+            //            user = controller
+            //                    .checkPasswordAllowance(ident, values.getPassword(),
+            //                            node.path("new_password").asText());
+            controller.updateAccount(user);
+        }catch (KustvaktException e) {
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+        return Response.ok().build();
+    }
+
+    @GET
+    @Path("confirm")
+    @Produces(MediaType.TEXT_HTML)
+    public Response confirmRegistration(@QueryParam("uri") String uritoken,
+            @Context Locale locale, @QueryParam("user") String username) {
+        if (uritoken == null)
+            throw BeanConfiguration.getResponseHandler()
+                    .throwit(StatusCodes.ILLEGAL_ARGUMENT, "parameter missing",
+                            "Uri-Token");
+        if (username == null)
+            throw BeanConfiguration.getResponseHandler()
+                    .throwit(StatusCodes.ILLEGAL_ARGUMENT, "parameter missing",
+                            "Username");
+
+        try {
+            controller.confirmRegistration(uritoken, username);
+        }catch (KustvaktException e) {
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+        return Response.ok("success").build();
+    }
+
+    // todo: auditing!
+    @POST
+    @Path("requestReset")
+    @Produces(MediaType.TEXT_HTML)
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response requestPasswordReset(@Context Locale locale, String json) {
+        JsonNode node = JsonUtils.readTree(json);
+        StringBuilder builder = new StringBuilder();
+        String username, email;
+        username = node.path(Attributes.USERNAME).asText();
+        email = node.path(Attributes.EMAIL).asText();
+
+        // deprecated --> depends on the client!
+        //        String url = config.getMailProperties()
+        //                .getProperty("korap.frontend.url", "");
+        //        if (url.isEmpty())
+        //            return Response.ok("URLException: Missing source URL").build();
+
+        //        URIUtils utils = new URIUtils(info);
+        // may inject the actual REST url in a redirect request?!
+        //        UriBuilder uriBuilder = UriBuilder.fromUri(url).fragment("reset");
+        Object[] objects;
+        try {
+            builder.append("?");
+            // just append the endpint fragment plus the query parameter.
+            // the address by which the data is handled depends on the frontend
+            objects = controller.validateResetPasswordRequest(username, email);
+            builder.append(Attributes.QUERY_PARAM_URI).append("=")
+                    .append(objects[0]);
+            builder.append(Attributes.QUERY_PARAM_USER).append("=")
+                    .append(username);
+        }catch (KustvaktException e) {
+            error.error("Eoxception encountered!", e);
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+
+        ObjectNode obj = JsonUtils.createObjectNode();
+        obj.put(Attributes.URI, builder.toString());
+        obj.put(Attributes.URI_EXPIRATION, String.valueOf(objects[1]));
+        return Response.ok(JsonUtils.toJSON(obj)).build();
+    }
+
+    @POST
+    @Path("reset")
+    @Produces(MediaType.TEXT_HTML)
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    public Response resetPassword(
+            @QueryParam(Attributes.QUERY_PARAM_URI) String uri,
+            @QueryParam(Attributes.QUERY_PARAM_USER) String username,
+            @Context HttpHeaders headers, String passphrase) {
+        try {
+            controller.resetPassword(uri, username, passphrase);
+        }catch (KustvaktException e) {
+            error.error("Exception encountered!", e);
+            return Response.notModified().build();
+        }
+        return Response.ok().build();
+    }
+
+    @GET
+    @Path("info")
+    @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+            PiwikFilter.class })
+    public Response getStatus(@Context SecurityContext context,
+            @QueryParam("scope") String scope) {
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        User user;
+        try {
+            user = controller.getUser(ctx.getUsername());
+            controller.getUserDetails(user);
+            Set<String> base_scope = StringUtils
+                    .toSet((String) ctx.getParameters().get(Attributes.SCOPES),
+                            " ");
+            base_scope.retainAll(StringUtils.toSet(scope));
+            scope = StringUtils.toString(base_scope);
+        }catch (KustvaktException e) {
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+        return Response.ok(JsonUtils.toJSON(Scopes
+                .mapOpenIDConnectScopes(scope, user.getDetails()))).build();
+    }
+
+    @GET
+    @Path("settings")
+    @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+            PiwikFilter.class })
+    public Response getUserSettings(@Context SecurityContext context,
+            @Context Locale locale) {
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        User user;
+        try {
+            user = controller.getUser(ctx.getUsername());
+            controller.getUserSettings(user);
+
+        }catch (KustvaktException e) {
+            error.error("Exception encountered!", e);
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+        return Response.ok(JsonUtils.toJSON(user.getSettings().toObjectMap()))
+                .build();
+    }
+
+    // todo: test
+    @POST
+    @Path("settings")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+            PiwikFilter.class })
+    public Response updateSettings(@Context SecurityContext context,
+            @Context Locale locale, String values) {
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        Map<String, Object> settings;
+        try {
+            settings = JsonUtils.read(values, Map.class);
+        }catch (IOException e) {
+            throw BeanConfiguration.getResponseHandler()
+                    .throwit(StatusCodes.REQUEST_INVALID,
+                            "Could not read parameters", values);
+        }
+
+        try {
+            User user = controller.getUser(ctx.getUsername());
+            UserSettings us = controller.getUserSettings(user);
+            // todo:
+            //            SecurityManager.findbyId(us.getDefaultConstfoundry(), user, Foundry.class);
+            //            SecurityManager.findbyId(us.getDefaultLemmafoundry(), user, Foundry.class);
+            //            SecurityManager.findbyId(us.getDefaultPOSfoundry(), user, Foundry.class);
+            //            SecurityManager.findbyId(us.getDefaultRelfoundry(), user, Foundry.class);
+            us.updateObjectSettings(settings);
+            controller.updateUserSettings(user, us);
+            if (user.isDemo())
+                return Response.notModified().build();
+        }catch (KustvaktException e) {
+            error.error("Exception encountered!", e);
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+
+        return Response.ok().build();
+    }
+
+    @GET
+    @Path("details")
+    @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+            PiwikFilter.class })
+    public Response getDetails(@Context SecurityContext context,
+            @Context Locale locale) {
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        User user;
+        try {
+            user = controller.getUser(ctx.getUsername());
+            controller.getUserDetails(user);
+        }catch (KustvaktException e) {
+            error.error("Exception encountered!", e);
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+
+        return Response.ok(JsonUtils.toJSON(user.getDetails().toMap())).build();
+    }
+
+    @POST
+    @Path("details")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+            PiwikFilter.class })
+    public Response updateDetails(@Context SecurityContext context,
+            @Context Locale locale, String values) {
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        System.out.println("TO BE UPDATED DATA " + values);
+        System.out.println("USER CONTEXT " + ctx);
+        Map<String, String> details;
+        try {
+            details = JsonUtils.read(values, Map.class);
+        }catch (IOException e) {
+            error.error("Exception encountered!", e);
+            throw BeanConfiguration.getResponseHandler()
+                    .throwit(StatusCodes.REQUEST_INVALID,
+                            "Could not read parameters", values);
+        }
+
+        try {
+            User user = controller.getUser(ctx.getUsername());
+            UserDetails det = controller.getUserDetails(user);
+            det.updateDetails(details);
+            controller.updateUserDetails(user, det);
+            if (user.isDemo())
+                return Response.notModified().build();
+        }catch (KustvaktException e) {
+            error.error("Exception encountered!", e);
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+
+        return Response.ok().build();
+    }
+
+    //fixme: if policy allows, foreign user might be allowed to change search!
+    @POST
+    @Path("queries")
+    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+    @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+            PiwikFilter.class })
+    public Response updateQueries(@Context SecurityContext context,
+            String json) {
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        Collection<UserQuery> add = new HashSet<>();
+        try {
+            User user = controller.getUser(ctx.getUsername());
+            List<UserQuery> userQuieres = new ArrayList<>();
+            JsonNode nodes = JsonUtils.readTree(json);
+            Iterator<JsonNode> node = nodes.elements();
+            while (node.hasNext()) {
+                JsonNode cursor = node.next();
+                UserQuery query = new UserQuery(cursor.path("id").asInt(),
+                        user.getId());
+                query.setQueryLanguage(cursor.path("queryLanguage").asText());
+                query.setQuery(cursor.path("query").asText());
+                query.setDescription(cursor.path("description").asText());
+                userQuieres.add(query);
+            }
+
+            //1: add all that are new, update all that are retained, delete the rest
+            //            Set<UserQuery> resources = ResourceFinder
+            //                    .search(user, UserQuery.class);
+            //
+            //            add.addAll(userQuieres);
+            //            add.removeAll(resources);
+            //            Collection<UserQuery> update = new HashSet<>(userQuieres);
+            //            update.retainAll(resources);
+            //            resources.removeAll(userQuieres);
+            //
+            //            if (!update.isEmpty()) {
+            //                resourceHandler.updateResources(user,
+            //                        update.toArray(new UserQuery[update.size()]));
+            //            }
+            //            if (!add.isEmpty()) {
+            //                resourceHandler.storeResources(user,
+            //                        add.toArray(new UserQuery[add.size()]));
+            //            }
+            //            if (!resources.isEmpty()) {
+            //                resourceHandler.deleteResources(user,
+            //                        resources.toArray(new UserQuery[resources.size()]));
+            //            }
+        }catch (KustvaktException e) {
+            error.error("Exception encountered!", e);
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+        return Response.ok(JsonUtils.toJSON(add)).build();
+    }
+
+    @DELETE
+    @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+            PiwikFilter.class })
+    public Response deleteUser(@Context SecurityContext context) {
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        try {
+            User user = controller.getUser(ctx.getUsername());
+            if (user.isDemo())
+                return Response.notModified().build();
+            controller.deleteAccount(user);
+        }catch (KustvaktException e) {
+            error.error("Exception encountered!", e);
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+        return Response.ok().build();
+    }
+
+    @GET
+    @Path("queries")
+    @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+            PiwikFilter.class })
+    public Response getQueries(@Context SecurityContext context,
+            @Context Locale locale) {
+        TokenContext ctx = (TokenContext) context.getUserPrincipal();
+        String queryStr;
+        try {
+            User user = controller.getUser(ctx.getUsername());
+            //            Set<UserQuery> queries = ResourceFinder
+            //                    .search(user, UserQuery.class);
+            //            queryStr = JsonUtils.toJSON(queries);
+            //todo:
+            queryStr = "";
+        }catch (KustvaktException e) {
+            error.error("Exception encountered!", e);
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+        return Response.ok(queryStr).build();
+    }
+
+    @GET
+    @Path("logout")
+    @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+            PiwikFilter.class })
+    public Response logout(@Context SecurityContext ctx,
+            @Context Locale locale) {
+        TokenContext context = (TokenContext) ctx.getUserPrincipal();
+        try {
+            controller.logout(context);
+        }catch (KustvaktException e) {
+            error.error("Logout Exception", e);
+            throw BeanConfiguration.getResponseHandler().throwit(e);
+        }
+        return Response.ok().build();
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/utils/FormRequestWrapper.java b/src/main/java/de/ids_mannheim/korap/web/utils/FormRequestWrapper.java
new file mode 100644
index 0000000..143aa04
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/utils/FormRequestWrapper.java
@@ -0,0 +1,64 @@
+package de.ids_mannheim.korap.web.utils;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.ws.rs.core.MultivaluedMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper class to wrapp multivaluedmap into a hashmap. Depending on the strict parameter,
+ * list values are retained in the resulting wrapper map.
+ *
+ * @author hanl
+ * @date 25/04/2015
+ */
+public class FormRequestWrapper extends HttpServletRequestWrapper {
+
+    private MultivaluedMap<String, String> form;
+
+    /**
+     * Constructs a request object wrapping the given request.
+     *
+     * @param request
+     * @throws IllegalArgumentException if the request is null
+     */
+    public FormRequestWrapper(HttpServletRequest request,
+            MultivaluedMap<String, String> form) {
+        super(request);
+        this.form = form;
+    }
+
+    @Override
+    public String getParameter(String name) {
+        String value = super.getParameter(name);
+        if (value == null)
+            value = form.getFirst(name);
+        return value;
+    }
+
+    @Override
+    public String[] getParameterValues(String name) {
+        String[] values = super.getParameterValues(name);
+        if (values == null && form.get(name) != null) {
+            values = new String[form.get(name).size()];
+            values = form.get(name).toArray(values);
+        }
+        return values;
+    }
+
+    public HashMap<String, Object> toMap(boolean strict) {
+        HashMap<String, Object> map = new HashMap<>();
+        for (Map.Entry<String, List<String>> 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/test/java/CollectionQueryBuilderTest.java b/src/test/java/CollectionQueryBuilderTest.java
new file mode 100644
index 0000000..de9a089
--- /dev/null
+++ b/src/test/java/CollectionQueryBuilderTest.java
@@ -0,0 +1,87 @@
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.utils.CollectionQueryBuilder3;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import org.junit.Test;
+
+/**
+ * @author hanl
+ * @date 12/08/2015
+ */
+public class CollectionQueryBuilderTest {
+
+    @Test
+    public void testsimpleAdd() {
+        CollectionQueryBuilder3 b = new CollectionQueryBuilder3();
+        b.addSegment("corpusID", CollectionQueryBuilder3.EQ.EQUAL, "WPD");
+        //        System.out.println(b.toJSON());
+    }
+
+    @Test
+    public void testSimpleConjunctive() {
+        CollectionQueryBuilder3 b = new CollectionQueryBuilder3();
+        b.addSegment("corpusID", CollectionQueryBuilder3.EQ.EQUAL, "WPD").and()
+                .addSegment("textClass", CollectionQueryBuilder3.EQ.EQUAL,
+                        "freizeit");
+        //        System.out.println(b.toJSON());
+    }
+
+    @Test
+    public void testSimpleDisjunctive() {
+        CollectionQueryBuilder3 b = new CollectionQueryBuilder3();
+        b.addSegment("corpusID", CollectionQueryBuilder3.EQ.EQUAL, "WPD").and()
+                .addSegment("textClass", CollectionQueryBuilder3.EQ.EQUAL,
+                        "freizeit");
+        //        System.out.println(b.toJSON());
+    }
+
+    @Test
+    public void testComplexSubQuery() {
+
+        CollectionQueryBuilder3 b = new CollectionQueryBuilder3();
+        b.addSegment("corpusID", CollectionQueryBuilder3.EQ.EQUAL, "ADF").and()
+                .addSub("textClass=freizeit & corpusID=WPD");
+        //        System.out.println(b.toJSON());
+    }
+
+    @Test // basically joining two or more resource queries
+    public void testAddResourceQueryAfter() {
+        CollectionQueryBuilder3 b = new CollectionQueryBuilder3();
+        b.addSegment("corpusID", CollectionQueryBuilder3.EQ.EQUAL, "ADF").and()
+                .addSub("textClass=freizeit & corpusID=WPD");
+        //        System.out.println(b.toJSON());
+
+        //        join.addSegment("textClass", "politik");
+    }
+
+    @Test // basically joining two or more resource queries
+    public void testAddResourceQueryBefore() {
+        CollectionQueryBuilder3 b = new CollectionQueryBuilder3();
+        b.addSegment("corpusID", CollectionQueryBuilder3.EQ.EQUAL, "ADF").and()
+                .addSub("textClass!=freizeit & corpusID=WPD");
+
+        //        CollectionQueryBuilder3 join = new CollectionQueryBuilder3();
+        //        join.addRaw(b.toJSON());
+        //        join.addSegment("textClass", "politik");
+        //        System.out.println("JOINED " + join.toJSON());
+    }
+
+    @Test
+    public void test1() {
+        CollectionQueryBuilder3 b = new CollectionQueryBuilder3();
+        b.addSegment("corpusID", CollectionQueryBuilder3.EQ.EQUAL, "ADF").or()
+                .addSub("textClass=freizeit & corpusID=WPD");
+
+        CollectionQueryBuilder3 c = new CollectionQueryBuilder3();
+        c.setBaseQuery(b.toJSON());
+        c.addSub("textClass=wissenschaft");
+
+        JsonNode node = JsonUtils.readTree(c.toJSON());
+
+        assert node != null;
+        assert node.at("/collection/operands/2/@type").asText()
+                .equals("koral:doc");
+        assert node.at("/collection/operands/2/value").asText()
+                .equals("wissenschaft");
+    }
+
+}