higher performance implementation for rewrite handler, test suite for rest functions, fast jersey junit tester, rewrite test suite
diff --git a/src/main/java/de/ids_mannheim/korap/resource/rewrite/CollectionCleanupFilter.java b/src/main/java/de/ids_mannheim/korap/resource/rewrite/CollectionCleanupFilter.java
new file mode 100644
index 0000000..93db8ae
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/resource/rewrite/CollectionCleanupFilter.java
@@ -0,0 +1,79 @@
+package de.ids_mannheim.korap.resource.rewrite;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+import java.util.Iterator;
+
+/**
+ * @author hanl
+ * @date 28/07/2015
+ */
+//todo: test
+public class CollectionCleanupFilter extends RewriteQuery {
+
+    // track path to operand
+    private StringBuilder builder;
+
+    public CollectionCleanupFilter() {
+        this.builder = new StringBuilder();
+    }
+
+    @Override
+    public JsonNode rewrite(KoralNode node) {
+        if (node.rawNode().has("collection")) {
+            JsonNode coll = node.rawNode().path("collection");
+            process(coll);
+        }
+        return null;
+    }
+
+    private JsonNode process(JsonNode root) {
+        if (root.isObject()) {
+            if (root.has("operands")) {
+                JsonNode node = root.at("/operands");
+                Iterator<JsonNode> it = node.elements();
+                while (it.hasNext()) {
+                    JsonNode n = it.next();
+                    JsonNode s = process(n);
+                    if (s == null)
+                        it.remove();
+                }
+            }
+
+            JsonNode sub = processNodes(root);
+            if (!root.equals(sub)) {
+                if (sub == null) {
+                    //can't do anything here -- fixme: edge case?!
+                    return null;
+                }else if (sub.isObject()) {
+                    ObjectNode ob = (ObjectNode) root;
+                    ob.removeAll();
+                    ob.putAll((ObjectNode) sub);
+                }
+
+            }
+        }
+        return root;
+    }
+
+    // return null deletes node, if node return replace at level -1
+    private JsonNode processNodes(JsonNode jsonNode) {
+        if (jsonNode.isObject()) {
+            if (jsonNode.has("operands")) {
+                JsonNode node = jsonNode.at("/operands");
+                int count = node.size();
+                if (count == 1) {
+                    // move to super node if any
+                    return node.path(0);
+                }else if (count == 0) {
+                    // remove container
+                    return null;
+                }
+                return jsonNode;
+            }
+        }
+        return JsonUtils.createArrayNode();
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/resource/rewrite/CollectionConstraint.java b/src/main/java/de/ids_mannheim/korap/resource/rewrite/CollectionConstraint.java
new file mode 100644
index 0000000..a4cf771
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/resource/rewrite/CollectionConstraint.java
@@ -0,0 +1,29 @@
+package de.ids_mannheim.korap.resource.rewrite;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.user.User;
+
+/**
+ * @author hanl
+ * @date 03/07/2015
+ */
+public class CollectionConstraint extends RewriteNode {
+
+    @Override
+    public JsonNode rewrite(KoralNode koralnode) {
+        JsonNode node = koralnode.rawNode();
+        if (node.at("/@type").asText().equals("koral:doc")) {
+            if (node.at("/key").asText().equals("corpusID") && !check(node,
+                    koralnode.getUser())) {
+                koralnode.removeNode();
+                // todo: add message that node was removed!
+            }
+        }
+        return node;
+    }
+
+    private boolean check(JsonNode node, User user) {
+        return false;
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/resource/rewrite/FoundryInject.java b/src/main/java/de/ids_mannheim/korap/resource/rewrite/FoundryInject.java
new file mode 100644
index 0000000..58e3ff2
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/resource/rewrite/FoundryInject.java
@@ -0,0 +1,39 @@
+package de.ids_mannheim.korap.resource.rewrite;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.resource.LayerMapper;
+
+/**
+ * @author hanl
+ * @date 30/06/2015
+ */
+public class FoundryInject extends RewriteNode {
+
+    private KustvaktConfiguration config;
+
+    public FoundryInject(KustvaktConfiguration config) {
+        this.config = config;
+    }
+
+    @Override
+    public JsonNode rewrite(KoralNode node) {
+        LayerMapper mapper;
+        if (node.hasUser())
+            mapper = new LayerMapper(config, node.getUser().getSettings());
+        else
+            mapper = new LayerMapper(config);
+
+        if (node.rawNode().path("@type").asText().equals("koral:term") && !node
+                .rawNode().has("foundry")) {
+            String layer;
+            if (node.rawNode().has("layer"))
+                layer = node.rawNode().path("layer").asText();
+            else
+                layer = node.rawNode().path("key").asText();
+            String foundry = mapper.findFoundry(layer);
+            node.set("foundry", foundry);
+        }
+        return node.rawNode();
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/resource/rewrite/KoralNode.java b/src/main/java/de/ids_mannheim/korap/resource/rewrite/KoralNode.java
new file mode 100644
index 0000000..e708504
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/resource/rewrite/KoralNode.java
@@ -0,0 +1,145 @@
+package de.ids_mannheim.korap.resource.rewrite;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author hanl
+ * @date 04/07/2015
+ */
+public abstract class KoralNode {
+    private JsonNode node;
+    private KoralRewriteBuilder builder;
+    private boolean toRemove;
+    private final User user;
+
+    private KoralNode(JsonNode node) {
+        this(node, null);
+    }
+
+    public KoralNode(JsonNode node, User user) {
+        this.node = node;
+        this.builder = new KoralRewriteBuilder();
+        this.toRemove = false;
+        this.user = user;
+    }
+
+    public boolean hasUser() {
+        return this.user != null;
+    }
+
+    public User getUser() {
+        return this.user;
+    }
+
+    public static KoralNode getNode(JsonNode node) {
+        return new KoralNode(node) {
+        };
+    }
+
+    public void set(String name, Object value) {
+
+        if (this.node.isObject()) {
+            ObjectNode node = (ObjectNode) this.node;
+            if (value instanceof String)
+                node.put(name, (String) value);
+            else if (value instanceof Integer)
+                node.put(name, (Integer) value);
+            builder.setOperation("injection");
+            builder.build(this.node);
+        }else
+            throw new UnsupportedOperationException(
+                    "node doesn't support this operation");
+    }
+
+    public void remove(Object identifier) {
+        boolean set = false;
+        if (this.node.isObject() && identifier instanceof String) {
+            ObjectNode n = (ObjectNode) this.node;
+            n.remove((String) identifier);
+            set = true;
+        }else if (this.node.isArray() && identifier instanceof Integer) {
+            ArrayNode n = (ArrayNode) this.node;
+            n.remove((Integer) identifier);
+            set = true;
+        }
+        if (set) {
+            builder.setOperation("deletion");
+            builder.build(this.node);
+        }
+    }
+
+    public void replace(String name, String value) {
+        if (this.node.isObject() && this.node.has(name)) {
+            ObjectNode n = (ObjectNode) this.node;
+            n.put(name, value);
+            builder.setOperation("override");
+            builder.build(this.node);
+        }
+    }
+
+    public JsonNode rawNode() {
+        return this.node;
+    }
+
+    public void removeNode() {
+        this.toRemove = true;
+    }
+
+    public boolean toRemove() {
+        return this.toRemove;
+    }
+
+    private static class KoralRewriteBuilder {
+
+        private Map<String, String> map;
+
+        public KoralRewriteBuilder() {
+            this.map = new LinkedHashMap<>();
+            this.map.put("@type", "koral:rewrite");
+            this.map.put("src", "Kustvakt");
+        }
+
+        public KoralRewriteBuilder setOperation(String op) {
+            if (!op.startsWith("operation:"))
+                op = "operation:" + op;
+            this.map.put("operation", op);
+            return this;
+        }
+
+        public KoralRewriteBuilder setScope(String scope) {
+            this.map.put("scope", scope);
+            return this;
+        }
+
+        public JsonNode build(JsonNode node) {
+            if (this.map.get("operation") == null)
+                throw new UnsupportedOperationException(
+                        "operation not set properly");
+
+            if (node.has("rewrites")) {
+                ArrayNode n = (ArrayNode) node.path("rewrites");
+                n.add(JsonUtils.valueToTree(this.map));
+            }else {
+                ObjectNode n = (ObjectNode) node;
+                List l = new LinkedList<>();
+                l.add(JsonUtils.valueToTree(this.map));
+                n.put("rewrites", JsonUtils.valueToTree(l));
+            }
+            return node;
+        }
+
+    }
+}
+
+
+
+
diff --git a/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteHandler.java b/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteHandler.java
new file mode 100644
index 0000000..5676817
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteHandler.java
@@ -0,0 +1,87 @@
+package de.ids_mannheim.korap.resource.rewrite;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.KustvaktLogger;
+import org.slf4j.Logger;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+
+/**
+ * @author hanl
+ * @date 30/06/2015
+ */
+//todo: load rewritenode and rewritequery automatically from classpath, but namespaced from package
+public class RewriteHandler {
+
+    private static Logger jlog = KustvaktLogger.initiate(RewriteHandler.class);
+    private Collection<RewriteNode> node_processors;
+    private Collection<RewriteQuery> query_processors;
+
+    public RewriteHandler() {
+        this.node_processors = new HashSet<>();
+        this.query_processors = new HashSet<>();
+        // add defaults?!
+    }
+
+    public void add(RewriteNode node) {
+        this.node_processors.add(node);
+    }
+
+    public void add(RewriteQuery node) {
+        this.query_processors.add(node);
+    }
+
+    public void clear() {
+        this.node_processors.clear();
+        this.query_processors.clear();
+    }
+
+    private boolean process(JsonNode root, User user) {
+        if (root.isObject()) {
+            if (root.has("operands")) {
+                JsonNode node = root.at("/operands");
+                Iterator<JsonNode> it = node.elements();
+                while (it.hasNext()) {
+                    JsonNode n = it.next();
+                    if (!process(n, user))
+                        it.remove();
+                }
+            }else if (root.has("wrap")) {
+                JsonNode node = root.at("/wrap");
+                //todo: remove object nodes as well
+                process(node, user);
+            }else
+                return processNode(root, user, this.node_processors);
+        }
+        return true;
+    }
+
+    public JsonNode apply(JsonNode root, User user) {
+        for (JsonNode n : root)
+            process(n, user);
+        processNode(root, user, this.query_processors);
+        return root;
+    }
+
+    public String apply(String json, User user) {
+        return JsonUtils.toJSON(apply(JsonUtils.readTree(json), user));
+    }
+
+    private static boolean processNode(JsonNode node, User user,
+            Collection<? extends RewriteTask> tasks) {
+        KoralNode knode = KoralNode.getNode(node);
+        for (RewriteTask task : tasks) {
+            jlog.debug("running node in processor " + node);
+            jlog.debug("on processor " + task.getClass().toString());
+            task.rewrite(knode);
+            if (knode.toRemove())
+                break;
+        }
+        return !knode.toRemove();
+    }
+
+}
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
new file mode 100644
index 0000000..cb0a06c
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteNode.java
@@ -0,0 +1,20 @@
+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
new file mode 100644
index 0000000..99bfb34
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteQuery.java
@@ -0,0 +1,18 @@
+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/resource/rewrite/RewriteTask.java b/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteTask.java
new file mode 100644
index 0000000..ed5ad34
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteTask.java
@@ -0,0 +1,17 @@
+package de.ids_mannheim.korap.resource.rewrite;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Getter;
+
+/**
+ * @author hanl
+ * @date 30/06/2015
+ */
+@Getter
+public abstract class RewriteTask {
+
+    protected RewriteTask() {
+    }
+
+    public abstract JsonNode rewrite(KoralNode node);
+}