/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.chemistry.opencmis.inmemory.query;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.Tree;
import org.apache.chemistry.opencmis.commons.data.ObjectData;
import org.apache.chemistry.opencmis.commons.data.ObjectList;
import org.apache.chemistry.opencmis.commons.data.PropertyData;
import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
import org.apache.chemistry.opencmis.commons.enums.Cardinality;
import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
import org.apache.chemistry.opencmis.commons.enums.PropertyType;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl;
import org.apache.chemistry.opencmis.inmemory.storedobj.api.DocumentVersion;
import org.apache.chemistry.opencmis.inmemory.storedobj.api.Filing;
import org.apache.chemistry.opencmis.inmemory.storedobj.api.Folder;
import org.apache.chemistry.opencmis.inmemory.storedobj.api.ObjectStore;
import org.apache.chemistry.opencmis.inmemory.storedobj.api.StoredObject;
import org.apache.chemistry.opencmis.inmemory.storedobj.api.VersionedDocument;
import org.apache.chemistry.opencmis.inmemory.storedobj.impl.ObjectStoreImpl;
import org.apache.chemistry.opencmis.inmemory.types.PropertyCreationHelper;
import org.apache.chemistry.opencmis.server.support.TypeManager;
import org.apache.chemistry.opencmis.server.support.query.AbstractQueryConditionProcessor;
import org.apache.chemistry.opencmis.server.support.query.CmisQlStrictLexer;
import org.apache.chemistry.opencmis.server.support.query.CmisQueryWalker;
import org.apache.chemistry.opencmis.server.support.query.CmisSelector;
import org.apache.chemistry.opencmis.server.support.query.ColumnReference;
import org.apache.chemistry.opencmis.server.support.query.FunctionReference;
import org.apache.chemistry.opencmis.server.support.query.QueryObject;
import org.apache.chemistry.opencmis.server.support.query.QueryObject.SortSpec;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* A processor for a CMIS query for the In-Memory server. During tree traversal conditions
* are checked against the data contained in the central hash map with all objects. In a first
* pass one time setup is performed, in a custom walk across the query expression tree an object
* is checked if it matches. In case of a match it is appended to a list of matching objects.
*
* @author Jens
*
*/
public class InMemoryQueryProcessor extends AbstractQueryConditionProcessor {
private static Log LOG = LogFactory.getLog(InMemoryQueryProcessor.class);
private List<StoredObject> matches = new ArrayList<StoredObject>();
private QueryObject queryObj;
private Tree whereTree;
public InMemoryQueryProcessor() {
}
/**
* Main entry function to process a query from discovery service
*/
public ObjectList query(TypeManager tm, ObjectStore objectStore, String user, String repositoryId, String statement, Boolean searchAllVersions,
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
BigInteger maxItems, BigInteger skipCount) {
queryObj = new QueryObject(tm, this);
processQueryAndCatchExc(statement); // calls query processor
// iterate over all the objects and check for each if the query matches
for (String objectId : ((ObjectStoreImpl) objectStore).getIds()) {
StoredObject so = objectStore.getObjectById(objectId);
match(so, searchAllVersions==null ? true : searchAllVersions.booleanValue());
}
ObjectList objList = buildResultList(tm, user, includeAllowableActions, includeRelationships, renditionFilter,
maxItems, skipCount);
LOG.debug("Query result, number of matching objects: " + objList.getNumItems());
return objList;
}
public CmisQueryWalker processQuery(String statement) throws UnsupportedEncodingException, IOException, RecognitionException {
CmisQueryWalker walker = AbstractQueryConditionProcessor.getWalker(statement);
walker.query(queryObj);
String errMsg = walker.getErrorMessageString();
if (null != errMsg) {
throw new RuntimeException("Walking of statement failed with error: \n " + errMsg +
"\n Statement was: " + statement);
}
/*CommonTree walkerTree = (CommonTree) */ walker.getTreeNodeStream().getTreeSource();
return walker;
}
public CmisQueryWalker processQueryAndCatchExc(String statement) {
try {
return processQuery(statement);
} catch (RecognitionException e) {
throw new RuntimeException("Walking of statement failed with RecognitionException error: \n " + e);
} catch (Exception e) {
throw new RuntimeException("Walking of statement failed with other exception: \n " + e);
}
}
public void onStartProcessing(Tree node) {
// log.debug("onStartProcessing()");
// checkRoot(node);
whereTree = node;
}
public void onStopProcessing() {
// log.debug("onStopProcessing()");
}
public ObjectList buildResultList(TypeManager tm, String user,
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
BigInteger maxItems, BigInteger skipCount) {
sortMatches();
ObjectListImpl res = new ObjectListImpl();
res.setNumItems(BigInteger.valueOf(matches.size()));
int start = 0;
if (skipCount != null)
start = (int)skipCount.longValue();
if (start < 0)
start = 0;
if (start > matches.size())
start = matches.size();
int stop = 0;
if (maxItems != null)
stop = start + (int)maxItems.longValue();
if (stop <= 0 || stop > matches.size())
stop = matches.size();
res.setHasMoreItems(stop < matches.size());
if (start > 0 || stop > 0)
matches = matches.subList(start, stop);
List<ObjectData> objDataList = new ArrayList<ObjectData>();
Map<String, String> props = queryObj.getRequestedProperties();
Map<String, String> funcs = queryObj.getRequestedFuncs();
for (StoredObject so : matches) {
TypeDefinition td = tm.getTypeById(so.getTypeId()).getTypeDefinition();
ObjectData od = PropertyCreationHelper.getObjectDataQueryResult(td, so, user,
props, funcs, includeAllowableActions, includeRelationships, renditionFilter);
objDataList.add(od);
}
res.setObjects(objDataList);
return res;
}
private boolean typeMatches(TypeDefinition td, StoredObject so) {
String typeId = so.getTypeId();
while (typeId != null) {
if (typeId.equals(td.getId())) {
return true;
}
// check parent type
TypeDefinition parentTD = queryObj.getParentType(typeId);
typeId = parentTD == null ? null : parentTD.getId();
}
return false;
}
private void sortMatches() {
final List<SortSpec> orderBy = queryObj.getOrderBys();
if (orderBy.size() > 1)
LOG.warn("ORDER BY has more than one sort criterium, all but the first are ignored.");
class ResultComparator implements Comparator<StoredObject> {
@SuppressWarnings("unchecked")
public int compare(StoredObject so1, StoredObject so2) {
SortSpec s = orderBy.get(0);
CmisSelector sel = s.getSelector();
int result;
if (sel instanceof ColumnReference) {
String propId = ((ColumnReference)sel).getPropertyId();
Object propVal1 = so1.getProperties().get(propId).getFirstValue();
Object propVal2 = so2.getProperties().get(propId).getFirstValue();
if (propVal1 == null && propVal2 == null)
result = 0;
else if (propVal1 == null)
result = -1;
else if (propVal2 == null)
result = 1;
else
result = ((Comparable)propVal1).compareTo(propVal2);
} else {
String funcName = ((FunctionReference)sel).getName();
// evaluate function here, currently ignore
result = 0;
}
if (!s.isAscending())
result = -result;
return result;
}
}
if (orderBy.size() > 0)
Collections.sort(matches, new ResultComparator());
}
/**
* Check for each object contained in the in-memory repository if it matches the current query
* expression. If yes add it to the list of matched objects.
*
* @param so
* object stored in the in-memory repository
*/
private void match(StoredObject so, boolean searchAllVersions) {
// log.debug("checkMatch() for object: " + so.getId());
// first check if type is matching...
String queryName = queryObj.getTypes().values().iterator().next(); // as we don't support JOINS take first type
TypeDefinition td = queryObj.getTypeDefinitionFromQueryName(queryName);
boolean skip = so instanceof VersionedDocument; // we are only interested in versions not in the series
boolean typeMatches = typeMatches(td, so);
if (!searchAllVersions && so instanceof DocumentVersion && ((DocumentVersion)so).getParentDocument().getLatestVersion(false) != so)
skip = true;
// ... then check expression...
if (typeMatches && !skip)
evalWhereTree(whereTree, so);
}
private void evalWhereTree(Tree node, StoredObject so) {
boolean match=true;
if (null != node)
match = evalWhereNode(so, node.getChild(0));
if (match)
matches.add(so); // add to list
}
// Standard expression evaluator for single pass walking. This is used as first
// pass walk in this object for one-time setup tasks (e.g. setup maps)
public void onEquals(Tree eqNode, Tree leftNode, Tree rightNode) {
}
public void onNotEquals(Tree neNode, Tree leftNode, Tree rightNode) {
}
public void onGreaterThan(Tree gtNode, Tree leftNode, Tree rightNode) {
}
public void onGreaterOrEquals(Tree geNode, Tree leftNode, Tree rightNode) {
}
public void onLessThan(Tree ltNode, Tree leftNode, Tree rightNode) {
}
public void onLessOrEquals(Tree leqNode, Tree leftNode, Tree rightNode) {
}
// Boolean operators
public void onNot(Tree opNode, Tree leftNode) {
}
public void onAnd(Tree opNode, Tree leftNode, Tree rightNode) {
}
public void onOr(Tree opNode, Tree leftNode, Tree rightNode) {
}
// Multi-value:
public void onIn(Tree node, Tree colNode, Tree listNode) {
}
public void onNotIn(Tree nod, Tree colNode, Tree listNode) {
}
public void onNotInList(Tree node) {
}
public void onInAny(Tree node, Tree colNode, Tree listNode) {
}
public void onNotInAny(Tree node, Tree colNode, Tree listNode) {
}
public void onEqAny(Tree node, Tree literalNode, Tree colNode) {
}
// Null comparisons:
public void onIsNull(Tree nullNode, Tree colNode) {
}
public void onIsNotNull(Tree notNullNode, Tree colNode) {
}
// String matching:
public void onIsLike(Tree node, Tree colNode, Tree stringNode) {
}
public void onIsNotLike(Tree node, Tree colNode, Tree stringNode) {
}
// Functions:
public void onContains(Tree node, Tree colNode, Tree paramNode) {
}
public void onInFolder(Tree node, Tree colNode, Tree paramNode) {
}
public void onInTree(Tree node, Tree colNode, Tree paramNode) {
}
public void onScore(Tree node, Tree paramNode) {
}
/**
* For each object check if it matches and append it to match-list if it does.
* We do here our own walking mechanism so that we can pass additional parameters
* and define the return types.
*
* @param node
* node in where clause
* @return
* true if it matches, false if it not matches
*/
boolean evalWhereNode(StoredObject so, Tree node) {
boolean matches = true;
switch (node.getType()) {
case CmisQlStrictLexer.WHERE:
matches = evalWhereNode(so, node.getChild(0));
break; // ignore
case CmisQlStrictLexer.EQ:
matches = evalWhereEquals(so, node, node.getChild(0), node.getChild(1));
break;
case CmisQlStrictLexer.NEQ:
matches = evalWhereNotEquals(so, node, node.getChild(0), node.getChild(1));
break;
case CmisQlStrictLexer.GT:
matches = evalWhereGreaterThan(so, node, node.getChild(0), node.getChild(1));
break;
case CmisQlStrictLexer.GTEQ:
matches = evalWhereGreaterOrEquals(so, node, node.getChild(0), node.getChild(1));
break;
case CmisQlStrictLexer.LT:
matches = evalWhereLessThan(so, node, node.getChild(0), node.getChild(1));
break;
case CmisQlStrictLexer.LTEQ:
matches = evalWhereLessOrEquals(so, node, node.getChild(0), node.getChild(1));
break;
case CmisQlStrictLexer.NOT:
matches = evalWhereNot(so, node, node.getChild(0));
break;
case CmisQlStrictLexer.AND:
matches = evalWhereAnd(so, node, node.getChild(0), node.getChild(1));
break;
case CmisQlStrictLexer.OR:
matches = evalWhereOr(so, node, node.getChild(0), node.getChild(1));
break;
// Multi-value:
case CmisQlStrictLexer.IN:
matches = evalWhereIn(so, node, node.getChild(0), node.getChild(1));
break;
case CmisQlStrictLexer.NOT_IN:
matches = evalWhereNotIn(so, node, node.getChild(0), node.getChild(1));
break;
case CmisQlStrictLexer.IN_ANY:
matches = evalWhereInAny(so, node, node.getChild(0), node.getChild(1));
break;
case CmisQlStrictLexer.NOT_IN_ANY:
matches = evalWhereNotInAny(so, node, node.getChild(0), node.getChild(1));
break;
case CmisQlStrictLexer.EQ_ANY:
matches = evalWhereEqAny(so, node, node.getChild(0), node.getChild(1));
break;
// Null comparisons:
case CmisQlStrictLexer.IS_NULL:
matches = evalWhereIsNull(so, node, node.getChild(0));
break;
case CmisQlStrictLexer.IS_NOT_NULL:
matches = evalWhereIsNotNull(so, node, node.getChild(0));
break;
// String matching
case CmisQlStrictLexer.LIKE:
matches = evalWhereIsLike(so, node, node.getChild(0), node.getChild(1));
break;
case CmisQlStrictLexer.NOT_LIKE:
matches = evalWhereIsNotLike(so, node, node.getChild(0), node.getChild(1));
break;
// Functions
case CmisQlStrictLexer.CONTAINS:
if (node.getChildCount() == 1)
matches = evalWhereContains(so, node, null, node.getChild(0));
else
matches = evalWhereContains(so, node, node.getChild(0), node.getChild(1));
break;
case CmisQlStrictLexer.IN_FOLDER:
if (node.getChildCount() == 1)
matches = evalWhereInFolder(so, node, null, node.getChild(0));
else
matches = evalWhereInFolder(so, node, node.getChild(0), node.getChild(1));
break;
case CmisQlStrictLexer.IN_TREE:
if (node.getChildCount() == 1)
matches = evalWhereInTree(so, node, null, node.getChild(0));
else
matches = evalWhereInTree(so, node, node.getChild(0), node.getChild(1));
break;
default:
// do nothing;
}
return matches;
}
private boolean evalWhereEquals(StoredObject so, Tree node, Tree leftChild, Tree rightChild) {
Integer cmp = compareTo(so, leftChild, rightChild);
if (null == cmp)
return false; // property is not set
else
return cmp == 0;
}
private boolean evalWhereNotEquals(StoredObject so, Tree node, Tree leftChild, Tree rightChild) {
Integer cmp = compareTo(so, leftChild, rightChild);
if (null == cmp)
return false; // property is not set
else
return cmp != 0;
}
private boolean evalWhereGreaterThan(StoredObject so, Tree node, Tree leftChild, Tree rightChild) {
Integer cmp = compareTo(so, leftChild, rightChild);
if (null == cmp)
return false; // property is not set
else
return cmp > 0;
}
private boolean evalWhereGreaterOrEquals(StoredObject so, Tree node, Tree leftChild, Tree rightChild) {
Integer cmp = compareTo(so, leftChild, rightChild);
if (null == cmp)
return false; // property is not set
else
return cmp >= 0;
}
private boolean evalWhereLessThan(StoredObject so, Tree node, Tree leftChild, Tree rightChild) {
Integer cmp = compareTo(so, leftChild, rightChild);
if (null == cmp)
return false; // property is not set
else
return cmp < 0;
}
private boolean evalWhereLessOrEquals(StoredObject so, Tree node, Tree leftChild, Tree rightChild) {
Integer cmp = compareTo(so, leftChild, rightChild);
if (null == cmp)
return false; // property is not set
else
return cmp <= 0;
}
private boolean evalWhereNot(StoredObject so, Tree node, Tree child) {
boolean matches = evalWhereNode(so, child);
return !matches;
}
private boolean evalWhereAnd(StoredObject so, Tree node, Tree leftChild, Tree rightChild) {
boolean matches1 = evalWhereNode(so, leftChild);
boolean matches2 = evalWhereNode(so, rightChild);
return matches1 && matches2;
}
private boolean evalWhereOr(StoredObject so, Tree node, Tree leftChild, Tree rightChild) {
boolean matches1 = evalWhereNode(so, leftChild);
boolean matches2 = evalWhereNode(so, rightChild);
return matches1 || matches2;
}
private boolean evalWhereIn(StoredObject so, Tree node, Tree colNode, Tree listNode) {
ColumnReference colRef = getColumnReference(colNode);
TypeDefinition td = colRef.getTypeDefinition();
PropertyDefinition<?> pd = td.getPropertyDefinitions().get(colRef.getPropertyId());
PropertyData<?> lVal = so.getProperties().get(colRef.getPropertyId());
List<Object> literals = onLiteralList(listNode);
if (pd.getCardinality() != Cardinality.SINGLE)
throw new RuntimeException("Operator IN only is allowed on single-value properties ");
else if (lVal == null)
return false;
else {
Object prop= lVal.getFirstValue();
if (literals.contains(prop))
return true;
else
return false;
}
}
private boolean evalWhereNotIn(StoredObject so, Tree node, Tree colNode, Tree listNode) {
// Note just return !evalWhereIn(so, node, colNode, listNode) is wrong, because
// then it evaluates to true for null values (not set properties).
ColumnReference colRef = getColumnReference(colNode);
TypeDefinition td = colRef.getTypeDefinition();
PropertyDefinition<?> pd = td.getPropertyDefinitions().get(colRef.getPropertyId());
PropertyData<?> lVal = so.getProperties().get(colRef.getPropertyId());
List<Object> literals = onLiteralList(listNode);
if (pd.getCardinality() != Cardinality.SINGLE)
throw new RuntimeException("Operator IN only is allowed on single-value properties ");
else if (lVal == null)
return false;
else {
Object prop= lVal.getFirstValue();
if (literals.contains(prop))
return false;
else
return true;
}
}
private boolean evalWhereInAny(StoredObject so, Tree node, Tree colNode, Tree listNode) {
ColumnReference colRef = getColumnReference(colNode);
TypeDefinition td = colRef.getTypeDefinition();
PropertyDefinition<?> pd = td.getPropertyDefinitions().get(colRef.getPropertyId());
PropertyData<?> lVal = so.getProperties().get(colRef.getPropertyId());
List<Object> literals = onLiteralList(listNode);
if (pd.getCardinality() != Cardinality.MULTI)
throw new RuntimeException("Operator ANY...IN only is allowed on multi-value properties ");
else if (lVal == null)
return false;
else {
List<?> props= lVal.getValues();
for (Object prop : props) {
LOG.debug("comparing with: " + prop);
if (literals.contains(prop))
return true;
}
return false;
}
}
private boolean evalWhereNotInAny(StoredObject so, Tree node, Tree colNode, Tree listNode) {
// Note just return !evalWhereInAny(so, node, colNode, listNode) is wrong, because
// then it evaluates to true for null values (not set properties).
ColumnReference colRef = getColumnReference(colNode);
TypeDefinition td = colRef.getTypeDefinition();
PropertyDefinition<?> pd = td.getPropertyDefinitions().get(colRef.getPropertyId());
PropertyData<?> lVal = so.getProperties().get(colRef.getPropertyId());
List<Object> literals = onLiteralList(listNode);
if (pd.getCardinality() != Cardinality.MULTI)
throw new RuntimeException("Operator ANY...IN only is allowed on multi-value properties ");
else if (lVal == null)
return false;
else {
List<?> props= lVal.getValues();
for (Object prop : props) {
LOG.debug("comparing with: " + prop);
if (literals.contains(prop))
return false;
}
return true;
}
}
private boolean evalWhereEqAny(StoredObject so, Tree node, Tree literalNode, Tree colNode) {
ColumnReference colRef = getColumnReference(colNode);
TypeDefinition td = colRef.getTypeDefinition();
PropertyDefinition<?> pd = td.getPropertyDefinitions().get(colRef.getPropertyId());
PropertyData<?> lVal = so.getProperties().get(colRef.getPropertyId());
Object literal = onLiteral(literalNode);
if (pd.getCardinality() != Cardinality.MULTI)
throw new RuntimeException("Operator = ANY only is allowed on multi-value properties ");
else if (lVal == null)
return false;
else {
List<?> props= lVal.getValues();
if (props.contains(literal))
return true;
else
return false;
}
}
private boolean evalWhereIsNull(StoredObject so, Tree node, Tree child) {
Object propVal = getPropertyValue(child, so);
return null == propVal;
}
private boolean evalWhereIsNotNull(StoredObject so, Tree node, Tree child) {
Object propVal = getPropertyValue(child, so);
return null != propVal;
}
private boolean evalWhereIsLike(StoredObject so, Tree node, Tree colNode, Tree StringNode) {
Object rVal = onLiteral(StringNode);
if (!(rVal instanceof String))
throw new RuntimeException("LIKE operator requires String literal on right hand side.");
ColumnReference colRef = getColumnReference(colNode);
TypeDefinition td = colRef.getTypeDefinition();
PropertyDefinition<?> pd = td.getPropertyDefinitions().get(colRef.getPropertyId());
PropertyType propType = pd.getPropertyType();
if (propType != PropertyType.STRING && propType != PropertyType.HTML && propType != PropertyType.ID &&
propType != PropertyType.URI)
throw new RuntimeException("Property type "+ propType.value() + " is not allowed FOR LIKE");
if (pd.getCardinality() != Cardinality.SINGLE)
throw new RuntimeException("LIKE is not allowed for multi-value properties ");
String propVal = (String) so.getProperties().get(colRef.getPropertyId()).getFirstValue();
String pattern = translatePattern((String) rVal); // SQL to Java regex syntax
Pattern p = Pattern.compile(pattern);
return p.matcher(propVal).matches();
}
private boolean evalWhereIsNotLike(StoredObject so, Tree node, Tree colNode, Tree stringNode) {
return ! evalWhereIsLike(so, node, colNode, stringNode);
}
private boolean evalWhereContains(StoredObject so, Tree node, Tree colNode, Tree paramNode) {
throw new RuntimeException("Operator CONTAINS not supported in InMemory server.");
}
private boolean evalWhereInFolder(StoredObject so, Tree node, Tree colNode, Tree paramNode) {
if (null != colNode) {
getTableReference(colNode);
// just for error checking we do not evaluate this, there is only one from without join support
}
Object lit = onLiteral(paramNode);
if (!(lit instanceof String))
throw new RuntimeException("Folder id in IN_FOLDER must be of type String");
String folderId = (String) lit;
// check if object is in folder
if (so instanceof Filing)
return hasParent((Filing)so, folderId);
else
return false;
}
private boolean evalWhereInTree(StoredObject so, Tree node, Tree colNode, Tree paramNode) {
if (null != colNode) {
getTableReference(colNode);
// just for error checking we do not evaluate this, there is only one from without join support
}
Object lit = onLiteral(paramNode);
if (!(lit instanceof String))
throw new RuntimeException("Folder id in IN_FOLDER must be of type String");
String folderId = (String) lit;
// check if object is in folder
if (so instanceof Filing)
return hasAncestor((Filing) so, folderId);
else
return false;
}
private boolean hasParent(Filing objInFolder, String folderId) {
List<Folder> parents = objInFolder.getParents();
for (Folder folder : parents)
if (folderId.equals(folder.getId()))
return true;
return false;
}
private boolean hasAncestor(Filing objInFolder, String folderId) {
List<Folder> parents = objInFolder.getParents();
for (Folder folder : parents)
if (folderId.equals(folder.getId()))
return true;
for (Folder folder : parents)
if (hasAncestor(folder, folderId))
return true;
return false;
}
private Integer compareTo(StoredObject so, Tree leftChild, Tree rightChild) {
Object rVal = onLiteral(rightChild);
//log.debug("retrieve node from where: " + System.identityHashCode(leftChild) + " is " + leftChild);
ColumnReference colRef = getColumnReference(leftChild);
TypeDefinition td = colRef.getTypeDefinition();
PropertyDefinition<?> pd = td.getPropertyDefinitions().get(colRef.getPropertyId());
PropertyData<?> lVal = so.getProperties().get(colRef.getPropertyId());
if (lVal instanceof List<?>)
throw new RuntimeException("You can't query operators <, <=, ==, !=, >=, > on multi-value properties ");
else
return compareTo(pd, lVal, rVal);
}
private int compareTo(PropertyDefinition<?> td, PropertyData<?> lVal, Object rVal) {
Object lValue = lVal.getFirstValue();
switch (td.getPropertyType()) {
case BOOLEAN:
if (rVal instanceof Boolean)
return ((Boolean)lValue).compareTo((Boolean)rVal);
else
throwIncompatibleTypesException(lValue, rVal);
break;
case INTEGER:
{
Long lLongValue = ((BigInteger)lVal.getFirstValue()).longValue();
if (rVal instanceof Long)
return (lLongValue).compareTo((Long)rVal);
else if (rVal instanceof Double)
return Double.valueOf(((Integer)lValue).doubleValue()).compareTo((Double)rVal);
else
throwIncompatibleTypesException(lValue, rVal);
break;
}
case DATETIME:
if (rVal instanceof GregorianCalendar) {
// LOG.debug("left:" + CalendarHelper.toString((GregorianCalendar)lValue) + " right: " + CalendarHelper.toString((GregorianCalendar)rVal));
return ((GregorianCalendar)lValue).compareTo((GregorianCalendar)rVal);
} else
throwIncompatibleTypesException(lValue, rVal);
break;
case DECIMAL:
{
Double lDoubleValue = ((BigDecimal)lVal.getFirstValue()).doubleValue();
if (rVal instanceof Double)
return lDoubleValue.compareTo((Double)rVal);
else if (rVal instanceof Long)
return Double.valueOf(((Integer)lValue).doubleValue()).compareTo((Double)rVal);
else
throwIncompatibleTypesException(lValue, rVal);
break;
}
case HTML:
case STRING:
case URI:
case ID:
if (rVal instanceof String) {
LOG.debug("compare strings: " + lValue + " with " + rVal);
return ((String)lValue).compareTo((String)rVal);
} else
throwIncompatibleTypesException(lValue, rVal);
break;
}
return 0;
}
private ColumnReference getColumnReference(Tree columnNode) {
CmisSelector sel = queryObj.getColumnReference(columnNode.getTokenStartIndex());
if (null == sel)
throw new RuntimeException("Unknown property query name " + columnNode.getChild(0));
else if (sel instanceof ColumnReference)
return (ColumnReference) sel;
else
throw new RuntimeException("Unexpected numerical value function in where clause");
}
private String getTableReference(Tree tableNode) {
String typeQueryName = queryObj.getTypeQueryName(tableNode.getText());
if (null == typeQueryName)
throw new RuntimeException("Inavlid type in IN_FOLDER() or IN_TREE(), must be in FROM list: " + tableNode.getText());
return typeQueryName;
}
private Object getPropertyValue(Tree columnNode, StoredObject so) {
ColumnReference colRef = getColumnReference(columnNode);
TypeDefinition td = colRef.getTypeDefinition();
PropertyDefinition<?> pd = td.getPropertyDefinitions().get(colRef.getPropertyId());
PropertyData<?> lVal = so.getProperties().get(colRef.getPropertyId());
if (null == lVal)
return null;
else {
if (pd.getCardinality() == Cardinality.SINGLE)
return null == lVal ? null : lVal.getFirstValue();
else
return lVal.getValues();
}
}
// translate SQL wildcards %, _ to Java regex syntax
public static String translatePattern(String wildcardString) {
int index = 0;
int start = 0;
StringBuffer res = new StringBuffer();
while (index >= 0) {
index = wildcardString.indexOf('%', start);
if (index < 0)
res.append(wildcardString.substring(start));
else if (index == 0 || index > 0 && wildcardString.charAt(index-1) != '\\') {
res.append(wildcardString.substring(start, index));
res.append(".*");
} else
res.append(wildcardString.substring(start, index+1));
start = index+1;
}
wildcardString = res.toString();
index = 0;
start = 0;
res = new StringBuffer();
while (index >= 0) {
index = wildcardString.indexOf('_', start);
if (index < 0)
res.append(wildcardString.substring(start));
else if (index == 0 || index > 0 && wildcardString.charAt(index-1) != '\\') {
res.append(wildcardString.substring(start, index));
res.append(".");
} else
res.append(wildcardString.substring(start, index+1));
start = index+1;
}
return res.toString();
}
private void throwIncompatibleTypesException(Object o1, Object o2) {
throw new RuntimeException("Incompatible Types to compare: " + o1 + " and " + o2);
}
}