/*
* Copyright 2009 DuraSpace.
*
* Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mulgara.connection;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.mulgara.query.Answer;
import org.mulgara.query.AskQuery;
import org.mulgara.query.BooleanAnswer;
import org.mulgara.query.ConstructQuery;
import org.mulgara.query.GraphAnswer;
import org.mulgara.query.Query;
import org.mulgara.query.QueryException;
import org.mulgara.query.TuplesException;
import org.mulgara.query.operation.Command;
import org.mulgara.query.operation.Load;
import org.mulgara.server.Session;
/**
* A central point to direct to commands on a connection.
*
* This class also synchronizes access to the session backing this connection, to
* ensure that the session is not accessed concurrently by multiple threads.
*
* Cancellation is implemented by calling {@link Thread#interrupt()} on the thread
* currently accessing the session.
*
* @created Feb 22, 2008
* @author Paula Gearon
* @copyright © 2008 <a href="mailto:pgearon@users.sourceforge.net">Paula Gearon</a>
* @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
*/
public abstract class CommandExecutor implements Connection {
/** Factory for creating proxy threads. */
private ThreadFactory threadFactory;
// Fields used to implement session locking and cancellation.
private Thread sessionThread = null;
private final Lock sessionLock = new ReentrantLock();
private final ReadWriteLock threadLock = new ReentrantReadWriteLock();
/**
* Construct an command executor, with an optional thread factory that will be used
* to create proxy threads for executing operations.
* @param threadFactory If non-null, then every call to {@link #execute(org.mulgara.connection.Connection.SessionOp)}
* will perform the operation in a proxy thread created with this factory.
*/
public CommandExecutor(ThreadFactory threadFactory) {
this.threadFactory = threadFactory;
}
/**
* Sets the factory for creating proxy threads.
* @param threadFactory If non-null, then every call to {@link #execute(org.mulgara.connection.Connection.SessionOp)}
* will perform the operation in a proxy thread created with this factory.
*/
void setThreadFactory(ThreadFactory threadFactory) {
this.threadFactory = threadFactory;
}
/**
* @see org.mulgara.connection.Connection#execute(org.mulgara.query.operation.Command)
*/
public String execute(Command cmd) throws Exception {
return cmd.execute(this).toString();
}
/**
* @see org.mulgara.connection.Connection#execute(org.mulgara.query.operation.Load)
*/
public Long execute(Load cmd) throws QueryException {
return (Long)cmd.execute(this);
}
/**
* @see org.mulgara.connection.Connection#execute(org.mulgara.query.Query)
*/
public Answer execute(Query cmd) throws QueryException, TuplesException {
return (Answer)cmd.execute(this);
}
/**
* @see org.mulgara.connection.Connection#execute(org.mulgara.query.AskQuery)
*/
public BooleanAnswer execute(AskQuery cmd) throws QueryException, TuplesException {
return (BooleanAnswer)cmd.execute(this);
}
/**
* @see org.mulgara.connection.Connection#execute(org.mulgara.query.AskQuery)
*/
public GraphAnswer execute(ConstructQuery cmd) throws QueryException, TuplesException {
return (GraphAnswer)cmd.execute(this);
}
/* (non-Javadoc)
* @see org.mulgara.connection.Connection#execute(org.mulgara.connection.Connection.Executable)
*/
final public <T,E extends Exception> T execute(SessionOp<T,E> cmd) throws E {
return (this.threadFactory != null) ? executeWithProxy(cmd) : doExecute(cmd);
}
/**
* Execute the given operation atomically on the session, using a new proxy thread.
*/
@SuppressWarnings("unchecked")
private <T,E extends Exception> T executeWithProxy(final SessionOp<T,E> cmd) throws E {
assert this.threadFactory != null;
final Wrapper<T> result = new Wrapper<T>();
final Wrapper<Throwable> exception = new Wrapper<Throwable>();
Runnable r = new Runnable() {
public void run() {
try {
result.set(doExecute(cmd));
} catch (Throwable t) {
// Save the error to re-throw in the calling thread - catch Throwable to make
// sure all possible errors get reported to the caller (uncaught errors in a
// child thread are usually lost).
exception.set(t);
}
}
};
Thread t = this.threadFactory.newThread(r);
t.start();
while (t.isAlive()) {
try {
t.join();
} catch (InterruptedException e) {
t.interrupt();
}
}
Throwable th = exception.get();
if (th != null) {
// The caught exception should have been an instance of the generic type E, but
// we can't use generic types in a catch statement. First, try an unchecked cast
// to the declared generic exception type.
try {
throw (E)th;
} catch (ClassCastException cce) {
// Whatever was caught wasn't of the declared generic exception type.
// It could be RuntimeException or Error, which don't need to be declared.
// Check for those, and if all else fails wrap in a RuntimeException so we can re-throw.
if (th instanceof RuntimeException) {
throw (RuntimeException)th;
} else if (th instanceof Error) {
throw (Error)th;
} else {
// TODO This could potentially mask a more serious exception --
// don't know how else to throw the proper generic exception type
throw new RuntimeException("Unexpected exception in proxy thread", th);
}
}
}
// No error, so return the operation result.
return result.get();
}
/**
* Execute the given operation atomically on the session that backs this connection.
*/
@SuppressWarnings("deprecation")
private <T,E extends Exception> T doExecute(SessionOp<T,E> cmd) throws E {
sessionLock.lock();
try {
Session session = getSession();
setSessionThread(Thread.currentThread());
try {
// TODO To be completely safe, we could wrap the session in a closeable facade, but that's probably overkill.
return cmd.fn(session);
} finally {
setSessionThread(null);
}
} finally {
sessionLock.unlock();
}
}
/* (non-Javadoc)
* @see org.mulgara.connection.Connection#cancel()
*/
final public void cancel() {
threadLock.readLock().lock();
try {
if (sessionThread != null) sessionThread.interrupt();
} finally {
threadLock.readLock().unlock();
}
}
/**
* Sets the thread currently using the session, subject to a write-lock on the thread.
* @param t
*/
private void setSessionThread(Thread t) {
threadLock.writeLock().lock();
try {
this.sessionThread = t;
} finally {
threadLock.writeLock().unlock();
}
}
/**
* Utility class for wrapping a variable so it can be get and set from an anonymous inner class.
* @param <T> The type of object being wrapped.
*/
private static class Wrapper<T> {
private T value = null;
/** Set the wrapped value. */
public void set(T value) { this.value = value; }
/** Get the wrapped value. */
public T get() { return this.value; }
}
}