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