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 }