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