/*
* (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser General Public License
* (LGPL) version 2.1 which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/lgpl-2.1.html
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* Contributors:
* Florent Guillaume
*/
package org.nuxeo.ecm.core.storage.dbs;
import static java.lang.Boolean.TRUE;
import java.io.Serializable;
import java.util.Comparator;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.query.sql.NXQL;
import org.nuxeo.ecm.core.query.sql.model.Expression;
import org.nuxeo.ecm.core.query.sql.model.OrderByClause;
import org.nuxeo.ecm.core.query.sql.model.OrderByExpr;
import org.nuxeo.ecm.core.query.sql.model.OrderByList;
import org.nuxeo.ecm.core.query.sql.model.Reference;
import org.nuxeo.ecm.core.schema.SchemaManager;
import org.nuxeo.ecm.core.schema.types.Field;
import org.nuxeo.ecm.core.schema.types.ListType;
import org.nuxeo.ecm.core.schema.types.Schema;
import org.nuxeo.ecm.core.schema.types.Type;
import org.nuxeo.ecm.core.schema.types.primitives.BooleanType;
import org.nuxeo.ecm.core.storage.ExpressionEvaluator;
import org.nuxeo.ecm.core.storage.State;
import org.nuxeo.runtime.api.Framework;
/**
* Expression evaluator for a {@link DBSDocument} state.
*
* @since 5.9.4
*/
public class DBSExpressionEvaluator extends ExpressionEvaluator {
private static final Log log = LogFactory.getLog(DBSExpressionEvaluator.class);
private static final Long ZERO = Long.valueOf(0);
private static final Long ONE = Long.valueOf(1);
protected final Expression expr;
protected final SchemaManager schemaManager;
protected State state;
public DBSExpressionEvaluator(DBSSession session, Expression expr,
String[] principals) {
super(new DBSPathResolver(session), principals);
this.expr = expr;
schemaManager = Framework.getLocalService(SchemaManager.class);
}
protected static class DBSPathResolver implements PathResolver {
protected final DBSSession session;
public DBSPathResolver(DBSSession session) {
this.session = session;
}
@Override
public String getIdForPath(String path) {
return session.getDocumentIdByPath(path);
}
}
public boolean matches(State state) {
this.state = state;
// security check
if (principals != null) {
String[] racl = (String[]) walkReference(new Reference(
NXQL_ECM_READ_ACL));
if (racl == null) {
log.error("NULL racl for " + state.get(DBSDocument.KEY_ID));
} else {
boolean allowed = false;
for (String user : racl) {
if (principals.contains(user)) {
allowed = true;
break;
}
}
if (!allowed) {
return false;
}
}
}
return TRUE.equals(walkExpression(expr));
}
public boolean matches(DBSDocumentState docState) {
return matches(docState.getState());
}
@Override
public Object walkReference(Reference ref) {
return evaluateReference(ref, state);
}
@Override
public Object evaluateReference(Reference ref, State state) {
String name = ref.name;
String[] split = name.split("/");
String prop = split[0];
boolean isArray;
boolean isBoolean;
boolean isTrueOrNullBoolean;
if (name.startsWith(NXQL.ECM_PREFIX)) {
prop = DBSSession.convToInternal(name);
isArray = DBSSession.isArray(prop);
isBoolean = DBSSession.isBoolean(prop);
isTrueOrNullBoolean = true;
} else {
Field field = schemaManager.getField(prop);
if (field == null) {
if (prop.indexOf(':') > -1) {
throw new RuntimeException("Unkown property: " + name);
}
// check without prefix
// TODO precompute this in SchemaManagerImpl
for (Schema schema : schemaManager.getSchemas()) {
if (!StringUtils.isBlank(schema.getNamespace().prefix)) {
// schema with prefix, do not consider as candidate
continue;
}
if (schema != null) {
field = schema.getField(prop);
if (field != null) {
break;
}
}
}
if (field == null) {
throw new RuntimeException("Unkown property: " + name);
}
}
prop = field.getName().getPrefixedName();
Type type = field.getType();
isArray = type instanceof ListType && ((ListType) type).isArray();
isBoolean = type instanceof BooleanType;
isTrueOrNullBoolean = false;
}
Serializable value = state.get(prop);
for (int i = 1; i < split.length; i++) {
if (value == null) {
return null;
}
if (!(value instanceof State)) {
throw new RuntimeException("Unkown property (no State): "
+ name);
}
value = ((State) value).get(split[i]);
}
if (value == null && isArray) {
// don't use null, as list-based matches don't use ternary logic
value = new Object[0];
}
if (isBoolean) {
// boolean evaluation is like 0 / 1
if (isTrueOrNullBoolean) {
value = TRUE.equals(value) ? ONE : ZERO;
} else {
value = value == null ? null
: (((Boolean) value).booleanValue() ? ONE : ZERO);
}
}
return value;
}
public static class OrderByComparator implements Comparator<State> {
protected final OrderByClause orderByClause;
protected ExpressionEvaluator matcher;
public OrderByComparator(OrderByClause orderByClause,
ExpressionEvaluator matcher) {
// replace ecm:path with ecm:__path for evaluation
// (we don't want to allow ecm:path to be usable anywhere else
// and resolve to a null value)
OrderByList obl = new OrderByList(null); // stupid constructor
obl.clear();
for (OrderByExpr ob : orderByClause.elements) {
if (ob.reference.name.equals(NXQL.ECM_PATH)) {
ob = new OrderByExpr(new Reference(NXQL_ECM_PATH),
ob.isDescending);
}
obl.add(ob);
}
this.orderByClause = new OrderByClause(obl);
this.matcher = matcher;
}
@Override
public int compare(State s1, State s2) {
for (OrderByExpr ob : orderByClause.elements) {
Reference ref = ob.reference;
boolean desc = ob.isDescending;
int sign = desc ? -1 : 1;
Object v1 = matcher.evaluateReference(ref, s1);
Object v2 = matcher.evaluateReference(ref, s2);
if (v1 == null) {
return v2 == null ? 0 : -sign;
} else if (v2 == null) {
return sign;
} else {
if (!(v1 instanceof Comparable)) {
throw new RuntimeException("Not a comparable: " + v1);
}
int cmp = ((Comparable<Object>) v1).compareTo(v2);
return desc ? -cmp : cmp;
}
}
return 0;
}
}
}