/*
* 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):
* getModel() contributed by Netymon Pty Ltd on behalf of
* The Australian Commonwealth Government under contract 4500507038.
*
* [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.util.HashSet;
import java.util.Set;
// Third party packages
import org.apache.log4j.Logger;
import org.jrdf.graph.Node;
// Locally written packages
import org.mulgara.query.*;
import org.mulgara.resolver.spi.LocalizeException;
import org.mulgara.resolver.spi.QueryEvaluationContext;
import org.mulgara.resolver.spi.ResolverSession;
import org.mulgara.store.tuples.LiteralTuples;
import org.mulgara.store.tuples.Tuples;
import org.mulgara.store.tuples.TuplesOperations;
/**
* This is an implementation of the walk function that begins or ends
* (anchored) from a particular subject or object.
* <p>
* Example of traversing statements directly: <p></p>
* <pre>
* select $xxx <rdfs:subClassOf> $zzz
* where trans(<b> <rdfs:subClassOf> $zzz and
* and $xxx <rdfs:subClassOf> $zzz) ;
*
* select $xxx <rdfs:subClassOf> <b>
* where trans($xxx <rdfs:subClassOf> <b> and
* and $xxx <rdfs:subClassOf> $zzz) ;
* </pre></p>
*
* @created 2004-06-07
*
* @author Andrew Newman
*
* @version $Revision: 1.9 $
*
* @modified $Date: 2005/02/22 08:16:10 $
*
* @maintenanceAuthor $Author: newmana $
*
* @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A>
*
* @copyright © 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 abstract class WalkFunction {
/**
* Logger.
*/
private static final Logger logger = Logger.getLogger(WalkFunction.class.getName());
/**
* Resolves a walk constraint. This means finding all statements matching
* the predicate statement and returning them.
*
* @param predConstraint a constraint describing a predicate to traverse with
* a supported model type fourth element of the constraint is not a Variable
* or is a Variable with the name _from.
* @param graphExpression the Graph to resolve the constraint against if the
* fourth element of the constraint is a Variable with the name _from.
* @return the answer satisfying the <var>constraint</var>
* @throws QueryException if the <var>constraint</var> has a model of an
* unsupported type, or if the answer otherwise couldn't be determined
* @throws TuplesException If there was a problem while moving around in results
* from various query resolutions.
*/
public static Tuples walk(QueryEvaluationContext query, WalkConstraint predConstraint,
GraphExpression graphExpression, ResolverSession session)
throws QueryException, TuplesException {
if (logger.isDebugEnabled()) {
logger.debug("Executing walk constraint");
}
if (!(predConstraint.getAnchoredConstraint().getElement(1) instanceof Value)) {
throw new QueryException("Predicate in walk function, " +
predConstraint.getAnchoredConstraint().getElement(1) +
", currently must be bound to a value.");
}
// Either the subject is a variable and the object is a constant or
// vice-versa in the first constraint
if (((predConstraint.getAnchoredConstraint().getElement(0) instanceof Variable) &&
!(predConstraint.getAnchoredConstraint().getElement(2) instanceof Value)) ||
((predConstraint.getAnchoredConstraint().getElement(0) instanceof Value) &&
!(predConstraint.getAnchoredConstraint().getElement(2) instanceof Variable))) {
throw new QueryException(
"The subject: " + predConstraint.getAnchoredConstraint().getElement(0) +
" and the object: " + predConstraint.getAnchoredConstraint().getElement(2) +
" are invalid, one must be a variable and the other a fixed value" +
" around a predicate.");
}
// Transitive constraint must be a fixed predicate
if (!(predConstraint.getUnanchoredConstraint().getElement(1) instanceof Value)) {
throw new QueryException("Predicate in transitive function, " +
predConstraint.getElement(1) + ", currently must be bound to a value.");
}
// The subject and object must be variables.
if (!((predConstraint.getUnanchoredConstraint().getElement(0) instanceof Variable) &&
(predConstraint.getUnanchoredConstraint().getElement(2) instanceof Variable))) {
throw new QueryException(
"The subject: " + predConstraint.getUnanchoredConstraint().getElement(0) +
" and the object: " + predConstraint.getUnanchoredConstraint().getElement(2) +
" are invalid, both must be variables.");
}
Tuples initialTuples = null;
Tuples predTuples = null;
boolean success = false;
try {
// ask for all statements for this predicate
initialTuples = query.resolve(graphExpression, predConstraint.getAnchoredConstraint());
// prepare to iterate through anchor
initialTuples.beforeFirst();
boolean tuplesAvailable = initialTuples.next();
// if nothing matches, then there is no work to do
if (!tuplesAvailable) {
return initialTuples;
}
// The value and variable in the anchored constraint
Long value;
Variable anchoredConstraintVariable;
Variable tmpVariable;
// construct a constraint without the fixed anchor
Constraint openConstraint;
Constraint anchoredConstraint = predConstraint.getAnchoredConstraint();
LiteralTuples inferredResult;
if ( logger.isInfoEnabled() ) {
logger.info("Anchored constraint = " + anchoredConstraint);
}
// determine if this is a forward anchor or a back anchor
try {
if (anchoredConstraint.getElement(0) instanceof Variable) {
// back anchor [$x predicate anchor]
ConstraintElement element = anchoredConstraint.getElement(2);
if (element instanceof LocalNode) {
value = new Long(((LocalNode)element).getValue());
} else {
value = new Long(session.lookup((Node)element));
}
anchoredConstraintVariable = (Variable) anchoredConstraint.getElement(0);
// Create a temporary second variable by appending _ to the first variable name
tmpVariable = new Variable(anchoredConstraintVariable.getName() + "_");
// build the constraint with the temporary variable name
openConstraint = new ConstraintImpl(anchoredConstraintVariable,
anchoredConstraint.getElement(1), tmpVariable,
anchoredConstraint.getModel());
// set up the final result built from the second constraint
inferredResult = new LiteralTuples(new Variable[] {
(Variable) predConstraint.getUnanchoredConstraint().getElement(2),
(Variable) predConstraint.getUnanchoredConstraint().getElement(0)
});
} else {
// forward anchor [anchor predicate $x]
ConstraintElement element = anchoredConstraint.getElement(0);
if (element instanceof LocalNode) {
value = new Long(((LocalNode)element).getValue());
} else {
value = new Long(session.lookup((Node)element));
}
anchoredConstraintVariable = (Variable) anchoredConstraint.getElement(2);
// Create a temporary second variable by appending _ to the first variable name
tmpVariable = new Variable(anchoredConstraintVariable.getName() + "_");
// build the constraint with the temporary variable name
openConstraint = new ConstraintImpl(tmpVariable,
anchoredConstraint.getElement(1), anchoredConstraintVariable,
anchoredConstraint.getModel());
// set up the final result built from the second constraint
inferredResult = new LiteralTuples(new Variable[] {
(Variable) predConstraint.getUnanchoredConstraint().getElement(0),
(Variable) predConstraint.getUnanchoredConstraint().getElement(2)
});
}
} catch (LocalizeException el) {
throw new QueryException("Failed to localize walk component", el);
}
if (logger.isDebugEnabled()) {
logger.debug("Variable has name: " +
anchoredConstraintVariable.getName() + "; Value=" + value);
}
// find *all* statements with the given predicate
predTuples = query.resolve(graphExpression, openConstraint);
// remember that the current value has been visited
Set<Long> visited = new HashSet<Long>();
visited.add(value);
// Get object/subject node value
long subject = value.longValue();
// set up given statement for inferences
LiteralTuples given = new LiteralTuples(new Variable[] {tmpVariable});
given.appendTuple(new long[] { subject });
// start inferring for the current subject, using this first set of objects
// modifies the inferredResult value for us.
walkStatements(predTuples, given, visited, inferredResult);
if (logger.isDebugEnabled()) {
logger.debug("Finished all inferencing");
}
success = true;
return inferredResult;
} finally {
// clean up tuples
try {
try {
if (initialTuples != null) initialTuples.close();
} finally {
if (predTuples != null) predTuples.close();
}
} catch (TuplesException e) {
if (success) throw e; // Everything worked up to this point, re-throw this one.
else logger.info("Suppressing exception closing failed tuples", e); // Log and ignore redundant exception.
}
}
}
/**
* This traverses statements from the <code>given</code> tuples which
* consists of a single subject node. It then returns existing tuples
* into the inferredResult via iteration.
*
* @param initialTuples All existing statements in the model which have the
* required predicate.
* @param given The current base of the chain, as single named column of the
* starting subject. Closed by this method.
* @param visited A set of all nodes which have been inferred from for
* subject in the given clause. This is to prevent loops.
* @param inferredResult An in/out parameter which holds all the inferred
* statements so far. This method will append to it.
* @throws TuplesException If there was a problem while moving around in
* results of the query resolutions.
* @throws QueryException If there was a problem while moving around in
* results of the query resolutions.
* @throws StatementStoreException If there was a problem while moving
* around in results of the query resolutions.
*/
public static void walkStatements(Tuples initialTuples,
Tuples given, Set<Long> visited, LiteralTuples inferredResult)
throws TuplesException, QueryException {
assert initialTuples.getNumberOfVariables() == 2;
assert given.getNumberOfVariables() == 1;
assert inferredResult.getNumberOfVariables() == 2;
// get out the column variable being used by given
Variable givenColumn = given.getVariables()[0];
// Print out debugging information of given and initial tuples.
if (logger.isDebugEnabled()) {
logger.debug("Given is on column: " + givenColumn.getName());
initialTuples.beforeFirst();
initialTuples.next();
do {
logger.debug("Initial tuples: " + initialTuples.getColumnValue(0) +
"," + initialTuples.getColumnValue(1));
} while (initialTuples.next());
initialTuples.beforeFirst();
Variable[] vars = initialTuples.getVariables();
String varnames = "";
for (int v = 0; v < vars.length; v++) {
varnames += " " + vars[v];
}
logger.debug("InitialTuples has columns: " + varnames);
}
LiteralTuples newGiven = null;
// Each iteration of this loop steps forward in the inference chain
do {
// join the current "given" on all the predicates
Tuples joinResult = TuplesOperations.join(given, initialTuples);
// Print out given debugging information.
if (logger.isDebugEnabled()) {
given.beforeFirst();
String gs = "";
while (given.next()) gs += " " + given.getColumnValue(0);
logger.debug("Joined with a given of: " + gs);
}
boolean success = false;
try {
// check if there is any data from the join
joinResult.beforeFirst();
if (!joinResult.next()) {
break;
}
// determine the column numbers for the variables
int[] tuplesIndex = new int[2];
tuplesIndex[0] = joinResult.getColumnIndex(givenColumn);
tuplesIndex[1] = 1 - tuplesIndex[0];
// Create a new given tuples, with the same column variable as the original
newGiven = new LiteralTuples(new Variable[] {
givenColumn
});
// Iterate through all the results, we are already on the first line
do {
// get the object
long object = joinResult.getColumnValue(tuplesIndex[1]);
Long objectL = new Long(object);
if (logger.isDebugEnabled()) {
long s = joinResult.getColumnValue(tuplesIndex[0]);
logger.debug("** found subject=" + s + "; object=" + object);
}
// Add existing s, o or o, s from initial tuples.
inferredResult.appendTuple(new long[] {
joinResult.getColumnValue(tuplesIndex[0]), object
});
// nothing more to do with this object if we have already seen it
if (visited.contains(objectL)) {
continue;
}
// add object to the new given
newGiven.appendTuple(new long[] {
object
});
// tell the visited set that we have been here
visited.add(objectL);
}
while (joinResult.next());
success = true;
}
finally {
// clean up the tuples objects, either by falling through, or from the break above
try {
try {
if (joinResult != null) joinResult.close();
} finally {
if (given != null) given.close();
}
} catch (TuplesException e) {
if (success) throw e; // Everything worked up to this point, re-throw this one.
else logger.info("Suppressing exception closing failed tuples", e); // Log and ignore redundant exception.
}
}
// use this new given, and iterate
given = newGiven;
}
while (newGiven.getRowCardinality() != Cursor.ZERO);
// close the empty tuples if it exists
if (newGiven != null) {
newGiven.close();
}
}
}