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.support.panels;
14  
15  import com.eviware.soapui.SoapUI;
16  import com.eviware.soapui.impl.support.AbstractHttpRequest;
17  import com.eviware.soapui.impl.support.EndpointsComboBoxModel;
18  import com.eviware.soapui.impl.support.actions.ShowOnlineHelpAction;
19  import com.eviware.soapui.impl.support.components.ModelItemXmlEditor;
20  import com.eviware.soapui.impl.support.components.RequestMessageXmlEditor;
21  import com.eviware.soapui.impl.support.components.ResponseMessageXmlEditor;
22  import com.eviware.soapui.impl.wsdl.submit.transports.http.HttpResponse;
23  import com.eviware.soapui.model.ModelItem;
24  import com.eviware.soapui.model.iface.Request.SubmitException;
25  import com.eviware.soapui.model.iface.Submit;
26  import com.eviware.soapui.model.iface.Submit.Status;
27  import com.eviware.soapui.model.iface.SubmitContext;
28  import com.eviware.soapui.model.iface.SubmitListener;
29  import com.eviware.soapui.settings.UISettings;
30  import com.eviware.soapui.support.UISupport;
31  import com.eviware.soapui.support.StringUtils;
32  import com.eviware.soapui.support.actions.ChangeSplitPaneOrientationAction;
33  import com.eviware.soapui.support.components.JEditorStatusBarWithProgress;
34  import com.eviware.soapui.support.components.JXToolBar;
35  import com.eviware.soapui.support.editor.views.xml.source.XmlSourceEditorView;
36  import com.eviware.soapui.support.editor.xml.XmlDocument;
37  import com.eviware.soapui.support.swing.SoapUISplitPaneUI;
38  import com.eviware.soapui.support.xml.JXEditTextArea;
39  import com.eviware.soapui.ui.support.ModelItemDesktopPanel;
40  import org.apache.log4j.Logger;
41  
42  import javax.swing.*;
43  import javax.swing.event.ChangeEvent;
44  import javax.swing.event.ChangeListener;
45  import java.awt.*;
46  import java.awt.event.*;
47  import java.beans.PropertyChangeEvent;
48  
49  /***
50   * Abstract DesktopPanel for HttpRequests
51   *
52   * @author Ole.Matzura
53   */
54  
55  public abstract class AbstractHttpRequestDesktopPanel<T extends ModelItem, T2 extends AbstractHttpRequest<?>> extends
56          ModelItemDesktopPanel<T> implements SubmitListener
57  {
58     private final static Logger log = Logger.getLogger( AbstractHttpRequestDesktopPanel.class );
59  
60     private JComponent endpointComponent;
61     private JButton submitButton;
62     private JButton cancelButton;
63     private EndpointsComboBoxModel endpointsModel;
64     private JEditorStatusBarWithProgress statusBar;
65     private JButton splitButton;
66     private Submit submit;
67     private JSplitPane requestSplitPane;
68     private MoveFocusAction moveFocusAction;
69     private ClosePanelAction closePanelAction = new ClosePanelAction();
70     private T2 request;
71  
72     private ModelItemXmlEditor<?, ?> requestEditor;
73     private ModelItemXmlEditor<?, ?> responseEditor;
74  
75     private JTabbedPane requestTabs;
76     private JPanel requestTabPanel;
77     private JToggleButton tabsButton;
78  
79     private boolean responseHasFocus;
80     private SubmitAction submitAction;
81     private boolean hasClosed;
82  
83     public AbstractHttpRequestDesktopPanel( T modelItem, T2 request )
84     {
85        super( modelItem );
86  
87        this.request = request;
88  
89        init( request );
90  
91        try
92        {
93           // required to avoid deadlock in UI when opening attachments inspector
94           if( request.getAttachmentCount() > 0 )
95           {
96              request.getOperation().getInterface().getDefinitionContext().loadIfNecessary();
97           }
98        }
99        catch( Exception e )
100       {
101          e.printStackTrace();
102       }
103    }
104 
105    protected void init( T2 request )
106    {
107       this.endpointsModel = new EndpointsComboBoxModel( request );
108 
109       request.addSubmitListener( this );
110       request.addPropertyChangeListener( this );
111 
112       add( buildContent(), BorderLayout.CENTER );
113       add( buildToolbar(), BorderLayout.NORTH );
114       add( buildStatusLabel(), BorderLayout.SOUTH );
115 
116       setPreferredSize( new Dimension( 600, 500 ) );
117 
118       addFocusListener( new FocusAdapter()
119       {
120 
121          @Override
122          public void focusGained( FocusEvent e )
123          {
124             if( requestTabs.getSelectedIndex() == 1 || responseHasFocus )
125                responseEditor.requestFocusInWindow();
126             else
127                requestEditor.requestFocusInWindow();
128          }
129       } );
130    }
131 
132    public final T2 getRequest()
133    {
134       return request;
135    }
136 
137    public final ModelItemXmlEditor<?, ?> getRequestEditor()
138    {
139       return requestEditor;
140    }
141 
142    public final ModelItemXmlEditor<?, ?> getResponseEditor()
143    {
144       return responseEditor;
145    }
146 
147    public Submit getSubmit()
148    {
149       return submit;
150    }
151 
152    protected JComponent buildStatusLabel()
153    {
154       statusBar = new JEditorStatusBarWithProgress();
155       statusBar.setBorder( BorderFactory.createEmptyBorder( 1, 0, 0, 0 ) );
156 
157       return statusBar;
158    }
159 
160    public JEditorStatusBarWithProgress getStatusBar()
161    {
162       return statusBar;
163    }
164 
165    protected JComponent buildContent()
166    {
167       requestSplitPane = UISupport.createHorizontalSplit();
168       requestSplitPane.setResizeWeight( 0.5 );
169       requestSplitPane.setBorder( null );
170 
171       submitAction = new SubmitAction();
172       submitButton = createActionButton( submitAction, true );
173       submitButton.setEnabled( request.getEndpoint() != null && request.getEndpoint().trim().length() > 0 );
174 
175       cancelButton = createActionButton( new CancelAction(), false );
176       splitButton = createActionButton( new ChangeSplitPaneOrientationAction( requestSplitPane ), true );
177 
178       tabsButton = new JToggleButton( new ChangeToTabsAction() );
179       tabsButton.setPreferredSize( UISupport.TOOLBAR_BUTTON_DIMENSION );
180 
181       moveFocusAction = new MoveFocusAction();
182 
183       requestEditor = buildRequestEditor();
184       responseEditor = buildResponseEditor();
185 
186       requestTabs = new JTabbedPane();
187       requestTabs.addChangeListener( new ChangeListener()
188       {
189 
190          public void stateChanged( ChangeEvent e )
191          {
192             SwingUtilities.invokeLater( new Runnable()
193             {
194 
195                public void run()
196                {
197                   int ix = requestTabs.getSelectedIndex();
198                   if( ix == 0 )
199                      requestEditor.requestFocus();
200                   else if( ix == 1 && responseEditor != null )
201                      responseEditor.requestFocus();
202                }
203             } );
204          }
205       } );
206 
207       requestTabPanel = UISupport.createTabPanel( requestTabs, true );
208 
209       if( request.getSettings().getBoolean( UISettings.START_WITH_REQUEST_TABS ) )
210       {
211          requestTabs.addTab( "Request", requestEditor );
212          if( responseEditor != null )
213             requestTabs.addTab( "Response", responseEditor );
214          splitButton.setEnabled( false );
215          tabsButton.setSelected( true );
216 
217          return requestTabPanel;
218       }
219       else
220       {
221          requestSplitPane.setTopComponent( requestEditor );
222          requestSplitPane.setBottomComponent( responseEditor );
223          requestSplitPane.setDividerLocation( 0.5 );
224          return requestSplitPane;
225       }
226    }
227 
228    public SubmitAction getSubmitAction()
229    {
230       return submitAction;
231    }
232 
233    protected abstract ModelItemXmlEditor<?, ?> buildResponseEditor();
234 
235    protected abstract ModelItemXmlEditor<?, ?> buildRequestEditor();
236 
237    protected JComponent buildToolbar()
238    {
239       endpointComponent = buildEndpointComponent();
240 
241       JXToolBar toolbar = UISupport.createToolbar();
242       toolbar.add( submitButton );
243 
244       insertButtons( toolbar );
245 
246       toolbar.add( cancelButton );
247 
248       if( endpointComponent != null )
249       {
250          toolbar.addSeparator();
251          toolbar.add( endpointComponent );
252       }
253 
254       toolbar.add( Box.createHorizontalGlue() );
255       toolbar.add( tabsButton );
256       toolbar.add( splitButton );
257       toolbar.add( UISupport.createToolbarButton( new ShowOnlineHelpAction( getHelpUrl() ) ) );
258 
259       return toolbar;
260    }
261 
262    protected JComponent buildEndpointComponent()
263    {
264       final JComboBox endpointCombo = new JComboBox( endpointsModel );
265       endpointCombo.setToolTipText( endpointsModel.getSelectedItem().toString() );
266 
267       endpointCombo.addItemListener( new ItemListener()
268       {
269          // set tooltip, property is set by model directly
270          public void itemStateChanged( ItemEvent e )
271          {
272             Object item = endpointCombo.getSelectedItem();
273             if( item == null )
274             {
275                endpointCombo.setToolTipText( "- no endpoint set for request -" );
276             }
277             else
278             {
279                String selectedItem = item.toString();
280                endpointCombo.setToolTipText( selectedItem );
281             }
282          }
283       } );
284 
285       return endpointCombo;
286    }
287 
288    public void propertyChange( PropertyChangeEvent evt )
289    {
290       if( evt.getPropertyName().equals( AbstractHttpRequest.ENDPOINT_PROPERTY ) )
291       {
292          submitButton.setEnabled( submit == null && StringUtils.hasContent( request.getEndpoint() ));
293       }
294 
295       super.propertyChange( evt );
296    }
297 
298    public JButton getSubmitButton()
299    {
300       return submitButton;
301    }
302 
303    protected abstract String getHelpUrl();
304 
305    protected abstract void insertButtons( JXToolBar toolbar );
306 
307    public void setEnabled( boolean enabled )
308    {
309       if( endpointComponent != null )
310          endpointComponent.setEnabled( enabled );
311 
312       requestEditor.setEditable( enabled );
313       if( responseEditor != null )
314          responseEditor.setEditable( enabled );
315 
316       submitButton.setEnabled( enabled && request.hasEndpoint() );
317 
318       statusBar.setIndeterminate( !enabled );
319    }
320 
321    public abstract class AbstractHttpRequestMessageEditor<T3 extends XmlDocument> extends RequestMessageXmlEditor<T2, T3>
322    {
323       private InputAreaFocusListener inputAreaFocusListener;
324       private JXEditTextArea inputArea;
325 
326       public AbstractHttpRequestMessageEditor( T3 document )
327       {
328          super( document, request );
329 
330          XmlSourceEditorView editor = getSourceEditor();
331          if( editor != null )
332          {
333             inputArea = editor.getInputArea();
334             inputArea.getInputHandler().addKeyBinding( "A+ENTER", submitButton.getAction() );
335             inputArea.getInputHandler().addKeyBinding( "A+X", cancelButton.getAction() );
336             inputArea.getInputHandler().addKeyBinding( "AC+TAB", moveFocusAction );
337             inputArea.getInputHandler().addKeyBinding( "C+F4", closePanelAction );
338 
339             inputAreaFocusListener = new InputAreaFocusListener( editor );
340             inputArea.addFocusListener( inputAreaFocusListener );
341          }
342       }
343 
344       @Override
345       public void release()
346       {
347          super.release();
348          if( inputArea != null )
349             inputArea.removeFocusListener( inputAreaFocusListener );
350       }
351    }
352 
353    public abstract class AbstractHttpResponseMessageEditor<T3 extends XmlDocument> extends ResponseMessageXmlEditor<T2, T3>
354    {
355       private JXEditTextArea inputArea;
356       private ResultAreaFocusListener resultAreaFocusListener;
357 
358       public AbstractHttpResponseMessageEditor( T3 document )
359       {
360          super( document, request );
361 
362          XmlSourceEditorView editor = getSourceEditor();
363 
364          inputArea = editor.getInputArea();
365          if( inputArea != null )
366          {
367             resultAreaFocusListener = new ResultAreaFocusListener( editor );
368             inputArea.addFocusListener( resultAreaFocusListener );
369 
370             inputArea.getInputHandler().addKeyBinding( "A+ENTER", submitButton.getAction() );
371             inputArea.getInputHandler().addKeyBinding( "A+X", cancelButton.getAction() );
372             inputArea.getInputHandler().addKeyBinding( "AC+TAB", moveFocusAction );
373             inputArea.getInputHandler().addKeyBinding( "C+F4", closePanelAction );
374          }
375       }
376 
377       @Override
378       public void release()
379       {
380          super.release();
381 
382          if( inputArea != null )
383             inputArea.removeFocusListener( resultAreaFocusListener );
384       }
385    }
386 
387    protected final class InputAreaFocusListener implements FocusListener
388    {
389       private final XmlSourceEditorView sourceEditor;
390 
391       public InputAreaFocusListener( XmlSourceEditorView editor )
392       {
393          this.sourceEditor = editor;
394       }
395 
396       public void focusGained( FocusEvent e )
397       {
398          responseHasFocus = false;
399 
400          statusBar.setTarget( sourceEditor.getInputArea() );
401          if( !splitButton.isEnabled() )
402          {
403             requestTabs.setSelectedIndex( 0 );
404             return;
405          }
406 
407          if( getModelItem().getSettings().getBoolean( UISettings.NO_RESIZE_REQUEST_EDITOR ) )
408             return;
409 
410          // dont resize if split has been dragged
411          if( ( (SoapUISplitPaneUI) requestSplitPane.getUI() ).hasBeenDragged() )
412             return;
413 
414          int pos = requestSplitPane.getDividerLocation();
415          if( pos >= 600 )
416             return;
417          if( requestSplitPane.getMaximumDividerLocation() > 700 )
418             requestSplitPane.setDividerLocation( 600 );
419          else
420             requestSplitPane.setDividerLocation( 0.8 );
421       }
422 
423       public void focusLost( FocusEvent e )
424       {
425       }
426    }
427 
428    protected final class ResultAreaFocusListener implements FocusListener
429    {
430       private final XmlSourceEditorView sourceEditor;
431 
432       public ResultAreaFocusListener( XmlSourceEditorView editor )
433       {
434          this.sourceEditor = editor;
435       }
436 
437       public void focusGained( FocusEvent e )
438       {
439          responseHasFocus = true;
440 
441          statusBar.setTarget( sourceEditor.getInputArea() );
442          if( !splitButton.isEnabled() )
443          {
444             requestTabs.setSelectedIndex( 1 );
445             return;
446          }
447 
448          if( request.getSettings().getBoolean( UISettings.NO_RESIZE_REQUEST_EDITOR ) )
449             return;
450 
451          // dont resize if split has been dragged or result is empty
452          if( ( (SoapUISplitPaneUI) requestSplitPane.getUI() ).hasBeenDragged() || request.getResponse() == null )
453             return;
454 
455          int pos = requestSplitPane.getDividerLocation();
456          int maximumDividerLocation = requestSplitPane.getMaximumDividerLocation();
457          if( pos + 600 < maximumDividerLocation )
458             return;
459 
460          if( maximumDividerLocation > 700 )
461             requestSplitPane.setDividerLocation( maximumDividerLocation - 600 );
462          else
463             requestSplitPane.setDividerLocation( 0.2 );
464       }
465 
466       public void focusLost( FocusEvent e )
467       {
468       }
469    }
470 
471    public class SubmitAction extends AbstractAction
472    {
473       public SubmitAction()
474       {
475          putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/submit_request.gif" ) );
476          putValue( Action.SHORT_DESCRIPTION, "Submit request to specified endpoint URL" );
477          putValue( Action.ACCELERATOR_KEY, UISupport.getKeyStroke( "alt ENTER" ) );
478       }
479 
480       public void actionPerformed( ActionEvent e )
481       {
482          onSubmit();
483       }
484    }
485 
486    protected abstract Submit doSubmit() throws SubmitException;
487 
488    private class CancelAction extends AbstractAction
489    {
490       public CancelAction()
491       {
492          super();
493          putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/cancel_request.gif" ) );
494          putValue( Action.SHORT_DESCRIPTION, "Aborts ongoing request" );
495          putValue( Action.ACCELERATOR_KEY, UISupport.getKeyStroke( "alt X" ) );
496       }
497 
498       public void actionPerformed( ActionEvent e )
499       {
500          onCancel();
501       }
502    }
503 
504    private class ClosePanelAction extends AbstractAction
505    {
506       public void actionPerformed( ActionEvent e )
507       {
508          SoapUI.getDesktop().closeDesktopPanel( getModelItem() );
509       }
510    }
511 
512    private class MoveFocusAction extends AbstractAction
513    {
514       public void actionPerformed( ActionEvent e )
515       {
516          if( requestEditor.hasFocus() )
517          {
518             responseEditor.requestFocus();
519          }
520          else
521          {
522             requestEditor.requestFocus();
523          }
524       }
525    }
526 
527    public boolean beforeSubmit( Submit submit, SubmitContext context )
528    {
529       if( submit.getRequest() != request )
530          return true;
531 
532       if( getModelItem().getSettings().getBoolean( UISettings.AUTO_VALIDATE_REQUEST ) )
533       {
534          boolean result = requestEditor.saveDocument( true );
535          if( !result && getModelItem().getSettings().getBoolean( UISettings.ABORT_ON_INVALID_REQUEST ) )
536          {
537             statusBar.setInfo( "Cancelled request due to invalid content" );
538             return false;
539          }
540       }
541       else
542       {
543          requestEditor.saveDocument( false );
544       }
545 
546       setEnabled( false );
547       cancelButton.setEnabled( AbstractHttpRequestDesktopPanel.this.submit != null );
548       return true;
549    }
550 
551    public void afterSubmit( Submit submit, SubmitContext context )
552    {
553       if( submit.getRequest() != request )
554          return;
555 
556       Status status = submit.getStatus();
557       HttpResponse response = (HttpResponse) submit.getResponse();
558       if( status != Status.CANCELED )
559       {
560          request.setResponse( response, context );
561       }
562 
563       if( hasClosed )
564       {
565          request.removeSubmitListener(this);
566          return;
567       }
568 
569       cancelButton.setEnabled( false );
570       setEnabled( true );
571 
572       String message = null;
573       String infoMessage = null;
574       String requestName = request.getOperation() == null ? request.getName() :
575               request.getOperation().getInterface().getName() + "." + request.getOperation().getName()
576                       + ":" + request.getName();
577 
578       if( status == Status.CANCELED )
579       {
580          message = "CANCELED";
581          infoMessage = "[" + requestName + "] - CANCELED";
582       }
583       else
584       {
585          if( status == Status.ERROR || response == null )
586          {
587             message = "Error getting response; " + submit.getError();
588             infoMessage = "Error getting response for [" + requestName + "]; " + submit.getError();
589          }
590          else
591          {
592             message = "response time: " + response.getTimeTaken() + "ms (" + response.getContentLength() + " bytes)";
593             infoMessage = "Got response for [" + requestName + "] in " + response.getTimeTaken() + "ms ("
594                     + response.getContentLength() + " bytes)";
595 
596             if( !splitButton.isEnabled() )
597                requestTabs.setSelectedIndex( 1 );
598 
599             responseEditor.requestFocus();
600          }
601       }
602 
603       logMessages( message, infoMessage );
604 
605       if( getModelItem().getSettings().getBoolean( UISettings.AUTO_VALIDATE_RESPONSE ) )
606          responseEditor.getSourceEditor().validate();
607 
608       AbstractHttpRequestDesktopPanel.this.submit = null;
609    }
610 
611    protected void logMessages( String message, String infoMessage )
612    {
613       log.info( infoMessage );
614       statusBar.setInfo( message );
615    }
616 
617    public boolean onClose( boolean canCancel )
618    {
619       if( canCancel )
620       {
621          if( submit != null && submit.getStatus() == Submit.Status.RUNNING )
622          {
623             Boolean retVal = UISupport.confirmOrCancel( "Cancel request before closing?", "Closing window" );
624             if( retVal == null )
625                return false;
626 
627             if( retVal.booleanValue() && submit.getStatus() == Submit.Status.RUNNING )
628             {
629                submit.cancel();
630             }
631 
632             hasClosed = true;
633          }
634          else
635          {
636             request.removeSubmitListener( this );
637          }
638       }
639       else if( submit != null && submit.getStatus() == Submit.Status.RUNNING )
640       {
641          submit.cancel();
642          hasClosed = true;
643       }
644       else
645       {
646          request.removeSubmitListener( this );
647       }
648 
649       request.removePropertyChangeListener( this );
650       requestEditor.saveDocument( false );
651 
652       if( responseEditor != null )
653          responseEditor.getParent().remove( responseEditor );
654 
655       requestEditor.getParent().remove( requestEditor );
656       requestSplitPane.removeAll();
657 
658       return release();
659    }
660 
661    @Override
662    protected boolean release()
663    {
664       endpointsModel.release();
665       requestEditor.release();
666 
667       if( responseEditor != null )
668          responseEditor.release();
669 
670       return super.release();
671    }
672 
673    public boolean dependsOn( ModelItem modelItem )
674    {
675       return request.dependsOn( modelItem );
676    }
677 
678    private final class ChangeToTabsAction extends AbstractAction
679    {
680       public ChangeToTabsAction()
681       {
682          putValue( Action.SMALL_ICON, UISupport.createImageIcon( "/toggle_tabs.gif" ) );
683          putValue( Action.SHORT_DESCRIPTION, "Toggles to tab-based layout" );
684       }
685 
686       public void actionPerformed( ActionEvent e )
687       {
688          if( splitButton.isEnabled() )
689          {
690             splitButton.setEnabled( false );
691             removeContent( requestSplitPane );
692             setContent( requestTabPanel );
693             requestTabs.addTab( "Request", requestEditor );
694 
695             if( responseEditor != null )
696                requestTabs.addTab( "Response", responseEditor );
697 
698             if( responseHasFocus )
699             {
700                requestTabs.setSelectedIndex( 1 );
701                requestEditor.requestFocus();
702             }
703          }
704          else
705          {
706             int selectedIndex = requestTabs.getSelectedIndex();
707 
708             splitButton.setEnabled( true );
709             removeContent( requestTabPanel );
710             setContent( requestSplitPane );
711             requestSplitPane.setTopComponent( requestEditor );
712             if( responseEditor != null )
713                requestSplitPane.setBottomComponent( responseEditor );
714             requestSplitPane.setDividerLocation( 0.5 );
715 
716             if( selectedIndex == 0 || responseEditor == null )
717                requestEditor.requestFocus();
718             else
719                responseEditor.requestFocus();
720          }
721 
722          revalidate();
723       }
724    }
725 
726    public void setContent( JComponent content )
727    {
728       add( content, BorderLayout.CENTER );
729    }
730 
731    public void removeContent( JComponent content )
732    {
733       remove( content );
734    }
735 
736    protected void onSubmit()
737    {
738       if( submit != null && submit.getStatus() == Submit.Status.RUNNING )
739       {
740          if( UISupport.confirm( "Cancel current request?", "Submit Request" ) )
741          {
742             submit.cancel();
743          }
744          else
745             return;
746       }
747 
748       try
749       {
750          submit = doSubmit();
751       }
752       catch( SubmitException e1 )
753       {
754          SoapUI.logError( e1 );
755       }
756    }
757 
758    protected void onCancel()
759    {
760       if( submit == null )
761          return;
762 
763       cancelButton.setEnabled( false );
764       submit.cancel();
765       setEnabled( true );
766       submit = null;
767    }
768 
769    public boolean isHasClosed()
770    {
771       return hasClosed;
772    }
773 }