0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018:
0019: package org.apache.jmeter.visualizers;
0020:
0021: import java.awt.BorderLayout;
0022: import java.awt.Color;
0023: import java.awt.Component;
0024: import java.awt.Dimension;
0025: import java.awt.GridLayout;
0026: import java.awt.event.ActionEvent;
0027: import java.awt.event.ActionListener;
0028: import java.io.IOException;
0029: import java.io.StringReader;
0030: import java.lang.Character;
0031: import java.text.DateFormat;
0032: import java.text.SimpleDateFormat;
0033: import java.util.Date;
0034: import java.util.regex.Matcher;
0035: import java.util.regex.Pattern;
0036:
0037: import javax.swing.BorderFactory;
0038: import javax.swing.ButtonGroup;
0039: import javax.swing.Icon;
0040: import javax.swing.ImageIcon;
0041: import javax.swing.JCheckBox;
0042: import javax.swing.JEditorPane;
0043: import javax.swing.JLabel;
0044: import javax.swing.JOptionPane;
0045: import javax.swing.JPanel;
0046: import javax.swing.JRadioButton;
0047: import javax.swing.JScrollPane;
0048: import javax.swing.JSplitPane;
0049: import javax.swing.JTabbedPane;
0050: import javax.swing.JTextArea;
0051: import javax.swing.JTextPane;
0052: import javax.swing.JTree;
0053: import javax.swing.ToolTipManager;
0054: import javax.swing.event.TreeSelectionEvent;
0055: import javax.swing.event.TreeSelectionListener;
0056: import javax.swing.text.BadLocationException;
0057: import javax.swing.text.ComponentView;
0058: import javax.swing.text.Document;
0059: import javax.swing.text.EditorKit;
0060: import javax.swing.text.Element;
0061: import javax.swing.text.Style;
0062: import javax.swing.text.StyleConstants;
0063: import javax.swing.text.StyledDocument;
0064: import javax.swing.text.View;
0065: import javax.swing.text.ViewFactory;
0066: import javax.swing.text.html.HTML;
0067: import javax.swing.text.html.HTMLEditorKit;
0068: import javax.swing.tree.DefaultMutableTreeNode;
0069: import javax.swing.tree.DefaultTreeCellRenderer;
0070: import javax.swing.tree.DefaultTreeModel;
0071: import javax.swing.tree.TreePath;
0072: import javax.swing.tree.TreeSelectionModel;
0073: import javax.xml.parsers.DocumentBuilder;
0074: import javax.xml.parsers.DocumentBuilderFactory;
0075: import javax.xml.parsers.ParserConfigurationException;
0076:
0077: import org.apache.jmeter.assertions.AssertionResult;
0078: import org.apache.jmeter.samplers.Clearable;
0079: import org.apache.jmeter.samplers.SampleResult;
0080: import org.apache.jmeter.util.JMeterUtils;
0081: import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
0082: import org.apache.jorphan.logging.LoggingManager;
0083: import org.apache.log.Logger;
0084: import org.w3c.dom.Node;
0085: import org.w3c.dom.NodeList;
0086: import org.xml.sax.ErrorHandler;
0087: import org.xml.sax.InputSource;
0088: import org.xml.sax.SAXException;
0089: import org.xml.sax.SAXParseException;
0090:
0091: /**
0092: * Allows the tester to view the textual response from sampling an Entry. This
0093: * also allows to "single step through" the sampling process via a nice
0094: * "Continue" button.
0095: *
0096: * Created 2001/07/25
0097: */
0098: public class ViewResultsFullVisualizer extends AbstractVisualizer
0099: implements ActionListener, TreeSelectionListener, Clearable {
0100:
0101: private static final Logger log = LoggingManager
0102: .getLoggerForClass();
0103:
0104: // N.B. these are not multi-threaded, so don't make it static
0105: private final DateFormat dateFormat = new SimpleDateFormat(
0106: "yyyy-MM-dd HH:mm:ss z"); // ISO format $NON-NLS-1$
0107:
0108: private static final String NL = "\n"; // $NON-NLS-1$
0109:
0110: private static final String XML_PFX = "<?xml "; // $NON-NLS-1$
0111:
0112: public final static Color SERVER_ERROR_COLOR = Color.red;
0113:
0114: public final static Color CLIENT_ERROR_COLOR = Color.blue;
0115:
0116: public final static Color REDIRECT_COLOR = Color.green;
0117:
0118: private static final String TEXT_HTML = "text/html"; // $NON-NLS-1$
0119:
0120: private static final String HTML_COMMAND = "html"; // $NON-NLS-1$
0121:
0122: private static final String JSON_COMMAND = "json"; // $NON-NLS-1$
0123:
0124: private static final String XML_COMMAND = "xml"; // $NON-NLS-1$
0125:
0126: private static final String TEXT_COMMAND = "text"; // $NON-NLS-1$
0127:
0128: private static final String STYLE_SERVER_ERROR = "ServerError"; // $NON-NLS-1$
0129:
0130: private static final String STYLE_CLIENT_ERROR = "ClientError"; // $NON-NLS-1$
0131:
0132: private static final String STYLE_REDIRECT = "Redirect"; // $NON-NLS-1$
0133:
0134: private boolean textMode = true;
0135:
0136: private static final String ESC_CHAR_REGEX = "\\\\[\"\\\\/bfnrt]|\\\\u[0-9A-Fa-f]{4}"; // $NON-NLS-1$
0137:
0138: private static final String NORMAL_CHARACTER_REGEX = "[^\"\\\\]"; // $NON-NLS-1$
0139:
0140: private static final String STRING_REGEX = "\"(" + ESC_CHAR_REGEX
0141: + "|" + NORMAL_CHARACTER_REGEX + ")*\""; // $NON-NLS-1$
0142:
0143: // This 'other value' regex is deliberately weak, even accepting an empty string, to be useful when reporting malformed data.
0144: private static final String OTHER_VALUE_REGEX = "[^\\{\\[\\]\\}\\,]*"; // $NON-NLS-1$
0145:
0146: private static final String VALUE_OR_PAIR_REGEX = "(("
0147: + STRING_REGEX + "\\s*:)?\\s*(" + STRING_REGEX + "|"
0148: + OTHER_VALUE_REGEX + ")\\s*,?\\s*)"; // $NON-NLS-1$
0149:
0150: private static final Pattern VALUE_OR_PAIR_PATTERN = Pattern
0151: .compile(VALUE_OR_PAIR_REGEX);
0152:
0153: // set default command to Text
0154: private String command = TEXT_COMMAND;
0155:
0156: // Keep copies of the two editors needed
0157: private static EditorKit customisedEditor = new LocalHTMLEditorKit();
0158:
0159: private static EditorKit defaultHtmlEditor = JEditorPane
0160: .createEditorKitForContentType(TEXT_HTML);
0161:
0162: private DefaultMutableTreeNode root;
0163:
0164: private DefaultTreeModel treeModel;
0165:
0166: private JTextPane stats;
0167:
0168: private JEditorPane results;
0169:
0170: private JScrollPane resultsScrollPane;
0171:
0172: private JPanel resultsPane;
0173:
0174: private JLabel imageLabel;
0175:
0176: private JTextArea sampleDataField;
0177:
0178: private JPanel requestPane;
0179:
0180: private JRadioButton textButton;
0181:
0182: private JRadioButton htmlButton;
0183:
0184: private JRadioButton jsonButton;
0185:
0186: private JRadioButton xmlButton;
0187:
0188: private JCheckBox downloadAll;
0189:
0190: private JTree jTree;
0191:
0192: private JTabbedPane rightSide;
0193:
0194: private static final ImageIcon imageSuccess = JMeterUtils
0195: .getImage(JMeterUtils.getPropDefault(
0196: "viewResultsTree.success", //$NON-NLS-1$
0197: "icon_success_sml.gif")); //$NON-NLS-1$
0198:
0199: private static final ImageIcon imageFailure = JMeterUtils
0200: .getImage(JMeterUtils.getPropDefault(
0201: "viewResultsTree.failure", //$NON-NLS-1$
0202: "icon_warning_sml.gif")); //$NON-NLS-1$
0203:
0204: public ViewResultsFullVisualizer() {
0205: super ();
0206: log.debug("Start : ViewResultsFullVisualizer1");
0207: init();
0208: log.debug("End : ViewResultsFullVisualizer1");
0209: }
0210:
0211: public void add(SampleResult res) {
0212: updateGui(res);
0213: }
0214:
0215: public String getLabelResource() {
0216: return "view_results_tree_title"; // $NON-NLS-1$
0217: }
0218:
0219: /**
0220: * Update the visualizer with new data.
0221: */
0222: private synchronized void updateGui(SampleResult res) {
0223: log.debug("Start : updateGui1");
0224: if (log.isDebugEnabled()) {
0225: log.debug("updateGui1 : sample result - " + res);
0226: }
0227: // Add sample
0228: DefaultMutableTreeNode currNode = new DefaultMutableTreeNode(
0229: res);
0230: treeModel.insertNodeInto(currNode, root, root.getChildCount());
0231: addSubResults(currNode, res);
0232: // Add any assertion that failed as children of the sample node
0233: AssertionResult assertionResults[] = res.getAssertionResults();
0234: int assertionIndex = currNode.getChildCount();
0235: for (int j = 0; j < assertionResults.length; j++) {
0236: AssertionResult item = assertionResults[j];
0237:
0238: if (item.isFailure() || item.isError()) {
0239: DefaultMutableTreeNode assertionNode = new DefaultMutableTreeNode(
0240: item);
0241: treeModel.insertNodeInto(assertionNode, currNode,
0242: assertionIndex++);
0243: }
0244: }
0245:
0246: if (root.getChildCount() == 1) {
0247: jTree.expandPath(new TreePath(root));
0248: }
0249: log.debug("End : updateGui1");
0250: }
0251:
0252: private void addSubResults(DefaultMutableTreeNode currNode,
0253: SampleResult res) {
0254: SampleResult[] subResults = res.getSubResults();
0255:
0256: int leafIndex = 0;
0257:
0258: for (int i = 0; i < subResults.length; i++) {
0259: SampleResult child = subResults[i];
0260:
0261: if (log.isDebugEnabled()) {
0262: log
0263: .debug("updateGui1 : child sample result - "
0264: + child);
0265: }
0266: DefaultMutableTreeNode leafNode = new DefaultMutableTreeNode(
0267: child);
0268:
0269: treeModel.insertNodeInto(leafNode, currNode, leafIndex++);
0270: addSubResults(leafNode, child);
0271: // Add any assertion that failed as children of the sample node
0272: AssertionResult assertionResults[] = child
0273: .getAssertionResults();
0274: int assertionIndex = leafNode.getChildCount();
0275: for (int j = 0; j < assertionResults.length; j++) {
0276: AssertionResult item = assertionResults[j];
0277:
0278: if (item.isFailure() || item.isError()) {
0279: DefaultMutableTreeNode assertionNode = new DefaultMutableTreeNode(
0280: item);
0281: treeModel.insertNodeInto(assertionNode, leafNode,
0282: assertionIndex++);
0283: }
0284: }
0285: }
0286: }
0287:
0288: /**
0289: * Clears the visualizer.
0290: */
0291: public void clearData() {
0292: log.debug("Start : clear1");
0293:
0294: if (log.isDebugEnabled()) {
0295: log.debug("clear1 : total child - " + root.getChildCount());
0296: }
0297: while (root.getChildCount() > 0) {
0298: // the child to be removed will always be 0 'cos as the nodes are
0299: // removed the nth node will become (n-1)th
0300: treeModel
0301: .removeNodeFromParent((DefaultMutableTreeNode) root
0302: .getChildAt(0));
0303: }
0304:
0305: results.setText("");// Response Data // $NON-NLS-1$
0306: sampleDataField.setText("");// Request Data // $NON-NLS-1$
0307: log.debug("End : clear1");
0308: }
0309:
0310: /**
0311: * Returns the description of this visualizer.
0312: *
0313: * @return description of this visualizer
0314: */
0315: public String toString() {
0316: String desc = "Shows the text results of sampling in tree form";
0317:
0318: if (log.isDebugEnabled()) {
0319: log.debug("toString1 : Returning description - " + desc);
0320: }
0321: return desc;
0322: }
0323:
0324: /**
0325: * Sets the right pane to correspond to the selected node of the left tree.
0326: */
0327: public void valueChanged(TreeSelectionEvent e) {
0328: log.debug("Start : valueChanged1");
0329: DefaultMutableTreeNode node = (DefaultMutableTreeNode) jTree
0330: .getLastSelectedPathComponent();
0331:
0332: if (log.isDebugEnabled()) {
0333: log.debug("valueChanged : selected node - " + node);
0334: }
0335:
0336: StyledDocument statsDoc = stats.getStyledDocument();
0337: try {
0338: statsDoc.remove(0, statsDoc.getLength());
0339: sampleDataField.setText(""); // $NON-NLS-1$
0340: results.setText(""); // $NON-NLS-1$
0341: if (node != null) {
0342: Object userObject = node.getUserObject();
0343: if (userObject instanceof SampleResult) {
0344: SampleResult res = (SampleResult) userObject;
0345:
0346: // We are displaying a SampleResult
0347: setupTabPaneForSampleResult();
0348:
0349: if (log.isDebugEnabled()) {
0350: log.debug("valueChanged1 : sample result - "
0351: + res);
0352: }
0353:
0354: if (res != null) {
0355: // load time label
0356:
0357: log.debug("valueChanged1 : load time - "
0358: + res.getTime());
0359: String sd = res.getSamplerData();
0360: if (sd != null) {
0361: String rh = res.getRequestHeaders();
0362: if (rh != null) {
0363: StringBuffer sb = new StringBuffer(sd
0364: .length()
0365: + rh.length() + 20);
0366: sb.append(sd);
0367: sb.append("\nRequest Headers:\n");
0368: sb.append(rh);
0369: sd = sb.toString();
0370: }
0371: sampleDataField.setText(sd);
0372: }
0373:
0374: StringBuffer statsBuff = new StringBuffer(200);
0375: statsBuff.append("Thread Name: ").append(
0376: res.getThreadName()).append(NL);
0377: String startTime = dateFormat.format(new Date(
0378: res.getStartTime()));
0379: statsBuff.append("Sample Start: ").append(
0380: startTime).append(NL);
0381: statsBuff.append("Load time: ").append(
0382: res.getTime()).append(NL);
0383: statsBuff.append("Latency: ").append(
0384: res.getLatency()).append(NL);
0385: statsBuff.append("Size in bytes: ").append(
0386: res.getBytes()).append(NL);
0387: statsBuff.append("Sample Count: ").append(
0388: res.getSampleCount()).append(NL);
0389: statsBuff.append("Error Count: ").append(
0390: res.getErrorCount()).append(NL);
0391: statsDoc.insertString(statsDoc.getLength(),
0392: statsBuff.toString(), null);
0393: statsBuff = new StringBuffer(); //reset for reuse
0394:
0395: String responseCode = res.getResponseCode();
0396: log.debug("valueChanged1 : response code - "
0397: + responseCode);
0398:
0399: int responseLevel = 0;
0400: if (responseCode != null) {
0401: try {
0402: responseLevel = Integer
0403: .parseInt(responseCode) / 100;
0404: } catch (NumberFormatException numberFormatException) {
0405: // no need to change the foreground color
0406: }
0407: }
0408:
0409: Style style = null;
0410: switch (responseLevel) {
0411: case 3:
0412: style = statsDoc.getStyle(STYLE_REDIRECT);
0413: break;
0414: case 4:
0415: style = statsDoc
0416: .getStyle(STYLE_CLIENT_ERROR);
0417: break;
0418: case 5:
0419: style = statsDoc
0420: .getStyle(STYLE_SERVER_ERROR);
0421: break;
0422: }
0423:
0424: statsBuff.append("Response code: ").append(
0425: responseCode).append(NL);
0426: statsDoc.insertString(statsDoc.getLength(),
0427: statsBuff.toString(), style);
0428: statsBuff = new StringBuffer(100); //reset for reuse
0429:
0430: // response message label
0431: String responseMsgStr = res
0432: .getResponseMessage();
0433:
0434: log.debug("valueChanged1 : response message - "
0435: + responseMsgStr);
0436: statsBuff.append("Response message: ").append(
0437: responseMsgStr).append(NL);
0438:
0439: statsBuff.append(NL)
0440: .append("Response headers:").append(NL);
0441: statsBuff.append(res.getResponseHeaders())
0442: .append(NL);
0443: statsDoc.insertString(statsDoc.getLength(),
0444: statsBuff.toString(), null);
0445: statsBuff = null; // Done
0446:
0447: // get the text response and image icon
0448: // to determine which is NOT null
0449: if ((SampleResult.TEXT).equals(res
0450: .getDataType())) // equals(null) is OK
0451: {
0452: String response = getResponseAsString(res);
0453: if (command.equals(TEXT_COMMAND)) {
0454: showTextResponse(response);
0455: } else if (command.equals(HTML_COMMAND)) {
0456: showRenderedResponse(response, res);
0457: } else if (command.equals(JSON_COMMAND)) {
0458: showRenderJSONResponse(response);
0459: } else if (command.equals(XML_COMMAND)) {
0460: showRenderXMLResponse(response);
0461: }
0462: } else {
0463: byte[] responseBytes = res
0464: .getResponseData();
0465: if (responseBytes != null) {
0466: showImage(new ImageIcon(responseBytes)); //TODO implement other non-text types
0467: }
0468: }
0469: }
0470: } else if (userObject instanceof AssertionResult) {
0471: AssertionResult res = (AssertionResult) userObject;
0472:
0473: // We are displaying an AssertionResult
0474: setupTabPaneForAssertionResult();
0475:
0476: if (log.isDebugEnabled()) {
0477: log.debug("valueChanged1 : sample result - "
0478: + res);
0479: }
0480:
0481: if (res != null) {
0482: StringBuffer statsBuff = new StringBuffer(100);
0483: statsBuff.append("Assertion error: ").append(
0484: res.isError()).append(NL);
0485: statsBuff.append("Assertion failure: ").append(
0486: res.isFailure()).append(NL);
0487: statsBuff
0488: .append("Assertion failure message : ")
0489: .append(res.getFailureMessage())
0490: .append(NL);
0491: statsDoc.insertString(statsDoc.getLength(),
0492: statsBuff.toString(), null);
0493: statsBuff = null;
0494: }
0495: }
0496: }
0497: } catch (BadLocationException exc) {
0498: log.error("Error setting statistics text", exc);
0499: stats.setText("");
0500: }
0501: log.debug("End : valueChanged1");
0502: }
0503:
0504: private void showImage(Icon image) {
0505: imageLabel.setIcon(image);
0506: resultsScrollPane.setViewportView(imageLabel);
0507: textButton.setEnabled(false);
0508: htmlButton.setEnabled(false);
0509: jsonButton.setEnabled(false);
0510: xmlButton.setEnabled(false);
0511: }
0512:
0513: protected void showTextResponse(String response) {
0514: results.setContentType("text/plain"); // $NON-NLS-1$
0515: results.setText(response == null ? "" : response); // $NON-NLS-1$
0516: results.setCaretPosition(0);
0517: resultsScrollPane.setViewportView(results);
0518:
0519: textButton.setEnabled(true);
0520: htmlButton.setEnabled(true);
0521: jsonButton.setEnabled(true);
0522: xmlButton.setEnabled(true);
0523: }
0524:
0525: // It might be useful also to make this available in the 'Request' tab, for
0526: // when posting JSON.
0527: private static String prettyJSON(String json) {
0528: StringBuffer pretty = new StringBuffer(json.length() * 2); // Educated guess
0529:
0530: final String tab = ": "; // $NON-NLS-1$
0531: StringBuffer index = new StringBuffer();
0532: String nl = ""; // $NON-NLS-1$
0533:
0534: Matcher valueOrPair = VALUE_OR_PAIR_PATTERN.matcher(json);
0535:
0536: boolean misparse = false;
0537:
0538: for (int i = 0; i < json.length();) {
0539: final char currentChar = json.charAt(i);
0540: if ((currentChar == '{') || (currentChar == '[')) {
0541: pretty.append(nl).append(index).append(currentChar);
0542: i++;
0543: index.append(tab);
0544: misparse = false;
0545: } else if ((currentChar == '}') || (currentChar == ']')) {
0546: if (index.length() > 0) {
0547: index.delete(0, tab.length());
0548: }
0549: pretty.append(nl).append(index).append(currentChar);
0550: i++;
0551: int j = i;
0552: while ((j < json.length())
0553: && Character.isWhitespace(json.charAt(j))) {
0554: j++;
0555: }
0556: if ((j < json.length()) && (json.charAt(j) == ',')) {
0557: pretty.append(","); // $NON-NLS-1$
0558: i = j + 1;
0559: }
0560: misparse = false;
0561: } else if (valueOrPair.find(i)
0562: && valueOrPair.group().length() > 0) {
0563: pretty.append(nl).append(index).append(
0564: valueOrPair.group());
0565: i = valueOrPair.end();
0566: misparse = false;
0567: } else {
0568: if (!misparse) {
0569: pretty.append(nl).append("- Parse failed from:");
0570: }
0571: pretty.append(currentChar);
0572: i++;
0573: misparse = true;
0574: }
0575: nl = "\n"; // $NON-NLS-1$
0576: }
0577: return pretty.toString();
0578: }
0579:
0580: private void showRenderJSONResponse(String response) {
0581: results.setContentType("text/plain"); // $NON-NLS-1$
0582: results.setText(response == null ? "" : prettyJSON(response));
0583: results.setCaretPosition(0);
0584: resultsScrollPane.setViewportView(results);
0585:
0586: textButton.setEnabled(true);
0587: htmlButton.setEnabled(true);
0588: jsonButton.setEnabled(true);
0589: xmlButton.setEnabled(true);
0590: }
0591:
0592: private static final SAXErrorHandler saxErrorHandler = new SAXErrorHandler();
0593:
0594: private void showRenderXMLResponse(String response) {
0595: String parsable = "";
0596: if (response == null) {
0597: results.setText(""); // $NON-NLS-1$
0598: parsable = ""; // $NON-NLS-1$
0599: } else {
0600: results.setText(response);
0601: int start = response.indexOf(XML_PFX);
0602: if (start > 0) {
0603: parsable = response.substring(start);
0604: } else {
0605: parsable = response;
0606: }
0607: }
0608: results.setContentType("text/xml"); // $NON-NLS-1$
0609: results.setCaretPosition(0);
0610:
0611: Component view = results;
0612:
0613: // there is duplicate Document class. Therefore I needed to declare the
0614: // specific
0615: // class that I want
0616: org.w3c.dom.Document document = null;
0617:
0618: try {
0619:
0620: DocumentBuilderFactory parserFactory = DocumentBuilderFactory
0621: .newInstance();
0622: parserFactory.setValidating(false);
0623: parserFactory.setNamespaceAware(false);
0624:
0625: // create a parser:
0626: DocumentBuilder parser = parserFactory.newDocumentBuilder();
0627:
0628: parser.setErrorHandler(saxErrorHandler);
0629: document = parser.parse(new InputSource(new StringReader(
0630: parsable)));
0631:
0632: JPanel domTreePanel = new DOMTreePanel(document);
0633:
0634: document.normalize();
0635:
0636: view = domTreePanel;
0637: } catch (SAXParseException e) {
0638: showErrorMessageDialog(saxErrorHandler.getErrorMessage(),
0639: saxErrorHandler.getMessageType());
0640: log.debug(e.getMessage());
0641: } catch (SAXException e) {
0642: showErrorMessageDialog(e.getMessage(),
0643: JOptionPane.ERROR_MESSAGE);
0644: log.debug(e.getMessage());
0645: } catch (IOException e) {
0646: showErrorMessageDialog(e.getMessage(),
0647: JOptionPane.ERROR_MESSAGE);
0648: log.debug(e.getMessage());
0649: } catch (ParserConfigurationException e) {
0650: showErrorMessageDialog(e.getMessage(),
0651: JOptionPane.ERROR_MESSAGE);
0652: log.debug(e.getMessage());
0653: }
0654: resultsScrollPane.setViewportView(view);
0655: textButton.setEnabled(true);
0656: htmlButton.setEnabled(true);
0657: jsonButton.setEnabled(true);
0658: xmlButton.setEnabled(true);
0659: }
0660:
0661: private static String getResponseAsString(SampleResult res) {
0662:
0663: String response = null;
0664: if ((SampleResult.TEXT).equals(res.getDataType())) {
0665: // Showing large strings can be VERY costly, so we will avoid
0666: // doing so if the response
0667: // data is larger than 200K. TODO: instead, we could delay doing
0668: // the result.setText
0669: // call until the user chooses the "Response data" tab. Plus we
0670: // could warn the user
0671: // if this happens and revert the choice if he doesn't confirm
0672: // he's ready to wait.
0673: int len = res.getResponseData().length;
0674: if (len > 200 * 1024) {
0675: response = "Response too large to be displayed (" + len
0676: + " bytes).";
0677: log.warn(response);
0678: } else {
0679: response = res.getResponseDataAsString();
0680: }
0681: }
0682: return response;
0683: }
0684:
0685: /**
0686: * Display the response as text or as rendered HTML. Change the text on the
0687: * button appropriate to the current display.
0688: *
0689: * @param e
0690: * the ActionEvent being processed
0691: */
0692: public void actionPerformed(ActionEvent e) {
0693: command = e.getActionCommand();
0694:
0695: if (command != null
0696: && (command.equals(TEXT_COMMAND)
0697: || command.equals(HTML_COMMAND)
0698: || command.equals(JSON_COMMAND) || command
0699: .equals(XML_COMMAND))) {
0700:
0701: textMode = command.equals(TEXT_COMMAND);
0702:
0703: DefaultMutableTreeNode node = (DefaultMutableTreeNode) jTree
0704: .getLastSelectedPathComponent();
0705:
0706: if (node == null) {
0707: results.setText("");
0708: return;
0709: }
0710:
0711: SampleResult res = (SampleResult) node.getUserObject();
0712: String response = getResponseAsString(res);
0713:
0714: if (command.equals(TEXT_COMMAND)) {
0715: showTextResponse(response);
0716: } else if (command.equals(HTML_COMMAND)) {
0717: showRenderedResponse(response, res);
0718: } else if (command.equals(JSON_COMMAND)) {
0719: showRenderJSONResponse(response);
0720: } else if (command.equals(XML_COMMAND)) {
0721: showRenderXMLResponse(response);
0722: }
0723: }
0724: }
0725:
0726: protected void showRenderedResponse(String response,
0727: SampleResult res) {
0728: if (response == null) {
0729: results.setText("");
0730: return;
0731: }
0732:
0733: int htmlIndex = response.indexOf("<HTML"); // could be <HTML lang=""> // $NON-NLS-1$
0734:
0735: // Look for a case variation
0736: if (htmlIndex < 0) {
0737: htmlIndex = response.indexOf("<html"); // ditto // $NON-NLS-1$
0738: }
0739:
0740: // If we still can't find it, just try using all of the text
0741: if (htmlIndex < 0) {
0742: htmlIndex = 0;
0743: }
0744:
0745: String html = response.substring(htmlIndex);
0746:
0747: /*
0748: * To disable downloading and rendering of images and frames, enable the
0749: * editor-kit. The Stream property can then be
0750: */
0751:
0752: // Must be done before setContentType
0753: results.setEditorKitForContentType(TEXT_HTML, downloadAll
0754: .isSelected() ? defaultHtmlEditor : customisedEditor);
0755:
0756: results.setContentType(TEXT_HTML);
0757:
0758: if (downloadAll.isSelected()) {
0759: // Allow JMeter to render frames (and relative images)
0760: // Must be done after setContentType [Why?]
0761: results.getDocument().putProperty(
0762: Document.StreamDescriptionProperty, res.getURL());
0763: }
0764:
0765: /*
0766: * Get round problems parsing <META http-equiv='content-type'
0767: * content='text/html; charset=utf-8'> See
0768: * http://issues.apache.org/bugzilla/show_bug.cgi?id=23315
0769: *
0770: * Is this due to a bug in Java?
0771: */
0772: results.getDocument().putProperty("IgnoreCharsetDirective",
0773: Boolean.TRUE); // $NON-NLS-1$
0774:
0775: results.setText(html);
0776: results.setCaretPosition(0);
0777: resultsScrollPane.setViewportView(results);
0778:
0779: textButton.setEnabled(true);
0780: htmlButton.setEnabled(true);
0781: jsonButton.setEnabled(true);
0782: xmlButton.setEnabled(true);
0783: }
0784:
0785: private Component createHtmlOrTextPane() {
0786: ButtonGroup group = new ButtonGroup();
0787:
0788: textButton = new JRadioButton(JMeterUtils
0789: .getResString("view_results_render_text")); // $NON-NLS-1$
0790: textButton.setActionCommand(TEXT_COMMAND);
0791: textButton.addActionListener(this );
0792: textButton.setSelected(textMode);
0793: group.add(textButton);
0794:
0795: htmlButton = new JRadioButton(JMeterUtils
0796: .getResString("view_results_render_html")); // $NON-NLS-1$
0797: htmlButton.setActionCommand(HTML_COMMAND);
0798: htmlButton.addActionListener(this );
0799: htmlButton.setSelected(!textMode);
0800: group.add(htmlButton);
0801:
0802: jsonButton = new JRadioButton(JMeterUtils
0803: .getResString("view_results_render_json")); // $NON-NLS-1$
0804: jsonButton.setActionCommand(JSON_COMMAND);
0805: jsonButton.addActionListener(this );
0806: jsonButton.setSelected(!textMode);
0807: group.add(jsonButton);
0808:
0809: xmlButton = new JRadioButton(JMeterUtils
0810: .getResString("view_results_render_xml")); // $NON-NLS-1$
0811: xmlButton.setActionCommand(XML_COMMAND);
0812: xmlButton.addActionListener(this );
0813: xmlButton.setSelected(!textMode);
0814: group.add(xmlButton);
0815:
0816: downloadAll = new JCheckBox(JMeterUtils
0817: .getResString("view_results_render_embedded")); // $NON-NLS-1$
0818:
0819: JPanel pane = new JPanel();
0820: pane.add(textButton);
0821: pane.add(htmlButton);
0822: pane.add(xmlButton);
0823: pane.add(jsonButton);
0824: pane.add(downloadAll);
0825: return pane;
0826: }
0827:
0828: /**
0829: * Initialize this visualizer
0830: */
0831: private void init() {
0832: setLayout(new BorderLayout(0, 5));
0833: setBorder(makeBorder());
0834:
0835: add(makeTitlePanel(), BorderLayout.NORTH);
0836:
0837: Component leftSide = createLeftPanel();
0838: rightSide = new JTabbedPane();
0839: // Add the common tab
0840: rightSide.addTab(JMeterUtils
0841: .getResString("view_results_tab_sampler"),
0842: createResponseMetadataPanel()); // $NON-NLS-1$
0843: // Create the panels for the other tabs
0844: requestPane = createRequestPanel();
0845: resultsPane = createResponseDataPanel();
0846:
0847: JSplitPane mainSplit = new JSplitPane(
0848: JSplitPane.HORIZONTAL_SPLIT, leftSide, rightSide);
0849: add(mainSplit, BorderLayout.CENTER);
0850: }
0851:
0852: private void setupTabPaneForSampleResult() {
0853: // Set the title for the first tab
0854: rightSide.setTitleAt(0, JMeterUtils
0855: .getResString("view_results_tab_sampler")); //$NON-NLS-1$
0856: // Add the other tabs if not present
0857: if (rightSide.indexOfTab(JMeterUtils
0858: .getResString("view_results_tab_request")) < 0) { // $NON-NLS-1$
0859: rightSide.addTab(JMeterUtils
0860: .getResString("view_results_tab_request"),
0861: requestPane); // $NON-NLS-1$
0862: }
0863: if (rightSide.indexOfTab(JMeterUtils
0864: .getResString("view_results_tab_response")) < 0) { // $NON-NLS-1$
0865: rightSide.addTab(JMeterUtils
0866: .getResString("view_results_tab_response"),
0867: resultsPane); // $NON-NLS-1$
0868: }
0869: }
0870:
0871: private void setupTabPaneForAssertionResult() {
0872: // Set the title for the first tab
0873: rightSide.setTitleAt(0, JMeterUtils
0874: .getResString("view_results_tab_assertion")); //$NON-NLS-1$
0875: // Remove the other tabs if present
0876: int requestTabIndex = rightSide.indexOfTab(JMeterUtils
0877: .getResString("view_results_tab_request")); // $NON-NLS-1$
0878: if (requestTabIndex >= 0) {
0879: rightSide.removeTabAt(requestTabIndex);
0880: }
0881: int responseTabIndex = rightSide.indexOfTab(JMeterUtils
0882: .getResString("view_results_tab_response")); // $NON-NLS-1$
0883: if (responseTabIndex >= 0) {
0884: rightSide.removeTabAt(responseTabIndex);
0885: }
0886: }
0887:
0888: private Component createLeftPanel() {
0889: SampleResult rootSampleResult = new SampleResult();
0890: rootSampleResult.setSampleLabel("Root");
0891: rootSampleResult.setSuccessful(true);
0892: root = new DefaultMutableTreeNode(rootSampleResult);
0893:
0894: treeModel = new DefaultTreeModel(root);
0895: jTree = new JTree(treeModel);
0896: jTree.setCellRenderer(new ResultsNodeRenderer());
0897: jTree.getSelectionModel().setSelectionMode(
0898: TreeSelectionModel.SINGLE_TREE_SELECTION);
0899: jTree.addTreeSelectionListener(this );
0900: jTree.setRootVisible(false);
0901: jTree.setShowsRootHandles(true);
0902:
0903: JScrollPane treePane = new JScrollPane(jTree);
0904: treePane.setPreferredSize(new Dimension(200, 300));
0905: return treePane;
0906: }
0907:
0908: private Component createResponseMetadataPanel() {
0909: stats = new JTextPane();
0910: stats.setEditable(false);
0911: stats.setBackground(getBackground());
0912:
0913: // Add styles to use for different types of status messages
0914: StyledDocument doc = (StyledDocument) stats.getDocument();
0915:
0916: Style style = doc.addStyle(STYLE_REDIRECT, null);
0917: StyleConstants.setForeground(style, REDIRECT_COLOR);
0918:
0919: style = doc.addStyle(STYLE_CLIENT_ERROR, null);
0920: StyleConstants.setForeground(style, CLIENT_ERROR_COLOR);
0921:
0922: style = doc.addStyle(STYLE_SERVER_ERROR, null);
0923: StyleConstants.setForeground(style, SERVER_ERROR_COLOR);
0924:
0925: JScrollPane pane = makeScrollPane(stats);
0926: pane.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
0927: return pane;
0928: }
0929:
0930: private JPanel createRequestPanel() {
0931: sampleDataField = new JTextArea();
0932: sampleDataField.setEditable(false);
0933: sampleDataField.setLineWrap(true);
0934: sampleDataField.setWrapStyleWord(true);
0935:
0936: JPanel pane = new JPanel(new BorderLayout(0, 5));
0937: pane.add(makeScrollPane(sampleDataField));
0938: return pane;
0939: }
0940:
0941: private JPanel createResponseDataPanel() {
0942: results = new JEditorPane();
0943: results.setEditable(false);
0944:
0945: resultsScrollPane = makeScrollPane(results);
0946: imageLabel = new JLabel();
0947:
0948: JPanel panel = new JPanel(new BorderLayout());
0949: panel.add(resultsScrollPane, BorderLayout.CENTER);
0950: panel.add(createHtmlOrTextPane(), BorderLayout.SOUTH);
0951:
0952: return panel;
0953: }
0954:
0955: private static class ResultsNodeRenderer extends
0956: DefaultTreeCellRenderer {
0957: public Component getTreeCellRendererComponent(JTree tree,
0958: Object value, boolean sel, boolean expanded,
0959: boolean leaf, int row, boolean focus) {
0960: super .getTreeCellRendererComponent(tree, value, sel,
0961: expanded, leaf, row, focus);
0962: boolean failure = true;
0963: Object userObject = ((DefaultMutableTreeNode) value)
0964: .getUserObject();
0965: if (userObject instanceof SampleResult) {
0966: failure = !(((SampleResult) userObject).isSuccessful());
0967: } else if (userObject instanceof AssertionResult) {
0968: AssertionResult assertion = (AssertionResult) userObject;
0969: failure = assertion.isError() || assertion.isFailure();
0970: }
0971:
0972: // Set the status for the node
0973: if (failure) {
0974: this .setForeground(Color.red);
0975: this .setIcon(imageFailure);
0976: } else {
0977: this .setIcon(imageSuccess);
0978: }
0979: return this ;
0980: }
0981: }
0982:
0983: private static class LocalHTMLEditorKit extends HTMLEditorKit {
0984:
0985: private static final ViewFactory defaultFactory = new LocalHTMLFactory();
0986:
0987: public ViewFactory getViewFactory() {
0988: return defaultFactory;
0989: }
0990:
0991: private static class LocalHTMLFactory extends
0992: javax.swing.text.html.HTMLEditorKit.HTMLFactory {
0993: /*
0994: * Provide dummy implementations to suppress download and display of
0995: * related resources: - FRAMEs - IMAGEs TODO create better dummy
0996: * displays TODO suppress LINK somehow
0997: */
0998: public View create(Element elem) {
0999: Object o = elem.getAttributes().getAttribute(
1000: StyleConstants.NameAttribute);
1001: if (o instanceof HTML.Tag) {
1002: HTML.Tag kind = (HTML.Tag) o;
1003: if (kind == HTML.Tag.FRAME) {
1004: return new ComponentView(elem);
1005: } else if (kind == HTML.Tag.IMG) {
1006: return new ComponentView(elem);
1007: }
1008: }
1009: return super .create(elem);
1010: }
1011: }
1012: }
1013:
1014: /**
1015: *
1016: * A Dom tree panel for to display response as tree view author <a
1017: * href="mailto:d.maung@mdl.com">Dave Maung</a> TODO implement to find any
1018: * nodes in the tree using TreePath.
1019: *
1020: */
1021: private static class DOMTreePanel extends JPanel {
1022:
1023: private JTree domJTree;
1024:
1025: public DOMTreePanel(org.w3c.dom.Document document) {
1026: super (new GridLayout(1, 0));
1027: try {
1028: Node firstElement = getFirstElement(document);
1029: DefaultMutableTreeNode top = new XMLDefaultMutableTreeNode(
1030: firstElement);
1031: domJTree = new JTree(top);
1032:
1033: domJTree.getSelectionModel().setSelectionMode(
1034: TreeSelectionModel.SINGLE_TREE_SELECTION);
1035: domJTree.setShowsRootHandles(true);
1036: JScrollPane domJScrollPane = new JScrollPane(domJTree);
1037: domJTree.setAutoscrolls(true);
1038: this .add(domJScrollPane);
1039: ToolTipManager.sharedInstance().registerComponent(
1040: domJTree);
1041: domJTree.setCellRenderer(new DomTreeRenderer());
1042: this .setPreferredSize(new Dimension(800, 600));
1043: } catch (SAXException e) {
1044: log.warn("", e);
1045: }
1046:
1047: }
1048:
1049: /**
1050: * Skip all DTD nodes, all prolog nodes. They dont support in tree view
1051: * We let user to insert them however in DOMTreeView, we dont display it
1052: *
1053: * @param root
1054: * @return
1055: */
1056: private Node getFirstElement(Node parent) {
1057: NodeList childNodes = parent.getChildNodes();
1058: Node toReturn = null;
1059: for (int i = 0; i < childNodes.getLength(); i++) {
1060: Node childNode = childNodes.item(i);
1061: toReturn = childNode;
1062: if (childNode.getNodeType() == Node.ELEMENT_NODE)
1063: break;
1064:
1065: }
1066: return toReturn;
1067: }
1068:
1069: /**
1070: * This class is to view as tooltext. This is very useful, when the
1071: * contents has long string and does not fit in the view. it will also
1072: * automatically wrap line for each 100 characters since tool tip
1073: * support html. author <a href="mailto:d.maung@mdl.com">Dave Maung</a>
1074: */
1075: private static class DomTreeRenderer extends
1076: DefaultTreeCellRenderer {
1077: public Component getTreeCellRendererComponent(JTree tree,
1078: Object value, boolean sel, boolean expanded,
1079: boolean leaf, int row, boolean phasFocus) {
1080: super .getTreeCellRendererComponent(tree, value, sel,
1081: expanded, leaf, row, phasFocus);
1082:
1083: DefaultMutableTreeNode valueTreeNode = (DefaultMutableTreeNode) value;
1084: setToolTipText(getHTML(valueTreeNode.toString(),
1085: "<br>", 100)); // $NON-NLS-1$
1086: return this ;
1087: }
1088:
1089: /**
1090: * get the html
1091: *
1092: * @param str
1093: * @param separator
1094: * @param maxChar
1095: * @return
1096: */
1097: private String getHTML(String str, String separator,
1098: int maxChar) {
1099: StringBuffer strBuf = new StringBuffer(
1100: "<html><body bgcolor=\"yellow\"><b>"); // $NON-NLS-1$
1101: char[] chars = str.toCharArray();
1102: for (int i = 0; i < chars.length; i++) {
1103:
1104: if (i % maxChar == 0 && i != 0)
1105: strBuf.append(separator);
1106: strBuf.append(encode(chars[i]));
1107:
1108: }
1109: strBuf.append("</b></body></html>"); // $NON-NLS-1$
1110: return strBuf.toString();
1111:
1112: }
1113:
1114: private String encode(char c) {
1115: String toReturn = String.valueOf(c);
1116: switch (c) {
1117: case '<': // $NON-NLS-1$
1118: toReturn = "<"; // $NON-NLS-1$
1119: break;
1120: case '>': // $NON-NLS-1$
1121: toReturn = ">"; // $NON-NLS-1$
1122: break;
1123: case '\'': // $NON-NLS-1$
1124: toReturn = "'"; // $NON-NLS-1$
1125: break;
1126: case '\"': // $NON-NLS-1$
1127: toReturn = """; // $NON-NLS-1$
1128: break;
1129:
1130: }
1131: return toReturn;
1132: }
1133: }
1134: }
1135:
1136: private static void showErrorMessageDialog(String message,
1137: int messageType) {
1138: JOptionPane.showMessageDialog(null, message, "Error",
1139: messageType);
1140: }
1141:
1142: // Helper method to construct SAX error details
1143: private static String errorDetails(SAXParseException spe) {
1144: StringBuffer str = new StringBuffer(80);
1145: int i;
1146: i = spe.getLineNumber();
1147: if (i != -1) {
1148: str.append("line=");
1149: str.append(i);
1150: str.append(" col=");
1151: str.append(spe.getColumnNumber());
1152: str.append(" ");
1153: }
1154: str.append(spe.getLocalizedMessage());
1155: return str.toString();
1156: }
1157:
1158: private static class SAXErrorHandler implements ErrorHandler {
1159: private String msg;
1160:
1161: private int messageType;
1162:
1163: public SAXErrorHandler() {
1164: msg = ""; // $NON-NLS-1$
1165:
1166: }
1167:
1168: public void error(SAXParseException exception)
1169: throws SAXParseException {
1170: msg = "error: " + errorDetails(exception);
1171:
1172: log.debug(msg);
1173: messageType = JOptionPane.ERROR_MESSAGE;
1174: throw exception;
1175: }
1176:
1177: /*
1178: * Can be caused by: - premature end of file - non-whitespace content
1179: * after trailer
1180: */
1181: public void fatalError(SAXParseException exception)
1182: throws SAXParseException {
1183:
1184: msg = "fatal: " + errorDetails(exception);
1185: messageType = JOptionPane.ERROR_MESSAGE;
1186: log.debug(msg);
1187:
1188: throw exception;
1189: }
1190:
1191: /*
1192: * Not clear what can cause this ? conflicting versions perhaps
1193: */
1194: public void warning(SAXParseException exception)
1195: throws SAXParseException {
1196: msg = "warning: " + errorDetails(exception);
1197: log.debug(msg);
1198: messageType = JOptionPane.WARNING_MESSAGE;
1199: }
1200:
1201: /**
1202: * get the JOptionPaneMessage Type
1203: *
1204: * @return the message type
1205: */
1206: public int getMessageType() {
1207: return messageType;
1208: }
1209:
1210: /**
1211: * get error message
1212: *
1213: * @return the error message
1214: */
1215: public String getErrorMessage() {
1216: return msg;
1217: }
1218: }
1219:
1220: }
|