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()); + } + +}
