package hk.hku.cecid.ebms.spa.task;
import java.net.*;
import java.io.*;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.sql.Timestamp;
import hk.hku.cecid.piazza.commons.dao.DAOException;
import hk.hku.cecid.piazza.commons.module.ActiveModule;
import hk.hku.cecid.piazza.commons.module.ModuleException;
import hk.hku.cecid.piazza.commons.net.HostInfo;
import hk.hku.cecid.ebms.spa.EbmsProcessor;
import hk.hku.cecid.ebms.spa.dao.MessageDAO;
import hk.hku.cecid.ebms.spa.dao.MessageDVO;
import hk.hku.cecid.ebms.spa.dao.ClusterDAO;
import hk.hku.cecid.ebms.spa.dao.ClusterDVO;
import hk.hku.cecid.ebms.spa.handler.MessageClassifier;
/**
* The <code>ClusterAudit</code> audits messages in the cluster and redistributes them if needed
*
* It uses the table cluster inside the database to determine which hosts are part of the cluster
* and to find out if it should wait for or start to audit the hosts and when needed their related
* messages.
*
* Creation Date: 25/11/2014
*
* @author Hans Sinnige
* @version 1.0.1
*
*/
public class ClusterAudit extends ActiveModule {
// Internal Message DAO object.
private MessageDAO msgDAO;
private ClusterDAO clusterDAO;
// The flag for initializing monitor related objects.
private boolean initialized = false;
// Our current hostname
private static String Hostname;
// Static properties, values are set inside ebms.properties.xml under <cluster>
private static int Interval = 180000;
private static String Port = "8080";
private static int Timeout = 10000;
/**
* Creates a new instance of MessageMonitor.
*
* @param descriptorLocation the module descriptor.
* @param loader the class loader for this module.
* @param shouldInitialize true if the module should be initialized.
*/
public ClusterAudit(String descriptorLocation, ClassLoader loader, boolean shouldInitialize) {
super(descriptorLocation, loader, shouldInitialize);
}
/**
* Invoke for initialization.
*/
public void init() {
super.init();
}
/**
* Post/Lazy initialization. This method is invoked at the firs time only
* this module execute.<br/><br/>
*
* Initialize all class data and register this host in the cluster list.
*/
public void initialize(){
try{
msgDAO = (MessageDAO) EbmsProcessor.core.dao.createDAO(MessageDAO.class);
clusterDAO = (ClusterDAO) EbmsProcessor.core.dao.createDAO(ClusterDAO.class);
this.Port = EbmsProcessor.core.properties.getProperty("/ebms/cluster/port");
this.Interval = Integer.parseInt(EbmsProcessor.core.properties.getProperty("/ebms/cluster/interval"));
this.Timeout = Integer.parseInt(EbmsProcessor.core.properties.getProperty("/ebms/cluster/connectiontimeout"));
this.Hostname = HostInfo.GetLocalhostAddress();
registerCurrentHost();
this.initialized = true;
}catch(DAOException daoe){
EbmsProcessor.core.log.fatal("ClusterAudit: Unable to intialize." + daoe);
}
}
/**
* The method is invoked constantly with interval defined in the configuration
* descriptor or 60 second by default.
*
* @return true if this method should be invoked again after a defined interval.
*/
public boolean execute() {
// Lazy initialization.
if (!this.initialized)
this.initialize();
int counter = 0;
try {
long timeStamp = getHostTimestamp(Hostname);
String oldHostname = "";
Date date = new Date();
long currentTimestamp = date.getTime();
// Check if audit starttime is passed or not
if (timeStamp > currentTimestamp) {
date.setTime( timeStamp );
EbmsProcessor.core.log.debug ( "ClusterAudit: wait (" + date.toString() + ")");
} else {
EbmsProcessor.core.log.debug ( "ClusterAudit: start (" + date.toString() + ")");
// To prevent more hosts auditing the cluster at the same time the
// status of our host is set to auditor. Then the cluster is checked
// if this is the only auditor in the cluster. In case this is the
// only auditor the audit will start. In case it is not the only
// auditor the audit will be skipped and this host will be added
// at the end of the cluster list.
updateStatusHostname( Hostname, "auditor" );
if (uniqueAuditor(Hostname)) {
// Walk through the list of hosts inside this cluster
List clusterDVOList = clusterDAO.findClusterAllEntries();
Iterator i = clusterDVOList.iterator();
while (i.hasNext()) {
ClusterDVO clusterEntry = (ClusterDVO) i.next();
oldHostname = clusterEntry.getHostname();
EbmsProcessor.core.log.debug ( "ClusterAudit: check host: " + oldHostname );
counter = 0;
// Skip this host and check if the host is available
if (!oldHostname.equals(Hostname) && !hostIsAvailable(oldHostname)) {
EbmsProcessor.core.log.debug ( "ClusterAudit: host no longer available: " + oldHostname );
// Replace hostname for those messages that are still related to the other host
counter = replaceHostname( Hostname, oldHostname );
if (counter == 0) {
// If there are no messages left in the other host remove it from the cluster list
EbmsProcessor.core.log.debug ( "ClusterAudit: remove host: " + oldHostname );
removeHostname( oldHostname );
} else {
// Update the status of the host to inactive
EbmsProcessor.core.log.debug ( "ClusterAudit: inactive host: " + oldHostname + " message(s) moved" );
updateStatusHostname( oldHostname, "inactive" );
}
}
}
}
// Put us at the end of the cluster list
registerCurrentHost();
EbmsProcessor.core.log.debug ( "ClusterAudit: end (" + date.toString() + ")");
}
}catch(DAOException daoe){
EbmsProcessor.core.log.fatal("ClusterAudit: Unable to complete cluster audit." + daoe);
}
return true;
}
/**
* getHostTimestamp() - retrieves the value of attribute timestamp from
* the database table 'cluster' for the provided host
*
* @param hostname the host for which information is requested
*
* @return timeStamp value of the provided host, or 0 in case the host isn't found
*/
private long getHostTimestamp(String hostname) throws DAOException {
long timeStamp = 0;
List clusterDVOList = clusterDAO.findClusterEntry(hostname);
Iterator i = clusterDVOList.iterator();
while (i.hasNext()) {
ClusterDVO clusterEntry = (ClusterDVO) i.next();
timeStamp = clusterEntry.getTimestamp();
}
return timeStamp;
}
/**
* getLatestClusterTimestamp() - retrieves the 'highest' value of attribute
* timestamp from database table cluster.
*
* @return timeStamp value of the provided host, or 0 in case there is no
* value found.
*/
private long getLatestClusterTimestamp() throws DAOException {
long timeStamp = 0;
List clusterDVOList = clusterDAO.findClusterLatestTimestamp();
Iterator i = clusterDVOList.iterator();
while (i.hasNext()) {
ClusterDVO clusterEntry = (ClusterDVO) i.next();
timeStamp = clusterEntry.getTimestamp();
}
return timeStamp;
}
/**
* getLatestTimestamp() - retrieves the 'highest' value of timestamp. The
* value is based upon the highest value from database table 'cluster' but
* in case that value is lower than the current time or in case the cluster
* is empty the current time is returned as timestamp.
*
* @return timeStamp value
*/
private long getLatestTimestamp() throws DAOException {
long timeStamp = getLatestClusterTimestamp();
Date currentDate = new Date();
long currentTimeStamp = currentDate.getTime();
if (currentTimeStamp > timeStamp) {
timeStamp = currentTimeStamp;
}
return timeStamp;
}
/**
* removeHostname() - removes given host from database table cluster
*
* @param hostname the host to be removed
*/
private void removeHostname( String hostname ) throws DAOException {
// Check if an entry for our host exist and preset all values
ClusterDVO clusterDVO = (ClusterDVO) clusterDAO.createDVO();
clusterDVO.setHostname(hostname);
clusterDAO.deleteCluster(clusterDVO);
}
/**
* updateStatusHostname() - update the status of given host in
* database table cluster
*
* @param hostname the host to be updated
* @param status the new status value
*
*/
private void updateStatusHostname( String hostname, String status ) throws DAOException {
ClusterDVO clusterDVO = (ClusterDVO) clusterDAO.createDVO();
clusterDVO.setHostname(hostname);
clusterDVO.setStatus(status);
clusterDAO.updateCluster(clusterDVO);
}
/**
* updateClusterData() - update the data of given host in
* database table cluster. In case the host is unknown in the
* table cluster a new entry will be added with given values
*
* @param hostname the host to be updated
* @param status the status value
* @param timestamp the timestamp value
*
*/
private void updateClusterData(String hostname, String status, long timestamp) throws DAOException {
// Check if an entry for our host exist and preset all values
ClusterDVO clusterDVO = (ClusterDVO) clusterDAO.createDVO();
clusterDVO.setHostname(hostname);
if (clusterDAO.findCluster(clusterDVO)) {
// Update host entry
clusterDVO.setHostname(hostname);
clusterDVO.setStatus(status);
clusterDVO.setTimestamp(timestamp);
clusterDAO.updateCluster(clusterDVO);
} else {
// Insert host entry
clusterDVO.setHostname(hostname);
clusterDVO.setStatus(status);
clusterDVO.setTimestamp(timestamp);
clusterDAO.addCluster(clusterDVO);
}
}
/**
* registerCurrentHost() - Add the current host to the database
* table cluster with an active status and up to date timestamp.
* In case the host already exist inside cluster its data will
* be updated.
*
*/
private void registerCurrentHost() throws DAOException {
// Retrieve the latest registrated timestamp from the cluster
long timeStamp = getLatestTimestamp();
timeStamp += Interval;
// Update or add entry to the cluster table
updateClusterData(Hostname, "active", timeStamp);
}
/**
* hostIsAvailable() - Checks if a given host is still available for receiving
* messages. This is done by scanning the listening port of the given host.
*
* @param hostname the host which is tried
*
* @return true in case host is available
*/
private boolean hostIsAvailable(String hostname) {
boolean available = true;
try {
Socket socket = new Socket();
socket.connect(new InetSocketAddress(hostname, Integer.parseInt(Port)), 1000);
socket.close();
} catch (Exception ex) {
available = false;
}
return available;
}
/**
* uniqueAuditor() - Is this host the only auditor
*
* @param hostname the host which is tried
*
* @return true in case the host is the unique auditor
*/
private boolean uniqueAuditor(String hostname) throws DAOException {
boolean unique = true;
String localHostname = "";
List clusterDVOList = clusterDAO.findClusterStatusEntries("auditor");
Iterator i = clusterDVOList.iterator();
while (i.hasNext()) {
ClusterDVO clusterEntry = (ClusterDVO) i.next();
localHostname = clusterEntry.getHostname();
if (!localHostname.equals(hostname) && hostIsAvailable(localHostname)) {
unique = false;
}
}
return unique;
}
/**
* replaceHostname() - replace the hostname in case messages are 'stuck'
*
* @param newhostname the host which will replace the old
* @param oldhostname the host which will be replace by the new
*
* @return number of messages by which the hostname is replaced
*/
private int replaceHostname(String newhostname, String oldhostname) throws DAOException {
int totcounter = 0;
int counter = 0;
counter = msgDAO.updateOldIncomingMessagesPendingbyTimestamp(newhostname, oldhostname);
if (counter > 0) {
EbmsProcessor.core.log.debug ( "ClusterAudit: inbox message(s) recovered.");
}
totcounter += counter;
counter = 0;
counter = msgDAO.updateOldOutboxPendingMessagesbyTimestamp(newhostname, oldhostname);
if (counter > 0) {
EbmsProcessor.core.log.debug ( "ClusterAudit: pending outbox message(s) recovered.");
}
totcounter += counter;
counter = 0;
counter = msgDAO.updateOldOutboxProcessingMessagesbyTimestamp(newhostname, oldhostname);
if (counter > 0) {
EbmsProcessor.core.log.debug ( "ClusterAudit: processing outbox message(s) recovered.");
}
totcounter += counter;
return totcounter;
}
}