package com.orbitz.monitoring.lib.renderer;
import com.orbitz.monitoring.api.Attribute;
import com.orbitz.monitoring.api.CompositeMonitor;
import com.orbitz.monitoring.api.Monitor;
import org.apache.commons.beanutils.LazyDynaBean;
import org.apache.log4j.Logger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* EventPatternMonitorRenderer renders a Monitor recursively based on name, vmid and failed
* attribute values.
*
* @author Matt O'Keefe
*/
public class EventPatternMonitorRenderer {
private static final Logger logger = Logger.getLogger(EventPatternMonitorRenderer.class);
private static final char delimeter = '|';
private Set monitorsToSkip;
private List allowedAttributes;
public EventPatternMonitorRenderer() {
monitorsToSkip = new HashSet();
allowedAttributes = new ArrayList();
}
public EventPatternMonitorRenderer(final List allowedAttributes) {
this();
this.allowedAttributes = allowedAttributes;
}
public Set getMonitorsToSkip() {
return monitorsToSkip;
}
public void setMonitorsToSkip(Set monitorsToSkip) {
this.monitorsToSkip.addAll(monitorsToSkip);
}
public List getAllowedAttributes() {
return allowedAttributes;
}
public void setAllowedAttributes(final List allowedAttributes) {
this.allowedAttributes = allowedAttributes;
}
/**
* Renders a Monitor recursively based on vmid, name and failed attrs.
*
* @param monitor
* @return a String
*/
public String renderMonitor(Monitor monitor) {
StringBuffer renderBuffer = new StringBuffer();
renderMonitorToBuffer(monitor, renderBuffer, 0);
return renderBuffer.toString();
}
protected void renderMonitorToBuffer(Monitor monitor, StringBuffer buffer, int indentLevel) {
buffer.append('\n').append(indentString(indentLevel));
Iterator it = allowedAttributes.iterator();
while (it.hasNext()) {
String attributeName = (String) it.next();
if (attributeName.equals(Attribute.FAILURE_THROWABLE)) {
assignFailureThrowable(monitor, buffer);
} else if (monitor.hasAttribute(attributeName)) {
Object attributeValue = monitor.get(attributeName);
buffer.append(attributeValue).append(delimeter);
}
}
if (buffer.lastIndexOf(String.valueOf(delimeter)) == buffer.length() - 1) {
buffer.deleteCharAt(buffer.length() - 1); //the last delimeter
}
if (monitor instanceof CompositeMonitor) {
CompositeMonitor compositeMonitor = (CompositeMonitor) monitor;
Collection childMonitors = compositeMonitor.getChildMonitors();
if (!childMonitors.isEmpty()) {
StringBuffer lastBuffer = null;
int monitorCount = 1;
for (Iterator i = childMonitors.iterator(); i.hasNext();) {
final Monitor childMonitor = (Monitor) i.next();
if (shouldRender(childMonitor)) {
final StringBuffer childBuffer = new StringBuffer();
renderMonitorToBuffer(childMonitor, childBuffer, indentLevel + 1);
if (lastBuffer != null && lastBuffer.toString().contentEquals(childBuffer)) {
monitorCount++;
} else {
if (lastBuffer != null) {
addChildToBuffer(buffer, lastBuffer, monitorCount);
}
lastBuffer = childBuffer;
monitorCount = 1;
}
}
}
if (lastBuffer != null) {
addChildToBuffer(buffer, lastBuffer, monitorCount);
}
}
}
}
private void assignFailureThrowable(Monitor monitor, StringBuffer buffer) {
if (! monitor.hasAttribute(Attribute.FAILURE_THROWABLE)) {
return;
}
Object o = monitor.get(Attribute.FAILURE_THROWABLE);
if (Throwable.class.isAssignableFrom(o.getClass())) {
Throwable t = (Throwable) o;
HashSet causeSet = new HashSet();
causeSet.add(t);
while (t.getCause() != null) {
if (!causeSet.contains(t.getCause())) {
t = t.getCause();
causeSet.add(t);
} else {
logger.warn("Unexpected cycle for failureThrowable field");
logger.warn(formatThrowable(t));
break;
}
}
buffer.append(t.getClass().getName());
} else if (LazyDynaBean.class.isAssignableFrom(o.getClass())) {
// serialized monitors will have throwableFailure fields that are decomposed
// into LazyDynaBean classes by the ReflectiveDecomposer, in other words this
// block will handle Throwable's created in remote VM's
LazyDynaBean ex = (LazyDynaBean) o;
HashSet causeSet = new HashSet();
causeSet.add(ex);
while ((ex.get("cause") != null)
&& (LazyDynaBean.class.isAssignableFrom(ex.get("cause").getClass()))) {
if (!causeSet.contains(ex.get("cause"))) {
ex = (LazyDynaBean) ex.get("cause");
causeSet.add(ex);
} else {
logger.warn("Unexpected cycle for failureThrowable field");
logger.warn(formatThrowable(ex));
break;
}
}
// the class field of any object is handled by the ClassDecomposer and
// should therefore be of type String
Object className = ex.get("class");
if ((className != null) && (String.class.isAssignableFrom(className.getClass()))) {
buffer.append(className);
} else {
logger.warn("Unexpected type for failureThrowable.class field");
buffer.append("failed");
}
} else {
logger.warn("Unexpected type for failureThrowable field : " + o.getClass().getName());
buffer.append("failed");
}
}
protected String indentString(int indentLevel) {
StringBuffer buf = new StringBuffer(indentLevel * 2);
while (indentLevel > 0) {
buf.append(" ");
indentLevel--;
}
return buf.toString();
}
// adds the child buffer to the real buffer
protected void addChildToBuffer(StringBuffer buffer, StringBuffer child, int count) {
if (count > 1) {
final int endOfFirst = child.indexOf("\n", 1);
if (endOfFirst > 0) {
child.insert(endOfFirst, String.valueOf(delimeter) + count + " occurences");
} else {
child.append(delimeter).append(count).append(" occurences");
}
}
buffer.append(child);
}
// returns true if a monitor should be rendered, otherwise false
protected boolean shouldRender(Monitor monitor) {
boolean render = true;
if (monitor.hasAttribute(Attribute.NAME)) {
String name = monitor.getAsString(Attribute.NAME);
Iterator it = monitorsToSkip.iterator();
while (render && it.hasNext()) {
String sName = (String) it.next();
if (name.indexOf(sName) > -1) {
render = false;
}
}
}
return render;
}
// used to prevent the stack trace from entering an infinite loop
private String formatThrowable(Throwable t) {
final StringBuffer sb = new StringBuffer();
sb.append(t.getClass().getName()).append(": ").append(t.getMessage());
final StackTraceElement[] trace = t.getStackTrace();
for (int i = 0; i < trace.length; i++) {
sb.append("\n ").append(trace[i].getClassName())
.append(".").append(trace[i].getMethodName());
if (trace[i].isNativeMethod()) {
sb.append("(Native Method)");
} else if (trace[i].getFileName() != null) {
sb.append("(").append(trace[i].getFileName());
if (trace[i].getLineNumber() >= 0) {
sb.append(":").append(trace[i].getLineNumber());
}
sb.append(")");
} else {
sb.append("(Unknown Source)");
}
}
return sb.toString();
}
// used to format dynabeans into a standard stack trace
private String formatThrowable(LazyDynaBean ex) {
final StringBuffer sb = new StringBuffer();
Object exceptionClass = (ex.get("class") == null) ? "(Unknown Exception)" : ex.get("class");
sb.append(exceptionClass).append(": ").append(ex.get("message"));
final Object[] trace = (Object[]) ex.get("stackTrace");
for (int i = 0; i < trace.length; i++) {
final LazyDynaBean bean = (LazyDynaBean) trace[i];
final Object nativeMethod = bean.get("nativeMethod");
final Object lineNumber = bean.get("lineNumber");
sb.append("\n ").append(bean.get("className"))
.append(".").append(bean.get("methodName"));
/* if this doesn't work, lineNumber == -2 can be used instead */
if (Boolean.class.isAssignableFrom(nativeMethod.getClass())
&& ((Boolean) nativeMethod).booleanValue()) {
sb.append("(Native Method)");
} else if (bean.get("fileName") != null) {
sb.append("(").append(bean.get("fileName"));
if (Integer.class.isAssignableFrom(lineNumber.getClass())
&& ((Integer) lineNumber).intValue() >= 0) {
sb.append(":").append(bean.get("lineNumber"));
}
sb.append(")");
} else {
sb.append("(Unknown Source)");
}
}
return sb.toString();
}
}