/*
* 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.net.URI;
import java.util.concurrent.ThreadFactory;
import org.apache.log4j.Logger;
import org.mulgara.query.QueryException;
import org.mulgara.server.NonRemoteSessionException;
import org.mulgara.server.Session;
import org.mulgara.server.SessionFactory;
import org.mulgara.server.driver.SessionFactoryFinder;
import org.mulgara.server.driver.SessionFactoryFinderException;
import org.neilja.net.interruptiblermi.InterruptibleRMIThreadFactory;
/**
* A connection for sending commands to a server using a session object.
*
* @created 2007-08-21
* @author Paula Gearon
* @copyright © 2007 <a href="mailto:pgearon@users.sourceforge.net">Paula Gearon</a>
* @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
*/
public class SessionConnection extends CommandExecutor implements Connection {
/** Logger. */
private static final Logger logger = Logger.getLogger(SessionConnection.class.getName());
/** Thread factory used to proxy session operations when interruptible RMI is enabled. */
private static final ThreadFactory interruptibleFactory = InterruptibleRMIThreadFactory.getInstance();
/** Flag to control whether interruptible operations on remote RMI sessions are supported. */
private final boolean useInterruptibleRmi;
/** The URI for the server to establish a session on. */
private URI serverUri;
/** The security domain URI. */
private URI securityDomainUri;
/** The session to use for this connection. */
Session session;
/** The factory used to create this connection */
private ConnectionFactory factory = null;
/** Indicates the current autocommit state */
private boolean autoCommit = true;
/** Indicates the connection has been closed */
private boolean closed = false;
/**
* Creates a new connection, given a URI to a server.
* @param serverUri The URI to connect to.
* @throws ConnectionException There was a problem establishing the details needed for a connection.
*/
public SessionConnection(URI serverUri) throws ConnectionException {
this(serverUri, true);
}
/**
* Creates a new connection, given a URI to a server,
* and a flag to indicate if the server should be "remote".
* @param serverUri The URI to connect to.
* @param isRemote <code>true</code> for a remote session, <code>false</code> for local.
* @throws ConnectionException There was a problem establishing the details needed for a connection.
*/
public SessionConnection(URI serverUri, boolean isRemote) throws ConnectionException {
this(serverUri, isRemote, false);
}
/**
* Creates a new connection, given a URI to a server, a flag to indicate if the server
* should be "remote", and another to indicate whether to use interruptible RMI operations.
* @param serverUri The URI to connect to.
* @param isRemote <code>true</code> for a remote session, <code>false</code> for local.
* @param useInterruptibleRmi <code>true</code> to support interruptible RMI operations on remote sessions.
* @throws ConnectionException There was a problem establishing the details needed for a connection.
*/
public SessionConnection(URI serverUri, boolean isRemote, boolean useInterruptibleRmi) throws ConnectionException {
super(null);
this.useInterruptibleRmi = useInterruptibleRmi;
setServerUri(serverUri, isRemote);
}
/**
* Creates a new connection, given a preassigned session.
* @param session The session to connect with.
*/
public SessionConnection(Session session) {
this(session, null, null, false);
}
/**
* Creates a new connection, given a preassigned session.
* @param session The session to connect with.
* @param securityDomainUri The security domain URI for the session
*/
public SessionConnection(Session session, URI securityDomainUri) {
this(session, securityDomainUri, null, false);
}
/**
* Creates a new connection, given a preassigned session
* @param session The session to connect with
* @param securityDomainUri The security domain URI for the session
* @param serverUri The server URI, needed for re-caching the session with the factory
*/
public SessionConnection(Session session, URI securityDomainUri, URI serverUri) {
this(session, securityDomainUri, serverUri, false);
}
/**
* Creates a new connection, given a preassigned session
* @param session The session to connect with
* @param securityDomainUri The security domain URI for the session
* @param serverUri The server URI, needed for re-caching the session with the factory
* @param useInterruptibleRmi <code>true</code> to support interruptible RMI operations on remote sessions.
*/
public SessionConnection(Session session, URI securityDomainUri, URI serverUri, boolean useInterruptibleRmi) {
super(null);
if (session == null) throw new IllegalArgumentException("Cannot create a connection without a server.");
this.useInterruptibleRmi = useInterruptibleRmi;
setSession(session, securityDomainUri, serverUri);
}
/**
* If a Connection was abandoned by the client without being closed first, attempt to
* reclaim the session for use by future clients.
*/
protected void finalize() throws Throwable {
try {
if (!closed) {
close();
}
} finally {
super.finalize();
}
}
/**
* Used to set a reference back to the factory that created it. If the factory
* reference is set, then the session will be re-cached when this connection is closed.
* @param factory The factory that created this connection.
*/
void setFactory(ConnectionFactory factory) {
this.factory = factory;
}
/**
* Give login credentials and security domain to the current session. This should only be needed
* once since the session does not change.
* @param securityDomainUri The security domain for the login.
* @param user The username.
* @param password The password for the given username.
*/
public void setCredentials(URI securityDomainUri, String user, char[] password) {
checkState();
if (securityDomainUri == null) throw new IllegalArgumentException("Must have a security domain to yuse credentials");
this.securityDomainUri = securityDomainUri;
setCredentials(user, password);
}
/**
* Give login credentials for the current security domain to the current session.
* This should only be needed
* once since the session does not change.
* @param user The username.
* @param password The password for the given username.
*/
public void setCredentials(String user, char[] password) {
checkState();
if (securityDomainUri == null) throw new IllegalArgumentException("Must have a security domain to yuse credentials");
session.login(securityDomainUri, user, password);
}
/**
* @return the session
*/
public Session getSession() {
checkState();
return session;
}
/**
* Starts and commits transactions on this connection, by turning the autocommit
* flag on and off.
* @param autoCommit <code>true</code> if the flag is to be on.
* @throws QueryException The session could not change state.
*/
public void setAutoCommit(boolean autoCommit) throws QueryException {
checkState();
if (this.autoCommit != autoCommit) {
this.autoCommit = autoCommit;
session.setAutoCommit(autoCommit);
}
}
/**
* @return the autoCommit value
*/
public boolean getAutoCommit() {
checkState();
return autoCommit;
}
/**
* Closes the current connection.
*/
public void close() throws QueryException {
checkState();
closed = true;
if (factory != null) {
factory.releaseSession(serverUri, session);
}
}
/**
* Disposes of the current connection and any underlying resources.
*/
public void dispose() throws QueryException {
checkState();
closed = true;
if (factory != null) {
factory.disposeSession(session);
}
if (session != null) {
session.close();
session = null;
}
}
// Private methods //
/**
* @return the serverUri
*/
URI getServerUri() {
return serverUri;
}
/**
* @return the securityDomainUri
*/
URI getSecurityDomainUri() {
return securityDomainUri;
}
/**
* Throws an IllegalStateException if the connection has already been closed.
*/
private void checkState() {
if (closed) {
throw new IllegalStateException("Attempt to access a closed connection");
}
}
/**
* Sets the session information for this connection
* @param session The session to set to.
* @param securityDomainUri The security domain to use for the session.
* @param serverUri The server the session is connected to.
*/
private void setSession(Session session, URI securityDomainUri, URI serverUri) {
this.session = session;
this.securityDomainUri = securityDomainUri;
this.serverUri = serverUri;
if (this.useInterruptibleRmi && !session.isLocal()) {
setThreadFactory(interruptibleFactory);
}
if (logger.isDebugEnabled()) logger.debug("Set server URI to: " + serverUri);
}
/**
* Establishes a session for this connection.
* @param uri The URI to set for the server.
* @param isRemote <code>true</code> for a remote session, <code>false</code> for local.
* @throws ConnectionException There was a problem establishing a session.
*/
private void setServerUri(URI uri, boolean isRemote) throws ConnectionException {
try {
if (uri == null) {
// no model given, and the factory didn't cache a connection, so make one up.
uri = SessionFactoryFinder.findServerURI();
}
if (logger.isDebugEnabled()) logger.debug("Finding session factory for " + uri);
SessionFactory sessionFactory = SessionFactoryFinder.newSessionFactory(uri, isRemote);
if (logger.isDebugEnabled()) logger.debug("Found " + sessionFactory.getClass() +
" session factory, obtaining session with " + uri);
// create a new session and set this connection to it
if (securityDomainUri == null) securityDomainUri = sessionFactory.getSecurityDomain();
setSession(sessionFactory.newSession(), sessionFactory.getSecurityDomain(), uri);
} catch (SessionFactoryFinderException e) {
throw new ConnectionException("Unable to connect to a server", e);
} catch (NonRemoteSessionException e) {
throw new ConnectionException("Error connecting to the local server", e);
} catch (QueryException e) {
throw new ConnectionException("Data error in connection attempt", e);
}
assert session != null;
}
/**
* Tests if the Connection is being conducted over a network.
* @return <code>true</code> if the underlying session is not local.
*/
public boolean isRemote() {
return session != null && !session.isLocal();
}
/**
* Provides access to a JenaConnection. This interface is isolated from this class
* to avoid needing Jena on the classpath.
* @return A new JenaConnection object which refers back to this object.
*/
public JenaConnection getJenaConnection() {
return new JenaConnectionImpl(this);
}
}