/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.jmeter.visualizers;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JEditorPane;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.JTree;
import javax.swing.ToolTipManager;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.ComponentView;
import javax.swing.text.Document;
import javax.swing.text.EditorKit;
import javax.swing.text.Element;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.apache.jmeter.assertions.AssertionResult;
import org.apache.jmeter.samplers.Clearable;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.util.XPathUtil;
import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.jorphan.util.JOrphanUtils;
import org.apache.log.Logger;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.tidy.Tidy;
import org.xml.sax.SAXException;
/**
* Allows the tester to view the textual response from sampling an Entry. This
* also allows to "single step through" the sampling process via a nice
* "Continue" button.
*
* Created 2001/07/25
*/
public class ViewResultsFullVisualizer extends AbstractVisualizer
implements ActionListener, TreeSelectionListener, Clearable
{
private static final Logger log = LoggingManager.getLoggerForClass();
// Maximum size that we will display
private static final int MAX_DISPLAY_SIZE =
JMeterUtils.getPropDefault("view.results.tree.max_size", 200 * 1024); // $NON-NLS-1$
// N.B. these are not multi-threaded, so don't make it static
private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); // ISO format $NON-NLS-1$
private static final String NL = "\n"; // $NON-NLS-1$
private static final byte[] XML_PFX = "<?xml ".getBytes(); // $NON-NLS-1$
public static final Color SERVER_ERROR_COLOR = Color.red;
public static final Color CLIENT_ERROR_COLOR = Color.blue;
public static final Color REDIRECT_COLOR = Color.green;
private static final String TEXT_HTML = "text/html"; // $NON-NLS-1$
private static final String HTML_COMMAND = "html"; // $NON-NLS-1$
private static final String JSON_COMMAND = "json"; // $NON-NLS-1$
private static final String XML_COMMAND = "xml"; // $NON-NLS-1$
private static final String TEXT_COMMAND = "text"; // $NON-NLS-1$
private static final String STYLE_SERVER_ERROR = "ServerError"; // $NON-NLS-1$
private static final String STYLE_CLIENT_ERROR = "ClientError"; // $NON-NLS-1$
private static final String STYLE_REDIRECT = "Redirect"; // $NON-NLS-1$
private boolean textMode = true;
private static final String ESC_CHAR_REGEX = "\\\\[\"\\\\/bfnrt]|\\\\u[0-9A-Fa-f]{4}"; // $NON-NLS-1$
private static final String NORMAL_CHARACTER_REGEX = "[^\"\\\\]"; // $NON-NLS-1$
private static final String STRING_REGEX = "\"(" + ESC_CHAR_REGEX + "|" + NORMAL_CHARACTER_REGEX + ")*\""; // $NON-NLS-1$
// This 'other value' regex is deliberately weak, even accepting an empty string, to be useful when reporting malformed data.
private static final String OTHER_VALUE_REGEX = "[^\\{\\[\\]\\}\\,]*"; // $NON-NLS-1$
private static final String VALUE_OR_PAIR_REGEX = "((" + STRING_REGEX + "\\s*:)?\\s*(" + STRING_REGEX + "|" + OTHER_VALUE_REGEX + ")\\s*,?\\s*)"; // $NON-NLS-1$
private static final Pattern VALUE_OR_PAIR_PATTERN = Pattern.compile(VALUE_OR_PAIR_REGEX);
// set default command to Text
private String command = TEXT_COMMAND;
// Keep copies of the two editors needed
private static final EditorKit customisedEditor = new LocalHTMLEditorKit();
private static final EditorKit defaultHtmlEditor = JEditorPane.createEditorKitForContentType(TEXT_HTML);
private DefaultMutableTreeNode root;
private DefaultTreeModel treeModel;
private JTextPane stats;
private JEditorPane results;
private JScrollPane resultsScrollPane;
private JPanel resultsPane;
private JLabel imageLabel;
private JTextArea sampleDataField;
private JPanel requestPane;
private JRadioButton textButton;
private JRadioButton htmlButton;
private JRadioButton jsonButton;
private JRadioButton xmlButton;
private JCheckBox downloadAll;
private JTree jTree;
private JTabbedPane rightSide;
private static final ImageIcon imageSuccess = JMeterUtils.getImage(
JMeterUtils.getPropDefault("viewResultsTree.success", //$NON-NLS-1$
"icon_success_sml.gif")); //$NON-NLS-1$
private static final ImageIcon imageFailure = JMeterUtils.getImage(
JMeterUtils.getPropDefault("viewResultsTree.failure", //$NON-NLS-1$
"icon_warning_sml.gif")); //$NON-NLS-1$
public ViewResultsFullVisualizer() {
super();
log.debug("Start : ViewResultsFullVisualizer1");
init();
log.debug("End : ViewResultsFullVisualizer1");
}
public void add(SampleResult res) {
updateGui(res);
}
public String getLabelResource() {
return "view_results_tree_title"; // $NON-NLS-1$
}
/**
* Update the visualizer with new data.
*/
private synchronized void updateGui(SampleResult res) {
log.debug("Start : updateGui1");
if (log.isDebugEnabled()) {
log.debug("updateGui1 : sample result - " + res);
}
// Add sample
DefaultMutableTreeNode currNode = new DefaultMutableTreeNode(res);
treeModel.insertNodeInto(currNode, root, root.getChildCount());
addSubResults(currNode, res);
// Add any assertion that failed as children of the sample node
AssertionResult assertionResults[] = res.getAssertionResults();
int assertionIndex = currNode.getChildCount();
for (int j = 0; j < assertionResults.length; j++) {
AssertionResult item = assertionResults[j];
if (item.isFailure() || item.isError()) {
DefaultMutableTreeNode assertionNode = new DefaultMutableTreeNode(item);
treeModel.insertNodeInto(assertionNode, currNode, assertionIndex++);
}
}
if (root.getChildCount() == 1) {
jTree.expandPath(new TreePath(root));
}
log.debug("End : updateGui1");
}
private void addSubResults(DefaultMutableTreeNode currNode, SampleResult res) {
SampleResult[] subResults = res.getSubResults();
int leafIndex = 0;
for (int i = 0; i < subResults.length; i++) {
SampleResult child = subResults[i];
if (log.isDebugEnabled()) {
log.debug("updateGui1 : child sample result - " + child);
}
DefaultMutableTreeNode leafNode = new DefaultMutableTreeNode(child);
treeModel.insertNodeInto(leafNode, currNode, leafIndex++);
addSubResults(leafNode, child);
// Add any assertion that failed as children of the sample node
AssertionResult assertionResults[] = child.getAssertionResults();
int assertionIndex = leafNode.getChildCount();
for (int j = 0; j < assertionResults.length; j++) {
AssertionResult item = assertionResults[j];
if (item.isFailure() || item.isError()) {
DefaultMutableTreeNode assertionNode = new DefaultMutableTreeNode(item);
treeModel.insertNodeInto(assertionNode, leafNode, assertionIndex++);
}
}
}
}
/**
* Clears the visualizer.
*/
public void clearData() {
log.debug("Start : clear1");
if (log.isDebugEnabled()) {
log.debug("clear1 : total child - " + root.getChildCount());
}
while (root.getChildCount() > 0) {
// the child to be removed will always be 0 'cos as the nodes are
// removed the nth node will become (n-1)th
treeModel.removeNodeFromParent((DefaultMutableTreeNode) root.getChildAt(0));
}
results.setText("");// Response Data // $NON-NLS-1$
sampleDataField.setText("");// Request Data // $NON-NLS-1$
log.debug("End : clear1");
}
/**
* Returns the description of this visualizer.
*
* @return description of this visualizer
*/
public String toString() {
String desc = "Shows the text results of sampling in tree form";
if (log.isDebugEnabled()) {
log.debug("toString1 : Returning description - " + desc);
}
return desc;
}
/**
* Sets the right pane to correspond to the selected node of the left tree.
*/
public void valueChanged(TreeSelectionEvent e) {
log.debug("Start : valueChanged1");
DefaultMutableTreeNode node = (DefaultMutableTreeNode) jTree.getLastSelectedPathComponent();
if (log.isDebugEnabled()) {
log.debug("valueChanged : selected node - " + node);
}
StyledDocument statsDoc = stats.getStyledDocument();
try {
statsDoc.remove(0, statsDoc.getLength());
sampleDataField.setText(""); // $NON-NLS-1$
results.setText(""); // $NON-NLS-1$
if (node != null) {
Object userObject = node.getUserObject();
if(userObject instanceof SampleResult) {
SampleResult res = (SampleResult) userObject;
// We are displaying a SampleResult
setupTabPaneForSampleResult();
if (log.isDebugEnabled()) {
log.debug("valueChanged1 : sample result - " + res);
}
// load time label
log.debug("valueChanged1 : load time - " + res.getTime());
String sd = res.getSamplerData();
if (sd != null) {
String rh = res.getRequestHeaders();
if (rh != null) {
StringBuffer sb = new StringBuffer(sd.length() + rh.length()+20);
sb.append(sd);
sb.append("\nRequest Headers:\n");
sb.append(rh);
sd = sb.toString();
}
sampleDataField.setText(sd);
}
StringBuffer statsBuff = new StringBuffer(200);
statsBuff.append("Thread Name: ").append(res.getThreadName()).append(NL);
String startTime = dateFormat.format(new Date(res.getStartTime()));
statsBuff.append("Sample Start: ").append(startTime).append(NL);
statsBuff.append("Load time: ").append(res.getTime()).append(NL);
statsBuff.append("Latency: ").append(res.getLatency()).append(NL);
statsBuff.append("Size in bytes: ").append(res.getBytes()).append(NL);
statsBuff.append("Sample Count: ").append(res.getSampleCount()).append(NL);
statsBuff.append("Error Count: ").append(res.getErrorCount()).append(NL);
statsDoc.insertString(statsDoc.getLength(), statsBuff.toString(), null);
statsBuff = new StringBuffer(); //reset for reuse
String responseCode = res.getResponseCode();
log.debug("valueChanged1 : response code - " + responseCode);
int responseLevel = 0;
if (responseCode != null) {
try {
responseLevel = Integer.parseInt(responseCode) / 100;
} catch (NumberFormatException numberFormatException) {
// no need to change the foreground color
}
}
Style style = null;
switch (responseLevel) {
case 3:
style = statsDoc.getStyle(STYLE_REDIRECT);
break;
case 4:
style = statsDoc.getStyle(STYLE_CLIENT_ERROR);
break;
case 5:
style = statsDoc.getStyle(STYLE_SERVER_ERROR);
break;
}
statsBuff.append("Response code: ").append(responseCode).append(NL);
statsDoc.insertString(statsDoc.getLength(), statsBuff.toString(), style);
statsBuff = new StringBuffer(100); //reset for reuse
// response message label
String responseMsgStr = res.getResponseMessage();
log.debug("valueChanged1 : response message - " + responseMsgStr);
statsBuff.append("Response message: ").append(responseMsgStr).append(NL);
statsBuff.append(NL).append("Response headers:").append(NL);
statsBuff.append(res.getResponseHeaders()).append(NL);
statsDoc.insertString(statsDoc.getLength(), statsBuff.toString(), null);
statsBuff = null; // Done
// get the text response and image icon
// to determine which is NOT null
if ((SampleResult.TEXT).equals(res.getDataType())) // equals(null) is OK
{
String response = getResponseAsString(res);
if (command.equals(TEXT_COMMAND)) {
showTextResponse(response);
} else if (command.equals(HTML_COMMAND)) {
showRenderedResponse(response, res);
} else if (command.equals(JSON_COMMAND)) {
showRenderJSONResponse(response);
} else if (command.equals(XML_COMMAND)) {
showRenderXMLResponse(res);
}
} else {
byte[] responseBytes = res.getResponseData();
if (responseBytes != null) {
showImage(new ImageIcon(responseBytes)); //TODO implement other non-text types
}
}
}
else if(userObject instanceof AssertionResult) {
AssertionResult res = (AssertionResult) userObject;
// We are displaying an AssertionResult
setupTabPaneForAssertionResult();
if (log.isDebugEnabled()) {
log.debug("valueChanged1 : sample result - " + res);
}
StringBuffer statsBuff = new StringBuffer(100);
statsBuff.append("Assertion error: ").append(res.isError()).append(NL);
statsBuff.append("Assertion failure: ").append(res.isFailure()).append(NL);
statsBuff.append("Assertion failure message : ").append(res.getFailureMessage()).append(NL);
statsDoc.insertString(statsDoc.getLength(), statsBuff.toString(), null);
statsBuff = null;
}
}
} catch (BadLocationException exc) {
log.error("Error setting statistics text", exc);
stats.setText("");
}
log.debug("End : valueChanged1");
}
private void showImage(Icon image) {
imageLabel.setIcon(image);
resultsScrollPane.setViewportView(imageLabel);
textButton.setEnabled(false);
htmlButton.setEnabled(false);
jsonButton.setEnabled(false);
xmlButton.setEnabled(false);
}
protected void showTextResponse(String response) {
results.setContentType("text/plain"); // $NON-NLS-1$
results.setText(response == null ? "" : response); // $NON-NLS-1$
results.setCaretPosition(0);
resultsScrollPane.setViewportView(results);
textButton.setEnabled(true);
htmlButton.setEnabled(true);
jsonButton.setEnabled(true);
xmlButton.setEnabled(true);
}
// It might be useful also to make this available in the 'Request' tab, for
// when posting JSON.
private static String prettyJSON(String json) {
StringBuffer pretty = new StringBuffer(json.length() * 2); // Educated guess
final String tab = ": "; // $NON-NLS-1$
StringBuffer index = new StringBuffer();
String nl = ""; // $NON-NLS-1$
Matcher valueOrPair = VALUE_OR_PAIR_PATTERN.matcher(json);
boolean misparse = false;
for (int i = 0; i < json.length(); ) {
final char currentChar = json.charAt(i);
if ((currentChar == '{') || (currentChar == '[')) {
pretty.append(nl).append(index).append(currentChar);
i++;
index.append(tab);
misparse = false;
}
else if ((currentChar == '}') || (currentChar == ']')) {
if (index.length() > 0) {
index.delete(0, tab.length());
}
pretty.append(nl).append(index).append(currentChar);
i++;
int j = i;
while ((j < json.length()) && Character.isWhitespace(json.charAt(j))) {
j++;
}
if ((j < json.length()) && (json.charAt(j) == ',')) {
pretty.append(","); // $NON-NLS-1$
i=j+1;
}
misparse = false;
}
else if (valueOrPair.find(i) && valueOrPair.group().length() > 0) {
pretty.append(nl).append(index).append(valueOrPair.group());
i=valueOrPair.end();
misparse = false;
}
else {
if (!misparse) {
pretty.append(nl).append("- Parse failed from:");
}
pretty.append(currentChar);
i++;
misparse = true;
}
nl = "\n"; // $NON-NLS-1$
}
return pretty.toString();
}
private void showRenderJSONResponse(String response) {
results.setContentType("text/plain"); // $NON-NLS-1$
results.setText(response == null ? "" : prettyJSON(response));
results.setCaretPosition(0);
resultsScrollPane.setViewportView(results);
textButton.setEnabled(true);
htmlButton.setEnabled(true);
jsonButton.setEnabled(true);
xmlButton.setEnabled(true);
}
private void showRenderXMLResponse(SampleResult res) {
results.setContentType("text/xml"); // $NON-NLS-1$
results.setCaretPosition(0);
byte[] source = res.getResponseData();
final ByteArrayInputStream baIS = new ByteArrayInputStream(source);
for(int i=0; i<source.length-XML_PFX.length; i++){
if (JOrphanUtils.startsWith(source, XML_PFX, i)){
baIS.skip(i);// Skip the leading bytes (if any)
break;
}
}
Component view = results;
// there is also a javax.swing.text.Document class.
org.w3c.dom.Document document = null;
StringWriter sw = new StringWriter();
Tidy tidy = XPathUtil.makeTidyParser(true, true, true, sw);
document = tidy.parseDOM(baIS, null);
document.normalize();
if (tidy.getParseErrors() > 0) {
showErrorMessageDialog(sw.toString(),
"Tidy: " + tidy.getParseErrors() + " errors, " + tidy.getParseWarnings() + " warnings",
JOptionPane.WARNING_MESSAGE);
}
JPanel domTreePanel = new DOMTreePanel(document);
view = domTreePanel;
resultsScrollPane.setViewportView(view);
textButton.setEnabled(true);
htmlButton.setEnabled(true);
jsonButton.setEnabled(true);
xmlButton.setEnabled(true);
}
private static String getResponseAsString(SampleResult res) {
String response = null;
if ((SampleResult.TEXT).equals(res.getDataType())) {
// Showing large strings can be VERY costly, so we will avoid
// doing so if the response
// data is larger than 200K. TODO: instead, we could delay doing
// the result.setText
// call until the user chooses the "Response data" tab. Plus we
// could warn the user
// if this happens and revert the choice if he doesn't confirm
// he's ready to wait.
int len = res.getResponseData().length;
if (MAX_DISPLAY_SIZE > 0 && len > MAX_DISPLAY_SIZE) {
response = "Response too large to be displayed. Size: " + len + " > Max: "+MAX_DISPLAY_SIZE;
log.warn(response);
} else {
response = res.getResponseDataAsString();
}
}
return response;
}
/**
* Display the response as text or as rendered HTML. Change the text on the
* button appropriate to the current display.
*
* @param e
* the ActionEvent being processed
*/
public void actionPerformed(ActionEvent e) {
command = e.getActionCommand();
if (command != null
&& (command.equals(TEXT_COMMAND) || command.equals(HTML_COMMAND)
|| command.equals(JSON_COMMAND) || command.equals(XML_COMMAND))) {
textMode = command.equals(TEXT_COMMAND);
DefaultMutableTreeNode node = (DefaultMutableTreeNode) jTree.getLastSelectedPathComponent();
if (node == null) {
results.setText("");
return;
}
SampleResult res = (SampleResult) node.getUserObject();
String response = getResponseAsString(res);
if (command.equals(TEXT_COMMAND)) {
showTextResponse(response);
} else if (command.equals(HTML_COMMAND)) {
showRenderedResponse(response, res);
} else if (command.equals(JSON_COMMAND)) {
showRenderJSONResponse(response);
} else if (command.equals(XML_COMMAND)) {
showRenderXMLResponse(res);
}
}
}
protected void showRenderedResponse(String response, SampleResult res) {
if (response == null) {
results.setText("");
return;
}
int htmlIndex = response.indexOf("<HTML"); // could be <HTML lang=""> // $NON-NLS-1$
// Look for a case variation
if (htmlIndex < 0) {
htmlIndex = response.indexOf("<html"); // ditto // $NON-NLS-1$
}
// If we still can't find it, just try using all of the text
if (htmlIndex < 0) {
htmlIndex = 0;
}
String html = response.substring(htmlIndex);
/*
* To disable downloading and rendering of images and frames, enable the
* editor-kit. The Stream property can then be
*/
// Must be done before setContentType
results.setEditorKitForContentType(TEXT_HTML, downloadAll.isSelected() ? defaultHtmlEditor : customisedEditor);
results.setContentType(TEXT_HTML);
if (downloadAll.isSelected()) {
// Allow JMeter to render frames (and relative images)
// Must be done after setContentType [Why?]
results.getDocument().putProperty(Document.StreamDescriptionProperty, res.getURL());
}
/*
* Get round problems parsing <META http-equiv='content-type'
* content='text/html; charset=utf-8'> See
* http://issues.apache.org/bugzilla/show_bug.cgi?id=23315
*
* Is this due to a bug in Java?
*/
results.getDocument().putProperty("IgnoreCharsetDirective", Boolean.TRUE); // $NON-NLS-1$
results.setText(html);
results.setCaretPosition(0);
resultsScrollPane.setViewportView(results);
textButton.setEnabled(true);
htmlButton.setEnabled(true);
jsonButton.setEnabled(true);
xmlButton.setEnabled(true);
}
private Component createHtmlOrTextPane() {
ButtonGroup group = new ButtonGroup();
textButton = new JRadioButton(JMeterUtils.getResString("view_results_render_text")); // $NON-NLS-1$
textButton.setActionCommand(TEXT_COMMAND);
textButton.addActionListener(this);
textButton.setSelected(textMode);
group.add(textButton);
htmlButton = new JRadioButton(JMeterUtils.getResString("view_results_render_html")); // $NON-NLS-1$
htmlButton.setActionCommand(HTML_COMMAND);
htmlButton.addActionListener(this);
htmlButton.setSelected(!textMode);
group.add(htmlButton);
jsonButton = new JRadioButton(JMeterUtils.getResString("view_results_render_json")); // $NON-NLS-1$
jsonButton.setActionCommand(JSON_COMMAND);
jsonButton.addActionListener(this);
jsonButton.setSelected(!textMode);
group.add(jsonButton);
xmlButton = new JRadioButton(JMeterUtils.getResString("view_results_render_xml")); // $NON-NLS-1$
xmlButton.setActionCommand(XML_COMMAND);
xmlButton.addActionListener(this);
xmlButton.setSelected(!textMode);
group.add(xmlButton);
downloadAll = new JCheckBox(JMeterUtils.getResString("view_results_render_embedded")); // $NON-NLS-1$
JPanel pane = new JPanel();
pane.add(textButton);
pane.add(htmlButton);
pane.add(xmlButton);
pane.add(jsonButton);
pane.add(downloadAll);
return pane;
}
/**
* Initialize this visualizer
*/
private void init() {
setLayout(new BorderLayout(0, 5));
setBorder(makeBorder());
add(makeTitlePanel(), BorderLayout.NORTH);
Component leftSide = createLeftPanel();
rightSide = new JTabbedPane();
// Add the common tab
rightSide.addTab(JMeterUtils.getResString("view_results_tab_sampler"), createResponseMetadataPanel()); // $NON-NLS-1$
// Create the panels for the other tabs
requestPane = createRequestPanel();
resultsPane = createResponseDataPanel();
JSplitPane mainSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftSide, rightSide);
add(mainSplit, BorderLayout.CENTER);
}
private void setupTabPaneForSampleResult() {
// Set the title for the first tab
rightSide.setTitleAt(0, JMeterUtils.getResString("view_results_tab_sampler")); //$NON-NLS-1$
// Add the other tabs if not present
if(rightSide.indexOfTab(JMeterUtils.getResString("view_results_tab_request")) < 0) { // $NON-NLS-1$
rightSide.addTab(JMeterUtils.getResString("view_results_tab_request"), requestPane); // $NON-NLS-1$
}
if(rightSide.indexOfTab(JMeterUtils.getResString("view_results_tab_response")) < 0) { // $NON-NLS-1$
rightSide.addTab(JMeterUtils.getResString("view_results_tab_response"), resultsPane); // $NON-NLS-1$
}
}
private void setupTabPaneForAssertionResult() {
// Set the title for the first tab
rightSide.setTitleAt(0, JMeterUtils.getResString("view_results_tab_assertion")); //$NON-NLS-1$
// Remove the other tabs if present
int requestTabIndex = rightSide.indexOfTab(JMeterUtils.getResString("view_results_tab_request")); // $NON-NLS-1$
if(requestTabIndex >= 0) {
rightSide.removeTabAt(requestTabIndex);
}
int responseTabIndex = rightSide.indexOfTab(JMeterUtils.getResString("view_results_tab_response")); // $NON-NLS-1$
if(responseTabIndex >= 0) {
rightSide.removeTabAt(responseTabIndex);
}
}
private Component createLeftPanel() {
SampleResult rootSampleResult = new SampleResult();
rootSampleResult.setSampleLabel("Root");
rootSampleResult.setSuccessful(true);
root = new DefaultMutableTreeNode(rootSampleResult);
treeModel = new DefaultTreeModel(root);
jTree = new JTree(treeModel);
jTree.setCellRenderer(new ResultsNodeRenderer());
jTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
jTree.addTreeSelectionListener(this);
jTree.setRootVisible(false);
jTree.setShowsRootHandles(true);
JScrollPane treePane = new JScrollPane(jTree);
treePane.setPreferredSize(new Dimension(200, 300));
return treePane;
}
private Component createResponseMetadataPanel() {
stats = new JTextPane();
stats.setEditable(false);
stats.setBackground(getBackground());
// Add styles to use for different types of status messages
StyledDocument doc = (StyledDocument) stats.getDocument();
Style style = doc.addStyle(STYLE_REDIRECT, null);
StyleConstants.setForeground(style, REDIRECT_COLOR);
style = doc.addStyle(STYLE_CLIENT_ERROR, null);
StyleConstants.setForeground(style, CLIENT_ERROR_COLOR);
style = doc.addStyle(STYLE_SERVER_ERROR, null);
StyleConstants.setForeground(style, SERVER_ERROR_COLOR);
JScrollPane pane = makeScrollPane(stats);
pane.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
return pane;
}
private JPanel createRequestPanel() {
sampleDataField = new JTextArea();
sampleDataField.setEditable(false);
sampleDataField.setLineWrap(true);
sampleDataField.setWrapStyleWord(true);
JPanel pane = new JPanel(new BorderLayout(0, 5));
pane.add(makeScrollPane(sampleDataField));
return pane;
}
private JPanel createResponseDataPanel() {
results = new JEditorPane();
results.setEditable(false);
resultsScrollPane = makeScrollPane(results);
imageLabel = new JLabel();
JPanel panel = new JPanel(new BorderLayout());
panel.add(resultsScrollPane, BorderLayout.CENTER);
panel.add(createHtmlOrTextPane(), BorderLayout.SOUTH);
return panel;
}
private static class ResultsNodeRenderer extends DefaultTreeCellRenderer {
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
boolean leaf, int row, boolean focus) {
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, focus);
boolean failure = true;
Object userObject = ((DefaultMutableTreeNode) value).getUserObject();
if(userObject instanceof SampleResult) {
failure = !(((SampleResult) userObject).isSuccessful());
}
else if(userObject instanceof AssertionResult) {
AssertionResult assertion = (AssertionResult) userObject;
failure = assertion.isError() || assertion.isFailure();
}
// Set the status for the node
if (failure) {
this.setForeground(Color.red);
this.setIcon(imageFailure);
} else {
this.setIcon(imageSuccess);
}
return this;
}
}
private static class LocalHTMLEditorKit extends HTMLEditorKit {
private static final ViewFactory defaultFactory = new LocalHTMLFactory();
public ViewFactory getViewFactory() {
return defaultFactory;
}
private static class LocalHTMLFactory extends javax.swing.text.html.HTMLEditorKit.HTMLFactory {
/*
* Provide dummy implementations to suppress download and display of
* related resources: - FRAMEs - IMAGEs TODO create better dummy
* displays TODO suppress LINK somehow
*/
public View create(Element elem) {
Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
if (o instanceof HTML.Tag) {
HTML.Tag kind = (HTML.Tag) o;
if (kind == HTML.Tag.FRAME) {
return new ComponentView(elem);
} else if (kind == HTML.Tag.IMG) {
return new ComponentView(elem);
}
}
return super.create(elem);
}
}
}
/**
*
* A Dom tree panel for to display response as tree view author <a
* href="mailto:d.maung@mdl.com">Dave Maung</a> TODO implement to find any
* nodes in the tree using TreePath.
*
*/
private static class DOMTreePanel extends JPanel {
private JTree domJTree;
public DOMTreePanel(org.w3c.dom.Document document) {
super(new GridLayout(1, 0));
try {
Node firstElement = getFirstElement(document);
DefaultMutableTreeNode top = new XMLDefaultMutableTreeNode(firstElement);
domJTree = new JTree(top);
domJTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
domJTree.setShowsRootHandles(true);
JScrollPane domJScrollPane = new JScrollPane(domJTree);
domJTree.setAutoscrolls(true);
this.add(domJScrollPane);
ToolTipManager.sharedInstance().registerComponent(domJTree);
domJTree.setCellRenderer(new DomTreeRenderer());
this.setPreferredSize(new Dimension(800, 600));
} catch (SAXException e) {
log.warn("", e);
}
}
/**
* Skip all DTD nodes, all prolog nodes. They dont support in tree view
* We let user to insert them however in DOMTreeView, we dont display it
*
* @param root
* @return
*/
private Node getFirstElement(Node parent) {
NodeList childNodes = parent.getChildNodes();
Node toReturn = parent; // Must return a valid node, or may generate an NPE
for (int i = 0; i < childNodes.getLength(); i++) {
Node childNode = childNodes.item(i);
toReturn = childNode;
if (childNode.getNodeType() == Node.ELEMENT_NODE){
break;
}
}
return toReturn;
}
/**
* This class is to view as tooltext. This is very useful, when the
* contents has long string and does not fit in the view. it will also
* automatically wrap line for each 100 characters since tool tip
* support html. author <a href="mailto:d.maung@mdl.com">Dave Maung</a>
*/
private static class DomTreeRenderer extends DefaultTreeCellRenderer {
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded,
boolean leaf, int row, boolean phasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, phasFocus);
DefaultMutableTreeNode valueTreeNode = (DefaultMutableTreeNode) value;
setToolTipText(getHTML(valueTreeNode.toString(), "<br>", 100)); // $NON-NLS-1$
return this;
}
/**
* get the html
*
* @param str
* @param separator
* @param maxChar
* @return
*/
private String getHTML(String str, String separator, int maxChar) {
StringBuffer strBuf = new StringBuffer("<html><body bgcolor=\"yellow\"><b>"); // $NON-NLS-1$
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; i++) {
if (i % maxChar == 0 && i != 0) {
strBuf.append(separator);
}
strBuf.append(encode(chars[i]));
}
strBuf.append("</b></body></html>"); // $NON-NLS-1$
return strBuf.toString();
}
private String encode(char c) {
String toReturn = String.valueOf(c);
switch (c) {
case '<': // $NON-NLS-1$
toReturn = "<"; // $NON-NLS-1$
break;
case '>': // $NON-NLS-1$
toReturn = ">"; // $NON-NLS-1$
break;
case '\'': // $NON-NLS-1$
toReturn = "'"; // $NON-NLS-1$
break;
case '\"': // $NON-NLS-1$
toReturn = """; // $NON-NLS-1$
break;
}
return toReturn;
}
}
}
private static void showErrorMessageDialog(String message, String title, int messageType) {
JOptionPane.showMessageDialog(null, message, title, messageType);
}
}