View Javadoc

1   /*
2    *  soapUI, copyright (C) 2006 eviware.com 
3    *
4    *  soapUI is free software; you can redistribute it and/or modify it under the 
5    *  terms of the GNU Lesser General Public License as published by the Free Software Foundation; 
6    *  either version 2.1 of the License, or (at your option) any later version.
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.event.ActionEvent;
17  
18  import javax.swing.AbstractAction;
19  import javax.swing.Action;
20  import javax.swing.BorderFactory;
21  import javax.swing.JDialog;
22  import javax.swing.JPanel;
23  import javax.swing.JScrollPane;
24  import javax.swing.JSplitPane;
25  import javax.swing.JTextArea;
26  
27  import org.apache.log4j.Logger;
28  import org.apache.xmlbeans.XmlAnySimpleType;
29  import org.apache.xmlbeans.XmlObject;
30  import org.apache.xmlbeans.XmlOptions;
31  import org.custommonkey.xmlunit.XMLAssert;
32  import org.w3c.dom.Element;
33  import org.w3c.dom.Node;
34  
35  import com.eviware.soapui.config.RequestAssertionConfig;
36  import com.eviware.soapui.impl.wsdl.WsdlSubmitContext;
37  import com.eviware.soapui.impl.wsdl.actions.support.ShowOnlineHelpAction;
38  import com.eviware.soapui.impl.wsdl.panels.support.assertions.Assertable;
39  import com.eviware.soapui.impl.wsdl.submit.WsdlMessageExchange;
40  import com.eviware.soapui.impl.wsdl.submit.filters.PropertyExpansionRequestFilter;
41  import com.eviware.soapui.impl.wsdl.support.HelpUrls;
42  import com.eviware.soapui.impl.wsdl.testcase.WsdlTestRunContext;
43  import com.eviware.soapui.impl.wsdl.teststeps.WsdlMessageAssertion;
44  import com.eviware.soapui.model.iface.SubmitContext;
45  import com.eviware.soapui.support.UISupport;
46  import com.eviware.soapui.support.action.ActionList;
47  import com.eviware.soapui.support.action.ActionSupport;
48  import com.eviware.soapui.support.action.DefaultActionList;
49  import com.eviware.soapui.support.components.JUndoableTextArea;
50  import com.eviware.soapui.support.xml.XmlObjectConfigurationBuilder;
51  import com.eviware.soapui.support.xml.XmlObjectConfigurationReader;
52  import com.eviware.soapui.support.xml.XmlUtils;
53  import com.jgoodies.forms.builder.ButtonBarBuilder;
54  
55  /***
56   * Assertion that matches a specified XPath expression and its expected result against
57   * the associated WsdlTestRequests response message
58   * 
59   * @author Ole.Matzura
60   */
61  
62  public class XPathContainsAssertion extends WsdlMessageAssertion implements RequestAssertion, ResponseAssertion
63  {
64  	private final static Logger log = Logger.getLogger( XPathContainsAssertion.class );
65  	private String expectedContent;
66     private String path;
67  	private JDialog configurationDialog;
68  	private JTextArea pathArea;
69  	private JTextArea contentArea;
70  	public boolean configureResult;
71  	public static final String ID = "XPath Match";
72  	public static final String LABEL = "XPath Match";
73  
74     public XPathContainsAssertion(RequestAssertionConfig assertionConfig, Assertable assertable)
75     {
76        super(assertionConfig, assertable);
77        
78        XmlObjectConfigurationReader reader = new XmlObjectConfigurationReader( getConfiguration());
79        path = reader.readString( "path", null );
80        expectedContent = reader.readString( "content", null );
81     }
82     
83     public String getExpectedContent()
84  	{
85  		return expectedContent;
86  	}
87  
88  	public void setContent(String content)
89  	{
90  		this.expectedContent = content;
91  	}
92  
93  	public String getPath()
94  	{
95  		return path;
96  	}
97  
98  	public void setPath(String path)
99  	{
100 		this.path = path;
101 	}
102 
103 	protected String internalAssertResponse( WsdlMessageExchange messageExchange, SubmitContext context ) throws AssertionException
104    {
105 		return assertContent( messageExchange.getResponseContent(), context, "Response" );
106    }
107 	
108 	public String assertContent( String response, SubmitContext context, String type ) throws AssertionException
109 	{
110       try
111 		{
112       	if( path == null ) return "Missing path for XPath assertion";
113       	if( expectedContent == null ) return "Missing content for XPath assertion";
114       	
115       	XmlObject xml = XmlObject.Factory.parse( response );
116       	String expandedPath = PropertyExpansionRequestFilter.expandProperties( context, path );
117 			XmlObject[] items = xml.selectPath( expandedPath);
118       	
119       	XmlObject contentObj = null;
120       	
121       	try
122 			{
123 				contentObj = XmlObject.Factory.parse(expectedContent);
124 			}
125 			catch (Exception e)
126 			{
127 				// this is ok.. it just means that the content to match is not xml but
128 				// (hopefully) just a string
129 			}
130       	
131       	if( items.length == 0 )
132       		throw new Exception( "Missing content for xpath [" + path + "] in " + type );
133       	
134       	XmlOptions options = new XmlOptions();
135    		options.setSavePrettyPrint();
136    		options.setSaveOuter();
137    		
138       	for( int c = 0; c < items.length; c++ )
139       	{
140       		try
141 				{
142 					if (contentObj == null)
143 					{
144 						String expandedContent = PropertyExpansionRequestFilter.expandProperties( context, expectedContent );
145 						if( items[c] instanceof XmlAnySimpleType )
146 						{
147 							String value = ((XmlAnySimpleType)items[c]).getStringValue();
148 							String expandedValue = PropertyExpansionRequestFilter.expandProperties( context, value );
149 							XMLAssert.assertEquals(expandedContent,expandedValue);
150 						}
151 						else
152 						{
153 							Node domNode = items[c].getDomNode();
154 							if (domNode.getNodeType() == Node.ELEMENT_NODE)
155 							{
156 								String expandedValue = PropertyExpansionRequestFilter.expandProperties( context, 
157 										XmlUtils.getElementText((Element) domNode) );
158 								XMLAssert.assertEquals(expandedContent, expandedValue);
159 							}
160 							else
161 							{
162 								String expandedValue = PropertyExpansionRequestFilter.expandProperties( context, 
163 										domNode.getNodeValue() );
164 								XMLAssert.assertEquals(expandedContent, expandedValue );
165 							}
166 						}
167 					}
168 					else
169 					{
170 						XMLAssert.assertXMLEqual(contentObj.xmlText(options), items[c].xmlText(options));
171 					}
172 					
173 					break;
174 				}
175 				catch (Throwable e)
176 				{
177 					if( c == items.length-1 )
178 						throw e;
179 				}
180       	}
181 		}
182 		catch (Throwable e)
183 		{
184 			String msg = "XPathContains assertion failed for path [" + path + 
185 					"] : " + e.getClass().getSimpleName() + ":" + e.getMessage();
186 			
187 			throw new AssertionException( new AssertionError(msg) );
188 		}
189       
190       return type + " matches content for [" + path + "]";
191    }
192 
193    public boolean configure()
194    {
195    	if( configurationDialog == null )
196    		buildConfigurationDialog();
197    	
198    	pathArea.setText( path );
199    	contentArea.setText( expectedContent );
200    	
201    	UISupport.showDialog( configurationDialog );
202    	return configureResult;
203    }
204 
205    protected void buildConfigurationDialog()
206 	{
207       configurationDialog = new JDialog( UISupport.getMainFrame() );
208 		configurationDialog.setTitle("XPath Match configuration" );
209       
210       JPanel contentPanel = new JPanel( new BorderLayout() );
211       contentPanel.add( UISupport.buildDescription( "Specify xpath expression and matching content", 
212       			"declare namespaces with <code>declare namespace &lt;prefix&gt;='&lt;namespace&gt;';</code>", null ), BorderLayout.NORTH );
213       
214       JSplitPane splitPane = UISupport.createVerticalSplit();
215       pathArea = new JUndoableTextArea();
216       pathArea.setToolTipText( "Specifies the XPath expression to select from the message for validation" );
217 		splitPane.setTopComponent( UISupport.addTitledBorder( new JScrollPane( pathArea ), "XPath Expression" ));
218       
219       contentArea = new JUndoableTextArea();
220       contentArea.setToolTipText( "Specifies the expected result of the XPath expression" );
221 		splitPane.setBottomComponent( UISupport.addTitledBorder(new JScrollPane( contentArea ), "Expected Result" ));
222 		splitPane.setDividerLocation( 100 );
223       splitPane.setBorder(BorderFactory.createEmptyBorder( 0, 1, 0, 1 ));
224 		
225       contentPanel.add( splitPane, BorderLayout.CENTER );
226       
227       ButtonBarBuilder builder = new ButtonBarBuilder();
228       
229 		builder.addFixed( UISupport.createToolbarButton( new ShowOnlineHelpAction( HelpUrls.XPATHASSERTIONEDITOR_HELP_URL )));
230       builder.addGlue();
231 
232       ActionList actionList = getEditorActions();
233       ActionSupport.addActions( actionList, builder );
234       builder.setBorder( BorderFactory.createEmptyBorder( 1, 5, 5, 5 ));
235       
236       contentPanel.add( builder.getPanel(), BorderLayout.SOUTH );
237       
238       configurationDialog.setContentPane( contentPanel );
239       configurationDialog.setSize(500, 400);
240       
241       configurationDialog.setModal( true );
242 	}
243 
244 	protected ActionList getEditorActions()
245 	{
246 		DefaultActionList actionList = new DefaultActionList();
247 		actionList.addAction( new SelectFromCurrentAction() );
248 		actionList.addAction( new TestPathAction() );
249 		actionList.addAction( new DeclareNamespacesFromCurrentAction() );
250 		actionList.addAction( new OkAction() );
251 		actionList.addAction( new CancelAction() );
252 		return actionList;
253 	}
254 
255 	public XmlObject createConfiguration()
256    {
257 		XmlObjectConfigurationBuilder builder = new XmlObjectConfigurationBuilder();
258 		builder.add( "path", path );
259 		builder.add( "content", expectedContent );
260       return builder.finish();
261    }
262 
263    public boolean isConfigurable()
264    {
265       return true;
266    }
267    
268    public void selectFromCurrent()
269 	{
270 		try
271 		{
272 			XmlOptions options = new XmlOptions();
273 			options.setSavePrettyPrint();
274 	   	options.setSaveOuter();
275 	   	options.setSaveAggressiveNamespaces();
276 			
277 			XmlObject xml = XmlObject.Factory.parse(getAssertable().getAssertableContent());
278 			
279 			String txt = pathArea == null || !pathArea.isVisible() ? getPath() : pathArea.getSelectedText();
280 			if( txt == null ) txt = pathArea == null ? "" : pathArea.getText();
281 			
282 			WsdlTestRunContext context = new WsdlTestRunContext( getAssertable().getTestStep() );
283 			
284 			XmlObject[] items = xml.selectPath( PropertyExpansionRequestFilter.expandProperties( context, txt.trim())); 
285 			
286 			if( contentArea != null && contentArea.isVisible() )
287 				contentArea.setText("");
288 			
289 			if( items.length == 0 )
290 			{
291 				UISupport.showErrorMessage( "No match in current response" );
292 			}
293 			else if( items.length > 1 )
294 			{
295 				UISupport.showErrorMessage( "More than one match in current response" );
296 			}
297 			else
298 			{
299 				String stringValue = null;
300 				
301 				if( items[0] instanceof XmlAnySimpleType )
302 				{
303 					stringValue = ((XmlAnySimpleType)items[0]).getStringValue();
304 				}
305 				else
306 				{
307 					Node domNode = items[0].getDomNode();
308 					if (domNode.getNodeType() == Node.ELEMENT_NODE)
309 					{
310 						stringValue = items[0].xmlText( options );
311 					}
312 					else
313 					{
314 						stringValue = domNode.getNodeValue();
315 					}
316 				}
317 				
318 				if( contentArea != null && contentArea.isVisible() )
319 					contentArea.setText( stringValue );
320 				else
321 					setContent(stringValue );
322 			}
323 		}
324 		catch (Throwable e)
325 		{
326 			UISupport.showErrorMessage( e.toString() );
327 			e.printStackTrace();
328 		}
329 	}
330 
331 	public class OkAction extends AbstractAction
332 	{
333 		public OkAction()
334 		{
335 			super( "Save" );
336 		}
337 
338 		public void actionPerformed(ActionEvent arg0)
339 		{
340 			setPath( pathArea.getText().trim() );
341 			setContent( contentArea.getText() );
342 			setConfiguration( createConfiguration() );
343 			configureResult = true;
344 			configurationDialog.setVisible( false );
345 		}
346 	}
347 
348    public class CancelAction extends AbstractAction
349    {
350    	public CancelAction()
351    	{
352    		super( "Cancel" );
353    	}
354    	
355 		public void actionPerformed(ActionEvent arg0)
356 		{
357 			configureResult = false;
358 			configurationDialog.setVisible( false );
359 		}
360    }
361    
362    public class DeclareNamespacesFromCurrentAction extends AbstractAction
363    {
364    	public DeclareNamespacesFromCurrentAction()
365    	{
366    		super( "Declare" );
367    		putValue( Action.SHORT_DESCRIPTION, "Add namespace declaration from current message to XPath expression");
368    	}
369    	
370 		public void actionPerformed(ActionEvent arg0)
371 		{
372 			try
373 			{
374 				String content = getAssertable().getAssertableContent();
375 				if( content != null && content.trim().length() > 0 )
376 				{
377 					pathArea.setText( XmlUtils.declareXPathNamespaces( content ) + pathArea.getText() );
378 				}
379 				else if( UISupport.confirm( "Declare namespaces from schema instead?", "Missing Response" ))
380 				{
381 					pathArea.setText( XmlUtils.declareXPathNamespaces( getAssertable().getInterface() ) + pathArea.getText() );
382 				}
383 			}
384 			catch (Exception e)
385 			{
386 				log.error( e.getMessage() );
387 			}
388 		}
389    }
390    
391    public class TestPathAction extends AbstractAction
392 	{
393    	public TestPathAction()
394    	{
395    		super( "Test" );
396    		putValue( Action.SHORT_DESCRIPTION, "Tests the XPath expression for the current message against the Expected Content field");
397    	}
398    	
399 		public void actionPerformed(ActionEvent arg0)
400 		{
401 			String oldPath = getPath();
402 			String oldContent = getExpectedContent();
403 			
404 			setPath( pathArea.getText().trim() );
405 			setContent( contentArea.getText() );
406 			
407 			try
408 			{
409 				String msg = assertContent( getAssertable().getAssertableContent(), 
410 							new WsdlSubmitContext( getAssertable().getTestStep() ), "Response"  );
411 				UISupport.showInfoMessage( msg, "Success" );
412 			}
413 			catch (AssertionException e)
414 			{
415 				UISupport.showErrorMessage( e.getMessage() );
416 			}
417 
418 			setPath( oldPath );
419 			setContent( oldContent );
420 		}
421 	}
422    
423    public class SelectFromCurrentAction extends AbstractAction
424 	{
425 		public SelectFromCurrentAction()
426       {
427       	super( "Select from current" );
428    		putValue( Action.SHORT_DESCRIPTION, "Selects the XPath expression from the current message into the Expected Content field");
429       }
430    	
431 		public void actionPerformed(ActionEvent arg0)
432 		{
433 			selectFromCurrent();			
434 		}
435 	}
436 
437 	@Override
438 	protected String internalAssertRequest( WsdlMessageExchange messageExchange, SubmitContext context ) throws AssertionException
439 	{
440 		if( !messageExchange.hasRequest( true ) )
441 			return "Missing Request";
442 		else
443 			return assertContent( messageExchange.getRequestContent(), context, "Request" );
444 	}
445 
446 	public JTextArea getContentArea()
447 	{
448 		return contentArea;
449 	}
450 
451 	public JTextArea getPathArea()
452 	{
453 		return pathArea;
454 	}
455 }