You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by da...@apache.org on 2014/03/07 19:12:34 UTC
[3/5] Dispatcher corrections, refactoring and tests
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/c211f0bb/server/src/com/cloud/api/dispatch/ParamGenericValidationWorker.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/dispatch/ParamGenericValidationWorker.java b/server/src/com/cloud/api/dispatch/ParamGenericValidationWorker.java
new file mode 100644
index 0000000..0c829e8
--- /dev/null
+++ b/server/src/com/cloud/api/dispatch/ParamGenericValidationWorker.java
@@ -0,0 +1,103 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 com.cloud.api.dispatch;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cloudstack.api.ApiConstants;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.log4j.Logger;
+
+/**
+ * This worker validates parameters in a generic way, by using annotated
+ * restrictions without involving the {@Link BaseCmd}. This worker doesn't
+ * know or care about the meaning of the parameters and that's why we can
+ * have it out of the {@Link BaseCmd}
+ *
+ * @author afornie
+ */
+public class ParamGenericValidationWorker implements DispatchWorker {
+
+ protected static Logger s_logger = Logger.getLogger(ParamGenericValidationWorker.class.getName());
+
+ protected static List<String> defaultParamNames = new ArrayList<String>();
+
+ static {
+ defaultParamNames.add(ApiConstants.CTX_START_EVENT_ID);
+ defaultParamNames.add(ApiConstants.COMMAND);
+ defaultParamNames.add(ApiConstants.CMD_EVENT_TYPE);
+ defaultParamNames.add(ApiConstants.USERNAME);
+ defaultParamNames.add(ApiConstants.USER_ID);
+ defaultParamNames.add(ApiConstants.PASSWORD);
+ defaultParamNames.add(ApiConstants.DOMAIN);
+ defaultParamNames.add(ApiConstants.DOMAIN_ID);
+ defaultParamNames.add(ApiConstants.DOMAIN__ID);
+ defaultParamNames.add(ApiConstants.SESSIONKEY);
+ defaultParamNames.add(ApiConstants.RESPONSE);
+ defaultParamNames.add(ApiConstants.PAGE);
+ defaultParamNames.add(ApiConstants.USER_API_KEY);
+ defaultParamNames.add(ApiConstants.API_KEY);
+ defaultParamNames.add(ApiConstants.PAGE_SIZE);
+ defaultParamNames.add(ApiConstants.HTTPMETHOD);
+ defaultParamNames.add(ApiConstants.SIGNATURE);
+ defaultParamNames.add(ApiConstants.CTX_ACCOUNT_ID);
+ defaultParamNames.add(ApiConstants.CTX_START_EVENT_ID);
+ defaultParamNames.add(ApiConstants.CTX_USER_ID);
+ defaultParamNames.add("_");
+ }
+
+ protected static final String ERROR_MSG_PREFIX = "Unknown parameters :";
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public void handle(final DispatchTask task) {
+ final BaseCmd cmd = task.getCmd();
+ final Map params = task.getParams();
+
+ final List<String> expectedParamNames = getParamNamesForCommand(cmd);
+
+ final StringBuilder errorMsg = new StringBuilder(ERROR_MSG_PREFIX);
+ boolean foundUnknownParam = false;
+ for (final Object paramName : params.keySet()) {
+ if (!expectedParamNames.contains(paramName)) {
+ errorMsg.append(" ").append(paramName);
+ foundUnknownParam= true;
+ }
+ }
+
+ if (foundUnknownParam) {
+ s_logger.warn(String.format("Received unknown parameters for command %s. %s", cmd.getActualCommandName(), errorMsg));
+ }
+ }
+
+ protected List<String> getParamNamesForCommand(final BaseCmd cmd) {
+ final List<String> paramNames = new ArrayList<String>();
+ // The expected param names are all the specific for the current command class ...
+ for (final Field field : cmd.getParamFields()) {
+ final Parameter parameterAnnotation = field.getAnnotation(Parameter.class);
+ paramNames.add(parameterAnnotation.name());
+ }
+ // ... plus the default ones
+ paramNames.addAll(defaultParamNames);
+ return paramNames;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/c211f0bb/server/src/com/cloud/api/dispatch/ParamProcessWorker.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/dispatch/ParamProcessWorker.java b/server/src/com/cloud/api/dispatch/ParamProcessWorker.java
new file mode 100644
index 0000000..e9bdd8b
--- /dev/null
+++ b/server/src/com/cloud/api/dispatch/ParamProcessWorker.java
@@ -0,0 +1,428 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 com.cloud.api.dispatch;
+
+import static org.apache.commons.lang.StringUtils.isNotBlank;
+
+import java.lang.reflect.Field;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.regex.Matcher;
+
+import javax.inject.Inject;
+
+import org.apache.log4j.Logger;
+
+import org.apache.cloudstack.acl.ControlledEntity;
+import org.apache.cloudstack.acl.InfrastructureEntity;
+import org.apache.cloudstack.acl.SecurityChecker.AccessType;
+import org.apache.cloudstack.api.ACL;
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.BaseAsyncCreateCmd;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.api.EntityReference;
+import org.apache.cloudstack.api.InternalIdentity;
+import org.apache.cloudstack.api.Parameter;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.api.BaseCmd.CommandType;
+import org.apache.cloudstack.api.command.admin.resource.ArchiveAlertsCmd;
+import org.apache.cloudstack.api.command.admin.resource.DeleteAlertsCmd;
+import org.apache.cloudstack.api.command.user.event.ArchiveEventsCmd;
+import org.apache.cloudstack.api.command.user.event.DeleteEventsCmd;
+import org.apache.cloudstack.api.command.user.event.ListEventsCmd;
+import org.apache.cloudstack.context.CallContext;
+
+import com.cloud.exception.InvalidParameterValueException;
+import com.cloud.user.Account;
+import com.cloud.user.AccountManager;
+import com.cloud.utils.DateUtil;
+import com.cloud.utils.db.EntityManager;
+import com.cloud.utils.exception.CloudRuntimeException;
+
+public class ParamProcessWorker implements DispatchWorker {
+
+ private static final Logger s_logger = Logger.getLogger(ParamProcessWorker.class.getName());
+
+ @Inject
+ protected AccountManager _accountMgr;
+
+ @Inject
+ protected EntityManager _entityMgr;
+
+ @Override
+ public void handle(final DispatchTask task) {
+ processParameters(task.getCmd(), task.getParams());
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public void processParameters(final BaseCmd cmd, final Map params) {
+ final Map<Object, AccessType> entitiesToAccess = new HashMap<Object, AccessType>();
+
+ final List<Field> cmdFields = cmd.getParamFields();
+
+ for (final Field field : cmdFields) {
+ final Parameter parameterAnnotation = field.getAnnotation(Parameter.class);
+ final Object paramObj = params.get(parameterAnnotation.name());
+ if (paramObj == null) {
+ if (parameterAnnotation.required()) {
+ throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to execute API command " +
+ cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) +
+ " due to missing parameter " + parameterAnnotation.name());
+ }
+ continue;
+ }
+
+ // marshall the parameter into the correct type and set the field value
+ try {
+ setFieldValue(field, cmd, paramObj, parameterAnnotation);
+ } catch (final IllegalArgumentException argEx) {
+ if (s_logger.isDebugEnabled()) {
+ s_logger.debug("Unable to execute API command " + cmd.getCommandName() + " due to invalid value " + paramObj + " for parameter " +
+ parameterAnnotation.name());
+ }
+ throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to execute API command " +
+ cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + " due to invalid value " + paramObj + " for parameter " +
+ parameterAnnotation.name());
+ } catch (final ParseException parseEx) {
+ if (s_logger.isDebugEnabled()) {
+ s_logger.debug("Invalid date parameter " + paramObj + " passed to command " + cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8));
+ }
+ throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to parse date " + paramObj + " for command " +
+ cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + ", please pass dates in the format mentioned in the api documentation");
+ } catch (final InvalidParameterValueException invEx) {
+ throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to execute API command " +
+ cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8) + " due to invalid value. " + invEx.getMessage());
+ } catch (final CloudRuntimeException cloudEx) {
+ s_logger.error("CloudRuntimeException", cloudEx);
+ // FIXME: Better error message? This only happens if the API command is not executable, which typically
+ //means
+ // there was
+ // and IllegalAccessException setting one of the parameters.
+ throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Internal error executing API command " +
+ cmd.getCommandName().substring(0, cmd.getCommandName().length() - 8));
+ }
+
+ //check access on the resource this field points to
+ try {
+ final ACL checkAccess = field.getAnnotation(ACL.class);
+ final CommandType fieldType = parameterAnnotation.type();
+
+ if (checkAccess != null) {
+ // Verify that caller can perform actions in behalf of vm owner
+ //acumulate all Controlled Entities together.
+
+ //parse the array of resource types and in case of map check access on key or value or both as specified in @acl
+ //implement external dao for classes that need findByName
+ //for maps, specify access to be checkd on key or value.
+
+ // find the controlled entity DBid by uuid
+ if (parameterAnnotation.entityType() != null) {
+ final Class<?>[] entityList = parameterAnnotation.entityType()[0].getAnnotation(EntityReference.class).value();
+
+ for (final Class entity : entityList) {
+ // Check if the parameter type is a single
+ // Id or list of id's/name's
+ switch (fieldType) {
+ case LIST:
+ final CommandType listType = parameterAnnotation.collectionType();
+ switch (listType) {
+ case LONG:
+ case UUID:
+ final List<Long> listParam = (List<Long>)field.get(cmd);
+ for (final Long entityId : listParam) {
+ final Object entityObj = _entityMgr.findById(entity, entityId);
+ entitiesToAccess.put(entityObj, checkAccess.accessType());
+ }
+ break;
+ /*
+ * case STRING: List<String> listParam =
+ * new ArrayList<String>(); listParam =
+ * (List)field.get(cmd); for(String
+ * entityName: listParam){
+ * ControlledEntity entityObj =
+ * (ControlledEntity
+ * )daoClassInstance(entityId);
+ * entitiesToAccess.add(entityObj); }
+ * break;
+ */
+ default:
+ break;
+ }
+ break;
+ case LONG:
+ case UUID:
+ final Object entityObj = _entityMgr.findById(entity, (Long)field.get(cmd));
+ entitiesToAccess.put(entityObj, checkAccess.accessType());
+ break;
+ default:
+ break;
+ }
+
+ if (ControlledEntity.class.isAssignableFrom(entity)) {
+ if (s_logger.isDebugEnabled()) {
+ s_logger.debug("ControlledEntity name is:" + entity.getName());
+ }
+ }
+
+ if (InfrastructureEntity.class.isAssignableFrom(entity)) {
+ if (s_logger.isDebugEnabled()) {
+ s_logger.debug("InfrastructureEntity name is:" + entity.getName());
+ }
+ }
+ }
+
+ }
+
+ }
+
+ } catch (final IllegalArgumentException e) {
+ s_logger.error("Error initializing command " + cmd.getCommandName() + ", field " + field.getName() + " is not accessible.");
+ throw new CloudRuntimeException("Internal error initializing parameters for command " + cmd.getCommandName() + " [field " + field.getName() +
+ " is not accessible]");
+ } catch (final IllegalAccessException e) {
+ s_logger.error("Error initializing command " + cmd.getCommandName() + ", field " + field.getName() + " is not accessible.");
+ throw new CloudRuntimeException("Internal error initializing parameters for command " + cmd.getCommandName() + " [field " + field.getName() +
+ " is not accessible]");
+ }
+
+ }
+
+ doAccessChecks(cmd, entitiesToAccess);
+ }
+
+
+ private void doAccessChecks(final BaseCmd cmd, final Map<Object, AccessType> entitiesToAccess) {
+ final Account caller = CallContext.current().getCallingAccount();
+ final Account owner = _accountMgr.getActiveAccountById(cmd.getEntityOwnerId());
+
+ if (cmd instanceof BaseAsyncCreateCmd) {
+ //check that caller can access the owner account.
+ _accountMgr.checkAccess(caller, null, true, owner);
+ }
+
+ if (!entitiesToAccess.isEmpty()) {
+ //check that caller can access the owner account.
+ _accountMgr.checkAccess(caller, null, true, owner);
+ for (final Object entity : entitiesToAccess.keySet()) {
+ if (entity instanceof ControlledEntity) {
+ _accountMgr.checkAccess(caller, entitiesToAccess.get(entity), true, (ControlledEntity)entity);
+ } else if (entity instanceof InfrastructureEntity) {
+ //FIXME: Move this code in adapter, remove code from Account manager
+ }
+ }
+ }
+ }
+
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private void setFieldValue(final Field field, final BaseCmd cmdObj, final Object paramObj, final Parameter annotation) throws IllegalArgumentException, ParseException {
+ try {
+ field.setAccessible(true);
+ final CommandType fieldType = annotation.type();
+ switch (fieldType) {
+ case BOOLEAN:
+ field.set(cmdObj, Boolean.valueOf(paramObj.toString()));
+ break;
+ case DATE:
+ // This piece of code is for maintaining backward compatibility
+ // and support both the date formats(Bug 9724)
+ // Do the date messaging for ListEventsCmd only
+ if (cmdObj instanceof ListEventsCmd || cmdObj instanceof DeleteEventsCmd || cmdObj instanceof ArchiveEventsCmd ||
+ cmdObj instanceof ArchiveAlertsCmd || cmdObj instanceof DeleteAlertsCmd) {
+ final boolean isObjInNewDateFormat = isObjInNewDateFormat(paramObj.toString());
+ if (isObjInNewDateFormat) {
+ final DateFormat newFormat = BaseCmd.NEW_INPUT_FORMAT;
+ synchronized (newFormat) {
+ field.set(cmdObj, newFormat.parse(paramObj.toString()));
+ }
+ } else {
+ final DateFormat format = BaseCmd.INPUT_FORMAT;
+ synchronized (format) {
+ Date date = format.parse(paramObj.toString());
+ if (field.getName().equals("startDate")) {
+ date = messageDate(date, 0, 0, 0);
+ } else if (field.getName().equals("endDate")) {
+ date = messageDate(date, 23, 59, 59);
+ }
+ field.set(cmdObj, date);
+ }
+ }
+ } else {
+ final DateFormat format = BaseCmd.INPUT_FORMAT;
+ synchronized (format) {
+ format.setLenient(false);
+ field.set(cmdObj, format.parse(paramObj.toString()));
+ }
+ }
+ break;
+ case FLOAT:
+ // Assuming that the parameters have been checked for required before now,
+ // we ignore blank or null values and defer to the command to set a default
+ // value for optional parameters ...
+ if (paramObj != null && isNotBlank(paramObj.toString())) {
+ field.set(cmdObj, Float.valueOf(paramObj.toString()));
+ }
+ break;
+ case INTEGER:
+ // Assuming that the parameters have been checked for required before now,
+ // we ignore blank or null values and defer to the command to set a default
+ // value for optional parameters ...
+ if (paramObj != null && isNotBlank(paramObj.toString())) {
+ field.set(cmdObj, Integer.valueOf(paramObj.toString()));
+ }
+ break;
+ case LIST:
+ final List listParam = new ArrayList();
+ final StringTokenizer st = new StringTokenizer(paramObj.toString(), ",");
+ while (st.hasMoreTokens()) {
+ final String token = st.nextToken();
+ final CommandType listType = annotation.collectionType();
+ switch (listType) {
+ case INTEGER:
+ listParam.add(Integer.valueOf(token));
+ break;
+ case UUID:
+ if (token.isEmpty())
+ break;
+ final Long internalId = translateUuidToInternalId(token, annotation);
+ listParam.add(internalId);
+ break;
+ case LONG: {
+ listParam.add(Long.valueOf(token));
+ }
+ break;
+ case SHORT:
+ listParam.add(Short.valueOf(token));
+ case STRING:
+ listParam.add(token);
+ break;
+ }
+ }
+ field.set(cmdObj, listParam);
+ break;
+ case UUID:
+ if (paramObj.toString().isEmpty())
+ break;
+ final Long internalId = translateUuidToInternalId(paramObj.toString(), annotation);
+ field.set(cmdObj, internalId);
+ break;
+ case LONG:
+ field.set(cmdObj, Long.valueOf(paramObj.toString()));
+ break;
+ case SHORT:
+ field.set(cmdObj, Short.valueOf(paramObj.toString()));
+ break;
+ case STRING:
+ if ((paramObj != null) && paramObj.toString().length() > annotation.length()) {
+ s_logger.error("Value greater than max allowed length " + annotation.length() + " for param: " + field.getName());
+ throw new InvalidParameterValueException("Value greater than max allowed length " + annotation.length() + " for param: " + field.getName());
+ }
+ field.set(cmdObj, paramObj.toString());
+ break;
+ case TZDATE:
+ field.set(cmdObj, DateUtil.parseTZDateString(paramObj.toString()));
+ break;
+ case MAP:
+ default:
+ field.set(cmdObj, paramObj);
+ break;
+ }
+ } catch (final IllegalAccessException ex) {
+ s_logger.error("Error initializing command " + cmdObj.getCommandName() + ", field " + field.getName() + " is not accessible.");
+ throw new CloudRuntimeException("Internal error initializing parameters for command " + cmdObj.getCommandName() + " [field " + field.getName() +
+ " is not accessible]");
+ }
+ }
+
+ private boolean isObjInNewDateFormat(final String string) {
+ final Matcher matcher = BaseCmd.newInputDateFormat.matcher(string);
+ return matcher.matches();
+ }
+
+ private Date messageDate(final Date date, final int hourOfDay, final int minute, final int second) {
+ final Calendar cal = Calendar.getInstance();
+ cal.setTime(date);
+ cal.set(Calendar.HOUR_OF_DAY, hourOfDay);
+ cal.set(Calendar.MINUTE, minute);
+ cal.set(Calendar.SECOND, second);
+ return cal.getTime();
+ }
+
+ private Long translateUuidToInternalId(final String uuid, final Parameter annotation) {
+ if (uuid.equals("-1")) {
+ // FIXME: This is to handle a lot of hardcoded special cases where -1 is sent
+ // APITODO: Find and get rid of all hardcoded params in API Cmds and service layer
+ return -1L;
+ }
+ Long internalId = null;
+ // If annotation's empty, the cmd existed before 3.x try conversion to long
+ final boolean isPre3x = annotation.since().isEmpty();
+ // Match against Java's UUID regex to check if input is uuid string
+ final boolean isUuid = uuid.matches("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$");
+ // Enforce that it's uuid for newly added apis from version 3.x
+ if (!isPre3x && !isUuid)
+ return null;
+ // Allow both uuid and internal id for pre3x apis
+ if (isPre3x && !isUuid) {
+ try {
+ internalId = Long.parseLong(uuid);
+ } catch (final NumberFormatException e) {
+ internalId = null;
+ }
+ if (internalId != null)
+ return internalId;
+ }
+ // There may be multiple entities defined on the @EntityReference of a Response.class
+ // UUID CommandType would expect only one entityType, so use the first entityType
+ final Class<?>[] entities = annotation.entityType()[0].getAnnotation(EntityReference.class).value();
+ // Go through each entity which is an interface to a VO class and get a VO object
+ // Try to getId() for the object using reflection, break on first non-null value
+ for (final Class<?> entity : entities) {
+ // For backward compatibility, we search within removed entities and let service layer deal
+ // with removed ones, return empty response or error
+ final Object objVO = _entityMgr.findByUuidIncludingRemoved(entity, uuid);
+ if (objVO == null) {
+ continue;
+ }
+ // Invoke the getId method, get the internal long ID
+ // If that fails hide exceptions as the uuid may not exist
+ try {
+ internalId = ((InternalIdentity)objVO).getId();
+ } catch (final IllegalArgumentException e) {
+ } catch (final NullPointerException e) {
+ }
+ // Return on first non-null Id for the uuid entity
+ if (internalId != null)
+ break;
+ }
+ if (internalId == null) {
+ if (s_logger.isDebugEnabled())
+ s_logger.debug("Object entity uuid = " + uuid + " does not exist in the database.");
+ throw new InvalidParameterValueException("Invalid parameter " + annotation.name() + " value=" + uuid +
+ " due to incorrect long value format, or entity does not exist or due to incorrect parameter annotation for the field in api cmd class.");
+ }
+ return internalId;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/c211f0bb/server/src/com/cloud/api/dispatch/ParamUnpackWorker.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/dispatch/ParamUnpackWorker.java b/server/src/com/cloud/api/dispatch/ParamUnpackWorker.java
new file mode 100644
index 0000000..12e1226
--- /dev/null
+++ b/server/src/com/cloud/api/dispatch/ParamUnpackWorker.java
@@ -0,0 +1,114 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 com.cloud.api.dispatch;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.Logger;
+
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.ServerApiException;
+
+public class ParamUnpackWorker implements DispatchWorker {
+
+ private static final Logger s_logger = Logger.getLogger(ParamUnpackWorker.class);
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ @Override
+ public void handle(final DispatchTask task) throws ServerApiException {
+ final Map<String, Object> lowercaseParams = new HashMap<String, Object>();
+ final Map<String, String> params = task.getParams();
+ for (final String key : params.keySet()) {
+ final int arrayStartIndex = key.indexOf('[');
+ final int arrayStartLastIndex = key.lastIndexOf('[');
+ if (arrayStartIndex != arrayStartLastIndex) {
+ throw new ServerApiException(ApiErrorCode.MALFORMED_PARAMETER_ERROR, "Unable to decode parameter " + key +
+ "; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup");
+ }
+
+ if (arrayStartIndex > 0) {
+ final int arrayEndIndex = key.indexOf(']');
+ final int arrayEndLastIndex = key.lastIndexOf(']');
+ if ((arrayEndIndex < arrayStartIndex) || (arrayEndIndex != arrayEndLastIndex)) {
+ // malformed parameter
+ throw new ServerApiException(ApiErrorCode.MALFORMED_PARAMETER_ERROR, "Unable to decode parameter " + key +
+ "; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup");
+ }
+
+ // Now that we have an array object, check for a field name in the case of a complex object
+ final int fieldIndex = key.indexOf('.');
+ String fieldName = null;
+ if (fieldIndex < arrayEndIndex) {
+ throw new ServerApiException(ApiErrorCode.MALFORMED_PARAMETER_ERROR, "Unable to decode parameter " + key +
+ "; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup");
+ } else {
+ fieldName = key.substring(fieldIndex + 1);
+ }
+
+ // parse the parameter name as the text before the first '[' character
+ String paramName = key.substring(0, arrayStartIndex);
+ paramName = paramName.toLowerCase();
+
+ Map<Integer, Map> mapArray = null;
+ Map<String, Object> mapValue = null;
+ final String indexStr = key.substring(arrayStartIndex + 1, arrayEndIndex);
+ int index = 0;
+ boolean parsedIndex = false;
+ try {
+ if (indexStr != null) {
+ index = Integer.parseInt(indexStr);
+ parsedIndex = true;
+ }
+ } catch (final NumberFormatException nfe) {
+ s_logger.warn("Invalid parameter " + key + " received, unable to parse object array, returning an error.");
+ }
+
+ if (!parsedIndex) {
+ throw new ServerApiException(ApiErrorCode.MALFORMED_PARAMETER_ERROR, "Unable to decode parameter " + key +
+ "; if specifying an object array, please use parameter[index].field=XXX, e.g. userGroupList[0].group=httpGroup");
+ }
+
+ final Object value = lowercaseParams.get(paramName);
+ if (value == null) {
+ // for now, assume object array with sub fields
+ mapArray = new HashMap<Integer, Map>();
+ mapValue = new HashMap<String, Object>();
+ mapArray.put(Integer.valueOf(index), mapValue);
+ } else if (value instanceof Map) {
+ mapArray = (HashMap)value;
+ mapValue = mapArray.get(Integer.valueOf(index));
+ if (mapValue == null) {
+ mapValue = new HashMap<String, Object>();
+ mapArray.put(Integer.valueOf(index), mapValue);
+ }
+ }
+
+ // we are ready to store the value for a particular field into the map for this object
+ mapValue.put(fieldName, params.get(key));
+
+ lowercaseParams.put(paramName, mapArray);
+ } else {
+ lowercaseParams.put(key.toLowerCase(), params.get(key));
+ }
+ }
+
+ // The chain continues processing the unpacked parameters
+ task.setParams(lowercaseParams);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cloudstack/blob/c211f0bb/server/src/com/cloud/api/dispatch/SpecificCmdValidationWorker.java
----------------------------------------------------------------------
diff --git a/server/src/com/cloud/api/dispatch/SpecificCmdValidationWorker.java b/server/src/com/cloud/api/dispatch/SpecificCmdValidationWorker.java
new file mode 100644
index 0000000..3566a1a
--- /dev/null
+++ b/server/src/com/cloud/api/dispatch/SpecificCmdValidationWorker.java
@@ -0,0 +1,34 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you 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 com.cloud.api.dispatch;
+
+/**
+ * This worker validates parameters in a semantic way, that is of
+ * course specific for each {@link BaseCmd}, so actually it delegates
+ * the validation on the {@link BaseCmd} itself
+ *
+ */
+public class SpecificCmdValidationWorker implements DispatchWorker {
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void handle(final DispatchTask task) {
+ task.getCmd().validateSpecificParameters(task.getParams());
+ }
+
+}