Java tutorial
// 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; 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.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.regex.Matcher; import javax.annotation.PostConstruct; import javax.inject.Inject; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.InfrastructureEntity; import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.BaseAsyncCreateCmd; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.BaseCmd.CommandType; import org.apache.cloudstack.api.BaseListCmd; 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.Validate; import org.apache.cloudstack.api.command.user.event.ListEventsCmd; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; import com.cloud.async.AsyncCommandQueued; import com.cloud.async.AsyncJobManager; import com.cloud.dao.EntityManager; import com.cloud.exception.AccountLimitException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.user.Account; import com.cloud.user.AccountManager; import com.cloud.user.UserContext; import com.cloud.utils.DateUtil; import com.cloud.utils.ReflectUtil; import com.cloud.utils.component.ComponentContext; import com.cloud.utils.exception.CSExceptionErrorCode; import com.cloud.utils.exception.CloudRuntimeException; @Component public class ApiDispatcher { private static final Logger s_logger = Logger.getLogger(ApiDispatcher.class.getName()); Long _createSnapshotQueueSizeLimit; @Inject AsyncJobManager _asyncMgr = null; @Inject AccountManager _accountMgr = null; @Inject EntityManager _entityMgr = null; private static ApiDispatcher s_instance; public static ApiDispatcher getInstance() { return s_instance; } public ApiDispatcher() { } @PostConstruct void init() { s_instance = this; } public void setCreateSnapshotQueueSizeLimit(Long snapshotLimit) { _createSnapshotQueueSizeLimit = snapshotLimit; } public void dispatchCreateCmd(BaseAsyncCreateCmd cmd, Map<String, String> params) throws Exception { processParameters(cmd, params); UserContext ctx = UserContext.current(); ctx.setAccountId(cmd.getEntityOwnerId()); cmd.create(); } private void doAccessChecks(BaseCmd cmd, List<Object> entitiesToAccess) { Account caller = UserContext.current().getCaller(); 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 (Object entity : entitiesToAccess) { if (entity instanceof ControlledEntity) { _accountMgr.checkAccess(caller, null, true, (ControlledEntity) entity); } else if (entity instanceof InfrastructureEntity) { //FIXME: Move this code in adapter, remove code from Account manager } } } } public void dispatch(BaseCmd cmd, Map<String, String> params) throws Exception { processParameters(cmd, params); UserContext ctx = UserContext.current(); ctx.setAccountId(cmd.getEntityOwnerId()); if (cmd instanceof BaseAsyncCmd) { BaseAsyncCmd asyncCmd = (BaseAsyncCmd) cmd; String startEventId = params.get("ctxStartEventId"); ctx.setStartEventId(Long.valueOf(startEventId)); // Synchronise job on the object if needed if (asyncCmd.getJob() != null && asyncCmd.getSyncObjId() != null && asyncCmd.getSyncObjType() != null) { Long queueSizeLimit = null; if (asyncCmd.getSyncObjType() != null && asyncCmd.getSyncObjType().equalsIgnoreCase(BaseAsyncCmd.snapshotHostSyncObject)) { queueSizeLimit = _createSnapshotQueueSizeLimit; } else { queueSizeLimit = 1L; } if (queueSizeLimit != null) { _asyncMgr.syncAsyncJobExecution(asyncCmd.getJob(), asyncCmd.getSyncObjType(), asyncCmd.getSyncObjId().longValue(), queueSizeLimit); } else { s_logger.trace("The queue size is unlimited, skipping the synchronizing"); } } } cmd.execute(); } @SuppressWarnings({ "unchecked", "rawtypes" }) public static void processParameters(BaseCmd cmd, Map<String, String> params) { List<Object> entitiesToAccess = new ArrayList<Object>(); Map<String, Object> unpackedParams = cmd.unpackParams(params); cmd = ComponentContext.getTargetObject(cmd); if (cmd instanceof BaseListCmd) { Object pageSizeObj = unpackedParams.get(ApiConstants.PAGE_SIZE); Long pageSize = null; if (pageSizeObj != null) { pageSize = Long.valueOf((String) pageSizeObj); } if ((unpackedParams.get(ApiConstants.PAGE) == null) && (pageSize != null && pageSize != BaseListCmd.PAGESIZE_UNLIMITED)) { ServerApiException ex = new ServerApiException(ApiErrorCode.PARAM_ERROR, "\"page\" parameter is required when \"pagesize\" is specified"); ex.setCSErrorCode(CSExceptionErrorCode.getCSErrCode(ex.getClass().getName())); throw ex; } else if (pageSize == null && (unpackedParams.get(ApiConstants.PAGE) != null)) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "\"pagesize\" parameter is required when \"page\" is specified"); } } List<Field> fields = ReflectUtil.getAllFieldsForClass(cmd.getClass(), BaseCmd.class); for (Field field : fields) { Parameter parameterAnnotation = field.getAnnotation(Parameter.class); if ((parameterAnnotation == null) || !parameterAnnotation.expose()) { continue; } //TODO: Annotate @Validate on API Cmd classes, FIXME how to process Validate Validate validateAnnotation = field.getAnnotation(Validate.class); Object paramObj = unpackedParams.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 (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 (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 (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 (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 { ACL checkAccess = field.getAnnotation(ACL.class); 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) { Class<?>[] entityList = parameterAnnotation.entityType()[0] .getAnnotation(EntityReference.class).value(); for (Class entity : entityList) { // Check if the parameter type is a single // Id or list of id's/name's switch (fieldType) { case LIST: CommandType listType = parameterAnnotation.collectionType(); switch (listType) { case LONG: case UUID: List<Long> listParam = (List<Long>) field.get(cmd); for (Long entityId : listParam) { Object entityObj = s_instance._entityMgr.findById(entity, entityId); entitiesToAccess.add(entityObj); } 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: Object entityObj = s_instance._entityMgr.findById(entity, (Long) field.get(cmd)); entitiesToAccess.add(entityObj); 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 (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 (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]"); } } //check access on the entities. getInstance().doAccessChecks(cmd, entitiesToAccess); } private static Long translateUuidToInternalId(String uuid, 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 boolean isPre3x = annotation.since().isEmpty(); // Match against Java's UUID regex to check if input is uuid string 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 (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 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 (Class<?> entity : entities) { // For backward compatibility, we search within removed entities and let service layer deal // with removed ones, return empty response or error Object objVO = s_instance._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 (IllegalArgumentException e) { } catch (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 value=" + uuid + " due to incorrect long value format, or entity was not found as it may have been deleted, or due to incorrect parameter annotation for the field in api cmd."); } return internalId; } @SuppressWarnings({ "unchecked", "rawtypes" }) private static void setFieldValue(Field field, BaseCmd cmdObj, Object paramObj, Parameter annotation) throws IllegalArgumentException, ParseException { try { field.setAccessible(true); 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) { boolean isObjInNewDateFormat = isObjInNewDateFormat(paramObj.toString()); if (isObjInNewDateFormat) { DateFormat newFormat = BaseCmd.NEW_INPUT_FORMAT; synchronized (newFormat) { field.set(cmdObj, newFormat.parse(paramObj.toString())); } } else { 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 { DateFormat format = BaseCmd.INPUT_FORMAT; format.setLenient(false); synchronized (format) { 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: List listParam = new ArrayList(); StringTokenizer st = new StringTokenizer(paramObj.toString(), ","); while (st.hasMoreTokens()) { String token = st.nextToken(); CommandType listType = annotation.collectionType(); switch (listType) { case INTEGER: listParam.add(Integer.valueOf(token)); break; case UUID: if (token.isEmpty()) break; 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; 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 (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 static boolean isObjInNewDateFormat(String string) { Matcher matcher = BaseCmd.newInputDateFormat.matcher(string); return matcher.matches(); } private static Date messageDate(Date date, int hourOfDay, int minute, int second) { 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(); } public static void plugService(Field field, BaseCmd cmd) { Class<?> fc = field.getType(); Object instance = null; if (instance == null) { throw new CloudRuntimeException("Unable to plug service " + fc.getSimpleName() + " in command " + cmd.getClass().getSimpleName()); } try { field.setAccessible(true); field.set(cmd, instance); } catch (IllegalArgumentException e) { s_logger.error("IllegalArgumentException at plugService for command " + cmd.getCommandName() + ", field " + field.getName()); throw new CloudRuntimeException("Internal error at plugService for command " + cmd.getCommandName() + " [Illegal argumet at field " + field.getName() + "]"); } catch (IllegalAccessException e) { s_logger.error("Error at plugService for command " + cmd.getCommandName() + ", field " + field.getName() + " is not accessible."); throw new CloudRuntimeException("Internal error at plugService for command " + cmd.getCommandName() + " [field " + field.getName() + " is not accessible]"); } } }