blob: 20e6639a89eb07c31b12fef8712d7f96e66b5394 [file] [log] [blame]
/*
* 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.
*
* Contributors:
* Jens Huebel
* Florent Guillaume, Nuxeo
*/
package org.apache.chemistry.opencmis.inmemory.query;
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.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.exceptions.CmisInvalidArgumentException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectListImpl;
import org.apache.chemistry.opencmis.inmemory.storedobj.api.Content;
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.ContentStreamDataImpl;
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.AbstractPredicateWalker;
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.QueryObject;
import org.apache.chemistry.opencmis.server.support.query.QueryObject.JoinSpec;
import org.apache.chemistry.opencmis.server.support.query.QueryObject.SortSpec;
import org.apache.chemistry.opencmis.server.support.query.QueryUtilStrict;
import org.apache.chemistry.opencmis.server.support.query.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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.
*/
public class InMemoryQueryProcessor {
private static final Logger LOG = LoggerFactory.getLogger(InMemoryQueryProcessor.class);
private List<StoredObject> matches = new ArrayList<StoredObject>();
private QueryObject queryObj;
private Tree whereTree;
private ObjectStoreImpl objStore;
private List<TypeDefinition> secondaryTypeIds;
public InMemoryQueryProcessor(ObjectStoreImpl objStore) {
this.objStore = objStore;
}
/**
* Main entry function to process a query from discovery service.
*
* @param tm
* type manager for the given repository
* @param objectStore
* object store to gets object from
* @param user
* user execuing the query
* @param repositoryId
* id of repository
* @param statement
* query statement
* @param searchAllVersions
* search in all versions of objects
* @param includeAllowableActions
* include allowable actions
* @param includeRelationships
* include relationships
* @param renditionFilter
* include renditions
* @param maxItems
* max number of items to return
* @param skipCount
* items to skip
* @return list of objects matching the query
*/
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) {
processQueryAndCatchExc(statement, tm); // 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, user, 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;
}
/**
* Process a query.
* @param statement
* CMISQL statement to execute
* @param tm
* type manager for the repository
*/
public void processQueryAndCatchExc(String statement, TypeManager tm) {
QueryUtilStrict queryUtil = new QueryUtilStrict(statement, tm, null);
queryUtil.processStatementUsingCmisExceptions();
CmisQueryWalker walker = queryUtil.getWalker();
queryObj = queryUtil.getQueryObject();
whereTree = walker.getWherePredicateTree();
secondaryTypeIds = queryObj.getJoinedSecondaryTypes();
doAdditionalChecks(walker);
}
/**
* Create the list of matching objects for this query.
* @param tm
* type manager for the given repository
* @param user
* user execuing the query
* @param includeAllowableActions
* include allowable actions
* @param includeRelationships
* include relationships
* @param renditionFilter
* include renditions
* @param maxItems
* max number of items to return
* @param skipCount
* items to skip
* @return
* list of objects matching the query
*/
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.getRequestedPropertiesByAlias();
Map<String, String> funcs = queryObj.getRequestedFuncsByAlias();
for (StoredObject so : matches) {
String queryName = queryObj.getTypes().values().iterator().next();
TypeDefinition td = queryObj.getTypeDefinitionFromQueryName(queryName);
ObjectData od = PropertyCreationHelper.getObjectDataQueryResult(tm, objStore, td, so, user, props, funcs,
secondaryTypeIds, 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 secondary types
List<String> secTypeIds = so.getSecondaryTypeIds();
for (String secTypeId : secTypeIds) {
if (secTypeId.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> {
@Override
@SuppressWarnings("unchecked")
public int compare(StoredObject so1, StoredObject so2) {
SortSpec s = orderBy.get(0);
CmisSelector sel = s.getSelector();
int result;
if (queryObj.isPredfinedQueryName(sel.getName())) {
// must be SEARCH_SCORE which is currently ignored
result = 0;
} else if (sel instanceof ColumnReference) {
String propId = ((ColumnReference) sel).getPropertyId();
PropertyDefinition<?> pd = ((ColumnReference) sel).getPropertyDefinition();
Object propVal1 = PropertyQueryUtil.getProperty(so1, propId, pd);
Object propVal2 = PropertyQueryUtil.getProperty(so2, propId, pd);
if (propVal1 == null && propVal2 == null) {
result = 0;
} else if (propVal1 == null) {
result = -1;
} else if (propVal2 == null) {
result = 1;
} else {
result = ((Comparable<Object>) propVal1).compareTo(propVal2);
}
} else {
// 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.
*/
private void match(StoredObject so, String user, boolean searchAllVersions) {
// first check if type is matching...
// as we don't support joins take first type
String queryName = queryObj.getTypes().values().iterator().next();
TypeDefinition td = queryObj.getTypeDefinitionFromQueryName(queryName);
// we are only interested in versions not in the series
boolean skip = so instanceof VersionedDocument;
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, user, so);
}
}
private void evalWhereTree(Tree node, String user, StoredObject so) {
boolean match = true;
if (null != node) {
match = evalWhereNode(so, user, node);
}
if (match && objStore.hasReadAccess(user, so)) {
matches.add(so); // add to list
}
}
/*
* 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.
*/
private boolean evalWhereNode(StoredObject so, String user, Tree node) {
return new InMemoryWhereClauseWalker(so, user).walkPredicate(node);
}
public class InMemoryWhereClauseWalker extends AbstractPredicateWalker {
protected final StoredObject so;
protected final String user;
public InMemoryWhereClauseWalker(StoredObject so, String user) {
this.so = so;
this.user = user;
}
@Override
public Boolean walkNot(Tree opNode, Tree node) {
boolean hasMatched = walkPredicate(node);
return !hasMatched;
}
@Override
public Boolean walkAnd(Tree opNode, Tree leftNode, Tree rightNode) {
boolean matches1 = walkPredicate(leftNode);
boolean matches2 = walkPredicate(rightNode);
return matches1 && matches2;
}
@Override
public Boolean walkOr(Tree opNode, Tree leftNode, Tree rightNode) {
boolean matches1 = walkPredicate(leftNode);
boolean matches2 = walkPredicate(rightNode);
return matches1 || matches2;
}
@Override
public Boolean walkEquals(Tree opNode, Tree leftNode, Tree rightNode) {
Integer cmp = compareTo(leftNode, rightNode);
return cmp == null ? false : cmp == 0;
}
@Override
public Boolean walkNotEquals(Tree opNode, Tree leftNode, Tree rightNode) {
Integer cmp = compareTo(leftNode, rightNode);
return cmp == null ? false : cmp != 0;
}
@Override
public Boolean walkGreaterThan(Tree opNode, Tree leftNode, Tree rightNode) {
Integer cmp = compareTo(leftNode, rightNode);
return cmp == null ? false : cmp > 0;
}
@Override
public Boolean walkGreaterOrEquals(Tree opNode, Tree leftNode, Tree rightNode) {
Integer cmp = compareTo(leftNode, rightNode);
return cmp == null ? false : cmp >= 0;
}
@Override
public Boolean walkLessThan(Tree opNode, Tree leftNode, Tree rightNode) {
Integer cmp = compareTo(leftNode, rightNode);
return cmp == null ? false : cmp < 0;
}
@Override
public Boolean walkLessOrEquals(Tree opNode, Tree leftNode, Tree rightNode) {
Integer cmp = compareTo(leftNode, rightNode);
return cmp == null ? false : cmp <= 0;
}
@Override
public Boolean walkIn(Tree opNode, Tree colNode, Tree listNode) {
ColumnReference colRef = getColumnReference(colNode);
PropertyDefinition<?> pd = colRef.getPropertyDefinition();
List<Object> literals = onLiteralList(listNode);
Object prop = PropertyQueryUtil.getProperty(so, colRef.getPropertyId(), pd);
if (pd.getCardinality() != Cardinality.SINGLE) {
throw new IllegalStateException("Operator IN only is allowed on single-value properties ");
} else if (prop == null) {
return false;
} else {
return literals.contains(prop);
}
}
@Override
public Boolean walkNotIn(Tree opNode, Tree colNode, Tree listNode) {
// Note just return !walkIn(node, colNode, listNode) is wrong,
// because
// then it evaluates to true for null values (not set properties).
ColumnReference colRef = getColumnReference(colNode);
PropertyDefinition<?> pd = colRef.getPropertyDefinition();
Object prop = PropertyQueryUtil.getProperty(so, colRef.getPropertyId(), pd);
List<Object> literals = onLiteralList(listNode);
if (pd.getCardinality() != Cardinality.SINGLE) {
throw new IllegalStateException("Operator IN only is allowed on single-value properties ");
} else if (prop == null) {
return false;
} else {
return !literals.contains(prop);
}
}
@Override
public Boolean walkInAny(Tree opNode, Tree colNode, Tree listNode) {
ColumnReference colRef = getColumnReference(colNode);
PropertyDefinition<?> pd = colRef.getPropertyDefinition();
PropertyData<?> lVal = so.getProperties().get(colRef.getPropertyId());
List<Object> literals = onLiteralList(listNode);
if (pd.getCardinality() != Cardinality.MULTI) {
throw new IllegalStateException("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;
}
}
@Override
public Boolean walkNotInAny(Tree opNode, Tree colNode, Tree listNode) {
// Note just return !walkNotInAny(node, colNode, listNode) is
// wrong, because
// then it evaluates to true for null values (not set properties).
ColumnReference colRef = getColumnReference(colNode);
PropertyDefinition<?> pd = colRef.getPropertyDefinition();
PropertyData<?> lVal = so.getProperties().get(colRef.getPropertyId());
List<Object> literals = onLiteralList(listNode);
if (pd.getCardinality() != Cardinality.MULTI) {
throw new IllegalStateException("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;
}
}
@Override
public Boolean walkEqAny(Tree opNode, Tree literalNode, Tree colNode) {
ColumnReference colRef = getColumnReference(colNode);
PropertyDefinition<?> pd = colRef.getPropertyDefinition();
PropertyData<?> lVal = so.getProperties().get(colRef.getPropertyId());
Object literal = walkExpr(literalNode);
if (pd.getCardinality() != Cardinality.MULTI) {
throw new IllegalStateException("Operator = ANY only is allowed on multi-value properties ");
} else if (lVal == null) {
return false;
} else {
List<?> props = lVal.getValues();
return props.contains(literal);
}
}
@Override
public Boolean walkIsNull(Tree opNode, Tree colNode) {
ColumnReference colRef = getColumnReference(colNode);
PropertyDefinition<?> pd = colRef.getPropertyDefinition();
Object propVal = PropertyQueryUtil.getProperty(so, colRef.getPropertyId(), pd);
return propVal == null;
}
@Override
public Boolean walkIsNotNull(Tree opNode, Tree colNode) {
ColumnReference colRef = getColumnReference(colNode);
PropertyDefinition<?> pd = colRef.getPropertyDefinition();
Object propVal = PropertyQueryUtil.getProperty(so, colRef.getPropertyId(), pd);
return propVal != null;
}
@Override
public Boolean walkLike(Tree opNode, Tree colNode, Tree stringNode) {
Object rVal = walkExpr(stringNode);
if (!(rVal instanceof String)) {
throw new IllegalStateException("LIKE operator requires String literal on right hand side.");
}
ColumnReference colRef = getColumnReference(colNode);
PropertyDefinition<?> pd = colRef.getPropertyDefinition();
PropertyType propType = pd.getPropertyType();
if (propType != PropertyType.STRING && propType != PropertyType.HTML && propType != PropertyType.ID
&& propType != PropertyType.URI) {
throw new IllegalStateException("Property type " + propType.value() + " is not allowed FOR LIKE");
}
if (pd.getCardinality() != Cardinality.SINGLE) {
throw new IllegalStateException("LIKE is not allowed for multi-value properties ");
}
String propVal = (String) PropertyQueryUtil.getProperty(so, colRef.getPropertyId(), pd);
if (null == propVal) {
return false;
} else {
String pattern = translatePattern((String) rVal); // SQL to Java
// regex
// syntax
Pattern p = Pattern.compile(pattern);
return p.matcher(propVal).matches();
}
}
@Override
public Boolean walkNotLike(Tree opNode, Tree colNode, Tree stringNode) {
return !walkLike(opNode, colNode, stringNode);
}
@Override
public Boolean walkInFolder(Tree opNode, Tree qualNode, Tree paramNode) {
if (null != qualNode) {
getTableReference(qualNode);
// just for error checking we do not evaluate this, there is
// only one from without join support
}
Object lit = walkExpr(paramNode);
if (!(lit instanceof String)) {
throw new IllegalStateException("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(so, folderId, user);
} else {
return false;
}
}
@Override
public Boolean walkInTree(Tree opNode, Tree qualNode, Tree paramNode) {
if (null != qualNode) {
getTableReference(qualNode);
// just for error checking we do not evaluate this, there is
// only one from without join support
}
Object lit = walkExpr(paramNode);
if (!(lit instanceof String)) {
throw new IllegalStateException("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(so, folderId, user);
} else {
return false;
}
}
protected Integer compareTo(Tree leftChild, Tree rightChild) {
Object rVal = walkExpr(rightChild);
ColumnReference colRef = getColumnReference(leftChild);
PropertyDefinition<?> pd = colRef.getPropertyDefinition();
Object val = PropertyQueryUtil.getProperty(so, colRef.getPropertyId(), pd);
if (val == null) {
return null;
} else if (val instanceof List<?>) {
throw new IllegalStateException(
"You can't query operators <, <=, ==, !=, >=, > on multi-value properties ");
} else {
return InMemoryQueryProcessor.this.compareTo(pd, val, rVal);
}
}
@SuppressWarnings("unchecked")
public List<Object> onLiteralList(Tree node) {
return (List<Object>) walkExpr(node);
}
@Override
protected Boolean walkTextAnd(Tree node) {
List<Tree> terms = getChildrenAsList(node);
for (Tree term : terms) {
Boolean foundOnce = walkSearchExpr(term);
if (foundOnce == null || !foundOnce) {
return false;
}
}
return true;
}
@Override
protected Boolean walkTextOr(Tree node) {
List<Tree> terms = getChildrenAsList(node);
for (Tree term : terms) {
Boolean foundOnce = walkSearchExpr(term);
if (foundOnce != null && foundOnce) {
return true;
}
}
return false;
}
@Override
protected Boolean walkTextMinus(Tree node) {
return !findText(node.getChild(0).getText());
}
@Override
protected Boolean walkTextWord(Tree node) {
return findText(node.getText());
}
@Override
protected Boolean walkTextPhrase(Tree node) {
String phrase = node.getText();
return findText(phrase.substring(1, phrase.length() - 1));
}
private List<Tree> getChildrenAsList(Tree node) {
List<Tree> res = new ArrayList<Tree>(node.getChildCount());
for (int i = 0; i < node.getChildCount(); i++) {
Tree childNnode = node.getChild(i);
res.add(childNnode);
}
return res;
}
private boolean findText(String nodeText) {
Content cont = (Content) so;
String pattern = StringUtil.unescape(nodeText, "\\'-");
if (null == pattern) {
throw new CmisInvalidArgumentException("Illegal Escape sequence in text search expression " + nodeText);
}
if (so instanceof Content && cont.hasContent()) {
ContentStreamDataImpl cdi = (ContentStreamDataImpl) cont.getContent();
if (cdi.getMimeType().startsWith("text/")) {
byte[] ba = cdi.getBytes();
String text;
try {
text = new String(ba, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new CmisRuntimeException("Internal error: Unsupported encoding UTF-8", e);
}
int match = text.indexOf(pattern);
return match >= 0;
} else {
return false;
}
}
return false;
}
}
private boolean hasParent(StoredObject objInFolder, String folderId, String user) {
List<String> parents = objStore.getParentIds(objInFolder, user);
for (String parentId : parents) {
if (folderId.equals(parentId)) {
return true;
}
}
return false;
}
private boolean hasAncestor(StoredObject objInFolder, String folderId, String user) {
List<String> parents = objStore.getParentIds(objInFolder, user);
for (String parentId : parents) {
if (folderId.equals(parentId)) {
return true;
}
}
for (String parentId : parents) {
Folder parentFolder = (Folder) objStore.getObjectById(parentId);
if (hasAncestor(parentFolder, folderId, user)) {
return true;
}
}
return false;
}
protected int compareTo(PropertyDefinition<?> td, Object lValue, Object rVal) {
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) lValue).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) {
return ((GregorianCalendar) lValue).compareTo((GregorianCalendar) rVal);
} else {
throwIncompatibleTypesException(lValue, rVal);
}
break;
case DECIMAL:
Double lDoubleValue = ((BigDecimal) lValue).doubleValue();
if (rVal instanceof Double) {
return lDoubleValue.compareTo((Double) rVal);
} else if (rVal instanceof Long) {
return lDoubleValue.compareTo(Double.valueOf(((Long) rVal)));
} else {
throwIncompatibleTypesException(lValue, rVal);
}
break;
case HTML:
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;
case STRING:
if (rVal instanceof String) {
String unesc = StringUtil.unescape((String) rVal, null);
LOG.debug("compare strings: " + lValue + " with " + unesc);
return ((String) lValue).compareTo(unesc);
} else {
throwIncompatibleTypesException(lValue, rVal);
}
break;
}
return 0;
}
private ColumnReference getColumnReference(Tree columnNode) {
CmisSelector sel = queryObj.getColumnReference(columnNode.getTokenStartIndex());
if (null == sel) {
throw new IllegalStateException("Unknown property query name " + columnNode.getChild(0));
} else if (sel instanceof ColumnReference) {
return (ColumnReference) sel;
} else {
throw new IllegalStateException("Unexpected numerical value function in where clause");
}
}
private String getTableReference(Tree tableNode) {
String typeQueryName = queryObj.getTypeQueryName(tableNode.getText());
if (null == typeQueryName) {
throw new IllegalStateException("Inavlid type in IN_FOLDER() or IN_TREE(), must be in FROM list: "
+ tableNode.getText());
}
return typeQueryName;
}
private void doAdditionalChecks(CmisQueryWalker walker) {
if (walker.getNumberOfContainsClauses() > 1) {
throw new CmisInvalidArgumentException("More than one CONTAINS clause is not allowed");
}
List<JoinSpec> joins = queryObj.getJoins();
if (null == secondaryTypeIds && joins.size() > 0) {
throw new CmisInvalidArgumentException(
"JOINs are not supported with the exception of secondary types and a LEFT OUTER JOIN");
} else if (null != secondaryTypeIds) {
for (JoinSpec join : joins) {
if (!join.kind.equals("LEFT")) {
throw new CmisInvalidArgumentException(
"JOINs are not supported with the exception of secondary types and a LEFT OUTER JOIN");
}
}
}
}
/**
* Translate SQL wildcards %, _ to Java regex syntax.
*
* @param wildcardString
* string to process
* @return
* string with replaced characters
*/
public static String translatePattern(String wildcardString) {
int index = 0;
int start = 0;
String wildcard = wildcardString;
StringBuilder res = new StringBuilder();
while (index >= 0) {
index = wildcard.indexOf('%', start);
if (index < 0) {
res.append(wildcard.substring(start));
} else if (index == 0 || index > 0 && wildcard.charAt(index - 1) != '\\') {
res.append(wildcard.substring(start, index));
res.append(".*");
} else {
res.append(wildcard.substring(start, index + 1));
}
start = index + 1;
}
wildcard = res.toString();
index = 0;
start = 0;
res = new StringBuilder();
while (index >= 0) {
index = wildcard.indexOf('_', start);
if (index < 0) {
res.append(wildcard.substring(start));
} else if (index == 0 || index > 0 && wildcard.charAt(index - 1) != '\\') {
res.append(wildcard.substring(start, index));
res.append(".");
} else {
res.append(wildcard.substring(start, index + 1));
}
start = index + 1;
}
return res.toString();
}
private static void throwIncompatibleTypesException(Object o1, Object o2) {
throw new CmisInvalidArgumentException("Incompatible Types to compare: " + o1 + " and " + o2);
}
}