/*
* soapUI, copyright (C) 2004-2011 eviware.com
*
* soapUI is free software; you can redistribute it and/or modify it under the
* terms of version 2.1 of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* soapUI is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details at gnu.org.
*/
package com.eviware.soapui.impl.wsdl.teststeps.assertions.basic;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlAnySimpleType;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.custommonkey.xmlunit.Diff;
import org.custommonkey.xmlunit.Difference;
import org.custommonkey.xmlunit.DifferenceEngine;
import org.custommonkey.xmlunit.DifferenceListener;
import org.custommonkey.xmlunit.XMLAssert;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import com.eviware.soapui.SoapUI;
import com.eviware.soapui.config.TestAssertionConfig;
import com.eviware.soapui.impl.support.actions.ShowOnlineHelpAction;
import com.eviware.soapui.impl.wsdl.WsdlInterface;
import com.eviware.soapui.impl.wsdl.support.HelpUrls;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestRunContext;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlMessageAssertion;
import com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequestStep;
import com.eviware.soapui.impl.wsdl.teststeps.assertions.AbstractTestAssertionFactory;
import com.eviware.soapui.model.TestModelItem;
import com.eviware.soapui.model.iface.MessageExchange;
import com.eviware.soapui.model.iface.SubmitContext;
import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansion;
import com.eviware.soapui.model.propertyexpansion.PropertyExpansionUtils;
import com.eviware.soapui.model.support.XPathReference;
import com.eviware.soapui.model.support.XPathReferenceContainer;
import com.eviware.soapui.model.support.XPathReferenceImpl;
import com.eviware.soapui.model.testsuite.Assertable;
import com.eviware.soapui.model.testsuite.AssertionError;
import com.eviware.soapui.model.testsuite.AssertionException;
import com.eviware.soapui.model.testsuite.RequestAssertion;
import com.eviware.soapui.model.testsuite.ResponseAssertion;
import com.eviware.soapui.model.testsuite.TestProperty;
import com.eviware.soapui.support.StringUtils;
import com.eviware.soapui.support.UISupport;
import com.eviware.soapui.support.components.JUndoableTextArea;
import com.eviware.soapui.support.components.JXToolBar;
import com.eviware.soapui.support.xml.XmlObjectConfigurationBuilder;
import com.eviware.soapui.support.xml.XmlObjectConfigurationReader;
import com.eviware.soapui.support.xml.XmlUtils;
import com.jgoodies.forms.builder.ButtonBarBuilder;
/**
* Assertion that matches a specified XQuery expression and its expected result
* against the associated WsdlTestRequests response message
*
* @author Ole.Matzura
*/
public class XQueryContainsAssertion extends WsdlMessageAssertion implements RequestAssertion, ResponseAssertion,
XPathReferenceContainer
{
private final static Logger log = Logger.getLogger( XQueryContainsAssertion.class );
private String expectedContent;
private String path;
private JDialog configurationDialog;
private JTextArea pathArea;
private JTextArea contentArea;
private boolean configureResult;
private boolean allowWildcards;
public static final String ID = "XQuery Match";
public static final String LABEL = "XQuery Match";
private JCheckBox allowWildcardsCheckBox;
public XQueryContainsAssertion( TestAssertionConfig assertionConfig, Assertable assertable )
{
super( assertionConfig, assertable, true, true, true, true );
XmlObjectConfigurationReader reader = new XmlObjectConfigurationReader( getConfiguration() );
path = reader.readString( "path", null );
expectedContent = reader.readString( "content", null );
allowWildcards = reader.readBoolean( "allowWildcards", false );
}
public String getExpectedContent()
{
return expectedContent;
}
public void setExpectedContent( String expectedContent )
{
this.expectedContent = expectedContent;
setConfiguration( createConfiguration() );
}
/**
* @deprecated
*/
public void setContent( String content )
{
setExpectedContent( content );
}
public String getPath()
{
return path;
}
public void setPath( String path )
{
this.path = path;
setConfiguration( createConfiguration() );
}
public boolean isAllowWildcards()
{
return allowWildcards;
}
public void setAllowWildcards( boolean allowWildcards )
{
this.allowWildcards = allowWildcards;
}
protected String internalAssertResponse( MessageExchange messageExchange, SubmitContext context )
throws AssertionException
{
if( !messageExchange.hasResponse() )
return "Missing Response";
else
return assertContent( messageExchange.getResponseContentAsXml(), context, "Response" );
}
public String assertContent( String response, SubmitContext context, String type ) throws AssertionException
{
try
{
if( path == null )
return "Missing path for XQuery Assertion";
if( expectedContent == null )
return "Missing content for XQuery Assertion";
// XmlObject xml = XmlObject.Factory.parse( response );
XmlObject xml = XmlUtils.createXmlObject( response );
String expandedPath = PropertyExpander.expandProperties( context, path );
XmlObject[] items = xml.execQuery( expandedPath );
XmlObject contentObj = null;
String expandedContent = PropertyExpander.expandProperties( context, expectedContent );
try
{
// contentObj = XmlObject.Factory.parse( expandedContent );
contentObj = XmlUtils.createXmlObject( expandedContent );
}
catch( Exception e )
{
// this is ok.. it just means that the content to match is not xml
// but
// (hopefully) just a string
}
if( items.length == 0 )
throw new Exception( "Missing content for xquery [" + path + "] in " + type );
XmlOptions options = new XmlOptions();
options.setSavePrettyPrint();
options.setSaveOuter();
for( int c = 0; c < items.length; c++ )
{
try
{
if( contentObj == null )
{
if( items[c] instanceof XmlAnySimpleType )
{
String value = ( ( XmlAnySimpleType )items[c] ).getStringValue();
String expandedValue = PropertyExpander.expandProperties( context, value );
XMLAssert.assertEquals( expandedContent, expandedValue );
}
else
{
Node domNode = items[c].getDomNode();
if( domNode.getNodeType() == Node.ELEMENT_NODE )
{
String expandedValue = PropertyExpander.expandProperties( context,
XmlUtils.getElementText( ( Element )domNode ) );
XMLAssert.assertEquals( expandedContent, expandedValue );
}
else
{
String expandedValue = PropertyExpander.expandProperties( context, items[c].xmlText( options ) );
XMLAssert.assertEquals( expandedContent, expandedValue );
}
}
}
else
{
compareValues( contentObj.xmlText( options ), items[c].xmlText( options ) );
}
break;
}
catch( Throwable e )
{
if( c == items.length - 1 )
throw e;
}
}
}
catch( Throwable e )
{
String msg = "XQuery Match Assertion failed for path [" + path + "] : " + e.getClass().getSimpleName() + ":"
+ e.getMessage();
throw new AssertionException( new AssertionError( msg ) );
}
return type + " matches content for [" + path + "]";
}
private void compareValues( String expandedContent, String expandedValue ) throws Exception
{
Diff diff = new Diff( expandedContent, expandedValue );
diff.overrideDifferenceListener( new DifferenceListener()
{
public int differenceFound( Difference diff )
{
if( allowWildcards
&& ( diff.getId() == DifferenceEngine.TEXT_VALUE.getId() || diff.getId() == DifferenceEngine.ATTR_VALUE
.getId() ) )
{
if( diff.getControlNodeDetail().getValue().equals( "*" ) )
return Diff.RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL;
}
return Diff.RETURN_ACCEPT_DIFFERENCE;
}
public void skippedComparison( Node arg0, Node arg1 )
{
}
} );
if( !diff.identical() )
throw new Exception( diff.toString() );
}
public boolean configure()
{
if( configurationDialog == null )
buildConfigurationDialog();
pathArea.setText( path );
contentArea.setText( expectedContent );
allowWildcardsCheckBox.setSelected( allowWildcards );
UISupport.showDialog( configurationDialog );
return configureResult;
}
protected void buildConfigurationDialog()
{
configurationDialog = new JDialog( UISupport.getMainFrame() );
configurationDialog.setTitle( "XQuery Match configuration" );
configurationDialog.addWindowListener( new WindowAdapter()
{
public void windowOpened( WindowEvent event )
{
SwingUtilities.invokeLater( new Runnable()
{
public void run()
{
pathArea.requestFocusInWindow();
}
} );
}
} );
JPanel contentPanel = new JPanel( new BorderLayout() );
contentPanel.add( UISupport.buildDescription( "Specify XQuery expression and expected result",
"declare namespaces with <code>declare namespace <prefix>='<namespace>';</code>", null ),
BorderLayout.NORTH );
JSplitPane splitPane = UISupport.createVerticalSplit();
JPanel pathPanel = new JPanel( new BorderLayout() );
JXToolBar pathToolbar = UISupport.createToolbar();
addPathEditorActions( pathToolbar );
pathArea = new JUndoableTextArea();
pathArea.setToolTipText( "Specifies the XQuery expression to select from the message for validation" );
pathPanel.add( pathToolbar, BorderLayout.NORTH );
pathPanel.add( new JScrollPane( pathArea ), BorderLayout.CENTER );
splitPane.setTopComponent( UISupport.addTitledBorder( pathPanel, "XQuery Expression" ) );
JPanel matchPanel = new JPanel( new BorderLayout() );
JXToolBar contentToolbar = UISupport.createToolbar();
addMatchEditorActions( contentToolbar );
contentArea = new JUndoableTextArea();
contentArea.setToolTipText( "Specifies the expected result of the XQuery expression" );
matchPanel.add( contentToolbar, BorderLayout.NORTH );
matchPanel.add( new JScrollPane( contentArea ), BorderLayout.CENTER );
splitPane.setBottomComponent( UISupport.addTitledBorder( matchPanel, "Expected Result" ) );
splitPane.setDividerLocation( 150 );
splitPane.setBorder( BorderFactory.createEmptyBorder( 0, 1, 0, 1 ) );
contentPanel.add( splitPane, BorderLayout.CENTER );
ButtonBarBuilder builder = new ButtonBarBuilder();
ShowOnlineHelpAction showOnlineHelpAction = new ShowOnlineHelpAction( HelpUrls.XQUERYASSERTIONEDITOR_HELP_URL );
builder.addFixed( UISupport.createToolbarButton( showOnlineHelpAction ) );
builder.addGlue();
JButton okButton = new JButton( new OkAction() );
builder.addFixed( okButton );
builder.addRelatedGap();
builder.addFixed( new JButton( new CancelAction() ) );
builder.setBorder( BorderFactory.createEmptyBorder( 1, 5, 5, 5 ) );
contentPanel.add( builder.getPanel(), BorderLayout.SOUTH );
configurationDialog.setContentPane( contentPanel );
configurationDialog.setSize( 600, 500 );
configurationDialog.setModal( true );
UISupport.initDialogActions( configurationDialog, showOnlineHelpAction, okButton );
}
protected void addPathEditorActions( JXToolBar toolbar )
{
toolbar.addFixed( new JButton( new DeclareNamespacesFromCurrentAction() ) );
}
protected void addMatchEditorActions( JXToolBar toolbar )
{
toolbar.addFixed( new JButton( new SelectFromCurrentAction() ) );
toolbar.addRelatedGap();
toolbar.addFixed( new JButton( new TestPathAction() ) );
allowWildcardsCheckBox = new JCheckBox( "Allow Wildcards" );
Dimension dim = new Dimension( 100, 20 );
allowWildcardsCheckBox.setSize( dim );
allowWildcardsCheckBox.setPreferredSize( dim );
allowWildcardsCheckBox.setOpaque( false );
toolbar.addRelatedGap();
toolbar.addFixed( allowWildcardsCheckBox );
}
public XmlObject createConfiguration()
{
XmlObjectConfigurationBuilder builder = new XmlObjectConfigurationBuilder();
builder.add( "path", path );
builder.add( "content", expectedContent );
builder.add( "allowWildcards", allowWildcards );
return builder.finish();
}
public void selectFromCurrent()
{
// XmlCursor cursor = null;
try
{
XmlOptions options = new XmlOptions();
options.setSavePrettyPrint();
options.setSaveAggressiveNamespaces();
String assertableContent = getAssertable().getAssertableContent();
if( assertableContent == null || assertableContent.trim().length() == 0 )
{
UISupport.showErrorMessage( "Missing content to select from" );
return;
}
// XmlObject xml = XmlObject.Factory.parse( assertableContent );
XmlObject xml = XmlUtils.createXmlObject( assertableContent );
String txt = pathArea == null || !pathArea.isVisible() ? getPath() : pathArea.getSelectedText();
if( txt == null )
txt = pathArea == null ? "" : pathArea.getText();
WsdlTestRunContext context = new WsdlTestRunContext( getAssertable().getTestStep() );
String expandedPath = PropertyExpander.expandProperties( context, txt.trim() );
if( contentArea != null && contentArea.isVisible() )
contentArea.setText( "" );
XmlObject[] paths = xml.execQuery( expandedPath );
if( paths.length == 0 )
{
UISupport.showErrorMessage( "No match in current response" );
}
else if( paths.length > 1 )
{
UISupport.showErrorMessage( "More than one match in current response" );
}
else
{
Node domNode = paths[0].getDomNode();
String stringValue = null;
if( domNode.getNodeType() == Node.ATTRIBUTE_NODE || domNode.getNodeType() == Node.TEXT_NODE )
{
stringValue = domNode.getNodeValue();
}
else
{
if( domNode.getNodeType() == Node.ELEMENT_NODE )
{
Element elm = ( Element )domNode;
if( elm.getChildNodes().getLength() == 1 && elm.getAttributes().getLength() == 0 )
stringValue = XmlUtils.getElementText( elm );
else
stringValue = paths[0].xmlText( options );
}
else
{
stringValue = paths[0].xmlText( options );
}
}
if( contentArea != null && contentArea.isVisible() )
contentArea.setText( stringValue );
else
setContent( stringValue );
}
}
catch( Throwable e )
{
UISupport.showErrorMessage( e.toString() );
SoapUI.logError( e );
}
finally
{
// if( cursor != null )
// cursor.dispose();
}
}
public class OkAction extends AbstractAction
{
public OkAction()
{
super( "Save" );
}
public void actionPerformed( ActionEvent arg0 )
{
setPath( pathArea.getText().trim() );
setContent( contentArea.getText() );
setAllowWildcards( allowWildcardsCheckBox.isSelected() );
setConfiguration( createConfiguration() );
configureResult = true;
configurationDialog.setVisible( false );
}
}
public class CancelAction extends AbstractAction
{
public CancelAction()
{
super( "Cancel" );
}
public void actionPerformed( ActionEvent arg0 )
{
configureResult = false;
configurationDialog.setVisible( false );
}
}
public class DeclareNamespacesFromCurrentAction extends AbstractAction
{
public DeclareNamespacesFromCurrentAction()
{
super( "Declare" );
putValue( Action.SHORT_DESCRIPTION, "Add namespace declaration from current message to XQuery expression" );
}
public void actionPerformed( ActionEvent arg0 )
{
try
{
String content = getAssertable().getAssertableContent();
if( content != null && content.trim().length() > 0 )
{
pathArea.setText( XmlUtils.declareXPathNamespaces( content ) + pathArea.getText() );
}
else if( UISupport.confirm( "Declare namespaces from schema instead?", "Missing Response" ) )
{
pathArea.setText( XmlUtils.declareXPathNamespaces( ( WsdlInterface )getAssertable().getInterface() )
+ pathArea.getText() );
}
}
catch( Exception e )
{
log.error( e.getMessage() );
}
}
}
public class TestPathAction extends AbstractAction
{
public TestPathAction()
{
super( "Test" );
putValue( Action.SHORT_DESCRIPTION,
"Tests the XQuery expression for the current message against the Expected Content field" );
}
public void actionPerformed( ActionEvent arg0 )
{
String oldPath = getPath();
String oldContent = getExpectedContent();
boolean oldAllowWildcards = isAllowWildcards();
setPath( pathArea.getText().trim() );
setContent( contentArea.getText() );
setAllowWildcards( allowWildcardsCheckBox.isSelected() );
try
{
String msg = assertContent( getAssertable().getAssertableContent(), new WsdlTestRunContext( getAssertable()
.getTestStep() ), "Response" );
UISupport.showInfoMessage( msg, "Success" );
}
catch( AssertionException e )
{
UISupport.showErrorMessage( e.getMessage() );
}
setPath( oldPath );
setContent( oldContent );
setAllowWildcards( oldAllowWildcards );
}
}
public class SelectFromCurrentAction extends AbstractAction
{
public SelectFromCurrentAction()
{
super( "Select from current" );
putValue( Action.SHORT_DESCRIPTION,
"Selects the XQuery expression from the current message into the Expected Content field" );
}
public void actionPerformed( ActionEvent arg0 )
{
selectFromCurrent();
}
}
@Override
protected String internalAssertRequest( MessageExchange messageExchange, SubmitContext context )
throws AssertionException
{
if( !messageExchange.hasRequest( true ) )
return "Missing Request";
else
return assertContent( messageExchange.getRequestContent(), context, "Request" );
}
public JTextArea getContentArea()
{
return contentArea;
}
public JTextArea getPathArea()
{
return pathArea;
}
public PropertyExpansion[] getPropertyExpansions()
{
List<PropertyExpansion> result = new ArrayList<PropertyExpansion>();
result.addAll( PropertyExpansionUtils.extractPropertyExpansions( getAssertable().getModelItem(), this,
"expectedContent" ) );
result.addAll( PropertyExpansionUtils.extractPropertyExpansions( getAssertable().getModelItem(), this, "path" ) );
return result.toArray( new PropertyExpansion[result.size()] );
}
public XPathReference[] getXPathReferences()
{
List<XPathReference> result = new ArrayList<XPathReference>();
if( StringUtils.hasContent( getPath() ) )
{
TestModelItem testStep = getAssertable().getTestStep();
TestProperty property = testStep instanceof WsdlTestRequestStep ? testStep.getProperty( "Response" )
: testStep.getProperty( "Request" );
result.add( new XPathReferenceImpl( "XQuery for " + getName() + " XQueryContainsAssertion in "
+ testStep.getName(), property, this, "path" ) );
}
return result.toArray( new XPathReference[result.size()] );
}
public static class Factory extends AbstractTestAssertionFactory
{
public Factory()
{
super( XQueryContainsAssertion.ID, XQueryContainsAssertion.LABEL, XQueryContainsAssertion.class );
}
@Override
public Class<? extends WsdlMessageAssertion> getAssertionClassType()
{
return XQueryContainsAssertion.class;
}
}
}