blob: f9c0213ab8d266e2f810123ab3826468455f3f19 [file] [log] [blame]
package de.ids_mannheim.korap.query.serialize;
import de.ids_mannheim.korap.util.QueryException;
import org.z3950.zing.cql.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author margaretha
* @date 09.05.14
*/
public class CQLTree extends AbstractSyntaxTree {
private static final String VERSION_1_1 = "1.1";
private static final String VERSION_1_2 = "1.2";
private static final String INDEX_CQL_SERVERCHOICE = "cql.serverChoice";
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 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 CQLTree(String query) throws QueryException {
this(query, VERSION_1_2, true);
}
public CQLTree(String query, String version) throws QueryException {
this(query, version, true);
}
public CQLTree(String query, String version, boolean isCaseSensitive) throws QueryException {
this.version = version;
this.isCaseSensitive = isCaseSensitive;
this.requestMap = new LinkedHashMap<>();
requestMap.put("@context", KORAP_CONTEXT);
process(query);
}
@Override
public Map<String, Object> getRequestMap() {
return this.requestMap;
}
@Override
public void process(String query) throws QueryException {
if ((query == null) || query.isEmpty())
throw new QueryException(400, "SRU diagnostic 27: An empty query is unsupported.");
CQLNode cqlNode = parseQuerytoCQLNode(query);
Map<String,Object> queryMap = parseCQLNode(cqlNode);
requestMap.put("query", queryMap);
//requestMap.put("query", sentenceWrapper(queryMap));
}
private Map<String,Object> sentenceWrapper(Map<String,Object> m){
Map<String, Object> map = new LinkedHashMap<String,Object>();
map.put("@type", "korap:group");
map.put("operation", OPERATION_POSITION);
map.put("frame", "frame:contains");
Map<String, Object> sentence = new LinkedHashMap<String,Object>();
sentence.put("@type", "korap:span");
sentence.put("key", "s");
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
list.add(sentence);
list.add(m);
map.put("operands", list);
return map;
}
private CQLNode parseQuerytoCQLNode(String query) throws QueryException {
try {
int compat = -1;
switch (version) {
case VERSION_1_1:
compat = CQLParser.V1POINT1;
break;
case VERSION_1_2:
compat = CQLParser.V1POINT2;
}
return new CQLParser(compat).parse(query);
} catch (CQLParseException | IOException e) {
throw new QueryException("Error parsing CQL");
}
}
private Map<String,Object> parseCQLNode(CQLNode node) throws QueryException {
if (node instanceof CQLTermNode) {
return parseTermNode((CQLTermNode) node);
} else if (node instanceof CQLAndNode) {
return parseAndNode((CQLAndNode) node);
} else if (node instanceof CQLOrNode) {
return parseOrNode((CQLOrNode) node);
} else {
throw new QueryException(400, "SRU diagnostic 48: Only basic search including term-only " +
"and boolean operator queries (AND and OR) are currently supported.");
}
}
private Map<String,Object> parseTermNode(CQLTermNode node) throws QueryException {
checkTermNode(node);
final String term = node.getTerm();
if ((term == null) || term.isEmpty()) {
throw new QueryException(400, "SRU diagnostic 27: An empty term is unsupported.");
} else if (term.contains(" ")) {
return writeSequence(term);
} else {
return writeTerm(term);
}
}
private Map<String,Object> parseAndNode(CQLAndNode node) throws QueryException {
checkBooleanModifier(node);
Map<String, Object> map = new LinkedHashMap<String,Object>();
map.put("@type", "korap:group");
map.put("operation", OPERATION_SEQUENCE);
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
Map<String, Object> distanceMap = new LinkedHashMap<String,Object>();
distanceMap.put("@type", "korap:distance");
distanceMap.put("key", "w");
distanceMap.put("min", "0");
distanceMap.put("max", "0");
list.add(distanceMap);
map.put("distances", list);
List<Map<String, Object>> operandList = new ArrayList<Map<String, Object>>();
operandList.add(parseCQLNode(node.getLeftOperand()));
operandList.add(parseCQLNode(node.getRightOperand()));
map.put("operands", operandList);
return map;
}
private Map<String,Object> parseOrNode(CQLOrNode node) throws QueryException {
checkBooleanModifier(node);
Map<String, Object> map = new LinkedHashMap<String,Object>();
map.put("@type", "korap:group");
map.put("operation", OPERATION_OR);
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
list.add(parseCQLNode(node.getLeftOperand()));
list.add(parseCQLNode(node.getRightOperand()));
map.put("operands", list);
return map;
}
private Map<String, Object> writeSequence(String str) {
Map<String, Object> sequenceMap = new LinkedHashMap<String,Object>();
sequenceMap.put("@type", "korap:group");
sequenceMap.put("operation", OPERATION_SEQUENCE);
List<Map<String, Object>> termList = new ArrayList<Map<String, Object>>();
String[] terms = str.split(" ");
for (String term : terms){
termList.add(writeTerm(term));
}
sequenceMap.put("operands", termList);
return sequenceMap;
}
private Map<String, Object> writeTerm(String term) {
Map<String, Object> map = new LinkedHashMap<String,Object>();
map.put("@type", "korap:term");
if (!isCaseSensitive) {
map.put("caseInsensitive","true");
}
map.put("key", term);
map.put("layer", "orth");
map.put("match", "match:eq");
Map<String, Object> tokenMap = new LinkedHashMap<String,Object>();
tokenMap.put("@type", "korap:token");
tokenMap.put("wrap", map);
return tokenMap;
}
private void checkBooleanModifier(CQLBooleanNode node) throws QueryException {
List<Modifier> modifiers = node.getModifiers();
if ((modifiers != null) && !modifiers.isEmpty()) {
Modifier modifier = modifiers.get(0);
throw new QueryException(400, "SRU diagnostic 20: Relation modifier " +
modifier.toCQL() + " is not supported.");
}
}
private void checkTermNode(CQLTermNode node) throws QueryException {
// only allow "cql.serverChoice" and "words" index
if (!(INDEX_CQL_SERVERCHOICE.equals(node.getIndex()) ||
INDEX_WORDS.equals(node.getIndex()))) {
throw new QueryException(400, "SRU diagnostic 16: Index " + node.getIndex() + " is not supported.");
}
// only allow "=" relation without any modifiers
CQLRelation relation = node.getRelation();
String baseRel = relation.getBase();
if (!(TERM_RELATION_CQL_1_1.equals(baseRel) ||
TERM_RELATION_CQL_1_2.equals(baseRel) ||
SUPPORTED_RELATION_EXACT.equals(baseRel))) {
throw new QueryException(400, "SRU diagnostic 19: Relation " +
relation.getBase() + " is not supported.");
}
List<Modifier> modifiers = relation.getModifiers();
if ((modifiers != null) && !modifiers.isEmpty()) {
Modifier modifier = modifiers.get(0);
throw new QueryException(400, "SRU diagnostic 20: Relation modifier " +
modifier.getValue() + " is not supported.");
}
}
}