/*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is the Kowari Metadata Store.
*
* The Initial Developer of the Original Code is Plugged In Software Pty
* Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions
* created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002
* Plugged In Software Pty Ltd. All Rights Reserved.
*
* Contributor(s): N/A.
*
* [NOTE: The text of this Exhibit A may differ slightly from the text
* of the notices in the Source Code files of the Original Code. You
* should use the text of this Exhibit A rather than the text found in the
* Original Code Source Code for Your Modifications.]
*
*/
package org.mulgara.resolver;
// Java 2 standard packages
import java.net.*;
import java.util.*;
// Logging classes
import org.apache.log4j.*;
// Locally written packages
import org.mulgara.query.*;
import org.mulgara.query.rdf.*;
import org.mulgara.resolver.spi.GlobalizeException;
import org.mulgara.resolver.spi.ResolverSession;
import org.mulgara.store.tuples.Tuples;
/**
* This wrapper doesn't just globalize columns, it also evaluates subqueries
* in aggregate-valued columns.
*
* @created 2003-12-02
*
* @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a>
* @author Andrew Newman
*
* @version $Revision: 1.10 $
*
* @modified $Date: 2005/05/06 04:07:57 $
*
* @maintenanceAuthor $Author: amuys $
*
* @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A>
*
* @copyright © 2001-2003 <A href="http://www.PIsoftware.com/">Plugged In
* Software Pty Ltd</A>
*
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
public class SubqueryAnswer extends GlobalizedAnswer {
/** Logger. This is named after the class. */
private final static Logger logger = Logger.getLogger(SubqueryAnswer.class.getName());
/** The target <code>SELECT</code> clause */
private List<? extends SelectElement> variableList;
/** The variables which are not bound */
private List<Variable> unboundVars = new ArrayList<Variable>();
/** All selected variables */
private Variable[] variables;
/** The current database session for this query. */
protected OperationContext operationContext;
/**
* Assignment property.
*
* This ought to be replaced by owl:sameIndividualAs.
*/
static final URIReferenceImpl MULGARA_IS = new URIReferenceImpl(URI.create(Mulgara.NAMESPACE+"is"));
//
// Constructor
//
/**
* Construct a wrapper around the <var>tuples</var> parameter.
*
* @param operationContext the session from which to globalize the local
* nodes within the <var>tuples</var> parameter
* @param resolverSession the session used to globalize the <var>tuples</var>
* @param tuples the resolved answer to the <code>WHERE<code> clause,
* providing variable bindings for the variables that aren't
* subqueries
* @param variableList the <code>SELECT<code> clause, including
* subquery-valued clauses to be resolved
* @throws IllegalArgumentException if <var>variableList</var> is <code>null</code>
* @throws TuplesException if it fails to get the row cardinality of the given tuples.
*/
SubqueryAnswer(OperationContext operationContext, ResolverSession resolverSession,
Tuples tuples, List<? extends SelectElement> variableList) throws TuplesException {
super(tuples, resolverSession);
this.operationContext = operationContext;
assignVariables(tuples, variableList);
}
/**
* Assigns the member variables based on the variables passed and the variable
* bindings in the tuples.
*
* @param tuples the tuples to test against the corret binding in the variable list.
* @param variableList the list of variables from the SELECT clause.
* @throws IllegalArgumentException if <var>variableList</var> is <code>null</code>
* @throws TuplesException if it fails to get the row cardinality of the given tuples.
*/
private void assignVariables(Tuples tuples, List<? extends SelectElement> variableList) throws TuplesException, IllegalArgumentException {
boolean empty = (tuples.isEmpty());
this.variableList = variableList;
this.variables = new Variable[variableList.size()];
for (int i = 0; i < variableList.size(); i++) {
SelectElement element = variableList.get(i);
if (element instanceof Variable) {
variables[i] = (Variable)element;
// Validate the variable
try {
// if (!empty && !variables[i].isBnodeVar()) tuples.getColumnIndex(variables[i]);
if (!empty) tuples.getColumnIndex(variables[i]);
} catch (TuplesException e) {
unboundVars.add(variables[i]);
if (logger.isDebugEnabled()) logger.debug(variables[i] + " does not appear in the \"tuples\" parameter");
}
} else if (element instanceof ConstantValue) {
variables[i] = ((ConstantValue) element).getVariable();
} else if (element instanceof AggregateFunction) {
variables[i] = ((AggregateFunction) element).getVariable();
} else {
throw new IllegalArgumentException("Unknown type in SELECT clause: " + element.getClass());
}
}
if (logger.isDebugEnabled()) logger.debug("Constructed for " + tuples + " around " + variableList);
}
/**
* Clone the current object. Relies on the clone from the superclass.
*/
public Object clone() {
SubqueryAnswer cloned = (SubqueryAnswer)super.clone();
cloned.variableList = this.variableList;
cloned.variables = new Variable[this.variables.length];
System.arraycopy(this.variables, 0, cloned.variables, 0, this.variables.length);
return cloned;
}
//
// Methods overriding GlobalizedAnswer's implementation of Answer
//
public int getColumnIndex(Variable variable) throws TuplesException {
if (variable == null) throw new IllegalArgumentException("Null \"variable\" parameter");
int index = variableList.indexOf(variable);
if (index >= 0) {
return index;
} else {
throw new TuplesException("No such variable " + variable + " in tuples " + variableList + " (" + getClass() + ")");
}
}
public int getNumberOfVariables() {
return variableList.size();
}
public Object getObject(int column) throws TuplesException {
if (logger.isDebugEnabled()) {
logger.debug("Getting object " + column + " from variableList " + variableList);
}
SelectElement element = variableList.get(column);
if (element instanceof Variable) {
Variable var = (Variable)element;
//if (unboundVars.contains(var) || var.isBnodeVar()) return null;
if (unboundVars.contains(var)) return null;
return super.getObject(super.getColumnIndex(var));
} else if (element instanceof ConstantValue) {
return ((ConstantValue) element).getValue();
} else if (element instanceof Count) {
// Atomic aggregate, already resolved by SelectedTuples
return super.getObject(super.getColumnIndex(((Count) element).getVariable()));
} else if (element instanceof Subquery) {
// Answer-valued aggregate, not yet resolved by SelectedTuples
try {
if (logger.isDebugEnabled()) logger.debug("Resolving Subquery in SubqueryAnswer: " + element);
return resolveSubquery((Subquery) element);
} catch (QueryException e) {
throw new TuplesException("Couldn't evaluate aggregate", e);
} catch (RuntimeException t) {
logger.error("RuntimeException thrown from resolveAggregate", t);
throw t;
}
} else {
throw new TuplesException("Unknown type in SELECT clause: " + element.getClass());
}
}
/**
* Return the column with a given name. This only applies to variables.
* @see org.mulgara.resolver.GlobalizedAnswer#getObject(java.lang.String)
* @return The bound value for the variable column with that name, or <code>null</code>
* if that variable is unbound.
* @throws TuplesException If there is no variable with that name.
*/
public Object getObject(String columnName) throws TuplesException {
for (Variable v: unboundVars) if (v.getName().equals(columnName)) return null;
for (Variable v: variables) if (v.getName().equals(columnName)) return super.getObject(super.getColumnIndex(v));
throw new TuplesException("Variable not found");
}
public Variable getVariable(int column) {
return variables[column];
}
public Variable[] getVariables() {
return (Variable[])variables.clone();
}
//
// Internal methods
//
/**
* Evaluate an aggregate-valued columns's function for the current tuples
* row.
*
* @param subquery the column aggregate function
* @throws QueryException if the <var>aggregateFunctions</var>'s embedded
* query can't be resolved
*/
private Object resolveSubquery(Subquery subquery) throws QueryException {
if (logger.isDebugEnabled()) logger.debug("Resolving subquery function " + subquery);
try {
Query query = subquery.getQuery();
Map<Variable,Value> bindings = createBindingMap(tuples, resolverSession);
ConstraintExpression where = new ConstraintConjunction(
ConstraintOperations.bindVariables(bindings, query.getConstraintExpression()),
constrainBindings(bindings));
query = new Query(query, where);
if (logger.isDebugEnabled()) logger.debug("Generated subquery: " + query);
return operationContext.doQuery(query);
} catch (Exception e) {
throw new QueryException("Failed to resolve subquery", e);
}
}
protected Map<Variable,Value> createBindingMap(Tuples tuples, ResolverSession resolverSession)
throws TuplesException, GlobalizeException {
Map<Variable,Value> bindings = new HashMap<Variable,Value>();
for (Variable var: tuples.getVariables()) {
int index = tuples.getColumnIndex(var);
if (tuples.getColumnValue(index) != Tuples.UNBOUND) {
// globalize returns a Node, but all our node implementations also implement Value
bindings.put(var, (Value)resolverSession.globalize(tuples.getColumnValue(index)));
}
}
return bindings;
}
protected ConstraintExpression constrainBindings(Map<Variable,Value> bindings) throws TuplesException {
List<ConstraintExpression> args = new ArrayList<ConstraintExpression>();
for (Map.Entry<Variable,Value> entry: bindings.entrySet()) {
args.add(new ConstraintIs(entry.getKey(), entry.getValue()));
}
return new ConstraintConjunction(args);
}
}