001: /*
002: * soapUI, copyright (C) 2004-2007 eviware.com
003: *
004: * soapUI is free software; you can redistribute it and/or modify it under the
005: * terms of version 2.1 of the GNU Lesser General Public License as published by
006: * the Free Software Foundation.
007: *
008: * soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
009: * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
010: * See the GNU Lesser General Public License for more details at gnu.org.
011: */
012:
013: package com.eviware.soapui.impl.wsdl.teststeps.assertions;
014:
015: import java.awt.BorderLayout;
016: import java.awt.Dimension;
017: import java.awt.event.ActionEvent;
018:
019: import javax.swing.AbstractAction;
020: import javax.swing.Action;
021: import javax.swing.BorderFactory;
022: import javax.swing.JButton;
023: import javax.swing.JCheckBox;
024: import javax.swing.JDialog;
025: import javax.swing.JPanel;
026: import javax.swing.JScrollPane;
027: import javax.swing.JSplitPane;
028: import javax.swing.JTextArea;
029:
030: import org.apache.log4j.Logger;
031: import org.apache.xmlbeans.XmlAnySimpleType;
032: import org.apache.xmlbeans.XmlCursor;
033: import org.apache.xmlbeans.XmlObject;
034: import org.apache.xmlbeans.XmlOptions;
035: import org.custommonkey.xmlunit.Diff;
036: import org.custommonkey.xmlunit.Difference;
037: import org.custommonkey.xmlunit.DifferenceEngine;
038: import org.custommonkey.xmlunit.DifferenceListener;
039: import org.custommonkey.xmlunit.XMLAssert;
040: import org.w3c.dom.Element;
041: import org.w3c.dom.Node;
042:
043: import com.eviware.soapui.SoapUI;
044: import com.eviware.soapui.config.RequestAssertionConfig;
045: import com.eviware.soapui.impl.wsdl.actions.support.ShowOnlineHelpAction;
046: import com.eviware.soapui.impl.wsdl.submit.WsdlMessageExchange;
047: import com.eviware.soapui.impl.wsdl.submit.filters.PropertyExpansionRequestFilter;
048: import com.eviware.soapui.impl.wsdl.support.HelpUrls;
049: import com.eviware.soapui.impl.wsdl.support.assertions.Assertable;
050: import com.eviware.soapui.impl.wsdl.testcase.WsdlTestRunContext;
051: import com.eviware.soapui.impl.wsdl.teststeps.WsdlMessageAssertion;
052: import com.eviware.soapui.model.iface.SubmitContext;
053: import com.eviware.soapui.support.UISupport;
054: import com.eviware.soapui.support.components.JUndoableTextArea;
055: import com.eviware.soapui.support.components.JXToolBar;
056: import com.eviware.soapui.support.xml.XmlObjectConfigurationBuilder;
057: import com.eviware.soapui.support.xml.XmlObjectConfigurationReader;
058: import com.eviware.soapui.support.xml.XmlUtils;
059: import com.jgoodies.forms.builder.ButtonBarBuilder;
060:
061: /**
062: * Assertion that matches a specified XPath expression and its expected result against
063: * the associated WsdlTestRequests response message
064: *
065: * @author Ole.Matzura
066: */
067:
068: public class XPathContainsAssertion extends WsdlMessageAssertion
069: implements RequestAssertion, ResponseAssertion {
070: private final static Logger log = Logger
071: .getLogger(XPathContainsAssertion.class);
072: private String expectedContent;
073: private String path;
074: private JDialog configurationDialog;
075: private JTextArea pathArea;
076: private JTextArea contentArea;
077: private boolean configureResult;
078: private boolean allowWildcards;
079:
080: public static final String ID = "XPath Match";
081: public static final String LABEL = "XPath Match";
082: private JCheckBox allowWildcardsCheckBox;
083:
084: public XPathContainsAssertion(
085: RequestAssertionConfig assertionConfig,
086: Assertable assertable) {
087: super (assertionConfig, assertable, true, true);
088:
089: XmlObjectConfigurationReader reader = new XmlObjectConfigurationReader(
090: getConfiguration());
091: path = reader.readString("path", null);
092: expectedContent = reader.readString("content", null);
093: allowWildcards = reader.readBoolean("allowWildcards", false);
094: }
095:
096: public String getExpectedContent() {
097: return expectedContent;
098: }
099:
100: public void setContent(String content) {
101: this .expectedContent = content;
102: setConfiguration(createConfiguration());
103: }
104:
105: public String getPath() {
106: return path;
107: }
108:
109: public void setPath(String path) {
110: this .path = path;
111: setConfiguration(createConfiguration());
112: }
113:
114: public boolean isAllowWildcards() {
115: return allowWildcards;
116: }
117:
118: public void setAllowWildcards(boolean allowWildcards) {
119: this .allowWildcards = allowWildcards;
120: }
121:
122: protected String internalAssertResponse(
123: WsdlMessageExchange messageExchange, SubmitContext context)
124: throws AssertionException {
125: return assertContent(messageExchange.getResponseContent(),
126: context, "Response");
127: }
128:
129: public String assertContent(String response, SubmitContext context,
130: String type) throws AssertionException {
131: try {
132: if (path == null)
133: return "Missing path for XPath assertion";
134: if (expectedContent == null)
135: return "Missing content for XPath assertion";
136:
137: XmlObject xml = XmlObject.Factory.parse(response);
138: String expandedPath = PropertyExpansionRequestFilter
139: .expandProperties(context, path);
140: XmlObject[] items = xml.selectPath(expandedPath);
141:
142: XmlObject contentObj = null;
143: String expandedContent = PropertyExpansionRequestFilter
144: .expandProperties(context, expectedContent);
145:
146: try {
147: contentObj = XmlObject.Factory.parse(expandedContent);
148: } catch (Exception e) {
149: // this is ok.. it just means that the content to match is not xml but
150: // (hopefully) just a string
151: }
152:
153: if (items.length == 0)
154: throw new Exception("Missing content for xpath ["
155: + path + "] in " + type);
156:
157: XmlOptions options = new XmlOptions();
158: options.setSavePrettyPrint();
159: options.setSaveOuter();
160:
161: for (int c = 0; c < items.length; c++) {
162: try {
163: if (contentObj == null) {
164: if (items[c] instanceof XmlAnySimpleType) {
165: String value = ((XmlAnySimpleType) items[c])
166: .getStringValue();
167: String expandedValue = PropertyExpansionRequestFilter
168: .expandProperties(context, value);
169: XMLAssert.assertEquals(expandedContent,
170: expandedValue);
171: } else {
172: Node domNode = items[c].getDomNode();
173: if (domNode.getNodeType() == Node.ELEMENT_NODE) {
174: String expandedValue = PropertyExpansionRequestFilter
175: .expandProperties(
176: context,
177: XmlUtils
178: .getElementText((Element) domNode));
179: XMLAssert.assertEquals(expandedContent,
180: expandedValue);
181: } else {
182: String expandedValue = PropertyExpansionRequestFilter
183: .expandProperties(context,
184: domNode.getNodeValue());
185: XMLAssert.assertEquals(expandedContent,
186: expandedValue);
187: }
188: }
189: } else {
190: compareValues(contentObj.xmlText(options),
191: items[c].xmlText(options));
192: }
193:
194: break;
195: } catch (Throwable e) {
196: if (c == items.length - 1)
197: throw e;
198: }
199: }
200: } catch (Throwable e) {
201: String msg = "XPathContains assertion failed for path ["
202: + path + "] : " + e.getClass().getSimpleName()
203: + ":" + e.getMessage();
204:
205: throw new AssertionException(new AssertionError(msg));
206: }
207:
208: return type + " matches content for [" + path + "]";
209: }
210:
211: private void compareValues(String expandedContent,
212: String expandedValue) throws Exception {
213: Diff diff = new Diff(expandedContent, expandedValue);
214: diff.overrideDifferenceListener(new DifferenceListener() {
215:
216: public int differenceFound(Difference diff) {
217: if (allowWildcards
218: && (diff.getId() == DifferenceEngine.TEXT_VALUE
219: .getId() || diff.getId() == DifferenceEngine.ATTR_VALUE
220: .getId())) {
221: if (diff.getControlNodeDetail().getValue().equals(
222: "*"))
223: return Diff.RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
224: }
225:
226: return Diff.RETURN_ACCEPT_DIFFERENCE;
227: }
228:
229: public void skippedComparison(Node arg0, Node arg1) {
230:
231: }
232: });
233:
234: if (!diff.identical())
235: throw new Exception(diff.toString());
236: }
237:
238: public boolean configure() {
239: if (configurationDialog == null)
240: buildConfigurationDialog();
241:
242: pathArea.setText(path);
243: contentArea.setText(expectedContent);
244: allowWildcardsCheckBox.setSelected(allowWildcards);
245:
246: UISupport.showDialog(configurationDialog);
247: return configureResult;
248: }
249:
250: protected void buildConfigurationDialog() {
251: configurationDialog = new JDialog(UISupport.getMainFrame());
252: configurationDialog.setTitle("XPath Match configuration");
253:
254: JPanel contentPanel = new JPanel(new BorderLayout());
255: contentPanel
256: .add(
257: UISupport
258: .buildDescription(
259: "Specify xpath expression and expected result",
260: "declare namespaces with <code>declare namespace <prefix>='<namespace>';</code>",
261: null), BorderLayout.NORTH);
262:
263: JSplitPane splitPane = UISupport.createVerticalSplit();
264:
265: JPanel pathPanel = new JPanel(new BorderLayout());
266: JXToolBar pathToolbar = UISupport.createToolbar();
267: addPathEditorActions(pathToolbar);
268:
269: pathArea = new JUndoableTextArea();
270: pathArea
271: .setToolTipText("Specifies the XPath expression to select from the message for validation");
272:
273: pathPanel.add(pathToolbar, BorderLayout.NORTH);
274: pathPanel.add(new JScrollPane(pathArea), BorderLayout.CENTER);
275:
276: splitPane.setTopComponent(UISupport.addTitledBorder(pathPanel,
277: "XPath Expression"));
278:
279: JPanel matchPanel = new JPanel(new BorderLayout());
280: JXToolBar contentToolbar = UISupport.createToolbar();
281: addMatchEditorActions(contentToolbar);
282:
283: contentArea = new JUndoableTextArea();
284: contentArea
285: .setToolTipText("Specifies the expected result of the XPath expression");
286:
287: matchPanel.add(contentToolbar, BorderLayout.NORTH);
288: matchPanel.add(new JScrollPane(contentArea),
289: BorderLayout.CENTER);
290:
291: splitPane.setBottomComponent(UISupport.addTitledBorder(
292: matchPanel, "Expected Result"));
293: splitPane.setDividerLocation(150);
294: splitPane
295: .setBorder(BorderFactory.createEmptyBorder(0, 1, 0, 1));
296:
297: contentPanel.add(splitPane, BorderLayout.CENTER);
298:
299: ButtonBarBuilder builder = new ButtonBarBuilder();
300:
301: builder.addFixed(UISupport
302: .createToolbarButton(new ShowOnlineHelpAction(
303: HelpUrls.XPATHASSERTIONEDITOR_HELP_URL)));
304: builder.addGlue();
305:
306: builder.addFixed(new JButton(new OkAction()));
307: builder.addRelatedGap();
308: builder.addFixed(new JButton(new CancelAction()));
309:
310: builder.setBorder(BorderFactory.createEmptyBorder(1, 5, 5, 5));
311:
312: contentPanel.add(builder.getPanel(), BorderLayout.SOUTH);
313:
314: configurationDialog.setContentPane(contentPanel);
315: configurationDialog.setSize(600, 500);
316:
317: configurationDialog.setModal(true);
318: }
319:
320: protected void addPathEditorActions(JXToolBar toolbar) {
321: toolbar.addFixed(new JButton(
322: new DeclareNamespacesFromCurrentAction()));
323: }
324:
325: protected void addMatchEditorActions(JXToolBar toolbar) {
326: toolbar.addFixed(new JButton(new SelectFromCurrentAction()));
327: toolbar.addRelatedGap();
328: toolbar.addFixed(new JButton(new TestPathAction()));
329: allowWildcardsCheckBox = new JCheckBox("Allow Wildcards");
330:
331: Dimension dim = new Dimension(100, 20);
332:
333: allowWildcardsCheckBox.setSize(dim);
334: allowWildcardsCheckBox.setPreferredSize(dim);
335:
336: allowWildcardsCheckBox.setOpaque(false);
337: toolbar.addRelatedGap();
338: toolbar.addFixed(allowWildcardsCheckBox);
339: }
340:
341: public XmlObject createConfiguration() {
342: XmlObjectConfigurationBuilder builder = new XmlObjectConfigurationBuilder();
343: builder.add("path", path);
344: builder.add("content", expectedContent);
345: builder.add("allowWildcards", allowWildcards);
346: return builder.finish();
347: }
348:
349: public void selectFromCurrent() {
350: XmlCursor cursor = null;
351:
352: try {
353: XmlOptions options = new XmlOptions();
354: options.setSavePrettyPrint();
355: options.setSaveOuter();
356: options.setSaveAggressiveNamespaces();
357:
358: String assertableContent = getAssertable()
359: .getAssertableContent();
360: if (assertableContent == null
361: || assertableContent.trim().length() == 0) {
362: UISupport
363: .showErrorMessage("Missing content to select from");
364: return;
365: }
366:
367: XmlObject xml = XmlObject.Factory.parse(assertableContent);
368:
369: String txt = pathArea == null || !pathArea.isVisible() ? getPath()
370: : pathArea.getSelectedText();
371: if (txt == null)
372: txt = pathArea == null ? "" : pathArea.getText();
373:
374: WsdlTestRunContext context = new WsdlTestRunContext(
375: getAssertable().getTestStep());
376:
377: String expandedPath = PropertyExpansionRequestFilter
378: .expandProperties(context, txt.trim());
379:
380: if (contentArea != null && contentArea.isVisible())
381: contentArea.setText("");
382:
383: cursor = xml.newCursor();
384: cursor.selectPath(expandedPath);
385: if (!cursor.toNextSelection()) {
386: UISupport
387: .showErrorMessage("No match in current response");
388: } else if (cursor.hasNextSelection()) {
389: UISupport
390: .showErrorMessage("More than one match in current response");
391: } else {
392: Node domNode = cursor.getDomNode();
393: String stringValue = null;
394:
395: if (domNode.getNodeType() == Node.ATTRIBUTE_NODE
396: || domNode.getNodeType() == Node.TEXT_NODE) {
397: stringValue = domNode.getNodeValue();
398: } else if (cursor.getObject() instanceof XmlAnySimpleType) {
399: stringValue = ((XmlAnySimpleType) cursor
400: .getObject()).getStringValue();
401: } else {
402: if (domNode.getNodeType() == Node.ELEMENT_NODE) {
403: Element elm = (Element) domNode;
404: if (elm.getChildNodes().getLength() == 1
405: && elm.getAttributes().getLength() == 0)
406: stringValue = XmlUtils.getElementText(elm);
407: else
408: stringValue = cursor.getObject().xmlText(
409: options);
410: } else {
411: stringValue = domNode.getNodeValue();
412: }
413: }
414:
415: if (contentArea != null && contentArea.isVisible())
416: contentArea.setText(stringValue);
417: else
418: setContent(stringValue);
419: }
420: } catch (Throwable e) {
421: UISupport.showErrorMessage(e.toString());
422: SoapUI.logError(e);
423: } finally {
424: if (cursor != null)
425: cursor.dispose();
426: }
427: }
428:
429: public class OkAction extends AbstractAction {
430: public OkAction() {
431: super ("Save");
432: }
433:
434: public void actionPerformed(ActionEvent arg0) {
435: setPath(pathArea.getText().trim());
436: setContent(contentArea.getText());
437: setAllowWildcards(allowWildcardsCheckBox.isSelected());
438: setConfiguration(createConfiguration());
439: configureResult = true;
440: configurationDialog.setVisible(false);
441: }
442: }
443:
444: public class CancelAction extends AbstractAction {
445: public CancelAction() {
446: super ("Cancel");
447: }
448:
449: public void actionPerformed(ActionEvent arg0) {
450: configureResult = false;
451: configurationDialog.setVisible(false);
452: }
453: }
454:
455: public class DeclareNamespacesFromCurrentAction extends
456: AbstractAction {
457: public DeclareNamespacesFromCurrentAction() {
458: super ("Declare");
459: putValue(Action.SHORT_DESCRIPTION,
460: "Add namespace declaration from current message to XPath expression");
461: }
462:
463: public void actionPerformed(ActionEvent arg0) {
464: try {
465: String content = getAssertable().getAssertableContent();
466: if (content != null && content.trim().length() > 0) {
467: pathArea.setText(XmlUtils
468: .declareXPathNamespaces(content)
469: + pathArea.getText());
470: } else if (UISupport.confirm(
471: "Declare namespaces from schema instead?",
472: "Missing Response")) {
473: pathArea.setText(XmlUtils
474: .declareXPathNamespaces(getAssertable()
475: .getInterface())
476: + pathArea.getText());
477: }
478: } catch (Exception e) {
479: log.error(e.getMessage());
480: }
481: }
482: }
483:
484: public class TestPathAction extends AbstractAction {
485: public TestPathAction() {
486: super ("Test");
487: putValue(
488: Action.SHORT_DESCRIPTION,
489: "Tests the XPath expression for the current message against the Expected Content field");
490: }
491:
492: public void actionPerformed(ActionEvent arg0) {
493: String oldPath = getPath();
494: String oldContent = getExpectedContent();
495: boolean oldAllowWildcards = isAllowWildcards();
496:
497: setPath(pathArea.getText().trim());
498: setContent(contentArea.getText());
499: setAllowWildcards(allowWildcardsCheckBox.isSelected());
500:
501: try {
502: String msg = assertContent(getAssertable()
503: .getAssertableContent(),
504: new WsdlTestRunContext(getAssertable()
505: .getTestStep()), "Response");
506: UISupport.showInfoMessage(msg, "Success");
507: } catch (AssertionException e) {
508: UISupport.showErrorMessage(e.getMessage());
509: }
510:
511: setPath(oldPath);
512: setContent(oldContent);
513: setAllowWildcards(oldAllowWildcards);
514: }
515: }
516:
517: public class SelectFromCurrentAction extends AbstractAction {
518: public SelectFromCurrentAction() {
519: super ("Select from current");
520: putValue(
521: Action.SHORT_DESCRIPTION,
522: "Selects the XPath expression from the current message into the Expected Content field");
523: }
524:
525: public void actionPerformed(ActionEvent arg0) {
526: selectFromCurrent();
527: }
528: }
529:
530: @Override
531: protected String internalAssertRequest(
532: WsdlMessageExchange messageExchange, SubmitContext context)
533: throws AssertionException {
534: if (!messageExchange.hasRequest(true))
535: return "Missing Request";
536: else
537: return assertContent(messageExchange.getRequestContent(),
538: context, "Request");
539: }
540:
541: public JTextArea getContentArea() {
542: return contentArea;
543: }
544:
545: public JTextArea getPathArea() {
546: return pathArea;
547: }
548: }
|