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