View Javadoc

1   /*
2    * Copyright 2006-2016 The JGUIraffe Team.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.jguiraffe.gui.app;
17  
18  import net.sf.jguiraffe.di.BeanContext;
19  import net.sf.jguiraffe.di.BeanContextClient;
20  import net.sf.jguiraffe.gui.builder.components.ComponentBuilderData;
21  import net.sf.jguiraffe.gui.builder.components.FormBuilderException;
22  import net.sf.jguiraffe.gui.builder.enablers.ElementEnabler;
23  import net.sf.jguiraffe.gui.builder.enablers.NullEnabler;
24  import net.sf.jguiraffe.gui.cmd.Command;
25  import net.sf.jguiraffe.gui.cmd.CommandQueue;
26  import net.sf.jguiraffe.gui.cmd.CommandWrapper;
27  import net.sf.jguiraffe.gui.cmd.ScheduleAware;
28  
29  /**
30   * <p>
31   * A specialized action task that executes a {@link Command} object.
32   * </p>
33   * <p>
34   * This class can be used as a task for
35   * {@link net.sf.jguiraffe.gui.builder.action.FormAction FormAction} objects.
36   * When invoked by the associated action it will put a command object in the
37   * application's command queue. This is especially useful for longer running
38   * actions that should not block the application's event dispatch thread.
39   * </p>
40   * <p>
41   * Instances can be initialized with either a concrete {@link Command} object or
42   * with the name of a command bean. In the former case the {@link Command}
43   * object will be reused each time the action is triggered. In the latter case
44   * for each invocation a bean with the specified name is queried from the
45   * current {@link BeanContext}. (This bean must implement the {@link Command}
46   * interface.)
47   * </p>
48   * <p>
49   * This class implements some support for disabling and enabling UI elements
50   * during the execution of the associated command. For instance, it may make
51   * sense to disable certain actions (e.g. menu items and/or tool bar buttons)
52   * while the command is running in background. After completion of the
53   * background task these actions can be enabled again. For this purpose two
54   * {@link ElementEnabler} objects can be set: one is invoked when the associated
55   * {@code Command} is passed to the command queue for execution. The other one
56   * is triggered at the end of the {@code Command}'s execution (in the event
57   * dispatch thread). The second {@code ElementEnabler} can be omitted if it is
58   * the exact counterpart of the first one.
59   * </p>
60   * <p>
61   * A {@code CommandActionTask} object can be fully declared in a builder script
62   * using the capabilities of the dependency injection framework and the
63   * {@link net.sf.jguiraffe.gui.builder.action.tags.ActionTaskTag ActionTaskTag}.
64   * Because this class implements the {@link BeanContextClient} interface the
65   * reference to the current {@link BeanContext} (which is required for accessing
66   * the central {@link Application} and also the command beans) will then be
67   * automatically set. If the object is created by hand, it lies in the
68   * responsibility of the developer to ensure that the reference to the
69   * {@link BeanContext} is correctly initialized. An example for an action
70   * declaration in a builder script making use of this class could look as
71   * follows:
72   *
73   * <pre>
74   * &lt;!-- Definition of the command bean --&gt;
75   * &lt;di:bean name=&quot;commandBean&quot; singleton=&quot;false&quot;
76   *   beanClass=&quot;com.acme.CommandBeanImpl&quot;/&gt;
77   * &lt;!-- The command task used by the action --&gt;
78   * &lt;di:bean name=&quot;commandTask&quot;
79   *   beanClass=&quot;net.sf.jguiraffe.gui.app.CommandActionTask&quot;&gt;
80   *   &lt;di:setProperty property=&quot;commandBeanName&quot; value=&quot;commandBean&quot;/&gt;
81   *   &lt;di:setProperty property=&quot;beforeEnabler&quot;&gt;
82   *     &lt;di:bean beanClass=&quot;net.sf.jguiraffe.gui.builder.enablers.ActionEnabler&quot;&gt;
83   *       &lt;di:constructor&gt;
84   *         &lt;di:param value=&quot;testAction&quot;/&gt;
85   *       &lt;/di:constructor&gt;
86   *     &lt;/di:bean&gt;
87   *   &lt;/di:setProperty&gt;
88   * &lt;/di:bean&gt;
89   * &lt;!-- The action itself --&gt;
90   * &lt;a:action name=&quot;testAction&quot; text=&quot;Test action&quot;
91   *   taskBean=&quot;commandTask&quot;/&gt;
92   * </pre>
93   *
94   * This script creates an action named <em>testAction</em> that is associated
95   * with a {@code CommandActionTask} object as its task. The task uses a
96   * {@code Command} object that is also defined as a bean. (Note that the command
97   * bean has the {@code singleton} attribute set to <b>false</b>, so each time
98   * the action task is executed a new instance of the command class will be
99   * created.) In this example also an {@link ElementEnabler} is defined. The way
100  * this enabler is defined, it disables the action when the task is executed and
101  * enables it again after the task's execution. (Thus the user cannot activate
102  * the action again as long as it is running.) <em>Note:</em> When executing a
103  * builder script default converters are in place. There is also a default
104  * converter which can deal with {@code ElementEnabler} objects, so you can use
105  * an abbreviated form in most cases.
106  * </p>
107  *
108  * @author Oliver Heger
109  * @version $Id: CommandActionTask.java 205 2012-01-29 18:29:57Z oheger $
110  */
111 public class CommandActionTask implements Runnable, BeanContextClient
112 {
113     /** Stores the current bean context. */
114     private BeanContext beanContext;
115 
116     /** Stores a command object. */
117     private Command command;
118 
119     /** Stores the name of the command bean. */
120     private String commandBeanName;
121 
122     /** The optional before enabler. */
123     private ElementEnabler beforeEnabler;
124 
125     /** The optional after enabler. */
126     private ElementEnabler afterEnabler;
127 
128     /**
129      * Returns the command object.
130      *
131      * @return the command object
132      */
133     public Command getCommand()
134     {
135         return command;
136     }
137 
138     /**
139      * Sets the command object. Here an object can be passed that will be reused
140      * for each invocation.
141      *
142      * @param command the command object to be used
143      */
144     public void setCommand(Command command)
145     {
146         this.command = command;
147     }
148 
149     /**
150      * Returns the name of the command bean.
151      *
152      * @return the name of the command bean
153      */
154     public String getCommandBeanName()
155     {
156         return commandBeanName;
157     }
158 
159     /**
160      * Sets the name of the command bean. If this property is set, the {@code
161      * Command} object to be executed by this task is obtained from the current
162      * {@link BeanContext} using this property as bean name. However, this
163      * property is only evaluated if no {@code Command} object was set using the
164      * {@link #setCommand(Command)} method.
165      *
166      * @param commandBeanName the name of the command bean
167      */
168     public void setCommandBeanName(String commandBeanName)
169     {
170         this.commandBeanName = commandBeanName;
171     }
172 
173     /**
174      * Returns the {@code ElementEnabler} that is invoked before the execution
175      * of this task. This method never returns <b>null</b>; if no enabler has
176      * been set explicitly, a default dummy enabler is returned.
177      *
178      * @return the {@code ElementEnabler} before the execution
179      */
180     public ElementEnabler getBeforeEnabler()
181     {
182         return (beforeEnabler != null) ? beforeEnabler : NullEnabler.INSTANCE;
183     }
184 
185     /**
186      * Sets the {@code ElementEnabler} that is invoked before the execution of
187      * this task. As soon as this task is triggered, this {@code ElementEnabler}
188      * is invoked with a value of <b>false</b> for the enabled state argument.
189      * If no after enabler was set, it is also called after the execution of the
190      * {@code Command} - this time with a value of <b>true</b> for the enabled
191      * state argument.
192      *
193      * @param beforeEnabler the {@code ElementEnabler} before the execution
194      * @see #setAfterEnabler(ElementEnabler)
195      */
196     public void setBeforeEnabler(ElementEnabler beforeEnabler)
197     {
198         this.beforeEnabler = beforeEnabler;
199     }
200 
201     /**
202      * Returns the {@code ElementEnabler} that is invoked after the execution of
203      * this task. This method never returns <b>null</b>; if no enabler has been
204      * set explicitly, a default dummy enabler is returned.
205      *
206      * @return the {@code ElementEnabler} after the execution
207      */
208     public ElementEnabler getAfterEnabler()
209     {
210         return (afterEnabler != null) ? afterEnabler : getBeforeEnabler();
211     }
212 
213     /**
214      * Sets the {@code ElementEnabler} that is invoked after the execution of
215      * this task. After the command has been executed this {@code
216      * ElementEnabler} is invoked (in the event dispatch thread) with a value of
217      * <b>true</b> for the enabled state argument. An after enabler is only
218      * necessary if the enabling/disabling is asymmetric, i.e. before the
219      * execution different elements are enabled/disabled than after the
220      * execution. If no after enabler is set, the before enabler is invoked both
221      * before and after execution. Also note that the after enabler is always
222      * called with the value <b>true</b> for the enabled state argument; if a
223      * different flag value is required, an
224      * {@link net.sf.jguiraffe.gui.builder.enablers.InverseEnabler} can be used
225      * to switch the behavior.
226      *
227      * @param afterEnabler the {@code ElementEnabler} after the execution
228      */
229     public void setAfterEnabler(ElementEnabler afterEnabler)
230     {
231         this.afterEnabler = afterEnabler;
232     }
233 
234     /**
235      * Returns the current {@code BeanContext}.
236      *
237      * @return the current {@code BeanContext}
238      */
239     public BeanContext getBeanContext()
240     {
241         return beanContext;
242     }
243 
244     /**
245      * Sets the current {@code BeanContext}. This method is usually
246      * automatically called by the dependency injection framework.
247      *
248      * @param context the current {@code BeanContext}
249      */
250     public void setBeanContext(BeanContext context)
251     {
252         beanContext = context;
253     }
254 
255     /**
256      * Executes this task. Obtains the {@code Command} object by invoking
257      * {@link #fetchCommand()} and {@link #createCommandWrapper(Command)} and
258      * passes it to the application.
259      */
260     public void run()
261     {
262         Command cmd = createCommandWrapper(fetchCommand());
263         getApplication().execute(cmd);
264     }
265 
266     /**
267      * Returns the {@code BeanContext} to use and checks whether it is defined.
268      * This method delegates to {@link #getBeanContext()} and throws an
269      * exception if no context was set.
270      *
271      * @return the current {@code BeanContext}
272      * @throws IllegalStateException if no {@code BeanContext} was set
273      */
274     protected BeanContext fetchBeanContext()
275     {
276         BeanContext ctx = getBeanContext();
277         if (ctx == null)
278         {
279             throw new IllegalStateException("No BeanContext set!");
280         }
281         return ctx;
282     }
283 
284     /**
285      * Returns a reference to the global application object. It is obtained
286      * through the current {@link BeanContext}.
287      *
288      * @return the application object
289      */
290     protected Application getApplication()
291     {
292         return Application.getInstance(fetchBeanContext());
293     }
294 
295     /**
296      * Obtains the command object to be executed. This implementation will use
297      * the command object if one was set. Otherwise it requests the command
298      * object from the current {@code BeanContext} using the bean name specified
299      * by the {@code commandBeanName} property. If neither a {@code Command}
300      * object nor the name of a {@code Command} bean was set, an
301      * {@link ApplicationRuntimeException} exception is thrown.
302      *
303      * @return the command object to use
304      * @throws ApplicationRuntimeException if the command is not specified
305      * @throws net.sf.jguiraffe.di.InjectionException if the command bean cannot
306      *         be obtained
307      */
308     protected Command fetchCommand() throws ApplicationRuntimeException
309     {
310         Command result = getCommand();
311 
312         if (result == null)
313         {
314             if (getCommandBeanName() == null)
315             {
316                 throw new ApplicationRuntimeException(
317                         "Command to be executed is undefined! "
318                                 + "Set either the command or the "
319                                 + "commandBeanName property of CommandActionTask.");
320             }
321             result = (Command) fetchBeanContext().getBean(getCommandBeanName());
322         }
323 
324         return result;
325     }
326 
327     /**
328      * Creates the {@code Command} object wrapper that is passed to the
329      * {@link Application#execute(Command)} method. This method is called by
330      * {@code run()} with the {@code Command} object returned by
331      * {@link #fetchCommand()} as argument. Because some additional tasks have
332      * to be performed (e.g. invoking the {@code ElementEnabler}) the {@code
333      * Command} managed by this task is not directly executed. Instead, a
334      * wrapper is created around this {@code Command}, which takes care about
335      * these tasks. This method creates this wrapper.
336      *
337      * @param actualCommand the {@code Command} object implementing the actual
338      *        logic to be executed
339      * @return a {@code Command} wrapping the actual command and performing
340      *         additional housekeeping tasks
341      */
342     protected Command createCommandWrapper(Command actualCommand)
343     {
344         return new CmdActionTaskWrapper(actualCommand);
345     }
346 
347     /**
348      * Helper method for invoking an {@code ElementEnabler}. The
349      * {@link ComponentBuilderData} object required for the invocation is
350      * obtained from the {@code BeanContext}. If the {@code ElementEnabler}
351      * throws an exception, it is re-thrown as a runtime exception.
352      *
353      * @param enabler the enabler to invoke
354      * @param state the enabled state
355      * @throws ApplicationRuntimeException if the enabler throws an exception
356      * @throws net.sf.jguiraffe.di.InjectionException if the component builder
357      *         data bean cannot be obtained
358      */
359     private void invokeElementEnabler(ElementEnabler enabler, boolean state)
360     {
361         ComponentBuilderData compData = (ComponentBuilderData) fetchBeanContext()
362                 .getBean(ComponentBuilderData.KEY_COMPONENT_BUILDER_DATA);
363         try
364         {
365             enabler.setEnabledState(compData, state);
366         }
367         catch (FormBuilderException fbex)
368         {
369             throw new ApplicationRuntimeException(
370                     "Element enabler threw an exception", fbex);
371         }
372     }
373 
374     /**
375      * A command wrapper implementation that wraps the command to be executed by
376      * this task. This wrapper class performs some housekeeping tasks before and
377      * after the execution of the actual command.
378      */
379     private class CmdActionTaskWrapper extends CommandWrapper implements
380             ScheduleAware
381     {
382         /**
383          * Creates a new instance of {@code CmdActionTaskWrapper} and sets the
384          * command to be wrapped.
385          *
386          * @param wrappedCmd the wrapped command
387          */
388         public CmdActionTaskWrapper(Command wrappedCmd)
389         {
390             super(wrappedCmd);
391         }
392 
393         /**
394          * Notifies this command that it was passed to a command queue. This
395          * implementation invokes the before enabler.
396          *
397          * @param queue the command queue (unused)
398          */
399         public void commandScheduled(CommandQueue queue)
400         {
401             invokeElementEnabler(getBeforeEnabler(), false);
402         }
403 
404         /**
405          * Performs GUI updates after the execution of the command. The returned
406          * object invokes the GUI updater of the wrapped command if any. It also
407          * calls the after enabler.
408          *
409          * @return a {@code Runnable} object for updating the GUI
410          */
411         @Override
412         public Runnable getGUIUpdater()
413         {
414             final Runnable wrappedUpdater = getWrappedCommand().getGUIUpdater();
415             return new Runnable()
416             {
417                 public void run()
418                 {
419                     if (wrappedUpdater != null)
420                     {
421                         wrappedUpdater.run();
422                     }
423                     invokeElementEnabler(getAfterEnabler(), true);
424                 }
425             };
426         }
427     }
428 }