/** * Copyright (C) 2015 Valkyrie RCP * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.valkyriercp.command.support; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.FactoryBean; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.valkyriercp.command.CommandConfigurer; import org.valkyriercp.command.CommandRegistry; import org.valkyriercp.core.Secured; import org.valkyriercp.util.ValkyrieRepository; import java.awt.*; /** * A {@link org.springframework.beans.factory.FactoryBean} that produces a {@link CommandGroup}. * * <p> * Use of this bean simplifies the process of building up complex nested command * groups such as the main menu of an application window, a toolbar or popup * menus. The main property of interest when creating a bean definition for this * class is the {@code members} list. This list defines the members of the * command group that will be produced by the factory. The objects contained in * this list can be instances of the actual command or they can be strings that * represent the identifier of the command. Some strings have special meaning: * * <ul> * <li>{@value #GLUE_MEMBER_CODE}: Represents a 'glue' component between * command group members.</li> * <li>{@value #SEPARATOR_MEMBER_CODE}: Represents a separator between command * group members.</li> * </ul> * </p> * * @author Keith Donald */ public class CommandGroupFactoryBean implements FactoryBean, Secured { /** * The string that represents a glue component, to be used between other * members of the command group. */ public static final String GLUE_MEMBER_CODE = "glue"; /** * The string that represents a separator between commands in the command * group. */ public static final String SEPARATOR_MEMBER_CODE = "separator"; /** * The string prefix that indicates a command group member that is a * command. */ public static final String COMMAND_MEMBER_PREFIX = "command:"; /** * The string prefix that indicates a command group member that is another * command group. */ public static final String GROUP_MEMBER_PREFIX = "group:"; /** Class logger, available to subclasses. */ protected Log logger = LogFactory.getLog(getClass()); private String groupId; private CommandRegistry commandRegistry; private CommandConfigurer commandConfigurer; private Object[] members = new Object[0]; private boolean exclusive; private boolean allowsEmptySelection; private CommandGroup commandGroup; private String securityControllerId; private String[] authorities; /** * Creates a new uninitialized {@code CommandGroupFactoryBean}. If created * by the Spring IoC container, the {@code groupId} assigned to this * instance will be the bean name of the bean as declared in the bean * definition file. If using this constructor, a non-null list of command * group members must be provided by calling the * {@link #setMembers(Object...)} method before this instance is used. */ public CommandGroupFactoryBean() { // do nothing } /** * Creates a new {@code CommandGroupFactoryBean} with the given group ID and * command group members. * * @param groupId The identifier that will be assigned to the command group * produced by this factory. Note that if this instance is created by a * Spring IoC container, the group ID provided here will be overwritten by * the bean name of this instance's bean definition. * * @param members The collection of objects that specify the members of the * command group. These objects are expected to be either instances of * {@link AbstractCommand} or strings. See the class documentation for * details on how these strings will be interpreted. Must not be null. * * @throws IllegalArgumentException if {@code members} is null. */ public CommandGroupFactoryBean(String groupId, Object... members) { this(groupId, null, null, members); } /** * Creates a new {@code CommandGroupFactoryBean}. * * @param groupId The value to be used as the command identifier of the * command group produced by this factory. * @param commandRegistry The registry that will be used to retrieve the * actual instances of the command group members as specified in * {@code members}. * @param members The collection of objects that specify the members of the * command group. These objects are expected to be either instances of * {@link AbstractCommand} or strings. See the class documentation for * details on how these strings will be interpreted. Must not be null. * * @throws IllegalArgumentException if {@code members} is null. */ public CommandGroupFactoryBean(String groupId, CommandRegistry commandRegistry, Object... members) { this(groupId, commandRegistry, null, members); } /** * Creates a new {@code CommandGroupFactoryBean}. * * @param groupId The value to be used as the command identifier of the * command group produced by this factory. * @param commandRegistry The registry that will be used to retrieve the * actual instances of the command group members as specified in * {@code members}. * @param members The collection of objects that specify the members of the * command group. These objects are expected to be either instances of * {@link AbstractCommand} or strings. See the class documentation for * details on how these strings will be interpreted. Must not be null. * @param commandConfigurer The object that will be used to configure the * command objects contained in this factory's command group. * * @throws IllegalArgumentException if {@code members} is null. */ public CommandGroupFactoryBean(String groupId, CommandRegistry commandRegistry, CommandConfigurer commandConfigurer, Object... members) { Assert.notNull(members, "members"); this.groupId = groupId; this.commandRegistry = commandRegistry; this.members = members; this.commandConfigurer = commandConfigurer; } /** * Sets the registry that will be used to retrieve the actual instances of * the command group members as specified in the encoded members collection. * * @param commandRegistry The registry containing commands for the command * group produced by this factory. May be null. */ public void setCommandRegistry(CommandRegistry commandRegistry) { this.commandRegistry = commandRegistry; } /** * @return commandRegistry containing commands for this command group. */ protected CommandRegistry getCommandRegistry() { if(commandRegistry == null) return ValkyrieRepository.getInstance().getApplicationConfig().commandManager(); return this.commandRegistry; } /** * Sets the object that will be used to configure the command objects in the * command groups produced by this factory. * * @param configurer The command configurer, may be null. */ public void setCommandConfigurer(CommandConfigurer configurer) { this.commandConfigurer = configurer; } public CommandConfigurer getCommandConfigurer() { if(commandConfigurer == null) return ValkyrieRepository.getInstance().getApplicationConfig().commandConfigurer(); return commandConfigurer; } /** * Sets the collection of objects that specify the members of the command * group produced by this factory. The objects in {@code members} are * expected to be either instances of {@link AbstractCommand} or strings. * See the class documentation for details on how these strings will be * interpreted. * * @param members The (possibly) encoded representation of the command group * members. Must not be null. * * @throws IllegalArgumentException if {@code members} is null. */ public final void setMembers(Object... members) { Assert.notNull(members, "members"); this.members = members; } /** * @return the possibly encoded representation of the command group members. */ protected Object[] getMembers() { return this.members; } /** * Returns the value of the flag that indicates whether or not this factory * produces an exclusive command group. * @return The exclusive flag. * @see ExclusiveCommandGroup */ public boolean isExclusive() { return this.exclusive; } /** * Sets the flag that indicates whether or not this factory produces an * exclusive command group. * * @param exclusive {@code true} to produce an exclusive command group, * false otherwise. * @see ExclusiveCommandGroup */ public void setExclusive(boolean exclusive) { this.exclusive = exclusive; } /** * Sets the flag that indicates whether or not the command group produced by * this factory allows no items in the group to be selected, default is * false. This is only relevant for exclusive command groups. * * @param allowsEmptySelection Set {@code true} for the command group to * allow none of its members to be selected. */ public void setAllowsEmptySelection(boolean allowsEmptySelection) { this.allowsEmptySelection = allowsEmptySelection; } /** * @return <code>true</code> if the exclusive commandGroup can have no * item selected. */ protected boolean isAllowsEmptySelection() { return this.allowsEmptySelection; } /** * Returns the command group that this factory produces. * * @return The factory's command group, never null. */ public Object getObject() throws Exception { return getCommandGroup(); } /** * Returns the command group that this factory produces. * * @return The factory's command group, never null. */ public CommandGroup getCommandGroup() { if (commandGroup == null) { commandGroup = createCommandGroup(); } // commandConfigurer.configure(commandGroup); return commandGroup; } /** * Creates the command group for this factory and assigns it an identifier * equal to the group id of the factory. The command group will also be * assigned the security controller id, if any, that was provided via the * {@link #setSecurityControllerId(String)} method and the values from the * encoded members list will be used to retrieve the corresponding command * objects from the command registry. * * @return The command group, never null. */ // NOTE: Find out (and add some comment about) what happens if a command // registry has not been provided. protected CommandGroup createCommandGroup() { CommandGroup group; if (isExclusive()) { ExclusiveCommandGroup g = new ExclusiveCommandGroup(getGroupId(), getCommandRegistry()); g.setAllowsEmptySelection(isAllowsEmptySelection()); group = g; } else { group = new CommandGroup(getGroupId(), getCommandRegistry()); } // Apply our security controller id to the new group group.setSecurityControllerId(getSecurityControllerId()); initCommandGroupMembers(group); return group; } /** * Iterates over the collection of encoded members and adds them to the * given command group. * * @param group The group that is to contain the commands from the encoded * members list. Must not be null. * * @throws InvalidGroupMemberEncodingException if a member prefix is * provided without a command id. */ protected void initCommandGroupMembers(CommandGroup group) { for (int i = 0; i < members.length; i++) { Object o = members[i]; if (o instanceof AbstractCommand) { group.addInternal((AbstractCommand) o); configureIfNecessary((AbstractCommand) o); } else if (o instanceof Component) { group.addComponentInternal((Component) o); } else if (o instanceof String) { String str = (String) o; if (str.equalsIgnoreCase(SEPARATOR_MEMBER_CODE)) { group.addSeparatorInternal(); } else if (str.equalsIgnoreCase(GLUE_MEMBER_CODE)) { group.addGlueInternal(); } else if (str.startsWith(COMMAND_MEMBER_PREFIX)) { String commandId = str.substring(COMMAND_MEMBER_PREFIX.length()); if (!StringUtils.hasText(commandId)) { throw new InvalidGroupMemberEncodingException( "The group member encoding does not specify a command id", str); } addCommandMember(str.substring(COMMAND_MEMBER_PREFIX.length()), group); } else if (str.startsWith(GROUP_MEMBER_PREFIX)) { String commandId = str.substring(GROUP_MEMBER_PREFIX.length()); if (!StringUtils.hasText(commandId)) { throw new InvalidGroupMemberEncodingException( "The group member encoding does not specify a command id", str); } addCommandMember(commandId, group); } else { addCommandMember(str, group); } } } } /** * Adds the command object with the given id to the given command group. If * a command registry has not yet been provided to this factory, the command * id will be passed as a 'lazy placeholder' to the group instead. * * @param commandId The id of the command to be added to the group. This is * expected to be in decoded form, i.e. any command prefixes have been * removed. Must not be null. * @param group The group that the commands will be added to. Must not be * null. * */ private void addCommandMember(String commandId, CommandGroup group) { Assert.notNull(commandId, "commandId"); Assert.notNull(group, "group"); if (logger.isDebugEnabled()) { logger.debug("adding command group member with id [" + commandId + "] to group [" + group.getId() + "]"); } AbstractCommand command = null; if (getCommandRegistry() != null) { command = (AbstractCommand) getCommandRegistry().getCommand(commandId); if (command != null) { group.addInternal(command); } } if (command == null) { group.addLazyInternal(commandId); } } /** * Configures the given command if it has not already been configured and * this instance has been provided with a {@link CommandConfigurer}. * * @param command The command to be configured. * @throws IllegalArgumentException if {@code command} is null. */ protected void configureIfNecessary(AbstractCommand command) { Assert.notNull(command, "command"); if (getCommandConfigurer() != null) { if (!command.isFaceConfigured()) { getCommandConfigurer().configure(command); } } } /** * Returns the Class object for {@link CommandGroup}. * @return CommandGroup.class */ public Class getObjectType() { return CommandGroup.class; } /** * Always returns true. The command groups produced by this factory are * always singletons. * @return {@code true} always. */ public boolean isSingleton() { return true; } /** * {@inheritDoc} */ public void setSecurityControllerId(String controllerId) { this.securityControllerId = controllerId; } /** * {@inheritDoc} */ public String getSecurityControllerId() { return securityControllerId; } /** * {@inheritDoc} */ public void setAuthorized(boolean authorized) { // nothing to do on the factory. This method is only implemented because // it is declared on the SecurityControllable interface, which we need // to implement in order to be assigned a securityControllerId that we // can then pass on to the commandGroup produced by this factory } /** * {@inheritDoc} */ public boolean isAuthorized() { return false; } public String getGroupId() { return groupId; } public void setGroupId(String groupId) { this.groupId = groupId; } public String[] getAuthorities() { return authorities; } public void setAuthorities(String... authorities) { this.authorities = authorities; } }