/*
Copyright (c) 2008, Centre for Educational Technology, Tallinn University
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package ee.tlu.htk.waramu.fire;
import ee.tlu.htk.waramu.base.BaseType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
* @author vahur
*/
public class PLQLParser extends ParserBase {
/**
* Creates a new instance of PLQLParser
*
* Query: lom.general.title = abc
* Translated query: lom.general.title:abc
* SQL: WHERE content LIKE '%%abc%%'
*
* Query: lre.discipline=195 and test
* Translated query:
* lom.classification.purpose=lomv1.0=discipline:195 AND contents: test
* SQL: WHERE content LIKE '%%195%%'
* AND content LIKE '%test%'
*
*
* Query: lre.cc="by-sa"
* Translated query: lom.rights.description.string.language=x-t-cc:by-sa
* SQL: WHERE content LIKE '%%by-sa%%'
*
* Query: lom.general.(title = "Design Patterns" and language = "en")
* Translated query:lom.general.( title = Design Patterns AND language = en )
* SQL: WHERE content LIKE '%%DP%%'
* AND content LIKE '%<>en>%'
*
* Query:
* Translated Query: null
*
* Query: (lom.general.title = "abc") and tiger
* Translated query:
* ( lom.general.title:abc )
* AND
* contents: tiger
*
* Query: lre.metadataId=lrefiredb151976
* Translated query:lom.metametadata.identifier.catalog.entry:lrefiredb151976
*
* Query: saiake
* Translated query:contents: saiake
*
* Query: lre.typicalAgeRange = 12-u
* Translated query:lom.educational.typicalagerange.string.language=x-t-lre:[012 TO 199]
*
* Query: lom.general.((title = 12) and language="fr")
* Translated query:lom.general.( ( title = 12 ) AND language = fr )
*
* Query: lre.status= "draft"
* Translated query:
* lom.lifecycle.status.source=lomv1.0.value:draft
*
* Query: lom.general.title = "Design Patterns" and lom.educational.(intendedEndUserRole = "learner" and typicalAgeRange = "15-18")
* Translated query:
* lom.general.title:Design Patterns
* AND
* lom.educational.( intendedenduserrole = learner AND typicalagerange = 15-18 )
*/
private List sampleObjects;
private static final Pattern parpat = Pattern.compile("[(](.*?)[)]", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
private static final Pattern andpat = Pattern.compile("AND|OR", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
PLQLParser(List samples) {
this.sampleObjects = samples;
}
@Override
public String[] parse(String query) {
List pipe = parseQuery(query);
String[] sqls = makeSQL(pipe);
// remove duplicates
List res = new ArrayList();
for ( String sq: sqls) {
if ( !res.contains(sq)) {
res.add(sq);
}
}
return res.toArray(new String[] {});
}
private List parseQuery(String query) {
List pipe1 = new ArrayList();
List pipe2 = new ArrayList();
List pipe3 = new ArrayList();
// split by ( )
//Pattern pat = Pattern.compile("[(](.*?)[)]", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
Matcher mat = parpat.matcher(query);
int prev = 0;
while (mat.find()) {
pipe1.add(query.substring(prev, mat.start()));
pipe1.add("(");
pipe1.add(mat.group(1));
pipe1.add(")");
prev = mat.end();
}
pipe1.add(query.substring(prev));
// split by AND or OR
for (int i = 0; i < pipe1.size(); i++) {
String p = pipe1.get(i);
mat = andpat.matcher(p);
boolean found = false;
while (mat.find()) {
String op = mat.group().trim();
String left = p.substring(0, mat.start()).trim();
String right = p.substring(mat.end()).trim();
if (left.length() > 0) {
pipe2.add(left);
}
pipe2.add(op);
if (right.length() > 0) {
pipe2.add(right);
}
found = true;
}
if (!found) {
pipe2.add(p.trim());
}
}
// handle parentheses
boolean with_prefix = false;
String prefix = "";
for (int i = 0; i < pipe2.size(); i++) {
String k = pipe2.get(i);
if (k.endsWith(".")) {
if (pipe2.get(i + 1).equals("(")) {
prefix = k;
with_prefix = true;
pipe3.add("(");
//i++;
continue;
}
}
if (k.equals("(")) {
if (with_prefix) {
continue;
}
}
if (k.equals(")")) {
if (with_prefix) {
with_prefix = false;
//continue;
}
}
if (with_prefix) {
if (k.toLowerCase().equals("and") || k.toLowerCase().equals("or")) {
pipe3.add(k);
} else {
pipe3.add(prefix + k);
}
}
if (!with_prefix) {
pipe3.add(k);
}
}
return pipe3;
}
private String[] makeSQL(List pipe3) {
List res = new ArrayList();
for (BaseType obj : sampleObjects) {
List joined = new ArrayList();
String q = "SELECT INTERNAL_ID FROM ";
String joins = "";
String wheres = " WHERE "; // length of 7, don't change!
HashMap mapp = obj.getQLMapping("plql");
boolean prev_skipped = false;
boolean has_conditions = false;
for (int j = 0; j < pipe3.size(); j++) {
String k = pipe3.get(j);
if (k.trim().length() == 0) {
continue;
}
if (k.contains("=")) {
String key = k.split("=")[0].trim();
String val = k.split("=")[1].trim();
if (!needHandling(key)) {
HashMap hm = (HashMap) mapp.get(key);
if (hm == null) {
if (j != 0 && wheres.length() != 7) {
String prev_k = pipe3.get(j - 1);
// || prev_k.equals("(") || prev_k.equals(")")
if (prev_k.toUpperCase().equals("AND") || prev_k.toUpperCase().equals("OR")) {
if (!prev_skipped) {
wheres = wheres.substring(0, wheres.length() - (prev_k.length() + 1));
}
}
}
prev_skipped = true;
continue; // just leave it out for now
}
String column = (String) hm.get("column");
String tablename = (String) hm.get("tableName");
if (!joined.contains(tablename)) {
joins += " JOIN " + tablename + " USING (INTERNAL_ID) ";
joined.add(tablename);
}
wheres += " " + column + "=" + val;
has_conditions = true;
} else {
HashMap resp = handleSpecial(mapp, key, val);
if (resp != null) {
has_conditions = true;
wheres += resp.get("wheres");
List sjo = (List) resp.get("joins");
for (String tabn : sjo) {
if (!joined.contains(tabn)) {
joins += " JOIN " + tabn + " USING (INTERNAL_ID) ";
joined.add(tabn);
}
}
}
}
} else if (k.toUpperCase().equals("AND") || k.toUpperCase().equals("OR")) {
if (wheres.length() == 7) {
// nothing added to where yet.
} else if (prev_skipped) {
continue;
} else {
wheres += " " + k + " ";
}
} else if (k.equals("(") || k.equals(")")) {
wheres += " " + k + " ";
} else {
HashMap hm = (HashMap) mapp.get("fulltext");
if (hm == null) {
prev_skipped = true;
continue;
}
String column = (String) hm.get("column");
String tablename = (String) hm.get("tableName");
if (!joined.contains(tablename)) {
joins += " JOIN " + tablename + " USING (INTERNAL_ID) ";
joined.add(tablename);
}
wheres += " " + column + " LIKE '%" + k + "%' ";
has_conditions = true;
}
prev_skipped = false;
}
q += joins + wheres;
if (has_conditions) {
res.add(q);
}
}
//return new String[]{};
return res.toArray(new String[]{});
}
private boolean needHandling(String key) {
if (key.equals("lom.educational.typicalAgeRange")) {
return true;
}
return false;
}
/**
* returns String[]
* * key "wheres" - SQL where part
* * key "joins" - table named needed for query to work
* @param map data type mapping
* @param key
* @param val
* @return
*/
private HashMap handleSpecial(HashMap map, String key, String val) {
HashMap hashres = new HashMap();
hashres.put("wheres", "");
hashres.put("joins", null);
String wheres = "";
List joins = new ArrayList();
if (key.equals("lom.educational.typicalAgeRange")) {
String[] c = new String[]{"min", "max"};
String[] ops = new String[]{">", "<"};
String[] vals = val.split("-");
for (int i = 0; i < c.length; i++) {
String s = c[i]; // "min" or "max"
String ival = vals[i]; // min or max value
HashMap hm = (HashMap) map.get(key + "." + s);
if (hm == null) {
return null;
}
String column = (String) hm.get("column");
String tableName = (String) hm.get("tableName");
if (!joins.contains(tableName)) {
joins.add(tableName);
}
if (i == 1) {
wheres += " AND ";
}
wheres += " " + column + ops[i] + ival.replaceAll("\\D", "");
}
}
hashres.put("wheres", wheres);
hashres.put("joins", joins);
return hashres;
}
}