1
2
3
4
5
6
7
8
9
10
11
12
13 package com.eviware.soapui.impl.wsdl.teststeps.assertions;
14
15 import java.awt.BorderLayout;
16 import java.awt.Dimension;
17 import java.awt.event.ActionEvent;
18 import java.awt.event.WindowAdapter;
19 import java.awt.event.WindowEvent;
20 import java.util.ArrayList;
21 import java.util.List;
22
23 import javax.swing.AbstractAction;
24 import javax.swing.Action;
25 import javax.swing.BorderFactory;
26 import javax.swing.JButton;
27 import javax.swing.JCheckBox;
28 import javax.swing.JDialog;
29 import javax.swing.JPanel;
30 import javax.swing.JScrollPane;
31 import javax.swing.JSplitPane;
32 import javax.swing.JTextArea;
33 import javax.swing.SwingUtilities;
34
35 import org.apache.log4j.Logger;
36 import org.apache.xmlbeans.XmlAnySimpleType;
37 import org.apache.xmlbeans.XmlCursor;
38 import org.apache.xmlbeans.XmlObject;
39 import org.apache.xmlbeans.XmlOptions;
40 import org.custommonkey.xmlunit.Diff;
41 import org.custommonkey.xmlunit.Difference;
42 import org.custommonkey.xmlunit.DifferenceEngine;
43 import org.custommonkey.xmlunit.DifferenceListener;
44 import org.custommonkey.xmlunit.XMLAssert;
45 import org.w3c.dom.Element;
46 import org.w3c.dom.Node;
47
48 import com.eviware.soapui.SoapUI;
49 import com.eviware.soapui.config.RequestAssertionConfig;
50 import com.eviware.soapui.impl.wsdl.WsdlInterface;
51 import com.eviware.soapui.impl.wsdl.actions.support.ShowOnlineHelpAction;
52 import com.eviware.soapui.impl.wsdl.submit.WsdlMessageExchange;
53 import com.eviware.soapui.impl.wsdl.support.HelpUrls;
54 import com.eviware.soapui.impl.wsdl.support.assertions.AssertedXPathImpl;
55 import com.eviware.soapui.impl.wsdl.support.assertions.AssertedXPathsContainer;
56 import com.eviware.soapui.impl.wsdl.testcase.WsdlTestRunContext;
57 import com.eviware.soapui.impl.wsdl.teststeps.WsdlMessageAssertion;
58 import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequestStep;
59 import com.eviware.soapui.model.TestModelItem;
60 import com.eviware.soapui.model.iface.SubmitContext;
61 import com.eviware.soapui.model.propertyexpansion.PropertyExpansion;
62 import com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils;
63 import com.eviware.soapui.model.support.XPathReference;
64 import com.eviware.soapui.model.support.XPathReferenceContainer;
65 import com.eviware.soapui.model.support.XPathReferenceImpl;
66 import com.eviware.soapui.model.testsuite.Assertable;
67 import com.eviware.soapui.model.testsuite.AssertionError;
68 import com.eviware.soapui.model.testsuite.AssertionException;
69 import com.eviware.soapui.model.testsuite.RequestAssertion;
70 import com.eviware.soapui.model.testsuite.ResponseAssertion;
71 import com.eviware.soapui.model.testsuite.TestProperty;
72 import com.eviware.soapui.model.testsuite.TestStep;
73 import com.eviware.soapui.support.StringUtils;
74 import com.eviware.soapui.support.UISupport;
75 import com.eviware.soapui.support.components.JUndoableTextArea;
76 import com.eviware.soapui.support.components.JXToolBar;
77 import com.eviware.soapui.support.types.StringList;
78 import com.eviware.soapui.support.xml.XmlObjectConfigurationBuilder;
79 import com.eviware.soapui.support.xml.XmlObjectConfigurationReader;
80 import com.eviware.soapui.support.xml.XmlUtils;
81 import com.jgoodies.forms.builder.ButtonBarBuilder;
82
83 /***
84 * Assertion that matches a specified XPath expression and its expected result
85 * against the associated WsdlTestRequests response message
86 *
87 * @author Ole.Matzura
88 */
89
90 public class XPathContainsAssertion extends WsdlMessageAssertion implements RequestAssertion, ResponseAssertion, XPathReferenceContainer
91 {
92 private final static Logger log = Logger.getLogger( XPathContainsAssertion.class );
93 private String expectedContent;
94 private String path;
95 private JDialog configurationDialog;
96 private JTextArea pathArea;
97 private JTextArea contentArea;
98 private boolean configureResult;
99 private boolean allowWildcards;
100
101 public static final String ID = "XPath Match";
102 public static final String LABEL = "XPath Match";
103 private JCheckBox allowWildcardsCheckBox;
104
105 public XPathContainsAssertion( RequestAssertionConfig assertionConfig, Assertable assertable )
106 {
107 super( assertionConfig, assertable, true, true, true, true );
108
109 XmlObjectConfigurationReader reader = new XmlObjectConfigurationReader( getConfiguration() );
110 path = reader.readString( "path", null );
111 expectedContent = reader.readString( "content", null );
112 allowWildcards = reader.readBoolean( "allowWildcards", false );
113 }
114
115 public String getExpectedContent()
116 {
117 return expectedContent;
118 }
119
120 public void setExpectedContent( String expectedContent )
121 {
122 this.expectedContent = expectedContent;
123 setConfiguration( createConfiguration() );
124 }
125
126 /***
127 * @deprecated
128 */
129
130 @Deprecated
131 public void setContent( String content )
132 {
133 setExpectedContent( content );
134 }
135
136 public String getPath()
137 {
138 return path;
139 }
140
141 public void setPath( String path )
142 {
143 this.path = path;
144 setConfiguration( createConfiguration() );
145 }
146
147 public boolean isAllowWildcards()
148 {
149 return allowWildcards;
150 }
151
152 public void setAllowWildcards( boolean allowWildcards )
153 {
154 this.allowWildcards = allowWildcards;
155 }
156
157 @Override
158 protected String internalAssertResponse( WsdlMessageExchange messageExchange, SubmitContext context )
159 throws AssertionException
160 {
161 return assertContent( messageExchange.getResponseContent(), context, "Response" );
162 }
163
164 public String assertContent( String response, SubmitContext context, String type ) throws AssertionException
165 {
166 try
167 {
168 if( path == null )
169 return "Missing path for XPath assertion";
170 if( expectedContent == null )
171 return "Missing content for XPath assertion";
172
173 XmlObject xml = XmlObject.Factory.parse( response );
174 String expandedPath = PropertyExpansionUtils.expandProperties( context, path );
175 XmlObject[] items = xml.selectPath( expandedPath );
176 AssertedXPathsContainer assertedXPathsContainer = ( AssertedXPathsContainer )
177 context.getProperty( AssertedXPathsContainer.ASSERTEDXPATHSCONTAINER_PROPERTY );
178
179 XmlObject contentObj = null;
180 String expandedContent = PropertyExpansionUtils.expandProperties( context, expectedContent );
181
182 try
183 {
184 contentObj = XmlObject.Factory.parse( expandedContent );
185 }
186 catch( Exception e )
187 {
188
189
190
191 }
192
193 if( items.length == 0 )
194 throw new Exception( "Missing content for xpath [" + path + "] in " + type );
195
196 XmlOptions options = new XmlOptions();
197 options.setSavePrettyPrint();
198 options.setSaveOuter();
199
200 for( int c = 0; c < items.length; c++ )
201 {
202 try
203 {
204 AssertedXPathImpl assertedXPathImpl = null;
205 if( assertedXPathsContainer != null )
206 {
207 String xpath = XmlUtils.createAbsoluteXPath( items[c].getDomNode() );
208 if(xpath != null)
209 {
210 XmlObject xmlObj = items[c];
211
212 assertedXPathImpl = new AssertedXPathImpl( this, xpath, xmlObj );
213 assertedXPathsContainer.addAssertedXPath( assertedXPathImpl );
214 }
215 }
216
217 if( contentObj == null )
218 {
219 if( items[c] instanceof XmlAnySimpleType )
220 {
221 String value = ( ( XmlAnySimpleType ) items[c] ).getStringValue();
222 String expandedValue = PropertyExpansionUtils.expandProperties( context, value );
223 XMLAssert.assertEquals( expandedContent, expandedValue );
224 }
225 else
226 {
227 Node domNode = items[c].getDomNode();
228 if( domNode.getNodeType() == Node.ELEMENT_NODE )
229 {
230 String expandedValue = PropertyExpansionUtils.expandProperties( context, XmlUtils
231 .getElementText( ( Element ) domNode ) );
232 XMLAssert.assertEquals( expandedContent, expandedValue );
233 }
234 else
235 {
236 String expandedValue = PropertyExpansionUtils.expandProperties( context, domNode
237 .getNodeValue() );
238 XMLAssert.assertEquals( expandedContent, expandedValue );
239 }
240 }
241 }
242 else
243 {
244 compareValues( contentObj.xmlText( options ), items[c].xmlText( options ), items[c] );
245 }
246
247 break;
248 }
249 catch( Throwable e )
250 {
251 if( c == items.length - 1 )
252 throw e;
253 }
254 }
255 }
256 catch( Throwable e )
257 {
258 String msg = "XPathContains assertion failed for path [" + path + "] : " + e.getClass().getSimpleName() + ":"
259 + e.getMessage();
260
261 throw new AssertionException( new AssertionError( msg ) );
262 }
263
264 return type + " matches content for [" + path + "]";
265 }
266
267 private void compareValues( String expandedContent, String expandedValue, XmlObject object ) throws Exception
268 {
269 Diff diff = new Diff( expandedContent, expandedValue );
270 InternalDifferenceListener internalDifferenceListener = new InternalDifferenceListener();
271 diff.overrideDifferenceListener( internalDifferenceListener );
272
273 if( !diff.identical() )
274 throw new Exception( diff.toString() );
275
276 StringList nodesToRemove = internalDifferenceListener.getNodesToRemove();
277
278 if( !nodesToRemove.isEmpty())
279 {
280 for( String node : nodesToRemove )
281 {
282 int ix = node.indexOf( "\n/" );
283 if( ix != -1 )
284 node = node.substring( 0, ix+1 ) + "/" + node.substring( ix+1 );
285 else if( node.startsWith( "/" ))
286 node = "/" + node;
287
288 XmlObject[] paths = object.selectPath( node );
289 if( paths.length > 0 )
290 {
291 Node domNode = paths[0].getDomNode();
292 domNode.getParentNode().removeChild( domNode );
293 }
294 }
295 }
296 }
297
298 @Override
299 public boolean configure()
300 {
301 if( configurationDialog == null )
302 buildConfigurationDialog();
303
304 pathArea.setText( path );
305 contentArea.setText( expectedContent );
306 allowWildcardsCheckBox.setSelected( allowWildcards );
307
308 UISupport.showDialog( configurationDialog );
309 return configureResult;
310 }
311
312 protected void buildConfigurationDialog()
313 {
314 configurationDialog = new JDialog( UISupport.getMainFrame() );
315 configurationDialog.setTitle( "XPath Match configuration" );
316 configurationDialog.addWindowListener( new WindowAdapter()
317 {
318 @Override
319 public void windowOpened( WindowEvent event )
320 {
321 SwingUtilities.invokeLater( new Runnable()
322 {
323 public void run()
324 {
325 pathArea.requestFocusInWindow();
326 }
327 } );
328 }
329 } );
330
331 JPanel contentPanel = new JPanel( new BorderLayout() );
332 contentPanel.add( UISupport.buildDescription( "Specify xpath expression and expected result",
333 "declare namespaces with <code>declare namespace <prefix>='<namespace>';</code>", null ),
334 BorderLayout.NORTH );
335
336 JSplitPane splitPane = UISupport.createVerticalSplit();
337
338 JPanel pathPanel = new JPanel( new BorderLayout() );
339 JXToolBar pathToolbar = UISupport.createToolbar();
340 addPathEditorActions( pathToolbar );
341
342 pathArea = new JUndoableTextArea();
343 pathArea.setToolTipText( "Specifies the XPath expression to select from the message for validation" );
344
345 pathPanel.add( pathToolbar, BorderLayout.NORTH );
346 pathPanel.add( new JScrollPane( pathArea ), BorderLayout.CENTER );
347
348 splitPane.setTopComponent( UISupport.addTitledBorder( pathPanel, "XPath Expression" ) );
349
350 JPanel matchPanel = new JPanel( new BorderLayout() );
351 JXToolBar contentToolbar = UISupport.createToolbar();
352 addMatchEditorActions( contentToolbar );
353
354 contentArea = new JUndoableTextArea();
355 contentArea.setToolTipText( "Specifies the expected result of the XPath expression" );
356
357 matchPanel.add( contentToolbar, BorderLayout.NORTH );
358 matchPanel.add( new JScrollPane( contentArea ), BorderLayout.CENTER );
359
360 splitPane.setBottomComponent( UISupport.addTitledBorder( matchPanel, "Expected Result" ) );
361 splitPane.setDividerLocation( 150 );
362 splitPane.setBorder( BorderFactory.createEmptyBorder( 0, 1, 0, 1 ) );
363
364 contentPanel.add( splitPane, BorderLayout.CENTER );
365
366 ButtonBarBuilder builder = new ButtonBarBuilder();
367
368 ShowOnlineHelpAction showOnlineHelpAction = new ShowOnlineHelpAction( HelpUrls.XPATHASSERTIONEDITOR_HELP_URL );
369 builder.addFixed( UISupport.createToolbarButton( showOnlineHelpAction ) );
370 builder.addGlue();
371
372 JButton okButton = new JButton( new OkAction() );
373 builder.addFixed( okButton );
374 builder.addRelatedGap();
375 builder.addFixed( new JButton( new CancelAction() ) );
376
377 builder.setBorder( BorderFactory.createEmptyBorder( 1, 5, 5, 5 ) );
378
379 contentPanel.add( builder.getPanel(), BorderLayout.SOUTH );
380
381 configurationDialog.setContentPane( contentPanel );
382 configurationDialog.setSize( 600, 500 );
383 configurationDialog.setModal( true );
384 UISupport.initDialogActions( configurationDialog, showOnlineHelpAction, okButton );
385 }
386
387 protected void addPathEditorActions( JXToolBar toolbar )
388 {
389 toolbar.addFixed( new JButton( new DeclareNamespacesFromCurrentAction() ) );
390 }
391
392 protected void addMatchEditorActions( JXToolBar toolbar )
393 {
394 toolbar.addFixed( new JButton( new SelectFromCurrentAction() ) );
395 toolbar.addRelatedGap();
396 toolbar.addFixed( new JButton( new TestPathAction() ) );
397 allowWildcardsCheckBox = new JCheckBox( "Allow Wildcards" );
398
399 Dimension dim = new Dimension( 100, 20 );
400
401 allowWildcardsCheckBox.setSize( dim );
402 allowWildcardsCheckBox.setPreferredSize( dim );
403
404 allowWildcardsCheckBox.setOpaque( false );
405 toolbar.addRelatedGap();
406 toolbar.addFixed( allowWildcardsCheckBox );
407 }
408
409 public XmlObject createConfiguration()
410 {
411 XmlObjectConfigurationBuilder builder = new XmlObjectConfigurationBuilder();
412 builder.add( "path", path );
413 builder.add( "content", expectedContent );
414 builder.add( "allowWildcards", allowWildcards );
415 return builder.finish();
416 }
417
418 public void selectFromCurrent()
419 {
420 XmlCursor cursor = null;
421
422 try
423 {
424 XmlOptions options = new XmlOptions();
425 options.setSavePrettyPrint();
426 options.setSaveOuter();
427 options.setSaveAggressiveNamespaces();
428
429 String assertableContent = getAssertable().getAssertableContent();
430 if( assertableContent == null || assertableContent.trim().length() == 0 )
431 {
432 UISupport.showErrorMessage( "Missing content to select from" );
433 return;
434 }
435
436 XmlObject xml = XmlObject.Factory.parse( assertableContent );
437
438 String txt = pathArea == null || !pathArea.isVisible() ? getPath() : pathArea.getSelectedText();
439 if( txt == null )
440 txt = pathArea == null ? "" : pathArea.getText();
441
442 WsdlTestRunContext context = new WsdlTestRunContext( ( TestStep ) getAssertable().getModelItem() );
443
444 String expandedPath = PropertyExpansionUtils.expandProperties( context, txt.trim() );
445
446 if( contentArea != null && contentArea.isVisible() )
447 contentArea.setText( "" );
448
449 cursor = xml.newCursor();
450 cursor.selectPath( expandedPath );
451 if( !cursor.toNextSelection() )
452 {
453 UISupport.showErrorMessage( "No match in current response" );
454 }
455 else if( cursor.hasNextSelection() )
456 {
457 UISupport.showErrorMessage( "More than one match in current response" );
458 }
459 else
460 {
461 Node domNode = cursor.getDomNode();
462 String stringValue = null;
463
464 if( domNode.getNodeType() == Node.ATTRIBUTE_NODE || domNode.getNodeType() == Node.TEXT_NODE )
465 {
466 stringValue = domNode.getNodeValue();
467 }
468 else if( cursor.getObject() instanceof XmlAnySimpleType )
469 {
470 stringValue = ( ( XmlAnySimpleType ) cursor.getObject() ).getStringValue();
471 }
472 else
473 {
474 if( domNode.getNodeType() == Node.ELEMENT_NODE )
475 {
476 Element elm = ( Element ) domNode;
477 if( elm.getChildNodes().getLength() == 1 && elm.getAttributes().getLength() == 0 )
478 stringValue = XmlUtils.getElementText( elm );
479 else
480 stringValue = cursor.getObject().xmlText( options );
481 }
482 else
483 {
484 stringValue = domNode.getNodeValue();
485 }
486 }
487
488 if( contentArea != null && contentArea.isVisible() )
489 contentArea.setText( stringValue );
490 else
491 setExpectedContent( stringValue );
492 }
493 }
494 catch( Throwable e )
495 {
496 UISupport.showErrorMessage( e.toString() );
497 SoapUI.logError( e );
498 }
499 finally
500 {
501 if( cursor != null )
502 cursor.dispose();
503 }
504 }
505
506 private final class InternalDifferenceListener implements DifferenceListener
507 {
508 private StringList nodesToRemove = new StringList();
509
510 public int differenceFound( Difference diff )
511 {
512 if( allowWildcards
513 && ( diff.getId() == DifferenceEngine.TEXT_VALUE.getId() || diff.getId() == DifferenceEngine.ATTR_VALUE.getId() ) )
514 {
515 if( diff.getControlNodeDetail().getValue().equals( "*" ) )
516 {
517 String xp = XmlUtils.createAbsoluteXPath( diff.getTestNodeDetail().getNode().getParentNode() );
518 nodesToRemove.add( xp );
519 return Diff.RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
520 }
521 }
522
523 return Diff.RETURN_ACCEPT_DIFFERENCE;
524 }
525
526 public void skippedComparison( Node arg0, Node arg1 )
527 {
528
529 }
530
531 public StringList getNodesToRemove()
532 {
533 return nodesToRemove;
534 }
535 }
536
537 public class OkAction extends AbstractAction
538 {
539 public OkAction()
540 {
541 super( "Save" );
542 }
543
544 public void actionPerformed( ActionEvent arg0 )
545 {
546 setPath( pathArea.getText().trim() );
547 setExpectedContent( contentArea.getText() );
548 setAllowWildcards( allowWildcardsCheckBox.isSelected() );
549 setConfiguration( createConfiguration() );
550 configureResult = true;
551 configurationDialog.setVisible( false );
552 }
553 }
554
555 public class CancelAction extends AbstractAction
556 {
557 public CancelAction()
558 {
559 super( "Cancel" );
560 }
561
562 public void actionPerformed( ActionEvent arg0 )
563 {
564 configureResult = false;
565 configurationDialog.setVisible( false );
566 }
567 }
568
569 public class DeclareNamespacesFromCurrentAction extends AbstractAction
570 {
571 public DeclareNamespacesFromCurrentAction()
572 {
573 super( "Declare" );
574 putValue( Action.SHORT_DESCRIPTION, "Add namespace declaration from current message to XPath expression" );
575 }
576
577 public void actionPerformed( ActionEvent arg0 )
578 {
579 try
580 {
581 String content = getAssertable().getAssertableContent();
582 if( content != null && content.trim().length() > 0 )
583 {
584 pathArea.setText( XmlUtils.declareXPathNamespaces( content ) + pathArea.getText() );
585 }
586 else if( UISupport.confirm( "Declare namespaces from schema instead?", "Missing Response" ) )
587 {
588 pathArea.setText( XmlUtils.declareXPathNamespaces( (WsdlInterface)getAssertable().getInterface() )
589 + pathArea.getText() );
590 }
591 }
592 catch( Exception e )
593 {
594 log.error( e.getMessage() );
595 }
596 }
597 }
598
599 public class TestPathAction extends AbstractAction
600 {
601 public TestPathAction()
602 {
603 super( "Test" );
604 putValue( Action.SHORT_DESCRIPTION,
605 "Tests the XPath expression for the current message against the Expected Content field" );
606 }
607
608 public void actionPerformed( ActionEvent arg0 )
609 {
610 String oldPath = getPath();
611 String oldContent = getExpectedContent();
612 boolean oldAllowWildcards = isAllowWildcards();
613
614 setPath( pathArea.getText().trim() );
615 setContent( contentArea.getText() );
616 setAllowWildcards( allowWildcardsCheckBox.isSelected() );
617
618 try
619 {
620 String msg = assertContent( getAssertable().getAssertableContent(), new WsdlTestRunContext( ( TestStep ) getAssertable()
621 .getModelItem() ), "Response" );
622 UISupport.showInfoMessage( msg, "Success" );
623 }
624 catch( AssertionException e )
625 {
626 UISupport.showErrorMessage( e.getMessage() );
627 }
628
629 setPath( oldPath );
630 setContent( oldContent );
631 setAllowWildcards( oldAllowWildcards );
632 }
633 }
634
635 public class SelectFromCurrentAction extends AbstractAction
636 {
637 public SelectFromCurrentAction()
638 {
639 super( "Select from current" );
640 putValue( Action.SHORT_DESCRIPTION,
641 "Selects the XPath expression from the current message into the Expected Content field" );
642 }
643
644 public void actionPerformed( ActionEvent arg0 )
645 {
646 selectFromCurrent();
647 }
648 }
649
650 @Override
651 protected String internalAssertRequest( WsdlMessageExchange messageExchange, SubmitContext context )
652 throws AssertionException
653 {
654 if( !messageExchange.hasRequest( true ) )
655 return "Missing Request";
656 else
657 return assertContent( messageExchange.getRequestContent(), context, "Request" );
658 }
659
660 public JTextArea getContentArea()
661 {
662 return contentArea;
663 }
664
665 public JTextArea getPathArea()
666 {
667 return pathArea;
668 }
669
670 @Override
671 public PropertyExpansion[] getPropertyExpansions()
672 {
673 List<PropertyExpansion> result = new ArrayList<PropertyExpansion>();
674
675 result.addAll( PropertyExpansionUtils.extractPropertyExpansions( getAssertable().getModelItem(), this, "expectedContent") );
676 result.addAll( PropertyExpansionUtils.extractPropertyExpansions( getAssertable().getModelItem(), this, "path") );
677
678 return result.toArray( new PropertyExpansion[result.size()] );
679 }
680
681 public XPathReference[] getXPathReferences()
682 {
683 List<XPathReference> result = new ArrayList<XPathReference>();
684
685 if( StringUtils.hasContent( getPath() ))
686 {
687 TestModelItem testStep = ( TestModelItem ) getAssertable().getModelItem();
688 TestProperty property = testStep instanceof WsdlTestRequestStep ? testStep.getProperty( "Response" ) : testStep.getProperty( "Request" );
689 result.add( new XPathReferenceImpl( "XPath for " + getName() + " XPathContainsAssertion in " + testStep.getName(), property, this, "path" ));
690 }
691
692 return result.toArray( new XPathReference[result.size()] );
693 }
694 }