package org.dcache.services.info.gathers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import dmg.cells.nucleus.CellMessage;
import dmg.cells.nucleus.CellMessageAnswerable;
import dmg.cells.nucleus.NoRouteToCellException;
import dmg.cells.nucleus.UOID;
import org.dcache.services.info.base.IntegerStateValue;
import org.dcache.services.info.base.StateComposite;
import org.dcache.services.info.base.StatePath;
import org.dcache.services.info.base.StateUpdate;
import org.dcache.services.info.base.StateUpdateManager;
import org.dcache.services.info.base.StringStateValue;
/**
* This Class introduces a number of useful utilities common to all
* CellMessageHandler parsing implementations.
*
* @author Paul Millar <paul.millar@desy.de>
*/
public abstract class CellMessageHandlerSkel implements CellMessageAnswerable
{
private static final Logger LOGGER = LoggerFactory.getLogger(CellMessageHandlerSkel.class);
private static final String SIMPLE_DATE_FORMAT = "MMM d, HH:mm:ss z";
private static final String ISO_8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm'Z'";
/**
* Adds a standard set of metrics that represent some point in time. We add three metrics that
* are (in essence) the same date to allow dumb clients (e.g., xslt) to select which one makes
* sense to them.
* @param update The StateUpdate the three additional metrics will be added to.
* @param parentPath the StatePath of the parent branch.
* @param theTime the Date describing the time to record.
* @param lifetime how long, in seconds, the metric should last.
*/
protected static void addTimeMetrics(StateUpdate update, StatePath parentPath,
Date theTime, long lifetime)
{
// Supply time as seconds since 1970
update.appendUpdate(parentPath.newChild("unix"),
new IntegerStateValue(theTime.getTime() / 1000, lifetime));
DateFormat simpleDateFormat = new SimpleDateFormat(SIMPLE_DATE_FORMAT);
DateFormat iso8601DateFormat = new SimpleDateFormat(ISO_8601_DATE_FORMAT);
iso8601DateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
// Supply the time in a simple format
update.appendUpdate(parentPath.newChild("simple"),
new StringStateValue(simpleDateFormat.format(theTime), lifetime));
// Supply the time in UTC in a standard format
update.appendUpdate(parentPath.newChild("ISO-8601"),
new StringStateValue(iso8601DateFormat.format(theTime), lifetime));
}
private final MessageMetadataRepository<UOID> _msgMetadataRepo;
private final StateUpdateManager _sum;
private String _domain;
public CellMessageHandlerSkel(StateUpdateManager sum,
MessageMetadataRepository<UOID> msgHandlerChain)
{
_sum = sum;
_msgMetadataRepo = msgHandlerChain;
}
/**
* Process the information payload. The metricLifetime gives how long
* the metrics should last, in seconds.
*
* We guarantee that msgPayload is never null and is never instanceof Exception.
*/
public abstract void process(Object msgPayload, long metricLifetime);
/**
* Build a list of items under a specific path. These are recorded as
* StateComposites (branch nodes).
* @param update the StateUpdate to append
* @param parentPath the StatePath pointing to the parent of these items
* @param items an array of items.
* @param metricLifetime how long the metric should last, in seconds.
*/
protected void addItems(StateUpdate update, StatePath parentPath,
Object[] items, long metricLifetime)
{
LOGGER.trace("appending list-items under {}", parentPath);
for (Object item : items) {
String listItem = (String) item;
LOGGER.trace(" adding item {}", listItem);
update.appendUpdate(parentPath
.newChild(listItem), new StateComposite(metricLifetime));
}
}
/**
* Send a StateUpdate object to our State singleton. If we get this wrong, log this
* fact somewhere.
* @param update the StateUpdate to apply to the state tree.
*/
protected void applyUpdates(StateUpdate update)
{
LOGGER.trace("adding update to state's to-do stack with {} updates for {}",
update.count(), getClass().getSimpleName());
_sum.enqueueUpdate(update);
}
/**
* The following methods are needed for CellMessageAnswerable.
*/
/**
* Incoming message: look it up and call the (abstract) process() method.
*/
@Override
public void answerArrived(CellMessage request, CellMessage answer)
{
Object payload = answer.getMessageObject();
if (payload == null) {
LOGGER.warn("ignoring incoming message for {} will null payload",
getClass().getSimpleName());
return;
}
LOGGER.trace("incoming CellMessage received from {}", answer.getSourcePath());
long ttl = _msgMetadataRepo.getMetricTTL(request.getLastUOID());
_msgMetadataRepo.remove(request.getLastUOID());
/**
* If we receive an exception, make a note of it and don't bother the super class.
*/
if (payload instanceof Exception) {
Exception e = (Exception) payload;
LOGGER.info("received exception: {}", e.getMessage());
return;
}
_domain = answer.getSourcePath().getCellDomainName();
process(payload, ttl);
}
/**
* @return the domain from which the message was sent.
*/
public String getDomain()
{
return _domain;
}
/**
* Exception arrived, record it and carry on.
*/
@Override
public void exceptionArrived(CellMessage request, Exception exception)
{
if (exception instanceof NoRouteToCellException) {
LOGGER.info("Sending message to {} failed: {}",
((NoRouteToCellException)exception).getDestinationPath(),
exception.getMessage());
} else {
LOGGER.error("Received remote exception: {}", exception);
}
}
/**
* Timeouts we just ignore.
*/
@Override
public void answerTimedOut(CellMessage request)
{
LOGGER.info("Message timed out");
_msgMetadataRepo.remove(request.getLastUOID());
}
}