/*
* 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: PeriodicRecovery.java 2342 2006-03-30 13:06:17Z $
*/
package com.arjuna.ats.internal.arjuna.recovery;
import java.lang.InterruptedException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Vector;
import java.net.*;
import java.io.*;
import com.arjuna.common.util.propertyservice.PropertyManager;
import com.arjuna.ats.arjuna.recovery.RecoveryModule;
import com.arjuna.ats.arjuna.recovery.RecoveryEnvironment;
import com.arjuna.ats.arjuna.common.arjPropertyManager;
import com.arjuna.ats.arjuna.logging.FacilityCode;
import com.arjuna.ats.arjuna.logging.tsLogger;
import com.arjuna.ats.internal.arjuna.utils.SocketProcessId;
import com.arjuna.common.util.logging.*;
/**
* Threaded object to perform the periodic recovery. Instantiated in
* the RecoveryManager. The work is actually completed by the recovery
* modules. These modules are dynamically loaded. The modules to load
* are specified by properties beginning with "RecoveryExtension"
* <P>
* @author
* @version $Id: PeriodicRecovery.java 2342 2006-03-30 13:06:17Z $
*
* @message com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_1 [com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_1] - Attempt to load recovery module with null class name!
* @message com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_2 [com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_2] - Recovery module {0} does not conform to RecoveryModule interface
* @message com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_3 [com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_3] - Loading recovery module: {0}
* @message com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_4 [com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_4] - Loading recovery module: {0}
* @message com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_5 [com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_5] - Loading recovery module: could not find class {0}
* @message com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_6 [com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_6] - {0} has inappropriate value ( {1} )
* @message com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_7 [com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_7] - {0} has inappropriate value ( {1} )
* @message com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_8 [com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_8] - Invalid port specified {0}
* @message com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_9 [com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_9] - Could not create recovery listener {0}
*/
public class PeriodicRecovery extends Thread
{
public PeriodicRecovery (boolean threaded)
{
initialise();
// Load the recovery modules that actually do the work.
loadModules();
try
{
_workerService = new WorkerService(this);
_listener = new Listener(getServerSocket(), _workerService);
_listener.setDaemon(true);
}
catch (Exception ex)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_9", new Object[]{ex});
}
}
if (threaded)
{
start();
}
_listener.start();
}
public void shutdown ()
{
_terminate = true;
this.interrupt();
}
/**
* Return the port specified by the property
* com.arjuna.ats.internal.arjuna.recovery.recoveryPort,
* otherwise return a default port.
*/
public static final ServerSocket getServerSocket () throws IOException
{
if (_socket == null)
{
// TODO these properties should be documented!!
String tsmPortStr = arjPropertyManager.propertyManager.getProperty(com.arjuna.ats.arjuna.common.Environment.RECOVERY_MANAGER_PORT);
int port = 0;
if (tsmPortStr != null)
{
try
{
port = Integer.parseInt( tsmPortStr );
}
catch (Exception ex)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_8", new Object[]{ex});
}
}
}
_socket = new ServerSocket(port);
}
return _socket;
}
/**
* Start the background thread to perform the periodic recovery
*/
public void run ()
{
boolean finished = false;
do
{
finished = doWork(true);
} while (!finished);
}
/**
* Perform the recovery scans on all registered modules.
*
* @param boolean periodic If <code>true</code> then this is being called
* as part of the normal periodic running of the manager and we'll sleep
* after phase 2 work. Otherwise, we're being called directly and there should
* be no sleep after phase 2.
*
* @return <code>true</code> if the manager has been instructed to finish,
* <code>false</code> otherwise.
*/
public final boolean doWork (boolean periodic)
{
boolean interrupted = false;
tsLogger.arjLogger.info("Periodic recovery - first pass <" +
_theTimestamper.format(new Date()) + ">" );
Enumeration modules = _recoveryModules.elements();
while (modules.hasMoreElements())
{
RecoveryModule m = (RecoveryModule) modules.nextElement();
m.periodicWorkFirstPass();
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug( DebugLevel.FUNCTIONS,
VisibilityLevel.VIS_PUBLIC,
FacilityCode.FAC_CRASH_RECOVERY,
" " );
}
}
if (interrupted)
{
interrupted = false;
_workerService.signalDone();
}
// wait for a bit to avoid catching (too many) transactions etc. that
// are really progressing quite happily
try
{
Thread.sleep( _backoffPeriod * 1000 );
}
catch ( InterruptedException ie )
{
interrupted = true;
}
if ( _terminate )
{
return true;
}
tsLogger.arjLogger.info("Periodic recovery - second pass <"+
_theTimestamper.format(new Date()) + ">" );
modules = _recoveryModules.elements();
while (modules.hasMoreElements())
{
RecoveryModule m = (RecoveryModule) modules.nextElement();
m.periodicWorkSecondPass();
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug ( DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC, FacilityCode.FAC_CRASH_RECOVERY, " " );
}
}
try
{
if (!interrupted && periodic)
Thread.sleep( _recoveryPeriod * 1000 );
}
catch ( InterruptedException ie )
{
interrupted = true;
}
if ( _terminate )
{
return true;
}
return false; // keep going
}
/**
* Add the specified module to the end of the recovery module list.
* There is no way to specify relative ordering of recovery modules
* with respect to modules loaded via the property file.
*
* @param RecoveryModule module The module to append.
*/
public final void addModule (RecoveryModule module)
{
_recoveryModules.add(module);
}
/**
* @return the recovery modules.
*/
public final Vector getModules ()
{
return _recoveryModules;
}
/**
* Load recovery modules prior to starting to recovery. The property
* name of each module is used to indicate relative ordering.
*/
private final static void loadModules ()
{
// scan the relevant properties so as to get them into sort order
Properties properties = arjPropertyManager.propertyManager.getProperties();
if (properties != null)
{
Vector moduleNames = new Vector();
Enumeration names = properties.propertyNames();
while (names.hasMoreElements())
{
String attrName = (String) names.nextElement();
if (attrName.startsWith(RecoveryEnvironment.MODULE_PROPERTY_PREFIX))
{
// this is one of ours - put it in the right place
int position = 0;
while ( position < moduleNames.size() &&
attrName.compareTo( (String)moduleNames.elementAt(position)) > 0 )
{
position++;
}
moduleNames.add(position,attrName);
}
}
// now go through again and load them
names = moduleNames.elements();
while (names.hasMoreElements())
{
String attrName = (String) names.nextElement();
loadModule(properties.getProperty(attrName));
}
}
}
private final static void loadModule (String className)
{
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug( DebugLevel.FUNCTIONS,
VisibilityLevel.VIS_PRIVATE,
FacilityCode.FAC_CRASH_RECOVERY,
"Loading recovery module "+
className );
}
if (className == null)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_1");
return;
}
else
{
try
{
Class c = Thread.currentThread().getContextClassLoader().loadClass( className );
try
{
RecoveryModule m = (RecoveryModule) c.newInstance();
_recoveryModules.add(m);
}
catch (ClassCastException e)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_2",
new Object[]{className});
}
}
catch (IllegalAccessException iae)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_3",
new Object[]{iae});
}
}
catch (InstantiationException ie)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_4",
new Object[]{ie});
}
}
c = null;
}
catch ( ClassNotFoundException cnfe )
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_5",
new Object[]{className});
}
}
}
}
private final void initialise ()
{
_recoveryModules = new Vector();
_terminate = false;
}
// this refers to the modules specified in the recovery manager
// property file which are dynamically loaded.
private static Vector _recoveryModules = null;
// back off period is the time between the first and second pass.
// recovery period is the time between the second pass and the start
// of the first pass.
private static int _backoffPeriod = 0;
private static int _recoveryPeriod = 0;
// default values for the above
private static final int _defaultBackoffPeriod = 10;
private static final int _defaultRecoveryPeriod = 120;
// exit thread flag
private static boolean _terminate = false;
private static SimpleDateFormat _theTimestamper = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss");
private static ServerSocket _socket = null;
private static Listener _listener = null;
private static WorkerService _workerService = null;
/*
* Read the system properties to set the configurable options
*
* Note: if we start and stop the service then changes to the timeouts
* won't be reflected. We will need to modify this eventually.
*/
static
{
_recoveryPeriod = _defaultRecoveryPeriod;
String recoveryPeriodString =
arjPropertyManager.propertyManager.getProperty(com.arjuna.ats.arjuna.common.Environment.PERIODIC_RECOVERY_PERIOD );
if ( recoveryPeriodString != null )
{
try
{
Integer recoveryPeriodInteger = new Integer( recoveryPeriodString );
_recoveryPeriod = recoveryPeriodInteger.intValue();
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug
( DebugLevel.FUNCTIONS,
VisibilityLevel.VIS_PRIVATE,
FacilityCode.FAC_CRASH_RECOVERY,
"com.arjuna.ats.arjuna.recovery.PeriodicRecovery" +
": Recovery period set to " + _recoveryPeriod + " seconds" );
}
}
catch (NumberFormatException e)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_6",
new Object[]{com.arjuna.ats.arjuna.common.Environment.PERIODIC_RECOVERY_PERIOD, recoveryPeriodString});
}
}
}
_backoffPeriod = _defaultBackoffPeriod;
String backoffPeriodString=
arjPropertyManager.propertyManager.getProperty(com.arjuna.ats.arjuna.common.Environment.RECOVERY_BACKOFF_PERIOD);
if (backoffPeriodString != null)
{
try
{
Integer backoffPeriodInteger = new Integer(backoffPeriodString);
_backoffPeriod = backoffPeriodInteger.intValue();
if (tsLogger.arjLogger.isDebugEnabled())
{
tsLogger.arjLogger.debug
( DebugLevel.FUNCTIONS,
VisibilityLevel.VIS_PRIVATE,
FacilityCode.FAC_CRASH_RECOVERY,
"PeriodicRecovery" +
": Backoff period set to " + _backoffPeriod + " seconds" );
}
}
catch (NumberFormatException e)
{
if (tsLogger.arjLoggerI18N.isWarnEnabled())
{
tsLogger.arjLoggerI18N.warn("com.arjuna.ats.internal.arjuna.recovery.PeriodicRecovery_7",
new Object[]{com.arjuna.ats.arjuna.common.Environment.RECOVERY_BACKOFF_PERIOD, backoffPeriodString});
}
}
}
}
}