Author: michiel
Date: 2010-03-19 19:04:52 +0100 (Fri, 19 Mar 2010)
New Revision: 41545
Added:
mmbase/trunk/bridge/src/main/java/org/mmbase/storage/search/QueryContext.java
mmbase/trunk/bridge/src/main/java/org/mmbase/storage/search/legacy/ConstraintParser.java
Modified:
mmbase/trunk/bridge/src/main/java/org/mmbase/bridge/util/Queries.java
mmbase/trunk/bridge/src/main/java/org/mmbase/storage/search/ClusterQueries.java
Log:
Moved ConstraintParser/QueryContext stuff to bridge jar
Modified: mmbase/trunk/bridge/src/main/java/org/mmbase/bridge/util/Queries.java
===================================================================
--- mmbase/trunk/bridge/src/main/java/org/mmbase/bridge/util/Queries.java
2010-03-19 17:56:00 UTC (rev 41544)
+++ mmbase/trunk/bridge/src/main/java/org/mmbase/bridge/util/Queries.java
2010-03-19 18:04:52 UTC (rev 41545)
@@ -121,7 +121,7 @@
searchDir = encoder.encode(searchDir);
}
if (constraints != null) {
- constraints = ConstraintParser.convertClauseToDBS(constraints);
+ constraints = ConstraintParser.convertClauseToDBS(new
QueryContext.Bridge(cloud), constraints);
if (! ConstraintParser.validConstraints(constraints)) {
throw new BridgeException("invalid constraints:" +
constraints);
}
@@ -167,7 +167,7 @@
}
// (Try to) parse constraints string to Constraint object.
- Constraint newConstraint = new
ConstraintParser(query).toConstraint(constraints);
+ Constraint newConstraint = new ConstraintParser(new
QueryContext.Bridge(query.getCloud()), query).toConstraint(constraints);
addConstraint(query, newConstraint);
return newConstraint;
}
@@ -1468,9 +1468,9 @@
/**
- * Deletes the relations with a node from a queries resulting relations
list.
+ * Deletes the relations with a node from a queries resulting relations
list.
* If multiple relations to a node exist all get removed.
- *
+ *
* @throws UnsupportedOperationException If it cannot be determined how
the node should be related.
* @param q query from which resulting list the node should be removed from
* @param n node to remove
@@ -1490,7 +1490,7 @@
*
* @throws UnsupportedOperationException If it cannot be determined how
the node is related.
* @since MMBase-1.9.1
- * @param q query that constructs the list
+ * @param q query that constructs the list
* @param n node to which relations are related
* @return The relation nodes
* @throws NullPointerException if q or n is <code>null</code>
Modified:
mmbase/trunk/bridge/src/main/java/org/mmbase/storage/search/ClusterQueries.java
===================================================================
---
mmbase/trunk/bridge/src/main/java/org/mmbase/storage/search/ClusterQueries.java
2010-03-19 17:56:00 UTC (rev 41544)
+++
mmbase/trunk/bridge/src/main/java/org/mmbase/storage/search/ClusterQueries.java
2010-03-19 18:04:52 UTC (rev 41545)
@@ -12,15 +12,11 @@
import java.util.*;
import org.mmbase.bridge.Field;
import org.mmbase.storage.search.implementation.*;
-import org.mmbase.storage.search.legacy.ConstraintParser;
-import org.mmbase.storage.search.legacy.QueryConvertor;
import org.mmbase.util.logging.*;
/**
- * This g
*
- * @author michiel
* @author Rob van Maris (ClusterBuilder)
* @version $Id: $
*/
@@ -110,7 +106,7 @@
protected String getTableName(String table) {
int end = table.length() ;
if (end == 0) throw new IllegalArgumentException("Table name too short
'" + table + "'");
- while (Character.isDigit(table.charAt(end -1))) {
+ while (Character.isDigit(table.charAt(end - 1))) {
--end;
}
return table.substring(0, end );
@@ -232,13 +228,14 @@
addRelationDirections(query, searchDirs, roles);
- // Add constraints.
- // QueryConverter supports the old formats for backward compatibility.
- QueryConvertor.setConstraint(query, where);
+ setConstraint(query, where);
return query;
}
+ protected abstract void setConstraint(BasicSearchQuery query, String
where);
+
+
protected abstract boolean isRelation(String builder);
/**
@@ -509,7 +506,7 @@
StepField field= fieldsByAlias.get(fieldName);
if (field == null) {
// Field has not been added.
- field = ConstraintParser.getField(fieldName, query.getSteps());
+ field = getField(fieldName, query);
}
if (field == null) {
throw new IllegalArgumentException("Invalid fieldname: \"" +
fieldName + "\"");
@@ -533,6 +530,8 @@
}
}
+ protected abstract StepField getField(String fieldName, BasicSearchQuery
query);
+
protected abstract String getBuilder(int nodeNumber);
protected abstract String getParentBuilder(String builder);
Copied:
mmbase/trunk/bridge/src/main/java/org/mmbase/storage/search/QueryContext.java
(from rev 41544,
mmbase/trunk/core/src/main/java/org/mmbase/storage/search/QueryContext.java)
===================================================================
---
mmbase/trunk/bridge/src/main/java/org/mmbase/storage/search/QueryContext.java
(rev 0)
+++
mmbase/trunk/bridge/src/main/java/org/mmbase/storage/search/QueryContext.java
2010-03-19 18:04:52 UTC (rev 41545)
@@ -0,0 +1,45 @@
+/*
+
+This software is OSI Certified Open Source Software.
+OSI Certified is a certification mark of the Open Source Initiative.
+
+The license (Mozilla version 1.0) can be read at the MMBase site.
+See http://www.MMBase.org/license
+
+*/
+package org.mmbase.storage.search;
+
+import java.util.*;
+import org.mmbase.bridge.*;
+
+/**
+ * How there is dealt with SearchQuery object may depend a bit on the actual
implementation of the bridge and storage.
+ * This is collected in implemetnations of this interface
+ *
+ *
+ * @author Michiel Meeuwissen
+ * @version $Id: Step.java 36486 2009-06-29 17:33:41Z michiel $
+ * @since MMBase-2.0
+ */
+public interface QueryContext {
+
+ String getStorageIdentifier(String s);
+ Field getField(String builder, String fieldName);
+
+
+ public static class Bridge implements QueryContext {
+
+ protected final Cloud cloud;
+ public Bridge(Cloud c) {
+ cloud = c;
+ }
+ public String getStorageIdentifier(String s) {
+ return s;
+ }
+ public Field getField(String builder, String fieldName) {
+ return cloud.getNodeManager(builder).getField(fieldName);
+ }
+ }
+
+
+}
Copied:
mmbase/trunk/bridge/src/main/java/org/mmbase/storage/search/legacy/ConstraintParser.java
(from rev 41544,
mmbase/trunk/core/src/main/java/org/mmbase/storage/search/legacy/ConstraintParser.java)
===================================================================
---
mmbase/trunk/bridge/src/main/java/org/mmbase/storage/search/legacy/ConstraintParser.java
(rev 0)
+++
mmbase/trunk/bridge/src/main/java/org/mmbase/storage/search/legacy/ConstraintParser.java
2010-03-19 18:04:52 UTC (rev 41545)
@@ -0,0 +1,1083 @@
+/*
+
+This software is OSI Certified Open Source Software.
+OSI Certified is a certification mark of the Open Source Initiative.
+
+The license (Mozilla version 1.0) can be read at the MMBase site.
+See http://www.MMBase.org/license
+
+ */
+package org.mmbase.storage.search.legacy;
+
+import java.util.*;
+import org.mmbase.bridge.*;
+import org.mmbase.storage.search.*;
+import org.mmbase.storage.search.implementation.*;
+import org.mmbase.util.logging.*;
+import org.mmbase.bridge.NodeQuery;
+
+/**
+ * Parser, tries to parse a <em>SQL-search-condition</em> for a query to a
+ * {...@link org.mmbase.storage.search.Constraint Constraint} object.
+ * <p>
+ * This class is provided for the sole purpose of alignment of old code with
+ * the new {...@link org.mmbase.storage.search.SearchQuery SearchQuery}
framework,
+ * and should not be called by new code.
+ * <p>
+ * A <em>SQL-search-condition</em> can be one of these forms:
+ * <ul>
+ * <li>[<b>NOT</b>] <b>(</b><em>SQL-search-condition</em><b>)</b>
+ * <li>[<b>NOT</b>] <em>simple-SQL-search-condition</em>
+ * <li><em>SQL-search-condition</em> <b>AND</b> <em>SQL-search-condition</em>
+ * <li><em>SQL-search-condition</em> <b>OR</b> <em>SQL-search-condition</em>
+ * </ul>
+ * A <em>simple-SQL-search-condition</em> string can be of one of these forms:
+ * <ul>
+ * <li><em>field</em> [<b>NOT</b>] <b>LIKE</b> <em>value</em>
+ * <li><b>UPPER(</b><em>field</em><b>)</b> [<b>NOT</b>] <b>LIKE</b>
<em>value</em>
+ * <li><b>LOWER(</b><em>field</em><b>)</b> [<b>NOT</b>] <b>LIKE</b>
<em>value</em>
+ * <li><em>field</em> <b>IS</b> [<b>NOT</b>] <b>NULL</b>
+ * <li><em>field</em> [<b>NOT</b>] <b>IN
+ * (</b><em>value1</em><b>,</b> <em>value2</em><b>,</b> ..<b>)</b>
+ * <li><em>field</em> [<b>NOT</b>] <b>BETWEEN</b> <em>value1</em> <b>AND</b>
<em>value2</em>
+ * <li><b>UPPER(</b><em>field</em><b>)</b> [<b>NOT</b>] <b>BETWEEN</b>
<em>value1</em> <b>AND</b> <em>value2</em>
+ * <li><b>LOWER(</b><em>field</em><b>)</b> [<b>NOT</b>] <b>BETWEEN</b>
<em>value1</em> <b>AND</b> <em>value2</em>
+ * <li><em>field</em> <b>=</b> <em>value</em>
+ * <li><em>field</em> <b>=</b> <em>field2</em>
+ * <li><b>UPPER(</b><em>field</em><b>) =</b> <em>value</em>
+ * <li><b>LOWER(</b><em>field</em><b>) =</b> <em>value</em>
+ * <li><em>field</em> <b>==</b> <em>value</em>
+ * <li><em>field</em> <b>==</b> <em>field2</em>
+ * <li><em>field</em> <b><=</b> <em>value</em>
+ * <li><em>field</em> <b><=</b> <em>field2</em>
+ * <li><em>field</em> <b><</b> <em>value</em>
+ * <li><em>field</em> <b><</b> <em>field2</em>
+ * <li><em>field</em> <b>>=</b> <em>value</em>
+ * <li><em>field</em> <b>>=</b> <em>field2</em>
+ * <li><em>field</em> <b>></b> <em>value</em>
+ * <li><em>field</em> <b>></b> <em>field2</em>
+ * <li><em>field</em> <b><></b> <em>value</em>
+ * <li><em>field</em> <b><></b> <em>field2</em>
+ * <li><em>field</em> <b>!=</b> <em>value</em>
+ * <li><em>field</em> <b>!=</b> <em>field2</em>
+ * <li><em>string-search-condition</em>
+ * </ul>
+ * A <em>field</em> can be one of these forms:
+ * <ul>
+ * <li><em>stepalias</em><b>.</b><em>fieldname</em>
+ * <li><em>fieldname</em> (only when the query has just one step).
+ * </ul>
+ * A <em>value</em> can be one of these forms:
+ * <ul>
+ * <li><em>string between single quotes</em> for string fields, example:
<code>'A string value'</code>
+ * <li><em>numerical value</em> for numerical fields, example:
<code>123.456</code>
+ * <li><em>numerical value between single quotes</em> for numerical fields,
example" <code>'123.456'</code>
+ * </ul>
+ * A <em>string-search-condition</em> can be of this form:
+ * <ul>
+ * <li><b>StringSearch(</b><em>field</em><b>,</b>PHRASE|PROXIMITY|WORD<b>,</b>
+ * FUZZY|LITERAL|SYNONYM<b>,</b>
+ * <em>searchterms</em><b>,</b>
+ * <em>casesensitive</em><b>)</b>
+ * [<b>.set(FUZZINESS,</b><em>fuzziness</em><b>)</b>]
+ * [<b>.set(PROXIMITY_LIMIT,</b><em>proximity</em><b>)</b>]
+ * </ul>
+ * <em>searchterms</em> can be of one of these forms:
+ * <ul>
+ * <li><b>'</b>term1<b>'</b>
+ * <li><b>"</b>term1<b>"</b>
+ * <li><b>'</b>term1 term2<b>'</b>
+ * <li><b>"</b>term1 term2<b>"</b>
+ * <li> etc...
+ * </ul>
+ * <em>casesensitive</em> can be of one on these forms:
+ * <ul>
+ * <li><b>true</b>
+ * <li><b>false</b>
+ * </ul>
+ * <em>fuzziness</em> must be a float value between 0.0 and 1.0,
+ * <em>proximity</em> must be a int value > 0<br />
+ * <p>
+ * See {...@link org.mmbase.storage.search.StringSearchConstraint
+ * StringSearchConstraint} for more info on string-search constraints.
+ * <p>
+ * A search condition that is not of one of these forms will be converted to a
+ * {...@link org.mmbase.storage.search.LegacyConstraint LegacyConstraint}, i.e.
+ * in that case the search condition string will not be interpreted, but
+ * instead be used "as-is".
+ * Each time this occurs is logged with priority <code>service</code> to
+ * category
<code>org.mmbase.storage.search.legacyConstraintParser.fallback</code>.
+ *
+ * @author Rob van Maris
+ * @version $Id$
+ * @since MMBase-1.7
+ */
+public class ConstraintParser {
+
+ private final static Logger log =
Logging.getLoggerInstance(ConstraintParser.class);
+
+ /** Logger instance dedicated to logging fallback to legacy constraint. */
+ private final static Logger fallbackLog =
Logging.getLoggerInstance(ConstraintParser.class.getName() + ".fallback");
+
+
+ /**
+ * Converts a constraint by turning all 'quoted' fields into
+ * database supported fields.
+ * XXX: todo: escape characters for '[' and ']'.
+ * @param constraints constraint to convert
+ * @return Converted constraint
+ * @since MMBase-1.8.1 (moved from org.mmbase.bridge.util.Queries)
+ */
+ private static String convertClausePartToDBS(QueryContext queryContext,
String constraints) {
+ StringBuilder result = new StringBuilder();
+ int posa = constraints.indexOf('[');
+ while (posa > -1) {
+ int posb = constraints.indexOf(']', posa);
+ if (posb == -1) {
+ posa = -1;
+ } else {
+ String fieldName = constraints.substring(posa + 1, posb);
+ int posc = fieldName.indexOf('.');
+ if (posc == -1) {
+ fieldName = queryContext.getStorageIdentifier(fieldName);
+ } else {
+ fieldName = fieldName.substring(0, posc + 1) +
+
queryContext.getStorageIdentifier(fieldName.substring(posc + 1));
+ }
+ result.append(constraints.substring(0,
posa)).append(fieldName);
+ constraints = constraints.substring(posb + 1);
+ posa = constraints.indexOf('[');
+ }
+ }
+ result.append(constraints);
+ return result.toString();
+ }
+
+ /**
+ * Converts a constraint by turning all 'quoted' fields into
+ * database supported fields.
+ * XXX: todo: escape characters for '[' and ']'.
+ * @param constraints constraints to convert
+ * @return converted constraint
+ * @since MMBase-1.8.1 (moved from org.mmbase.bridge.util.Queries)
+ */
+ public static String convertClauseToDBS(QueryContext queryContext, String
constraints) {
+ if (constraints.startsWith("MMNODE")) {
+ // wil probably not work
+ // @todo check
+ return constraints;
+ } else if (constraints.startsWith("ALTA")) {
+ // wil probably not work
+ // @todo check
+ return constraints.substring(5);
+ }
+
+ //keesj: what does this code do?
+
+ StringBuilder result = new StringBuilder();
+ //if there is a quote in the constraints posa will not be equals -1
+
+ int quoteOpen = constraints.indexOf('\'');
+ while (quoteOpen > -1) {
+ //keesj: posb can be the same a posa maybe the method should read
indexOf("\"",posa) ?
+ int quoteClose = constraints.indexOf('\'', quoteOpen + 1);
+ if (quoteClose == -1) {
+ // unmatching quote?
+ log.warn("unbalanced quote in " + constraints);
+ break;
+ }
+
+ //keesj:part is now the first part of the constraints if there is
a quote in the query
+ String part = constraints.substring(0, quoteOpen);
+
+ //append to the string buffer "part" the first part
+ result.append(convertClausePartToDBS(queryContext, part));
+ result.append(constraints.substring(quoteOpen, quoteClose + 1));
+
+ constraints = constraints.substring(quoteClose + 1);
+ quoteOpen = constraints.indexOf('\'');
+
+ }
+ result.append(convertClausePartToDBS(queryContext, constraints));
+ return result.toString();
+ }
+
+ /**
+ * returns false, when escaping wasnt closed, or when a ";" was found
outside a escaped part (to prefent spoofing)
+ * This is used by createQuery (i wonder if it still makes sense)
+ * @param constraints constraint to check
+ * @return is valid constraint
+ * @since MMBase-1.8.1 (moved from org.mmbase.bridge.util.Queries)
+ */
+ static public boolean validConstraints(String constraints) {
+ // first remove all the escaped "'" ('' occurences) chars...
+ String remaining = constraints;
+ while (remaining.indexOf("''") != -1) {
+ int start = remaining.indexOf("''");
+ int stop = start + 2;
+ if (stop < remaining.length()) {
+ String begin = remaining.substring(0, start);
+ String end = remaining.substring(stop);
+ remaining = begin + end;
+ } else {
+ remaining = remaining.substring(0, start);
+ }
+ }
+ // assume we are not escaping... and search the string..
+ // Keep in mind that at this point, the remaining string could contain
different information
+ // than the original string. This doesnt matter for the next
sequence...
+ // but it is important to realize!
+ while (remaining.length() > 0) {
+ if (remaining.indexOf('\'') != -1) {
+ // we still contain a "'"
+ int start = remaining.indexOf('\'');
+
+ // escaping started, but no stop
+ if (start == remaining.length()) {
+ log.warn("reached end, but we are still escaping(you
should sql-escape the search query inside the jsp-page?)\noriginal:" +
constraints);
+ return false;
+ }
+
+ String notEscaped = remaining.substring(0, start);
+ if (notEscaped.indexOf(';') != -1) {
+ log.warn("found a ';' outside the constraints(you should
sql-escape the search query inside the jsp-page?)\noriginal:" + constraints +
"\nnot excaped:" + notEscaped);
+ return false;
+ }
+
+ int stop = remaining.substring(start + 1).indexOf('\'');
+ if (stop < 0) {
+ log.warn("reached end, but we are still escaping(you
should sql-escape the search query inside the jsp-page?)\noriginal:" +
constraints + "\nlast escaping:" + remaining.substring(start + 1));
+ return false;
+ }
+ // we added one to to start, thus also add this one to stop...
+ stop = start + stop + 1;
+
+ // when the last character was the stop of our escaping
+ if (stop == remaining.length()) {
+ return true;
+ }
+
+ // cut the escaped part from the string, and continue with
resting sting...
+ remaining = remaining.substring(stop + 1);
+ } else {
+ if (remaining.indexOf(';') != -1) {
+ log.warn("found a ';' inside our constrain:" +
constraints);
+ return false;
+ }
+ return true;
+ }
+ }
+ return true;
+ }
+
+
+
+ private SearchQuery query = null;
+ private List<? extends Step> steps = null;
+
+ /**
+ * Parses string or numerical value from list of tokens, to match the type
+ * of the specified field.
+ * If the first token is not "'", it is interpreted as a numerical value,
+ * otherwise it is required to be the first token of the sequence
+ * "'", "value", "'", representing a string value for string fields, or
+ * a numerical value for numerical fields.
+ *
+ * @param iTokens Tokens iterator, must be positioned before the (first)
+ * token representing the value.
+ * @param field The field.
+ * @return A <code>String</code> or <code>Double</code> object representing
+ * the value.
+ * @throws NumberFormatException when the first token is not (the start of)
+ * a valid value expression (it may be a <em>field</em> instead).
+ */
+ // package visibility!
+ static Object parseValue(Iterator<String> iTokens, StepField field) throws
NumberFormatException {
+ Object result = null;
+ String token = iTokens.next();
+ if (token.equals("'")) {
+ // String value.
+ result = iTokens.next();
+ token = iTokens.next();
+ if (!token.equals("'")) {
+ throw new IllegalArgumentException("Unexpected token (expected
\"'\"): \"" + token + "\"");
+ }
+
+ int fieldType = field.getType();
+ if (fieldType == Field.TYPE_BINARY || fieldType ==
Field.TYPE_DOUBLE ||
+ fieldType == Field.TYPE_FLOAT || fieldType ==
Field.TYPE_INTEGER ||
+ fieldType == Field.TYPE_LONG || fieldType == Field.TYPE_NODE) {
+ // String represents a numerical value.
+ result = Double.valueOf((String) result);
+ }
+ } else {
+ result = Double.valueOf(token);
+ }
+ return result;
+ }
+
+ /**
+ * Parses SQL search condition string into separate tokens, discarding
+ * white spaces, concatenating strings between (single/double) quotes,
+ * and replacing escaped (single/double) quotes in strings by the
+ * original character.
+ *
+ * @param sqlConstraint The SQL constraint string.
+ * @return List of tokens.
+ */
+ // package visibility!
+ static List<String> tokenize(String sqlConstraint) {
+ // Parse into separate tokens.
+ List<String> tokens = new ArrayList<String>();
+ StringTokenizer st = new StringTokenizer(sqlConstraint, " ()'\"=<>!,",
true);
+ tokenize:
+ while (st.hasMoreTokens()) {
+ String token = st.nextToken(" ()'\"=<>!,");
+
+ // String, delimited by single or double quotes.
+ if (token.equals("'") || token.equals("\"")) {
+ tokens.add("'");
+ StringBuilder sb = new StringBuilder();
+ while (true) {
+ String token2 = st.nextToken(token);
+ if (token2.equals(token)) {
+ if (!st.hasMoreTokens()) {
+ // Token 2 is end delimiter and last token.
+ tokens.add(sb.toString());
+ tokens.add("'");
+ break tokenize;
+ } else {
+ String token3 = st.nextToken(" ()'\"=<>!,");
+ if (token3.equals(token)) {
+ // Token 2 and 3 are escaped delimiter.
+ sb.append(token);
+ } else {
+ // Token 2 is end delimiter, but not last
token.
+ tokens.add(sb.toString());
+ tokens.add("'");
+ token = token3;
+ break;
+ }
+ }
+ } else {
+ // Token 2 is string.
+ sb.append(token2);
+ }
+ }
+ }
+
+ // Add token, but skip white spaces.
+ if (!token.equals(" ")) {
+ tokens.add(token);
+ }
+ }
+ return tokens;
+ }
+
+ /**
+ * Creates <code>StepField</code> corresponding to field indicated by
+ * token, of one of the specified steps.
+ * <p>
+ * A <em>field</em> can be one of these forms:
+ * <ul>
+ * <li><em>stepalias</em><b>.</b><em>fieldname</em>
+ * <li>[<em>stepalias</em><b>.</b><em>fieldname</em>]
+ * <li><em>fieldname</em> (only when just one step is specified).
+ * <li>[<em>fieldname</em>] (only when just one step is specified).
+ * </ul>
+ *
+ * @param token The token.
+ * @param steps The steps.
+ * @return The field.
+ */
+
+ public static StepField getField(QueryContext queryContext, String token,
List<? extends Step> steps) {
+ return getField(queryContext, token, (List<BasicStep>) steps, null);
+ }
+ /**
+ * Creates <code>StepField</code> corresponding to field indicated by
+ * token, of one of the specified steps.
+ * <p>
+ * A <em>field</em> can be one of these forms:
+ * <ul>
+ * <li><em>stepalias</em><b>.</b><em>fieldname</em>
+ * <li>[<em>stepalias</em><b>.</b><em>fieldname</em>]
+ * <li><em>fieldname</em> (only when just one step is specified, or query
is an instance of NodeQuery).
+ * <li>[<em>fieldname</em>] (only when just one step is specified, or
query is an instance of NodeQuery).
+ * </ul>
+ *
+ * @param token The token.
+ * @param steps The steps.
+ * @param query The used query
+ * @return The field.
+ * @since MMBase-1.7.1
+ */
+
+ static StepField getField(QueryContext queryContext, String token,
List<BasicStep> steps, SearchQuery query) {
+ BasicStep step = null;
+ int bracketOffset = (token.startsWith("[") && token.endsWith("]")) ? 1
: 0;
+ int idx = token.indexOf('.');
+ if (idx == -1) {
+ if (steps.size() > 1) {
+ if (query != null && query instanceof NodeQuery) {
+ step = (BasicStep) ((NodeQuery) query).getNodeStep();
+ if (step == null) {
+ throw new IllegalArgumentException( "NodeQuery has no
step; Fieldname not prefixed with table alias: \"" + token + "\"");
+ }
+ } else {
+ throw new IllegalArgumentException( "Fieldname not
prefixed with table alias: \"" + token + "\"");
+ }
+ } else {
+ step = steps.get(0);
+ }
+ } else {
+ step = getStep(token.substring(bracketOffset, idx), steps);
+ }
+
+ String builder = step.getTableName();
+ String fieldName;
+ if (idx == -1) {
+ fieldName = token.substring(bracketOffset, token.length() -
bracketOffset);
+ } else {
+ fieldName = token.substring(idx + 1, token.length() -
bracketOffset);
+ }
+
+ Field coreField = queryContext.getField(builder, fieldName);
+ // maybe the field was already escaped with getAllowedField
+ // otherwise it will definitely fail!
+ if (coreField == null) {
+ String escapedFieldName =
queryContext.getStorageIdentifier(fieldName);
+ if (! escapedFieldName.equals(fieldName)) {
+ coreField = queryContext.getField(builder, fieldName);
+ if (coreField == null) {
+ throw new IllegalArgumentException("Unknown field (of
builder " + builder + "): \"" + escapedFieldName + "\"");
+ }
+ }
+ }
+ if (coreField == null) {
+ throw new IllegalArgumentException("Unknown field (of builder " +
builder + "): \"" + fieldName + "\"");
+ }
+ BasicStepField field = new BasicStepField(step, coreField);
+ return field;
+ }
+
+ /**
+ * Finds step by alias.
+ *
+ * @param alias The alias.
+ * @param steps The steps
+ * @return The step.
+ */
+ private static BasicStep getStep(String alias, List<BasicStep> steps) {
+ Iterator<BasicStep> iSteps = steps.iterator();
+ while (iSteps.hasNext()) {
+ BasicStep step = iSteps.next();
+ String alias2 = step.getAlias();
+ if (alias2 == null) {
+ alias2 = step.getTableName();
+ }
+ if (alias2.equals(alias)) {
+ return step;
+ }
+ }
+
+ // Not found.
+ throw new IllegalArgumentException("Unknown table alias: \"" + alias +
"\"");
+ }
+
+
+ /** Creates a new instance of ConstraintParser
+ * @param query
+ */
+ public ConstraintParser(QueryContext queryContext, SearchQuery query) {
+ this.query = query;
+ this.steps = query.getSteps();
+ this.queryContext = queryContext;
+ }
+
+ /**
+ * Parses <em>SQL-search-condition</em>, and produces a corresponding
+ * {...@link org.mmbase.storage.search.Constraint Constraint} object.
+ * <p>
+ * See {...@link ConstraintParser above} for the format of a
+ * <em>SQL-search-condition</em>.
+ *
+ * @param sqlConstraint The non-null SQL constraint string.
+ * @return The constraint.
+ */
+ public Constraint toConstraint(String sqlConstraint) {
+ Constraint result = null;
+ try {
+ ListIterator<String> iTokens =
tokenize(sqlConstraint).listIterator();
+ result = parseCondition(iTokens);
+
+ // If this doesn't work, fall back to legacy code.
+ } catch (Exception e) {
+ // Log to fallback logger.
+ if (fallbackLog.isServiceEnabled()) {
+ fallbackLog.service(
+ "Failed to parse Constraint from search condition string: "
+ + "\n sqlConstraint = " + sqlConstraint
+ + "\n exception: " + e.getMessage()
+ + "\nFalling back to BasicLegacyConstraint...", e);
+ }
+ String escapedSqlConstraint = convertClauseToDBS(queryContext,
sqlConstraint);
+ if (! validConstraints(escapedSqlConstraint)) {
+ throw new IllegalArgumentException("Invalid constraints: " +
sqlConstraint);
+ }
+ result = new BasicLegacyConstraint(escapedSqlConstraint);
+ }
+
+ if (log.isDebugEnabled()) {
+ log.debug("Parsed constraint \"" + sqlConstraint + "\" to :\n" +
result);
+ }
+ return result;
+ }
+
+
+ /**
+ * Parses a <em>field</em> string, and produces a corresponding
+ * <code>StepField</code> object.
+ * <p>
+ * See {...@link ConstraintParser above} for the format of a
+ * <em>field</em>
+ *
+ * @param token The token.
+ * @return The field.
+ */
+ // package visibility!
+ StepField getField(String token) {
+ return getField(queryContext, token, (List<BasicStep>) steps, query);
+ }
+
+ /**
+ * Parses <em>SQL-search-condition</em> string from list of tokens, and
+ * produces a corresponding <code>BasicConstraint</code> object.
+ * <p>
+ * See {...@link ConstraintParser above} for the format of a
+ * <em>SQL-search-condition</em>
+ *
+ * @param iTokens Tokens iterator, must be positioned before the (first)
+ * token representing the condition.
+ * @return The constraint.
+ */
+ // package visibility!
+ BasicConstraint parseCondition(ListIterator<String> iTokens) {
+ BasicCompositeConstraint composite = null;
+ BasicConstraint constraint= null;
+ while (iTokens.hasNext()) {
+ boolean inverse = false;
+ String token = iTokens.next();
+ if (token.equalsIgnoreCase("NOT")) {
+ // NOT.
+ inverse = true;
+ token = iTokens.next();
+ }
+
+ if (token.equals("(")) {
+ // Start of (simple or composite) constraint
+ // between parenthesis.
+ constraint = parseCondition(iTokens);
+ } else {
+ // Simple condition.
+ iTokens.previous();
+ constraint = parseSimpleCondition(iTokens);
+ }
+ if (inverse) {
+ constraint.setInverse(!constraint.isInverse());
+ }
+ if (composite != null) {
+ composite.addChild(constraint);
+ }
+
+ if (iTokens.hasNext()) {
+ token = iTokens.next();
+ if (token.equals(")")) {
+ // Start of (simple or composite) constraint
+ // between parenthesis.
+ break;
+ }
+ int logicalOperator = 0;
+ if (token.equalsIgnoreCase("OR")) {
+ logicalOperator = CompositeConstraint.LOGICAL_OR;
+ } else if (token.equalsIgnoreCase("AND")) {
+ logicalOperator = CompositeConstraint.LOGICAL_AND;
+ } else {
+ throw new IllegalArgumentException(
+ "Unexpected token (expected \"AND\" or \"OR\"): \""
+ + token + "\"");
+ }
+ if (composite == null) {
+ composite = new BasicCompositeConstraint(logicalOperator).
+ addChild(constraint);
+ }
+
+ if (composite.getLogicalOperator() != logicalOperator) {
+ composite = new BasicCompositeConstraint(logicalOperator).
+ addChild(composite);
+ }
+
+ if (!iTokens.hasNext()) {
+ throw new IllegalArgumentException("Unexpected end of
tokens after \"" + token + "\"");
+ }
+ }
+ }
+ if (composite != null) {
+ return composite;
+ } else {
+ return constraint;
+ }
+ }
+
+ /**
+ * Parses a <em>simple-SQL-search-condition</em> string from list of
tokens,
+ * and produces a corresponding <code>BasicConstraint</code> object.
+ * <p>
+ * See {...@link ConstraintParser above} for the format of a
+ * <em>simple-SQL-search-condition</em>
+ *
+ * @param iTokens Tokens iterator, must be positioned before the (first)
+ * token representing the condition.
+ * @return The constraint.
+ */
+ // package visibility!
+ BasicConstraint parseSimpleCondition(ListIterator<String> iTokens) {
+ BasicConstraint result = null;
+
+ String token = iTokens.next();
+ if (token.equalsIgnoreCase("StringSearch")) {
+ // StringSearch constraint.
+ return parseStringSearchCondition(iTokens);
+ }
+
+ String function = token.toUpperCase();
+ if (function.equals("LOWER") || function.equals("UPPER")) {
+ if (iTokens.next().equals("(")) {
+ // Function.
+ token = iTokens.next();
+ } else {
+ // Not a function.
+ iTokens.previous();
+ function = null;
+ }
+ } else {
+ function = null;
+ }
+
+ StepField field = getField(token);
+
+ token = iTokens.next();
+ if (function != null) {
+ if (!token.equals(")")) {
+ throw new IllegalArgumentException(
+ "Unexpected token (expected \")\"): \""
+ + token + "\"");
+ }
+ token = iTokens.next();
+ }
+
+ boolean inverse = false;
+ if (token.equalsIgnoreCase("NOT")) {
+ // NOT LIKE/NOT IN/NOT BETWEEN
+ inverse = true;
+ token = iTokens.next();
+ if (!token.equalsIgnoreCase("LIKE")
+ && !token.equalsIgnoreCase("IN")
+ && !token.equalsIgnoreCase("BETWEEN")) {
+ throw new IllegalArgumentException(
+ "Unexpected token (expected "
+ + "\"LIKE\" OR \"IN\" OR \"BETWEEN\"): \""
+ + token + "\"");
+ }
+ }
+
+ if (token.equalsIgnoreCase("LIKE")) {
+ // LIKE 'value'
+ String value = (String) parseValue(iTokens, field);
+ boolean caseSensitive = true;
+ if (function != null) {
+ if ((function.equals("LOWER")
+ && value.equals(value.toLowerCase()))
+ || (function.equals("UPPER")
+ && value.equals(value.toUpperCase()))) {
+ caseSensitive = false;
+ }
+ }
+ result = new BasicFieldValueConstraint(field, value)
+ .setOperator(FieldCompareConstraint.LIKE)
+ .setCaseSensitive(caseSensitive);
+
+ } else if (token.equalsIgnoreCase("IS")) {
+ // IS [NOT] NULL
+ token = iTokens.next();
+ if (token.equalsIgnoreCase("NOT")) {
+ inverse = !inverse;
+ token = iTokens.next();
+ }
+ if (token.equalsIgnoreCase("NULL")) {
+ result = new BasicFieldNullConstraint(field);
+ } else {
+ throw new IllegalArgumentException(
+ "Unexpected token (expected \"NULL\"): \""
+ + token + "\"");
+ }
+ } else if (token.equalsIgnoreCase("IN")) {
+ // IN (value1, value2, ...)
+ String separator = iTokens.next();
+ if (!separator.equals("(")) {
+ throw new IllegalArgumentException(
+ "Unexpected token (expected \"(\"): \""
+ + separator + "\"");
+ }
+ BasicFieldValueInConstraint fieldValueInConstraint
+ = new BasicFieldValueInConstraint(field);
+ if (!iTokens.next().equals(")")) {
+
+ iTokens.previous();
+ do {
+ Object value = parseValue(iTokens, field);
+ separator = iTokens.next();
+ if (separator.equals(",") || separator.equals(")")) {
+ fieldValueInConstraint.addValue(value);
+ } else {
+ throw new IllegalArgumentException(
+ "Unexpected token (expected \",\" or \")\"): \""
+ + separator + "\"");
+ }
+ } while (separator.equals(","));
+ }
+ result = fieldValueInConstraint;
+
+ } else if (token.equalsIgnoreCase("BETWEEN")) {
+ // BETWEEN value1 AND value2
+ Object value1 = parseValue(iTokens, field);
+ String separator = iTokens.next();
+ if (!separator.equals("AND")) {
+ throw new IllegalArgumentException(
+ "Unexpected token (expected \"AND\"): \""
+ + separator + "\"");
+ }
+ Object value2 = parseValue(iTokens, field);
+ boolean caseSensitive = true;
+ if (function != null
+ && value1 instanceof String && value2 instanceof String) {
+ String strValue1 = (String) value1;
+ String strValue2 = (String) value2;
+ if ((function.equals("LOWER")
+ && strValue1.equals(strValue1.toLowerCase())
+ && strValue2.equals(strValue2.toLowerCase()))
+ || (function.equals("UPPER")
+ && strValue1.equals(strValue1.toUpperCase())
+ && strValue2.equals(strValue2.toUpperCase()))) {
+ caseSensitive = false;
+ }
+ }
+
+ BasicFieldValueBetweenConstraint fieldValueBetweenConstraint
+ = (BasicFieldValueBetweenConstraint)
+ new BasicFieldValueBetweenConstraint(field, value1, value2)
+ .setCaseSensitive(caseSensitive);
+ result = fieldValueBetweenConstraint;
+
+ } else if (token.equals("=")) {
+ token = iTokens.next();
+ if (token.equals("=")) {
+ try {
+ // == value
+ Object value = parseValue(iTokens, field);
+ result = new BasicFieldValueConstraint(field, value)
+ .setOperator(FieldCompareConstraint.EQUAL);
+ } catch (NumberFormatException e) {
+ // == field2
+ iTokens.previous();
+ token = iTokens.next();
+ StepField field2 = getField(token);
+ result = new BasicCompareFieldsConstraint(field, field2)
+ .setOperator(FieldCompareConstraint.EQUAL);
+ }
+ } else {
+ iTokens.previous();
+ try {
+ // = value
+ Object value = parseValue(iTokens, field);
+ boolean caseSensitive = true;
+ if (function != null && value instanceof String) {
+ String strValue = (String) value;
+ if ((function.equals("LOWER")
+ && strValue.equals(strValue.toLowerCase()))
+ || (function.equals("UPPER")
+ && strValue.equals(strValue.toUpperCase()))) {
+ caseSensitive = false;
+ }
+ }
+ result = new BasicFieldValueConstraint(field, value)
+ .setOperator(FieldCompareConstraint.EQUAL)
+ .setCaseSensitive(caseSensitive);
+ } catch (NumberFormatException e) {
+ // = field2
+ iTokens.previous();
+ token = iTokens.next();
+ StepField field2 = getField(token);
+ result = new BasicCompareFieldsConstraint(field, field2)
+ .setOperator(FieldCompareConstraint.EQUAL);
+ }
+ }
+ } else if (token.equals("<")) {
+ token = iTokens.next();
+ if (token.equals("=")) {
+ try {
+ // <= value
+ Object value = parseValue(iTokens, field);
+ result = new BasicFieldValueConstraint(field, value)
+ .setOperator(FieldCompareConstraint.LESS_EQUAL);
+ } catch (NumberFormatException e) {
+ // <= field2
+ iTokens.previous();
+ token = iTokens.next();
+ StepField field2 = getField(token);
+ result = new BasicCompareFieldsConstraint(field, field2)
+ .setOperator(FieldCompareConstraint.LESS_EQUAL);
+ }
+ } else if (token.equals(">")) {
+ try {
+ // <> value
+ Object value = parseValue(iTokens, field);
+ result = new BasicFieldValueConstraint(field, value)
+ .setOperator(FieldCompareConstraint.NOT_EQUAL);
+ } catch (NumberFormatException e) {
+ // <> field2
+ iTokens.previous();
+ token = iTokens.next();
+ StepField field2 = getField(token);
+ result = new BasicCompareFieldsConstraint(field, field2)
+ .setOperator(FieldCompareConstraint.NOT_EQUAL);
+ }
+ } else {
+ try {
+ // < value
+ iTokens.previous();
+ Object value = parseValue(iTokens, field);
+ result = new BasicFieldValueConstraint(field, value)
+ .setOperator(FieldCompareConstraint.LESS);
+ } catch (NumberFormatException e) {
+ // < field2
+ iTokens.previous();
+ token = iTokens.next();
+ StepField field2 = getField(token);
+ result = new BasicCompareFieldsConstraint(field, field2)
+ .setOperator(FieldCompareConstraint.LESS);
+ }
+ }
+ } else if (token.equals(">")) {
+ token = iTokens.next();
+ if (token.equals("=")) {
+ try {
+ // >= value
+ Object value = parseValue(iTokens, field);
+ result = new BasicFieldValueConstraint(field, value)
+ .setOperator(FieldCompareConstraint.GREATER_EQUAL);
+ } catch (NumberFormatException e) {
+ // >= field2
+ iTokens.previous();
+ token = iTokens.next();
+ StepField field2 = getField(token);
+ result = new BasicCompareFieldsConstraint(field, field2)
+ .setOperator(FieldCompareConstraint.GREATER_EQUAL);
+ }
+ } else {
+ try {
+ // > value
+ iTokens.previous();
+ Object value = parseValue(iTokens, field);
+ result = new BasicFieldValueConstraint(field, value)
+ .setOperator(FieldCompareConstraint.GREATER);
+ } catch (NumberFormatException e) {
+ // > field2
+ iTokens.previous();
+ token = iTokens.next();
+ StepField field2 = getField(token);
+ result = new BasicCompareFieldsConstraint(field, field2)
+ .setOperator(FieldCompareConstraint.GREATER);
+ }
+ }
+ } else if (token.equals("!")) {
+ token = iTokens.next();
+ if (token.equals("=")) {
+ try {
+ // != value
+ Object value = parseValue(iTokens, field);
+ result = new BasicFieldValueConstraint(field, value)
+ .setOperator(FieldCompareConstraint.NOT_EQUAL);
+ } catch (NumberFormatException e) {
+ // != field2
+ iTokens.previous();
+ token = iTokens.next();
+ StepField field2 = getField(token);
+ result = new BasicCompareFieldsConstraint(field, field2)
+ .setOperator(FieldCompareConstraint.NOT_EQUAL);
+ }
+ } else {
+ throw new IllegalArgumentException("Unexpected token (expected
\"=\"): \"" + token + "\"");
+ }
+ } else {
+ throw new IllegalArgumentException("Unexpected token: \"" + token
+ "\"");
+ }
+
+ if (inverse) {
+ result.setInverse(!result.isInverse());
+ }
+
+ return result;
+ }
+
+ /**
+ * Parses a <em>stringsearch-condition</em> string from list of tokens,
+ * and produces a corresponding <code>BasicStringSearchConstraint</code>
object.
+ * <p>
+ * See {...@link ConstraintParser above} for the format of a
+ * <em>stringsearch-condition</em>
+ *
+ * @param iTokens Tokens iterator, must be positioned after the (first)
+ * token representing the condition (e.g. after "StringSearch").
+ * @return The constraint.
+ */
+ private BasicStringSearchConstraint parseStringSearchCondition(
+ ListIterator<String> iTokens) {
+
+ String token = iTokens.next();
+ if (!token.equals("(")) {
+ throw new IllegalArgumentException("Unexpected token (expected
\"(\"): \"" + token + "\"");
+ }
+
+ // Field
+ token = iTokens.next();
+ StepField field = getField(token);
+
+ token = iTokens.next();
+ if (!token.equals(",")) {
+ throw new IllegalArgumentException("Unexpected token (expected
\",\"): \"" + token + "\"");
+ }
+
+ // Searchtype
+ int searchType;
+ token = iTokens.next();
+ if (token.equalsIgnoreCase("PHRASE")) {
+ searchType = StringSearchConstraint.SEARCH_TYPE_PHRASE_ORIENTED;
+ } else if (token.equalsIgnoreCase("PROXIMITY")) {
+ searchType = StringSearchConstraint.SEARCH_TYPE_PROXIMITY_ORIENTED;
+ } else if (token.equalsIgnoreCase("WORD")) {
+ searchType = StringSearchConstraint.SEARCH_TYPE_WORD_ORIENTED;
+ } else {
+ throw new IllegalArgumentException("Invalid searchtype (expected
\"PHRASE\", \"PROXIMITY\" or \"WORD\": \"" + token + "\"");
+ }
+
+ token = iTokens.next();
+ if (!token.equals(",")) {
+ throw new IllegalArgumentException(
+ "Unexpected token (expected \",\"): \""
+ + token + "\"");
+ }
+
+ // Matchtype
+ int matchType;
+ token = iTokens.next();
+ if (token.equalsIgnoreCase("FUZZY")) {
+ matchType = StringSearchConstraint.MATCH_TYPE_FUZZY;
+ } else if (token.equalsIgnoreCase("LITERAL")) {
+ matchType = StringSearchConstraint.MATCH_TYPE_LITERAL;
+ } else if (token.equalsIgnoreCase("SYNONYM")) {
+ matchType = StringSearchConstraint.MATCH_TYPE_SYNONYM;
+ } else {
+ throw new IllegalArgumentException("Invalid matchtype (expected
\"FUZZY\", \"LITERAL\" or \"SYNONYM\": \"" + token + "\"");
+ }
+
+ token = iTokens.next();
+ if (!token.equals(",")) {
+ throw new IllegalArgumentException("Unexpected token (expected
\",\"): \"" + token + "\"");
+ }
+
+ // SearchTerms
+ String searchTerms;
+ token = iTokens.next();
+ if (!token.equals("'")) {
+ throw new IllegalArgumentException("Unexpected token (expected
\"'\" or \"\"\"): \"" + token + "\"");
+ }
+ searchTerms = iTokens.next();
+ token = iTokens.next();
+ if (!token.equals("'")) {
+ throw new IllegalArgumentException("Unexpected token (expected
\"'\" or \"\"\"): \"" + token + "\"");
+ }
+
+ token = iTokens.next();
+ if (!token.equals(",")) {
+ throw new IllegalArgumentException("Unexpected token (expected
\",\"): \"" + token + "\"");
+ }
+
+ // CaseSensitive property
+ boolean caseSensitive;
+ token = iTokens.next();
+ if (token.equalsIgnoreCase("true")) {
+ caseSensitive = true;
+ } else if (token.equalsIgnoreCase("false")) {
+ caseSensitive = false;
+ } else {
+ throw new IllegalArgumentException("Invalid caseSensitive value
(expected \"true\" " + "or \"false\": \"" + token + "\"");
+ }
+
+ token = iTokens.next();
+ if (!token.equals(")")) {
+ throw new IllegalArgumentException("Unexpected token (expected
\")\"): \"" + token + "\"");
+ }
+
+ BasicStringSearchConstraint result = (BasicStringSearchConstraint)
+ new BasicStringSearchConstraint(
+ field, searchType, matchType, searchTerms)
+ .setCaseSensitive(caseSensitive);
+
+ // .set(parametername, value)
+ while (iTokens.hasNext()) {
+ token = iTokens.next();
+ if (!token.equalsIgnoreCase(".set")) {
+ iTokens.previous();
+ break;
+ }
+
+ token = iTokens.next();
+ if (!token.equals("(")) {
+ throw new IllegalArgumentException("Unexpected token (expected
\"(\"): \"" + token + "\"");
+ }
+
+ String parameterName = iTokens.next();
+
+ token = iTokens.next();
+ if (!token.equals(",")) {
+ throw new IllegalArgumentException("Unexpected token (expected
\",\"): \"" + token + "\"");
+ }
+
+ String parameterValue = iTokens.next();
+
+ token = iTokens.next();
+ if (!token.equals(")")) {
+ throw new IllegalArgumentException("Unexpected token (expected
\")\"): \"" + token + "\"");
+ }
+
+ if (parameterName.equalsIgnoreCase("FUZZINESS")) {
+ result.setParameter(StringSearchConstraint.PARAM_FUZZINESS,
+ Float.valueOf(parameterValue));
+ } else if (parameterName.equalsIgnoreCase("PROXIMITY_LIMIT")) {
+ result.setParameter(
+ StringSearchConstraint.PARAM_PROXIMITY_LIMIT,
+ parameterValue);
+ } else {
+ throw new IllegalArgumentException("Invalid parameter name
(expected \"FUZZINESS\" or \"PROXIMITY\": \"" + parameterName + "\"");
+ }
+ }
+
+ return result;
+ }
+
+
+
+ protected static final QueryContext BRIDGE = new
QueryContext.Bridge(org.mmbase.bridge.ContextProvider.getDefaultCloudContext().getCloud("mmbase"));
+
+}
_______________________________________________
Cvs mailing list
[email protected]
http://lists.mmbase.org/mailman/listinfo/cvs