View Javadoc

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