View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2007 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of version 2.1 of the GNU Lesser General Public License as published by 
6    *  the Free Software Foundation.
7    *
8    *  soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
9    *  even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
10   *  See the GNU Lesser General Public License for more details at gnu.org.
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 				// this is ok.. it just means that the content to match is not xml
188 				// but
189 				// (hopefully) just a string
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 &lt;prefix&gt;='&lt;namespace&gt;';</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 }