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 		addlQueryToolbarActions( 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 addlQueryToolbarActions( 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 							return getMockOperation().getMockResponseByName( query.getResponse() );
253 						}
254 					}
255 
256 					cursorCache.put( path, cursor );
257 				}
258 			}
259 
260 			return null;
261 		}
262 		catch( Throwable e )
263 		{
264 			throw new DispatchException( e );
265 		}
266 		finally
267 		{
268 			for( XmlCursor cursor : cursorCache.values() )
269 			{
270 				if( cursor != null )
271 				{
272 					cursor.dispose();
273 				}
274 			}
275 		}
276 	}
277 
278 	public Query addQuery( String name )
279 	{
280 		Query query = new Query( conf.addNewQuery() );
281 		query.setName( name );
282 		queries.add( query );
283 
284 		getPropertyChangeSupport().firePropertyChange( "queries", null, query );
285 
286 		if( queryItemListModel != null )
287 			queryItemListModel.fireAdded();
288 
289 		return query;
290 	}
291 
292 	public void deleteQuery( Query query )
293 	{
294 		int ix = queries.indexOf( query );
295 		queries.remove( ix );
296 		getPropertyChangeSupport().firePropertyChange( "queries", query, null );
297 
298 		if( queryItemListModel != null )
299 			queryItemListModel.fireRemoved( ix );
300 
301 		conf.removeQuery( ix );
302 		saveConfig();
303 	}
304 
305 	public Query[] getQueries()
306 	{
307 		return queries.toArray( new Query[queries.size()] );
308 	}
309 
310 	public int getQueryCount()
311 	{
312 		return queries.size();
313 	}
314 
315 	public Query getQueryAt( int index )
316 	{
317 		return queries.get( index );
318 	}
319 
320 	public void propertyChange( PropertyChangeEvent evt )
321 	{
322 		if( queryItemListModel != null )
323 			queryItemListModel.refresh();
324 	}
325 
326 	public static class Factory implements MockOperationDispatchFactory
327 	{
328 		public MockOperationDispatcher build( WsdlMockOperation mockOperation )
329 		{
330 			return new QueryMatchMockOperationDispatcher( mockOperation );
331 		}
332 	}
333 
334 	public class Query extends AbstractPropertyChangeNotifier
335 	{
336 		private MockOperationQueryMatchDispatchConfig.Query config;
337 
338 		protected Query( MockOperationQueryMatchDispatchConfig.Query config )
339 		{
340 			this.config = config;
341 		}
342 
343 		public String getName()
344 		{
345 			return config.getName();
346 		}
347 
348 		public void setName( String s )
349 		{
350 			String old = config.getName();
351 			config.setName( s );
352 			saveConfig();
353 			firePropertyChange( "name", old, s );
354 		}
355 
356 		public boolean isDisabled()
357 		{
358 			return config.getDisabled();
359 		}
360 
361 		public void setDisabled( boolean disabled )
362 		{
363 			boolean old = config.getDisabled();
364 			if( old == disabled )
365 				return;
366 			config.setDisabled( disabled );
367 			saveConfig();
368 			firePropertyChange( "disabled", old, disabled );
369 			queryItemListModel.refresh();
370 		}
371 
372 		public String getQuery()
373 		{
374 			return config.getQuery();
375 		}
376 
377 		public void setQuery( String s )
378 		{
379 			String old = config.getQuery();
380 			config.setQuery( s );
381 			saveConfig();
382 			firePropertyChange( "query", old, s );
383 		}
384 
385 		public String getMatch()
386 		{
387 			return config.getMatch();
388 		}
389 
390 		public void setMatch( String s )
391 		{
392 			String old = config.getMatch();
393 			config.setMatch( s );
394 			saveConfig();
395 			firePropertyChange( "match", old, s );
396 		}
397 
398 		public String getResponse()
399 		{
400 			return config.getResponse();
401 		}
402 
403 		public void setResponse( String s )
404 		{
405 			String old = config.getResponse();
406 			config.setResponse( s );
407 			saveConfig();
408 			firePropertyChange( "response", old, s );
409 		}
410 	}
411 
412 	private void saveConfig()
413 	{
414 		saveConfig( conf );
415 	}
416 
417 	private class QueryItemListModel extends AbstractListModel
418 	{
419 		public int getSize()
420 		{
421 			return getQueryCount();
422 		}
423 
424 		public Object getElementAt( int index )
425 		{
426 			return getQueryAt( index );
427 		}
428 
429 		public void refresh()
430 		{
431 			fireContentsChanged( this, 0, getQueryCount() );
432 		}
433 
434 		public void fireAdded()
435 		{
436 			fireIntervalAdded( this, getQueryCount(), getQueryCount() );
437 		}
438 
439 		public void fireRemoved( int index )
440 		{
441 			fireIntervalRemoved( this, index, index );
442 		}
443 	}
444 
445 	private class MockResponsesComboBoxModel extends AbstractListModel implements ComboBoxModel
446 	{
447 		public int getSize()
448 		{
449 			return getMockOperation().getMockResponseCount();
450 		}
451 
452 		public Object getElementAt( int index )
453 		{
454 			return getMockOperation().getMockResponseAt( index ).getName();
455 		}
456 
457 		public void setSelectedItem( Object anItem )
458 		{
459 			Query query = getSelectedQuery();
460 			if( query != null )
461 				query.setResponse( String.valueOf( anItem ) );
462 		}
463 
464 		public Object getSelectedItem()
465 		{
466 			Query query = getSelectedQuery();
467 			return query != null ? query.getResponse() : null;
468 		}
469 	}
470 
471 	protected Query getSelectedQuery()
472 	{
473 		return queryDetailFormPresentationModel == null ? null : queryDetailFormPresentationModel.getBean();
474 	}
475 
476 	private final class AddAction extends AbstractAction
477 	{
478 		public AddAction()
479 		{
480 			putValue( Action.SHORT_DESCRIPTION, "Adds a new Match" );
481 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/add_property.gif" ) );
482 		}
483 
484 		public void actionPerformed( ActionEvent e )
485 		{
486 			String name = UISupport.prompt( "Specify name for Match", "Add Query Match", "" );
487 			if( name == null || name.trim().length() == 0 )
488 				return;
489 
490 			addQuery( name );
491 		}
492 	}
493 
494 	private final class CopyAction extends AbstractAction
495 	{
496 		public CopyAction()
497 		{
498 			putValue( Action.SHORT_DESCRIPTION, "Copies the selected Match" );
499 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/clone_request.gif" ) );
500 		}
501 
502 		public void actionPerformed( ActionEvent e )
503 		{
504 			QueryMatchMockOperationDispatcher.Query selectedQuery = getSelectedQuery();
505 			if( selectedQuery == null )
506 				return;
507 
508 			String name = UISupport.prompt( "Specify name for copied Query", "Copy Query", selectedQuery.getName() );
509 			if( name == null || name.trim().length() == 0 )
510 				return;
511 
512 			QueryMatchMockOperationDispatcher.Query query = addQuery( name );
513 			query.setMatch( selectedQuery.getMatch() );
514 			query.setQuery( selectedQuery.getQuery() );
515 			query.setResponse( selectedQuery.getResponse() );
516 
517 			itemList.setSelectedIndex( getQueryCount() - 1 );
518 		}
519 	}
520 
521 	private final class DeleteAction extends AbstractAction
522 	{
523 		public DeleteAction()
524 		{
525 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/remove_property.gif" ) );
526 			putValue( Action.SHORT_DESCRIPTION, "Deletes the selected Property Transfer" );
527 		}
528 
529 		public void actionPerformed( ActionEvent e )
530 		{
531 			QueryMatchMockOperationDispatcher.Query selectedQuery = getSelectedQuery();
532 			if( selectedQuery == null )
533 				return;
534 
535 			if( UISupport.confirm( "Delete selected Query", "Delete Query" ) )
536 			{
537 				deleteQuery( selectedQuery );
538 				if( getQueryCount() > 0 )
539 					itemList.setSelectedIndex( 0 );
540 			}
541 		}
542 	}
543 
544 	private final class RenameAction extends AbstractAction
545 	{
546 		public RenameAction()
547 		{
548 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/rename.gif" ) );
549 			putValue( Action.SHORT_DESCRIPTION, "Renames the selected Property Transfer" );
550 		}
551 
552 		public void actionPerformed( ActionEvent e )
553 		{
554 			QueryMatchMockOperationDispatcher.Query selectedQuery = getSelectedQuery();
555 			if( selectedQuery == null )
556 				return;
557 
558 			String newName = UISupport.prompt( "Specify new name for Query", "Rename Query", selectedQuery.getName() );
559 
560 			if( newName != null && !selectedQuery.getName().equals( newName ) )
561 			{
562 				selectedQuery.setName( newName );
563 				queryItemListModel.refresh();
564 			}
565 		}
566 	}
567 
568 	private final class DeclareNamespacesAction extends AbstractAction
569 	{
570 		public DeclareNamespacesAction()
571 		{
572 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/declareNs.gif" ) );
573 			putValue( Action.SHORT_DESCRIPTION, "Declare request namespaces in current query" );
574 		}
575 
576 		public void actionPerformed( ActionEvent e )
577 		{
578 			QueryMatchMockOperationDispatcher.Query selectedQuery = getSelectedQuery();
579 			if( selectedQuery == null )
580 				return;
581 
582 			try
583 			{
584 				WsdlMockResult lastResult = getMockOperation().getLastMockResult();
585 				String content = null;
586 				if( lastResult == null )
587 				{
588 					if( !UISupport.confirm( "Missing last result, declare from default request instead?",
589 							"Declare Namespaces" ) )
590 					{
591 						return;
592 					}
593 
594 					content = getMockOperation().getOperation().createRequest( true );
595 				}
596 				else
597 				{
598 					content = lastResult.getMockRequest().getRequestContent();
599 				}
600 
601 				String path = selectedQuery.getQuery();
602 				if( path == null )
603 					path = "";
604 
605 				selectedQuery.setQuery( XmlUtils.declareXPathNamespaces( content ) + path );
606 			}
607 			catch( Exception e1 )
608 			{
609 				UISupport.showErrorMessage( e1 );
610 			}
611 		}
612 	}
613 
614 	private final class RunAction extends AbstractAction
615 	{
616 		public RunAction()
617 		{
618 			putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/run.gif" ) );
619 			putValue( Action.SHORT_DESCRIPTION, "Runs Queries on last request" );
620 		}
621 
622 		public void actionPerformed( ActionEvent e )
623 		{
624 			WsdlMockResult result = getMockOperation().getLastMockResult();
625 			if( result != null )
626 			{
627 				try
628 				{
629 					UISupport.showInfoMessage( "Selected ["
630 							+ selectMockResponse( result.getMockRequest(), result ).getName() + "]" );
631 				}
632 				catch( DispatchException e1 )
633 				{
634 					UISupport.showErrorMessage( e1 );
635 				}
636 			}
637 			else
638 			{
639 				UISupport.showErrorMessage( "Missing request to query" );
640 			}
641 		}
642 	}
643 
644 	private final class ExtractFromCurrentAction extends AbstractAction
645 	{
646 		public ExtractFromCurrentAction()
647 		{
648 			super( "Extract" );
649 			putValue( Action.SHORT_DESCRIPTION, "Extracts the current value into the Value field" );
650 		}
651 
652 		public void actionPerformed( ActionEvent e )
653 		{
654 			QueryMatchMockOperationDispatcher.Query selectedQuery = getSelectedQuery();
655 			if( selectedQuery == null )
656 				return;
657 
658 			WsdlMockResult result = getMockOperation().getLastMockResult();
659 			String content;
660 
661 			if( result != null && StringUtils.hasContent( result.getMockRequest().getRequestContent() ) )
662 			{
663 				content = result.getMockRequest().getRequestContent();
664 			}
665 			else
666 			{
667 				if( !UISupport.confirm( "Missing last result, extract from default request instead?", "Extract Match" ) )
668 				{
669 					return;
670 				}
671 
672 				content = getMockOperation().getOperation().createRequest( true );
673 			}
674 
675 			XmlCursor cursor = null;
676 
677 			try
678 			{
679 				XmlObject xmlObject = XmlObject.Factory.parse( content );
680 				cursor = xmlObject.newCursor();
681 				cursor.selectPath( selectedQuery.getQuery() );
682 				if( !cursor.toNextSelection() )
683 				{
684 					UISupport.showErrorMessage( "Missing match in request" );
685 				}
686 				else
687 				{
688 					selectedQuery.setMatch( XmlUtils.getValueForMatch( cursor ) );
689 				}
690 			}
691 			catch( Throwable e1 )
692 			{
693 				SoapUI.logError( e1 );
694 			}
695 			finally
696 			{
697 				if( cursor != null )
698 				{
699 					cursor.dispose();
700 				}
701 			}
702 		}
703 	}
704 
705 	private class QueryItemListCellRenderer extends DefaultListCellRenderer
706 	{
707 		private Color defaultForeground;
708 
709 		private QueryItemListCellRenderer()
710 		{
711 			this.defaultForeground = getForeground();
712 		}
713 
714 		@Override
715 		public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected,
716 				boolean cellHasFocus )
717 		{
718 			JLabel component = ( JLabel )super.getListCellRendererComponent( list, value, index, isSelected, cellHasFocus );
719 
720 			Query query = ( Query )value;
721 			component.setText( query.getName() );
722 			component.setForeground( ( ( Query )value ).isDisabled() ? Color.GRAY : defaultForeground );
723 
724 			return component;
725 		}
726 	}
727 }