/*
* 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):
* SymbolicTransformationContext 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.net.URI;
import java.net.URISyntaxException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
// Third party packages
import org.apache.log4j.Logger;
import org.jrdf.graph.Node;
import org.jrdf.graph.URIReference;
// Local packages
import org.mulgara.query.*;
import org.mulgara.query.rdf.URIReferenceImpl;
import org.mulgara.resolver.spi.DatabaseMetadata;
import org.mulgara.resolver.spi.GlobalizeException;
import org.mulgara.resolver.spi.LocalizeException;
import org.mulgara.resolver.spi.Resolution;
import org.mulgara.resolver.spi.Resolver;
import org.mulgara.resolver.spi.ResolverFactory;
import org.mulgara.resolver.spi.ResolverFactoryException;
import org.mulgara.resolver.spi.SecurityAdapter;
import org.mulgara.resolver.spi.Statements;
import org.mulgara.resolver.spi.SumOfProductExpansionTransformer;
import org.mulgara.resolver.spi.SymbolicTransformation;
import org.mulgara.resolver.spi.SymbolicTransformationContext;
import org.mulgara.resolver.spi.SystemResolver;
import org.mulgara.resolver.spi.SystemResolverFactory;
import org.mulgara.resolver.spi.TuplesWrapperStatements;
import org.mulgara.resolver.view.ViewMarker;
import org.mulgara.resolver.view.SessionView;
import org.mulgara.store.tuples.Tuples;
import org.mulgara.store.tuples.TuplesOperations;
/**
* Services provided by {@link DatabaseSession} to invocations of the
* {@link Operation#execute} method.
*
* @created 2004-11-08
* @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a>
* @version $Revision: 1.10 $
* @modified $Date: 2005/05/02 20:07:56 $ by $Author: raboczi $
* @company <a href="mailto:info@PIsoftware.com">Plugged In Software</a>
* @copyright ©2004 <a href="http://www.tucanatech.com/">Tucana
* Technology, Inc</a>
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
class DatabaseOperationContext implements OperationContext, SessionView, SymbolicTransformationContext {
/** Logger. */
private static final Logger logger = Logger.getLogger(DatabaseOperationContext.class.getName());
/** Logger for {@link SymbolicTransformation} plugins. */
private static final Logger symbolicLogger =
Logger.getLogger(DatabaseOperationContext.class.getName() + "#symbolic");
/**
* The graphs from external resolvers which have been cached as temporary graphs.
* Every graph in this set can be manipulated by resolvers from the {@link #temporaryResolverFactory}.
*/
private final Set<LocalNode> cachedGraphSet;
/**
* The graphs from external resolvers which have been cached as temporary graphs and modified.
* Every graph in this set can be manipulated by resolvers from the {@link #temporaryResolverFactory}.
*/
private final Set<LocalNode> changedCachedGraphSet;
/**
* A map from {@link URI}s of graphs to {@link LocalNode}s representing the localized type of the graph.
* This is populated by {@link #findGraphTypeURI} and cleared by clear()
*/
private final Map<LocalNode,URI> systemGraphCacheMap = new WeakHashMap<LocalNode,URI>();
/** Resolver used for accessing the system graph (<code>#</code>). */
protected SystemResolverFactory systemResolverFactory;
protected SystemResolver systemResolver;
/** The transaction associated with these operations */
private MulgaraTransaction transaction;
// Immutable properties of the containing DatabaseSession
private final Set<ResolverFactory> cachedResolverFactorySet; // NOTE: Currently unused
private final Map<ResolverFactory,Resolver> enlistedResolverMap;
private final Map<String,ResolverFactory> externalResolverFactoryMap;
// Hold a specific type of ResolverFactory to restrict what should be in there
private final Map<URI,InternalResolverFactory> internalResolverFactoryMap;
private final DatabaseMetadata metadata;
private final List<SecurityAdapter> securityAdapterList;
private final URI temporaryGraphTypeURI;
private final ResolverFactory temporaryResolverFactory;
/** Symbolic transformations this instance should apply. */
private final List<SymbolicTransformation> symbolicTransformationList;
private final boolean isWriting;
// Used as a set, all values are null. Java doesn't provide a WeakHashSet.
private WeakHashMap<TransactionalAnswer,Object> answers;
DatabaseOperationContext(Set<ResolverFactory> cachedResolverFactorySet,
Map<String,ResolverFactory> externalResolverFactoryMap,
Map<URI,InternalResolverFactory> internalResolverFactoryMap,
DatabaseMetadata metadata,
List<SecurityAdapter> securityAdapterList,
URI temporaryGraphTypeURI,
ResolverFactory temporaryResolverFactory,
List<SymbolicTransformation> symbolicTransformationList,
SystemResolverFactory systemResolverFactory,
boolean isWriting)
{
assert cachedResolverFactorySet != null;
assert externalResolverFactoryMap != null;
assert internalResolverFactoryMap != null;
assert metadata != null;
assert securityAdapterList != null;
assert temporaryGraphTypeURI != null;
assert temporaryResolverFactory != null;
assert symbolicTransformationList != null;
assert systemResolverFactory != null;
this.cachedResolverFactorySet = cachedResolverFactorySet;
this.externalResolverFactoryMap = externalResolverFactoryMap;
this.internalResolverFactoryMap = internalResolverFactoryMap;
this.metadata = metadata;
this.securityAdapterList = securityAdapterList;
this.temporaryGraphTypeURI = temporaryGraphTypeURI;
this.temporaryResolverFactory = temporaryResolverFactory;
this.symbolicTransformationList = symbolicTransformationList;
this.isWriting = isWriting;
this.systemResolverFactory = systemResolverFactory;
this.cachedGraphSet = new HashSet<LocalNode>();
this.changedCachedGraphSet = new HashSet<LocalNode>();
this.enlistedResolverMap = new HashMap<ResolverFactory,Resolver>();
this.answers = new WeakHashMap<TransactionalAnswer,Object>();
}
//
// Methods implementing OperationContext
//
public ResolverFactory findModelResolverFactory(long graph) throws QueryException {
if (logger.isDebugEnabled()) logger.debug("Finding resolver factory for graph " + graph);
// See if the graph is an internal one, with a graph type
try {
URI graphTypeURI = findGraphTypeURI(graph);
if (graphTypeURI != null) {
// The graph had a type recorded in the system graph, so it's internal
if (logger.isDebugEnabled()) {
logger.debug("Graph " + graph + " type is " + graphTypeURI);
}
InternalResolverFactory internalResolverFactory = internalResolverFactoryMap.get(graphTypeURI);
if (internalResolverFactory == null) {
throw new QueryException("Unsupported graph type for graph " + graph); }
return internalResolverFactory;
} else {
// This might be an external graph or an aliased internal graph.
// get the graph URI
Node graphNode = systemResolver.globalize(graph);
if (!(graphNode instanceof URIReference)) {
throw new QueryException(graphNode.toString() + " is not a valid Graph");
}
URI graphURI = ((URIReference)graphNode).getURI();
// check if this is really a reference to a local graph, using a different server name
Node aliasedNode = getCanonicalAlias(graphURI);
if (aliasedNode != null) {
long aliasedGraph = systemResolver.localize(aliasedNode);
// In some situations, a canonical alias can localize back to the original graph node.
if (aliasedGraph != graph) {
return findModelResolverFactory(aliasedGraph);
}
}
// test the graph URI against the current server
try {
if (logger.isDebugEnabled()) {
logger.debug("Comparing " + metadata.getURI().toString() + " to " + (new URI(graphURI.getScheme(),
graphURI.getSchemeSpecificPart(), null)).toString());
}
// Check all the hostname aliases to see if we're attempting to
// contact the local server.
URI tmpGraphName = new URI(graphURI.getScheme(),
graphURI.getSchemeSpecificPart(), null);
String host = tmpGraphName.getHost();
// Ensure that the host name can be extracted - in case there's an
// opaque hostname.
if (tmpGraphName.isOpaque()) {
throw new QueryException("Graph not in local storage, and not able to be found with the \"" + graphURI.getScheme() + "\" scheme: <" + tmpGraphName + ">");
}
// Do not test for locality if jar or file protocol
if (!(graphURI.getScheme().startsWith("file")) &&
!(graphURI.getScheme().startsWith("jar"))) {
// Check that it's the same host name and server name.
if ((metadata.getHostnameAliases().contains(host)) &&
(metadata.getServerName().equals(metadata.getServerName(graphURI)))) {
// should be on the current server, but was not found here
throw new QueryException(graphNode.toString() +
" has a URI indicating the local server, but was not found");
}
}
} catch (URISyntaxException use) {
throw new QueryException("Internal error. Graph URI cannot be manipulated.");
}
// This is not a local graph, get the protocol
String graphProtocol = findProtocol(graph);
if (logger.isDebugEnabled()) {
logger.debug("Graph " + graph + " protocol is " + graphProtocol);
}
// find the factory for this protocol
ResolverFactory resolverFactory = externalResolverFactoryMap.get(graphProtocol);
if (resolverFactory == null) {
throw new QueryException(
"Graph <" + graph + "> was not found locally, and the \"" + graphProtocol + "\" protocol is unsupported");
}
// For the moment, not applying caching to any external graphs
// TODO: add a method to ResolverFactory interface to test whether
// caching is appropriate for that particular implementation
if (cachedResolverFactorySet.contains(resolverFactory)) {
return new CacheResolverFactory(resolverFactory,
temporaryResolverFactory,
temporaryGraphTypeURI,
cachedGraphSet,
changedCachedGraphSet);
} else {
return resolverFactory;
}
}
} catch (GlobalizeException eg) {
throw new QueryException("Unable to globalize graph type", eg);
} catch (LocalizeException el) {
throw new QueryException("Unable to localize graph", el);
}
}
/**
* Find a cached resolver factory for write back.
*
* @return a completely unwrapped resolver factory
*/
// TODO: Common code with findModelResolverFactory should be consolidated.
private ResolverFactory findResolverFactory(long graph) throws QueryException {
if (logger.isDebugEnabled()) logger.debug("Finding raw resolver factory for graph " + graph);
try {
// get the graph URI
Node graphNode = systemResolver.globalize(graph);
if (!(graphNode instanceof URIReference)) {
throw new QueryException(graphNode.toString() + " is not a valid Graph");
}
URI graphURI = ((URIReference)graphNode).getURI();
// test the graph URI against the current server
try {
if (logger.isDebugEnabled()) {
logger.debug("Comparing " + metadata.getURI().toString() + " to "
+ (new URI(graphURI.getScheme(), graphURI.getSchemeSpecificPart(), null)).toString());
}
if (metadata.getURI().equals(
new URI(graphURI.getScheme(), graphURI.getSchemeSpecificPart(), null))) {
// should be on the current server, but was not found here
throw new QueryException(graphNode.toString() + " is not a Graph");
}
} catch (URISyntaxException use) {
throw new QueryException("Internal error. Graph URI cannot be manipulated.");
}
// This is not a local graph, get the protocol
String graphProtocol = findProtocol(graph);
if (logger.isDebugEnabled()) {
logger.debug("Graph " + graph + " protocol is " + graphProtocol);
}
// find the factory for this protocol
ResolverFactory resolverFactory = externalResolverFactoryMap.get(graphProtocol);
if (resolverFactory == null) {
throw new QueryException(
"Unsupported protocol for destination graph (" +
graphProtocol + ", " + graph + " : '" + graphProtocol + "')");
}
return resolverFactory;
} catch (GlobalizeException eg) {
throw new QueryException("Unable to globalize graph type", eg);
}
}
public ResolverFactory findModelTypeResolverFactory(URI graphTypeURI) throws QueryException {
return internalResolverFactoryMap.get(graphTypeURI);
}
public List<SecurityAdapter> getSecurityAdapterList() {
return securityAdapterList;
}
public Resolver obtainResolver(ResolverFactory resolverFactory) throws QueryException {
// Obtain a resolver
Resolver resolver = enlistedResolverMap.get(resolverFactory);
if (resolver != null) {
return resolver;
}
try {
resolver = resolverFactory.newResolver(isWriting, systemResolver, systemResolver);
// FIXME: This is a kludge. This should be done using a query rewriting
// hook in the ResolverFactory interface. This hook is also
// required for efficient evaluation of XSD/Type constraints
// (specifically intervals), and distributed queries
// (specificially appended joins).
if (resolver instanceof ViewMarker) {
((ViewMarker) resolver).setSession(this);
}
} catch (ResolverFactoryException e) {
throw new QueryException("Unable to obtain resolver", e);
}
assert resolver != null;
try {
transaction.enlist(resolver);
} catch (Exception e) {
logger.warn("Failed to enlist resolver, aborting resolver");
resolver.abort();
throw new QueryException("Unable to enlist " + resolver + " into transaction", e);
}
enlistedResolverMap.put(resolverFactory, resolver);
return resolver;
}
/**
* Returns the canonical form of the graph, leaving the graph alone if it is recognised or unknown.
*
* @param graph The graph to check.
* @return The new graph node, or the current graph if it is already canonical or unknown.
*/
public long getCanonicalModel(long graph) {
// globalize to a URI
try {
Node graphNode = systemResolver.globalize(graph);
if (!(graphNode instanceof URIReference)) {
logger.warn(graphNode.toString() + " is not a valid Graph");
return graph;
}
URI graphURI = ((URIReference)graphNode).getURI();
// check if this is really a reference to a local graph, using a different server name
Node aliasedNode = getCanonicalAlias(graphURI);
if (aliasedNode != null) {
return systemResolver.localize(aliasedNode);
}
} catch (Exception e) {
// unable to get a canonical form, so leave this graph alone
}
// graph was not recognised as being on this server, so leave it alone
return graph;
}
//
// Methods required by SymbolicTransformationContext
//
public URI mapToModelTypeURI(URI graphURI) throws QueryException {
try {
if (logger.isDebugEnabled()) logger.debug("Finding graphTypeURI for " + graphURI);
long rawGraph = systemResolver.localize(new URIReferenceImpl(graphURI, false));
long canGraph = getCanonicalModel(rawGraph);
URI graphTypeURI = findGraphTypeURI(canGraph);
if (logger.isDebugEnabled()) {
logger.debug("Mapped " + graphURI + " via " + rawGraph + ":" + canGraph + " to GraphTypeURI: " + graphTypeURI);
}
return graphTypeURI;
} catch (GlobalizeException eg) {
throw new QueryException("Failed to map graph to graphType", eg);
} catch (LocalizeException el) {
throw new QueryException("Failed to map graph to graphType", el);
}
}
//
// Internal methods
//
/**
* Find the type of a graph.
*
* @param graph the local node of a graph
* @return the local node representing the type of the <var>graph</var>, or
* {@link org.mulgara.store.nodepool.NodePool#NONE} if the <var>graph</var>
* isn't stored within the system
* @throws QueryException if the graph type can't be determined
*/
private URI findGraphTypeURI(long graph) throws QueryException, GlobalizeException {
// If graph is a query-node, graph cannot exist in the system graph so return null.
if (graph < 0) return null;
// Check our cached version of the system graph
LocalNode graphLocalNode = new LocalNode(graph);
URI graphTypeURI = systemGraphCacheMap.get(graphLocalNode);
if (graphTypeURI != null) {
return graphTypeURI;
}
// Query the system graph for the type of the graph
Variable graphTypeVariable = new Variable("graphType");
Constraint graphConstraint =
new ConstraintImpl(new LocalNode(graph),
new LocalNode(metadata.getRdfTypeNode()),
graphTypeVariable,
new LocalNode(metadata.getSystemModelNode()));
Resolution resolution = systemResolver.resolve(graphConstraint);
assert resolution != null;
// Check the solution and extract the graph type (if any) from it
try {
resolution.beforeFirst();
if (resolution.next()) {
long graphType = resolution.getColumnValue(
resolution.getColumnIndex(graphTypeVariable));
if (resolution.next()) {
throw new QueryException("Graph " + graph + " has more than one type!");
}
Node graphNode = systemResolver.globalize(graphType);
assert graphNode instanceof URIReferenceImpl;
graphTypeURI = ((URIReferenceImpl) graphNode).getURI();
systemGraphCacheMap.put(graphLocalNode, graphTypeURI);
return graphTypeURI;
} else {
return null;
}
} catch (TuplesException e) {
throw new QueryException("Unable to determine graph type of " + graph, e);
} finally {
if ( resolution != null ) {
try {
resolution.close();
} catch (TuplesException e) {
logger.warn("Unable to close find graph type resolution to graph " + graph, e);
}
}
}
}
/**
* @param n the local node corresponding to the URI reference
* @return the scheme part of the <var>node</var>'s URI reference
* @throws QueryException if the <var>node</var> can't be globalized or
* isn't a URI reference
*/
private String findProtocol(long n) throws QueryException {
try {
// Globalize the node
Node node = (Node) systemResolver.globalize(n);
if (!(node instanceof URIReference)) {
throw new QueryException(node + " is not a URI reference");
}
if (logger.isDebugEnabled()) {
logger.debug("Graph URI for graph " + n + " is " + node);
}
// Return the protocol
return ((URIReference) node).getURI().getScheme();
} catch (GlobalizeException e) {
throw new QueryException("Unable to globalize node " + n, e);
}
}
/**
* Check if the given graph actually refers to a graph on the local server.
*
* @param graphURI The URI of the graph being searched for.
* @return The Node for the local graph, or <code>null</code> if not found.
* @throws QueryException When the graph URI cannot be manipulated.
*/
private Node getCanonicalAlias(URI graphURI) throws QueryException {
if (logger.isDebugEnabled()) {
logger.debug("Checking for an alias on: " + graphURI);
}
// extract the host name
String host = graphURI.getHost();
if (host == null) {
return null;
}
// Check if this host has been heard of before
if (metadata.getHostnameAliases().contains(host)) {
// this name is acceptable, so leave it alone
return null;
}
// Check with a DNS server to see if this host is recognised
InetAddress addr = null;
try {
addr = InetAddress.getByName(host);
} catch (UnknownHostException uhe) {
// The host was unknown, so allow resolution to continue as before
return null;
}
// check the various names against known aliases
if (metadata.getHostnameAliases().contains(addr.getHostName()) ||
metadata.getHostnameAliases().contains(addr.getCanonicalHostName()) ||
metadata.getHostnameAliases().contains(addr.getHostAddress())) {
// change the host name to one that is recognised
// Make sure that it doesn't canonicalize to the original URI; this could cause an infinite loop.
URI newGraphURI = getLocalURI(graphURI);
if (!graphURI.equals(newGraphURI)) {
return new URIReferenceImpl(graphURI);
}
}
// not found, so return nothing
return null;
}
/**
* Convert a URI to a URIReference which refers to the canonical local machine name.
*
* @param uri The URI to update.
* @return The URIReference representing the same URI as the parameter, with the host name updated.
* @throws QueryException When the uri cannot be manipulated.
*/
private URI getLocalURI(URI uri) throws QueryException {
// use the system graph to find the local host name
String newHost = metadata.getSystemModelURI().getHost();
// update the URI
try {
URI newGraphURI = new URI(uri.getScheme(), uri.getUserInfo(), newHost, uri.getPort(),
uri.getPath(), uri.getQuery(), uri.getFragment());
logger.debug("Changing graph URI from " + uri + " to " + newGraphURI);
return newGraphURI;
} catch (URISyntaxException e) {
throw new QueryException("Internal error. Graph URI cannot be manipulated.");
}
}
/**
* Resolve a localized constraint into the tuples which satisfy it.
*
* This method must be called within a transactional context.
*
* Will be made package-scope as soon as the View kludge is resolved.
*
* Deprecation warning removed to assist development.
*
* @param constraint a localized constraint
* @return the tuples satisfying the <var>constraint</var>
* @throws IllegalArgumentException if <var>constraint</var> is
* <code>null</code>
* @throws QueryException if the <var>constraint</var> can't be resolved
*/
public Tuples resolve(Constraint constraint) throws QueryException {
if (logger.isDebugEnabled()) {
logger.debug("Resolving " + constraint);
}
// Validate "constraint" parameter
if (constraint == null) {
throw new IllegalArgumentException("Null \"constraint\" parameter");
}
ConstraintElement graphElem = constraint.getModel();
if (graphElem instanceof Variable) {
return resolveVariableGraph(constraint);
} else if (graphElem instanceof LocalNode) {
long graph = ((LocalNode) graphElem).getValue();
long realGraph = getCanonicalModel(graph);
// Make sure security adapters are satisfied
for (SecurityAdapter securityAdapter: securityAdapterList) {
// Lie to the user
if (!securityAdapter.canSeeModel(realGraph, systemResolver)) {
try {
throw new QueryException("No such graph " + systemResolver.globalize(realGraph));
} catch (GlobalizeException e) {
logger.warn("Unable to globalize graph " + realGraph);
throw new QueryException("No such graph");
}
}
}
for (SecurityAdapter securityAdapter: securityAdapterList) {
// Tell a different lie to the user
if (!securityAdapter.canResolve(realGraph, systemResolver)) {
return TuplesOperations.empty();
}
}
// if the graph was changed then update the constraint
if (graph != realGraph) {
constraint = ConstraintOperations.rewriteConstraintModel(new LocalNode(realGraph), constraint);
}
// Evaluate the constraint
Tuples result = obtainResolver(findModelResolverFactory(realGraph)).resolve(constraint);
assert result != null;
return result;
} else {
throw new QueryException("Non-localized graph in resolve: " + graphElem);
}
}
/**
* Resolve a {@link Constraint} in the case where the graph isn't fixed.
*
* This is mostly relevant in the case where the <code>in</code> clause takes
* a variable parameter. It's tricky to resolve because external graphs may
* be accessible to the system, but aren't known to it unless they're named.
* The policy we take is to only consider internal graphs.
*
* @param constraint a constraint with a {@link Variable}-valued graph
* element, never <code>null</code>
* @return the solutions to the <var>constraint</var> occurring in all
* internal graphs, never <code>null</code>
* @throws QueryException if the solution can't be evaluated
*/
private Tuples resolveVariableGraph(Constraint constraint) throws QueryException {
assert constraint != null;
assert constraint.getElement(3) instanceof Variable;
Tuples tuples = TuplesOperations.empty();
// This is the alternate code we'd use if we were to consult external
// graphs as well as internal graphs during the resolution of variable IN
// clauses:
//
//Iterator i = resolverFactoryList.iterator();
for (ResolverFactory resolverFactory: internalResolverFactoryMap.values()) {
assert resolverFactory != null;
// Resolve the constraint
Resolver resolver = obtainResolver(resolverFactory);
if (logger.isDebugEnabled()) {
logger.debug("Resolving " + constraint + " against " + resolver);
}
Resolution resolution = resolver.resolve(constraint);
assert resolution != null;
try {
// If this is a complete resolution of the constraint, we won't have to
// consider any of the other resolvers
if (resolution.isComplete()) {
if (logger.isDebugEnabled()) {
logger.debug("Returning complete resolution from " + resolver);
}
tuples.close();
return resolution;
} else {
// Append the resolution to the overall solutions
if (logger.isDebugEnabled()) {
logger.debug("Appending " + resolver);
}
Tuples oldTuples = tuples;
try {
tuples = TuplesOperations.append(tuples, resolution);
} finally {
resolution.close();
}
oldTuples.close();
}
} catch (TuplesException e) {
throw new QueryException("Unable to resolve " + constraint, e);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Resolved " + constraint + " to " +
TuplesOperations.formatTuplesTree(tuples));
}
return tuples;
}
Tuples innerCount(Query query) throws QueryException {
if (query == null) {
throw new IllegalArgumentException("Null \"query\" parameter");
}
if (logger.isInfoEnabled()) {
logger.info("Inner Count: " + query);
}
try {
query = transform(query);
LocalQueryResolver lq = new LocalQueryResolver(this, systemResolver);
Tuples result = lq.resolveE(query);
query.close();
return result;
} catch (QueryException eq) {
throw eq;
} catch (Exception e) {
throw new QueryException("Failed to evaluate count", e);
}
}
protected void doModify(URI graphURI, Statements statements, boolean insert) throws Throwable {
long graph = systemResolver.localize(new URIReferenceImpl(graphURI));
graph = getCanonicalModel(graph);
// Make sure security adapters are satisfied
for (SecurityAdapter securityAdapter: securityAdapterList) {
// Lie to the user
if (!securityAdapter.canSeeModel(graph, systemResolver)) {
throw new QueryException("No such graph " + graphURI);
}
// Tell the truth to the user
if (!securityAdapter.canModifyModel(graph, systemResolver)) {
throw new QueryException("You aren't allowed to modify " + graphURI);
}
}
// Obtain a resolver for the destination graph type
Resolver resolver = obtainResolver(findModelResolverFactory(graph));
assert resolver != null;
if (logger.isDebugEnabled()) {
logger.debug("Modifying " + graphURI + " using " + resolver);
}
resolver.modifyModel(graph, statements, insert);
if (logger.isDebugEnabled()) {
logger.debug("Modified " + graphURI);
}
}
public Answer doQuery(Query query) throws Exception {
Answer result;
query = transform(query);
LocalQueryResolver localQuery = new LocalQueryResolver(this, systemResolver);
// Complete the numerical phase of resolution
Tuples tuples = localQuery.resolveE(query);
if (query instanceof AskQuery) {
// strip the answer down to true/false
result = new BooleanAnswer(tuples.getRowCardinality() != 0);
} else {
result = new TransactionalAnswer(transaction, new SubqueryAnswer(this, systemResolver, tuples, query.getVariableList()));
answers.put((TransactionalAnswer)result, null);
// check if the query was a CONSTRUCT, and wrap in a graph filter if needed
if (query instanceof ConstructQuery) result = new GraphAnswer(result);
}
tuples.close();
return result;
}
/**
* Apply the registered transformations to the query until we reach a
* fixed-point.
*/
Query transform(Query query) throws Exception {
// Start with the symbolic phase of resolution
if (symbolicLogger.isDebugEnabled()) {
symbolicLogger.debug("Before transformation: " + query);
}
MutableLocalQueryImpl mutable = new MutableLocalQueryImpl(query);
List<SymbolicTransformation> symTxList;
symTxList = symbolicTransformationList;
// non-DISTINCT queries need to be expanded so the disjunction is at the root
if (!query.isDistinct()) {
symTxList = new ArrayList<SymbolicTransformation>(symbolicTransformationList);
symTxList.add(new SumOfProductExpansionTransformer());
}
Iterator<SymbolicTransformation> i = symTxList.iterator();
while (i.hasNext()) {
SymbolicTransformation symbolicTransformation = i.next();
assert symbolicTransformation != null;
symbolicTransformation.transform(this, mutable);
// When a transformation succeeds, we rewind and start from the
// beginning of the symbolicTransformationList again
if (mutable.isModified()) {
if (symbolicLogger.isDebugEnabled()) {
symbolicLogger.debug("Symbolic transformation: " + mutable);
}
Query tmp = query;
query = new Query(query, mutable.getConstraintExpression());
tmp.close();
mutable = new MutableLocalQueryImpl(query);
// start again
i = symTxList.iterator();
}
}
return query;
}
void clear() throws QueryException {
Throwable error = null;
try {
for (TransactionalAnswer answer : answers.keySet()) {
try {
answer.sessionClose();
} catch (Throwable th) {
if (error == null) {
error = th;
}
}
}
answers.clear();
} finally {
try {
clearCache();
} finally {
systemResolver = null;
systemGraphCacheMap.clear();
enlistedResolverMap.clear();
if (error != null) {
throw new QueryException("Error force-closing answers", error);
}
}
}
}
public SystemResolver getSystemResolver() {
return systemResolver;
}
/**
* Clear the cache of temporary graphs.
*/
private void clearCache() {
// Clear the temporary graphs
if (!cachedGraphSet.isEmpty()) {
try {
Resolver temporaryResolver =
temporaryResolverFactory.newResolver(true, systemResolver, systemResolver);
for (Iterator<LocalNode> i = cachedGraphSet.iterator(); i.hasNext();) {
LocalNode graphLocalNode = i.next();
long graph = graphLocalNode.getValue();
if (changedCachedGraphSet.contains(graphLocalNode)) {
// Write back the modifications to the original graph
try {
Resolver resolver =
findResolverFactory(graph).newResolver(true, systemResolver, systemResolver);
Variable s = new Variable("s");
Variable p = new Variable("p");
Variable o = new Variable("o");
resolver.modifyModel(graph,
new TuplesWrapperStatements(temporaryResolver.resolve(
new ConstraintImpl(s, p, o, graphLocalNode)), s, p, o),
true // insert the content
);
} catch (Exception e) {
logger.error("Failed to write back cached graph " + graph + " after transaction", e);
}
changedCachedGraphSet.remove(graphLocalNode);
}
// Remove the cached graph
try {
temporaryResolver.removeModel(graph);
} catch (Exception e) {
logger.error("Failed to clear cached graph " + graph + " after transaction", e);
}
i.remove();
}
} catch (Exception e) {
logger.error("Failed to clear cached graphs after transaction", e);
}
}
}
public void initiate(MulgaraTransaction transaction) throws QueryException {
try {
this.transaction = transaction;
this.systemResolver = systemResolverFactory.newResolver(isWriting);
transaction.enlist(systemResolver);
} catch (Exception e) {
throw new QueryException("Unable to enlist systemResolver:" +
systemResolver + " into transaction", e);
}
}
}