/*
* 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.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// Third party packages
import org.apache.log4j.Logger;
import org.jrdf.graph.*;
// Local packages
import org.mulgara.query.*;
import org.mulgara.query.rdf.*;
import org.mulgara.resolver.spi.*;
import org.mulgara.store.nodepool.NodePool;
import org.mulgara.store.nodepool.NodePoolException;
import org.mulgara.store.stringpool.SPObject;
import org.mulgara.store.stringpool.SPObjectFactory;
import org.mulgara.store.stringpool.SPURI;
import org.mulgara.store.stringpool.StringPool;
import org.mulgara.store.stringpool.StringPoolException;
import org.mulgara.store.tuples.Tuples;
import org.mulgara.store.tuples.TuplesOperations;
import org.mulgara.store.xa.SimpleXAResource;
import org.mulgara.store.xa.SimpleXAResourceException;
import org.mulgara.store.xa.XANodePool;
import org.mulgara.store.xa.XAResolverSession;
import org.mulgara.store.xa.XAStringPool;
import org.mulgara.util.LongMapper;
import org.mulgara.util.QueryParams;
import org.mulgara.util.StackTrace;
/**
* A database session.
*
* @created 2004-04-26
* @author <a href="http://staff.pisoftware.com/andrae">Andrae Muys</a>
* @version $Revision: 1.9 $
* @modified $Date: 2005/02/22 08:16:10 $ by $Author: newmana $
* @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>
*/
public class StringPoolSession implements XAResolverSession, BackupRestoreSession {
/** Logger. */
private static final Logger logger =
Logger.getLogger(StringPoolSession.class.getName());
/**
* Stopgap to deal with the lack of a "no match" return value from the
* string pool.
*/
private static final long NONE = NodePool.NONE;
private static final int OBTAIN = 0;
private static final int PREPARE = 1;
private static final int COMMIT = 2;
private static final int ROLLBACK = 3;
private static final int RELEASE = 4;
static final int READ = 0;
/** Query stringpool, create node if not found */
static final int WRITE = 1;
/** Query temp stringpool then persistent, node will be created in temp stringpool if required */
static final int TEMP = 0;
/** Query only persistent stringpool, node will be created in persistent stringpool if required */
static final int PERSIST = 2;
/** Extracts RW_FLAG */
static final int WRITE_MASK = 1;
/** Extracts STORE_FLAG */
static final int STORE_MASK = 2;
/** The name of the graph parameter in a URI */
static final String GRAPH = "graph";
/** The system URI scheme */
static final String RMI_SCHEME = "rmi";
/** The relative name of the system graph */
static final String SYSTEM_GRAPH_NAME = "#";
/** The relative URI for the system graph */
static final URI SYSTEM_GRAPH_URI = URI.create(SYSTEM_GRAPH_NAME);
/** The unique {@link URI} naming this database. */
private final URI databaseURI;
/** The set of alternative hostnames for the current host. */
private final Set<String> hostnameAliases;
/** Where to store literals for this phase. */
private XAStringPool persistentStringPool;
/** The source of nodes for this phase. */
private XANodePool persistentNodePool;
/** The source of nodes which won't outlive this session. */
private final NodePool temporaryNodePool;
/** Where to store literals which won't outlive this session. */
private final StringPool temporaryStringPool;
/** An enumeration of the various states for this session. */
private int state;
/** A lock for managing the modification of state. */
private static Lock stateLock = new ReentrantLock();
private SimpleXAResource[] resources;
private Object globalLock;
private Thread currentThread;
StringPoolSession(URI databaseURI,
Set<String> hostnameAliases,
XAStringPool persistentStringPool,
XANodePool persistentNodePool,
StringPool temporaryStringPool,
NodePool temporaryNodePool,
Object globalLock
) {
if (logger.isDebugEnabled()) {
logger.debug("Constructing StringPoolSession " + System.identityHashCode(this) + "\n" + new StackTrace());
}
assert databaseURI.getFragment() == null;
this.databaseURI = databaseURI;
this.hostnameAliases = hostnameAliases;
this.persistentStringPool = persistentStringPool;
this.persistentNodePool = persistentNodePool;
this.temporaryStringPool = temporaryStringPool;
this.temporaryNodePool = temporaryNodePool;
this.globalLock = globalLock;
this.state = OBTAIN;
this.currentThread = null;
this.persistentStringPool.setNodePool(this.persistentNodePool);
}
//
// Globalize/Localize methods.
//
public Node globalize(long localNode) throws GlobalizeException {
// this should not require guarding, as read-only operations will usually not be on the current phase
// any reads on the current phase are about to start failing anyway if the state changes under us
// this should not require guarding, as read-only operations will usually not be on the current phase
// any reads on the current phase are about to start failing anyway if the state changes under us
if (state == ROLLBACK || state == RELEASE) {
throw new GlobalizeException(localNode, "Attempting to globalize outside transaction.");
}
// Validate "localNode" parameter
if (localNode == NONE) {
throw new IllegalArgumentException("NONE isn't a local node");
}
// Look up the local node in the string pool
SPObject spObject;
try {
if (localNode < 0) {
spObject = temporaryStringPool.findSPObject(-localNode);
} else {
spObject = mapAbsolute(persistentStringPool.findSPObject(localNode));
}
} catch (StringPoolException e) {
throw new GlobalizeException(localNode, "String pool lookup failed", e);
}
// Generate and return the corresponding RDF node
Node node = globalizeBlankNode(localNode, spObject);
assert node != null;
// Return the RDF node
return node;
}
public long lookup(Node node) throws LocalizeException {
return localize(node, READ | TEMP);
}
public long lookupPersistent(Node node) throws LocalizeException {
return localize(node, READ | PERSIST);
}
public long localize(Node node) throws LocalizeException {
return localize(node, WRITE | TEMP);
}
public long localizePersistent(Node node) throws LocalizeException {
checkCurrentThread();
try {
return localize(node, WRITE | PERSIST);
} finally {
releaseCurrentThread();
}
}
public long newBlankNode() throws NodePoolException {
checkCurrentThread();
try {
return persistentNodePool.newNode();
} finally {
releaseCurrentThread();
}
}
public void refresh(SimpleXAResource[] resources) throws SimpleXAResourceException {
checkCurrentThread();
try {
if (logger.isDebugEnabled()) {
logger.debug("Obtaining phase on StringPoolSession " + System.identityHashCode(this));
}
this.resources = resources;
synchronized (this.globalLock) {
this.persistentStringPool.refresh(); // calls refresh on the node pool
// !!Review: Call rollback on temporary? NB. Can't rollback non XA-SP/NP.
//this.temporaryStringPool.refresh();
//this.temporaryNodePool.refresh();
for (int i = 0; i < this.resources.length; i++) {
this.resources[i].refresh();
}
}
} finally {
releaseCurrentThread();
}
}
public void prepare() throws SimpleXAResourceException {
checkCurrentThread();
try {
stateLock.lock();
try {
if (logger.isDebugEnabled()) {
logger.debug("Preparing phase on StringPoolSession " + System.identityHashCode(this) + " SP=" + System.identityHashCode(persistentStringPool));
}
if (state == PREPARE) {
return;
} else if (state != OBTAIN) {
throw new SimpleXAResourceException("Attempting to prepare phase without obtaining phase");
}
state = PREPARE;
persistentStringPool.prepare(); // calls prepare on the node pool
for (int i = 0; i < resources.length; i++) resources[i].prepare();
} finally {
stateLock.unlock();
}
} finally {
releaseCurrentThread();
}
}
public void commit() throws SimpleXAResourceException {
checkCurrentThread();
try {
stateLock.lock();
try {
if (logger.isDebugEnabled()) {
logger.debug("Committing phase on StringPoolSession " + System.identityHashCode(this));
}
if (state == COMMIT) {
return;
} else if (state != PREPARE) {
throw new SimpleXAResourceException("Attempting to commit phase without preparing");
}
state = COMMIT;
// now holding BOTH the stateLock AND the globalLock. This should be short!
synchronized (globalLock) {
persistentStringPool.commit(); // calls commit() on the node pool
for (int i = 0; i < resources.length; i++) resources[i].commit();
}
} finally {
stateLock.unlock();
}
} finally {
releaseCurrentThread();
}
}
public void rollback() throws SimpleXAResourceException {
checkCurrentThread();
try {
stateLock.lock();
try {
if (logger.isDebugEnabled()) {
logger.debug("Rollback phase on StringPoolSession " + System.identityHashCode(this));
}
if (state == RELEASE) {
throw new SimpleXAResourceException("Attempting to rollback phase outside transaction");
}
state = ROLLBACK;
persistentStringPool.rollback(); // calls rollback on the node pool
for (int i = 0; i < resources.length; i++) resources[i].rollback();
} finally {
stateLock.unlock();
}
} finally {
releaseCurrentThread();
}
}
public void release() throws SimpleXAResourceException {
checkCurrentThread();
try {
stateLock.lock();
try {
if (logger.isDebugEnabled()) {
logger.debug("Release phase on StringPoolSession " + System.identityHashCode(this));
}
if (state == RELEASE) {
return;
} else if (state != COMMIT && state != ROLLBACK) {
throw new SimpleXAResourceException("Attempting to release phase without commit or rollback");
}
state = RELEASE;
persistentStringPool.release(); // calls release on the node pool
// TODO determine if release() should be called for the temp components.
//temporaryStringPool.release();
//temporaryNodePool.release();
for (int i = 0; i < resources.length; i++) resources[i].release();
} finally {
stateLock.unlock();
}
} finally {
releaseCurrentThread();
}
}
/**
* {@inheritDoc}
*/
public Tuples findStringPoolRange(
SPObject lowValue, boolean inclLowValue,
SPObject highValue, boolean inclHighValue
) throws StringPoolException {
try {
// get the nodes from both string pools
Tuples[] tuples = new Tuples[2];
tuples[0] = persistentStringPool.findGNodes(lowValue, inclLowValue, highValue, inclHighValue);
tuples[1] = temporaryStringPool.findGNodes(lowValue, inclLowValue, highValue, inclHighValue);
Tuples result = appendTuples(tuples);
tuples[0].close();
tuples[1].close();
return result;
} catch (TuplesException te) {
throw new StringPoolException(te);
}
}
/**
* {@inheritDoc}
*/
public Tuples findStringPoolType(
SPObject.TypeCategory typeCategory, URI typeURI
) throws StringPoolException {
try {
// get the nodes from both string pools
Tuples[] tuples = new Tuples[2];
tuples[0] = persistentStringPool.findGNodes(typeCategory, typeURI);
tuples[1] = temporaryStringPool.findGNodes(typeCategory, typeURI);
return appendTuples(tuples);
} catch (TuplesException te) {
throw new StringPoolException(te);
}
}
/**
* {@inheritDoc}
*/
public SPObject findStringPoolObject(long gNode) throws StringPoolException {
// Container for our SPObject
SPObject spo = null;
if (gNode >= NodePool.MIN_NODE) {
if (logger.isDebugEnabled()) {
logger.debug("!! Searching for persistent node from id: " + gNode);
}
// Check if we have a persistent Node and find it if we have
spo = mapAbsolute(persistentStringPool.findSPObject(gNode));
} else {
if (logger.isDebugEnabled()) {
logger.debug("!! Searching for temporary node from id: " + gNode);
}
// We have a temporary node so check the temporary pool (Using the
// inverted negative id)
spo = temporaryStringPool.findSPObject(-gNode);
}
return spo;
}
/**
* Retrieve the SPObject factory from the stringpool to allow for the creation
* of new SPObjects.
*
* @return The factory to allow for creation of SPObjects
*/
public SPObjectFactory getSPObjectFactory() {
return persistentStringPool.getSPObjectFactory();
}
//
// Type specific localize methods.
//
protected long localize(Node node, int flags) throws LocalizeException
{
// this should not require guarding, as read-only operations will usually not be on the current phase
// any reads on the current phase are about to start failing anyway if the state changes under us
if (state != OBTAIN) {
throw new LocalizeException(node, "Attempting to localize outside transaction (STATE = " + state + ") " + System.identityHashCode(this));
}
if (node == null) {
throw new IllegalArgumentException("Null 'node' parameter");
}
if (node instanceof BlankNode) {
return localizeBlankNode((BlankNode)node, flags);
}
SPObjectFactory spoFactory = persistentStringPool.getSPObjectFactory();
SPObject spObject;
try {
spObject = spoFactory.newSPObject(node);
} catch (RuntimeException ex) {
throw new LocalizeException(node, "Couldn't convert Node to SPObject", ex);
}
assert spObject != null;
try {
return localizeSPObject(spObject, flags);
} catch (NodePoolException e) {
throw new LocalizeException(node, "Couldn't localize node", e);
} catch (StringPoolException e) {
throw new LocalizeException(node, "Couldn't localize node", e);
}
}
protected long localizeBlankNode(BlankNode node, int flags) throws LocalizeException {
try {
// Check to see that it's a blank node impl (a Mulgara blank node)
if (node instanceof BlankNodeImpl) {
BlankNodeImpl bi = (BlankNodeImpl) node;
// If the blank node id is greater then zero return it.
// FIXME: we should be checking that the BlankNodeImpl came from the
// correct phase, otherwise it is invalid to extract the NodeId.
if (bi.getNodeId() > 0) return bi.getNodeId();
// If the blank node does not have a blank node id and we are in a read
// phase then throw an exception.
if ((bi.getNodeId() == 0) && ((flags & WRITE_MASK) == READ)) {
throw new LocalizeException(node, "Attempt to get a node ID from a non-allocated BlankNodeImpl in a read phase");
}
// If we are in a write phase.
if ((flags & WRITE_MASK) == WRITE) {
// If the blank node if less than zero (query node) and we are
// persisting.
if ((bi.getNodeId() < 0) && ((flags & STORE_MASK) == PERSIST)) {
bi.setNodeId(persistentNodePool.newNode());
} else if (bi.getNodeId() == 0) {
if ((flags & STORE_MASK) == TEMP) {
bi.setNodeId(-temporaryNodePool.newNode());
} else {
bi.setNodeId(persistentNodePool.newNode());
}
}
return bi.getNodeId();
}
// Throw an exception here if we're in a read phase and the blank node
// id is negative.
throw new LocalizeException(node, "Attempt to persist a local blank node in a read phase");
} else if ((flags & WRITE_MASK) == WRITE) {
// Some other implementation of BlankNode, so we can't access internal
// node ID and we can only create one - we must be in the WRITE phase.
return getAllocatedNodeId(node, flags);
} else {
// If it's a read phase and not the local BlankNode then throw an
// exception.
throw new LocalizeException(node, "Attempt to read BlankNode from stringpool");
}
} catch (NodePoolException e) {
throw new LocalizeException(node, "Couldn't create blank node", e);
}
}
/**
* Allocates new node IDs for unknown nodes. Stores node IDs for later lookups.
* @param bn The blank node to get the ID for.
* @param flags Indicates the type of storage for the node ids.
* @return The node ID for this given blank node.
* @throws NodePoolException An error while allocating a new node.
*/
protected long getAllocatedNodeId(BlankNode bn, int flags) throws NodePoolException {
assert !(bn instanceof BlankNodeImpl);
long nodeId;
if ((flags & STORE_MASK) == TEMP) {
nodeId = -temporaryNodePool.newNode();
} else {
nodeId = persistentNodePool.newNode();
}
return nodeId;
}
protected Node globalizeBlankNode(long localNode, SPObject spObject) throws GlobalizeException {
return (spObject == null) ? new BlankNodeImpl(localNode) : spObject.getRDFNode();
}
private long localizeSPObject(SPObject spObject, int flags) throws StringPoolException, NodePoolException {
boolean persistent = true;
SPObject relativeSPObject = mapRelative(spObject);
long localNode = persistentStringPool.findGNode(relativeSPObject);
// If not found persistently then try the temp pool if permitted.
if (localNode == NONE && ((flags & STORE_MASK) == TEMP)) {
localNode = temporaryStringPool.findGNode(spObject);
persistent = false;
}
/* The following block could cause misbehavior if someone tries to
globalize a local node which was originally transient, but was later
"promoted" to being persistent.
if (localNode == NONE && ((flags & STORE_MASK) == PERSIST)) {
localNode = temporaryStringPool.findGNode(spObject);
if (localNode != NONE) {
// The node exists in the temporary pool, but not in the persistent
// pool, and we've been directed to write it into the persistent pool.
// We can therefore remove the version in the temporary pool.
if (logger.isDebugEnabled()) {
logger.debug("Removing " + spObject + " as " + localNode + " from temeporary sp " + temporaryStringPool + " because it's about to be written persistently");
}
temporaryStringPool.remove(localNode);
localNode = NONE;
}
}
*/
// If the literal wasn't already in the string pool, create it if requested.
if (localNode == NONE) {
if ((flags & WRITE_MASK) == WRITE) {
// Node does not already exist: create node in requested pool.
if ((flags & STORE_MASK) == PERSIST) {
localNode = persistentStringPool.put(relativeSPObject); // allocates from the internal node pool
if (logger.isDebugEnabled()) {
//logger.debug("Inserted " + spObject + " as " + localNode + " into persistent sp");
}
} else {
localNode = temporaryNodePool.newNode();
temporaryStringPool.put(localNode, spObject);
if (logger.isDebugEnabled()) {
//logger.debug("Inserted " + spObject + " as " + localNode + " into temporary sp");
}
persistent = false;
}
} else {
// Node not found when expected: throw exception.
throw new StringPoolException("Unable to find literal in StringPool");
}
}
// Node was found or has been successfully created in requested pool.
assert localNode != NONE;
// logger.warn("Localized " + spObject + " as " + (persistent ? localNode : -localNode));
return persistent ? localNode : -localNode;
}
private SPObject mapAbsolute(SPObject spObject) {
if (
spObject != null &&
spObject.getTypeCategory() == SPObject.TypeCategory.URI
) {
URI uri = ((SPURI)spObject).getURI();
if (!uri.isAbsolute()) {
// Graph URIs are stored as a relative URI containing only a fragment.
// Relative URIs with both a query string and a fragment are also used
// for views.
SPObjectFactory spObjectFactory =
persistentStringPool.getSPObjectFactory();
try {
// Construct an absolute URI based on the database URI.
String query = uri.getQuery();
String ssp = databaseURI.getSchemeSpecificPart();
if (query != null) ssp += '?' + query;
String fragment = uri.getFragment();
if (fragment != null) {
// this is a graph fragment
spObject = spObjectFactory.newSPURI(new URI(databaseURI.getScheme(), ssp, uri.getFragment()));
} else {
// this is a path-relative URI
String relPath = uri.getSchemeSpecificPart();
// ensure that the relPath can be concatenated to the ssp
if (ssp.endsWith("/")) {
if (relPath.startsWith("/")) relPath = relPath.substring(1);
} else {
if (!relPath.startsWith("/")) relPath = "/" + relPath;
}
spObject = spObjectFactory.newSPURI(new URI(databaseURI.getScheme(), ssp + relPath, null));
}
} catch (URISyntaxException ex) {
logger.warn(
"Cannot create absolute URI with base:\"" + databaseURI +
"\", query:\"" + uri.getQuery() + "\", fragment:\"" +
uri.getFragment() + "\"", ex
);
}
}
}
return spObject;
}
private SPObject mapRelative(SPObject spObject) {
if (
spObject != null &&
spObject.getTypeCategory() == SPObject.TypeCategory.URI
) {
URI uri = ((SPURI)spObject).getURI();
// Check if the URI is relative to the database URI.
// The user info of the uri is ignored and is stripped from the URI if it
// ends up being relativized.
String scheme = uri.getScheme();
String fragment = uri.getFragment();
// we're only going to fiddle with this if the database scheme is RMI
if (scheme != null && scheme.equals(databaseURI.getScheme()) && scheme.equals(RMI_SCHEME)) {
if (databaseURI.isOpaque()) {
// databaseURI is opaque.
if (fragment != null && uri.isOpaque()) {
// Get the query string.
// We have to do it this way for opaque URIs.
String ssp = uri.getSchemeSpecificPart();
String query;
int qIndex = ssp.indexOf('?');
if (qIndex >= 0) {
query = ssp.substring(qIndex + 1);
ssp = ssp.substring(0, qIndex);
} else {
query = null;
}
if (ssp.equals(databaseURI.getSchemeSpecificPart())) {
// Construct a new relative uri with just the fragment and
// optional query string.
SPObjectFactory spObjectFactory = persistentStringPool.getSPObjectFactory();
try {
spObject = spObjectFactory.newSPURI(new URI(null, null, null, query, fragment));
} catch (URISyntaxException ex) {
logger.warn("Cannot create relative URI with fragment:\"" + fragment + "\"", ex);
}
}
}
} else {
// databaseURI is hierarchial.
String path = null;
String host;
if (
!uri.isOpaque() && (
uri.getSchemeSpecificPart().equals(
databaseURI.getSchemeSpecificPart()
) || (
(host = uri.getHost()) != null &&
uri.getPort() == databaseURI.getPort() &&
(path = uri.getPath()) != null &&
path.startsWith(databaseURI.getPath()) &&
hostnameAliases.contains(host.toLowerCase())
)
)
) {
// Construct a new relative uri with just the fragment and
// optional query string.
SPObjectFactory spObjectFactory = persistentStringPool.getSPObjectFactory();
QueryParams query = QueryParams.decode(uri);
String gName = query.get(GRAPH);
if (path == null) path = uri.getPath();
String dbPath = databaseURI.getPath();
if (gName != null) {
// wrapped graph name
try {
spObject = spObjectFactory.newSPURI(new URI(gName));
} catch (URISyntaxException ex) {
logger.warn("Cannot extract a valid URI from:\"" + gName + "\"", ex);
}
} else if (!path.equals(dbPath)) {
// relative URI
path = path.substring(dbPath.length());
if (path.startsWith("/")) path = path.substring(1);
try {
spObject = spObjectFactory.newSPURI(new URI(null, null, path, uri.getQuery(), fragment));
} catch (URISyntaxException ex) {
logger.warn("Cannot create relative URI with path:\"" + path + "\"", ex);
}
} else if (fragment != null) {
// fragment graph name
try {
spObject = spObjectFactory.newSPURI(new URI(null, null, null, uri.getQuery(), fragment));
} catch (URISyntaxException ex) {
logger.warn("Cannot create relative URI with fragment:\"" + fragment + "\"", ex);
}
}
}
}
} else if (scheme != null && scheme.equals(databaseURI.getScheme())) {
// not RMI, but we still want to catch the system graph
if (uri.toString().equals(databaseURI.toString() + SYSTEM_GRAPH_NAME)) {
SPObjectFactory spObjectFactory = persistentStringPool.getSPObjectFactory();
spObject = spObjectFactory.newSPURI(SYSTEM_GRAPH_URI);
}
}
}
return spObject;
}
/**
* Internal helper method to append tuples.
*
* @param tuples Array of tuples to be unioned. Each element will be closed.
* @return A single tuples containing the contents of each of the elements in the tuples array.
* @throws TuplesException Internal exception while manipulating tuples.
*/
private Tuples appendTuples(Tuples[] tuples) throws TuplesException {
assert tuples[0] != null && tuples[1] != null;
// TODO: check to make sure these have the same variable names
int t;
if (logger.isDebugEnabled()) {
for (t = 0; t < tuples.length; t++) {
logger.debug("concatenating " + tuples[t].getRowCount() + " stringpool objects");
}
}
// sort each tuples object
Tuples[] st = new Tuples[tuples.length];
for (t = 0; t < tuples.length; t++) {
st[t] = TuplesOperations.sort(tuples[t]);
// close the original tuples
tuples[t].close();
}
// union the sorted tuples
Tuples result = TuplesOperations.append(Arrays.asList(st));
// close the sorted tuples
for (t = 0; t < st.length; t++) {
st[t].close();
}
return result;
}
/**
* {@inheritDoc}
*
* NB: This method does not perform any absolute/relative URI mapping.
*/
public SPObject findSPObject(long gNode) throws StringPoolException {
if (gNode < NodePool.MIN_NODE) {
throw new IllegalArgumentException("Attempt to resolve temporary gNode in BackupRestoreSession");
}
return persistentStringPool.findSPObject(gNode);
}
public long findGNode(SPObject spObject) throws StringPoolException {
return persistentStringPool.findGNode(spObject, persistentNodePool);
}
/** @see org.mulgara.resolver.spi.BackupRestoreSession#getRestoreMapper() */
public LongMapper getRestoreMapper() throws Exception {
return persistentNodePool.getNodeMapper();
}
/**
* Used purely as a sanity check in the hope that we might catch concurrency bugs in higher layers should
* they exist.
*/
private void checkCurrentThread() {
synchronized(this) {
if (currentThread == null || currentThread.equals(Thread.currentThread())) {
currentThread = Thread.currentThread();
} else {
logger.warn("Concurrent Access of StringPoolSession Attempted");
throw new IllegalStateException("Concurrent Access of StringPoolSession Attempted");
}
}
}
private void releaseCurrentThread() {
synchronized(this) {
currentThread = null;
}
}
}