001 /*
002 * Copyright 2002-2004 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package org.springframework.richclient.command;
017
018 import org.apache.commons.logging.Log;
019 import org.apache.commons.logging.LogFactory;
020 import org.springframework.beans.factory.BeanNameAware;
021 import org.springframework.beans.factory.FactoryBean;
022 import org.springframework.beans.factory.InitializingBean;
023 import org.springframework.richclient.application.PropertyNotSetException;
024 import org.springframework.richclient.command.config.CommandConfigurer;
025 import org.springframework.richclient.core.SecurityControllable;
026 import org.springframework.richclient.util.Assert;
027 import org.springframework.util.StringUtils;
028
029 import java.awt.*;
030
031 /**
032 * A {@link FactoryBean} that produces a {@link CommandGroup}.
033 *
034 * <p>
035 * Use of this bean simplifies the process of building up complex nested command
036 * groups such as the main menu of an application window, a toolbar or popup
037 * menus. The main property of interest when creating a bean definition for this
038 * class is the {@code members} list. This list defines the members of the
039 * command group that will be produced by the factory. The objects contained in
040 * this list can be instances of the actual command or they can be strings that
041 * represent the identifier of the command. Some strings have special meaning:
042 *
043 * <ul>
044 * <li>{@value #GLUE_MEMBER_CODE}: Represents a 'glue' component between
045 * command group members.</li>
046 * <li>{@value #SEPARATOR_MEMBER_CODE}: Represents a separator between command
047 * group members.</li>
048 * </ul>
049 * </p>
050 *
051 * @author Keith Donald
052 */
053 public class CommandGroupFactoryBean implements BeanNameAware, FactoryBean, SecurityControllable, InitializingBean {
054
055 /**
056 * The string that represents a glue component, to be used between other
057 * members of the command group.
058 */
059 public static final String GLUE_MEMBER_CODE = "glue";
060
061 /**
062 * The string that represents a separator between commands in the command
063 * group.
064 */
065 public static final String SEPARATOR_MEMBER_CODE = "separator";
066
067 /**
068 * The string prefix that indicates a command group member that is a
069 * command.
070 */
071 public static final String COMMAND_MEMBER_PREFIX = "command:";
072
073 /**
074 * The string prefix that indicates a command group member that is another
075 * command group.
076 */
077 public static final String GROUP_MEMBER_PREFIX = "group:";
078
079 /** Class logger, available to subclasses. */
080 protected Log logger = LogFactory.getLog(getClass());
081
082 private String groupId;
083
084 private CommandRegistry commandRegistry;
085
086 private CommandConfigurer commandConfigurer;
087
088 private Object[] members;
089
090 private boolean exclusive;
091
092 private boolean allowsEmptySelection;
093
094 private CommandGroup commandGroup;
095
096 private String securityControllerId;
097
098 /**
099 * Creates a new uninitialized {@code CommandGroupFactoryBean}. If created
100 * by the Spring IoC container, the {@code groupId} assigned to this
101 * instance will be the bean name of the bean as declared in the bean
102 * definition file. If using this constructor, a non-null list of command
103 * group members must be provided by calling the
104 * {@link #setMembers(Object[])} method before this instance is used.
105 */
106 public CommandGroupFactoryBean() {
107 // do nothing
108 }
109
110 /**
111 * Creates a new {@code CommandGroupFactoryBean} with the given group ID and
112 * command group members.
113 *
114 * @param groupId The identifier that will be assigned to the command group
115 * produced by this factory. Note that if this instance is created by a
116 * Spring IoC container, the group ID provided here will be overwritten by
117 * the bean name of this instance's bean definition.
118 *
119 * @param members The collection of objects that specify the members of the
120 * command group. These objects are expected to be either instances of
121 * {@link AbstractCommand} or strings. See the class documentation for
122 * details on how these strings will be interpreted. Must not be null.
123 *
124 * @throws IllegalArgumentException if {@code members} is null.
125 */
126 public CommandGroupFactoryBean(String groupId, Object[] members) {
127 this(groupId, null, null, members);
128 }
129
130 /**
131 * Creates a new {@code CommandGroupFactoryBean}.
132 *
133 * @param groupId The value to be used as the command identifier of the
134 * command group produced by this factory.
135 * @param commandRegistry The registry that will be used to retrieve the
136 * actual instances of the command group members as specified in
137 * {@code members}.
138 * @param members The collection of objects that specify the members of the
139 * command group. These objects are expected to be either instances of
140 * {@link AbstractCommand} or strings. See the class documentation for
141 * details on how these strings will be interpreted. Must not be null.
142 *
143 * @throws IllegalArgumentException if {@code members} is null.
144 */
145 public CommandGroupFactoryBean(String groupId, CommandRegistry commandRegistry, Object[] members) {
146 this(groupId, commandRegistry, null, members);
147 }
148
149 /**
150 * Creates a new {@code CommandGroupFactoryBean}.
151 *
152 * @param groupId The value to be used as the command identifier of the
153 * command group produced by this factory.
154 * @param commandRegistry The registry that will be used to retrieve the
155 * actual instances of the command group members as specified in
156 * {@code members}.
157 * @param members The collection of objects that specify the members of the
158 * command group. These objects are expected to be either instances of
159 * {@link AbstractCommand} or strings. See the class documentation for
160 * details on how these strings will be interpreted. Must not be null.
161 * @param commandConfigurer The object that will be used to configure the
162 * command objects contained in this factory's command group.
163 *
164 * @throws IllegalArgumentException if {@code members} is null.
165 */
166 public CommandGroupFactoryBean(String groupId, CommandRegistry commandRegistry,
167 CommandConfigurer commandConfigurer, Object[] members) {
168
169 Assert.required(members, "members");
170
171 this.groupId = groupId;
172 this.commandRegistry = commandRegistry;
173 this.members = members;
174 this.commandConfigurer = commandConfigurer;
175
176 }
177
178 /**
179 * {@inheritDoc}
180 */
181 public void afterPropertiesSet() {
182 PropertyNotSetException.throwIfNull(this.members, "members", getClass());
183 }
184
185 /**
186 * Sets the registry that will be used to retrieve the actual instances of
187 * the command group members as specified in the encoded members collection.
188 *
189 * @param commandRegistry The registry containing commands for the command
190 * group produced by this factory. May be null.
191 */
192 public void setCommandRegistry(CommandRegistry commandRegistry) {
193 this.commandRegistry = commandRegistry;
194 }
195
196 /**
197 * @return commandRegistry containing commands for this command group.
198 */
199 protected CommandRegistry getCommandRegistry() {
200 return this.commandRegistry;
201 }
202
203 /**
204 * Sets the object that will be used to configure the command objects in the
205 * command groups produced by this factory.
206 *
207 * @param configurer The command configurer, may be null.
208 */
209 public void setCommandConfigurer(CommandConfigurer configurer) {
210 this.commandConfigurer = configurer;
211 }
212
213 /**
214 * Sets the collection of objects that specify the members of the command
215 * group produced by this factory. The objects in {@code members} are
216 * expected to be either instances of {@link AbstractCommand} or strings.
217 * See the class documentation for details on how these strings will be
218 * interpreted.
219 *
220 * @param members The (possibly) encoded representation of the command group
221 * members. Must not be null.
222 *
223 * @throws IllegalArgumentException if {@code members} is null.
224 */
225 public final void setMembers(Object[] members) {
226 Assert.required(members, "members");
227 this.members = members;
228 }
229
230 /**
231 * @return the possibly encoded representation of the command group members.
232 */
233 protected Object[] getMembers() {
234 return this.members;
235 }
236
237 /**
238 * Accepts notification from the IoC container of this instance's bean name
239 * as declared in the bean definition file. This value is used as the id of
240 * the command group produced by this factory.
241 */
242 public void setBeanName(String beanName) {
243 this.groupId = beanName;
244 }
245
246 /**
247 * @return beanName.
248 */
249 protected String getBeanName() {
250 return this.groupId;
251 }
252
253 /**
254 * Returns the value of the flag that indicates whether or not this factory
255 * produces an exclusive command group.
256 * @return The exclusive flag.
257 * @see ExclusiveCommandGroup
258 */
259 public boolean isExclusive() {
260 return this.exclusive;
261 }
262
263 /**
264 * Sets the flag that indicates whether or not this factory produces an
265 * exclusive command group.
266 *
267 * @param exclusive {@code true} to produce an exclusive command group,
268 * false otherwise.
269 * @see ExclusiveCommandGroup
270 */
271 public void setExclusive(boolean exclusive) {
272 this.exclusive = exclusive;
273 }
274
275 /**
276 * Sets the flag that indicates whether or not the command group produced by
277 * this factory allows no items in the group to be selected, default is
278 * false. This is only relevant for exclusive command groups.
279 *
280 * @param allowsEmptySelection Set {@code true} for the command group to
281 * allow none of its members to be selected.
282 */
283 public void setAllowsEmptySelection(boolean allowsEmptySelection) {
284 this.allowsEmptySelection = allowsEmptySelection;
285 }
286
287 /**
288 * @return <code>true</code> if the exclusive commandGroup can have no
289 * item selected.
290 */
291 protected boolean isAllowsEmptySelection() {
292 return this.allowsEmptySelection;
293 }
294
295 /**
296 * Returns the command group that this factory produces.
297 *
298 * @return The factory's command group, never null.
299 */
300 public Object getObject() throws Exception {
301 return getCommandGroup();
302 }
303
304 /**
305 * Returns the command group that this factory produces.
306 *
307 * @return The factory's command group, never null.
308 */
309 public CommandGroup getCommandGroup() {
310 if (commandGroup == null) {
311 commandGroup = createCommandGroup();
312 }
313 return commandGroup;
314 }
315
316 /**
317 * Creates the command group for this factory and assigns it an identifier
318 * equal to the group id of the factory. The command group will also be
319 * assigned the security controller id, if any, that was provided via the
320 * {@link #setSecurityControllerId(String)} method and the values from the
321 * encoded members list will be used to retrieve the corresponding command
322 * objects from the command registry.
323 *
324 * @return The command group, never null.
325 */
326 // NOTE: Find out (and add some comment about) what happens if a command
327 // registry has not been provided.
328 protected CommandGroup createCommandGroup() {
329 CommandGroup group;
330 if (isExclusive()) {
331 ExclusiveCommandGroup g = new ExclusiveCommandGroup(getBeanName(), getCommandRegistry());
332 g.setAllowsEmptySelection(isAllowsEmptySelection());
333 group = g;
334 }
335 else {
336 group = new CommandGroup(getBeanName(), getCommandRegistry());
337 }
338
339 // Apply our security controller id to the new group
340 group.setSecurityControllerId(getSecurityControllerId());
341
342 initCommandGroupMembers(group);
343 return group;
344 }
345
346 /**
347 * Iterates over the collection of encoded members and adds them to the
348 * given command group.
349 *
350 * @param group The group that is to contain the commands from the encoded
351 * members list. Must not be null.
352 *
353 * @throws InvalidGroupMemberEncodingException if a member prefix is
354 * provided without a command id.
355 */
356 protected void initCommandGroupMembers(CommandGroup group) {
357 for (int i = 0; i < members.length; i++) {
358 Object o = members[i];
359
360 if (o instanceof AbstractCommand) {
361 group.addInternal((AbstractCommand) o);
362 configureIfNecessary((AbstractCommand) o);
363 }
364 else if (o instanceof Component) {
365 group.addComponentInternal((Component) o);
366 }
367 else if (o instanceof String) {
368 String str = (String) o;
369 if (str.equalsIgnoreCase(SEPARATOR_MEMBER_CODE)) {
370 group.addSeparatorInternal();
371 }
372 else if (str.equalsIgnoreCase(GLUE_MEMBER_CODE)) {
373 group.addGlueInternal();
374 }
375 else if (str.startsWith(COMMAND_MEMBER_PREFIX)) {
376
377 String commandId = str.substring(COMMAND_MEMBER_PREFIX.length());
378
379 if (!StringUtils.hasText(commandId)) {
380 throw new InvalidGroupMemberEncodingException(
381 "The group member encoding does not specify a command id", str);
382 }
383
384 addCommandMember(str.substring(COMMAND_MEMBER_PREFIX.length()), group);
385 }
386 else if (str.startsWith(GROUP_MEMBER_PREFIX)) {
387
388 String commandId = str.substring(GROUP_MEMBER_PREFIX.length());
389
390 if (!StringUtils.hasText(commandId)) {
391 throw new InvalidGroupMemberEncodingException(
392 "The group member encoding does not specify a command id", str);
393 }
394
395 addCommandMember(commandId, group);
396 }
397 else {
398 addCommandMember(str, group);
399 }
400 }
401 }
402 }
403
404 /**
405 * Adds the command object with the given id to the given command group. If
406 * a command registry has not yet been provided to this factory, the command
407 * id will be passed as a 'lazy placeholder' to the group instead.
408 *
409 * @param commandId The id of the command to be added to the group. This is
410 * expected to be in decoded form, i.e. any command prefixes have been
411 * removed. Must not be null.
412 * @param isGroup indicates if the command is actually a command group.
413 * @param group The group that the commands will be added to. Must not be
414 * null.
415 *
416 */
417 private void addCommandMember(String commandId, CommandGroup group) {
418
419 Assert.required(commandId, "commandId");
420 Assert.required(group, "group");
421
422 if (logger.isDebugEnabled()) {
423 logger.debug("adding command group member with id [" + commandId + "] to group [" + group.getId() + "]");
424 }
425
426 AbstractCommand command = null;
427
428 if (commandRegistry != null) {
429 command = (AbstractCommand) commandRegistry.getCommand(commandId);
430 if (command != null) {
431 group.addInternal(command);
432 }
433 }
434
435 if (command == null) {
436 group.addLazyInternal(commandId);
437 }
438
439 }
440
441 /**
442 * Configures the given command if it has not already been configured and
443 * this instance has been provided with a {@link CommandConfigurer}.
444 *
445 * @param command The command to be configured.
446 * @throws IllegalArgumentException if {@code command} is null.
447 */
448 protected void configureIfNecessary(AbstractCommand command) {
449
450 Assert.required(command, "command");
451
452 if (commandConfigurer != null) {
453 if (!command.isFaceConfigured()) {
454 commandConfigurer.configure(command);
455 }
456 }
457
458 }
459
460 /**
461 * Returns the Class object for {@link CommandGroup}.
462 * @return CommandGroup.class
463 */
464 public Class getObjectType() {
465 return CommandGroup.class;
466 }
467
468 /**
469 * Always returns true. The command groups produced by this factory are
470 * always singletons.
471 * @return {@code true} always.
472 */
473 public boolean isSingleton() {
474 return true;
475 }
476
477 /**
478 * {@inheritDoc}
479 */
480 public void setSecurityControllerId(String controllerId) {
481 this.securityControllerId = controllerId;
482 }
483
484 /**
485 * {@inheritDoc}
486 */
487 public String getSecurityControllerId() {
488 return securityControllerId;
489 }
490
491 /**
492 * {@inheritDoc}
493 */
494 public void setAuthorized(boolean authorized) {
495 // nothing to do on the factory. This method is only implemented because
496 // it is declared on the SecurityControllable interface, which we need
497 // to implement in order to be assigned a securityControllerId that we
498 // can then pass on to the commandGroup produced by this factory
499 }
500
501 /**
502 * {@inheritDoc}
503 */
504 public boolean isAuthorized() {
505 return false;
506 }
507
508 }