View Javadoc

1   /*
2    *  soapUI, copyright (C) 2004-2008 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.mock;
14  
15  import com.eviware.soapui.SoapUI;
16  import com.eviware.soapui.config.DispatchStyleConfig;
17  import com.eviware.soapui.config.DispatchStyleConfig.Enum;
18  import com.eviware.soapui.config.MockOperationConfig;
19  import com.eviware.soapui.config.MockResponseConfig;
20  import com.eviware.soapui.impl.wsdl.AbstractWsdlModelItem;
21  import com.eviware.soapui.impl.wsdl.WsdlInterface;
22  import com.eviware.soapui.impl.wsdl.WsdlOperation;
23  import com.eviware.soapui.impl.wsdl.support.CompressedStringSupport;
24  import com.eviware.soapui.impl.wsdl.support.wsdl.WsdlUtils;
25  import com.eviware.soapui.model.ModelItem;
26  import com.eviware.soapui.model.iface.Interface;
27  import com.eviware.soapui.model.iface.Operation;
28  import com.eviware.soapui.model.mock.MockOperation;
29  import com.eviware.soapui.model.mock.MockResponse;
30  import com.eviware.soapui.model.mock.MockRunContext;
31  import com.eviware.soapui.model.support.InterfaceListenerAdapter;
32  import com.eviware.soapui.model.support.ProjectListenerAdapter;
33  import com.eviware.soapui.settings.WsdlSettings;
34  import com.eviware.soapui.support.StringUtils;
35  import com.eviware.soapui.support.UISupport;
36  import com.eviware.soapui.support.scripting.ScriptEnginePool;
37  import com.eviware.soapui.support.scripting.SoapUIScriptEngine;
38  import com.eviware.soapui.support.xml.XmlUtils;
39  import org.apache.log4j.Logger;
40  import org.apache.xmlbeans.XmlCursor;
41  import org.apache.xmlbeans.XmlException;
42  import org.apache.xmlbeans.XmlObject;
43  
44  import javax.swing.*;
45  import java.beans.PropertyChangeEvent;
46  import java.beans.PropertyChangeListener;
47  import java.util.ArrayList;
48  import java.util.List;
49  import java.util.Map;
50  import java.util.concurrent.ConcurrentHashMap;
51  import java.util.concurrent.atomic.AtomicLong;
52  
53  /***
54   * A WsdlMockOperation in a WsdlMockService
55   * 
56   * @author ole.matzura
57   */
58  
59  public class WsdlMockOperation extends AbstractWsdlModelItem<MockOperationConfig> implements MockOperation, PropertyChangeListener
60  {
61     private final static Logger log = Logger.getLogger(WsdlMockOperation.class);
62     
63  	public final static String DISPATCH_STYLE_PROPERTY = WsdlMockOperation.class.getName() + "@dispatchstyle";
64  	public final static String DEFAULT_RESPONSE_PROPERTY = WsdlMockOperation.class.getName() + "@defaultresponse";
65  	public final static String DISPATCH_PATH_PROPERTY = WsdlMockOperation.class.getName() + "@dispatchpath";
66  	public final static String OPERATION_PROPERTY = WsdlMockOperation.class.getName() + "@operation";
67  	
68  	private WsdlOperation operation;
69  	private List<WsdlMockResponse> responses = new ArrayList<WsdlMockResponse>();
70  	private int currentDispatchIndex;
71  	private ScriptEnginePool scriptEnginePool;
72  	private InternalInterfaceListener interfaceListener = new InternalInterfaceListener();
73  	private InternalProjectListener projectListener = new InternalProjectListener();
74  	private ImageIcon oneWayIcon;
75  	private ImageIcon notificationIcon;
76  	private ImageIcon solicitResponseIcon;
77  	
78     private AtomicLong counter;
79     private Map<QueryValuePair, WsdlMockResponse> responseMap;
80  
81  	public WsdlMockOperation(WsdlMockService mockService, MockOperationConfig config)
82     {
83     	super( config, mockService, "/mockOperation.gif" );
84     	
85     	Interface iface = mockService.getProject().getInterfaceByName( config.getInterface() );
86     	if( iface == null )
87     	{
88     		SoapUI.log.warn( "Missing interface [" + config.getInterface() + 
89     					"] for MockOperation in project" );
90     	}
91     	else
92     	{
93     		operation = ( WsdlOperation ) iface.getOperationByName( config.getOperation() );
94     	}
95     	
96     	List<MockResponseConfig> responseConfigs = config.getResponseList();
97     	for( MockResponseConfig responseConfig : responseConfigs )
98     	{
99     		WsdlMockResponse wsdlMockResponse = new WsdlMockResponse( this, responseConfig );
100    		wsdlMockResponse.addPropertyChangeListener( this );
101 			responses.add( wsdlMockResponse );
102    	}
103    	
104    	initData( config );
105    	
106       counter = new AtomicLong();
107       responseMap = new ConcurrentHashMap<QueryValuePair, WsdlMockResponse>();
108    }
109 
110 	private void initData( MockOperationConfig config )
111 	{
112 		if( !config.isSetName() )
113    		config.setName( operation == null ? "<missing operation>" : operation.getName() );
114    	
115    	if( !config.isSetDispatchStyle())
116    		config.setDispatchStyle( DispatchStyleConfig.SEQUENCE );
117    	
118    	if( !config.isSetDefaultResponse() && responses.size() > 0 )
119    		setDefaultResponse( responses.get( 0 ).getName() );
120    	
121    	scriptEnginePool = new ScriptEnginePool( this );
122    	scriptEnginePool.setScript( getDispatchPath() );
123    	
124    	if( operation != null )
125    	{
126    		operation.getInterface().getProject().addProjectListener( projectListener );
127    		operation.getInterface().addInterfaceListener( interfaceListener );
128    		operation.getInterface().addPropertyChangeListener( WsdlInterface.NAME_PROPERTY, this );
129    	}
130    	
131    	oneWayIcon = UISupport.createImageIcon( "/onewaymockoperation.gif" );
132 		notificationIcon = UISupport.createImageIcon("/mocknotificationoperation.gif");
133 		solicitResponseIcon = UISupport.createImageIcon("/mocksolicitresponseoperation.gif");
134 	}
135 	
136 	public WsdlMockOperation( WsdlMockService mockService, MockOperationConfig config, WsdlOperation operation )
137 	{
138 		super( config, mockService, "/mockOperation.gif" );
139 		this.operation = operation;
140 		
141 		config.setInterface( operation.getInterface().getName() );
142 		config.setOperation( operation.getName() );
143 		
144 		initData( config );
145 		interfaceListener = new InternalInterfaceListener();
146 		
147       counter = new AtomicLong();
148       responseMap = new ConcurrentHashMap<QueryValuePair, WsdlMockResponse>();
149 	}
150 
151 	@Override
152    public ImageIcon getIcon()
153 	{
154 		if (operation != null)
155 		{
156 			if (isOneWay())
157 			{
158 			   return oneWayIcon;
159 			}
160 			else if (isNotification())
161 			{
162 				return notificationIcon;
163 			}
164 			else if (isSolicitResponse())
165 			{
166 				return solicitResponseIcon;
167 			}
168 		}
169 
170 	   return super.getIcon();
171 	}
172 	
173 	public WsdlMockService getMockService()
174 	{
175 		return ( WsdlMockService ) getParent();
176 	}
177 
178 	public WsdlMockResponse getMockResponseAt( int index )
179 	{
180 		return responses.get( index );
181 	}
182 
183 	public WsdlOperation getOperation()
184 	{
185 		return operation;
186 	}
187 
188 	public WsdlMockResponse getMockResponseByName( String name )
189 	{
190 		return ( WsdlMockResponse ) getWsdlModelItemByName( responses, name );
191 	}
192 
193 	public int getMockResponseCount()
194 	{
195 		return responses.size();
196 	}
197 
198 	public WsdlMockResponse addNewMockResponse( MockResponseConfig responseConfig )
199 	{
200 		WsdlMockResponse mockResponse = new WsdlMockResponse( this, responseConfig );
201 		
202 		responses.add( mockResponse );
203 		if( responses.size() == 1 )
204 			setDefaultResponse( mockResponse.getName() );
205 		
206 		(getMockService()).fireMockResponseAdded( mockResponse );
207 		
208 		//add ws-a action
209 		WsdlUtils.setDefaultWsaAction( mockResponse.getWsaConfig(), true );
210 		
211 //   	String [] attrs = WsdlUtils.getExentsibilityAttributes(getOperation().getBindingOperation().getOperation().getOutput(), 
212 //   			new QName("http://www.w3.org/2006/05/addressing/wsdl", "Action") );
213 //   	if (attrs.length > 0)
214 //		{
215 //   		mockResponse.getWsaConfig().setAction(attrs[0]);
216 //		} else {
217 //			WsdlUtils.createDefaultWsaAction(mockResponse);
218 //		}
219 		
220 		return mockResponse;
221 	}
222 	
223 	public WsdlMockResponse addNewMockResponse( String name, boolean createResponse )
224 	{
225 		MockResponseConfig responseConfig = getConfig().addNewResponse();
226 		responseConfig.setName( name );
227 		responseConfig.addNewResponseContent();
228 		
229 		if( createResponse && getOperation() != null && getOperation().isBidirectional() )
230 		{
231 			boolean createOptional = SoapUI.getSettings().getBoolean( WsdlSettings.XML_GENERATION_ALWAYS_INCLUDE_OPTIONAL_ELEMENTS );
232 			CompressedStringSupport.setString( responseConfig.getResponseContent(), getOperation().createResponse( createOptional ));
233 		}
234 		
235 		return addNewMockResponse( responseConfig );
236 	}
237 	
238    public WsdlMockResponse addNewMockResponse(String requestQuery, String matchingValue)
239    {
240       // Create a unique name.
241       String name = String.valueOf(counter.addAndGet(1));
242       
243       // Create the mock response and store it for later retrieval.
244       WsdlMockResponse mockResponse = addNewMockResponse(name, false);
245       responseMap.put(new QueryValuePair(requestQuery, matchingValue), mockResponse);
246       return mockResponse;
247    }
248 
249 	public void removeMockResponse( WsdlMockResponse mockResponse )
250    {
251       int ix = responses.indexOf( mockResponse );
252       responses.remove( ix );
253       mockResponse.removePropertyChangeListener( this );
254       
255       try
256       {
257       	(getMockService()).fireMockResponseRemoved( mockResponse );
258       }
259       finally
260       {
261 	      mockResponse.release();
262 	      getConfig().removeResponse( ix );
263       }
264    }
265 
266 	public WsdlMockResult dispatchRequest( WsdlMockRequest request ) throws DispatchException
267 	{
268 		try
269 		{
270 			request.setOperation( getOperation() );
271 			WsdlMockResult result = new WsdlMockResult( request );
272 			
273 			if( getMockResponseCount() == 0 )
274 				throw new DispatchException( "Missing MockResponse(s) in MockOperation [" + getName() + "]" );
275 			
276 			Enum dispatchStyle = getDispatchStyle();
277          if( dispatchStyle == DispatchStyleConfig.XPATH )
278 			{
279 				dispatchRequestXPath(request, result);
280 			}
281 			else if( dispatchStyle == DispatchStyleConfig.SCRIPT )
282 			{
283 			   dispatchRequestScript(request, result);
284 			}
285          // I added DispatchStyleConfig.QUERY_MATCH to handle async response test step. /Lars
286 			else if( dispatchStyle == DispatchStyleConfig.QUERY_MATCH )
287          {
288 			   dispatchRequestQueryMatch(request, result);
289          }
290 			else 
291 			{
292 			   dispatchRequestSequence(request, result);
293 			}
294 			
295 			return result;
296 		}
297 		catch( Throwable e )
298 		{
299 			throw new DispatchException( e );
300 		}
301 	}
302 	
303    private void dispatchRequestQueryMatch(WsdlMockRequest request, WsdlMockResult result) throws DispatchException
304    {
305       WsdlMockResponse mockResponse = getMatchingMockResponse(request);
306       if (mockResponse != null)
307       {
308          result.setMockResponse( mockResponse );
309          mockResponse.execute( request, result );
310       }
311       else
312       {
313          log.error("Unable to find a response for the request. Dropping it!");
314          
315 // TODO Ericsson: Throw exception if no response was found?
316 //         throw new DispatchException("Unable to find a response for the request.");
317       }
318    }
319 
320    private void dispatchRequestXPath(WsdlMockRequest request, WsdlMockResult result)
321    throws XmlException, DispatchException
322    {
323       XmlObject[] items = evaluateDispatchXPath( request );
324       for( XmlObject item : items )
325       {
326          WsdlMockResponse mockResponse = getMockResponseByName( XmlUtils.getNodeValue( item.getDomNode() ));
327          
328          if( mockResponse == null )
329             mockResponse = getMockResponseByName( getDefaultResponse() );
330          
331          if( mockResponse != null )
332          {
333             result.setMockResponse( mockResponse );
334             mockResponse.execute( request, result );
335             return;
336          }
337       }
338       
339       throw new DispatchException( "Missing matching response message" );
340    }
341    
342    private void dispatchRequestScript(WsdlMockRequest request, WsdlMockResult result)
343    throws DispatchException
344    {
345       Object retVal = evaluateDispatchScript( request );
346       
347       WsdlMockResponse mockResponse = retVal == null ? getMockResponseByName( getDefaultResponse() ) 
348       			: getMockResponseByName( retVal.toString() );
349 
350       if( mockResponse == null )
351          mockResponse = getMockResponseByName( getDefaultResponse() );
352       
353       if( mockResponse != null )
354       {
355       	result.setMockResponse( mockResponse );
356       	mockResponse.execute( request, result );
357       	return;
358       }
359       else
360       {
361       	throw new DispatchException( "Missing matching response message [" + retVal + "]" );
362       }
363    }
364 
365    private void dispatchRequestSequence(WsdlMockRequest request, WsdlMockResult result)
366    throws DispatchException
367    {
368       WsdlMockResponse mockResponse = null;
369       synchronized( this )
370       {
371          if( getDispatchStyle() == DispatchStyleConfig.RANDOM )
372          {
373             currentDispatchIndex = ( int ) ( (Math.random() * getMockResponseCount()) + 0.5F );
374          }
375 
376          if( currentDispatchIndex >= getMockResponseCount() ) 
377             currentDispatchIndex = 0;
378 
379          mockResponse = getMockResponseAt( currentDispatchIndex );
380          result.setMockResponse( mockResponse );
381          
382          currentDispatchIndex++;
383       }
384 
385       mockResponse.execute( request, result );
386    }
387 
388 	@Override
389    public void release()
390 	{
391 		super.release();
392 		
393 		for( WsdlMockResponse response : responses )
394 		{
395 			response.removePropertyChangeListener( this );
396 			response.release();
397 		}
398 		
399 		scriptEnginePool.release();
400 		
401 		if( operation != null )
402 		{
403 			operation.getInterface().getProject().removeProjectListener( projectListener );
404 			operation.getInterface().removeInterfaceListener( interfaceListener );
405 			operation.getInterface().removePropertyChangeListener( WsdlInterface.NAME_PROPERTY, this );
406 		}
407 	}
408 	
409 	public XmlObject[] evaluateDispatchXPath( WsdlMockRequest request ) throws XmlException
410 	{
411 		XmlObject xmlObject = request.getRequestXmlObject();
412 		String path = getDispatchPath();
413 		if( StringUtils.isNullOrEmpty(path))
414 			throw new XmlException( "Missing dispatch XPath expression" );
415 			
416 		XmlObject[] items = xmlObject.selectPath( path );
417 		return items;
418 	}
419 
420 	public Object evaluateDispatchScript( WsdlMockRequest request ) throws DispatchException
421 	{
422 		String dispatchPath = getDispatchPath();
423 		if( dispatchPath == null || dispatchPath.trim().length() == 0 )
424 		{
425 			throw new DispatchException( "Dispatch Script is empty" );
426 		}
427 		
428 		SoapUIScriptEngine scriptEngine = scriptEnginePool.getScriptEngine();
429 		
430 		try
431 		{
432 			WsdlMockService mockService = getMockService();
433 			WsdlMockRunner mockRunner = mockService.getMockRunner();
434 			MockRunContext context = mockRunner == null ? new WsdlMockRunContext( mockService, null ) : mockRunner.getMockContext(); 
435 			
436 			scriptEngine.setVariable( "context", context );
437 			scriptEngine.setVariable( "requestContext", request == null ? null : request.getRequestContext() );
438 			scriptEngine.setVariable( "mockRequest", request);
439 			scriptEngine.setVariable( "mockOperation", this );
440 			scriptEngine.setVariable( "log", SoapUI.ensureGroovyLog() );
441 			
442 			scriptEngine.setScript( dispatchPath );
443 			Object retVal = scriptEngine.run();
444 			return retVal;
445 		}
446 		catch( Throwable e )
447 		{
448 			SoapUI.logError( e );
449 			throw new DispatchException( "Failed to dispatch using script; " + e );
450 		}
451 		finally
452 		{
453 			scriptEnginePool.returnScriptEngine( scriptEngine );
454 		}
455 	}
456 	
457 	public DispatchStyleConfig.Enum getDispatchStyle()
458 	{
459 		return getConfig().isSetDispatchStyle() ? getConfig().getDispatchStyle() : DispatchStyleConfig.SEQUENCE;
460 	}
461 	
462 	public void setDispatchStyle( DispatchStyleConfig.Enum dispatchStyle )
463 	{
464 		Enum old = getDispatchStyle();
465 		getConfig().setDispatchStyle( dispatchStyle );
466 		notifyPropertyChanged( DISPATCH_STYLE_PROPERTY, old, dispatchStyle );
467 	}
468 	
469 	public String getDispatchPath()
470 	{
471 		return getConfig().getDispatchPath();
472 	}
473 	
474 	public void setDispatchPath( String dispatchPath )
475 	{
476 		String old = getDispatchPath();
477 		getConfig().setDispatchPath( dispatchPath );
478 		notifyPropertyChanged( DISPATCH_PATH_PROPERTY, old, dispatchPath );
479 		
480 		scriptEnginePool.setScript( dispatchPath );
481 	}
482 	
483 	public String getWsdlOperationName()
484 	{
485 		return operation == null ? null : operation.getName();
486 	}
487 
488 	public String getDefaultResponse()
489 	{
490 		return getConfig().getDefaultResponse();
491 	}
492 	
493 	public void setDefaultResponse( String defaultResponse )
494 	{
495 		String old = getDefaultResponse();
496 		getConfig().setDefaultResponse( defaultResponse );
497 		notifyPropertyChanged( DEFAULT_RESPONSE_PROPERTY, old, defaultResponse );
498 	}
499 
500 	public List<MockResponse> getMockResponses()
501 	{
502 		return new ArrayList<MockResponse>( responses );
503 	}
504 
505 	public void propertyChange( PropertyChangeEvent arg0 )
506 	{
507 		if( arg0.getPropertyName().equals( WsdlMockResponse.NAME_PROPERTY ))
508 		{
509 			if( arg0.getOldValue().equals( getDefaultResponse() ))
510 				setDefaultResponse( arg0.getNewValue().toString() );
511 		}
512 		else if( arg0.getPropertyName().equals( WsdlInterface.NAME_PROPERTY ))
513 		{
514 			getConfig().setInterface( arg0.getNewValue().toString() );
515 		}
516 	}
517 	
518 	public WsdlMockResult getLastMockResult()
519 	{
520 		WsdlMockResult result = null;
521 		
522 		for( WsdlMockResponse response : responses )
523 		{
524 			WsdlMockResult mockResult = response.getMockResult();
525 			if( mockResult != null )
526 			{
527 				if( result == null || result.getTimestamp() > mockResult.getTimestamp() )
528 					result = mockResult;
529 			}
530 		}
531 		
532 		return result;
533 	}
534 
535 	public void setOperation( WsdlOperation operation )
536 	{
537 		WsdlOperation oldOperation = getOperation();
538 		
539 		if( operation == null )
540 		{
541 			getConfig().unsetInterface();
542 			getConfig().unsetOperation();
543 		}
544 		else
545 		{
546 			getConfig().setInterface( operation.getInterface().getName() );
547 			getConfig().setOperation( operation.getName() );
548 		}
549 		
550 		this.operation = operation;
551 		
552 		notifyPropertyChanged( OPERATION_PROPERTY, oldOperation, operation );
553 	}
554 
555 	@Override
556 	public void beforeSave()
557 	{
558 		for( WsdlMockResponse mockResponse : responses )
559 			mockResponse.beforeSave();
560 	}
561 	
562 	private class InternalInterfaceListener extends InterfaceListenerAdapter
563 	{
564 		@Override
565 		public void operationUpdated( Operation operation )
566 		{
567 			if( operation == WsdlMockOperation.this.operation )
568 				getConfig().setOperation( operation.getName() );
569 		}
570 
571 		@Override
572 		public void operationRemoved( Operation operation )
573 		{
574 			if( operation == WsdlMockOperation.this.operation )
575 				getMockService().removeMockOperation( WsdlMockOperation.this );
576 		}
577 	}
578 
579 	private class InternalProjectListener extends ProjectListenerAdapter
580 	{
581 		@Override
582 		public void interfaceRemoved( Interface iface )
583 		{
584 			if( operation.getInterface() == iface )
585 				getMockService().removeMockOperation( WsdlMockOperation.this );
586 		}
587 
588 		@Override
589 		public void interfaceUpdated(Interface iface)
590 		{
591 			if( operation.getInterface() == iface )
592 				getConfig().setInterface( iface.getName() );
593 		}
594 	}
595 	
596 	public boolean isOneWay()
597 	{
598 		return operation == null ? false : operation.isOneWay();
599 	}
600 
601 	public boolean isNotification()
602 	{
603 		return operation == null ? false : operation.isNotification();
604 	}
605 
606 	public boolean isSolicitResponse()
607 	{
608 		return operation == null ? false : operation.isSolicitResponse();
609 	}
610 
611 	public boolean isUnidirectional()
612 	{
613 	   return operation == null ? false : operation.isUnidirectional();
614 	}
615 
616 	public boolean isBidirectional()
617 	{
618 	   return !isUnidirectional();
619 	}
620 	 
621 	public List<? extends ModelItem> getChildren()
622    {
623       return responses;
624    }
625 
626 	public void onStart()
627 	{
628 		currentDispatchIndex = 0;
629 	}
630 
631    private WsdlMockResponse getMatchingMockResponse(WsdlMockRequest request)
632    throws DispatchException
633    {
634       try
635       {
636          XmlObject xmlObject = request.getRequestXmlObject();
637 
638          for (QueryValuePair pair : responseMap.keySet())
639          {
640             log.debug("Testing request for match: " + pair);
641             
642             XmlObject[] nodes = xmlObject.selectPath(pair.getQuery());
643             if (nodes != null && nodes.length > 0)
644             {
645                // Only look at the first node.
646                log.debug("Comparing selected node " + nodes[0] +
647                      " with the value " + pair.getValue());
648                XmlCursor cursor = nodes[0].newCursor();
649                try
650                {
651                   if (pair.getValue().equals(cursor.getTextValue()))
652                   {
653                      log.debug("Found a request with a matching query: +" +
654                            request.getRequestContent());
655                      
656                      return responseMap.get(pair);
657                   }
658                }
659                finally
660                {
661                   cursor.dispose();
662                }
663             }
664          }
665          
666          return null;
667 //       throw new DispatchException("No request query matched");
668       }
669       catch (XmlException e)
670       {
671          throw new DispatchException(e);
672       }
673    }
674 }