View Javadoc

1   /*
2    * soapUI, copyright (C) 2004-2010 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.dispatch;
14  
15  import java.awt.BorderLayout;
16  import java.awt.Color;
17  import java.awt.Component;
18  import java.awt.event.ActionEvent;
19  import java.beans.PropertyChangeEvent;
20  import java.beans.PropertyChangeListener;
21  import java.util.ArrayList;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import javax.swing.AbstractAction;
27  import javax.swing.AbstractListModel;
28  import javax.swing.Action;
29  import javax.swing.ComboBoxModel;
30  import javax.swing.DefaultListCellRenderer;
31  import javax.swing.JButton;
32  import javax.swing.JComboBox;
33  import javax.swing.JComponent;
34  import javax.swing.JLabel;
35  import javax.swing.JList;
36  import javax.swing.JPanel;
37  import javax.swing.JScrollPane;
38  import javax.swing.JSplitPane;
39  import javax.swing.event.ListSelectionEvent;
40  import javax.swing.event.ListSelectionListener;
41  
42  import org.apache.xmlbeans.XmlCursor;
43  import org.apache.xmlbeans.XmlException;
44  import org.apache.xmlbeans.XmlObject;
45  
46  import com.eviware.soapui.SoapUI;
47  import com.eviware.soapui.config.MockOperationQueryMatchDispatchConfig;
48  import com.eviware.soapui.impl.support.actions.ShowOnlineHelpAction;
49  import com.eviware.soapui.impl.wsdl.mock.DispatchException;
50  import com.eviware.soapui.impl.wsdl.mock.WsdlMockOperation;
51  import com.eviware.soapui.impl.wsdl.mock.WsdlMockRequest;
52  import com.eviware.soapui.impl.wsdl.mock.WsdlMockResponse;
53  import com.eviware.soapui.impl.wsdl.mock.WsdlMockResult;
54  import com.eviware.soapui.impl.wsdl.support.HelpUrls;
55  import com.eviware.soapui.model.propertyexpansion.PropertyExpander;
56  import com.eviware.soapui.support.AbstractPropertyChangeNotifier;
57  import com.eviware.soapui.support.StringUtils;
58  import com.eviware.soapui.support.UISupport;
59  import com.eviware.soapui.support.components.JXToolBar;
60  import com.eviware.soapui.support.components.SimpleBindingForm;
61  import com.eviware.soapui.support.xml.XmlUtils;
62  import com.eviware.soapui.ui.support.ModelItemDesktopPanel;
63  import com.jgoodies.binding.PresentationModel;
64  
65  public class QueryMatchMockOperationDispatcher extends AbstractMockOperationDispatcher implements
66  		PropertyChangeListener
67  {
68  	private MockOperationQueryMatchDispatchConfig conf;
69  	private List<Query> queries = new ArrayList<Query>();
70  	private PresentationModel<Query> queryDetailFormPresentationModel;
71  	private QueryItemListModel queryItemListModel;
72  	private JList itemList;
73  	private JButton deleteButton;
74  	private JButton copyButton;
75  	private JButton renameButton;
76  	private SimpleBindingForm detailForm;
77  	private JButton runButton;
78  	private JButton declareNsButton = new JButton( new DeclareNamespacesAction() );
79  	private JButton extractFromCurrentButton = new JButton( new ExtractFromCurrentAction() );
80  
81  	public QueryMatchMockOperationDispatcher( WsdlMockOperation mockOperation )
82  	{
83  		super( mockOperation );
84  
85  		try
86  		{
87  			conf = MockOperationQueryMatchDispatchConfig.Factory.parse( getConfig().xmlText() );
88  
89  			for( MockOperationQueryMatchDispatchConfig.Query query : conf.getQueryList() )
90  			{
91  				queries.add( new Query( query ) );
92  			}
93  		}
94  		catch( XmlException e )
95  		{
96  			e.printStackTrace();
97  		}
98  
99  		mockOperation.addPropertyChangeListener( "mockResponses", this );
100 	}
101 
102 	@Override
103 	public void release()
104 	{
105 		getMockOperation().removePropertyChangeListener( "mockResponses", this );
106 		super.release();
107 	}
108 
109 	@Override
110 	public JComponent getEditorComponent()
111 	{
112 		JSplitPane splitPane = UISupport.createHorizontalSplit( buildQueryListComponent(), buildQueryDetailComponent() );
113 		splitPane.setDividerLocation( 300 );
114 		setEnabled();
115 		return splitPane;
116 	}
117 
118 	protected Component buildQueryListComponent()
119 	{
120 		JPanel panel = new JPanel( new BorderLayout() );
121 
122 		queryItemListModel = new QueryItemListModel();
123 		itemList = new JList( queryItemListModel );
124 		itemList.setCellRenderer( new QueryItemListCellRenderer() );
125 		itemList.addListSelectionListener( new ListSelectionListener()
126 		{
127 			public void valueChanged( ListSelectionEvent e )
128 			{
129 				queryDetailFormPresentationModel.setBean( ( Query )itemList.getSelectedValue() );
130 				setEnabled();
131 			}
132 		} );
133 
134 		panel.add( buildItemsToolbar(), BorderLayout.NORTH );
135 		panel.add( new JScrollPane( itemList ), BorderLayout.CENTER );
136 
137 		return panel;
138 	}
139 
140 	protected void setEnabled()
141 	{
142 		QueryMatchMockOperationDispatcher.Query bean = queryDetailFormPresentationModel.getBean();
143 
144 		detailForm.setEnabled( bean != null );
145 		renameButton.setEnabled( bean != null );
146 		deleteButton.setEnabled( bean != null );
147 		copyButton.setEnabled( bean != null );
148 		extractFromCurrentButton.setEnabled( bean != null );
149 		copyButton.setEnabled( bean != null );
150 		declareNsButton.setEnabled( bean != null );
151 		runButton.setEnabled( getQueryCount() > 0 );
152 	}
153 
154 	private JXToolBar buildItemsToolbar()
155 	{
156 		JXToolBar toolbar = UISupport.createSmallToolbar();
157 
158 		runButton = UISupport.createToolbarButton( new RunAction() );
159 		toolbar.addFixed( runButton );
160 		toolbar.addSeparator();
161 
162 		toolbar.addFixed( UISupport.createToolbarButton( new AddAction() ) );
163 		deleteButton = UISupport.createToolbarButton( new DeleteAction() );
164 		deleteButton.setEnabled( false );
165 		toolbar.addFixed( deleteButton );
166 		toolbar.addSeparator();
167 		copyButton = UISupport.createToolbarButton( new CopyAction() );
168 		copyButton.setEnabled( false );
169 		toolbar.addFixed( copyButton );
170 		renameButton = UISupport.createToolbarButton( new RenameAction() );
171 		renameButton.setEnabled( false );
172 		toolbar.addFixed( renameButton );
173 
174 		toolbar.addSeparator();
175 
176 		return toolbar;
177 	}
178 
179 	protected Component buildQueryDetailComponent()
180 	{
181 		queryDetailFormPresentationModel = new PresentationModel<Query>( null );
182 		detailForm = new SimpleBindingForm( queryDetailFormPresentationModel );
183 
184 		detailForm.setDefaultTextAreaRows( 5 );
185 		detailForm.setDefaultTextAreaColumns( 50 );
186 
187 		detailForm.append( buildQueryToolbar() );
188 		detailForm.appendTextArea( "query", "XPath", "The XPath to query in the request" );
189 		detailForm.appendTextArea( "match", "Expected Value", "The value to match" );
190 		JComboBox comboBox = detailForm.appendComboBox( "response", "Dispatch to", new MockResponsesComboBoxModel(),
191 				"The MockResponse to dispatch to" );
192 		UISupport.setFixedSize( comboBox, 150, 20 );
193 		detailForm.appendCheckBox( "disabled", "Disabled", "Disables this Query" );
194 
195 		return new JScrollPane( detailForm.getPanel() );
196 	}
197 
198 	protected JXToolBar buildQueryToolbar()
199 	{
200 		JXToolBar toolBar = UISupport.createSmallToolbar();
201 
202 		addQueryToolbarActions( toolBar );
203 
204 		toolBar.addGlue();
205 		toolBar.addFixed( ModelItemDesktopPanel.createActionButton( new ShowOnlineHelpAction(
206 				HelpUrls.MOCKOPERATION_QUERYMATCHDISPATCH_HELP_URL ), true ) );
207 
208 		return toolBar;
209 	}
210 
211 	protected void addQueryToolbarActions( JXToolBar toolBar )
212 	{
213 		toolBar.addFixed( declareNsButton );
214 		toolBar.addFixed( extractFromCurrentButton );
215 	}
216 
217 	public WsdlMockResponse selectMockResponse( WsdlMockRequest request, WsdlMockResult result )
218 			throws DispatchException
219 	{
220 		Map<String, XmlCursor> cursorCache = new HashMap<String, XmlCursor>();
221 
222 		try
223 		{
224 			XmlObject xmlObject = request.getRequestXmlObject();
225 
226 			for( Query query : getQueries() )
227 			{
228 				if( query.isDisabled() )
229 					continue;
230 
231 				String path = PropertyExpander.expandProperties( request.getContext(), query.getQuery() );
232 				if( StringUtils.hasContent( path ) )
233 				{
234 					XmlCursor cursor = cursorCache.get( path );
235 					if( cursor == null && !cursorCache.containsKey( path ) )
236 					{
237 						cursor = xmlObject.newCursor();
238 						cursor.selectPath( path );
239 						if( !cursor.toNextSelection() )
240 						{
241 							cursor.dispose();
242 							cursor = null;
243 						}
244 					}
245 
246 					if( cursor != null )
247 					{
248 						String value = PropertyExpander.expandProperties( request.getContext(), query.getMatch() );
249 
250 						if( value.equals( XmlUtils.getValueForMatch( cursor ) ) )
251 						{
252 							request.getRequestContext().put( "usedQueryMatch", query.getName() );
253 							return getMockOperation().getMockResponseByName( query.getResponse() );
254 						}
255 					}
256 
257 					cursorCache.put( path, cursor );
258 				}
259 			}
260 
261 			return null;
262 		}
263 		catch( Throwable e )
264 		{
265 			throw new DispatchException( e );
266 		}
267 		finally
268 		{
269 			for( XmlCursor cursor : cursorCache.values() )
270 			{
271 				if( cursor != null )
272 				{
273 					cursor.dispose();
274 				}
275 			}
276 		}
277 	}
278 
279 	public Query addQuery( String name )
280 	{
281 		Query query = new Query( conf.addNewQuery() );
282 		query.setName( name );
283 		queries.add( query );
284 
285 		getPropertyChangeSupport().firePropertyChange( "queries", null, query );
286 
287 		if( queryItemListModel != null )
288 			queryItemListModel.fireAdded();
289 
290 		return query;
291 	}
292 
293 	public void deleteQuery( Query query )
294 	{
295 		int ix = queries.indexOf( query );
296 		queries.remove( ix );
297 		getPropertyChangeSupport().firePropertyChange( "queries", query, null );
298 
299 		if( queryItemListModel != null )
300 			queryItemListModel.fireRemoved( ix );
301 
302 		conf.removeQuery( ix );
303 		saveConfig();
304 	}
305 
306 	public Query[] getQueries()
307 	{
308 		return queries.toArray( new Query[queries.size()] );
309 	}
310 
311 	public int getQueryCount()
312 	{
313 		return queries.size();
314 	}
315 
316 	public Query getQueryByName( String name )
317 	{
318 		for( Query q : queries )
319 		{
320 			if( q.getName().equals( name ) )
321 				return q;
322 		}
323 
324 		return null;
325 	}
326 
327 	public Query getQueryAt( int index )
328 	{
329 		return queries.get( index );
330 	}
331 
332 	public void propertyChange( PropertyChangeEvent evt )
333 	{
334 		if( queryItemListModel != null )
335 			queryItemListModel.refresh();
336 	}
337 
338 	public static class Factory implements MockOperationDispatchFactory
339 	{
340 		public MockOperationDispatcher build( WsdlMockOperation mockOperation )
341 		{
342 			return new QueryMatchMockOperationDispatcher( mockOperation );
343 		}
344 	}
345 
346 	public class Query extends AbstractPropertyChangeNotifier
347 	{
348 		private MockOperationQueryMatchDispatchConfig.Query config;
349 
350 		protected Query( MockOperationQueryMatchDispatchConfig.Query config )
351 		{
352 			this.config = config;
353 		}
354 
355 		public String getName()
356 		{
357 			return config.getName();
358 		}
359 
360 		public void setName( String s )
361 		{
362 			String old = config.getName();
363 			config.setName( s );
364 			saveConfig();
365 			firePropertyChange( "name", old, s );
366 		}
367 
368 		public boolean isDisabled()
369 		{
370 			return config.getDisabled();
371 		}
372 
373 		public void setDisabled( boolean disabled )
374 		{
375 			boolean old = config.getDisabled();
376 			if( old == disabled )
377 				return;
378 			config.setDisabled( disabled );
379 			saveConfig();
380 			firePropertyChange( "disabled", old, disabled );
381 			queryItemListModel.refresh();
382 		}
383 
384 		public String getQuery()
385 		{
386 			return config.getQuery();
387 		}
388 
389 		public void setQuery( String s )
390 		{
391 			String old = config.getQuery();
392 			config.setQuery( s );
393 			saveConfig();
394 			firePropertyChange( "query", old, s );
395 		}
396 
397 		public String getMatch()
398 		{
399 			return config.getMatch();
400 		}
401 
402 		public void setMatch( String s )
403 		{
404 			String old = config.getMatch();
405 			config.setMatch( s );
406 			saveConfig();
407 			firePropertyChange( "match", old, s );
408 		}
409 
410 		public String getResponse()
411 		{
412 			return config.getResponse();
413 		}
414 
415 		public void setResponse( String s )
416 		{
417 			String old = config.getResponse();
418 			config.setResponse( s );
419 			saveConfig();
420 			firePropertyChange( "response", old, s );
421 		}
422 	}
423 
424 	private void saveConfig()
425 	{
426 		saveConfig( conf );
427 	}
428 
429 	private class QueryItemListModel extends AbstractListModel
430 	{
431 		public int getSize()
432 		{
433 			return getQueryCount();
434 		}
435 
436 		public Object getElementAt( int index )
437 		{
438 			return getQueryAt( index );
439 		}
440 
441 		public void refresh()
442 		{
443 			fireContentsChanged( this, 0, getQueryCount() );
444 		}
445 
446 		public void fireAdded()
447 		{
448 			fireIntervalAdded( this, getQueryCount(), getQueryCount() );
449 		}
450 
451 		public void fireRemoved( int index )
452 		{
453 			fireIntervalRemoved( this, index, index );
454 		}
455 	}
456 
457 	private class MockResponsesComboBoxModel extends AbstractListModel implements ComboBoxModel
458 	{
459 		public int getSize()
460 		{
461 			return getMockOperation().getMockResponseCount();
462 		}
463 
464 		public Object getElementAt( int index )
465 		{
466 			return getMockOperation().getMockResponseAt( index ).getName();
467 		}
468 
469 		public void setSelectedItem( Object anItem )
470 		{
471 			Query query = getSelectedQuery();
472 			if( query != null )
473 				query.setResponse( String.valueOf( anItem ) );
474 		}
475 
476 		public Object getSelectedItem()
477 		{
478 			Query query = getSelectedQuery();
479 			return query != null ? query.getResponse() : null;
480 		}
481 	}
482 
483 	protected Query getSelectedQuery()
484 	{
485 		return queryDetailFormPresentationModel == null ? null : queryDetailFormPresentationModel.getBean();
486 	}
487 
488 	private final class AddAction extends AbstractAction
489 	{
490 		public AddAction()
491 		{
492 			putValue( Action.SHORT_DESCRIPTION, "Adds a new Match" );
493 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/add_property.gif" ) );
494 		}
495 
496 		public void actionPerformed( ActionEvent e )
497 		{
498 			String name = UISupport.prompt( "Specify name for Match", "Add Query Match", "" );
499 			if( name == null || name.trim().length() == 0 )
500 				return;
501 
502 			addQuery( name );
503 		}
504 	}
505 
506 	private final class CopyAction extends AbstractAction
507 	{
508 		public CopyAction()
509 		{
510 			putValue( Action.SHORT_DESCRIPTION, "Copies the selected Match" );
511 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/clone_request.gif" ) );
512 		}
513 
514 		public void actionPerformed( ActionEvent e )
515 		{
516 			QueryMatchMockOperationDispatcher.Query selectedQuery = getSelectedQuery();
517 			if( selectedQuery == null )
518 				return;
519 
520 			String name = UISupport.prompt( "Specify name for copied Query", "Copy Query", selectedQuery.getName() );
521 			if( name == null || name.trim().length() == 0 )
522 				return;
523 
524 			QueryMatchMockOperationDispatcher.Query query = addQuery( name );
525 			query.setMatch( selectedQuery.getMatch() );
526 			query.setQuery( selectedQuery.getQuery() );
527 			query.setResponse( selectedQuery.getResponse() );
528 
529 			itemList.setSelectedIndex( getQueryCount() - 1 );
530 		}
531 	}
532 
533 	private final class DeleteAction extends AbstractAction
534 	{
535 		public DeleteAction()
536 		{
537 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/remove_property.gif" ) );
538 			putValue( Action.SHORT_DESCRIPTION, "Deletes the selected Property Transfer" );
539 		}
540 
541 		public void actionPerformed( ActionEvent e )
542 		{
543 			QueryMatchMockOperationDispatcher.Query selectedQuery = getSelectedQuery();
544 			if( selectedQuery == null )
545 				return;
546 
547 			if( UISupport.confirm( "Delete selected Query", "Delete Query" ) )
548 			{
549 				deleteQuery( selectedQuery );
550 				if( getQueryCount() > 0 )
551 					itemList.setSelectedIndex( 0 );
552 			}
553 		}
554 	}
555 
556 	private final class RenameAction extends AbstractAction
557 	{
558 		public RenameAction()
559 		{
560 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/rename.gif" ) );
561 			putValue( Action.SHORT_DESCRIPTION, "Renames the selected Property Transfer" );
562 		}
563 
564 		public void actionPerformed( ActionEvent e )
565 		{
566 			QueryMatchMockOperationDispatcher.Query selectedQuery = getSelectedQuery();
567 			if( selectedQuery == null )
568 				return;
569 
570 			String newName = UISupport.prompt( "Specify new name for Query", "Rename Query", selectedQuery.getName() );
571 
572 			if( newName != null && !selectedQuery.getName().equals( newName ) )
573 			{
574 				selectedQuery.setName( newName );
575 				queryItemListModel.refresh();
576 			}
577 		}
578 	}
579 
580 	private final class DeclareNamespacesAction extends AbstractAction
581 	{
582 		public DeclareNamespacesAction()
583 		{
584 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/declareNs.gif" ) );
585 			putValue( Action.SHORT_DESCRIPTION, "Declare request namespaces in current query" );
586 		}
587 
588 		public void actionPerformed( ActionEvent e )
589 		{
590 			QueryMatchMockOperationDispatcher.Query selectedQuery = getSelectedQuery();
591 			if( selectedQuery == null )
592 				return;
593 
594 			try
595 			{
596 				WsdlMockResult lastResult = getMockOperation().getLastMockResult();
597 				String content = null;
598 				if( lastResult == null )
599 				{
600 					if( !UISupport.confirm( "Missing last result, declare from default request instead?",
601 							"Declare Namespaces" ) )
602 					{
603 						return;
604 					}
605 
606 					content = getMockOperation().getOperation().createRequest( true );
607 				}
608 				else
609 				{
610 					content = lastResult.getMockRequest().getRequestContent();
611 				}
612 
613 				String path = selectedQuery.getQuery();
614 				if( path == null )
615 					path = "";
616 
617 				selectedQuery.setQuery( XmlUtils.declareXPathNamespaces( content ) + path );
618 			}
619 			catch( Exception e1 )
620 			{
621 				UISupport.showErrorMessage( e1 );
622 			}
623 		}
624 	}
625 
626 	private final class RunAction extends AbstractAction
627 	{
628 		public RunAction()
629 		{
630 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/run.gif" ) );
631 			putValue( Action.SHORT_DESCRIPTION, "Runs Queries on last request" );
632 		}
633 
634 		public void actionPerformed( ActionEvent e )
635 		{
636 			WsdlMockResult result = getMockOperation().getLastMockResult();
637 			if( result != null )
638 			{
639 				try
640 				{
641 					UISupport.showInfoMessage( "Selected ["
642 							+ selectMockResponse( result.getMockRequest(), result ).getName() + "]" );
643 				}
644 				catch( DispatchException e1 )
645 				{
646 					UISupport.showErrorMessage( e1 );
647 				}
648 			}
649 			else
650 			{
651 				UISupport.showErrorMessage( "Missing request to query" );
652 			}
653 		}
654 	}
655 
656 	private final class ExtractFromCurrentAction extends AbstractAction
657 	{
658 		public ExtractFromCurrentAction()
659 		{
660 			super( "Extract" );
661 			putValue( Action.SHORT_DESCRIPTION, "Extracts the current value into the Value field" );
662 		}
663 
664 		public void actionPerformed( ActionEvent e )
665 		{
666 			QueryMatchMockOperationDispatcher.Query selectedQuery = getSelectedQuery();
667 			if( selectedQuery == null )
668 				return;
669 
670 			WsdlMockResult result = getMockOperation().getLastMockResult();
671 			String content;
672 
673 			if( result != null && StringUtils.hasContent( result.getMockRequest().getRequestContent() ) )
674 			{
675 				content = result.getMockRequest().getRequestContent();
676 			}
677 			else
678 			{
679 				if( !UISupport.confirm( "Missing last result, extract from default request instead?", "Extract Match" ) )
680 				{
681 					return;
682 				}
683 
684 				content = getMockOperation().getOperation().createRequest( true );
685 			}
686 
687 			XmlCursor cursor = null;
688 
689 			try
690 			{
691 				XmlObject xmlObject = XmlObject.Factory.parse( content );
692 				cursor = xmlObject.newCursor();
693 				cursor.selectPath( selectedQuery.getQuery() );
694 				if( !cursor.toNextSelection() )
695 				{
696 					UISupport.showErrorMessage( "Missing match in request" );
697 				}
698 				else
699 				{
700 					selectedQuery.setMatch( XmlUtils.getValueForMatch( cursor ) );
701 				}
702 			}
703 			catch( Throwable e1 )
704 			{
705 				SoapUI.logError( e1 );
706 			}
707 			finally
708 			{
709 				if( cursor != null )
710 				{
711 					cursor.dispose();
712 				}
713 			}
714 		}
715 	}
716 
717 	private class QueryItemListCellRenderer extends DefaultListCellRenderer
718 	{
719 		private Color defaultForeground;
720 
721 		private QueryItemListCellRenderer()
722 		{
723 			this.defaultForeground = getForeground();
724 		}
725 
726 		@Override
727 		public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected,
728 				boolean cellHasFocus )
729 		{
730 			JLabel component = ( JLabel )super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
731 
732 			Query query = ( Query )value;
733 			component.setText( query.getName() );
734 			component.setForeground( ( ( Query )value ).isDisabled() ? Color.GRAY : defaultForeground );
735 
736 			return component;
737 		}
738 	}
739 }