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