libreccm-legacy/ccm-core/src/com/arsdigita/persistence/DataQueryImpl.java

812 lines
20 KiB
Java
Executable File

/*
* Copyright (C) 2001-2004 Red Hat Inc. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
package com.arsdigita.persistence;
import com.arsdigita.persistence.metadata.CompoundType;
import com.arsdigita.profiler.Profiler;
import com.arsdigita.util.Assert;
import com.redhat.persistence.Cursor;
import com.redhat.persistence.DataSet;
import com.redhat.persistence.ProtoException;
import com.redhat.persistence.Signature;
import com.redhat.persistence.common.ParseException;
import com.redhat.persistence.common.Path;
import com.redhat.persistence.common.SQLParser;
import com.redhat.persistence.metadata.ObjectType;
import com.redhat.persistence.metadata.Property;
import com.redhat.persistence.metadata.Root;
import com.redhat.persistence.oql.All;
import com.redhat.persistence.oql.Define;
import com.redhat.persistence.oql.Equals;
import com.redhat.persistence.oql.Exists;
import com.redhat.persistence.oql.Expression;
import com.redhat.persistence.oql.LeftJoin;
import com.redhat.persistence.oql.Limit;
import com.redhat.persistence.oql.Literal;
import com.redhat.persistence.oql.Offset;
import com.redhat.persistence.oql.Sort;
import com.redhat.persistence.oql.Static;
import com.redhat.persistence.oql.Variable;
import com.redhat.persistence.pdl.PDL;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
/**
* DataQueryImpl
*
* @author Rafael H. Schloming <rhs@mit.edu>
* @author Sören Bernstein <quasi@quasiweb.de>
* @version $Id: DataQueryImpl.java 1304 2006-08-31 13:12:47Z sskracic $
*/
class DataQueryImpl implements DataQuery {
private static final Logger s_log = Logger.getLogger(DataQueryImpl.class);
private static final String s_unalias =
"com.arsdigita.persistence.DataQueryImpl.unalias";
private static final String s_mapAndAddPath =
"com.arsdigita.persistence.DataQueryImpl.mapAndAddPath";
private Map m_options = new HashMap();
private SQLParser getParser(String key, Reader reader,
SQLParser.Mapper mapper) {
TransactionContext ctx = m_ssn.getTransactionContext();
SQLParser p = (SQLParser) ctx.getAttribute(key);
if (p == null) {
p = new SQLParser(reader, mapper);
ctx.setAttribute(key, p);
} else {
p.initialize(reader, mapper);
}
return p;
}
private Session m_ssn;
private com.redhat.persistence.Session m_pssn;
private HashMap m_bindings = new HashMap();
final Signature m_originalSig;
private Signature m_signature;
final private Expression m_originalExpr;
private Expression m_expr;
Cursor m_cursor = null;
private CompoundFilterImpl m_filter;
private ArrayList m_orders = new ArrayList();
// This indicates the offset/limit sent of the query
private Integer m_offset = null;
private Integer m_limit = null;
// This indicates the limits on the number of rows returned by the query
private int m_lowerBound = 0;
private int m_upperBound = Integer.MAX_VALUE;
private final List m_aliases = new ArrayList();
// used by addPath to implement addPath for paths traversing 0..n
private HashMap m_joins = new HashMap();
private final FilterFactory m_factory;
DataQueryImpl(Session ssn, DataSet ds) {
this(ssn, ds.getSignature(), ds.getExpression());
}
DataQueryImpl(Session ssn, Signature sig, Expression expr) {
m_ssn = ssn;
m_pssn = ssn.getProtoSession();
m_originalSig = sig;
m_originalExpr = expr;
m_factory = new FilterFactoryImpl(ssn);
reset();
}
Session getSession() {
return m_ssn;
}
/**
* Returns the com.redhat.persistence type of the query.
*/
ObjectType getTypeInternal() {
return m_originalSig.getObjectType();
}
public CompoundType getType() {
throw new Error("not implemented");
}
public boolean hasProperty(String propertyName) {
Path p = unalias(Path.get(propertyName));
return hasProperty(p);
}
boolean hasProperty(Path p) {
return getTypeInternal().getProperty(p) != null;
}
public void reset() {
close();
m_cursor = null;
clearOrder();
clearFilter();
m_lowerBound = 0;
m_upperBound = Integer.MAX_VALUE;
m_offset = null;
m_limit = null;
m_signature = new Signature(m_originalSig);
m_joins.clear();
m_bindings.clear();
// XXX: hack for data queries with bindings that have calls to addPath
// addJoin needs to join against the static-ified version of the All.
// this is equivalent to testing if m_originalExpr instanceof All
if (this.getClass().equals(DataQueryImpl.class)) {
m_expr = new Define(new Static(getTypeInternal().getQualifiedName(), m_bindings),
"this");
} else {
m_expr = new Define(m_originalExpr, "this");
}
m_signature = new Signature();
m_signature.addSignature(m_originalSig, Path.get("this"));
m_joins.put(null, "this");
}
@Override
public boolean first() {
throw new Error("not implemented");
}
@Override
public boolean isEmpty() {
try {
// can not use checkCursor() because then we can't add filters
// after calls to isEmpty
if (m_cursor == null) {
return new DataSet(m_pssn, m_signature, makeExpr()).isEmpty();
} else {
return m_cursor.getDataSet().isEmpty();
}
} catch (ProtoException e) {
throw PersistenceException.newInstance(e);
}
}
@Override
public boolean isBeforeFirst() {
checkCursor();
return m_cursor.isBeforeFirst();
}
@Override
public boolean isFirst() {
checkCursor();
return m_cursor.isFirst();
}
@Override
public boolean isLast() {
return size() == getPosition();
}
@Override
public boolean isAfterLast() {
checkCursor();
return m_cursor.isAfterLast();
}
@Override
public boolean last() {
throw new Error("not implemented");
}
@Override
public boolean previous() {
checkCursor();
if (m_cursor.isClosed()) {
return false;
}
int pre = getPosition();
boolean result;
try {
Profiler.startOp("DB");
result = m_cursor.previous();
} catch (ProtoException e) {
throw PersistenceException.newInstance(e);
} finally {
Profiler.stopOp("DB");
}
if (result) {
if (getPosition() == m_upperBound) {
if (m_cursor.previous()) {
throw new PersistenceException("cursor exceeded upper bound");
}
}
} else {
if (pre < m_lowerBound) {
throw new PersistenceException("cursor failed to meet lower bound");
}
}
return result;
}
public void addPath(String path) {
Path p = unalias(Path.get(path));
addPath(p, true);
}
private void addPath(Path path, boolean requiresFetching) {
if (m_cursor != null) {
throw new PersistenceException("Paths cannot be added on an active data query.");
}
addJoin(path);
path = resolvePath(path);
if (requiresFetching) {
m_signature.addPath(path);
} else {
Assert.isTrue(m_signature.exists(path));
}
}
protected Path resolvePath(Path path) {
if (m_joins.size() == 0) {
return path;
}
Path base = path;
for (; base != null; base = base.getParent()) {
if (m_joins.containsKey(base)) {
break;
}
}
Path candidate = Path.add((String) m_joins.get(base), Path.relative(base, path));
if (m_signature.exists(candidate)) {
return candidate;
}
return path;
}
private void addJoin(Path path) {
List elts = new ArrayList();
for (Path p = path; p != null; p = p.getParent()) {
elts.add(p.getName());
}
ObjectType type = getTypeInternal();
Path coll = null;
Path prev = null;
boolean collectionFound = false;
for (int i = elts.size() - 1; i >= 0; i--) {
String propName = (String) elts.get(i);
Property prop = type.getProperty(propName);
type = prop.getType();
coll = Path.add(coll, propName);
if (prop.isCollection()) {
collectionFound = true;
String alias = coll.getPath().replace('.', '_');
if (m_joins.containsKey(coll)) {
prev = coll;
continue;
}
m_joins.put(coll, alias);
Expression prevColl;
if (prev == null) {
prevColl = new Define(Expression.valueOf(Path.add("this", coll)), "target");
} else {
Path p = Path.add((String) m_joins.get(prev),
Path.relative(prev, coll));
prevColl = new Define(Expression.valueOf(p), "target");
}
Expression cond = new Exists(new com.redhat.persistence.oql.Filter(prevColl,
new Equals(new Variable(alias), new Variable("target"))));
m_expr = new LeftJoin(m_expr,
new Define(new All(type.getQualifiedName()), alias),
cond);
m_signature.addSource(type, Path.get(alias));
prev = coll;
}
if (propName.endsWith(PDL.LINK)) {
Path rel = null;
String assocName = propName.substring(0, propName.length() - PDL.LINK.length());
Path assoc = Path.add(coll.getParent(), assocName);
addJoin(assoc);
Path pathThroughLink = Path.add(resolvePath(coll), assocName);
m_expr = new com.redhat.persistence.oql.Filter(m_expr, new Equals(Expression.valueOf(pathThroughLink),
Expression.valueOf(resolvePath(assoc))));
}
}
}
public Filter setFilter(String conditions) {
clearFilter();
return addFilter(conditions);
}
public Filter addFilter(String conditions) {
if (m_cursor != null) {
throw new PersistenceException("The filter cannot be set on an active data query. "
+ "Data query must be rewound.");
}
return m_filter.addFilter(conditions);
}
public Filter addFilter(Filter filter) {
if (m_cursor != null) {
throw new PersistenceException("The filter cannot be set on an active data query. "
+ "Data query must be rewound.");
}
return m_filter.addFilter(filter);
}
public boolean removeFilter(Filter filter) {
if (m_cursor != null) {
throw new PersistenceException("The filter cannot be removed on an active data query. "
+ "Data query must be rewound.");
}
return m_filter.removeFilter(filter);
}
public Filter addInSubqueryFilter(String propertyName,
String subqueryName) {
return addFilter(getFilterFactory().in(propertyName, subqueryName));
}
public Filter addInSubqueryFilter(String propertyName,
String subQueryProperty,
String queryName) {
return addFilter(getFilterFactory().in(propertyName, subQueryProperty, queryName));
}
public Filter addNotInSubqueryFilter(String propertyName,
String subqueryName) {
return addFilter(getFilterFactory().notIn(propertyName, subqueryName));
}
public Filter addEqualsFilter(String attribute, Object value) {
return addFilter(getFilterFactory().equals(attribute, value));
}
public Filter addNotEqualsFilter(String attribute, Object value) {
return addFilter(getFilterFactory().notEquals(attribute, value));
}
public void clearFilter() {
if (m_cursor != null) {
throw new PersistenceException("Cannot clear the filter on an active data query. "
+ "Data query must be rewound.");
}
m_filter = (CompoundFilterImpl) getFilterFactory().and();
}
public FilterFactory getFilterFactory() {
return m_factory;
}
public void setOrder(String order) {
clearOrder();
addOrder(order);
}
public void addOrder(String order) {
if (m_cursor != null) {
throw new PersistenceException("Cannot order an active data query. "
+ "Data query must be rewound.");
}
order = unalias(order);
m_orders.add(order);
}
private int m_order = 0;
public void addOrderWithNull(String orderOne, Object orderTwo,
boolean isAscending) {
String suffix = null;
if (isAscending) {
suffix = "asc";
} else {
suffix = "desc";
}
Object secondElement = orderTwo;
if ((orderTwo != null) && (orderTwo instanceof String)) {
Path two = unalias(Path.get((String) orderTwo));
// XXX:
if (!hasProperty(two)) {
String var = "order" + m_order++;
secondElement = ":" + var;
setParameter(var, orderTwo);
if (orderOne != null) {
Root root = getSession().getRoot();
ObjectType typeOne = getTypeInternal().getProperty(unalias(Path.get(orderOne))).getType();
if (!root.getObjectType("global.String").equals(typeOne)) {
// this means that there is going to be a type conflict
// by the DB so we prevent it here
throw new PersistenceException("type mismatch");
}
}
}
}
if ((orderTwo != null) && (orderTwo instanceof Date)) {
}
addOrder("case when (" + orderOne + " is null) then "
+ secondElement + " else " + orderOne + " end " + suffix);
}
public void clearOrder() {
m_orders.clear();
m_order = 0;
}
public void setParameter(String parameterName, Object value) {
m_bindings.put(parameterName, value);
}
public Object getParameter(String parameterName) {
return m_bindings.get(parameterName);
}
public void setOption(String optionName, Object value) {
m_options.put(optionName, value);
}
public Object getOption(String optionName) {
return m_options.get(optionName);
}
public void setRange(Integer beginIndex) {
setRange(beginIndex, null);
}
public void setRange(Integer beginIndex, Integer endIndex) {
if (endIndex != null && endIndex.compareTo(beginIndex) <= 0) {
throw new PersistenceException("The beginIndex [" + beginIndex + "] must be strictly less "
+ "than the endIndex [" + endIndex + "]");
}
m_offset = new Integer(beginIndex.intValue() - 1);
if (endIndex != null) {
m_limit = new Integer(endIndex.intValue() - beginIndex.intValue());
}
}
public Map getPropertyValues() {
throw new Error("not implemented");
}
public void setReturnsUpperBound(int upperBound) {
m_upperBound = upperBound;
}
public void setReturnsLowerBound(int lowerBound) {
if (lowerBound > 1 || lowerBound < 0) {
throw new PersistenceException("The lower bound for a given query must be 0 or 1.");
}
m_lowerBound = lowerBound;
}
public void alias(String fromPrefix, String toPrefix) {
m_aliases.add(new Alias(fromPrefix, toPrefix));
}
public void close() {
if (m_cursor != null) {
m_cursor.close();
}
}
public void rewind() {
if (m_cursor != null) {
m_cursor.rewind();
}
}
public Object get(String propertyName) {
Path path = resolvePath(unalias(Path.get(propertyName)));
try {
return m_cursor.get(path);
} catch (ProtoException e) {
throw PersistenceException.newInstance(e);
}
}
public int getPosition() {
checkCursor();
return (int) m_cursor.getPosition();
}
private class AddPathMapper implements SQLParser.Mapper {
public Path map(Path path) {
Path p = unalias(path);
// XXX: hasProperty(p) does not work because you can't
// addPath doesn't accept paths starting with
// Session.LINK_ASSOCIATION
if (getTypeInternal().getProperty(p) != null) {
addPath(p, false);
}
return resolvePath(p);
}
}
private SQLParser.Mapper m_mapper = new AddPathMapper();
Path mapAndAddPath(Path p) {
return m_mapper.map(p);
}
String mapAndAddPaths(String s) {
StringReader reader = new StringReader(s);
SQLParser p = getParser(s_mapAndAddPath, reader, m_mapper);
try {
p.sql();
} catch (ParseException e) {
throw new IllegalArgumentException(e.getMessage());
}
return p.getSQL().toString();
}
private Expression makeExpr() {
String[] orders = new String[m_orders.size()];
for (int i = m_orders.size() - 1; i >= 0; i--) {
String order = (String) m_orders.get(i);
orders[i] = mapAndAddPaths(order);
}
Expression filter = m_filter.makeExpression(this, m_bindings);
// can't start finalizing expr until all paths have been added
Expression expr = m_expr;
if (filter != null) {
expr = new com.redhat.persistence.oql.Filter(expr, filter);
}
for (int i = orders.length - 1; i >= 0; i--) {
expr = new Sort(expr, new Static(orders[i], m_bindings));
}
if (m_offset != null) {
expr = new Offset(expr, new Literal(m_offset));
}
if (m_limit != null) {
expr = new Limit(expr, new Literal(m_limit));
}
return expr;
}
private void checkCursor() {
if (m_cursor == null) {
try {
Profiler.startOp("DB");
m_cursor = execute(m_signature, makeExpr());
} catch (ProtoException e) {
throw PersistenceException.newInstance(e);
} finally {
Profiler.stopOp("DB");
}
}
}
protected Cursor execute(Signature sig, Expression expr) {
Cursor cursor = new DataSet(m_pssn, sig, expr).getCursor();
cursor.setOptions(m_options);
return cursor;
}
public boolean next() {
checkCursor();
if (m_cursor.isClosed()) {
return false;
}
int pre = getPosition();
boolean result;
try {
Profiler.startOp("DB");
result = m_cursor.next();
} catch (ProtoException e) {
throw PersistenceException.newInstance(e);
} finally {
Profiler.stopOp("DB");
}
if (result) {
if (getPosition() == m_upperBound) {
if (m_cursor.next()) {
throw new PersistenceException("cursor exceeded upper bound");
}
}
} else {
if (pre < m_lowerBound) {
throw new PersistenceException("cursor failed to meet lower bound");
}
}
return result;
}
public long size() {
try {
// can not use checkCursor() because then we can't add filters
// after calls to size
if (m_cursor == null) {
return new DataSet(m_pssn, m_signature, makeExpr()).size();
} else {
return m_cursor.getDataSet().size();
}
} catch (ProtoException e) {
throw PersistenceException.newInstance(e);
}
}
private class UnaliasMapper implements SQLParser.Mapper {
public Path map(Path path) {
return unalias(path);
}
}
private SQLParser.Mapper m_unaliaser = new UnaliasMapper();
String unalias(String expr) {
if (expr == null) {
return null;
}
StringReader reader = new StringReader(expr);
SQLParser p = getParser(s_unalias, reader, m_unaliaser);
try {
p.sql();
} catch (ParseException e) {
s_log.warn("Could not parse SQL: " + expr);
throw new IllegalArgumentException(e.getMessage());
}
return p.getSQL().toString();
}
Path unalias(Path path) {
if (s_log.isDebugEnabled()) {
s_log.debug("External Path: " + path);
s_log.debug("Aliases: " + m_aliases.toString());
}
String str = path.getPath();
final int index = str.indexOf(".link.");
if (index != -1) {
str = str.substring(0, index)
+ PDL.LINK + "." + str.substring(index + 6);
}
path = Path.get(str);
Path result = path;
for (Iterator it = m_aliases.iterator(); it.hasNext();) {
Alias alias = (Alias) it.next();
if (alias.isMatch(path)) {
if (s_log.isDebugEnabled()) {
s_log.debug("matched " + alias);
}
Path candidate = alias.unalias(path);
if (hasProperty(candidate)) {
result = candidate;
break;
}
if (s_log.isDebugEnabled()) {
s_log.debug("Candidate " + candidate + " doesn't exist.");
}
} else {
if (s_log.isDebugEnabled()) {
s_log.debug("didn't match " + alias);
}
}
}
if (s_log.isDebugEnabled()) {
s_log.debug("Internal Path: " + result);
}
return result;
}
private static class Alias {
private Path m_from;
private Path m_to;
public Alias(String from, String to) {
Assert.assertNotEmpty(from, "from");
Assert.assertNotEmpty(to, "to");
m_from = Path.get(from);
m_to = Path.get(to);
}
private static final boolean isWildcard(Path path) {
return path.getParent() == null && path.getName().equals("*");
}
public boolean isMatch(Path path) {
if (isWildcard(m_from)) {
return true;
}
if (m_from.getParent() == null) {
return m_from.equals(path);
}
while (path.getParent() != null) {
path = path.getParent();
}
return m_from.getParent().equals(path);
}
public Path unalias(Path path) {
if (isWildcard(m_from) && isWildcard(m_to)) {
return path;
} else if (isWildcard(m_from) && !isWildcard(m_to)) {
if (m_to.getParent() != null) {
return Path.add(m_to.getParent(), path);
} else {
throw new IllegalStateException(this + " " + path);
}
} else if (!isWildcard(m_from) && isWildcard(m_to)) {
return path.getRelative(m_from);
} else {
try {
return Path.add(m_to, path.getRelative(m_from));
} catch (RuntimeException e) {
throw new PersistenceException(this + " " + path, e);
}
}
}
public String toString() {
return m_from + " --> " + m_to;
}
}
}