package org.infoglue.cms.util;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
/**
* This class implements a simple queue for updating the live servers. If a update did not reach a server it can be specified to retry with regular intervals.
*
* @author Mattias Bogeblad
*/
public class LiveNotificationThread implements Runnable
{
private final static Logger logger = Logger.getLogger(LiveNotificationThread.class.getName());
private static LiveNotificationThread singleton = null;
private boolean keepRunning = true;
private Map<String, List<FailedNotification>> serversFailedNotifications = new HashMap<String, List<FailedNotification>>();
public static LiveNotificationThread getLiveNotificationThread()
{
if(singleton == null)
{
singleton = new LiveNotificationThread();
Thread thread = new Thread (singleton);
thread.start();
}
return singleton;
}
public void addServerFailedNotifications(String url, Hashtable message)
{
List<FailedNotification> serverFailedNotifications = serversFailedNotifications.get(url);
if(serverFailedNotifications == null)
{
serverFailedNotifications = new ArrayList<FailedNotification>();
serversFailedNotifications.put(url, serverFailedNotifications);
}
serverFailedNotifications.add(new FailedNotification(url, message));
}
public void addServerFailedNotifications(FailedNotification failedNotification)
{
List<FailedNotification> serverFailedNotifications = serversFailedNotifications.get(failedNotification.getUrl());
if(serverFailedNotifications == null)
{
serverFailedNotifications = new ArrayList<FailedNotification>();
serversFailedNotifications.put(failedNotification.getUrl(), serverFailedNotifications);
}
serverFailedNotifications.add(failedNotification);
}
public synchronized void run()
{
logger.info("Running HttpUniqueRequestQueue...");
long timeLastLongRun = -1;
long timeLastVeryLongRun = -1;
while(keepRunning)
{
try
{
Thread.sleep(30000);
}
catch( InterruptedException e )
{
logger.error("Interrupted Exception caught");
}
logger.info("Running..");
Map<String, List<FailedNotification>> localServersFailedNotifications = new HashMap<String, List<FailedNotification>>();
synchronized (serversFailedNotifications)
{
localServersFailedNotifications.putAll(serversFailedNotifications);
serversFailedNotifications.clear();
}
for(String serverUrl : localServersFailedNotifications.keySet())
{
logger.info("serverUrl:" + serverUrl);
boolean skipMoreOnThisServer = false;
List<FailedNotification> failedNotifications = localServersFailedNotifications.get(serverUrl);
Iterator<FailedNotification> failedNotificationsIterator = failedNotifications.iterator();
while(failedNotificationsIterator.hasNext())
{
FailedNotification failedNotification = failedNotificationsIterator.next();
try
{
if(!failedNotification.shouldRun())
{
logger.error("Skipping any more updates to this server right now it has been downprio " + failedNotification.getUrl() + "(" + failedNotification.getFailures() + ")");
addServerFailedNotifications(failedNotification);
}
else if(skipMoreOnThisServer)
{
logger.error("Skipping any more updates to this server right now as communications seems down " + failedNotification.getUrl());
failedNotification.increaseFailuresAndSetRunTime();
addServerFailedNotifications(failedNotification);
}
else
{
logger.info("Gonna try again with " + failedNotification.getUrl() + " and " + failedNotification.getMessage());
String response = postToUrl(failedNotification.getUrl(), failedNotification.getMessage());
logger.warn("Success when updating previously failed cache at " + failedNotification.getUrl() + ":" + response);
}
}
catch(Exception e)
{
logger.error("Error updating cache at " + failedNotification.getUrl() + ":" + e.getMessage());
e.printStackTrace();
skipMoreOnThisServer = true;
failedNotification.increaseFailuresAndSetRunTime();
addServerFailedNotifications(failedNotification);
}
finally
{
failedNotificationsIterator.remove();
}
}
}
}
}
/**
* This method post information to an URL and returns a string.It throws
* an exception if anything goes wrong.
* (Works like most 'doPost' methods)
*
* @param urlAddress The address of the URL you would like to post to.
* @param inHash The parameters you would like to post to the URL.
* @return The result of the postToUrl method as a string.
* @exception java.lang.Exception
*/
private String postToUrl(String urlAddress, Hashtable inHash) throws Exception
{
URL url = new URL(urlAddress);
URLConnection urlConn = url.openConnection();
urlConn.setConnectTimeout(10000);
urlConn.setReadTimeout(10000);
urlConn.setAllowUserInteraction(false);
urlConn.setDoOutput (true);
urlConn.setDoInput (true);
urlConn.setUseCaches (false);
urlConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
PrintWriter printout = new PrintWriter(urlConn.getOutputStream(), true);
String argString = "";
if(inHash != null)
{
argString = toEncodedString(inHash);
}
printout.print(argString);
printout.flush();
printout.close ();
InputStream inStream = null;
inStream = urlConn.getInputStream();
InputStreamReader inStreamReader = new InputStreamReader(inStream);
BufferedReader buffer = new BufferedReader(inStreamReader);
StringBuffer strbuf = new StringBuffer();
String line;
while((line = buffer.readLine()) != null)
{
strbuf.append(line);
}
String readData = strbuf.toString();
buffer.close();
return readData;
}
/**
* Encodes a hash table to an URL encoded string.
*
* @param inHash The hash table you would like to encode
* @return A URL encoded string.
*/
private String toEncodedString(Hashtable inHash) throws Exception
{
StringBuffer buffer = new StringBuffer();
Enumeration names = inHash.keys();
while(names.hasMoreElements())
{
String name = names.nextElement().toString();
String value = inHash.get(name).toString();
buffer.append(URLEncoder.encode(name, "UTF-8") + "=" + URLEncoder.encode(value, "UTF-8"));
if(names.hasMoreElements())
{
buffer.append("&");
}
}
return buffer.toString();
}
}
class FailedNotification
{
private String url;
private Hashtable message;
private Integer failures = 1;
private Long lastRun = System.currentTimeMillis();
public FailedNotification(String url, Hashtable message)
{
this.url = url;
this.message = message;
}
/**
* @return the url
*/
public String getUrl()
{
return url;
}
/**
* @return the publicMessage
*/
public Hashtable getMessage()
{
return message;
}
/**
* @return the failures
*/
public Integer getFailures()
{
return failures;
}
/**
* @param failures the failures to set
*/
public void increaseFailuresAndSetRunTime()
{
this.failures++;
this.lastRun = System.currentTimeMillis();
}
/**
* @param failures the failures to set
*/
public boolean shouldRun()
{
if(this.failures < 3)
return true;
else if(this.failures < 10 && (System.currentTimeMillis() - this.lastRun > 120000))
return true;
else if(this.failures > 10 && (System.currentTimeMillis() - this.lastRun > 1500000))
return true;
else
return false;
}
}