Merge branch 'master' of ssh://korap.ids-mannheim.de:29418/KorAP/Koral
diff --git a/pom.xml b/pom.xml
index 2516d11..e59fd5c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -52,6 +52,11 @@
     </dependency>
     <dependency>
       <groupId>org.antlr</groupId>
+      <artifactId>antlr4-runtime</artifactId>
+      <version>4.5.1</version>
+    </dependency>
+    <dependency>
+      <groupId>org.antlr</groupId>
       <artifactId>antlr4-maven-plugin</artifactId>
       <version>4.2</version>
     </dependency>
@@ -117,6 +122,26 @@
       <artifactId>slf4j-log4j12</artifactId>
       <version>1.7.5</version>
     </dependency>
+    <dependency>
+      <groupId>eu.clarin.sru.fcs</groupId>
+      <artifactId>fcs-simple-endpoint</artifactId>
+      <version>1.3.0</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.lucene</groupId>
+      <artifactId>lucene-core</artifactId>
+      <version>5.2.1</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.lucene</groupId>
+      <artifactId>lucene-analyzers-common</artifactId>
+      <version>5.2.1</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.lucene</groupId>
+      <artifactId>lucene-queryparser</artifactId>
+      <version>5.2.1</version>
+    </dependency>
   </dependencies>
   <build>
     <sourceDirectory>${basedir}/src/main/java</sourceDirectory>
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralContext.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralContext.java
new file mode 100644
index 0000000..abdde0b
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralContext.java
@@ -0,0 +1,22 @@
+package de.ids_mannheim.korap.query.object;
+
+/**
+ * @author margaretha
+ * 
+ */
+public enum KoralContext {
+	SENTENCE("s"), PARAGRAPH("p"), TEXT("t");
+	
+	private final String key;
+	public static final String FOUNDRY ="base";
+	public static final String LAYER="s"; // surface
+	
+	KoralContext(String key){
+		this.key = key;
+	}
+
+	public String getKey() {
+		return key;
+	}
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralGroup.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralGroup.java
new file mode 100644
index 0000000..090ecbc
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralGroup.java
@@ -0,0 +1,153 @@
+package de.ids_mannheim.korap.query.object;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import de.ids_mannheim.korap.query.serialize.MapBuilder;
+import de.ids_mannheim.korap.query.object.KoralObject;
+import de.ids_mannheim.korap.query.object.KoralOperation;
+import de.ids_mannheim.korap.query.object.KoralType;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class KoralGroup implements KoralObject {
+
+    private static final KoralType type = KoralType.GROUP;
+
+    private KoralOperation operation;
+
+    private boolean inOrder = false;
+    private List<KoralObject> operands;
+    private List<Distance> distances;
+    private List<Frame> frames;
+
+    public KoralGroup (KoralOperation operation) {
+        this.operation = operation;
+    }
+
+    public boolean isInOrder() {
+        return inOrder;
+    }
+
+    public void setInOrder(boolean inOrder) {
+        this.inOrder = inOrder;
+    }
+
+    public List<KoralObject> getOperands() {
+		return operands;
+	}
+    
+    public void setOperands(List<KoralObject> operands) {
+		this.operands = operands;
+	}
+
+    public List<Distance> getDistances() {
+        return distances;
+    }
+
+    public void setDistances(List<Distance> distances) {
+        this.distances = distances;
+    }
+    
+    public List<Frame> getFrames() {
+		return frames;
+	}
+
+	public void setFrames(List<Frame> frames) {
+		this.frames = frames;
+	}
+
+    @Override
+    public Map<String, Object> buildMap() {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+        map.put("@type", type.toString());
+        map.put("operation", operation.toString());
+
+        if (getDistances() != null) {
+            map.put("inOrder", isInOrder());
+            List<Map<String, Object>> distanceList = new ArrayList<Map<String, Object>>();
+            for (Distance d : getDistances()) {
+                distanceList.add(d.buildMap());
+            }
+            map.put("distances", distanceList);
+        }
+
+        List<Map<String, Object>> operandList = new ArrayList<Map<String, Object>>();
+        for (Object o : getOperands()) {
+            operandList.add(MapBuilder.buildQueryMap(o));
+        }
+        map.put("operands", operandList);
+        return map;
+    }
+
+    public enum Frame{
+		SUCCEDS("succeeds"), SUCCEDS_DIRECTLY("succeedsDirectly"), OVERLAPS_RIGHT("overlapsRight"), 
+		ALIGNS_RIGHT("alignsRight"), IS_WITHIN("isWithin"), STARTS_WITH("startsWith"), 
+		MATCHES("matches"), ALIGNS_LEFT("alignsLeft"), IS_AROUND("isAround"), ENDS_WITH("endsWith"),
+		OVERLAPS_LEFT("overlapsLeft"), PRECEEDS_DIRECTLY("precedesDirectly"), PRECEDES("precedes");
+		
+		private String value;
+		Frame(String value) {
+			this.value = value;
+		}
+		
+		@Override
+		public String toString() {
+			return "frame:"+value;
+		}
+	}
+    
+    public class Distance implements KoralObject {
+
+        private final KoralType type = KoralType.DISTANCE;
+        private String key;
+        private String min;
+        private String max;
+
+        public Distance (String key, int min, int max) {
+            this.key = key;
+            this.min = String.valueOf(min);
+            this.max = String.valueOf(max);
+        }
+
+        public String getKey() {
+            return key;
+        }
+
+        public void setKey(String key) {
+            this.key = key;
+        }
+
+        public String getMin() {
+            return min;
+        }
+
+        public void setMin(String min) {
+            this.min = min;
+        }
+
+        public String getMax() {
+            return max;
+        }
+
+        public void setMax(String max) {
+            this.max = max;
+        }
+
+        @Override
+        public Map<String, Object> buildMap() {
+            Map<String, Object> distanceMap = new LinkedHashMap<String, Object>();
+            distanceMap.put("@type", type.toString());
+            distanceMap.put("key", getKey());
+            distanceMap.put("min", getMin());
+            distanceMap.put("max", getMax());
+            return distanceMap;
+
+        }
+
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralMatchOperator.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralMatchOperator.java
new file mode 100644
index 0000000..bd5f6ea
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralMatchOperator.java
@@ -0,0 +1,20 @@
+package de.ids_mannheim.korap.query.object;
+
+/**
+ * @author margaretha
+ * 
+ */
+public enum KoralMatchOperator {
+    EQUALS("eq"), NOT_EQUALS("ne");
+
+    String value;
+
+    KoralMatchOperator (String value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return "match:" + value;
+    };
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralObject.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralObject.java
new file mode 100644
index 0000000..21fcf67
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralObject.java
@@ -0,0 +1,12 @@
+package de.ids_mannheim.korap.query.object;
+
+import java.util.Map;
+
+/**
+ * @author margaretha
+ * 
+ */
+public interface KoralObject {
+
+    public Map<String, Object> buildMap();
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralOperation.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralOperation.java
new file mode 100644
index 0000000..972bfd3
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralOperation.java
@@ -0,0 +1,15 @@
+package de.ids_mannheim.korap.query.object;
+
+/**
+ * @author margaretha
+ * 
+ */
+public enum KoralOperation {
+    SEQUENCE, POSITION, DISJUNCTION, REPETITION, CLASS, MERGE, RELATION;
+
+    @Override
+    public String toString() {
+        return "operation:" + super.toString().toLowerCase();
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralRelation.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralRelation.java
new file mode 100644
index 0000000..84a1ff8
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralRelation.java
@@ -0,0 +1,15 @@
+package de.ids_mannheim.korap.query.object;
+
+/**
+ * @author margaretha
+ * 
+ */
+public enum KoralRelation {
+
+    AND, OR;
+
+    @Override
+    public String toString() {
+        return "relation:" + super.toString().toLowerCase();
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralSpan.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralSpan.java
new file mode 100644
index 0000000..4310bb9
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralSpan.java
@@ -0,0 +1,62 @@
+package de.ids_mannheim.korap.query.object;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import de.ids_mannheim.korap.query.serialize.util.KoralException;
+import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
+
+/**
+ * @author margaretha
+ *
+ */
+public class KoralSpan implements KoralObject{
+	
+	private static final KoralType type = KoralType.SPAN;
+	private final KoralObject wrappedObject;
+	private KoralObject attribute;
+	
+	public KoralSpan(KoralTerm term) throws KoralException {
+		if (term == null){
+			throw new KoralException(StatusCodes.MALFORMED_QUERY, "KoralSpan must not wrap null.");
+		}
+		this.wrappedObject = term;
+	}
+	
+	public KoralSpan(KoralTermGroup termGroup) throws KoralException {
+		if (termGroup == null){
+			throw new KoralException(StatusCodes.MALFORMED_QUERY,"KoralSpan must not wrap null.");
+		}
+		this.wrappedObject = termGroup;
+	}
+
+	public KoralObject getWrappedObject() {
+		return wrappedObject;
+	}
+	
+	public KoralObject getAttribute() {
+		return attribute;
+	}
+	
+	public void setAttribute(KoralTerm attribute) {
+		this.attribute = attribute;
+	}
+	
+	public void setAttribute(KoralTermGroup attributes) {
+		this.attribute = attributes;
+	}
+	
+	@Override
+	public Map<String, Object> buildMap() {
+		Map<String, Object> map = new LinkedHashMap<String, Object>();
+        map.put("@type", type.toString());
+        if (wrappedObject == null){
+        	throw new NullPointerException("KoralSpan does not have a wrapped object.");
+        }
+        map.put("wrap", wrappedObject.buildMap());
+        if(attribute != null){
+        	map.put("wrap", attribute.buildMap());
+        }
+		return map;
+	}
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralTerm.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralTerm.java
new file mode 100644
index 0000000..f9f60cd
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralTerm.java
@@ -0,0 +1,138 @@
+package de.ids_mannheim.korap.query.object;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import de.ids_mannheim.korap.query.serialize.util.KoralException;
+import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
+import de.ids_mannheim.korap.query.object.KoralMatchOperator;
+import de.ids_mannheim.korap.query.object.KoralType;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class KoralTerm implements KoralObject {
+
+    private static final KoralType koralType = KoralType.TERM;
+  
+    private final String key;
+    private String value;
+    private String layer;
+    private String foundry;
+    private KoralMatchOperator operator; // match
+    
+    private KoralTermType type;
+    
+    private boolean caseSensitive = true;
+
+    public KoralTerm(String key) throws KoralException {
+    	if (key == null){
+    		throw new KoralException(StatusCodes.MALFORMED_QUERY, 
+    				"KoralTerm key cannot be null.");
+    	}
+    	this.key = key;
+    }
+    
+    public KoralTerm(KoralContext context) throws KoralException {
+    	if (context.getKey() == null){
+    		throw new KoralException(StatusCodes.MALFORMED_QUERY, 
+    				"KoralTerm key cannot be null.");
+    	}
+    	this.key = context.getKey();
+    	this.foundry = KoralContext.FOUNDRY;
+    	this.layer = KoralContext.LAYER;
+	}
+    
+    public String getValue() {
+		return value;
+	}
+    
+    public void setValue(String value) {
+		this.value = value;
+	}
+    
+	public String getLayer() {
+        return layer;
+    }
+
+    public void setLayer(String layer) {
+        this.layer = layer;
+    }
+
+    public String getFoundry() {
+        return foundry;
+    }
+
+    public void setFoundry(String foundry) {
+        this.foundry = foundry;
+    }
+
+    public KoralMatchOperator getOperator() {
+		return operator;
+	}
+    
+    public void setOperator(KoralMatchOperator operator) {
+		this.operator = operator;
+	}
+
+    public String getKey() {
+        return key;
+    }
+
+    public KoralTermType getType() {
+        return type;
+    }
+
+    public void setType(KoralTermType regex) {
+        this.type = regex;
+    }
+
+    public boolean isCaseSensitive() {
+        return caseSensitive;
+    }
+
+    public void setCaseSensitive(boolean isCaseSensitive) {
+        this.caseSensitive = isCaseSensitive;
+    }
+
+    @Override
+    public Map<String, Object> buildMap() {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+        map.put("@type", koralType.toString());
+        if (!isCaseSensitive()) {
+            map.put("caseInsensitive", "true");
+        }
+        
+        map.put("key", getKey());
+        if (value != null){
+        	map.put("value", getValue());
+        }
+        map.put("foundry", getFoundry());
+        map.put("layer", getLayer());
+        if (type != null){
+        	map.put("type", getType().toString());
+        }
+		if (operator !=null){
+			map.put("match", getOperator().toString());
+		}
+        return map;
+    }
+
+    public enum KoralTermType {
+        STRING("type:string"), REGEX("type:regex"), WILDCARD("type:wildcard"), PUNCT(
+                "type:punct");
+
+        String value;
+
+        KoralTermType (String value) {
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return value;
+        }
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralTermGroup.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralTermGroup.java
new file mode 100644
index 0000000..eaae803
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralTermGroup.java
@@ -0,0 +1,57 @@
+package de.ids_mannheim.korap.query.object;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import de.ids_mannheim.korap.query.serialize.MapBuilder;
+import de.ids_mannheim.korap.query.serialize.util.KoralException;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class KoralTermGroup implements KoralObject {
+
+    private static final KoralType type = KoralType.TERMGROUP;
+
+    private String relation;
+    private List<KoralObject> operands = new ArrayList<KoralObject>();
+
+    public KoralTermGroup (KoralRelation relation, List<KoralObject> operands)
+            throws KoralException {
+        this.relation = relation.toString();
+        this.operands = operands;
+    }
+
+    public String getRelation() {
+        return relation;
+    }
+
+    public void setRelation(String relation) {
+        this.relation = relation;
+    }
+
+    public List<KoralObject> getOperands() {
+        return operands;
+    }
+
+    public void setOperands(List<KoralObject> operands) {
+        this.operands = operands;
+    }
+
+    @Override
+    public Map<String, Object> buildMap() {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+        map.put("@type", type.toString());
+        map.put("relation", getRelation());
+
+        List<Map<String, Object>> operandList = new ArrayList<Map<String, Object>>();
+        for (Object o : getOperands()) {
+            operandList.add(MapBuilder.buildQueryMap(o));
+        }
+        map.put("operands", operandList);
+        return map;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralToken.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralToken.java
new file mode 100644
index 0000000..06a39ce
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralToken.java
@@ -0,0 +1,36 @@
+package de.ids_mannheim.korap.query.object;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import de.ids_mannheim.korap.query.object.KoralObject;
+import de.ids_mannheim.korap.query.object.KoralType;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class KoralToken implements KoralObject {
+
+    private final static KoralType type = KoralType.TOKEN;
+    private KoralObject wrappedObject;
+
+    public KoralToken (KoralObject wrappedObject) {
+        this.wrappedObject = wrappedObject;
+    }
+
+    public KoralObject getWrappedObject() {
+		return wrappedObject;
+	}
+    public void setWrappedObject(KoralObject wrappedObject) {
+		this.wrappedObject = wrappedObject;
+	}
+
+    @Override
+    public Map<String, Object> buildMap() {
+        Map<String, Object> map = new LinkedHashMap<String, Object>();
+        map.put("@type", type.toString());
+        map.put("wrap", wrappedObject.buildMap());
+        return map;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/object/KoralType.java b/src/main/java/de/ids_mannheim/korap/query/object/KoralType.java
new file mode 100644
index 0000000..df7608f
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/object/KoralType.java
@@ -0,0 +1,23 @@
+package de.ids_mannheim.korap.query.object;
+
+/**
+ * @author margaretha
+ * 
+ */
+public enum KoralType {
+    TERMGROUP("koral:termGroup"), TERM("koral:term"), TOKEN("koral:token"), SPAN(
+            "koral:span"), GROUP("koral:group"), BOUNDARY("koral:boundary"), RELATION(
+            "koral:relation"), DISTANCE("koral:distance"), REFERENCE(
+            "koral:reference");
+
+    String value;
+
+    KoralType (String value) {
+        this.value = value;
+    }
+
+    @Override
+    public String toString() {
+        return value;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/ExpressionParser.java b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/ExpressionParser.java
new file mode 100644
index 0000000..488b4c8
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/ExpressionParser.java
@@ -0,0 +1,219 @@
+package de.ids_mannheim.korap.query.parse.fcsql;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+import de.ids_mannheim.korap.query.object.KoralMatchOperator;
+import de.ids_mannheim.korap.query.object.KoralObject;
+import de.ids_mannheim.korap.query.object.KoralRelation;
+import de.ids_mannheim.korap.query.object.KoralTerm;
+import de.ids_mannheim.korap.query.object.KoralTerm.KoralTermType;
+import de.ids_mannheim.korap.query.object.KoralTermGroup;
+import de.ids_mannheim.korap.query.object.KoralToken;
+import de.ids_mannheim.korap.query.serialize.util.KoralException;
+import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
+import eu.clarin.sru.server.fcs.parser.Expression;
+import eu.clarin.sru.server.fcs.parser.ExpressionAnd;
+import eu.clarin.sru.server.fcs.parser.ExpressionGroup;
+import eu.clarin.sru.server.fcs.parser.ExpressionNot;
+import eu.clarin.sru.server.fcs.parser.ExpressionOr;
+import eu.clarin.sru.server.fcs.parser.Operator;
+import eu.clarin.sru.server.fcs.parser.QueryNode;
+import eu.clarin.sru.server.fcs.parser.RegexFlag;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class ExpressionParser {
+
+    private static final String FOUNDRY_CNX = "cnx";
+    private static final String FOUNDRY_OPENNLP = "opennlp";
+    private static final String FOUNDRY_TT = "tt";
+    private static final String FOUNDRY_MATE = "mate";
+    private static final String FOUNDRY_XIP = "xip";
+
+    private List<String> supportedFoundries = Arrays
+            .asList(new String[] { FOUNDRY_CNX, FOUNDRY_OPENNLP, FOUNDRY_TT,
+                    FOUNDRY_MATE, FOUNDRY_XIP });
+
+    public KoralObject parseExpression(QueryNode queryNode) throws KoralException {
+        return parseExpression(queryNode, false, true);
+    }
+
+    public KoralObject parseExpression(QueryNode queryNode, boolean isNot,
+            boolean isToken) throws KoralException {
+
+        if (queryNode instanceof Expression) {
+            return parseSimpleExpression((Expression) queryNode, isNot, isToken);
+        }
+        else if (queryNode instanceof ExpressionAnd) {
+            List<QueryNode> operands = queryNode.getChildren();
+            if (isNot) {
+                return parseBooleanExpression(operands, KoralRelation.OR);
+            }
+            else {
+                return parseBooleanExpression(operands, KoralRelation.AND);
+            }
+        }
+        else if (queryNode instanceof ExpressionGroup) {
+            // Ignore the group
+            return parseExpression(queryNode.getFirstChild());
+        }
+        else if (queryNode instanceof ExpressionNot) {
+            boolean negation = isNot ? false : true;
+            return parseExpression(queryNode.getChild(0), negation, isToken);
+        }
+        else if (queryNode instanceof ExpressionOr) {
+            List<QueryNode> operands = queryNode.getChildren();
+            if (isNot) {
+                return parseBooleanExpression(operands, KoralRelation.AND);
+            }
+            else {
+                return parseBooleanExpression(operands, KoralRelation.OR);
+            }
+        }
+        // else if (queryNode instanceof ExpressionWildcard) {
+        // for distance query, using empty token
+        // }
+        else {
+            throw new KoralException(StatusCodes.QUERY_TOO_COMPLEX,
+                    "FCS diagnostic 11: Query is too complex.");
+        }
+    }
+
+    private KoralToken parseBooleanExpression(List<QueryNode> operands,
+            KoralRelation relation) throws KoralException {
+        List<KoralObject> terms = new ArrayList<>();
+        for (QueryNode node : operands) {
+            terms.add(parseExpression(node, false, false));
+        }
+        KoralTermGroup termGroup = new KoralTermGroup(relation, terms);
+        return new KoralToken(termGroup);
+    }
+
+    private KoralObject parseSimpleExpression(Expression expression, boolean isNot,
+            boolean isToken) throws KoralException {
+        KoralTerm koralTerm = parseTerm(expression, isNot);
+        if (isToken) {
+            return new KoralToken(koralTerm);
+        }
+        else {
+            return koralTerm;
+        }
+    }
+
+    public KoralTerm parseTerm(Expression expression, boolean isNot) throws KoralException {
+    	KoralTerm koralTerm = null;
+    	koralTerm = new KoralTerm(expression.getRegexValue());
+        koralTerm.setType(KoralTermType.REGEX);
+        parseLayerIdentifier(koralTerm, expression.getLayerIdentifier());
+        parseQualifier(koralTerm, expression.getLayerQualifier());
+        parseOperator(koralTerm, expression.getOperator(), isNot);
+        parseRegexFlags(koralTerm, expression.getRegexFlags());
+        return koralTerm;
+    }
+
+    private void parseLayerIdentifier(KoralTerm koralTerm, String identifier) throws KoralException {
+        String layer = null;
+        if (identifier == null) {
+            throw new KoralException(StatusCodes.MALFORMED_QUERY,
+                    "FCS diagnostic 10: Layer identifier is missing.");
+        }
+        else if (identifier.equals("text")) {
+            layer = "orth";
+        }
+        else if (identifier.equals("pos")) {
+            layer = "p";
+        }
+        else if (identifier.equals("lemma")) {
+            layer = "l";
+        }
+        else {
+            throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                    "SRU diagnostic 48: Layer " + identifier
+                            + " is unsupported.");
+        }
+
+        koralTerm.setLayer(layer);
+    }
+
+    private void parseQualifier(KoralTerm koralTerm, String qualifier) throws KoralException {
+        String layer = koralTerm.getLayer();
+        if (layer == null) {
+            return;
+        }
+        // Set default foundry
+        if (qualifier == null) {
+            if (layer.equals("orth")) {
+                qualifier = FOUNDRY_OPENNLP;
+            }
+            else {
+                qualifier = FOUNDRY_TT;
+            }
+        }
+        else if (qualifier.equals(FOUNDRY_OPENNLP) && layer.equals("l")) {
+            throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                            "SRU diagnostic 48: Layer lemma with qualifier opennlp is unsupported.");
+        }
+        else if (!supportedFoundries.contains(qualifier)) {
+            throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                    "SRU diagnostic 48: Qualifier " + qualifier
+                            + " is unsupported.");
+        }
+
+        koralTerm.setFoundry(qualifier);
+    }
+
+    private void parseOperator(KoralTerm koralTerm, Operator operator,
+            boolean isNot) throws KoralException {
+    	KoralMatchOperator matchOperator = null;
+        if (operator == null || operator == Operator.EQUALS) {
+            matchOperator = isNot ? KoralMatchOperator.NOT_EQUALS : KoralMatchOperator.EQUALS;
+        }
+        else if (operator == Operator.NOT_EQUALS) {
+            matchOperator = isNot ? KoralMatchOperator.EQUALS : KoralMatchOperator.NOT_EQUALS;
+        }
+        else {
+        	throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                            "SRU diagnostic 37:" + operator.name()
+                                    + " is unsupported.");
+        }
+        koralTerm.setOperator(matchOperator);
+    }
+
+    private void parseRegexFlags(KoralTerm koralTerm, Set<RegexFlag> set) throws KoralException {
+        // default case sensitive
+        if (set == null) return;
+        
+        ArrayList<String> names = new ArrayList<String>();
+        Iterator<RegexFlag> i = set.iterator();
+        while (i.hasNext()) {
+            RegexFlag f = i.next();
+            if (f == RegexFlag.CASE_SENSITVE) {
+                koralTerm.setCaseSensitive(true);
+            }
+            else if (f == RegexFlag.CASE_INSENSITVE) {
+                koralTerm.setCaseSensitive(false);
+            }
+            else {
+                names.add(f.name());
+            }
+        }
+
+        if (names.size() == 1) {
+            throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                    "SRU diagnostic 48: Regexflag: " + names.get(0)
+                            + " is unsupported.");
+        }
+        else if (names.size() > 1) {
+            throw new KoralException(StatusCodes.UNKNOWN_QUERY_ELEMENT,
+                    "SRU diagnostic 48: Regexflags: " + names.toString()
+                            + " are unsupported.");
+        }
+    }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java
new file mode 100644
index 0000000..85be144
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/parse/fcsql/FCSSRUQueryParser.java
@@ -0,0 +1,122 @@
+package de.ids_mannheim.korap.query.parse.fcsql;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import de.ids_mannheim.korap.query.object.KoralContext;
+import de.ids_mannheim.korap.query.serialize.util.KoralException;
+import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
+import de.ids_mannheim.korap.query.object.KoralGroup;
+import de.ids_mannheim.korap.query.object.KoralObject;
+import de.ids_mannheim.korap.query.object.KoralOperation;
+import de.ids_mannheim.korap.query.object.KoralSpan;
+import de.ids_mannheim.korap.query.object.KoralTerm;
+import de.ids_mannheim.korap.query.object.KoralGroup.Frame;
+import eu.clarin.sru.server.fcs.parser.QueryDisjunction;
+import eu.clarin.sru.server.fcs.parser.QueryGroup;
+import eu.clarin.sru.server.fcs.parser.QueryNode;
+import eu.clarin.sru.server.fcs.parser.QuerySegment;
+import eu.clarin.sru.server.fcs.parser.QuerySequence;
+import eu.clarin.sru.server.fcs.parser.QueryWithWithin;
+import eu.clarin.sru.server.fcs.parser.SimpleWithin;
+import eu.clarin.sru.server.fcs.parser.SimpleWithin.Scope;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class FCSSRUQueryParser {
+
+    private ExpressionParser expressionParser;
+
+    public FCSSRUQueryParser () {
+        this.expressionParser = new ExpressionParser();
+    }
+
+    public KoralObject parseQueryNode(QueryNode queryNode) throws KoralException {
+
+        if (queryNode instanceof QuerySegment) {
+            return parseQuerySegment((QuerySegment) queryNode);
+        }
+        else if (queryNode instanceof QueryGroup) {
+            return parseQueryNode(queryNode.getChild(0));
+        }
+        else if (queryNode instanceof QuerySequence) {
+            return parseGroupQuery(queryNode.getChildren(),
+                    KoralOperation.SEQUENCE);
+        }
+        else if (queryNode instanceof QueryDisjunction) {
+            return parseGroupQuery(queryNode.getChildren(),
+                    KoralOperation.DISJUNCTION);
+        }
+        else if (queryNode instanceof QueryWithWithin) {
+        	return parseWithinQuery((QueryWithWithin)queryNode);
+        }
+        else if (queryNode instanceof SimpleWithin) {
+	    	SimpleWithin withinNode = (SimpleWithin) queryNode;
+	    	return parseWithinScope(withinNode.getScope());
+	    }
+        else {
+            throw new KoralException(StatusCodes.QUERY_TOO_COMPLEX,
+                    "FCS diagnostic 11:" + queryNode.getNodeType().name()
+                            + " is currently unsupported.");
+        }
+    }
+    private KoralObject parseWithinQuery(QueryWithWithin queryNode) throws KoralException {
+    	KoralGroup koralGroup = new KoralGroup(KoralOperation.POSITION);
+    	koralGroup.setFrames(Arrays.asList(Frame.IS_AROUND));
+    	
+    	List<KoralObject> operands = new ArrayList<KoralObject>();
+    	operands.add(parseQueryNode(queryNode.getWithin()));
+    	operands.add(parseQueryNode(queryNode.getQuery()));
+    	koralGroup.setOperands(operands);
+    	return koralGroup;
+	}
+
+    private KoralSpan parseWithinScope(Scope scope) throws KoralException{
+    	if (scope == null){
+    		throw new KoralException(StatusCodes.MALFORMED_QUERY,
+                    "FCS diagnostic 11: Within context is missing.");
+    	}
+
+    	KoralContext contextSpan;
+    	if (scope == Scope.SENTENCE) {
+			contextSpan = KoralContext.SENTENCE;
+    	}
+    	else if (scope == Scope.PARAGRAPH){
+			contextSpan = KoralContext.PARAGRAPH;
+    	}
+    	else if (scope == Scope.TEXT){
+            contextSpan = KoralContext.TEXT;
+    	}
+    	else{
+    		throw new KoralException(StatusCodes.QUERY_TOO_COMPLEX,
+                    "FCS diagnostic 11: Within scope " + scope.toString()
+                            + " is currently unsupported.");
+    	}
+    	
+    	return new KoralSpan(new KoralTerm(contextSpan));
+    }
+    
+	private KoralGroup parseGroupQuery(List<QueryNode> children,
+            KoralOperation operation) throws KoralException {
+        KoralGroup koralGroup = new KoralGroup(operation);
+        List<KoralObject> operands = new ArrayList<KoralObject>();
+        for (QueryNode child : children) {
+            operands.add(parseQueryNode(child));
+        }
+        koralGroup.setOperands(operands);
+        return koralGroup;
+    }
+
+    private KoralObject parseQuerySegment(QuerySegment segment) throws KoralException {
+        if ((segment.getMinOccurs() == 1) && (segment.getMaxOccurs() == 1)) {
+            return expressionParser.parseExpression(segment.getExpression());
+        }
+        else {
+            throw new KoralException(StatusCodes.QUERY_TOO_COMPLEX,
+                    "FCS diagnostic 11: Query is too complex.");
+        }
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessor.java b/src/main/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessor.java
index b06731c..2dd8985 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessor.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessor.java
@@ -12,7 +12,7 @@
 
 /**
  * @author margaretha
- * @date 09.05.14
+ * @date 30.05.16
  */
 public class CqlQueryProcessor extends AbstractQueryProcessor {
 
@@ -22,51 +22,40 @@
     private static final String INDEX_WORDS = "words";
     private static final String TERM_RELATION_CQL_1_1 = "scr";
     private static final String TERM_RELATION_CQL_1_2 = "=";
-    private static final String SUPPORTED_RELATION_EXACT = "exact"; // not
-                                                                    // in
-                                                                    // the
-                                                                    // doc
+    private static final String SUPPORTED_RELATION_EXACT = "exact"; // not in the doc
     private static final String OPERATION_OR = "operation:or";
     private static final String OPERATION_SEQUENCE = "operation:sequence";
     private static final String OPERATION_POSITION = "operation:position";
-    private static final String KORAP_CONTEXT = "http://ids-mannheim.de/ns/KorAP/json-ld/v0.1/context.jsonld";
 
-    private LinkedHashMap<String, Object> requestMap;
     private String version;
     private boolean isCaseSensitive; // default true
 
-
     public CqlQueryProcessor (String query) {
         this(query, VERSION_1_2, true);
     }
 
-
     public CqlQueryProcessor (String query, String version) {
         this(query, version, true);
     }
 
-
     public CqlQueryProcessor (String query, String version,
-                              boolean isCaseSensitive) {
+            boolean isCaseSensitive) {
+        super();
         this.version = version;
         this.isCaseSensitive = isCaseSensitive;
-        this.requestMap = new LinkedHashMap<>();
-        requestMap.put("@context", KORAP_CONTEXT);
         process(query);
     }
 
-
     @Override
-    public Map<String, Object> getRequestMap () {
+    public Map<String, Object> getRequestMap() {
         return this.requestMap;
     }
 
-
     @Override
-    public void process (String query) {
+    public void process(String query) {
         if ((query == null) || query.isEmpty())
-            addError(StatusCodes.MALFORMED_QUERY,
-                    "SRU diagnostic 27: An empty query is unsupported.");
+            addError(StatusCodes.NO_QUERY,
+                    "SRU diagnostic 27: Empty query is unsupported.");
 
         CQLNode cqlNode = parseQuerytoCQLNode(query);
         Map<String, Object> queryMap = parseCQLNode(cqlNode);
@@ -74,8 +63,7 @@
         // requestMap.put("query", sentenceWrapper(queryMap));
     }
 
-
-    private Map<String, Object> sentenceWrapper (Map<String, Object> m) {
+    private Map<String, Object> sentenceWrapper(Map<String, Object> m) {
         Map<String, Object> map = new LinkedHashMap<String, Object>();
         map.put("@type", "koral:group");
         map.put("operation", OPERATION_POSITION);
@@ -93,8 +81,7 @@
         return map;
     }
 
-
-    private CQLNode parseQuerytoCQLNode (String query) {
+    private CQLNode parseQuerytoCQLNode(String query) {
         try {
             int compat = -1;
             switch (version) {
@@ -113,8 +100,7 @@
         }
     }
 
-
-    private Map<String, Object> parseCQLNode (CQLNode node) {
+    private Map<String, Object> parseCQLNode(CQLNode node) {
 
         if (node instanceof CQLTermNode) {
             return parseTermNode((CQLTermNode) node);
@@ -134,8 +120,7 @@
         }
     }
 
-
-    private Map<String, Object> parseTermNode (CQLTermNode node) {
+    private Map<String, Object> parseTermNode(CQLTermNode node) {
         checkTermNode(node);
         final String term = node.getTerm();
         if ((term == null) || term.isEmpty()) {
@@ -151,8 +136,7 @@
         }
     }
 
-
-    private Map<String, Object> parseAndNode (CQLAndNode node) {
+    private Map<String, Object> parseAndNode(CQLAndNode node) {
         checkBooleanModifier(node);
 
         Map<String, Object> map = new LinkedHashMap<String, Object>();
@@ -177,8 +161,7 @@
         return map;
     }
 
-
-    private Map<String, Object> parseOrNode (CQLOrNode node) {
+    private Map<String, Object> parseOrNode(CQLOrNode node) {
         checkBooleanModifier(node);
 
         Map<String, Object> map = new LinkedHashMap<String, Object>();
@@ -193,8 +176,7 @@
         return map;
     }
 
-
-    private Map<String, Object> writeSequence (String str) {
+    private Map<String, Object> writeSequence(String str) {
         Map<String, Object> sequenceMap = new LinkedHashMap<String, Object>();
         sequenceMap.put("@type", "koral:group");
         sequenceMap.put("operation", OPERATION_SEQUENCE);
@@ -209,8 +191,7 @@
         return sequenceMap;
     }
 
-
-    private Map<String, Object> writeTerm (String term) {
+    private Map<String, Object> writeTerm(String term) {
         Map<String, Object> map = new LinkedHashMap<String, Object>();
         map.put("@type", "koral:term");
         if (!isCaseSensitive) {
@@ -226,8 +207,7 @@
         return tokenMap;
     }
 
-
-    private void checkBooleanModifier (CQLBooleanNode node) {
+    private void checkBooleanModifier(CQLBooleanNode node) {
         List<Modifier> modifiers = node.getModifiers();
         if ((modifiers != null) && !modifiers.isEmpty()) {
             Modifier modifier = modifiers.get(0);
@@ -237,8 +217,7 @@
         }
     }
 
-
-    private void checkTermNode (CQLTermNode node) {
+    private void checkTermNode(CQLTermNode node) {
         // only allow "cql.serverChoice" and "words" index
         if (!(INDEX_CQL_SERVERCHOICE.equals(node.getIndex()) || INDEX_WORDS
                 .equals(node.getIndex()))) {
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessor.java b/src/main/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessor.java
new file mode 100644
index 0000000..369e8e3
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessor.java
@@ -0,0 +1,109 @@
+package de.ids_mannheim.korap.query.serialize;
+
+import java.util.Map;
+
+import de.ids_mannheim.korap.query.parse.fcsql.FCSSRUQueryParser;
+import de.ids_mannheim.korap.query.serialize.util.KoralException;
+import de.ids_mannheim.korap.query.serialize.util.StatusCodes;
+import eu.clarin.sru.server.SRUQueryBase;
+import eu.clarin.sru.server.fcs.Constants;
+import eu.clarin.sru.server.fcs.parser.QueryNode;
+import eu.clarin.sru.server.fcs.parser.QueryParser;
+import eu.clarin.sru.server.fcs.parser.QueryParserException;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class FCSQLQueryProcessor extends AbstractQueryProcessor {
+
+    public static final class FCSSRUQuery extends SRUQueryBase<QueryNode> {
+
+        private FCSSRUQuery (String rawQuery, QueryNode parsedQuery) {
+            super(rawQuery, parsedQuery);
+        }
+
+        @Override
+        public String getQueryType() {
+            return Constants.FCS_QUERY_TYPE_FCS;
+        }
+    }
+
+    private static final String VERSION_2_0 = "2.0";
+
+    private final QueryParser fcsParser = new QueryParser();
+    private String version;
+
+    public FCSQLQueryProcessor (String query, String version) {
+        super();
+        this.version = version;
+        process(query);
+    }
+
+    @Override
+    public Map<String, Object> getRequestMap() {
+        return this.requestMap;
+    }
+
+    @Override
+    public void process(String query) {
+        if (isVersionValid()) {
+            FCSSRUQuery fcsSruQuery = parseQueryStringtoFCSQuery(query);
+            if (fcsSruQuery != null) {
+                QueryNode fcsQueryNode = fcsSruQuery.getParsedQuery();
+                try {
+					parseFCSQueryToKoralQuery(fcsQueryNode);
+				} catch (KoralException e) {
+					addError(e.getStatusCode(), e.getMessage());
+				}
+            }
+        }
+    }
+
+    private boolean isVersionValid() {
+        if (version == null || version.isEmpty()) {
+            addError(StatusCodes.MISSING_VERSION,
+                    "SRU diagnostic 7: Version number is missing.");
+            return false;
+        }
+        else if (!version.equals(VERSION_2_0)) {
+            addError(StatusCodes.MISSING_VERSION,
+                    "SRU diagnostic 5: Only supports SRU version 2.0.");
+            return false;
+        }
+        return true;
+    }
+
+    private FCSSRUQuery parseQueryStringtoFCSQuery(String query) {
+        if ((query == null) || query.isEmpty())
+            addError(StatusCodes.NO_QUERY,
+                    "SRU diagnostic 1: No query has been passed.");
+        FCSSRUQuery fcsQuery = null;
+        try {
+            QueryNode parsedQuery = fcsParser.parse(query);
+            fcsQuery = new FCSSRUQuery(query, parsedQuery);
+            if (fcsQuery == null) {
+                addError(StatusCodes.UNKNOWN_QUERY_ERROR,
+                        "FCS diagnostic 10: Unexpected error while parsing query.");
+            }
+        }
+        catch (QueryParserException e) {
+            addError(
+                    StatusCodes.UNKNOWN_QUERY_ERROR,
+                    "FCS diagnostic 10: Query cannot be parsed, "
+                            + e.getMessage());
+        }
+        catch (Exception e) {
+            addError(StatusCodes.UNKNOWN_QUERY_ERROR, "FCS diagnostic 10: "
+                    + "Unexpected error while parsing query.");
+        }
+        return fcsQuery;
+    }
+
+    private void parseFCSQueryToKoralQuery(QueryNode queryNode) throws KoralException {
+        FCSSRUQueryParser parser = new FCSSRUQueryParser();
+        Object o = parser.parseQueryNode(queryNode);
+        Map<String, Object> queryMap = MapBuilder.buildQueryMap(o);
+        if (queryMap != null) requestMap.put("query", queryMap);
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/MapBuilder.java b/src/main/java/de/ids_mannheim/korap/query/serialize/MapBuilder.java
new file mode 100644
index 0000000..9d1c2ee
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/MapBuilder.java
@@ -0,0 +1,42 @@
+package de.ids_mannheim.korap.query.serialize;
+
+import java.util.Map;
+
+import de.ids_mannheim.korap.query.object.KoralGroup;
+import de.ids_mannheim.korap.query.object.KoralSpan;
+import de.ids_mannheim.korap.query.object.KoralTerm;
+import de.ids_mannheim.korap.query.object.KoralTermGroup;
+import de.ids_mannheim.korap.query.object.KoralToken;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class MapBuilder {
+
+    public static Map<String, Object> buildQueryMap(Object o) {
+        if (o != null) {
+            if (o instanceof KoralToken) {
+                KoralToken token = (KoralToken) o;
+                return token.buildMap();
+            }
+            else if (o instanceof KoralGroup) {
+                KoralGroup group = (KoralGroup) o;
+                return group.buildMap();
+            }
+            if (o instanceof KoralTerm) {
+                KoralTerm term = (KoralTerm) o;
+                return term.buildMap();
+            }
+            else if (o instanceof KoralTermGroup) {
+                KoralTermGroup termGroup = (KoralTermGroup) o;
+                return termGroup.buildMap();
+            }
+            else if (o instanceof KoralSpan){
+            	KoralSpan span = (KoralSpan) o;
+            	return span.buildMap();
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java b/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java
index dcf0d3d..d4cb27e 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/QuerySerializer.java
@@ -12,11 +12,10 @@
 import java.util.*;
 
 /**
- * Main class for Koral, serializes queries from concrete QLs to
- * KoralQuery
+ * Main class for Koral, serializes queries from concrete QLs to KoralQuery
  * 
- * @author Joachim Bingel (bingel@ids-mannheim.de),
- *         Michael Hanl (hanl@ids-mannheim.de)
+ * @author Joachim Bingel (bingel@ids-mannheim.de), Michael Hanl
+ *         (hanl@ids-mannheim.de), Eliza Margaretha (margaretha@ids-mannheim.de)
  * @version 0.3.0
  * @since 0.1.0
  */
@@ -28,10 +27,11 @@
 
 
 
+
     static {
         qlProcessorAssignment = new HashMap<String, Class<? extends AbstractQueryProcessor>>();
-        qlProcessorAssignment.put("poliqarpplus",
-                PoliqarpPlusQueryProcessor.class);
+		qlProcessorAssignment.put("poliqarpplus",
+				PoliqarpPlusQueryProcessor.class);
         qlProcessorAssignment.put("cosmas2", Cosmas2QueryProcessor.class);
         qlProcessorAssignment.put("annis", AnnisQueryProcessor.class);
         qlProcessorAssignment.put("cql", CqlQueryProcessor.class);
@@ -50,18 +50,16 @@
     private org.slf4j.Logger log = LoggerFactory
             .getLogger(QuerySerializer.class);
 
-
-    public QuerySerializer () {
+	public QuerySerializer() {
         this.errors = new LinkedList<>();
         this.warnings = new LinkedList<>();
         this.messages = new LinkedList<>();
     }
 
-
     /**
      * @param args
      */
-    public static void main (String[] args) {
+	public static void main(String[] args) {
         /*
          * just for testing...
          */
@@ -74,8 +72,7 @@
             System.err
                     .println("Usage: QuerySerializer \"query\" queryLanguage");
             System.exit(1);
-        }
-        else {
+		} else {
             queries = new String[] { args[0] };
             ql = args[1];
         }
@@ -84,141 +81,118 @@
             try {
                 jg.run(q, ql);
                 System.out.println();
-            }
-            catch (NullPointerException npe) {
+			} catch (NullPointerException npe) {
                 npe.printStackTrace();
                 System.out.println("null\n");
-            }
-            catch (IOException e) {
+			} catch (IOException e) {
                 e.printStackTrace();
             }
         }
     }
 
-
     /**
-     * Runs the QuerySerializer by initializing the relevant
-     * AbstractSyntaxTree implementation (depending on specified query
-     * language) and transforms and writes the tree's requestMap to
-     * the specified output file.
-     * 
-     * @param query
-     *            The query string
-     * @param queryLanguage
-     *            The query language. As of 17 Dec 2014, this must be
-     *            one of 'poliqarpplus', 'cosmas2', 'annis' or 'cql'.
-     * @throws IOException
-     */
-    public void run (String query, String queryLanguage) throws IOException {
+	 * Runs the QuerySerializer by initializing the relevant AbstractSyntaxTree
+	 * implementation (depending on specified query language) and transforms and
+	 * writes the tree's requestMap to the specified output file.
+	 * 
+	 * @param query
+	 *            The query string
+	 * @param queryLanguage
+	 *            The query language. As of 17 Dec 2014, this must be one of
+	 *            'poliqarpplus', 'cosmas2', 'annis' or 'cql'.
+	 * @throws IOException
+	 */
+	public void run(String query, String queryLanguage) throws IOException {
         if (queryLanguage.equalsIgnoreCase("poliqarp")) {
             ast = new PoliqarpPlusQueryProcessor(query);
-        }
-        else if (queryLanguage.equalsIgnoreCase("cosmas2")) {
+		} else if (queryLanguage.equalsIgnoreCase("cosmas2")) {
             ast = new Cosmas2QueryProcessor(query);
-        }
-        else if (queryLanguage.equalsIgnoreCase("poliqarpplus")) {
+		} else if (queryLanguage.equalsIgnoreCase("poliqarpplus")) {
             ast = new PoliqarpPlusQueryProcessor(query);
-        }
-        else if (queryLanguage.equalsIgnoreCase("cql")) {
+		} else if (queryLanguage.equalsIgnoreCase("cql")) {
             ast = new CqlQueryProcessor(query);
-        }
-        else if (queryLanguage.equalsIgnoreCase("annis")) {
+		} else if (queryLanguage.equalsIgnoreCase("fcsql")) {
+			ast = new FCSQLQueryProcessor(query, "2.0");
+        }else if (queryLanguage.equalsIgnoreCase("annis")) {
             ast = new AnnisQueryProcessor(query);
-        }
-        else {
-            throw new IllegalArgumentException(queryLanguage
-                    + " is not a supported query language!");
+		} else {
+			throw new IllegalArgumentException(queryLanguage
+					+ " is not a supported query language!");
         }
         toJSON();
     }
 
-
-    public QuerySerializer setQuery (String query, String ql, String version) {
+	public QuerySerializer setQuery(String query, String ql, String version) {
         ast = new DummyQueryProcessor();
         if (query == null || query.isEmpty()) {
             ast.addError(StatusCodes.NO_QUERY, "You did not specify a query!");
-        }
-        else if (ql == null || ql.isEmpty()) {
+		} else if (ql == null || ql.isEmpty()) {
             ast.addError(StatusCodes.NO_QUERY,
                     "You did not specify any query language!");
-        }
-        else if (ql.equalsIgnoreCase("poliqarp")) {
+		} else if (ql.equalsIgnoreCase("poliqarp")) {
             ast = new PoliqarpPlusQueryProcessor(query);
-        }
-        else if (ql.equalsIgnoreCase("cosmas2")) {
+		} else if (ql.equalsIgnoreCase("cosmas2")) {
             ast = new Cosmas2QueryProcessor(query);
-        }
-        else if (ql.equalsIgnoreCase("poliqarpplus")) {
+		} else if (ql.equalsIgnoreCase("poliqarpplus")) {
             ast = new PoliqarpPlusQueryProcessor(query);
-        }
-        else if (ql.equalsIgnoreCase("cql")) {
-            if (version == null)
+        }else if (ql.equalsIgnoreCase("cql")) {
+			if (version == null) {
                 ast = new CqlQueryProcessor(query);
-            else
+			} else {
                 ast = new CqlQueryProcessor(query, version);
-        }
-        else if (ql.equalsIgnoreCase("annis")) {
+			}
+		} else if (ql.equalsIgnoreCase("fcsql")) {
+			if (version == null) {
+				ast.addError(StatusCodes.MISSING_VERSION,
+						"SRU Version is missing!");
+			} else {
+				ast = new FCSQLQueryProcessor(query, version);
+			}
+		} else if (ql.equalsIgnoreCase("annis")) {
             ast = new AnnisQueryProcessor(query);
-        }
-        else {
-            ast.addError(StatusCodes.UNKNOWN_QL, ql
-                    + " is not a supported query language!");
+        }else {
+			ast.addError(StatusCodes.UNKNOWN_QUERY_LANGUAGE,
+                    ql + " is not a supported query language!");
         }
         return this;
     }
 
-
-    public QuerySerializer setQuery (String query, String ql) {
+	public QuerySerializer setQuery(String query, String ql) {
         return setQuery(query, ql, "");
     }
 
-
-    public void setVerbose (boolean verbose) {
+	public void setVerbose(boolean verbose) {
         AbstractQueryProcessor.verbose = verbose;
     }
 
-
-    public final String toJSON () {
+	public final String toJSON() {
         String ser;
         try {
             ser = mapper.writeValueAsString(raw());
             qllogger.info("Serialized query: " + ser);
-        }
-        catch (JsonProcessingException e) {
+		} catch (JsonProcessingException e) {
             return "";
         }
         return ser;
     }
 
-
-    public final Map build () {
+	public final Map build() {
         return raw();
     }
 
-
-    private Map raw () {
+	private Map raw() {
         if (ast != null) {
-            Map<String, Object> requestMap = new HashMap<>(ast.getRequestMap());
+			Map<String, Object> requestMap = ast.getRequestMap();
             Map meta = (Map) requestMap.get("meta");
             Map collection = (Map) requestMap.get("collection");
             List errors = (List) requestMap.get("errors");
             List warnings = (List) requestMap.get("warnings");
             List messages = (List) requestMap.get("messages");
-            collection = mergeCollection(collection, this.collection);
-            requestMap.put("collection", collection);
-            
-            if (meta == null)
-                meta = new HashMap();
-            if (errors == null)
-                errors = new LinkedList();
-            if (warnings == null)
-                warnings = new LinkedList();
-            if (messages == null)
-                messages = new LinkedList();
-
+			this.collection = mergeCollection(collection, this.collection);
+			requestMap.put("collection", this.collection);
             if (this.meta != null) {
-                meta.putAll(this.meta);
-                requestMap.put("meta", meta);
+				this.meta.putAll(meta);
+				requestMap.put("meta", this.meta);
             }
             if (this.errors != null && !this.errors.isEmpty()) {
                 errors.addAll(this.errors);
@@ -232,43 +206,37 @@
                 messages.addAll(this.messages);
                 requestMap.put("messages", messages);
             }
+
             return cleanup(requestMap);
         }
         return new HashMap<>();
     }
 
-
-    private Map<String, Object> cleanup (Map<String, Object> requestMap) {
+	private Map<String, Object> cleanup(Map<String, Object> requestMap) {
         Iterator<Map.Entry<String, Object>> set = requestMap.entrySet()
                 .iterator();
         while (set.hasNext()) {
             Map.Entry<String, Object> entry = set.next();
-            if (entry.getValue() instanceof List
-                    && ((List) entry.getValue()).isEmpty())
+			if (entry.getValue() instanceof List
+					&& ((List) entry.getValue()).isEmpty())
                 set.remove();
-            else if (entry.getValue() instanceof Map
-                    && ((Map) entry.getValue()).isEmpty())
+			else if (entry.getValue() instanceof Map
+					&& ((Map) entry.getValue()).isEmpty())
                 set.remove();
-            else if (entry.getValue() instanceof String
-                    && ((String) entry.getValue()).isEmpty())
+			else if (entry.getValue() instanceof String
+					&& ((String) entry.getValue()).isEmpty())
                 set.remove();
         }
         return requestMap;
     }
 
-
-    private Map<String, Object> mergeCollection (
-            Map<String, Object> collection1, Map<String, Object> collection2) {
+	private Map<String, Object> mergeCollection(
+			Map<String, Object> collection1, Map<String, Object> collection2) {
         if (collection1 == null || collection1.isEmpty()) {
             return collection2;
-        }
-        else if (collection2 == null || collection2.isEmpty()) {
+		} else if (collection2 == null || collection2.isEmpty()) {
             return collection1;
-        }
-        else if (collection1.equals(collection2)) {
-            return collection1;
-        }
-        else {
+		} else {
             LinkedHashMap<String, Object> docGroup = KoralObjectGenerator
                     .makeDocGroup("and");
             ArrayList<Object> operands = (ArrayList<Object>) docGroup
@@ -279,9 +247,8 @@
         }
     }
 
-
     @Deprecated
-    public QuerySerializer addMeta (String cli, String cri, int cls, int crs,
+	public QuerySerializer addMeta(String cli, String cri, int cls, int crs,
             int num, int pageIndex) {
         MetaQueryBuilder meta = new MetaQueryBuilder();
         meta.setSpanContext(cls, cli, crs, cri);
@@ -291,14 +258,17 @@
         return this;
     }
 
-
-    public QuerySerializer setMeta (Map<String, Object> meta) {
+	public QuerySerializer setMeta(Map<String, Object> meta) {
         this.meta = meta;
         return this;
     }
 
+	public QuerySerializer setMeta(MetaQueryBuilder meta) {
+		this.meta = meta.raw();
+		return this;
+	}
 
-    public QuerySerializer setCollection (String collection) {
+	public QuerySerializer setCollection(String collection) {
         CollectionQueryProcessor tree = new CollectionQueryProcessor();
         tree.process(collection);
         Map<String, Object> collectionRequest = tree.getRequestMap();
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/util/KoralException.java b/src/main/java/de/ids_mannheim/korap/query/serialize/util/KoralException.java
new file mode 100644
index 0000000..34d4569
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/util/KoralException.java
@@ -0,0 +1,30 @@
+package de.ids_mannheim.korap.query.serialize.util;
+
+public class KoralException extends Exception{
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 5463242042200109417L;
+	private int statusCode;
+	
+	public KoralException(int code, String message) {
+		super(message);
+		this.statusCode = code;
+	}
+	
+	public KoralException(int code, String message, Throwable cause) {
+        super(message, cause);
+        this.statusCode = code;
+    }
+
+	public int getStatusCode() {
+		return statusCode;
+	}
+
+	public void setStatusCode(int statusCode) {
+		this.statusCode = statusCode;
+	}
+
+	
+}
diff --git a/src/main/java/de/ids_mannheim/korap/query/serialize/util/StatusCodes.java b/src/main/java/de/ids_mannheim/korap/query/serialize/util/StatusCodes.java
index 10614ea..1f896b3 100644
--- a/src/main/java/de/ids_mannheim/korap/query/serialize/util/StatusCodes.java
+++ b/src/main/java/de/ids_mannheim/korap/query/serialize/util/StatusCodes.java
@@ -7,7 +7,9 @@
     public final static int INVALID_CLASS_REFERENCE = 304;
     public final static int INCOMPATIBLE_OPERATOR_AND_OPERAND = 305;
     public final static int UNKNOWN_QUERY_ELEMENT = 306;
-    public final static int UNKNOWN_QL = 307;
+	public final static int UNKNOWN_QUERY_LANGUAGE = 307;
     public final static int UNBOUND_ANNIS_RELATION = 308;
-    public final static int UNKNOWN_QUERY_ERROR = 399;
-}
+	public final static int MISSING_VERSION = 309;
+	public final static int QUERY_TOO_COMPLEX = 310;
+	public final static int UNKNOWN_QUERY_ERROR = 399;
+}
\ No newline at end of file
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessorTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessorTest.java
index 6f2bdd9..5af4b60 100644
--- a/src/test/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessorTest.java
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/CqlQueryProcessorTest.java
@@ -3,81 +3,58 @@
 import static org.junit.Assert.assertEquals;
 
 import java.io.IOException;
+import java.util.List;
 
 import org.junit.Test;
 import org.z3950.zing.cql.CQLParseException;
 
-import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 
-import de.ids_mannheim.korap.query.serialize.CqlQueryProcessor;
-import de.ids_mannheim.korap.query.serialize.Cosmas2QueryProcessor;
-
-
 public class CqlQueryProcessorTest {
 
     String query;
-    String version = "1.2";
+    String VERSION = "1.2";
     ObjectMapper mapper = new ObjectMapper();
 
-
-    @Test
-    public void testExceptions () throws CQLParseException, IOException {
-        query = "(Kuh) prox (Germ) ";
-        try {
-            CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
-        }
-        catch (Exception e) {
-            int errorCode = Integer.parseInt(e.getMessage().split(":")[0]
-                    .replace("SRU diagnostic ", ""));
-            assertEquals(48, errorCode);
-        }
-
-        query = "(Kuh) or/rel.combine=sum (Germ) ";
-        try {
-            CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
-        }
-        catch (Exception e) {
-            int errorCode = Integer.parseInt(e.getMessage().split(":")[0]
-                    .replace("SRU diagnostic ", ""));
-            assertEquals(20, errorCode);
-        }
-
-        query = "dc.title any Germ ";
-        try {
-            CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
-        }
-        catch (Exception e) {
-            int errorCode = Integer.parseInt(e.getMessage().split(":")[0]
-                    .replace("SRU diagnostic ", ""));
-            assertEquals(16, errorCode);
-        }
-
-        query = "cql.serverChoice any Germ ";
-        try {
-            CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
-        }
-        catch (Exception e) {
-            int errorCode = Integer.parseInt(e.getMessage().split(":")[0]
-                    .replace("SRU diagnostic ", ""));
-            assertEquals(19, errorCode);
-        }
-
-        query = "";
-        try {
-            CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
-        }
-        catch (Exception e) {
-            int errorCode = Integer.parseInt(e.getMessage().split(":")[0]
-                    .replace("SRU diagnostic ", ""));
-            assertEquals(27, errorCode);
-        }
+    private List<Object> getError(CqlQueryProcessor processor) {
+        List<Object> errors = (List<Object>) processor.requestMap.get("errors");
+        return (List<Object>) errors.get(0);
     }
 
+    @Test
+    public void testExceptions() throws CQLParseException, IOException {
+        query = "(Kuh) prox (Germ) ";
+        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, VERSION);
+        List<Object> error = getError(cqlTree);
+        assertEquals(
+                "SRU diagnostic 48: Only basic search including term-only "
+                        + "and boolean (AND,OR) operator queries are currently supported.",
+                error.get(1));
+
+        query = "(Kuh) or/rel.combine=sum (Germ) ";
+        error = getError(new CqlQueryProcessor(query, VERSION));
+        assertEquals(
+                "SRU diagnostic 20: Relation modifier rel.combine = sum is not supported.",
+                error.get(1));
+
+        query = "dc.title any Germ ";
+        error = getError(new CqlQueryProcessor(query, VERSION));
+        assertEquals("SRU diagnostic 16: Index dc.title is not supported.",
+                error.get(1));
+
+        query = "cql.serverChoice any Germ ";
+        error = getError(new CqlQueryProcessor(query, VERSION));
+        assertEquals("SRU diagnostic 19: Relation any is not supported.",
+                error.get(1));
+
+        query = "";
+        error = getError(new CqlQueryProcessor(query, VERSION));
+        assertEquals("SRU diagnostic 27: Empty query is unsupported.",
+                error.get(1));
+    }
 
     @Test
-    public void testAndQuery () throws CQLParseException, IOException,
-            Exception {
+    public void testAndQuery() throws CQLParseException, IOException, Exception {
         query = "(Sonne) and (scheint)";
         String jsonLd = "{@type : koral:group, operation : operation:sequence, inOrder : false,"
                 + "distances:[ "
@@ -87,19 +64,20 @@
                 + "{@type : koral:token,wrap : {@type : koral:term,key : scheint,layer : orth,match : match:eq}"
                 + "}]}";
 
-        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
+        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, VERSION);
         String serializedQuery = mapper.writeValueAsString(cqlTree
                 .getRequestMap().get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
-        //			/System.out.println(serializedQuery);
-        //CosmasTree ct = new CosmasTree("Sonne und scheint");
-        //serializedQuery = mapper.writeValueAsString(ct.getRequestMap().get("query"));
-        //assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
+        // /System.out.println(serializedQuery);
+        // CosmasTree ct = new CosmasTree("Sonne und scheint");
+        // serializedQuery =
+        // mapper.writeValueAsString(ct.getRequestMap().get("query"));
+        // assertEquals(jsonLd.replace(" ", ""),
+        // serializedQuery.replace("\"", ""));
     }
 
-
     @Test
-    public void testBooleanQuery () throws CQLParseException, IOException,
+    public void testBooleanQuery() throws CQLParseException, IOException,
             Exception {
         query = "((Sonne) or (Mond)) and (scheint)";
         String jsonLd = "{@type:koral:group, operation:operation:sequence, inOrder : false, distances:["
@@ -111,12 +89,11 @@
                 + "]},"
                 + "{@type:koral:token, wrap:{@type:koral:term, key:scheint, layer:orth, match:match:eq}}"
                 + "]}";
-        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
+        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, VERSION);
         String serializedQuery = mapper.writeValueAsString(cqlTree
                 .getRequestMap().get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
 
-
         query = "(scheint) and ((Sonne) or (Mond))";
         jsonLd = "{@type:koral:group, operation:operation:sequence, inOrder : false, distances:["
                 + "{@type:koral:distance, key:s, min:0, max:0}"
@@ -126,23 +103,22 @@
                 + "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, layer:orth, match:match:eq}},"
                 + "{@type:koral:token, wrap:{@type:koral:term, key:Mond, layer:orth, match:match:eq}}"
                 + "]}" + "]}";
-        cqlTree = new CqlQueryProcessor(query, version);
+        cqlTree = new CqlQueryProcessor(query, VERSION);
         serializedQuery = mapper.writeValueAsString(cqlTree.getRequestMap()
                 .get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
 
     }
 
-
     @Test
-    public void testOrQuery () throws CQLParseException, IOException, Exception {
+    public void testOrQuery() throws CQLParseException, IOException, Exception {
         query = "(Sonne) or (Mond)";
         String jsonLd = "{@type:koral:group, operation:operation:or, operands:["
                 + "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, layer:orth, match:match:eq}},"
                 + "{@type:koral:token, wrap:{@type:koral:term, key:Mond, layer:orth, match:match:eq}}"
                 + "]}";
 
-        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
+        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, VERSION);
         String serializedQuery = mapper.writeValueAsString(cqlTree
                 .getRequestMap().get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
@@ -156,7 +132,7 @@
                 + "{@type:koral:token, wrap:{@type:koral:term, key:Mond, layer:orth, match:match:eq}}"
                 + "]}";
 
-        cqlTree = new CqlQueryProcessor(query, version);
+        cqlTree = new CqlQueryProcessor(query, VERSION);
         serializedQuery = mapper.writeValueAsString(cqlTree.getRequestMap()
                 .get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
@@ -171,27 +147,25 @@
                 + "{@type:koral:token, wrap:{@type:koral:term, key:Mond, layer:orth, match:match:eq}},"
                 + "{@type:koral:token, wrap:{@type:koral:term, key:scheint, layer:orth, match:match:eq}}"
                 + "]}" + "]}";
-        cqlTree = new CqlQueryProcessor(query, version);
+        cqlTree = new CqlQueryProcessor(query, VERSION);
         serializedQuery = mapper.writeValueAsString(cqlTree.getRequestMap()
                 .get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
     }
 
-
     @Test
-    public void testTermQuery () throws CQLParseException, IOException,
+    public void testTermQuery() throws CQLParseException, IOException,
             Exception {
         query = "Sonne";
         String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, layer:orth, match:match:eq}}";
-        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
+        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, VERSION);
         String serializedQuery = mapper.writeValueAsString(cqlTree
                 .getRequestMap().get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
     }
 
-
     @Test
-    public void testPhraseQuery () throws CQLParseException, IOException,
+    public void testPhraseQuery() throws CQLParseException, IOException,
             Exception {
         query = "\"der Mann\"";
         String jsonLd = "{@type:koral:group, operation:operation:sequence, operands:["
@@ -199,12 +173,11 @@
                 + "{@type:koral:token, wrap:{@type:koral:term, key:Mann, layer:orth, match:match:eq}}"
                 + "]}";
 
-        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, version);
+        CqlQueryProcessor cqlTree = new CqlQueryProcessor(query, VERSION);
         String serializedQuery = mapper.writeValueAsString(cqlTree
                 .getRequestMap().get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
 
-
         query = "der Mann schläft";
         jsonLd = "{@type:koral:group, operation:operation:sequence, operands:["
                 + "{@type:koral:token, wrap:{@type:koral:term, key:der, layer:orth, match:match:eq}},"
@@ -212,7 +185,7 @@
                 + "{@type:koral:token, wrap:{@type:koral:term, key:schläft, layer:orth, match:match:eq}}"
                 + "]}";
 
-        cqlTree = new CqlQueryProcessor(query, version);
+        cqlTree = new CqlQueryProcessor(query, VERSION);
         serializedQuery = mapper.writeValueAsString(cqlTree.getRequestMap()
                 .get("query"));
         assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLComplexTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLComplexTest.java
new file mode 100644
index 0000000..3973aea
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLComplexTest.java
@@ -0,0 +1,173 @@
+package de.ids_mannheim.korap.query.serialize;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.Test;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class FCSQLComplexTest {
+
+    // -------------------------------------------------------------------------
+    // simple-query ::= '(' main_query ')' /* grouping */
+    // | implicit-query
+    // | segment-query
+    // -------------------------------------------------------------------------
+    // implicit-query ::= flagged-regexp
+    // segment-query ::= "[" expression? "]"
+    // -------------------------------------------------------------------------
+    // simple-query ::= '(' main_query ')' /* grouping */
+    @Test
+    public void testGroupQuery() throws IOException {
+        String query = "(\"blaue\"|\"grüne\")";
+        String jsonLd = "{@type:koral:group,"
+                + "operation:operation:disjunction,"
+                + "operands:["
+                + "{@type:koral:token, wrap:{@type:koral:term,key:blaue,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token, wrap:{@type:koral:term,key:grüne,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}]}";;
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        // group and disjunction
+        query = "([pos=\"NN\"]|[cnx:pos=\"N\"]|[text=\"Mann\"])";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:N,foundry:cnx,layer:p,type:type:regex,match:match:eq}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/1", jsonLd);
+
+        // sequence and disjunction
+        query = "([pos=\"NN\"]|[cnx:pos=\"N\"])[text=\"Mann\"]";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:sequence,"
+                + "operands:["
+                + "{@type:koral:group,"
+                + "operation:operation:disjunction,"
+                + "operands:[{@type:koral:token,wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:N,foundry:cnx,layer:p,type:type:regex,match:match:eq}}"
+                + "]},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:Mann,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        // group and sequence
+        query = "([text=\"blaue\"][pos=\"NN\"])";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:sequence,"
+                + "operands:["
+                + "{@type:koral:token,wrap:{@type:koral:term,key:blaue,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    // -------------------------------------------------------------------------
+    // main-query ::= simple-query
+    // | simple-query "|" main-query /* or */
+    // | simple-query main-query /* sequence */
+    // | simple-query quantifier /* quatification */
+    // -------------------------------------------------------------------------
+
+    // | simple-query "|" main-query /* or */
+    @Test
+    public void testOrQuery() throws IOException {
+        String query = "\"man\"|\"Mann\"";
+        String jsonLd = "{@type:koral:group,"
+                + "operation:operation:disjunction,"
+                + "operands:["
+                + "{@type:koral:token,wrap:{@type:koral:term,key:man,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:Mann,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[pos=\"NN\"]|\"Mann\"";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/0", jsonLd);
+
+        // group with two segment queries
+        query = "[pos=\"NN\"]|[text=\"Mann\"]";
+        jsonLd = "{@type:koral:group,"
+                + "operation:operation:disjunction,"
+                + "operands:["
+                + "{@type:koral:token, wrap:{@type:koral:term,key:NN,foundry:tt,layer:p,type:type:regex,match:match:eq}},"
+                + "{@type:koral:token, wrap:{@type:koral:term,key:Mann,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[pos=\"NN\"]&[text=\"Mann\"]";
+        List<Object> error = FCSQLQueryProcessorTest
+                .getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(399, error.get(0));
+        String msg = (String) error.get(1);
+        assertEquals(true, msg.startsWith("FCS diagnostic 10"));
+    }
+
+    // | simple-query main-query /* sequence */
+    @Test
+    public void testSequenceQuery() throws IOException {
+        String query = "\"blaue|grüne\" [pos = \"NN\"]";
+        String jsonLd = "{@type:koral:group, "
+                + "operation:operation:sequence, "
+                + "operands:["
+                + "{@type:koral:token, wrap:{@type:koral:term, key:blaue|grüne, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}},"
+                + "{@type:koral:token, wrap:{@type:koral:term, key:NN, foundry:tt, layer:p, type:type:regex, match:match:eq}}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[text=\"blaue|grüne\"][pos = \"NN\"]";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "\"blaue\" \"grüne\" [pos = \"NN\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:grüne, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/1", jsonLd);
+
+    }
+
+    // | simple-query quantifier /* quatification */
+    @Test
+    public void testQueryWithQuantifier() throws IOException {
+
+    }
+
+    // -------------------------------------------------------------------------
+    // query ::= main-query within-part?
+    // -------------------------------------------------------------------------
+    // within-part ::= simple-within-part
+    // simple-within-part ::= "within" simple-within-scope
+
+    @Test
+    public void testWithinQuery() throws IOException {
+        String query = "[cnx:pos=\"VVFIN\"] within s";
+        String jsonLd = "{@type:koral:group,"
+                + "operation:operation:position,"
+                + "operands:["
+                + "{@type:koral:span,wrap:{@type:koral:term,key:s,foundry:base,layer:s}},"
+                + "{@type:koral:token,wrap:{@type:koral:term,key:VVFIN,foundry:cnx,layer:p,type:type:regex,match:match:eq}}"
+                + "]}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[cnx:pos=\"VVFIN\"] within sentence";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[cnx:pos=\"VVFIN\"] within p";
+        jsonLd = "{@type:koral:span,wrap:{@type:koral:term,key:p,foundry:base,layer:s}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/0", jsonLd);
+
+        query = "[cnx:pos=\"VVFIN\"] within text";
+        jsonLd = "{@type:koral:span,wrap:{@type:koral:term,key:t,foundry:base,layer:s}}";
+        FCSQLQueryProcessorTest
+                .validateNode(query, "/query/operands/0", jsonLd);
+
+        query = "[cnx:pos=\"VVFIN\"] within u";
+        List<Object> error = FCSQLQueryProcessorTest
+                .getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(310, error.get(0));
+        assertEquals(
+                "FCS diagnostic 11: Within scope UTTERANCE is currently unsupported.",
+                (String) error.get(1));
+    }
+    
+}
diff --git a/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessorTest.java b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessorTest.java
new file mode 100644
index 0000000..7fa90d0
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/query/serialize/FCSQLQueryProcessorTest.java
@@ -0,0 +1,303 @@
+package de.ids_mannheim.korap.query.serialize;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ * @author margaretha
+ * 
+ */
+public class FCSQLQueryProcessorTest {
+
+    static QuerySerializer qs = new QuerySerializer();
+    static ObjectMapper mapper = new ObjectMapper();
+    static JsonNode node;
+
+    public static void runAndValidate(String query, String jsonLD)
+            throws JsonProcessingException {
+        FCSQLQueryProcessor processor = new FCSQLQueryProcessor(query, "2.0");
+        String serializedQuery = mapper.writeValueAsString(processor
+                .getRequestMap().get("query"));
+        assertEquals(jsonLD.replace(" ", ""), serializedQuery.replace("\"", ""));
+    }
+
+    public static void validateNode(String query, String path, String jsonLd)
+            throws JsonProcessingException, IOException {
+        qs.setQuery(query, "fcsql", "2.0");
+        node = mapper.readTree(qs.toJSON());
+        String serializedQuery = mapper.writeValueAsString(node.at(path));
+        assertEquals(jsonLd.replace(" ", ""), serializedQuery.replace("\"", ""));
+    }
+
+    public static List<Object> getError(FCSQLQueryProcessor processor) {
+        List<Object> errors = (List<Object>) processor.requestMap.get("errors");
+        return (List<Object>) errors.get(0);
+    }
+
+    @Test
+    public void testVersion() throws JsonProcessingException {
+        List<Object> error = getError(new FCSQLQueryProcessor("\"Sonne\"",
+                "1.0"));
+        assertEquals(309, error.get(0));
+        assertEquals("SRU diagnostic 5: Only supports SRU version 2.0.",
+                error.get(1));
+
+        error = getError(new FCSQLQueryProcessor("\"Sonne\"", null));
+        assertEquals(309, error.get(0));
+        assertEquals("SRU diagnostic 7: Version number is missing.",
+                error.get(1));
+    }
+
+    // regexp ::= quoted-string
+    @Test
+    public void testTermQuery() throws JsonProcessingException {
+        String query = "\"Sonne\"";
+        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, "
+                + "foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
+        runAndValidate(query, jsonLd);
+    }
+
+    @Test
+    public void testRegex() throws JsonProcessingException {
+        String query = "[text=\"M(a|ä)nn(er)?\"]";
+        String jsonLd = "{@type:koral:token,wrap:{@type:koral:term,"
+                + "key:M(a|ä)nn(er)?,foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
+        runAndValidate(query, jsonLd);
+
+        query = "\".*?Mann.*?\"";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:.*?Mann.*?,"
+                + "foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
+        runAndValidate(query, jsonLd);
+
+        query = "\"z.B.\"";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:z.B.,"
+                + "foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
+        runAndValidate(query, jsonLd);
+
+        query = "\"Sonne&scheint\"";
+        jsonLd = "{@type:koral:token,wrap:{@type:koral:term,key:Sonne&scheint,"
+                + "foundry:opennlp,layer:orth,type:type:regex,match:match:eq}}";
+        runAndValidate(query, jsonLd);
+
+        // Not possible
+        // query = "\"a\\.\"";
+    }
+
+    // flagged-regexp ::= regexp
+    // | regexp "/" regexp-flag+
+    @Test
+    public void testTermQueryWithRegexFlag() throws IOException {
+        String query = "\"Fliegen\" /c";
+        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, caseInsensitive:true, "
+                + "key:Fliegen, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "\"Fliegen\" /i";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "\"Fliegen\" /C";
+        jsonLd = "{@type:koral:term, key:Fliegen, foundry:opennlp, layer:orth, type:type:regex, match:match:eq}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/wrap", jsonLd);
+
+        query = "\"Fliegen\" /I";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/wrap", jsonLd);
+
+        query = "\"Fliegen\" /l";
+        List<Object> error = FCSQLQueryProcessorTest
+                .getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        String msg = (String) error.get(1);
+        assertEquals(true, msg.startsWith("SRU diagnostic 48: Regexflags"));
+
+        query = "\"Fliegen\" /d";
+        error = FCSQLQueryProcessorTest.getError(new FCSQLQueryProcessor(query,
+                "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals(
+                "SRU diagnostic 48: Regexflag: IGNORE_DIACRITICS is unsupported.",
+                (String) error.get(1));
+    }
+
+    // operator ::= "=" /* equals */
+    // | "!=" /* non-equals */
+    @Test
+    public void testOperator() throws IOException {
+        String query = "[cnx:pos != \"N\"]";
+        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:N, "
+                + "foundry:cnx, layer:p, type:type:regex, match:match:ne}}";
+        runAndValidate(query, jsonLd);
+    }
+
+
+    // attribute operator flagged-regexp
+    // -------------------------------------------------------------------------
+    // attribute ::= simple-attribute | qualified-attribute
+    // -------------------------------------------------------------------------
+
+    // simple-attribute ::= identifier
+    @Test
+    public void testTermQueryWithSpecificLayer() throws JsonProcessingException {
+        String query = "[text = \"Sonne\"]";
+        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:Sonne, "
+                + "foundry:opennlp, layer:orth, type:type:regex, match:match:eq}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[lemma = \"sein\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:sein, "
+                + "foundry:tt, layer:l, type:type:regex, match:match:eq}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[pos = \"NN\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:NN, "
+                + "foundry:tt, layer:p, type:type:regex, match:match:eq}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    // qualified-attribute ::= identifier ":" identifier
+    @Test
+    public void testTermQueryWithQualifier() throws JsonProcessingException {
+        String query = "[mate:lemma = \"sein\"]";
+        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:sein, "
+                + "foundry:mate, layer:l, type:type:regex, match:match:eq}}";
+        runAndValidate(query, jsonLd);
+
+        query = "[cnx:pos = \"N\"]";
+        jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:N, "
+                + "foundry:cnx, layer:p, type:type:regex, match:match:eq}}";
+        runAndValidate(query, jsonLd);
+    }
+
+    @Test
+    public void testTermQueryException() throws JsonProcessingException {
+        String query = "[opennlp:lemma = \"sein\"]";
+        List<Object> error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals(
+                "SRU diagnostic 48: Layer lemma with qualifier opennlp is unsupported.",
+                error.get(1));
+
+        query = "[malt:lemma = \"sein\"]";
+        error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals("SRU diagnostic 48: Qualifier malt is unsupported.",
+                error.get(1));
+
+        query = "[cnx:morph = \"heit\"]";
+        error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(306, error.get(0));
+        assertEquals("SRU diagnostic 48: Layer morph is unsupported.",
+                error.get(1));
+
+    }
+
+    // segment-query ::= "[" expression? "]"
+    // -------------------------------------------------------------------------
+    // expression ::= basic-expression
+    // | expression "|" expression /* or */
+    // | expression "&" expression /* and */
+    // -------------------------------------------------------------------------
+
+    // | expression "|" expression /* or */
+    @Test
+    public void testExpressionOr() throws IOException {
+        String query = "[mate:lemma=\"sein\" | mate:pos=\"PPOSS\"]";
+        String jsonLd = "{@type: koral:token,"
+                + " wrap: { @type: koral:termGroup,"
+                + "relation: relation:or,"
+                + " operands:["
+                + "{@type: koral:term, key: sein, foundry: mate, layer: l, type:type:regex, match: match:eq},"
+                + "{@type: koral:term, key: PPOSS, foundry: mate, layer: p, type:type:regex, match: match:eq}]}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[cnx:lemma=\"sein\" | mate:lemma=\"sein\" | mate:pos=\"PPOSS\"]";
+        jsonLd = "{@type: koral:term, key: sein, foundry: cnx, layer: l, type:type:regex, match: match:eq}";
+        FCSQLQueryProcessorTest.validateNode(query, "/query/wrap/operands/0",
+                jsonLd);
+
+    }
+
+    // | expression "&" expression /* and */
+    @Test
+    public void testExpressionAnd() throws IOException {
+        String query = "[mate:lemma=\"sein\" & mate:pos=\"PPOSS\"]";
+        String jsonLd = "{@type: koral:token,"
+                + " wrap: { @type: koral:termGroup,"
+                + "relation: relation:and,"
+                + " operands:["
+                + "{@type: koral:term, key: sein, foundry: mate, layer: l, type:type:regex, match: match:eq},"
+                + "{@type: koral:term, key: PPOSS, foundry: mate, layer: p, type:type:regex, match: match:eq}]}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    // -------------------------------------------------------------------------
+    // basic-expression ::= '(' expression ')' /* grouping */
+    // | "!" expression /* not */
+    // | attribute operator flagged-regexp
+    // -------------------------------------------------------------------------
+
+    // basic-expression ::= '(' expression ')' /* grouping */
+
+    @Test
+    public void testExpressionGroup() throws JsonProcessingException {
+        String query = "[(text=\"blau\"|pos=\"ADJ\")]";
+        String jsonLd = "{@type: koral:token,"
+                + "wrap: {@type: koral:termGroup,"
+                + "relation: relation:or,"
+                + "operands: ["
+                + "{@type: koral:term, key: blau, foundry: opennlp, layer: orth, type:type:regex,match: match:eq},"
+                + "{@type: koral:term, key: ADJ, foundry: tt, layer: p, type:type:regex, match: match:eq}]}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+    // "!" expression /* not */
+    @Test
+    public void testExpressionNot() throws IOException {
+        String jsonLd = "{@type:koral:token, wrap:{@type:koral:term, key:NN, "
+                + "foundry:tt, layer:p, type:type:regex, match:match:eq}}";
+        String query = "[!pos != \"NN\"]";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+        query = "[!!pos = \"NN\"]";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+        query = "[!!!pos != \"NN\"]";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+
+        query = "[mate:lemma=\"sein\" & !mate:pos=\"PPOSS\"]";
+        jsonLd = "{@type: koral:token,"
+                + " wrap: { "
+                + "@type: koral:termGroup,"
+                + "relation: relation:and,"
+                + " operands:["
+                + "{@type: koral:term, key: sein, foundry: mate, layer: l, type:type:regex, match: match:eq},"
+                + "{@type: koral:term, key: PPOSS, foundry: mate, layer: p, type:type:regex, match: match:ne}]}}";
+        FCSQLQueryProcessorTest.runAndValidate(query, jsonLd);
+    }
+
+
+    @Test
+    public void testWrongQuery() throws IOException {
+        String query = "!(mate:lemma=\"sein\" | mate:pos=\"PPOSS\")";
+        List<Object> error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(399, error.get(0));
+        assertEquals(true,
+                error.get(1).toString().startsWith("FCS diagnostic 10"));
+
+        query = "![mate:lemma=\"sein\" | mate:pos=\"PPOSS\"]";
+        error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(true,
+                error.get(1).toString().startsWith("FCS diagnostic 10"));
+
+        query = "(\"blaue\"&\"grüne\")";
+        error = getError(new FCSQLQueryProcessor(query, "2.0"));
+        assertEquals(true,
+                error.get(1).toString().startsWith("FCS diagnostic 10"));
+    }
+
+}