001    /*
002     * This file is part of the Echo Point Project.  This project is a
003     * collection of Components that have extended the Echo Web Application
004     * Framework Version 3.
005     *
006     * Version: MPL 1.1
007     *
008     * The contents of this file are subject to the Mozilla Public License Version
009     * 1.1 (the "License"); you may not use this file except in compliance with
010     * the License. You may obtain a copy of the License at
011     * http://www.mozilla.org/MPL/
012     *
013     * Software distributed under the License is distributed on an "AS IS" basis,
014     * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
015     * for the specific language governing rights and limitations under the
016     * License.
017     */
018    package echopoint.tucana;
019    
020    import echopoint.ProgressBar;
021    import echopoint.internal.AbstractContainer;
022    import echopoint.tucana.event.DefaultUploadListener;
023    import echopoint.tucana.event.InvalidContentTypeEvent;
024    import echopoint.tucana.event.UploadCallback;
025    import echopoint.tucana.event.UploadCancelEvent;
026    import echopoint.tucana.event.UploadEvent;
027    import echopoint.tucana.event.UploadFailEvent;
028    import echopoint.tucana.event.UploadFinishEvent;
029    import echopoint.tucana.event.UploadProgressEvent;
030    import echopoint.tucana.event.UploadStartEvent;
031    import nextapp.echo.app.ApplicationInstance;
032    import nextapp.echo.app.Component;
033    import nextapp.echo.app.ImageReference;
034    import nextapp.echo.app.TaskQueueHandle;
035    import nextapp.echo.app.event.ActionEvent;
036    import nextapp.echo.app.event.ActionListener;
037    
038    import java.util.Collections;
039    import java.util.HashSet;
040    import java.util.Set;
041    
042    /**
043     * The file upload selector component.  This component is a re-implementation
044     * of the original <i>tucana file upload selector</i> component for Echo2.
045     *
046     * <p><b>Note:</b> It is critical that this component be set absolute height
047     * and width properties.  Percentage based values can cause problems if using
048     * a progress bar.</p>
049     *
050     * <p>The following code shows sample usage of this component:</p>
051     * <pre>
052     *  import nextapp.echo.app.Border;
053     *  import nextapp.echo.app.Color;
054     *  import echopoint.tucana.ButtonMode;
055     *  import echopoint.tucana.ButtonDisplay;
056     *  import echopoint.tucana.FileUploadSelector;
057     *  import echopoint.tucana.ProgressBar;
058     *  import static echopoint.tucana.UploadSPI;
059     *  import echopoint.tucana.event.DefaultUploadCallback;
060     *
061     *    ...
062     *    final FileUploadSelector selector = new FileUploadSelector();
063     *    selector.setButtonMode( ButtonMode.image );
064     *    selector.setButtonDisplayMode( ButtonDisplay.right );
065     *    selector.setInputSize( 20 );
066     *    selector.setUploadSizeLimit( NO_SIZE_LIMIT );
067     *    selector.setBackground( new Color( 0xa1a1a1 ) );
068     *    selector.setBorder( new Border( 1, Color.BLUE, Border.STYLE_GROOVE ) );
069     *    selector.setProgressBar( new ProgressBar() );
070     *    selector.setUploadCallback( new DefaultUploadCallback( new File( "/tmp" ) ) );
071     *    selector.addActionListener( ... );
072     *
073     *    // Allow only the following types of files to be uploaded.
074     *    final HashSet&lt;String&gt; set = new HashSet&lt;String&gt;();
075     *    set.add( "image/gif" );
076     *    set.add( "image/jpeg" );
077     *    set.add( "image/png" );
078     *    selector.setContentTypeFilter( set );
079     *
080     *    parent.add( selector );
081     * </pre>
082     *
083     * <p>There are two different (equivalent) ways to process a completed upload:
084     * <ol>
085     *   <li>{@link echopoint.tucana.event.UploadCallback} based.  It is best
086     *     to use a sub-class of either {@link echopoint.tucana.event.DefaultUploadCallback}
087     *     or {@link echopoint.tucana.event.UploadCallbackAdapter} since they
088     *     ensure removal of the task queue used to enqueue processing from the
089     *     call back methods to the UI thread.  If you sub-class please note that
090     *     calls to {@code super.uploadXxx} methods should be invoked at the end
091     *     of your over-ridden implementation and not at the top.  The super class
092     *     implementations destroys the queue and sets it to {@code null}.
093     *     If you implement your own handler
094     *     please make sure that you invoke {@link FileUploadSelector#removeTaskQueue()}
095     *     at the end of your handler methods.  The queue will be automatically
096     *     cleaned up when the component is removed from the hierarchy, so it may
097     *     be acceptable to not invoke {@code removeTaskQueue} depending upon how
098     *     your application logic works.</li>
099     *   <li>{@link nextapp.echo.app.event.ActionListener} based.  Two types of
100     *     events are recieved by the event handler:
101     *     <ol>
102       *     <li>{@link #START_ACTION} - The action command that indicates that
103     *         the file upload has commenced.</li>
104     *     <li>{@link #COMPLETE_ACTION} - The action command that indicates that
105     *         the file upload has finished.</li>
106     *     </ol>
107     *     <p>Please note that it is safest to check on the command value rather
108     *     than check one and default action for the other value.</p>
109     *   </li>
110     * </ol>
111     *
112     * <p>The following callback class and associated runnable may be used to
113     * update the UI after an upload.</p>
114     * <pre>
115     *  import nextapp.echo.app.ApplicationInstance;
116     *  import nextapp.echo.app.Component;
117     *  import nextapp.echo.app.TaskQueueHandle;
118     *  import echopoint.DirectHtml;
119     *  import echopoint.tucana.event.UploadCallbackAdapter;
120     *
121     *  public class UploadCallbackImpl extends UploadCallbackAdapter
122     *  {
123     *    private static final long serialVersionUID = 1l;
124     *    private final Component parent;
125     *
126     *    private UploadCallbackImpl( final Component parent )
127     *    {
128     *      this.parent = parent;
129     *    }
130     *
131     *    &#64;Override
132     *    public void uploadSucceeded( final UploadFinishEvent event )
133     *    {
134     *      final StringBuilder builder = new StringBuilder( 128 );
135     *      builder.append( "Upload of file: &lt;b&gt;" );
136     *      builder.append( event.getFileName() );
137     *      builder.append( "&lt;/b&gt; succeeded.  File size is: &lt;i&gt;");
138     *      builder.append( event.getFileSize() / 1000 );
139     *      builder.append( "&lt;/i&gt; kilobytes." );
140     *      final DirectHtml html = new DirectHtml( builder.toString() );
141     *      parent.add( child );
142     *      super.uploadSucceeded( event );
143     *    }
144     *
145     *    &#64;Override
146     *    public void uploadFailed( final UploadFailEvent event )
147     *    {
148     *      final StringBuilder builder = new StringBuilder( 128 );
149     *      builder.append( "File upload failed." );
150     *      if ( event.getFileName() != null )
151     *      {
152     *        builder.append( "  Failed file: &lt;i&gt;" );
153     *        builder.append( event.getFileName() );
154     *        builder.append( "&lt;/i&gt;." );
155     *      }
156     *
157     *      if ( event.getException() != null )
158     *      {
159     *        builder.append( "Exception: &lt;p&gt;&lt;pre&gt;" );
160     *        builder.append( event.getException().toString() );
161     *        builder.append( "&lt;/pre&gt;&lt;/p&gt;" );
162     *      }
163     *
164     *      final DirectHtml html = new DirectHtml( builder.toString() );
165     *      parent.add( child );
166     *      super.uploadFailed( event );
167     *    }
168     * </pre>
169     *
170     * <p>Processing UI updates after upload completion is much simpler (and
171     * more network friendly) when using an action listener.  A simple action
172     * listener may be configured as follows:</p>
173     * <pre>
174     *  import nextapp.echo.app.Component;
175     *  import nextapp.echo.app.event.ActionEvent;
176     *  import nextapp.echo.app.event.ActionListener;
177     *  import echopoint.tucana.FileUploadSelector;
178     *  import echopoint.tucana.event.UploadCallback;
179     *  import echopoint.tucana.event.UploadFinishEvent;
180     *  import echopoint.DirectHtml;
181     *
182     *  public class FinishListener implements ActionListener
183     *  {
184     *    private static final long serialVersionUID = 1l;
185     *
186     *    public void actionPerformed( final ActionEvent event )
187     *    {
188     *      final FileUploadSelector upload = ( FileUploadSelector) event.getSource();
189     *      final UploadCallback callback = upload.getUploadCallback();
190     *      if ( callback != null )
191     *      {
192     *        final StringBuilder builder = new StringBuilder( 128 );
193     *        final boolean success = ( callback.getEvent() instanceof UploadFinishEvent );
194     *
195     *        if ( success )
196     *        {
197     *          builder.append( "Upload of file: &lt;b&gt;" );
198     *          builder.append( callback.getEvent().getFileName() );
199     *          builder.append( "&lt;/b&gt; succeeded.  File size is: &lt;i&gt;");
200     *          builder.append( callback.getEvent().getFileSize() / 1000 );
201     *          builder.append( "&lt;/i&gt; kilobytes." );
202     *        }
203     *        else
204     *        {
205     *          builder.append( "Upload " );
206     *          if ( callback.getEvent() != null )
207     *          {
208     *            builder.append( " of file: &lt;b&gt;" );
209     *            builder.append( callback.getEvent().getFileName() );
210     *            builder.append( "&lt;/b&gt;" );
211     *          }
212     *
213     *          builder.append( " failed/cancelled." );
214     *        }
215     *
216     *        upload.getParent().add( new DirectHtml( builder.toString() ) );
217     *
218     *        if ( upload.getProgressBar() != null )
219     *        {
220     *          upload.getProgressBar().setText( ( success ) ?
221     *              "Finished upload!" : "Cancelled upload!" );
222     *        }
223     *      }
224     *    }
225     *  }
226     * </pre>
227     *
228     * <p><b>Note:</b> Development of this component was sponsored by <a
229     * href='http://tcnbroadcasting.com/index.jsp' target='_top'>TCN
230     * Broadcasting</a>.  We are grateful for their support and sponsorship.</p>
231     *
232     * @author jvolkman (Echo2), Rakesh 2008-11-2
233     * @version $Id: FileUploadSelector.java 204 2009-05-20 18:50:57Z sptrakesh $
234     */
235    public class FileUploadSelector extends AbstractContainer
236    {
237      private static final long serialVersionUID = 1l;
238    
239      public static final String PROPERTY_BUTTON_TEXT_UPLOAD = "buttonTextUpload";
240    
241      public static final String PROPERTY_BUTTON_TEXT_CANCEL = "buttonTextCancel";
242    
243      public static final String PROPERTY_BUTTON_TEXT_WAIT = "buttonTextWait";
244    
245      public static final String PROPERTY_BUTTON_IMAGE_UPLOAD = "buttonImageUpload";
246    
247      public static final String PROPERTY_BUTTON_IMAGE_CANCEL = "buttonImageCancel";
248    
249      public static final String PROPERTY_BUTTON_IMAGE_WAIT = "buttonImageWait";
250    
251      public static final String PROPERTY_BUTTON_MODE = "buttonMode";
252    
253      public static final String PROPERTY_BUTTON_DISPLAY = "buttonDisplay";
254    
255      public static final String PROPERTY_CANCEL_ENABLED = "cancelEnabled";
256    
257      /**
258       * The size (indicates number of characters) for the input field.
259       * This is the same as the old <code>PROPERTY_WIDTH_SIZE</code>.
260       * This property is best styled.
261       */
262      public static final String PROPERTY_INPUT_SIZE = "inputSize";
263    
264      /**
265       * The interval (in milliseconds) at which the progress service is
266       * to be polled for updates.  Note that upload completion and cancellation
267       * are inferred through the response from the service.  This property
268       * is best styled.
269       */
270      public static final String PROPERTY_POLLING_INTERVAL = "pollingInterval";
271    
272      /** The maximum size of file that is allowed to be uploaded. */
273      public static final String PROPERTY_UPLOAD_SIZE_LIMIT = "uploadSizeLimit";
274    
275      /**
276       * The name of the action that is fired by the client upon completion
277       * (regardless of complete or cancel) of the upload process.
278       *
279       * {@value}
280       */
281      public static final String COMPLETE_ACTION = "complete";
282    
283      /**
284       * The name of the action that is fired by the client upon start
285       * of the upload process.
286       *
287       * {@value}
288       */
289      public static final String START_ACTION = "start";
290    
291      /**
292       * The callback handler that will be notified of the progress of the file
293       * upload process.
294       */
295      private UploadCallback callback = null;
296    
297      /**
298       * The allowed content-type(s) for the upload.  Uploads of other types of
299       * files will be rejected.
300       */
301      private Set<String> contentTypeFilter = new HashSet<String>();
302    
303      /**
304       * A task queue that may be used to perform UI updates from call back
305       * handlers.  Automatically cleaned up in {@link #dispose}.
306       */
307      private TaskQueueHandle taskQueue;
308    
309      /**
310       * Default constructor.  Add {@link echopoint.tucana.event.DefaultUploadListener}
311       * as a listener for this component to initialise the {@link #taskQueue}.
312       */
313      public FileUploadSelector()
314      {
315        addActionListener( new DefaultUploadListener() );
316      }
317    
318      /**
319       * Sets the upload button image.
320       *
321       * @param image The new upload button image.
322       */
323      public void setButtonUploadImage( final ImageReference image )
324      {
325        set( PROPERTY_BUTTON_IMAGE_UPLOAD, image );
326      }
327    
328      /**
329       * Gets the current upload button image.
330       *
331       * @return The current upload button image.
332       */
333      public ImageReference getButtonUploadImage()
334      {
335        return (ImageReference) get( PROPERTY_BUTTON_IMAGE_UPLOAD );
336      }
337    
338      /**
339       * Sets the cancel button image.
340       *
341       * @param image The new cancel button image.
342       */
343      public void setButtonCancelImage( final ImageReference image )
344      {
345        set( PROPERTY_BUTTON_IMAGE_CANCEL, image );
346      }
347    
348      /**
349       * Gets the current cancel button image.
350       *
351       * @return The current cancel button image.
352       */
353      public ImageReference getButtonCancelImage()
354      {
355        return (ImageReference) get( PROPERTY_BUTTON_IMAGE_CANCEL );
356      }
357    
358      /**
359       * Sets the wait button image.
360       *
361       * @param image The new wait button image.
362       */
363      public void setButtonWaitImage( final ImageReference image )
364      {
365        set( PROPERTY_BUTTON_IMAGE_WAIT, image );
366      }
367    
368      /**
369       * Gets the current wait button image.
370       *
371       * @return The current wait button image.
372       */
373      public ImageReference getButtonWaitImage()
374      {
375        return (ImageReference) get( PROPERTY_BUTTON_IMAGE_WAIT );
376      }
377    
378      /**
379       * Sets the upload button text.
380       *
381       * @param text The new upload button text.
382       */
383      public void setButtonUploadText( final String text )
384      {
385        set( PROPERTY_BUTTON_TEXT_UPLOAD, text );
386      }
387    
388      /**
389       * Gets the current upload button text.
390       *
391       * @return The current upload button text.
392       */
393      public String getButtonUploadText()
394      {
395        return (String) get( PROPERTY_BUTTON_TEXT_UPLOAD );
396      }
397    
398      /**
399       * Sets the cancel button text.
400       *
401       * @param text The new cancel button text.
402       */
403      public void setButtonCancelText( final String text )
404      {
405        set( PROPERTY_BUTTON_TEXT_CANCEL, text );
406      }
407    
408      /**
409       * Gets the current cancel button text.
410       *
411       * @return The current cancel button text.
412       */
413      public String getButtonCancelText()
414      {
415        return (String) get( PROPERTY_BUTTON_TEXT_CANCEL );
416      }
417    
418      /**
419       * Sets the wait button text.
420       *
421       * @param text The new wait button text.
422       */
423      public void setButtonWaitText( final String text )
424      {
425        set( PROPERTY_BUTTON_TEXT_WAIT, text );
426      }
427    
428      /**
429       * Gets the current wait button text.
430       *
431       * @return The current wait button text.
432       */
433      public String getButtonWaitText()
434      {
435        return (String) get( PROPERTY_BUTTON_TEXT_WAIT );
436      }
437    
438      /**
439       * Sets the upload button display mode
440       *
441       * @param mode The new upload button display mode
442       */
443      public void setButtonMode( final ButtonMode mode )
444      {
445        set( PROPERTY_BUTTON_MODE, mode );
446      }
447    
448      /**
449       * Get the current upload button display mode.
450       *
451       * @return The current display mode, or {@link ButtonMode#submit} if not set.
452       */
453      public ButtonMode getButtonMode()
454      {
455        final ButtonMode mode = (ButtonMode) get( PROPERTY_BUTTON_MODE );
456        return ( mode == null ) ? ButtonMode.submit : mode;
457      }
458    
459      /**
460       * Set the button display mode for the input submit button.
461       *
462       * @param mode Set to configure the location of the submit button.
463       */
464      public void setButtonDisplayMode( final ButtonDisplay mode )
465      {
466        set( PROPERTY_BUTTON_DISPLAY, mode );
467      }
468    
469      /**
470       * Get the configuration for display of the submit button.
471       *
472       * @return The current mode, or {@link ButtonDisplay#auto} if not set.
473       */
474      public ButtonDisplay getButtonDisplayMode()
475      {
476        final ButtonDisplay display = (ButtonDisplay) get( PROPERTY_BUTTON_DISPLAY );
477        return ( display == null ) ? ButtonDisplay.auto : display;
478      }
479    
480      /**
481       * Mutator for property 'cancelEnabled'.
482       *
483       * @param enabled Value to set for property 'cancelEnabled'.
484       */
485      public void setCancelEnabled( final boolean enabled )
486      {
487        set( PROPERTY_CANCEL_ENABLED, enabled );
488      }
489    
490      /**
491       * Accessor for property 'cancelEnabled'.
492       *
493       * @return Value for property 'cancelEnabled'.
494       */
495      public boolean isCancelEnabled()
496      {
497        return ( (Boolean) get( PROPERTY_CANCEL_ENABLED ) );
498      }
499    
500      /**
501       * Return the value of the {@link #PROPERTY_INPUT_SIZE} property.
502       *
503       * @return The size of the input text field.
504       */
505      public int getInputSize()
506      {
507        return (Integer) get( PROPERTY_INPUT_SIZE );
508      }
509    
510      /**
511       * Set the value of the {@link #PROPERTY_INPUT_SIZE} property.
512       *
513       * @param size The size of the input text field.
514       */
515      public void setInputSize( final int size )
516      {
517        set( PROPERTY_INPUT_SIZE, size );
518      }
519    
520      /**
521       * Return the value of the {@link #PROPERTY_POLLING_INTERVAL} property.
522       *
523       * @return The time interval at which the progress service will be polled
524       */
525      public int getPollingInterval()
526      {
527        return (Integer) get( PROPERTY_POLLING_INTERVAL );
528      }
529    
530      /**
531       * Set the value of the {@link #PROPERTY_POLLING_INTERVAL} property.
532       *
533       * @param interval The time interval at which the progress service will be polled
534       */
535      public void setPollingInterval( final int interval )
536      {
537        set( PROPERTY_POLLING_INTERVAL, interval );
538      }
539    
540      /**
541       * Return the value of the {@link #PROPERTY_UPLOAD_SIZE_LIMIT} property.
542       * If not set the implementation defaults to
543       * {@link echopoint.tucana.AbstractFileUploadProvider#DEFAULT_UPLOAD_SIZE_LIMIT}.
544       *
545       * @return The maximum size in bytes that is allowed to be uploaded.
546       */
547      public long getUploadSizeLimit()
548      {
549        final Object obj = get( PROPERTY_UPLOAD_SIZE_LIMIT );
550        return ( obj == null ) ? 0 : (Long) obj;
551      }
552    
553      /**
554       * Set the value of the {@link #PROPERTY_UPLOAD_SIZE_LIMIT} property.
555       * Specify {@link echopoint.tucana.UploadSPI#NO_SIZE_LIMIT} to prevent
556       * size checking.
557       *
558       * @param limit The maximum size in bytes that is allowed.
559       */
560      public void setUploadSizeLimit( final long limit )
561      {
562        set( PROPERTY_UPLOAD_SIZE_LIMIT, limit );
563      }
564    
565      /**
566       * Set the callback used when a file is uploaded.
567       *
568       * @param callback The upload callback.
569       */
570      public void setUploadCallback( final UploadCallback callback )
571      {
572        this.callback = callback;
573      }
574    
575      /**
576       * Get the callback used when a file is uploaded.
577       *
578       * @return The upload callback.
579       */
580      public UploadCallback getUploadCallback()
581      {
582        return callback;
583      }
584    
585      /**
586       * Return the progress bar component configured for this component.
587       *
588       * @return The progress bar if one was added or <code>null</code>.
589       */
590      public ProgressBar getProgressBar()
591      {
592        return ( getComponentCount() > 0 ) ? (ProgressBar) getComponent( 0 ) : null;
593      }
594    
595      /**
596       * Add the specified progress bar to this component.
597       *
598       * @param progressBar The progress bar component to add.
599       */
600      public void setProgressBar( final ProgressBar progressBar )
601      {
602        add( progressBar, 0 );
603      }
604    
605      /**
606       * Accessor for property 'contentTypeFilter'.  Returns the content-types
607       * that are allowed to be uploaded by this component.
608       *
609       * @return Value for property 'contentTypeFilter'.
610       */
611      public Set<String> getContentTypeFilter()
612      {
613        return Collections.unmodifiableSet( contentTypeFilter );
614      }
615    
616      /**
617       * Mutator for property 'contentTypeFilter'.
618       *
619       * @param contentTypeFilter Value to set for property 'contentTypeFilter'.
620       */
621      public void setContentTypeFilter( final Set<String> contentTypeFilter )
622      {
623        this.contentTypeFilter.clear();
624    
625        if ( contentTypeFilter != null )
626        {
627          this.contentTypeFilter.addAll( contentTypeFilter );
628        }
629      }
630    
631      /**
632       * The only allowed sub-component is a {@link echopoint.ProgressBar}.
633       *
634       * @param child The child component that is to be added if allowed.
635       * @return Return <code>true</code> if child is an instance of {@link
636       *   echopoint.ProgressBar} and a progress bar has not already been
637       *   assigned.
638       */
639      @Override
640      public boolean isValidChild( final Component child )
641      {
642        boolean result = ( child instanceof ProgressBar );
643        return result && ( getComponentCount() < 2 );
644      }
645    
646      /**
647       * Return the task queue that may be used to enqueue asynchronous tasks
648       * (usually from callback handlers).
649       *
650       * @return Value for property 'taskQueue'.
651       */
652      public TaskQueueHandle getTaskQueue()
653      {
654        return taskQueue;
655      }
656    
657      /**
658       * Set the task queue for the component.
659       *
660       * @param taskQueue Value to set for property 'taskQueue'.
661       */
662      public void setTaskQueue( final TaskQueueHandle taskQueue )
663      {
664        this.taskQueue = taskQueue;
665      }
666    
667      /**
668       * Remove the current task queue.  It is strongly recommended that this
669       * method be used rather than wait for {@link #dispose}.
670       */
671      public void removeTaskQueue()
672      {
673        if ( taskQueue != null )
674        {
675          ApplicationInstance.getActive().removeTaskQueue( taskQueue );
676          taskQueue = null;
677        }
678      }
679    
680      /**
681       * Notifies the listener that the given event has occurred.
682       *
683       * @param e the event
684       */
685      protected void notifyCallback( final UploadEvent e )
686      {
687        if ( callback == null ) return;
688    
689        if ( e instanceof UploadCancelEvent )
690        {
691          callback.uploadCancelled( (UploadCancelEvent) e );
692        }
693        if ( e instanceof InvalidContentTypeEvent )
694        {
695          callback.uploadDisallowed( (InvalidContentTypeEvent) e );
696        }
697        else if ( e instanceof UploadFailEvent )
698        {
699          callback.uploadFailed( (UploadFailEvent) e );
700        }
701        else if ( e instanceof UploadFinishEvent )
702        {
703          callback.uploadSucceeded( (UploadFinishEvent) e );
704        }
705        else if ( e instanceof UploadProgressEvent )
706        {
707          callback.uploadProgressed( (UploadProgressEvent) e );
708        }
709        else if ( e instanceof UploadStartEvent )
710        {
711          callback.uploadStarted( (UploadStartEvent) e );
712        }
713      }
714    
715      /** {@inheritDoc} */
716      @Override
717      public void addActionListener( final ActionListener listener )
718      {
719        super.addActionListener( listener );
720      }
721    
722      /** {@inheritDoc} */
723      @Override
724      public void removeActionListener( final ActionListener listener )
725      {
726        super.removeActionListener( listener );
727      }
728    
729      /**
730       * Over-ridden to fire action events with action commands that are set to
731       * {@link #START_ACTION} or {@link #COMPLETE_ACTION} depending upon whether
732       * the event represents start of the upload or completion of the upload.
733       *
734       * @see nextapp.echo.app.Component#processInput(String, Object)
735       */
736      @Override
737      public void processInput( String name, Object value )
738      {
739        super.processInput( name, value );
740    
741        if ( COMPLETE_ACTION.equals( name ) )
742        {
743          fireActionPerformed( new ActionEvent( this, name ) );
744        }
745        else if ( START_ACTION.equals( name ) )
746        {
747          fireActionPerformed( new ActionEvent( this, name ) );
748        }
749      }
750    
751      /**
752       * Over-ridden to clean up {@link #taskQueue} if not already cleaned up.
753       *
754       * @see #removeTaskQueue()
755       * @see nextapp.echo.app.Component#dispose()
756       */
757      @Override
758      public void dispose()
759      {
760        removeTaskQueue();
761      }
762    }