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