/*
* 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;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import mazz.i18n.Logger;
import org.rhq.enterprise.communications.command.param.InvalidParameterDefinitionException;
import org.rhq.enterprise.communications.command.param.InvalidParameterValueException;
import org.rhq.enterprise.communications.command.param.NoParameterDefinitionsException;
import org.rhq.enterprise.communications.command.param.OptionalParameterDefinitionIterator;
import org.rhq.enterprise.communications.command.param.ParameterDefinition;
import org.rhq.enterprise.communications.command.param.RequiredParameterDefinitionIterator;
import org.rhq.enterprise.communications.i18n.CommI18NFactory;
import org.rhq.enterprise.communications.i18n.CommI18NResourceKeys;
/**
* Superclass to all {@link Command} objects that may be executed by a command processor.
*
* @author John Mazzitelli
*/
public abstract class AbstractCommand implements Command {
/**
* Logger
*/
private static final Logger LOG = CommI18NFactory.getLogger(AbstractCommand.class);
/**
* this command's type, which includes the name of the command
*/
private CommandType m_commandType;
/**
* this command's parameter definitions; may be <code>null</code> if all parameters are accepted
*/
private Map<String, ParameterDefinition> m_parameterDefinitions;
/**
* optional parameters to this command; may be <code>null</code>
*/
private Map<String, Object> m_commandParameters;
/**
* if <code>true</code>, the command processor will return this command in the response back to the client
*/
private boolean m_commandInResponse;
/**
* This instance's configuration.
*/
private Properties m_config;
/**
* the Serializable UID
*/
private static final long serialVersionUID = 1L;
/**
* Constructor for {@link AbstractCommand} that defines just the type without any parameters.
*
* @throws IllegalArgumentException if <code>commandType</code> was not defined by the command subclass
* @throws InvalidParameterDefinitionException if more than one parameter is defined with the same name
*
* @see #buildCommandType()
* @see #buildParameterDefinitions()
* @see #AbstractCommand(Map)
*/
public AbstractCommand() throws IllegalArgumentException, InvalidParameterDefinitionException {
this((Map<String, Object>) null); // by default, there are no extra parameters
}
/**
* Constructor for {@link AbstractCommand} that allows the caller to define both the name and parameter default
* values.
*
* @param commandParameters optional set of parameters to be passed with the command (may be empty or <code>
* null</code>)
*
* @throws IllegalArgumentException if <code>commandType</code> was not defined by the command subclass
* @throws InvalidParameterDefinitionException if more than one parameter is defined with the same name
*
* @see #buildCommandType()
* @see #buildParameterDefinitions()
*/
@SuppressWarnings("unchecked")
public AbstractCommand(Map<String, Object> commandParameters) throws IllegalArgumentException,
InvalidParameterDefinitionException {
initializeMetadata();
m_commandParameters = commandParameters;
m_commandInResponse = false; // by default, don't return this object back with the response
m_config = new Properties();
return;
}
/**
* Constructor for {@link AbstractCommand} that acts as a pseudo-copy constructor and a command
* decorator/transformer. The given command's parameter values will be copied to this new command object. However,
* the command type and the parameter definitions will be those defined by the subclass'
* {@link #buildParameterDefinitions()}, not those of the given command.
*
* <p>So, as you can see, this isn't truely a follower of the Decorator pattern or a copy-constructor; instead,
* think of this constructor as a transformer from one command implementation to another, while maintaining the
* command's original parameter values (as well as the {@link Command#isCommandInResponse() flag}).</p>
*
* <p>This is typically used when the given command is a generic command (one with no parameter definitions for
* example) and the caller wants to convert it to a more concrete command implementation. This is usually due to the
* fact that the creator of the given command object did not know at compile time the specific concrete command type
* it needed.</p>
*
* <p>Transforming a command allows the caller to "decorate" the given command with a concrete command
* implementation's API (which typically has more strongly typed methods to extract out command parameters).</p>
*
* <p>If the given <code>commandToTransform</code>
* {@link Command#allowAnyParameter() allowed any and all parameters} but the newly transformed command (this
* object) does not, this method forces the current set of parameters to be validated (after possibly being
* converted to the appropriate types). If the parameters do not validate, an exception will be thrown.</p>
*
* <p>The only thing a subclass must do in order to support this transformer constructor is to override it and call
* it via <code>super</code>.</p>
*
* @param commandToTransform the command object to transform into this class type
*
* @throws InvalidParameterValueException if the original command's parameters are not valid for this command
*/
public AbstractCommand(Command commandToTransform) throws InvalidParameterValueException {
// build our subclass' command type and parameter definitions
initializeMetadata();
//copy the rest of the command's data from the given command to this new command object
m_commandInResponse = commandToTransform.isCommandInResponse();
Map<String, Object> parameterValues = commandToTransform.getParameterValues();
if ((parameterValues != null) && (parameterValues.size() > 0)) {
m_commandParameters = new HashMap<String, Object>();
m_commandParameters.putAll(parameterValues);
}
if (commandToTransform.allowAnyParameter() && !this.allowAnyParameter()) {
checkParameterValidity(true);
}
m_config = new Properties();
if (commandToTransform.getConfiguration() != null) {
m_config.putAll(commandToTransform.getConfiguration());
}
return;
}
/**
* @see Command#getCommandType()
*/
public CommandType getCommandType() {
return m_commandType;
}
/**
* @see Command#isCommandInResponse()
*/
public boolean isCommandInResponse() {
return m_commandInResponse;
}
/**
* @see Command#setCommandInResponse(boolean)
*/
public void setCommandInResponse(boolean flag) {
m_commandInResponse = flag;
}
/**
* @see Command#allowAnyParameter()
*/
public boolean allowAnyParameter() {
return (m_parameterDefinitions == null);
}
/**
* @see Command#getParameterDefinition(String)
*/
public ParameterDefinition getParameterDefinition(String paramName) throws IllegalArgumentException,
NoParameterDefinitionsException {
if (m_parameterDefinitions == null) {
throw new NoParameterDefinitionsException(LOG.getMsgString(CommI18NResourceKeys.NO_PARAM_DEF_ACCEPTS_ALL));
}
if (paramName == null) {
throw new IllegalArgumentException(LOG.getMsgString(CommI18NResourceKeys.NULL_PARAM_NAME));
}
return m_parameterDefinitions.get(paramName);
}
/**
* @see Command#getParameterDefinitions()
*/
public ParameterDefinition[] getParameterDefinitions() throws NoParameterDefinitionsException {
if (m_parameterDefinitions == null) {
throw new NoParameterDefinitionsException(LOG.getMsgString(CommI18NResourceKeys.NO_PARAM_DEF_ACCEPTS_ALL));
}
Collection<ParameterDefinition> paramDefs = m_parameterDefinitions.values();
return paramDefs.toArray(new ParameterDefinition[paramDefs.size()]);
}
/**
* @see Command#hasParameterValue(String)
*/
public boolean hasParameterValue(String paramName) throws IllegalArgumentException {
if (paramName == null) {
throw new IllegalArgumentException(LOG.getMsgString(CommI18NResourceKeys.NULL_PARAM_NAME));
}
boolean hasValue = false;
if (m_commandParameters != null) {
hasValue = m_commandParameters.containsKey(paramName);
}
return hasValue;
}
/**
* @see Command#getParameterValues()
*/
public Map<String, Object> getParameterValues() throws IllegalArgumentException {
Map<String, Object> retValues = new HashMap<String, Object>();
if (m_commandParameters != null) {
retValues.putAll(m_commandParameters);
}
return retValues;
}
/**
* @see Command#getParameterValue(String)
*/
public Object getParameterValue(String paramName) {
if (paramName == null) {
throw new IllegalArgumentException(LOG.getMsgString(CommI18NResourceKeys.NULL_PARAM_NAME));
}
Object retValue = null;
if (m_commandParameters != null) {
retValue = m_commandParameters.get(paramName);
}
return retValue;
}
/**
* @see Command#setParameterValue(String, Object)
*/
public void setParameterValue(String paramName, Object paramValue) throws IllegalArgumentException {
if (paramName == null) {
throw new IllegalArgumentException(LOG.getMsgString(CommI18NResourceKeys.NULL_PARAM_NAME_SET));
}
// if there is no parameters map yet, create one now
if (m_commandParameters == null) {
m_commandParameters = new HashMap<String, Object>();
}
m_commandParameters.put(paramName, paramValue);
return;
}
/**
* @see Command#removeParameterValue(String)
*/
public void removeParameterValue(String paramName) {
if (paramName == null) {
throw new IllegalArgumentException(LOG.getMsgString(CommI18NResourceKeys.NULL_PARAM_NAME_REMOVE));
}
if (m_commandParameters != null) {
m_commandParameters.remove(paramName);
if (m_commandParameters.isEmpty()) {
m_commandParameters = null;
}
}
return;
}
/**
* @see Command#removeParameterValues()
*/
public void removeParameterValues() {
if (m_commandParameters != null) {
m_commandParameters.clear();
m_commandParameters = null;
}
return;
}
/**
* Returns the string representation of this command that includes the parameter values but not the parameter
* definitions. See {@link #toString(boolean, boolean)} if you want the string to include the definitions or omit
* the parameter values.
*
* @see Object#toString()
*/
public String toString() {
return toString(true, false);
}
/**
* Returns a toString representation of the command but provides the caller the option to include or omit the
* parameters values and/or the parameter definitions.
*
* <p>Parameter definitions are noisy and can make the string very verbose.</p>
*
* @param includeParameters if <code>true</code>, show the parameter values in the returned string
* @param includeParameterDefs if <code>true</code>, show the parameter definitions in the returned string
*
* @return string describing the command
*
* @see Object#toString()
*/
public String toString(boolean includeParameters, boolean includeParameterDefs) {
StringBuffer strBuf = new StringBuffer("Command: type=[");
strBuf.append(getCommandType());
strBuf.append("]; cmd-in-response=[");
strBuf.append(m_commandInResponse);
strBuf.append("]; config=[");
strBuf.append(m_config);
if (includeParameters) {
strBuf.append("]; params=[");
strBuf.append(m_commandParameters);
}
if (includeParameterDefs) {
strBuf.append("]; param-defs=[");
strBuf.append(m_parameterDefinitions);
}
strBuf.append("]");
return strBuf.toString();
}
/**
* @see Command#checkParameterValidity(boolean)
*/
public void checkParameterValidity(boolean convertIfNecessary) throws InvalidParameterValueException {
// return immediately if this command accepts any and all parameters
if (allowAnyParameter()) {
return;
}
// placeholder to remember parameter values as we check them
Object paramValue;
// get the names of all the command's current set of parameters
Set existingParamNames;
if (m_commandParameters != null) {
existingParamNames = m_commandParameters.keySet();
} else {
existingParamNames = new HashSet();
}
// make sure the current set of parameters have all required parameters defined with the proper types
for (Iterator iter = new RequiredParameterDefinitionIterator(m_parameterDefinitions.values()); iter.hasNext();) {
ParameterDefinition paramDef = (ParameterDefinition) iter.next();
if (!existingParamNames.contains(paramDef.getName())) {
throw new InvalidParameterValueException(LOG.getMsgString(CommI18NResourceKeys.MISSING_REQUIRED_FIELD,
paramDef.getName(), this));
}
paramValue = getParameterValue(paramDef.getName());
if (!paramDef.isValidValue(paramValue)) {
boolean valid = false;
if (convertIfNecessary) {
paramValue = paramDef.convertObject(paramValue);
// if we got this far, our invalid value was converted to a valid value
valid = true;
// overwrite the invalid value with the new valid one
m_commandParameters.put(paramDef.getName(), paramValue);
}
if (!valid) {
throw new InvalidParameterValueException(LOG.getMsgString(
CommI18NResourceKeys.BAD_REQUIRED_PARAM_TYPE, paramDef.getName(), paramDef.getType(), paramDef
.isNullable(), ((paramValue == null) ? "<null>" : paramValue.getClass().toString()), this));
}
}
}
// if the current set of parameters has optional parameters defined,
// make sure their values are of the proper type
for (Iterator iter = new OptionalParameterDefinitionIterator(m_parameterDefinitions.values()); iter.hasNext();) {
ParameterDefinition paramDef = (ParameterDefinition) iter.next();
if (existingParamNames.contains(paramDef.getName())) {
paramValue = getParameterValue(paramDef.getName());
if (!paramDef.isValidValue(paramValue)) {
boolean valid = false;
if (convertIfNecessary) {
paramValue = paramDef.convertObject(paramValue);
// if we got this far, our invalid value was converted to a valid value
valid = true;
// overwrite the invalid value with the new valid one
m_commandParameters.put(paramDef.getName(), paramValue);
}
if (!valid) {
throw new InvalidParameterValueException(LOG.getMsgString(
CommI18NResourceKeys.BAD_OPTIONAL_PARAM_TYPE, paramDef.getName(), paramDef.getType(),
paramDef.isNullable(),
((paramValue == null) ? "<null>" : paramValue.getClass().toString()), this));
}
}
}
}
// now make sure the current set of parameters do not contain unused, extra parameters
for (Iterator iter = existingParamNames.iterator(); iter.hasNext();) {
String paramName = (String) iter.next();
if (!m_parameterDefinitions.containsKey(paramName)) {
throw new InvalidParameterValueException(LOG.getMsgString(CommI18NResourceKeys.UNEXPECTED_PARAM,
paramName, this));
}
}
// everything checks out OK
return;
}
/**
* @see Command#convertParameters()
*/
public void convertParameters() {
// if any parameters are allow, or we have no parameters at all, then just return
if (allowAnyParameter() || (m_commandParameters == null)) {
return;
}
// we put all parameters that we've converted in here, key=param name, value=the new converted value
Map<String, Object> convertedParameters = new HashMap<String, Object>();
for (Iterator iter = m_commandParameters.entrySet().iterator(); iter.hasNext();) {
Map.Entry entry = (Map.Entry) iter.next();
String paramName = (String) entry.getKey();
Object paramValue = entry.getValue();
try {
ParameterDefinition def = getParameterDefinition(paramName);
// if there is no definition defined, any value goes - don't bother trying to convert if no definition is available
if (def != null) {
Object convertedParamValue = def.convertObject(paramValue);
// overwrite the old value with the new one of the proper, converted type
// the references will be identical if no conversion was performed (i.e. the original value was of the proper type)
if (convertedParamValue != paramValue) {
convertedParameters.put(paramName, convertedParamValue);
}
}
} catch (NoParameterDefinitionsException ignore) // we checked that !allowAnyParameter, this shouldn't occur
{
throw new IllegalStateException(LOG.getMsgString(CommI18NResourceKeys.SHOULD_NOT_OCCUR));
}
}
m_commandParameters.putAll(convertedParameters);
return;
}
/**
* @see Command#getConfiguration()
*/
public Properties getConfiguration() {
return m_config;
}
/**
* Returns a <code>Map</code> containing all optional parameters set on this command. This may return <code>
* null</code> in which case no parameters will be sent along with the command when it is to be executed.
*
* <p>Note: the returned <code>Map</code> is not a copy - changes made to the returned object are reflected back
* into this command object.</p>
*
* @return map containing all parameters (may be empty or <code>null</code>)
*/
protected Map getParameterValuesInternalMap() {
return m_commandParameters;
}
/**
* Sets the parameter map, overwriting any previously existing map of parameters. Note the scope is <code>
* protected</code> - users of commands should not be allowed to insert a new map of parameters; rather, subclasses
* should provide setters for individual parameter values.
*
* @param commandParameters the new map of parameters (may be <code>null</code>)
*/
protected void setParameterValuesInternalMap(Map<String, Object> commandParameters) {
m_commandParameters = commandParameters;
}
/**
* Initializes the newly constructed command so its type and parameter definition metadata are built properly.
*
* <p>This method is called from the constructors. However, it is protected to allow for specialized command
* implementations to be able to re-initialize the command metadata.</p>
*
* @throws IllegalArgumentException if the command type was not specified by the command subclass
* @throws InvalidParameterDefinitionException if more than one parameter is defined with the same name
*
* @see #buildCommandType()
* @see #buildParameterDefinitions()
* @see #AbstractCommand(Map)
*/
protected void initializeMetadata() throws IllegalArgumentException, InvalidParameterDefinitionException {
// the command subclass will tell us what its type and parameter definitions are
CommandType commandType = buildCommandType();
ParameterDefinition[] parameterDefinitions = buildParameterDefinitions();
if (commandType == null) {
throw new IllegalArgumentException(LOG.getMsgString(CommI18NResourceKeys.NULL_COMMAND_TYPE));
}
m_commandType = commandType;
if (parameterDefinitions != null) {
// for easier retrieval, store each definition in a Map keyed on the parameter name
m_parameterDefinitions = new HashMap<String, ParameterDefinition>();
for (int i = 0; i < parameterDefinitions.length; i++) {
ParameterDefinition paramDef = parameterDefinitions[i];
String paramName = paramDef.getName();
if (null != m_parameterDefinitions.put(paramName, paramDef)) {
// this is bad - it probably means the command is coded wrong and will never be able to be used
throw new InvalidParameterDefinitionException(LOG.getMsgString(CommI18NResourceKeys.DUP_DEFS,
paramName));
}
}
} else {
m_parameterDefinitions = null;
}
return;
}
/**
* Builds the command type of this command. It must not be <code>null</code>.
*
* <p>This method is called by the command's constructors.</p>
*
* @return the command's type definition; must not be <code>null</code>
*/
protected abstract CommandType buildCommandType();
/**
* Builds the set of parameter definitions that this command will use. Parameter definitions define what parameters
* this command accepts. Implementors must return one of the following:
*
* <ul>
* <li>An array of one or more definitions if the command accepts required and/or optional parameters</li>
* <li>An empty array if the command does not accept <i>any</i> parameters (i.e. a no-arg command)</li>
* <li><code>null</code> if the command accepts any and all parameters, regardless of name or type</li>
* </ul>
*
* <p>This method is called by the command's constructors.</p>
*
* @return an array of parameter definitions or <code>null</code>
*/
protected abstract ParameterDefinition[] buildParameterDefinitions();
}