/*
* JBoss, Home of Professional Open Source
* Copyright 2006, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags.
* See the copyright.txt in the distribution for a
* full listing of individual contributors.
* This copyrighted material is made available to anyone wishing to use,
* modify, copy, or redistribute it subject to the terms and conditions
* of the GNU Lesser General Public License, v. 2.1.
* This program is distributed in the hope that it will be useful, but WITHOUT A
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public License,
* v.2.1 along with this distribution; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*
* (C) 2005-2006,
* @author JBoss Inc.
*/
/*
* Copyright (C) 1999-2001 by HP Bluestone Software, Inc. All rights Reserved.
*
* HP Arjuna Labs,
* Newcastle upon Tyne,
* Tyne and Wear,
* UK.
*
* $Id: RecoveryManager.java 2342 2006-03-30 13:06:17Z $
*/
package com.arjuna.ats.arjuna.recovery;
import java.util.Vector;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.io.IOException;
import com.arjuna.ats.internal.arjuna.recovery.RecoveryManagerImple;
import com.arjuna.ats.arjuna.utils.Utility;
import com.arjuna.ats.arjuna.logging.tsLogger;
import com.arjuna.ats.arjuna.exceptions.FatalError;
import com.arjuna.common.util.propertyservice.PropertyManager;
import com.arjuna.common.util.propertyservice.PropertyManagerFactory;
/**
* @message com.arjuna.ats.arjuna.recovery.RecoveryManager_1 [com.arjuna.ats.arjuna.recovery.RecoveryManager_1] - Invalid recovery manager port specified {0}
* @message com.arjuna.ats.arjuna.recovery.RecoveryManager_2 [com.arjuna.ats.arjuna.recovery.RecoveryManager_2] - Invalid recovery manager host specified {0}
* @message com.arjuna.ats.arjuna.recovery.RecoveryManager_3 [com.arjuna.ats.arjuna.recovery.RecoveryManager_3] - Recovery manager bound to {0}:{1}
* @message com.arjuna.ats.arjuna.recovery.RecoveryManager_4 [com.arjuna.ats.arjuna.recovery.RecoveryManager_4] - Connected to recovery manager on {0}:{1}
* @message com.arjuna.ats.arjuna.recovery.RecoveryManager_5 [com.arjuna.ats.arjuna.recovery.RecoveryManager_5] - Invalid recovery manager port specified
*/
class ScanThread extends Thread
{
public ScanThread (RecoveryManagerImple theImple, RecoveryScan callback)
{
super("RecoveryManagerScanThread");
_theImple = theImple;
_callback = callback;
setDaemon(true);
}
public void run ()
{
if (_theImple != null)
{
_theImple.scan();
if (_callback != null)
_callback.completed();
}
}
private RecoveryManagerImple _theImple;
private RecoveryScan _callback;
}
/**
* The RecoveryManager daemon.
*/
public class RecoveryManager
{
/**
* In this mode the recovery manager runs periodically but may
* also be driven through messages or via the scan operation if
* it is embedded.
*/
public static final int INDIRECT_MANAGEMENT = 0;
/**
* In this mode the recovery manager does not run periodically and
* will only work if driven through messages or via the scan
* operation if it is embedded.
*/
public static final int DIRECT_MANAGEMENT = 1;
/**
* Obtain a reference to the RecoveryManager singleton. If it hasn't
* been created yet then it will be. The manager will be created in the
* INDIRECT_MANAGEMENT mode.
*
* @throw IllegalArgumentException thrown if the manager has already been
* created in a different mode to that requested.
*
* @return the manager.
*/
public static synchronized final RecoveryManager manager () throws IllegalArgumentException
{
return manager(RecoveryManager.INDIRECT_MANAGEMENT);
}
/**
* Obtain a reference to the RecoveryManager singleton. If it hasn't
* been created yet then it will be. The manager can be created in a
* management mode defined by the parameter.
*
* @param mode the management mode for the manager.
*
* @throw IllegalArgumentException thrown if the manager has already been
* created in a different mode to that requested.
*
* @return the manager.
*/
public static synchronized final RecoveryManager manager (int mode) throws IllegalArgumentException
{
if (_recoveryManager == null)
_recoveryManager = new RecoveryManager(mode);
else
{
if (_recoveryManager.mode() != mode)
throw new IllegalArgumentException();
}
return _recoveryManager;
}
/**
* Delay the start of the recovery manager thread when creating an indirect recovery manager.
*/
public static synchronized void delayRecoveryManagerThread()
{
delayRecoveryManagerThread = true ;
}
/**
* Force a recovery scan now. This is a blocking operation
* and will only return once the recovery scan has completed.
*
* @throws IllegalStateException if the recovery manager has been shutdown.
*/
public final void scan ()
{
checkState();
_theImple.scan();
}
/**
* Force a recovery scan now. This is a non-blocking operation
* and will return immediately. Notification of completion of the
* scan is done through the RecoveryScan object.
*
* @param callback callback The callback mechanism used to
* inform users that the scan has completed. If this is <code>null</code>
* then no callback will happen and asynchronous scanning will occur.
*
* @throws IllegalStateException if the recovery manager has been shutdown.
*/
public final void scan (RecoveryScan callback)
{
checkState();
ScanThread st = new ScanThread(_theImple, callback);
st.start();
}
/**
* Stop the periodic recovery manager waiting for any recovery scan in progress to complete.
*
* @throws IllegalStateException if the recovery manager has been shutdown.
*
* @deprecated use terminate instead.
*/
public final void stop ()
{
stop(false);
}
/**
* Stop the periodic recovery manager.
* @param async false means wait for any recovery scan in progress to complete.
* @throws IllegalStateException if the recovery manager has been shutdown.
*
* @deprecated use terminate instead.
*/
public final void stop (boolean async)
{
checkState();
_theImple.stop(async);
}
/**
* Terminate and cleanup the recovery manager. There is no going back from this. This is a
* synchronous operation so return means that the recovery has completed.
*
* @throws IllegalStateException if the recovery manager has been shutdown.
*/
public final void terminate ()
{
terminate(false);
}
/**
* Terminate and cleanup the recovery manager. There is no going back from this. Can be called
* synchronous or asynchronously. If you have any intention of restarting the recovery manager
* later then you MUST use the async=false option.
*
* @param async false means wait for any recovery scan in progress to complete.
* @throws IllegalStateException if the recovery manager has been shutdown.
*/
public final synchronized void terminate (boolean async)
{
checkState();
_theImple.stop(async);
_theImple = null;
}
/**
* If the recovery manager has been shutdown previously then recreate it in
* the same mode as before. Otherwise ignore.
*/
public final synchronized void initialize ()
{
if (_theImple == null)
{
if ((_mode == RecoveryManager.INDIRECT_MANAGEMENT) && !delayRecoveryManagerThread)
_theImple = new RecoveryManagerImple(true);
else
_theImple = new RecoveryManagerImple(false);
}
}
// does nothing when running embedded.
/**
* @throws IllegalStateException if the recovery manager has been shutdown.
*/
public void waitForTermination ()
{
checkState();
_theImple.waitForTermination();
_theImple = null;
}
/**
* Suspend the recovery manager. If the recovery manager is in the process of
* doing recovery scans then it will be suspended afterwards, in order to
* preserve data integrity.
*
* @param async false means wait for the recovery manager to finish any scans before returning.
* @throws IllegalStateException if the recovery manager has been shutdown.
* @deprecated use suspend
*/
public void suspendScan (boolean async)
{
suspend(async);
}
/**
* Suspend the recovery manager. If the recovery manager is in the process of
* doing recovery scans then it will be suspended afterwards, in order to
* preserve data integrity.
*
* @param async false means wait for the recovery manager to finish any scans before returning.
* @throws IllegalStateException if the recovery manager has been shutdown.
*/
public void suspend (boolean async)
{
checkState();
_theImple.suspendScan(async);
}
/**
* @throws IllegalStateException if the recovery manager has been shutdown.
* @deprecated use resume
*/
public void resumeScan ()
{
resume();
}
/**
* @throws IllegalStateException if the recovery manager has been shutdown.
*/
public void resume ()
{
checkState();
_theImple.resumeScan();
}
/**
* Start the recovery manager thread.
*
* @throws IllegalStateException if the recovery manager has been shutdown.
*/
public void startRecoveryManagerThread()
{
checkState();
_theImple.start() ;
}
/**
* Add a recovery module to the system.
*
* @param module module The module to add.
* @throws IllegalStateException if the recovery manager has been shutdown.
*/
public final void addModule (RecoveryModule module)
{
checkState();
_theImple.addModule(module);
}
/**
* Remove a recovery module from the system.
*
* @param module The module to remove.
* @param waitOnScan true if the remove operation should wait for any in-progress scan to complete
* @throws IllegalStateException if the recovery manager has been shutdown.
*/
public final void removeModule (RecoveryModule module, boolean waitOnScan)
{
checkState();
_theImple.removeModule(module, waitOnScan);
}
/**
* Obtain a snapshot list of available recovery modules.
*
* @return a snapshot list of the currently installed recovery modules
* @throws IllegalStateException if the recovery manager has been shutdown.
*/
public final Vector getModules ()
{
checkState();
return _theImple.getModules();
}
/**
* Indicates what mode (INDIRECT_MANAGEMENT or DIRECT_MANAGEMENT)
* the recovery manager is configured for.
*
* @return the management mode.
*/
public final int mode ()
{
return _mode;
}
public static InetAddress getRecoveryManagerHost() throws UnknownHostException
{
PropertyManager pm = PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "recoverymanager");
if ( pm == null )
return InetAddress.getLocalHost();
String hostPropName = com.arjuna.ats.arjuna.common.Environment.RECOVERY_MANAGER_ADDRESS;
String host = pm.getProperty(hostPropName);
return Utility.hostNameToInetAddress(host, "com.arjuna.ats.arjuna.recovery.RecoveryManager_2");
}
public static int getRecoveryManagerPort()
{
PropertyManager pm = PropertyManagerFactory.getPropertyManager("com.arjuna.ats.propertymanager", "recoverymanager");
if (pm == null)
return 0;
String portPropName = com.arjuna.ats.arjuna.common.Environment.RECOVERY_MANAGER_PORT;
Integer port = Utility.lookupBoundedIntegerProperty(pm, portPropName, null,
"com.arjuna.ats.arjuna.recovery.RecoveryManager_1",
0, Utility.MAX_PORT);
if (port == null)
{
String portStr = pm.getProperty(portPropName);
/*
* if the property files specified a value for the port which is invalid throw a fatal error. An empty value or no value
* corresponds to any port
*/
if (portStr == null || portStr.length() == 0)
port = 0;
else
throw new com.arjuna.ats.arjuna.exceptions.FatalError(tsLogger.log_mesg.getString("com.arjuna.ats.arjuna.recovery.RecoveryManager_5"));
}
return port;
}
/**
* Obtain a client connection to the recovery manager
*
* @return a bound client socket connection to the recovery manager
* @throws IOException
*/
public static Socket getClientSocket () throws IOException
{
Socket socket = new Socket(getRecoveryManagerHost(), getRecoveryManagerPort());
if (tsLogger.arjLoggerI18N.isInfoEnabled())
{
tsLogger.arjLoggerI18N.info("com.arjuna.ats.arjuna.recovery.RecoveryManager_4",
new Object[]{socket.getInetAddress().getHostAddress(), socket.getLocalPort()});
}
return socket;
}
/**
* Run the RecoveryManager. See Administration manual for details.
*/
public static void main (String[] args)
{
boolean testMode = false;
for (int i = 0; i < args.length; i++)
{
if (args[i].compareTo("-help") == 0)
{
System.out.println("Usage: com.arjuna.ats.arjuna.recovery.RecoveryManager [-help] [-test] [-version]");
System.exit(0);
}
if (args[i].compareTo("-version") == 0)
{
System.out.println("Version " + com.arjuna.ats.arjuna.common.Configuration.version());
System.exit(0);
}
if (args[i].compareTo("-test") == 0)
{
testMode = true;
}
}
try
{
RecoveryManager manager = null;
try
{
manager = manager();
}
catch(FatalError e)
{
if(testMode)
{
// in some test cases the recovery manager is killed and restarted in quick succession.
// sometimes the O/S does not free up the port fast enough, so we can't reclaim it on restart.
// For test mode only, we therefore have a simple backoff-retry kludge:
System.err.println("Warning: got Fatal error '"+e.toString()+"' on startup, will retry in 5 seconds in the hope it is transient.");
try
{
Thread.sleep(5000);
}
catch(InterruptedException interruptedException)
{
// do nothing
}
manager = manager();
}
else
{
throw e;
}
}
if (testMode)
{
System.out.println("Ready");
}
manager.waitForTermination();
}
catch (Throwable e)
{
e.printStackTrace();
}
}
private RecoveryManager (int mode)
{
if ((mode == RecoveryManager.INDIRECT_MANAGEMENT) && !delayRecoveryManagerThread)
_theImple = new RecoveryManagerImple(true);
else
_theImple = new RecoveryManagerImple(false);
_mode = mode;
}
private final void checkState ()
{
if (_theImple == null)
throw new IllegalStateException();
}
private RecoveryManagerImple _theImple = null;
private int _mode;
private static RecoveryManager _recoveryManager = null;
private static boolean delayRecoveryManagerThread ;
}