/*
* Copyright 2003-2012 Yusuke Yamamoto
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package samurai.web;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import samurai.core.StackLine;
import samurai.core.ThreadDumpSequence;
import samurai.core.ThreadStatistic;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
public class VelocityHtmlRenderer implements Constants {
private ResourceBundle resource = ResourceBundle.getBundle("samurai.web.messages");
public String config_wrapDump = "true";
public String style;
private Template tableView;
private Template threaddumpView;
private Template sequenceView;
private String baseurl;
private Util util = new Util();
public VelocityHtmlRenderer(String style) {
this(style, VelocityHtmlRenderer.class.getProtectionDomain().getCodeSource().getLocation().toString());
if (baseurl.endsWith(".jar")) {
baseurl = "jar:" + baseurl + "!/";
}
baseurl += "samurai/web/images/";
}
public VelocityHtmlRenderer(String style, String baseurl) {
this.style = style;
this.baseurl = baseurl;
Velocity.setProperty("input.encoding", "UTF-8");
Velocity.setProperty("resource.loader", "class");
Velocity.setProperty("class.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
Velocity.setProperty("runtime.log.logsystem.class", "samurai.web.NullVelocityLogger");
try {
Velocity.init();
tableView = Velocity.getTemplate("samurai/web/table.vm");
threaddumpView = Velocity.getTemplate("samurai/web/threaddump.vm");
sequenceView = Velocity.getTemplate("samurai/web/sequence.vm");
} catch (Exception ex) {
ex.printStackTrace();
}
}
public String render(ThreadFilter filter, ThreadStatistic statistic, Map myContext) {
VelocityContext context = new VelocityContext(new VelocityContext(myContext));
Writer writer = new StringWriter(20480);
context.put("resource", resource);
context.put("style", style);
context.put("wrap", config_wrapDump);
context.put("stats", statistic);
context.put("filter", filter);
context.put("util", util);
context.put("baseurl", baseurl);
try {
if (filter.isTableView()) {
tableView.merge(context, writer);
} else if (filter.isThreadDumpView()) {
threaddumpView.merge(context, writer);
} else if (filter.isSequenceView()) {
sequenceView.merge(context, writer);
}
writer.close();
return writer.toString();
} catch (Exception ex) {
ex.printStackTrace();
throw new AssertionError("should never happen");
}
}
public int length(Object[] array) {
return array.length;
}
/**
* Saves threadstatistic as html files.<br>
* @param stats statistics to be saved.
* @param directory Directory to save the html files.
* @param listener
* @throws IOException
*/
public void saveTo(ThreadStatistic stats, File directory, ProgressListener listener) throws IOException {
if(null != directory){
File tableDir = new File(directory.getAbsolutePath() + File.separator + Constants.MODE_TABLE);
File fullDir = new File(directory.getAbsolutePath() + File.separator + Constants.MODE_FULL);
File sequenceDir = new File(directory.getAbsolutePath() + File.separator + Constants.MODE_SEQUENCE);
directory.mkdirs();
tableDir.mkdirs();
fullDir.mkdirs();
sequenceDir.mkdirs();
}
ThreadFilter filter = new ThreadFilter();
ThreadDumpSequence[] st = stats.getStackTracesAsArray();
int count = stats.getFullThreadDumpCount() * 2 + st.length * 2 + 2;
int progress = 0;
listener.notifyProgress(progress++, count);
//save index page
saveAs(directory, "index.html", "<html><head><meta http-equiv=\"Refresh\" content=\"0;URL=./table/index.html\"/></head><body></body></html>");
listener.notifyProgress(progress++, count);
filter.setMode(Constants.MODE_TABLE);
filter.setThreadId(stats.getFirstThreadId());
filter.setFullThreadIndex(0);
Map velocityContext = new HashMap(2);
//save table view
saveAs(directory, Constants.MODE_TABLE + "/index.html", stats, filter, velocityContext);
listener.notifyProgress(progress++, count);
//save full thread dump view
filter.setShrinkIdle(false);
filter.setMode(Constants.MODE_FULL);
do {
for (int i = 0; i < stats.getFullThreadDumpCount(); i++) {
filter.setFullThreadIndex(i);
saveAs(directory, Constants.MODE_FULL + "/index-" + i + "_shrink-" + filter.getShrinkIdle() + ".html", stats, filter, velocityContext);
listener.notifyProgress(progress++, count);
}
filter.setShrinkIdle(!filter.getShrinkIdle());
} while (filter.getShrinkIdle());
//save sequence thread dump view
filter.setShrinkIdle(false);
filter.setMode(Constants.MODE_SEQUENCE);
do {
for (ThreadDumpSequence aSt : st) {
filter.setThreadId(aSt.getId());
saveAs(directory, Constants.MODE_SEQUENCE + "/threadId-" + filter.getThreadId() + "_shrink-" + filter.getShrinkIdle() + ".html", stats, filter, velocityContext);
listener.notifyProgress(progress++, count);
}
filter.setShrinkIdle(!filter.getShrinkIdle());
} while (filter.getShrinkIdle());
}
public void saveAs(File dir, String fileName, ThreadStatistic stats, ThreadFilter filter, Map velocityContext) throws IOException {
saveAs(dir, fileName, render(filter, stats, velocityContext));
}
public void saveAs(File dir, String fileName, String utf8Content) throws IOException {
if (null != dir) {
FileOutputStream fos = null;
BufferedWriter writer = null;
try {
fos = new FileOutputStream(dir + File.separator + fileName);
writer = new BufferedWriter(new OutputStreamWriter(fos, "UTF-8"));
writer.write(utf8Content);
writer.close();
} finally {
if (null != writer) {
writer.close();
}
if (null != fos) {
fos.close();
}
}
}
}
public static class Util {
public String asHTML(StackLine line, int index, boolean shrink) {
if (line.isHoldingLock()) {
String objId = line.getLockedObjectId();
int objIdBegin = line.getLine().indexOf(objId);
StringBuffer html = new StringBuffer();
html.append(escape(line.getLine().substring(0, objIdBegin)));
if (-1 != index) {
html.append("<a name=\"").append(objId).append("_").append(index).append("\"></a>");
} else {
html.append("<a name=\"").append(objId).append("\"></a>");
}
html.append(objId);
html.append(escape(line.getLine().substring(objIdBegin + objId.length())));
return html.toString();
} else
if (line.isTryingToGetLock() && null != line.getLockedObjectId()) {
String objId = line.getLockedObjectId();
int objIdBegin = line.getLine().indexOf(objId);
StringBuffer html = new StringBuffer();
html.append(escape(line.getLine().substring(0, objIdBegin)));
if (-1 != index) {
html.append("<a href=\"../sequence/threadId-").append(line.getBlockerId()).append("_shrink-").append(shrink).append(".html#").append(objId).append("_").append(index).append("\">");
} else {
html.append("<a href=\"#").append(objId).append("\">");
}
html.append(objId);
html.append("</a>");
html.append(escape(line.getLine().substring(objIdBegin + objId.length())));
return html.toString();
} else {
return escape(line.getLine());
}
}
public String asHTML(StackLine line) {
return asHTML(line, -1, false);
}
public String escape(String from) {
int lessThanIndex = from.indexOf("<");
int greaterThanIndex = from.indexOf(">");
if(-1 == lessThanIndex && -1 == greaterThanIndex){
return from;
}
StringBuffer to = new StringBuffer(from);
while (-1 != lessThanIndex) {
to.replace(lessThanIndex, lessThanIndex + 1, "<");
lessThanIndex = to.indexOf("<", lessThanIndex + 4);
if(greaterThanIndex != -1){
greaterThanIndex += 3;
}
}
while (-1 != greaterThanIndex) {
to.replace(greaterThanIndex, greaterThanIndex + 1, ">");
greaterThanIndex = to.indexOf(">", greaterThanIndex + 4);
}
return to.toString();
}
}
}