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 }