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    }