Added comments & updated code structure.

Change-Id: I2ff7adb2954b87e8345a6e0619b14614883c4200
diff --git a/core/src/main/java/de/ids_mannheim/korap/rewrite/CollectionCleanRewrite.java b/core/src/main/java/de/ids_mannheim/korap/rewrite/CollectionCleanRewrite.java
new file mode 100644
index 0000000..6cacd4b
--- /dev/null
+++ b/core/src/main/java/de/ids_mannheim/korap/rewrite/CollectionCleanRewrite.java
@@ -0,0 +1,74 @@
+package de.ids_mannheim.korap.rewrite;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import de.ids_mannheim.korap.config.BeanInjectable;
+import de.ids_mannheim.korap.config.ContextHolder;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import edu.emory.mathcs.backport.java.util.Arrays;
+
+import java.util.Iterator;
+
+/**
+ * @author hanl
+ * @date 28/07/2015
+ */
+public class CollectionCleanRewrite implements RewriteTask.RewriteNodeAt {
+
+    @Override
+    public KoralNode rewriteQuery (KoralNode node, KustvaktConfiguration config,
+            User user) {
+        JsonNode jsonNode = process(node.rawNode());
+        return node.wrapNode(jsonNode);
+    }
+
+
+    private JsonNode process (JsonNode root) {
+        JsonNode sub = 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();
+                }
+
+                int count = node.size();
+                // remove group element and replace with single doc
+                if (count == 1)
+                    sub = node.path(0);
+                // indicate empty group
+                else if (count == 0) // can't do anything here -- fixme: edge case?!
+                    return null;
+            }
+
+            // what happens to array nodes?
+            if (!root.equals(sub)) {
+                if (sub.isObject()) {
+                    ObjectNode ob = (ObjectNode) root;
+                    ob.remove(Arrays.asList(new String[] { "@type",
+                            "operation", "operands" }));
+                    ob.putAll((ObjectNode) sub);
+                }
+            }
+        }
+        return root;
+    }
+
+
+    @Override
+    public JsonNode rewriteResult (KoralNode node) {
+        return null;
+    }
+
+
+    @Override
+    public String at () {
+        return "/collection";
+    }
+}
diff --git a/core/src/main/java/de/ids_mannheim/korap/rewrite/FoundryInject.java b/core/src/main/java/de/ids_mannheim/korap/rewrite/FoundryInject.java
new file mode 100644
index 0000000..f682414
--- /dev/null
+++ b/core/src/main/java/de/ids_mannheim/korap/rewrite/FoundryInject.java
@@ -0,0 +1,80 @@
+package de.ids_mannheim.korap.rewrite;
+
+import java.util.Collection;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import de.ids_mannheim.korap.config.BeanInjectable;
+import de.ids_mannheim.korap.config.ContextHolder;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.rewrite.KoralNode.RewriteIdentifier;
+import de.ids_mannheim.korap.user.User;
+import edu.emory.mathcs.backport.java.util.Collections;
+
+/**
+ * @author hanl
+ * @date 30/06/2015
+ */
+public class FoundryInject implements RewriteTask.IterableRewritePath,
+        BeanInjectable {
+
+    private Collection userdaos;
+
+
+    public FoundryInject () {
+        this.userdaos = Collections.emptyList();
+    }
+
+
+    @Override
+    public KoralNode rewriteQuery (KoralNode node, KustvaktConfiguration config,
+            User user) throws KustvaktException {
+        LayerMapper mapper;
+        // EM: do not use DB
+//        if (user != null && !userdaos.isEmpty()) {
+//            UserDataDbIface dao = BeansFactory.getTypeFactory()
+//                    .getTypeInterfaceBean(userdaos, UserSettings.class);
+//            mapper = new LayerMapper(config, dao.get(user));
+//        }
+//        else
+            mapper = new LayerMapper(config);
+            
+        if (node.get("@type").equals("koral:span")) {
+            if (!node.isMissingNode("/wrap")){
+                node = node.at("/wrap");
+                JsonNode term = rewriteQuery(node, config, user).rawNode();
+                node.replaceAt("/wrap", term, new RewriteIdentifier("koral:term", "replace"));
+            }
+        }
+        else if (node.get("@type").equals("koral:term") && !node.has("foundry")) {
+            String layer;
+            if (node.has("layer"))
+                layer = node.get("layer");
+            else
+                layer = node.get("key");
+            String foundry = mapper.findFoundry(layer);
+            if (foundry != null)
+                node.put("foundry", foundry);
+        }
+        return node;
+    }
+
+
+    @Override
+    public String path () {
+        return "query";
+    }
+
+
+    @Override
+    public JsonNode rewriteResult (KoralNode node) {
+        return null;
+    }
+
+
+    @Override
+    public <T extends ContextHolder> void insertBeans (T beans) {
+        this.userdaos = beans.getUserDataProviders();
+    }
+}
diff --git a/core/src/main/java/de/ids_mannheim/korap/rewrite/IdWriter.java b/core/src/main/java/de/ids_mannheim/korap/rewrite/IdWriter.java
new file mode 100644
index 0000000..1f0dad6
--- /dev/null
+++ b/core/src/main/java/de/ids_mannheim/korap/rewrite/IdWriter.java
@@ -0,0 +1,41 @@
+package de.ids_mannheim.korap.rewrite;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.config.BeanInjectable;
+import de.ids_mannheim.korap.config.ContextHolder;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.user.User;
+
+/**
+ * @author hanl
+ * @date 25/09/2015
+ */
+public class IdWriter implements RewriteTask.RewriteKoralToken {
+
+    private int counter;
+
+
+    public IdWriter () {
+        this.counter = 0;
+    }
+
+
+    @Override
+    public KoralNode rewriteQuery (KoralNode node, KustvaktConfiguration config,
+            User user) {
+        if (node.get("@type").equals("koral:token")) {
+            String s = extractToken(node.rawNode());
+            if (s != null && !s.isEmpty())
+                node.put("idn", s + "_" + counter++);
+        }
+        return node;
+    }
+
+
+    private String extractToken (JsonNode token) {
+        JsonNode wrap = token.path("wrap");
+        if (!wrap.isMissingNode())
+            return wrap.path("key").asText();
+        return null;
+    }
+}
diff --git a/core/src/main/java/de/ids_mannheim/korap/rewrite/KoralNode.java b/core/src/main/java/de/ids_mannheim/korap/rewrite/KoralNode.java
new file mode 100644
index 0000000..a8870b7
--- /dev/null
+++ b/core/src/main/java/de/ids_mannheim/korap/rewrite/KoralNode.java
@@ -0,0 +1,301 @@
+package de.ids_mannheim.korap.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.utils.JsonUtils;
+
+import java.util.*;
+
+/**
+ * @author hanl
+ * @date 04/07/2015
+ */
+public class KoralNode {
+    private JsonNode node;
+    private KoralRewriteBuilder rewrites;
+    private boolean remove;
+
+
+    public KoralNode (JsonNode node) {
+        this.node = node;
+        this.rewrites = new KoralRewriteBuilder();
+        this.remove = false;
+    }
+    
+    public KoralNode (JsonNode node, KoralRewriteBuilder rewrites) {
+        this.node = node;
+        this.rewrites = rewrites;
+        this.remove = false;
+    }
+
+
+    public static KoralNode wrapNode (JsonNode node) {
+        return new KoralNode(node);
+    }
+    
+    public void buildRewrites (JsonNode node) {
+        this.rewrites.build(node);
+    }
+
+
+    public void buildRewrites () {
+        this.rewrites.build(this.node);
+    }
+
+
+    @Override
+    public String toString () {
+        return this.node.toString();
+    }
+
+
+    public void put (String name, Object value) {
+        if (this.node.isObject() && this.node.path(name).isMissingNode()) {
+            ObjectNode node = (ObjectNode) this.node;
+            if (value instanceof String)
+                node.put(name, (String) value);
+            else if (value instanceof Integer)
+                node.put(name, (Integer) value);
+            else if (value instanceof JsonNode)
+                node.put(name, (JsonNode) value);
+            this.rewrites.add("injection", name);
+        }
+        else
+            throw new UnsupportedOperationException(
+                    "node doesn't support this operation");
+    }
+
+
+    public void remove (Object identifier, RewriteIdentifier ident) {
+        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 (ident != null)
+            identifier = ident.toString();
+
+        if (set) {
+            this.rewrites.add("deletion", identifier);
+        }
+    }
+
+
+    public void replace (String name, Object value, RewriteIdentifier ident) {
+        if (this.node.isObject() && this.node.has(name)) {
+            ObjectNode n = (ObjectNode) this.node;
+            if (value instanceof String)
+                n.put(name, (String) value);
+            else if (value instanceof Integer)
+                n.put(name, (Integer) value);
+            else if (value instanceof JsonNode)
+                n.put(name, (JsonNode) value);
+
+            if (ident != null)
+                name = ident.toString();
+
+            this.rewrites.add("override", name);
+        }
+    }
+    
+    public void replaceAt (String path, Object value, RewriteIdentifier ident) {
+        if (this.node.isObject() && !this.node.at(path).isMissingNode()) {
+            ObjectNode n = (ObjectNode) this.node.at(path);
+            n.removeAll();
+            n.putAll((ObjectNode)value);
+
+            String name = path;
+            if (ident != null)
+                name = ident.toString();
+
+            this.rewrites.add("override", name);
+        }
+    }
+
+    public void set (String name, Object value, RewriteIdentifier ident) {
+        if (this.node.isObject()) {
+            ObjectNode n = (ObjectNode) this.node;
+            if (value instanceof String)
+                n.put(name, (String) value);
+            else if (value instanceof Integer)
+                n.put(name, (Integer) value);
+            else if (value instanceof JsonNode)
+                n.put(name, (JsonNode) value);
+
+
+            if (ident != null)
+                name = ident.toString();
+
+            this.rewrites.add("insertion", name);
+        }
+    }
+
+    public void setAll (ObjectNode other) {
+        if (this.node.isObject()) {
+            ObjectNode n = (ObjectNode) this.node;
+            n.setAll(other);
+        }
+        this.rewrites.add("insertion",null);
+    }
+
+    public String get (String name) {
+        if (this.node.isObject())
+            return this.node.path(name).asText();
+        return null;
+    }
+
+
+    public KoralNode at (String name) {
+//        this.node = this.node.at(name);
+//        return this;
+        return new KoralNode(this.node.at(name), this.rewrites);
+    }
+
+
+    public boolean has (Object ident) {
+        if (ident instanceof String)
+            return this.node.has((String) ident);
+        else if (ident instanceof Integer)
+            return this.node.has((int) ident);
+        return false;
+    }
+
+
+    public JsonNode rawNode () {
+        return this.node;
+    }
+
+
+    public void removeNode (RewriteIdentifier ident) {
+        this.rewrites.add("deletion", ident.toString());
+        this.remove = true;
+    }
+
+    public static class RewriteIdentifier {
+
+        private String key, value;
+
+
+        public RewriteIdentifier (String key, Object value) {
+            this.key = key;
+            this.value = value.toString();
+        }
+
+
+        @Override
+        public String toString () {
+            return key + "(" + value + ")";
+        }
+
+
+    }
+
+
+    public boolean isRemove () {
+        return this.remove;
+    }
+
+
+    public static class KoralRewriteBuilder {
+
+        private List<KoralRewrite> rewrites;
+
+
+        public KoralRewriteBuilder () {
+            this.rewrites = new ArrayList<>();
+        }
+
+
+        public KoralRewriteBuilder add (String op, Object scope) {
+            KoralRewrite rewrite = new KoralRewrite();
+            rewrite.setOperation(op);
+            if (scope !=null){
+                rewrite.setScope(scope.toString());
+            }
+            this.rewrites.add(rewrite);
+            return this;
+        }
+
+
+        public JsonNode build (JsonNode node) {
+            for (KoralRewrite rewrite : this.rewrites) {
+                if (rewrite.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(rewrite.map));
+                }
+                else if (node.isObject()) {
+                    ObjectNode n = (ObjectNode) node;
+                    List l = new LinkedList<>();
+                    l.add(JsonUtils.valueToTree(rewrite.map));
+                    n.put("rewrites", JsonUtils.valueToTree(l));
+                }
+                else {
+                    //fixme: matches in result will land here. rewrites need to be placed under root node - though then there might be unclear where they belong to
+                }
+
+            }
+            this.rewrites.clear();
+            return node;
+        }
+
+    }
+
+
+
+    private static class KoralRewrite {
+
+        private Map<String, String> map;
+
+
+        private KoralRewrite () {
+            this.map = new LinkedHashMap<>();
+            this.map.put("@type", "koral:rewrite");
+            this.map.put("src", "Kustvakt");
+        }
+
+
+        public KoralRewrite setOperation (String op) {
+            if (!op.startsWith("operation:"))
+                op = "operation:" + op;
+            this.map.put("operation", op);
+            return this;
+        }
+
+
+        public KoralRewrite setScope (String scope) {
+            this.map.put("scope", scope);
+            return this;
+        }
+
+    }
+
+
+    public boolean isMissingNode (String string) {
+        return this.node.at(string).isMissingNode();
+    }
+
+
+    public int size () {
+        return this.node.size();
+    }
+
+
+    public KoralNode get (int i) {
+//        this.node = this.node.get(i);
+        return this.wrapNode(this.node.get(i));
+    }
+    
+    
+}
diff --git a/core/src/main/java/de/ids_mannheim/korap/rewrite/LayerMapper.java b/core/src/main/java/de/ids_mannheim/korap/rewrite/LayerMapper.java
new file mode 100644
index 0000000..672f7e7
--- /dev/null
+++ b/core/src/main/java/de/ids_mannheim/korap/rewrite/LayerMapper.java
@@ -0,0 +1,110 @@
+package de.ids_mannheim.korap.rewrite;
+
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.user.Userdata;
+
+/**
+ * @author hanl
+ * @date 14/10/2014
+ */
+public class LayerMapper {
+
+    private Userdata settings;
+    private KustvaktConfiguration config;
+
+
+    public LayerMapper (KustvaktConfiguration config, Userdata settings) {
+        this.settings = settings;
+        this.config = config;
+    }
+
+
+    public LayerMapper (KustvaktConfiguration config) {
+        this.config = config;
+    }
+
+
+    /**
+     * find foundry entry in settings specific settings. Includes a
+     * call to #translateLayer to get the
+     * correct mapping for the layer denomination!
+     * 
+     * @param layer
+     * @return
+     */
+
+    //todo: make mapping configurable!
+    public String findFoundry (String layer) {
+        if (settings != null) {
+            switch (translateLayer(layer.toLowerCase().trim())) {
+                case "d":
+                    return (String) settings
+                            .get(Attributes.DEFAULT_REL_FOUNDRY);
+                case "c":
+                    return (String) settings
+                            .get(Attributes.DEFAULT_CONST_FOUNDRY);
+                case "pos":
+                    return (String) settings
+                            .get(Attributes.DEFAULT_POS_FOUNDRY);
+                case "lemma":
+                    return (String) settings
+                            .get(Attributes.DEFAULT_LEMMA_FOUNDRY);
+                case "surface":
+                    return "opennlp";
+                default:
+                    // if the layer is not in this specific listing, assume a default layer
+                    // like orth or other tokenization layers
+                    return null;
+            }
+        }
+        else {
+            switch (translateLayer(layer.toLowerCase().trim())) {
+                case "d":
+                    return config.getDefault_dep();
+                case "c":
+                    return config.getDefault_const();
+                case "pos":
+                    return config.getDefault_pos();
+                case "lemma":
+                    return config.getDefault_lemma();
+                case "surface":
+                    return config.getDefault_token();
+                    // refers to "structure" and is used for paragraphs or sentence boundaries
+                case "s":
+                    return "base";
+                default:
+                    // if the layer is not in this specific listing, assume a default layer
+                    // like orth or other tokenization layers
+                    return null;
+            }
+        }
+    }
+
+
+    // relevance: map to access control id references. p is usually mapped to pos, l to lemma, etc.
+    public String translateLayer (String layer) {
+        switch (layer.toLowerCase().trim()) {
+        //            case "pos":
+        //                return "p";
+        //            case "lemma":
+        //                return "l";
+            case "m":
+                return "msd";
+                //todo the orth layer does not need a foundry entry
+            case "orth":
+                return "surface";
+            case "t":
+                return "surface";
+            case "const":
+                return "c";
+            case "p":
+                return "pos";
+            case "l":
+                return "lemma";
+            default:
+                return layer;
+        }
+    }
+
+}
diff --git a/core/src/main/java/de/ids_mannheim/korap/rewrite/MetaConstraint.java b/core/src/main/java/de/ids_mannheim/korap/rewrite/MetaConstraint.java
new file mode 100644
index 0000000..a9380b9
--- /dev/null
+++ b/core/src/main/java/de/ids_mannheim/korap/rewrite/MetaConstraint.java
@@ -0,0 +1,39 @@
+package de.ids_mannheim.korap.rewrite;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.config.BeanInjectable;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.user.User;
+
+/**
+ * @author hanl
+ * @date 04/07/2015
+ */
+public class MetaConstraint implements RewriteTask.RewriteNodeAt {
+
+
+    @Override
+    public KoralNode rewriteQuery (KoralNode node, KustvaktConfiguration config,
+            User user) {
+        // redundant
+        if (node.rawNode().has("meta")) {
+            JsonNode meta = node.rawNode().path("meta");
+            //todo: check meta parameter
+            System.out.println("HAVE TO CHECK THE META ENTRIES");
+        }
+        return node;
+    }
+
+
+    @Override
+    public JsonNode rewriteResult (KoralNode node) {
+        return null;
+    }
+
+
+    @Override
+    public String at () {
+        return "/meta";
+    }
+
+}
diff --git a/core/src/main/java/de/ids_mannheim/korap/rewrite/RewriteHandler.java b/core/src/main/java/de/ids_mannheim/korap/rewrite/RewriteHandler.java
new file mode 100644
index 0000000..ddd7538
--- /dev/null
+++ b/core/src/main/java/de/ids_mannheim/korap/rewrite/RewriteHandler.java
@@ -0,0 +1,308 @@
+package de.ids_mannheim.korap.rewrite;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import de.ids_mannheim.korap.config.BeanInjectable;
+import de.ids_mannheim.korap.config.ContextHolder;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.JsonUtils;
+
+/**
+ * @author hanl
+ * @date 30/06/2015
+ */
+// todo: do post processing!
+//todo: load rewritenode and rewritequery automatically from classpath by default, but namespaced from package
+public class RewriteHandler{
+    //implements BeanInjectable {
+
+    private static Logger jlog = LogManager.getLogger(RewriteHandler.class);
+    private Collection<RewriteTask.IterableRewritePath> node_processors;
+    private Collection<RewriteTask.RewriteKoralToken> token_node_processors;
+    private Collection<RewriteTask> query_processors;
+
+    private Set<Class> failed_task_registration;
+    @Autowired
+    private KustvaktConfiguration config;
+    private ContextHolder beans;
+
+
+    public RewriteHandler (KustvaktConfiguration config) {
+        this();
+        this.config = config;
+    }
+
+
+    public RewriteHandler () {
+        this.node_processors = new HashSet<>();
+        this.token_node_processors = new HashSet<>();
+        this.query_processors = new LinkedHashSet<>();
+        this.failed_task_registration = new HashSet<>();
+        this.beans = null;
+    }
+
+    public Set getFailedProcessors () {
+        return this.failed_task_registration;
+    }
+
+
+    public boolean addProcessor (RewriteTask rewriter) {
+        if (rewriter instanceof RewriteTask.RewriteKoralToken)
+            return this.token_node_processors
+                    .add((RewriteTask.RewriteKoralToken) rewriter);
+        else if (rewriter instanceof RewriteTask.IterableRewritePath)
+            return this.node_processors
+                    .add((RewriteTask.IterableRewritePath) rewriter);
+        else if (rewriter instanceof RewriteTask.RewriteQuery
+                | rewriter instanceof RewriteTask.RewriteResult)
+            return this.query_processors.add(rewriter);
+
+        this.failed_task_registration.add(rewriter.getClass());
+        return false;
+    }
+
+
+    @Override
+    public String toString () {
+        StringBuilder b = new StringBuilder();
+        b.append("--------------------------");
+        b.append("pre/post: " + this.node_processors.toString()).append("\n")
+                .append("\n")
+                .append("query: " + this.query_processors.toString())
+                .append("\n")
+                .append("koraltoken: " + this.token_node_processors.toString());
+        b.append("---------------------------");
+        return b.toString();
+    }
+
+
+    /**
+     * expects extended RewriteNode/Query class with empty default
+     * constructor
+     * 
+     * @param rewriter
+     * @return boolean if rewriter class was successfully added to
+     *         rewrite handler!
+     */
+    public boolean add (Class<? extends RewriteTask> rewriter) {
+        RewriteTask task;
+        try {
+            Constructor c = rewriter.getConstructor();
+            task = (RewriteTask) c.newInstance();
+        }
+        catch (NoSuchMethodException | InvocationTargetException
+                | IllegalAccessException | InstantiationException e) {
+            this.failed_task_registration.add(rewriter);
+            return false;
+        }
+        return addProcessor(task);
+    }
+
+
+
+    public String processQuery (JsonNode root, User user)
+            throws KustvaktException {
+        RewriteProcess process = new RewriteProcess(root, user);
+        JsonNode pre = process.start(false);
+        return JsonUtils.toJSON(pre);
+    }
+
+
+    public String processQuery (String json, User user)
+            throws KustvaktException {
+        return processQuery(JsonUtils.readTree(json), user);
+    }
+
+
+    public String processResult (String json, User user)
+            throws KustvaktException {
+        return processResult(JsonUtils.readTree(json), user);
+    }
+
+
+    public String processResult (JsonNode node, User user)
+            throws KustvaktException {
+        RewriteProcess process = new RewriteProcess(node, user);
+        JsonNode pre = process.start(true);
+        return JsonUtils.toJSON(pre);
+    }
+
+
+    public void clear () {
+        this.node_processors.clear();
+        this.query_processors.clear();
+        this.token_node_processors.clear();
+    }
+
+
+//    public <T extends ContextHolder> void insertBeans (T beans) {
+//        this.beans = beans;
+//        this.config = beans.getConfiguration();
+//    }
+
+
+
+    public class RewriteProcess {
+
+        private static final boolean DEBUG = false;
+        private JsonNode root;
+        private User user;
+
+
+        private RewriteProcess (JsonNode root, User user) {
+            this.root = root;
+            this.user = user;
+        }
+
+
+        private KoralNode processNode (String key, JsonNode value,
+                boolean result) throws KustvaktException {
+            KoralNode kroot = KoralNode.wrapNode(value);
+            if (value.isObject()) {
+                if (value.has("operands")) {
+                    JsonNode ops = value.at("/operands");
+                    Iterator<JsonNode> it = ops.elements();
+                    while (it.hasNext()) {
+                        JsonNode next = it.next();
+                        KoralNode kn = processNode(key, next, result);
+                        if (kn.isRemove())
+                            it.remove();
+                    }
+                }
+                else if (value.path("@type").asText().equals("koral:token")) {
+                    // todo: koral:token nodes cannot be flagged for deletion --> creates the possibility for empty koral:token nodes
+                    rewrite(key, kroot,
+                            RewriteHandler.this.token_node_processors, result);
+                    return processNode(key, value.path("wrap"), result);
+                }
+                else {
+                    return rewrite(key, kroot,
+                            RewriteHandler.this.node_processors, result);
+                }
+            }
+            else if (value.isArray()) {
+                Iterator<JsonNode> it = value.elements();
+                while (it.hasNext()) {
+                    JsonNode next = it.next();
+                    KoralNode kn = processNode(key, next, result);
+                    if (kn.isRemove())
+                        it.remove();
+                }
+            }
+            return kroot;
+        }
+
+
+        private JsonNode start (boolean result) throws KustvaktException {
+            if (DEBUG){
+                jlog.debug("Running rewrite process on query "+ root);
+            }
+            if (root != null) {
+                Iterator<Map.Entry<String, JsonNode>> it = root.fields();
+                while (it.hasNext()) {
+                    Map.Entry<String, JsonNode> next = it.next();
+                    processNode(next.getKey(), next.getValue(), result);
+                }
+                processFixedNode(root, RewriteHandler.this.query_processors,
+                        result);
+            }
+            return root;
+        }
+
+
+        /**
+         * @param node
+         * @param tasks
+         * @return boolean true if node is to be removed from parent!
+         *         Only
+         *         applies if parent is an array node
+         */
+        private KoralNode rewrite (String rootNode, KoralNode node,
+                Collection<? extends RewriteTask> tasks, boolean result)
+                throws KustvaktException {
+            if (RewriteHandler.this.config == null)
+                throw new RuntimeException("KustvaktConfiguration must be set!");
+
+            for (RewriteTask task : tasks) {
+                if (DEBUG) {
+                    jlog.debug("running processor on node: " + node);
+                    jlog.debug("on processor: " + task.getClass().toString());
+                }
+
+                if (RewriteHandler.this.beans != null
+                        && task instanceof BeanInjectable)
+                    ((BeanInjectable) task)
+                            .insertBeans(RewriteHandler.this.beans);
+
+                if (task instanceof RewriteTask.IterableRewritePath) {
+                    RewriteTask.IterableRewritePath rw = (RewriteTask.IterableRewritePath) task;
+                    if (rw.path() != null && !rw.path().equals(rootNode)) {
+                        if (DEBUG){
+                            jlog.debug("skipping node: " + node);
+                        }
+                        continue;
+                    }
+                }
+                if (!result && task instanceof RewriteTask.RewriteQuery) {
+                    ((RewriteTask.RewriteQuery) task).rewriteQuery(node,
+                            RewriteHandler.this.config, this.user);
+                }
+                else if (task instanceof RewriteTask.RewriteResult) {
+                    ((RewriteTask.RewriteResult) task).rewriteResult(node);
+                }
+
+                if (node.isRemove()) {
+                    node.buildRewrites(this.root.at("/" + rootNode));
+                    break;
+                }
+                else
+                    node.buildRewrites();
+            }
+            return node;
+        }
+
+
+        // fixme: merge with processNode!
+        private void processFixedNode (JsonNode node,
+                Collection<RewriteTask> tasks, boolean post)
+                throws KustvaktException {
+            for (RewriteTask task : tasks) {
+                KoralNode next = KoralNode.wrapNode(node);
+                if (task instanceof RewriteTask.RewriteNodeAt) {
+                    RewriteTask.RewriteNodeAt rwa = (RewriteTask.RewriteNodeAt) task;
+                    if ((rwa.at() != null && !node.at(rwa.at()).isMissingNode()))
+                        next = next.at(rwa.at());
+                }
+
+                if (!post & task instanceof RewriteTask.RewriteQuery)
+                    next = ((RewriteTask.RewriteQuery) task).rewriteQuery(next,
+                            RewriteHandler.this.config, user);
+                else if (task instanceof RewriteTask.RewriteResult)
+                    ((RewriteTask.RewriteResult) task).rewriteResult(next);
+                next.buildRewrites();
+
+            }
+        }
+
+
+    }
+
+    public void defaultRewriteConstraints () {
+        this.add(FoundryInject.class);
+    }
+}
diff --git a/core/src/main/java/de/ids_mannheim/korap/rewrite/RewriteTask.java b/core/src/main/java/de/ids_mannheim/korap/rewrite/RewriteTask.java
new file mode 100644
index 0000000..79065a5
--- /dev/null
+++ b/core/src/main/java/de/ids_mannheim/korap/rewrite/RewriteTask.java
@@ -0,0 +1,75 @@
+package de.ids_mannheim.korap.rewrite;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.user.User;
+
+/**
+ * @author hanl
+ * @date 30/06/2015
+ */
+public interface RewriteTask {
+
+
+    /**
+     * unspecified query rewrite that gets injected the entire root
+     * node during preprocessing
+     */
+    interface RewriteQuery extends RewriteTask {
+        /**
+         * @param node
+         *            Json node in KoralNode wrapper
+         * @param config
+         *            {@link KustvaktConfiguration} singleton instance
+         *            to use default configuration parameters
+         * @param user
+         *            injected by rewrite handler if available. Might
+         *            cause {@link NullPointerException} if not
+         *            checked properly
+         * @return
+         */
+        KoralNode rewriteQuery (KoralNode node, KustvaktConfiguration config,
+                User user) throws KustvaktException;
+
+    }
+
+    /**
+     * Post processor targeted at result sets for queries
+     * {@link RewriteResult} queries will run
+     * after {@link IterableRewritePath} have been processed
+     */
+    interface RewriteResult extends RewriteTask {
+        JsonNode rewriteResult (KoralNode node) throws KustvaktException;
+    }
+
+    /**
+     * nodes subject to rewrites at fixed json pointer location.
+     * Json-pointer based rewrites are processed after iterable
+     * rewrites
+     * Deletion via KoralNode not allowed. Supports pre- and
+     * post-processing
+     */
+    interface RewriteNodeAt extends RewriteQuery, RewriteResult {
+        String at ();
+    }
+
+    /**
+     * terminal object nodes that are subject to rewrites through node
+     * iteration
+     * (both object and array node iteration supported)
+     */
+    interface IterableRewritePath extends RewriteQuery, RewriteResult {
+        String path ();
+    }
+
+    /**
+     * koral token nodes that are subject to rewrites
+     * Be aware that node rewrites are processed before query
+     * rewrites. Thus query rewrite may override previous node
+     * rewrites {@link RewriteKoralToken} rewrite DOES NOT support the
+     * deletion of the respective node
+     */
+    interface RewriteKoralToken extends RewriteQuery {}
+
+}
diff --git a/core/src/main/java/de/ids_mannheim/korap/rewrite/TreeConstraint.java b/core/src/main/java/de/ids_mannheim/korap/rewrite/TreeConstraint.java
new file mode 100644
index 0000000..1315c7a
--- /dev/null
+++ b/core/src/main/java/de/ids_mannheim/korap/rewrite/TreeConstraint.java
@@ -0,0 +1,81 @@
+package de.ids_mannheim.korap.rewrite;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.config.BeanInjectable;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.user.User;
+
+/**
+ * #ELEM(W ANA=N)
+ * <p/>
+ * {
+ * "@context":
+ * "http://korap.ids-mannheim.de/ns/koral/0.3/context.jsonld",
+ * "errors": [],
+ * "warnings": [],
+ * "messages": [],
+ * "collection": {},
+ * "query": {
+ * "@type": "koral:span",
+ * "key": "w",
+ * "attr": {
+ * "@type": "koral:term",
+ * "layer": "p",
+ * "key": "N",
+ * "match": "match:eq"
+ * }
+ * },
+ * "meta": {}
+ * }
+ * <p/>
+ * <p/>
+ * email reference:
+ * Hallo Michael,
+ * mir fiel gestern bei der neuen KoralQuery Serialisierung noch ein
+ * Fall
+ * für default-Werte ein, die zumindest für viele Beispiele, die wir
+ * haben,
+ * relevant ist: Wenn ein koral:term in einem koral:span gewrappt ist,
+ * dann
+ * kann er eventuell nur einen Schlüssel haben ("s" oder "p" von "<s>"
+ * oder
+ * "<p>". In diesem Fall wäre der default layer "s" und die default
+ * foundry
+ * "base". (Im alten KoralQuery wurden spans nicht gewrappt - der Fall
+ * sollte aber erstmal weiter unterstützt werden.)
+ * Viele Grüße,
+ * Nils
+ * 
+ * @author hanl
+ * @date 02/07/2015
+ */
+public class TreeConstraint implements RewriteTask.RewriteNodeAt {
+
+    private String pointer;
+
+
+    public TreeConstraint () {
+        super();
+    }
+
+
+    @Override
+    public KoralNode rewriteQuery (KoralNode node, KustvaktConfiguration config,
+            User user) {
+        System.out.println("FIND PATH " + node.rawNode().findParent(pointer));
+
+        return node;
+    }
+
+
+    @Override
+    public JsonNode rewriteResult (KoralNode node) {
+        return null;
+    }
+
+
+    @Override
+    public String at () {
+        return null;
+    }
+}