/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.communications.command.server;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.rhq.enterprise.communications.command.Command;
import org.rhq.enterprise.communications.command.CommandExecutor;
import org.rhq.enterprise.communications.command.CommandResponse;
import org.rhq.enterprise.communications.command.CommandType;
import org.rhq.enterprise.communications.command.impl.generic.GenericCommandResponse;
import org.rhq.enterprise.communications.i18n.CommI18NResourceKeys;
/**
* Provides some infrastructure to more easily facilitate the ability to execute different {@link Command commands}.
* This class provides another level of indirection to locate the executors of commands. Where {@link CommandService} is
* the object that explicitly executes the requested command, this class will actually hand off the command to the
* actual {@link CommandExecutor} object that will execute the command.
*
* <p>This is similar to what the {@link CommandProcessor} does; however, this class allows a single, deployed MBean to
* handle multiple, distinctly different, command types (whereas the command processor looks for
* {@link CommandService command service MBeans} which do not easily support distinctly different commands (short of
* implementing a series of <code>if-else</code> statements to perform <code>instanceof</code> checks on the incoming
* command). Therefore, this class allows a single, deployed MBean to handle any number of commands, allowing for a
* smaller deployment configuration (rather than being forced to deploy X MBeans to handle X commands; this allows you
* to deploy 1 MBean to handle X commands).</p>
*
* <p>Usually, this class is helpful when a command service needs to execute distinctly different commands, but where
* those commands are logically related to one another.</p>
*
* <p>Subclasses of this class should not override {@link CommandServiceMBean#getSupportedCommandTypes()}, instead they
* must implement {@link #getSupportedCommandTypeExecutors()}.</p>
*
* <p>Subclasses also should not override {@link #execute(Command, InputStream, OutputStream)}.</p>
*
* <p>The executors this class uses can be instantiated on a per-request basis, or they may be reused (in which case,
* the implementor of the {@link CommandExecutor} class must ensure its thread safety).</p>
*
* @author John Mazzitelli
*/
public abstract class MultipleCommandService extends CommandService {
/**
* contans a map keyed on {@link CommandType} whose values must be
* {@link MultipleCommandService.CommandTypeExecutor}
*/
private Map<CommandType, CommandTypeExecutor> m_executors;
/**
* Given a command to execute, this method will lookup that command's type to determine what
* {@link CommandExecutor executor} should be used. Once the executor is determined, this method delegates to that
* object to execute the command.
*
* @throws IllegalArgumentException if the command is of an invalid or unknown type
*
* @see CommandExecutor#execute(Command, InputStream, OutputStream)
*/
public CommandResponse execute(Command command, InputStream in, OutputStream out) {
// find what executor should be responsible for executing the command
CommandType commandTypeToExecute = command.getCommandType();
CommandTypeExecutor typeExecutor = getExecutors().get(commandTypeToExecute);
if (typeExecutor == null) {
// this should never really happen; I can't think of a case where it would under normal circumstances
throw new IllegalArgumentException(getLog()
.getMsgString(CommI18NResourceKeys.UNKNOWN_COMMAND_TYPE, command));
}
// get the executor instance and hand off the command to it
CommandResponse retResponse;
try {
retResponse = typeExecutor.getExecutor().execute(command, null, null);
} catch (Throwable t) {
retResponse = new GenericCommandResponse(command, false, null, t);
}
return retResponse;
}
/**
* Subclasses to this class do not override this method; instead, they need to implement
* {@link #getSupportedCommandTypeExecutors()}.
*
* @see CommandServiceMBean#getSupportedCommandTypes()
*/
public CommandType[] getSupportedCommandTypes() {
Set<CommandType> executorCommandTypes = getExecutors().keySet();
return executorCommandTypes.toArray(new CommandType[executorCommandTypes.size()]);
}
/**
* Gets the set of executors whose map keys are the {@link CommandType command types} and whose map values are
* {@link CommandTypeExecutor} objects.
*
* <p>This is the method that actually calls the subclass' {@link #getSupportedCommandTypeExecutors()} and builds
* the map from that. We create a <code>Map</code> as opposed to just using the array to make lookups by command
* type faster.</p>
*
* @return map of executors
*/
protected Map<CommandType, CommandTypeExecutor> getExecutors() {
if (m_executors == null) {
// synch just to avoid the rare occurrence of getting here concurrently
// if this happens, we just rebuild the map twice, but that is harmless
synchronized (this) {
m_executors = new HashMap<CommandType, CommandTypeExecutor>();
CommandTypeExecutor[] supportedExecutors = getSupportedCommandTypeExecutors();
for (int i = 0; i < supportedExecutors.length; i++) {
m_executors.put(supportedExecutors[i].m_type, supportedExecutors[i]);
}
}
}
return m_executors;
}
/**
* Returns a set of {@link CommandTypeExecutor} objects that define what command types this service supports and the
* executors that will execute commands of those types.
*
* <p>The returned array should be fixed during the lifetime of this service (or at least during its registration in
* an MBeanServer). Changes to the supported command types during runtime will not be detected once the
* {@link CommandServiceDirectory} has discovered this service. As a corollary to this rule, this method must be
* ready to provide the array of support command types at the time it is registered on an MBeanServer (in other
* words, this method will be called, specifically by the {@link CommandServiceDirectory directory}, as soon as this
* service is registered).</p>
*
* <p>Unlike direct subclasses to {@link CommandService}, subclasses of this class do not need to override
* {@link CommandServiceMBean#getSupportedCommandTypes()}; instead, they override this method to inform the
* framework not only what command types the subclass supports but also what executors should handle the execution
* of commands.</p>
*
* @return array of supported command types/executors
*/
protected abstract CommandTypeExecutor[] getSupportedCommandTypeExecutors();
/**
* An inner class used only by the {@link MultipleCommandService} that encasulates a supported command type and the
* executor that should be used to execute commands of that type. Note the two constructors - one takes a <code>
* java.lang.Class</code> and one an instance of {@link CommandExecutor}. If the <code>java.lang.Class</code>
* constructor is used, then for each command request that is issued to this command service, a new instance should
* be created to handle each command. If the {@link CommandExecutor} constructor is used, that means each command
* that comes in should be handed off to that specific instance. In that case, the executor instance <b>must</b>
* ensure thread-safety, since commands may come in concurrently.
*
* <p>Subclasses create instances of these objects and return them in
* {@link MultipleCommandService#getSupportedCommandTypeExecutors()}.</p>
*
* @author John Mazzitelli
*/
protected class CommandTypeExecutor {
/**
* the type of command this executor will handle
*/
public final CommandType m_type;
/**
* the executor's class - this must implement {@link CommandExecutor}
*/
public final Class m_executorClass;
/**
* if not <code>null</code>, this instance will handle all command executions for the command type (must be
* thread safe!)
*/
private CommandExecutor m_executorInstance;
/**
* Creates a new object that defines what class to instantiate for each new command to execute. Each new
* instantiation of the given class will handle only a single command execution.
*
* @param type the type of command to be handed off to new instances of the given executor class
* @param executorClass class of the executor to instantiate when new commands are to be executed
*
* @throws IllegalArgumentException if the given class is an interface, an abstract class or not assignable to
* {@link CommandExecutor}; also if any parameter is <code>null</code>. Note
* that this is also thrown if the class is a {@link MultipleCommandService}
* object, since that would result in an infinite recursive loop.
*/
public CommandTypeExecutor(CommandType type, Class executorClass) {
if (type == null) {
throw new IllegalArgumentException("type=null");
}
if (executorClass == null) {
throw new IllegalArgumentException("executorClass=null");
}
boolean isAssignable = CommandExecutor.class.isAssignableFrom(executorClass);
boolean isInterface = executorClass.isInterface();
boolean isAbstract = Modifier.isAbstract(executorClass.getModifiers());
boolean isRecursive = MultipleCommandService.class.isAssignableFrom(executorClass);
if (!isAssignable || isInterface || isAbstract || isRecursive) {
throw new IllegalArgumentException(getLog().getMsgString(CommI18NResourceKeys.INVALID_EXECUTOR_CLASS,
executorClass, CommandExecutor.class, MultipleCommandService.class));
}
m_type = type;
m_executorClass = executorClass;
m_executorInstance = null;
}
/**
* Creates a new object that defines what executor instance to use to execute all commands of the given <code>
* type</code>. Because all commands will be handed off to the given executor instance, that instance must
* ensure thread-safety.
*
* @param type the type of command that will be handled by the given executor instance
* @param executorInstance the executor that will handle all commands of the given type
*
* @throws IllegalArgumentException if any parameter is <code>null</code> or if the instance is a
* {@link MultipleCommandService} object, since that would result in an
* infinite recursive loop.
*/
public CommandTypeExecutor(CommandType type, CommandExecutor executorInstance) {
if (type == null) {
throw new IllegalArgumentException("type=null");
}
if (executorInstance == null) {
throw new IllegalArgumentException("executorInstance=null");
}
if (executorInstance instanceof MultipleCommandService) {
throw new IllegalArgumentException(getLog().getMsgString(
CommI18NResourceKeys.INVALID_EXECUTOR_INSTANCE, executorInstance.getClass(),
MultipleCommandService.class));
}
m_type = type;
m_executorClass = executorInstance.getClass();
m_executorInstance = executorInstance;
}
/**
* Returns the executor instance that should be used to execute the next command.
*
* @return command executor that should be used to execute the next command
*
* @throws RuntimeException failed to create the executor instance (should rarely, if ever, occur)
*/
public CommandExecutor getExecutor() {
try {
return (m_executorInstance != null) ? m_executorInstance : (CommandExecutor) m_executorClass
.newInstance();
} catch (Exception e) {
// convert to a runtime exception since this should rarely occur
throw new RuntimeException(getLog().getMsgString(CommI18NResourceKeys.CANNOT_CREATE_EXECUTOR), e);
}
}
}
}