/* * 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): * XAResource access copyright 2007 The Topaz Foundation. * * [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.server.rmi; // Java 2 standard packages import java.io.InputStream; import java.io.Serializable; import java.net.URI; import java.rmi.RemoteException; import java.util.*; import java.io.*; import javax.transaction.xa.XAResource; // Third party packages import org.apache.log4j.Logger; // Locally written packages import org.jrdf.graph.Triple; import org.mulgara.query.Answer; import org.mulgara.query.AskQuery; import org.mulgara.query.ConstructQuery; import org.mulgara.query.GraphAnswer; import org.mulgara.query.GraphExpression; import org.mulgara.query.Query; import org.mulgara.query.QueryException; import org.mulgara.rules.RulesRef; import org.mulgara.server.NonRemoteSessionException; import org.mulgara.server.Session; import javax.activation.MimeType; import javax.naming.*; /** * Wrapper around a {@link RemoteSession} to make it look like a {@link * Session}. The only real functionality this wrapper implements is to nest any * {@link RemoteException}s inside {@link QueryException}s. * * @author <a href="http://staff.pisoftware.com/raboczi">Simon Raboczi</a> * * @created 2002-01-03 * * @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A> * * @copyright © 2002-2003 <A href="http://www.PIsoftware.com/">Plugged In * Software Pty Ltd</A> * * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a> */ class RemoteSessionWrapperSession implements Serializable, Session { /** Logger. */ private static final Logger logger = Logger.getLogger(RemoteSessionWrapperSession.class.getName()); /** * Allow newer compiled version of the stub to operate when changes * have not occurred with the class. * NOTE : update this serialVersionUID when a method or a public member is * deleted. */ static final long serialVersionUID = -2647357071965350751L; /** The number of times to retry a call. */ protected static final int RETRY_COUNT = 2; /** The number of times remaining to retry the current call. */ protected int retryCount; /** The wrapped {@link RemoteSession} */ private RemoteSession remoteSession; /** The serverURI of the remoteSessionFactory. Used to reconnect sessions. */ protected URI serverURI = null; /** * Maintain the state of autcommit to determine if an exception * needs to be throw if a reconnection is attempted when a transaction * was previously in process when the server was bounced (a rollback has * occured). */ protected boolean autoCommit = true; // // Constructor // /** * Wrap a remote session to make it appear as a local session. * @param remoteSession the wrapped remote session. * @param serverURI The server the session is connecting to. * @throws IllegalArgumentException if <var>remoteSession</var> is <code>null</code> */ protected RemoteSessionWrapperSession(RemoteSession remoteSession, URI serverURI) { // Validate "remoteSession" parameter if (remoteSession == null) { throw new IllegalArgumentException("Null \"remoteSession\" parameter"); } // Initialize fields this.remoteSession = remoteSession; this.serverURI = serverURI; resetRetries(); } /** * Sets the contents of a model, via a model expression. * * @param uri The name of the model to set. * @param sourceUri The expression describing the data to put in the model. * @return The number of statements inserted into the model. * @throws QueryException An error getting data for the model, or inserting into the new model. */ public long setModel(URI uri, URI sourceUri) throws QueryException { try { long r = remoteSession.setModel(uri, sourceUri); resetRetries(); return r; } catch (RemoteException e) { testRetry(e); return setModel(uri, sourceUri); } } /** * Define the contents of a model via an {@link InputStream}. * * @param inputStream a remote inputstream * @param uri the {@link URI} of the model to be redefined * @param sourceUri the new content for the model * @return The number of statements inserted into the model * @throws QueryException if the model can't be modified */ public long setModel(InputStream inputStream, URI uri, URI sourceUri, MimeType contentType) throws QueryException { try { long r = remoteSession.setModel(inputStream, uri, sourceUri, contentType); resetRetries(); return r; } catch (RemoteException e) { testRetry(e); return setModel(inputStream, uri, sourceUri, contentType); } } /** * Sets the AutoCommit attribute of the RemoteSessionWrapperSession object * * @param autoCommit The new AutoCommit value * @throws QueryException Autocommit cannot be changed. */ public void setAutoCommit(boolean autoCommit) throws QueryException { try { remoteSession.setAutoCommit(autoCommit); // autoCommit has been successfully issued on the // first attempt. this.autoCommit = autoCommit; resetRetries(); } catch (RemoteException e) { // if autocommit was set the off/false then an // exception would be thrown by testRetry informing // the user of a rollback testRetry(e); // a successful retry to re-establish server // connectivity has been made. Autocommit will // now default to true (a new session). // set the requested value of autocommit setAutoCommit(autoCommit); } } // // Methods implementing the Session interface // /** * {@inheritDoc} */ public void insert(URI modelURI, Set<? extends Triple> statements) throws QueryException { try { remoteSession.insert(modelURI, statements); resetRetries(); } catch (RemoteException e) { testRetry(e); insert(modelURI, statements); } } /** * {@inheritDoc} */ public void insert(URI modelURI, Query query) throws QueryException { try { remoteSession.insert(modelURI, query); resetRetries(); } catch (RemoteException e) { testRetry(e); insert(modelURI, query); } } /** * {@inheritDoc} */ public void delete(URI modelURI, Set<? extends Triple> statements) throws QueryException { try { remoteSession.delete(modelURI, statements); resetRetries(); } catch (RemoteException e) { testRetry(e); delete(modelURI, statements); } } /** * {@inheritDoc} */ public void delete(URI modelURI, Query query) throws QueryException { try { remoteSession.delete(modelURI, query); resetRetries(); } catch (RemoteException e) { testRetry(e); delete(modelURI, query); } } /** * Backup all the data on the specified server. The database is not changed by * this method. * * @param destinationURI The URI of the file to backup into. * @throws QueryException if the backup cannot be completed. */ public void backup(URI destinationURI) throws QueryException { try { remoteSession.backup(destinationURI); resetRetries(); } catch (RemoteException e) { testRetry(e); backup(destinationURI); } } /** * Backup all the data on the specified server to an output stream. * The database is not changed by this method. * * @param outputStream The stream to receive the contents * @throws QueryException if the backup cannot be completed. */ public void backup(OutputStream outputStream) throws QueryException { try { remoteSession.backup(outputStream); resetRetries(); } catch (RemoteException e) { testRetry(e); backup(outputStream); } } /** * Export the data in the specified graph. The database is not changed by this method. * * @param graphURI The URI of the graph to export. * @param destinationURI The URI of the file to export into. * @throws QueryException if the export cannot be completed. */ public void export(URI graphURI, URI destinationURI) throws QueryException { try { remoteSession.export(graphURI, destinationURI); resetRetries(); } catch (RemoteException e) { testRetry(e); export(graphURI, destinationURI); } } /** * Export the data in the specified graph using predefined namespace prefixes. * The database is not changed by this method. * * @param graphURI The URI of the graph to export. * @param destinationURI The URI of the file to export into. * @param prefixes An optional mapping for pre-populating the RDF/XML namespace prefixes. * @throws QueryException if the export cannot be completed. */ public void export(URI graphURI, URI destinationURI, Map<String,URI> prefixes) throws QueryException { try { remoteSession.export(graphURI, destinationURI, prefixes); resetRetries(); } catch (RemoteException e) { testRetry(e); export(graphURI, destinationURI); } } /** * Export the data in the specified graph to an output stream. * The database is not changed by this method. * * @param graphURI The URI of the server or model to export. * @param outputStream The stream to receive the contents * @throws QueryException if the export cannot be completed. */ public void export(URI graphURI, OutputStream outputStream, MimeType contentType) throws QueryException { try { remoteSession.export(graphURI, outputStream, contentType); resetRetries(); } catch (RemoteException e) { testRetry(e); export(graphURI, outputStream, contentType); } } /** * Export the data in the specified graph to an output stream using predefined namespace prefixes. * The database is not changed by this method. * * @param graphURI The URI of the server or model to export. * @param outputStream The stream to receive the contents * @param prefixes An optional mapping for pre-populating the RDF/XML namespace prefixes. * @throws QueryException if the export cannot be completed. */ public void export(URI graphURI, OutputStream outputStream, Map<String,URI> prefixes, MimeType contentType) throws QueryException { try { remoteSession.export(graphURI, outputStream, prefixes, contentType); resetRetries(); } catch (RemoteException e) { testRetry(e); export(graphURI, outputStream, prefixes, contentType); } } /** * Restore all the data on the server. If the database is not * currently empty then the current contents of the database will be replaced * with the content of the backup file when this method returns. * * @param sourceURI The URI of the backup file to restore from. * @throws QueryException if the restore cannot be completed. */ public void restore(URI sourceURI) throws QueryException { try { remoteSession.restore(sourceURI); resetRetries(); } catch (RemoteException e) { testRetry(e); restore(sourceURI); } } /** * Restore all the data on the server. If the database is not * currently empty then the current contents of the database will be replaced * with the content of the backup file when this method returns. * * @param inputStream a client supplied inputStream to obtain the restore * content from. If null assume the sourceURI has been supplied. * @param sourceURI The URI of the backup file to restore from. * @throws QueryException if the restore cannot be completed. */ public void restore(InputStream inputStream, URI sourceURI) throws QueryException { try { remoteSession.restore(inputStream, sourceURI); resetRetries(); } catch (RemoteException e) { testRetry(e); restore(inputStream, sourceURI); } } /** * {@inheritDoc} */ public void createModel(URI modelURI, URI modelTypeURI) throws QueryException { try { remoteSession.createModel(modelURI, modelTypeURI); resetRetries(); } catch (RemoteException e) { testRetry(e); createModel(modelURI, modelTypeURI); } } /** * {@inheritDoc} */ public void removeModel(URI uri) throws QueryException { try { remoteSession.removeModel(uri); resetRetries(); } catch (RemoteException e) { testRetry(e); removeModel(uri); } } /** * {@inheritDoc} */ public boolean modelExists(URI uri) throws QueryException { try { boolean modelExists = remoteSession.modelExists(uri); resetRetries(); return modelExists; } catch (RemoteException e) { testRetry(e); return modelExists(uri); } } /** * {@inheritDoc} */ public void commit() throws QueryException { try { remoteSession.commit(); resetRetries(); } catch (RemoteException e) { testRetry(e); commit(); } } /** * {@inheritDoc} */ public void rollback() throws QueryException { try { remoteSession.rollback(); resetRetries(); } catch (RemoteException e) { testRetry(e); rollback(); } } /** * {@inheritDoc} */ public List<Answer> query(List<Query> queries) throws QueryException { try { List<Object> remoteAnswers = remoteSession.query(queries); resetRetries(); List<Answer> localAnswers = new ArrayList<Answer>(remoteAnswers.size()); for (Object ans: remoteAnswers) { if (!(ans instanceof RemoteAnswer) && !(ans instanceof Answer)) { throw new QueryException("Non-answer returned from query."); } if (ans instanceof RemoteAnswer) localAnswers.add(new RemoteAnswerWrapperAnswer((RemoteAnswer)ans)); else localAnswers.add((Answer)ans); } return localAnswers; } catch (RemoteException e) { testRetry(e); return query(queries); } } /** * {@inheritDoc} */ public Answer query(Query query) throws QueryException { try { RemoteAnswer ans = remoteSession.query(query); resetRetries(); return new RemoteAnswerWrapperAnswer(ans); } catch (RemoteException e) { testRetry(e); return query(query); } } /** * {@inheritDoc} * Provices a lightweight GraphAnswer wrapper over the returned Answer (which is already * a GraphAnswer at the server end). */ public GraphAnswer query(ConstructQuery query) throws QueryException { try { RemoteAnswer ans = remoteSession.query(query); resetRetries(); return new GraphAnswer(new RemoteAnswerWrapperAnswer(ans)); } catch (RemoteException e) { testRetry(e); return query(query); } } /** * {@inheritDoc} */ public boolean query(AskQuery query) throws QueryException { try { return remoteSession.query(query); } catch (RemoteException e) { testRetry(e); return query(query); } } /** * {@inheritDoc} */ public void close() throws QueryException { try { remoteSession.close(); resetRetries(); } catch (java.rmi.NoSuchObjectException e) { // do nothing as the RMI server has removed // the reference } catch (RemoteException e) { // no need to retry, since the session is gone throw new QueryException("Java RMI failure", e); } } /** * {@inheritDoc} */ public void login(URI securityDomain, String username, char[] password) { try { remoteSession.login(securityDomain, username, password); } catch (RemoteException e) { try { // test if this should be retried testRetry(e); try { // successfully got new session. Try to log in. remoteSession.login(securityDomain, username, password); } catch (RemoteException re) { // unable to log in to a new session logger.warn("Cannot log in to remote session", re); } finally { resetRetries(); } } catch (QueryException qe) { // retry not possible logger.warn("Cannot connect to remote session", qe); } } } /** * Tests if an RMIException was caused by a retryable condition. * If so, then obtains a new session for retrying. * * @throws QueryException if remote method can't be retried. */ protected void testRetry(RemoteException e) throws QueryException { // determine if a retry should be attempted. if (!(e instanceof java.rmi.ConnectException) || retryCount == 0) { resetRetries(); throw new QueryException("Java RMI failure", e); } try { RmiSessionFactory rmiSessionFactory = null; // create a new RMI session factory try { rmiSessionFactory = new RmiSessionFactory(serverURI); } catch (NamingException ex) { throw new QueryException("Java RMI reconnection failure", ex); } catch (NonRemoteSessionException nrse) { throw new QueryException("Server name modification during a query reconnection"); } // obtain a new remoteSession and replace the current one remoteSession = rmiSessionFactory.getRemoteSessionFactory().newRemoteSession(); // was a transaction in progress before the server connectivity was lost? if (!autoCommit) { // all new sessions will result in automcommit set to on; autoCommit = true; // since a transaction was in progress when server connectivity // was lost we must notify the user a possible rollback throw new QueryException("Connectivity to server "+ this.serverURI + " was lost during a "+ "transaction, which has resulted in a "+ "transaction rollback. "+ "Connectivity has now been re-established."); } } catch (RemoteException re) { throw new QueryException("Java RMI reconnection failure", re); } retryCount--; } /** * Resets the retry count. */ protected void resetRetries() { retryCount = RETRY_COUNT; } /** * {@inheritDoc} */ public boolean isLocal() { return false; } /** * {@inheritDoc} */ public RulesRef buildRules(URI ruleModel, GraphExpression baseModel, URI destModel) throws QueryException, org.mulgara.rules.InitializerException { try { RulesRef ref = remoteSession.buildRules(ruleModel, baseModel, destModel); if (logger.isDebugEnabled()) logger.debug("got rules from RMI"); return ref; } catch (RemoteException re) { Throwable cause = re.getCause(); if (cause != null) throw new org.mulgara.rules.InitializerException("Unable to load rules: " + cause.getMessage(), cause); throw new org.mulgara.rules.InitializerException("Unable to load rules", re); } } /** * {@inheritDoc} */ public void applyRules(RulesRef rules) throws QueryException { try { remoteSession.applyRules(rules); } catch (RemoteException re) { Throwable cause = re.getCause(); if (cause != null) throw new QueryException("Error applying rules: " + cause.getMessage(), cause); throw new QueryException("Error applying rules", re); } } public XAResource getXAResource() throws QueryException { try { return new RemoteXAResourceWrapperXAResource(remoteSession.getXAResource()); } catch (RemoteException re){ throw new QueryException("Java RMI failure", re); } } public XAResource getReadOnlyXAResource() throws QueryException { try { return new RemoteXAResourceWrapperXAResource(remoteSession.getReadOnlyXAResource()); } catch (RemoteException re){ throw new QueryException("Java RMI failure", re); } } public void setIdleTimeout(long millis) throws QueryException { try { remoteSession.setIdleTimeout(millis); resetRetries(); } catch (RemoteException re){ testRetry(re); setIdleTimeout(millis); } } public void setTransactionTimeout(long millis) throws QueryException { try { remoteSession.setTransactionTimeout(millis); resetRetries(); } catch (RemoteException re){ testRetry(re); setTransactionTimeout(millis); } } public boolean ping() throws QueryException { try { boolean ping = remoteSession.ping(); resetRetries(); return ping; } catch (RemoteException re) { testRetry(re); return ping(); } } }