collection query builder
diff --git a/src/main/java/de/ids_mannheim/korap/config/Attributes.java b/src/main/java/de/ids_mannheim/korap/config/Attributes.java
new file mode 100644
index 0000000..8e607eb
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/config/Attributes.java
@@ -0,0 +1,164 @@
+package de.ids_mannheim.korap.config;
+
+public class Attributes {
+
+    public static final String AUTHORIZATION = "Authorization";
+    public static final String SESSION_AUTHENTICATION = "session_token";
+    public static final String API_AUTHENTICATION = "api_token";
+    public static final String OAUTH2_AUTHORIZATION = "bearer";
+    public static final String OPENID_AUTHENTICATION = "id_token";
+    public static final String BASIC_AUTHENTICATION = "basic";
+
+    public static final String CLIENT_ID = "client_id";
+    public static final String CLIENT_SECRET = "client_secret";
+    public static final String SCOPES = "scopes";
+
+    public static final String PUBLIC_GROUP = "public";
+
+    public static final String SERVICE_ACCESS = "service_access";
+    public static final String USER = "KorapUser";
+    public static final String SHIBUSER = "ShibUser";
+    public static final String DEMO_DISPLAY = "Anonymous";
+    public static final String DEMOUSER_PASSWORD = "demo";
+
+    public static final String SETTINGS = "LocalSettings";
+    //    public static final String STORAGE_SETTINGS = "StorageSettings";
+
+    public static final String QUERY_ABBREVIATION = "Q";
+    public static final String LAYER = "layer";
+
+    public static final String TYPE = "type";
+
+    public static final String ID = "ID";
+    @Deprecated
+    //refactor
+    public static final String UID = "accountID";
+    public static final String USERNAME = "username";
+    public static final String PASSWORD = "password";
+    public static final String GENDER = "gender";
+    public static final String FIRSTNAME = "firstName";
+    public static final String LASTNAME = "lastName";
+    public static final String PHONE = "phone";
+    public static final String INSTITUTION = "institution";
+    public static final String EMAIL = "email";
+    public static final String ADDRESS = "address";
+    public static final String COUNTRY = "country";
+    public static final String IPADDRESS = "ipaddress";
+    // deprcated, use created
+    public static final String ACCOUNT_CREATION = "account_creation";
+    public static final String ACCOUNTLOCK = "account_lock";
+    public static final String ACCOUNTLINK = "account_link";
+    public static final String URI = "uri";
+    public static final String URI_FRAGMENT = "uri_fragment";
+    public static final String URI_EXPIRATION = "uri_expiration";
+    public static final String PRIVATE_USAGE = "privateUsage";
+
+    /**
+     * token context
+     */
+    public static final String TOKEN = "authToken";
+    public static final String TOKEN_EXPIRATION = "tokenExpires";
+    public static final String TOKEN_CREATION = "tokenCreated";
+    public static final String USER_AGENT = "User-Agent";
+    public static final String HOST = "userIP";
+
+    public static final String QUERY_PARAM_URI = "uri";
+    public static final String QUERY_PARAM_USER = "user";
+
+    /**
+     * shibboleth attribute names
+     */
+    public static final String EPPN = "eppn";
+    public static final String COMMON_NAME = "cn";
+    public static final String SURNAME = "sn";
+
+    public static final String EDUPERSON = "eduPersonPrincipalName";
+    public static final String CN = "cn";
+    public static final String MAIL = "mail";
+    public static final String EDU_AFFIL = "eduPersonScopedAffiliation";
+
+    /**
+     * resource mappings
+     */
+
+    public static final String RID = "id";
+    public static final String OWNER = "owner";
+    public static final String NAME = "name";
+    public static final String DESCRIPTION = "description";
+
+    public static final String CORPUS_SIGLE = "corpusSigle";
+
+    public static final String REF_CORPUS = "refCorpus";
+    public static final String QUERY = "query";
+    public static final String CACHE = "cache";
+    public static final String DOCIDS = "docIDs";
+    public static final String FOUNDRIES = "foundries";
+    public static final String DEFAULT_VALUE = "defaultColl";
+
+    public static final String FILE_FORMAT_FOR_EXPORT = "fileFormatForExport";
+    public static final String FILENAME_FOR_EXPORT = "fileNameForExport";
+    @Deprecated
+    public static final String ITEM_FOR_SIMPLE_ANNOTATION = "itemForSimpleAnnotation";
+    public static final String LEFT_CONTEXT_ITEM_FOR_EXPORT = "leftContextItemForExport";
+    public static final String LEFT_CONTEXT_SIZE_FOR_EXPORT = "leftContextSizeForExport";
+    public static final String LOCALE = "locale";
+    public static final String LEFT_CONTEXT_ITEM = "leftContextItem";
+    public static final String LEFT_CONTEXT_SIZE = "leftContextSize";
+    public static final String RIGHT_CONTEXT_ITEM = "rightContextItem";
+    public static final String RIGHT_CONTEXT_ITEM_FOR_EXPORT = "rightContextItemForExport";
+    public static final String RIGHT_CONTEXT_SIZE = "rightContextSize";
+    public static final String RIGHT_CONTEXT_SIZE_FOR_EXPORT = "rightContextSizeForExport";
+    public static final String SELECTED_COLLECTION = "selectedCollection";
+    public static final String QUERY_LANGUAGE = "queryLanguage";
+    public static final String PAGE_LENGTH = "pageLength";
+    public static final String METADATA_QUERY_EXPERT_MODUS = "metadataQueryExpertModus";
+    @Deprecated
+    public static final String SEARCH_SETTINGS_TAB = "searchSettingsTab";
+    @Deprecated
+    public static final String SELECTED_BROWSER_PROPERTY = "selectedBrowserProperty";
+    @Deprecated
+    public static final String SELECTED_CONTEXT_ITEM = "selectedContextItem";
+    @Deprecated
+    public static final String SELECTED_GRAPH_TYPE = "selectedGraphType";
+    @Deprecated
+    public static final String SELECTED_SORT_TYPE = "selectedSortType";
+    @Deprecated
+    public static final String SELECTED_VIEW_FOR_SEARCH_RESULTS = "selectedViewForSearchResults";
+    public static final String COLLECT_AUDITING_DATA = "collectData";
+
+    /**
+     * default layers
+     */
+    public static final String DEFAULT_POS_FOUNDRY = "POSFoundry";
+    public static final String DEFAULT_LEMMA_FOUNDRY = "lemmaFoundry";
+    public static final String DEFAULT_CONST_FOUNDRY = "constFoundry";
+    public static final String DEFAULT_REL_FOUNDRY = "relFoundry";
+
+    /**
+     * db column keys
+     */
+
+    public static final String SELF_REF = "self";
+
+    public static final String SYM_USE = "sym_use";
+    public static final String COMMERCIAL = "commercial";
+    public static final String LICENCE = "licence";
+    public static final String QUERY_ONLY = "query_only";
+    public static final String EXPORT = "export";
+    public static final String TIME_SPANS = "spans";
+    public static final String RANGE = "range";
+
+    public static final String GROUP_ID = "group_id";
+    public static final String CREATED = "created";
+    public static final String CREATOR = "creator";
+    public static final String ENABLED = "enabled";
+    public static final String EXPIRE = "expired";
+    public static final String TARGET_ID = "target_id";
+    public static final String IP_RANG = "ip_range";
+    public static final String PERSISTENT_ID = "persistent_id";
+    public static final String DISABLED = "disabled";
+    public static final String USER_ID = "user_id";
+    public static final String PARENT_ID = "parent_id";
+    //    public static final String
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/KoralCollectionQueryBuilder.java b/src/main/java/de/ids_mannheim/korap/utils/KoralCollectionQueryBuilder.java
new file mode 100644
index 0000000..a736b1d
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/KoralCollectionQueryBuilder.java
@@ -0,0 +1,196 @@
+package de.ids_mannheim.korap.utils;
+
+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.query.serialize.CollectionQueryProcessor;
+import edu.emory.mathcs.backport.java.util.Arrays;
+
+import java.util.Map;
+
+/**
+ * convenience builder class for collection query
+ * 
+ * @author hanl
+ * @date 16/09/2014
+ */
+public class KoralCollectionQueryBuilder {
+
+    public enum EQ {
+        EQUAL, UNEQUAL
+    }
+
+    private boolean verbose;
+    private JsonNode base;
+    private StringBuilder builder;
+
+
+    public KoralCollectionQueryBuilder() {
+        this(false);
+    }
+
+
+    public KoralCollectionQueryBuilder(boolean verbose) {
+        this.verbose = verbose;
+        this.builder = new StringBuilder();
+        this.base = null;
+    }
+
+
+    /**
+     * raw method for field - value pair adding. Supports all
+     * operators (leq, geq, contains, etc.)
+     * 
+     * @param field
+     * @param op
+     * @param value
+     * @return
+     */
+    public KoralCollectionQueryBuilder fieldValue (String field, String op,
+                                                   String value) {
+        this.builder.append(field + op + value);
+        return this;
+    }
+
+
+    /**
+     * element can be a more complex sub query like
+     * (textClass=freizeit & Attributes.CORPUS_SIGLE=WPD)
+     * 
+     * @param query
+     *            will be enclosed by parenthesis in order to make sub
+     *            query
+     *            element
+     * @return
+     */
+    public KoralCollectionQueryBuilder with(String query) {
+        if (!query.startsWith("(") && !query.endsWith(")"))
+            query = "(" + query + ")";
+        this.builder.append(query);
+        return this;
+    }
+
+
+
+
+
+    public KoralCollectionQueryBuilder and () {
+        this.builder.append(" & ");
+        return this;
+    }
+
+
+    public KoralCollectionQueryBuilder or () {
+        this.builder.append(" | ");
+        return this;
+    }
+
+
+    public Object rebaseCollection () {
+        if (builder.length() == 0 && base == null)
+            return null;
+
+        JsonNode request = null;
+        if (builder.length() != 0) {
+            CollectionQueryProcessor tree = new CollectionQueryProcessor(
+                    this.verbose);
+            tree.process(this.builder.toString());
+            request = JsonUtils.valueToTree(tree.getRequestMap());
+        }
+
+        if (base != null) {
+            // check that collection non empty
+            if (request != null) {
+                JsonNode tobase = request.at("/collection");
+                request = base.deepCopy();
+                JsonNode result = JsonBuilder.buildDocGroup("and",
+                        request.at("/collection"), tobase);
+                ((ObjectNode) request).put("collection", result);
+            }
+        }
+        return request;
+    }
+
+    public JsonNode mergeWith(JsonNode node) {
+        if (base != null) {
+            // check that collection non empty
+            if (node != null) {
+                JsonNode tobase = node.at("/collection");
+                JsonNode base = this.base.deepCopy();
+                JsonNode result = JsonBuilder.buildDocGroup("and",
+                        base.at("/collection"), tobase);
+                ((ObjectNode) base).put("collection", result);
+                return base;
+            }
+            return null;
+        }
+        return null;
+    }
+
+
+    /**
+     * sets base query. All consequent queries are added to the first
+     * koral:docGroup within the collection base query
+     * If no group in base query, consequent queries are skipped.
+     * 
+     * @param query
+     */
+    public KoralCollectionQueryBuilder setBaseQuery (String query) {
+        this.base = JsonUtils.readTree(query);
+        return this;
+    }
+
+
+    public String toJSON () {
+        return JsonUtils.toJSON(rebaseCollection());
+    }
+
+
+    @Override
+    public String toString () {
+        return this.builder.toString();
+    }
+
+
+    @Deprecated
+    private KoralCollectionQueryBuilder appendToBaseGroup (JsonNode node) {
+        if (base.at("/collection/@type").asText().equals("koral:docGroup")) {
+            ArrayNode group = (ArrayNode) base.at("/collection/operands");
+            if (node instanceof ArrayNode)
+                group.addAll((ArrayNode) node);
+            else
+                group.add(node);
+        }
+        else
+            throw new IllegalArgumentException("No group found to add to!");
+        // fixme: if base is a doc only, this function is not supported. requirement is a koral:docGroup, since
+        // combination operator is unknown otherwise
+        return this;
+    }
+
+    private static class JsonBuilder {
+
+        public static ObjectNode buildDoc (String key, String value) {
+            ObjectNode node = JsonUtils.createObjectNode();
+            node.put("@type", "koral:doc");
+            // eq.equals(EQ.EQUAL) ? "match:eq" : "match:ne"
+            node.put("match", "match:eq");
+            node.put("key", key);
+            node.put("value", value);
+            return node;
+        }
+
+
+        public static ObjectNode buildDocGroup (String op, JsonNode ... groups) {
+            ObjectNode node = JsonUtils.createObjectNode();
+            node.put("@type", "koral:docGroup");
+            node.put("operation", "operation:" + op);
+            ArrayNode ops = JsonUtils.createArrayNode();
+            ops.addAll(Arrays.asList(groups));
+            node.put("operands", ops);
+            return node;
+        }
+
+    }
+
+}