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 * <!-- Definition of the command bean -->
75 * <di:bean name="commandBean" singleton="false"
76 * beanClass="com.acme.CommandBeanImpl"/>
77 * <!-- The command task used by the action -->
78 * <di:bean name="commandTask"
79 * beanClass="net.sf.jguiraffe.gui.app.CommandActionTask">
80 * <di:setProperty property="commandBeanName" value="commandBean"/>
81 * <di:setProperty property="beforeEnabler">
82 * <di:bean beanClass="net.sf.jguiraffe.gui.builder.enablers.ActionEnabler">
83 * <di:constructor>
84 * <di:param value="testAction"/>
85 * </di:constructor>
86 * </di:bean>
87 * </di:setProperty>
88 * </di:bean>
89 * <!-- The action itself -->
90 * <a:action name="testAction" text="Test action"
91 * taskBean="commandTask"/>
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 }