/* ===============================================================================
*
* Part of the InfoGlue Content Management Platform (www.infoglue.org)
*
* ===============================================================================
*
* Copyright (C)
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License version 2, as published by the
* Free Software Foundation. See the file LICENSE.html for more information.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY, including the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc. / 59 Temple
* Place, Suite 330 / Boston, MA 02111-1307 / USA.
*
* ===============================================================================
*/
package org.infoglue.deliver.util.ioqueue;
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.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.infoglue.cms.applications.common.VisualFormatter;
import org.infoglue.cms.controllers.kernel.impl.simple.TransactionHistoryController;
import org.infoglue.cms.util.NotificationMessage;
import org.infoglue.deliver.util.LiveInstanceMonitor;
/*
* This class keeps track of all live deliver states by polling them with regular intervals.
* It also acts as a message queue so publication messages are resent if not successful the first time.
*/
public class PublicationQueue implements Runnable
{
private final static Logger logger = Logger.getLogger(PublicationQueue.class.getName());
private static PublicationQueue singleton = null;
private Map<String, Set<PublicationQueueBean>> instancePublicationQueueBeans = new HashMap<String, Set<PublicationQueueBean>>();
private Map<String, String> instancePublicationQueueMeta = new HashMap<String, String>();
private boolean keepRunning = true;
private PublicationQueue()
{
}
/**
* Get the singleton and start the thread if not active
*/
public static PublicationQueue getPublicationQueue()
{
if(singleton == null)
{
singleton = new PublicationQueue();
Thread thread = new Thread (singleton);
thread.start();
}
return singleton;
}
public Map<String, Set<PublicationQueueBean>> getInstancePublicationQueueBeans()
{
return instancePublicationQueueBeans;
}
/**
* This method gets when the queue for a specific instance was cleared manually last.
*/
public String getInstancePublicationQueueManualClearTimestamp(String serverURL)
{
String timestamp = instancePublicationQueueMeta.get(serverURL + "_manualClearTimestamp");
if(timestamp != null && !timestamp.equals(""))
{
VisualFormatter vf = new VisualFormatter();
return vf.formatDate(Long.parseLong(timestamp), "yyyy-MM-dd HH:mm:ss");
}
else
return "Never cleared";
}
/**
* This method gets when the queued beans for a specific instance.
*/
public Set<PublicationQueueBean> getInstancePublicationQueueBeans(String serverURL)
{
if(instancePublicationQueueBeans.containsKey(serverURL))
return instancePublicationQueueBeans.get(serverURL);
else
return new HashSet<PublicationQueueBean>();
}
/**
* This method allows you to add a publication queue bean and register it against a certain deliver instance.
* It makes sure each bean is unique.
*/
public void addPublicationQueueBean(String liveInstanceValidationUrl, PublicationQueueBean bean)
{
logger.info("Adding url..");
Set<PublicationQueueBean> publicationQueueBeans = instancePublicationQueueBeans.get(liveInstanceValidationUrl);
if(publicationQueueBeans == null)
{
publicationQueueBeans = Collections.synchronizedSet(new HashSet<PublicationQueueBean>());
instancePublicationQueueBeans.put(liveInstanceValidationUrl, publicationQueueBeans);
}
synchronized(publicationQueueBeans)
{
if(publicationQueueBeans.size() < 1000)
publicationQueueBeans.add(bean);
else
logger.error("Skipping publication queue for this bean as to many beans allready is in queue - must be something very wrong with the instance");
}
logger.info("Done...");
}
/**
* Allows for manual clearing of a live instance queue.
*/
public void clearPublicationQueueBean(String liveInstanceValidationUrl)
{
logger.error("Clearing queue manually for " + liveInstanceValidationUrl);
Set<PublicationQueueBean> publicationQueueBeans = instancePublicationQueueBeans.get(liveInstanceValidationUrl);
if(publicationQueueBeans == null)
{
publicationQueueBeans = Collections.synchronizedSet(new HashSet<PublicationQueueBean>());
instancePublicationQueueBeans.put(liveInstanceValidationUrl, publicationQueueBeans);
}
synchronized(publicationQueueBeans)
{
publicationQueueBeans.clear();
}
instancePublicationQueueMeta.put(liveInstanceValidationUrl + "_manualClearTimestamp", "" + System.currentTimeMillis());
}
/**
* The thread runner - with each run it goes through all the queues and tries to call (POST) the deliver instance
* If the post fails the beans is kept in the queue and retried later if the instance is up at that time.
*/
public synchronized void run()
{
logger.info("Running HttpUniqueRequestQueue...");
while(keepRunning)
{
logger.info("Running..");
Map<String, Set<PublicationQueueBean>> localPublicationQueueBeans = new HashMap<String, Set<PublicationQueueBean>>();
synchronized (instancePublicationQueueBeans)
{
localPublicationQueueBeans.putAll(instancePublicationQueueBeans);
instancePublicationQueueBeans.clear();
}
if(logger.isInfoEnabled())
logger.info("Released lock - got " + localPublicationQueueBeans.size() + " urls.");
Iterator<String> serverBaseUrlsIterator = localPublicationQueueBeans.keySet().iterator();
while(serverBaseUrlsIterator.hasNext())
{
String serverBaseUrl = serverBaseUrlsIterator.next();
Boolean status = LiveInstanceMonitor.getInstance().getServerStatus(serverBaseUrl);
Set<PublicationQueueBean> beans = localPublicationQueueBeans.get(serverBaseUrl);
if(status != null && status)
{
Iterator<PublicationQueueBean> beansIterator = beans.iterator();
while(beansIterator.hasNext())
{
PublicationQueueBean publicationQueueBean = beansIterator.next();
if(logger.isInfoEnabled())
logger.info("publicationQueueBean URL:" + publicationQueueBean.getUrlAddress());
try
{
try
{
String response = postToUrl(publicationQueueBean.getUrlAddress(), publicationQueueBean.getRequestParameters());
if(logger.isInfoEnabled())
logger.info("response:" + response);
NotificationMessage notificationMessage = new NotificationMessage("Publishing notification solved", "Publication", "SYSTEM", NotificationMessage.LIVE_NOTIFICATION_SOLVED, "" + publicationQueueBean.getRequestParameters().get("0.objectId"), "" + serverBaseUrl);
TransactionHistoryController.getController().create(notificationMessage);
beansIterator.remove();
}
catch(Exception e)
{
synchronized (instancePublicationQueueBeans)
{
Map<String, Set<PublicationQueueBean>> currentLiveInstancePublicationQueueBeans = instancePublicationQueueBeans;
Set<PublicationQueueBean> currentPublicationQueueBeans = currentLiveInstancePublicationQueueBeans.get(serverBaseUrl);
if(currentPublicationQueueBeans == null)
{
currentPublicationQueueBeans = new HashSet<PublicationQueueBean>();
currentLiveInstancePublicationQueueBeans.put(serverBaseUrl, currentPublicationQueueBeans);
}
currentPublicationQueueBeans.addAll(beans);
}
logger.error("Error updating cache at " + publicationQueueBean.getUrlAddress() + ". We skip further tries for now and queue it:" + e.getMessage());
}
}
catch (Exception e)
{
logger.error("Error posting data to:" + publicationQueueBean.getUrlAddress() + " - reason:" + e.getMessage());
}
}
}
else
{
if(LiveInstanceMonitor.getInstance().getInstanceStatusMap().containsKey(serverBaseUrl))
{
synchronized (instancePublicationQueueBeans)
{
Map<String, Set<PublicationQueueBean>> currentLiveInstancePublicationQueueBeans = instancePublicationQueueBeans;
Set<PublicationQueueBean> currentPublicationQueueBeans = currentLiveInstancePublicationQueueBeans.get(serverBaseUrl);
if(currentPublicationQueueBeans == null)
{
currentPublicationQueueBeans = new HashSet<PublicationQueueBean>();
currentLiveInstancePublicationQueueBeans.put(serverBaseUrl, currentPublicationQueueBeans);
}
currentPublicationQueueBeans.addAll(beans);
}
//logger.warn("The instance seems to be down. Let's try later instead.");
}
else
{
logger.warn("The live server at " + serverBaseUrl + " does not seem to be registered any more. Removing it's queue.");
}
}
}
try
{
Thread.sleep(10000);
}
catch( InterruptedException e )
{
logger.error("Interrupted Exception caught");
}
}
}
/**
* 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 map) 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(map != null)
{
argString = toEncodedString(map);
}
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();
}
}