content: // 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 org.apache.cloudstack.api;
  
  import java.lang.reflect.Field;
  import java.text.DateFormat;
  import java.text.SimpleDateFormat;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.regex.Pattern;
  
  import javax.inject.Inject;
  
  import com.cloud.bgp.BGPService;
  import org.apache.cloudstack.acl.ProjectRoleService;
  import org.apache.cloudstack.acl.RoleService;
  import org.apache.cloudstack.acl.RoleType;
  import org.apache.cloudstack.affinity.AffinityGroupService;
  import org.apache.cloudstack.alert.AlertService;
  import org.apache.cloudstack.annotation.AnnotationService;
  import org.apache.cloudstack.context.CallContext;
  import org.apache.cloudstack.gpu.GpuService;
  import org.apache.cloudstack.network.RoutedIpv4Manager;
  import org.apache.cloudstack.network.lb.ApplicationLoadBalancerService;
  import org.apache.cloudstack.network.lb.InternalLoadBalancerVMService;
  import org.apache.cloudstack.query.QueryService;
  import org.apache.cloudstack.storage.object.BucketApiService;
  import org.apache.cloudstack.storage.ImageStoreService;
  import org.apache.cloudstack.storage.template.VnfTemplateManager;
  import org.apache.cloudstack.usage.UsageService;
  import org.apache.commons.collections.MapUtils;
  import org.apache.logging.log4j.Logger;
  import org.apache.logging.log4j.LogManager;
  
  import com.cloud.configuration.ConfigurationService;
  import com.cloud.exception.ConcurrentOperationException;
  import com.cloud.exception.InsufficientCapacityException;
  import com.cloud.exception.NetworkRuleConflictException;
  import com.cloud.exception.ResourceAllocationException;
  import com.cloud.exception.ResourceUnavailableException;
  import com.cloud.network.Ipv6Service;
  import com.cloud.network.NetworkModel;
  import com.cloud.network.NetworkService;
  import com.cloud.network.NetworkUsageService;
  import com.cloud.network.StorageNetworkService;
  import com.cloud.network.VpcVirtualNetworkApplianceService;
  import com.cloud.network.as.AutoScaleService;
  import com.cloud.network.firewall.FirewallService;
  import com.cloud.network.lb.LoadBalancingRulesService;
  import com.cloud.network.rules.RulesService;
  import com.cloud.network.security.SecurityGroupService;
  import com.cloud.network.vpc.NetworkACLService;
  import com.cloud.network.vpc.VpcProvisioningService;
  import com.cloud.network.vpc.VpcService;
  import com.cloud.network.vpn.RemoteAccessVpnService;
  import com.cloud.network.vpn.Site2SiteVpnService;
  import com.cloud.projects.ProjectService;
  import com.cloud.resource.ResourceService;
  import com.cloud.server.ManagementService;
  import com.cloud.server.ResourceIconManager;
  import com.cloud.server.ResourceManagerUtil;
  import com.cloud.server.ResourceMetaDataService;
  import com.cloud.server.TaggedResourceService;
  import com.cloud.storage.DataStoreProviderApiService;
  import com.cloud.storage.StorageService;
  import com.cloud.storage.VolumeApiService;
  import com.cloud.storage.snapshot.SnapshotApiService;
  import com.cloud.template.TemplateApiService;
  import com.cloud.user.Account;
  import com.cloud.user.AccountService;
  import com.cloud.user.DomainService;
  import com.cloud.user.ResourceLimitService;
  import com.cloud.utils.HttpUtils;
  import com.cloud.utils.ReflectUtil;
  import com.cloud.utils.db.EntityManager;
  import com.cloud.utils.db.UUIDManager;
  import com.cloud.vm.UserVmService;
  import com.cloud.vm.VmDetailConstants;
  import com.cloud.vm.snapshot.VMSnapshotService;
  
  public abstract class BaseCmd {
      protected transient Logger logger = LogManager.getLogger(getClass());
      public static final String RESPONSE_SUFFIX = "response";
      public static final String RESPONSE_TYPE_XML = HttpUtils.RESPONSE_TYPE_XML;
      public static final String RESPONSE_TYPE_JSON = HttpUtils.RESPONSE_TYPE_JSON;
      public static final String USER_ERROR_MESSAGE = "Internal error executing command, please contact your system administrator";
      public static Pattern newInputDateFormat = Pattern.compile("[\\d]+-[\\d]+-[\\d]+ [\\d]+:[\\d]+:[\\d]+");
      private static final DateFormat s_outputFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
      protected static final Map<Class<?>, List<Field>> fieldsForCmdClass = new HashMap<Class<?>, List<Field>>();
  
      public static enum HTTPMethod {
          GET, POST, PUT, DELETE
      }
      public static enum CommandType {
          BOOLEAN, DATE, FLOAT, DOUBLE, INTEGER, SHORT, LIST, LONG, OBJECT, MAP, STRING, UUID
      }
  
      private Object _responseObject;
      private Map<String, String> fullUrlParams;
      private HTTPMethod httpMethod;
      @Parameter(name = "response", type = CommandType.STRING)
      private String responseType;
  
      @Inject
      public ConfigurationService _configService;
      @Inject
      public AccountService _accountService;
      @Inject
      public RoleService roleService;
      @Inject
      public ProjectRoleService projRoleService;
      @Inject
      public UserVmService _userVmService;
      @Inject
      public GpuService gpuService;
      @Inject
      public ManagementService _mgr;
      @Inject
      public StorageService _storageService;
      @Inject
      public VolumeApiService _volumeService;
      @Inject
      public ResourceService _resourceService;
      @Inject
      public NetworkService _networkService;
      @Inject
      public TemplateApiService _templateService;
      @Inject
      public ImageStoreService _imageStoreService;
      @Inject
      public SecurityGroupService _securityGroupService;
      @Inject
      public SnapshotApiService _snapshotService;
      @Inject
      public VpcVirtualNetworkApplianceService _routerService;
      @Inject
      public ResponseGenerator _responseGenerator;
      @Inject
      public EntityManager _entityMgr;
      @Inject
      public RulesService _rulesService;
      @Inject
      public AutoScaleService _autoScaleService;
      @Inject
      public LoadBalancingRulesService _lbService;
      @Inject
      public RemoteAccessVpnService _ravService;
      @Inject
      public ProjectService _projectService;
      @Inject
      public FirewallService _firewallService;
      @Inject
      public DomainService _domainService;
      @Inject
      public ResourceLimitService _resourceLimitService;
      @Inject
      public StorageNetworkService _storageNetworkService;
      @Inject
      public TaggedResourceService _taggedResourceService;
      @Inject
      public ResourceManagerUtil resourceManagerUtil;
      @Inject
      public ResourceMetaDataService _resourceMetaDataService;
      @Inject
      public VpcService _vpcService;
      @Inject
      public NetworkACLService _networkACLService;
      @Inject
      public Site2SiteVpnService _s2sVpnService;
      @Inject
      public QueryService _queryService;
      @Inject
      public UsageService _usageService;
      @Inject
      public NetworkUsageService _networkUsageService;
      @Inject
      public VMSnapshotService _vmSnapshotService;
      @Inject
      public DataStoreProviderApiService dataStoreProviderApiService;
      @Inject
      public VpcProvisioningService _vpcProvSvc;
      @Inject
      public ApplicationLoadBalancerService _newLbSvc;
      @Inject
      public ApplicationLoadBalancerService _appLbService;
      @Inject
      public AffinityGroupService _affinityGroupService;
      @Inject
      public InternalLoadBalancerVMService _internalLbSvc;
      @Inject
      public NetworkModel _ntwkModel;
      @Inject
      public AlertService _alertSvc;
      @Inject
      public UUIDManager _uuidMgr;
      @Inject
      public AnnotationService annotationService;
      @Inject
      public ResourceIconManager resourceIconManager;
      @Inject
      public Ipv6Service ipv6Service;
      @Inject
      public VnfTemplateManager vnfTemplateManager;
      @Inject
      public BucketApiService _bucketService;
      @Inject
      public BGPService bgpService;
  
      @Inject
      public RoutedIpv4Manager routedIpv4Manager;
  
      public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException,
          ResourceAllocationException, NetworkRuleConflictException;
  
      public void configure() {
      }
  
      public HTTPMethod getHttpMethod() {
          return httpMethod;
      }
  
      public void setHttpMethod(final String method) {
          if (method != null) {
              if (method.equalsIgnoreCase("GET"))
                  httpMethod = HTTPMethod.GET;
              else if (method.equalsIgnoreCase("PUT"))
                  httpMethod = HTTPMethod.PUT;
              else if (method.equalsIgnoreCase("POST"))
                  httpMethod = HTTPMethod.POST;
              else if (method.equalsIgnoreCase("DELETE"))
                  httpMethod = HTTPMethod.DELETE;
          } else {
              httpMethod = HTTPMethod.GET;
          }
      }
  
      public String getResponseType() {
          if (responseType == null) {
              return RESPONSE_TYPE_XML;
          }
          return responseType;
      }
  
      public void setResponseType(final String responseType) {
          this.responseType = responseType;
      }
  
      /**
       * Gets the CommandName based on the class annotations: the value from {@link APICommand#name()}
       *
       * @return the value from {@link APICommand#name()}
       */
      public static String getCommandNameByClass(Class<?> clazz) {
          String cmdName = null;
          APICommand apiClassAnnotation = clazz.getAnnotation(APICommand.class);
  
          if (apiClassAnnotation != null && apiClassAnnotation.name() != null) {
              cmdName = apiClassAnnotation.name();
          } else {
              cmdName = clazz.getName();
          }
          return cmdName;
      }
  
      public String getActualCommandName() {
          return getCommandNameByClass(this.getClass());
      }
  
      public String getCommandName() {
          return getResponseNameByClass(this.getClass());
      }
  
      /**
       * Retrieves the name defined in {@link APICommand#name()}, in lower case, with the prefix {@link BaseCmd#RESPONSE_SUFFIX}
       */
      public static String getResponseNameByClass(Class<?> clazz) {
          return getCommandNameByClass(clazz).toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
      }
  
      /**
       * For commands the API framework needs to know the owner of the object being acted upon. This method is
       * used to determine that information.
       *
       * @return the id of the account that owns the object being acted upon
       */
      public abstract long getEntityOwnerId();
  
      public List<Long> getEntityOwnerIds() {
          return null;
      }
  
      public Object getResponseObject() {
          return _responseObject;
      }
  
      public void setResponseObject(final Object responseObject) {
          _responseObject = responseObject;
      }
  
      public static String getDateString(final Date date) {
          if (date == null) {
              return "";
          }
          String formattedString = null;
          synchronized (s_outputFormat) {
              formattedString = s_outputFormat.format(date);
          }
          return formattedString;
      }
  
      protected List<Field> getAllFieldsForClass(final Class<?> clazz) {
          List<Field> filteredFields = fieldsForCmdClass.get(clazz);
  
          // If list of fields was not cached yet
          if (filteredFields == null) {
              final List<Field> allFields = ReflectUtil.getAllFieldsForClass(this.getClass(), BaseCmd.class);
              filteredFields = new ArrayList<Field>();
  
              for (final Field field : allFields) {
                  final Parameter parameterAnnotation = field.getAnnotation(Parameter.class);
                  if ((parameterAnnotation != null) && parameterAnnotation.expose()) {
                      filteredFields.add(field);
                      }
                  }
  
              // Cache the prepared list for future use
              fieldsForCmdClass.put(clazz, filteredFields);
                      }
          return filteredFields;
                  }
  
      /**
       * This method doesn't return all the @{link Parameter}, but only the ones exposed
       * and allowed for current @{link RoleType}. This method will get the fields for a given
       * Cmd class only once and never again, so in case of a dynamic update the result would
       * be obsolete (this might be a plugin update. It is agreed upon that we will not do
       * upgrades dynamically but in case we come back on that decision we need to revisit this)
       *
       * @return
       */
      public List<Field> getParamFields() {
          final List<Field> allFields = getAllFieldsForClass(this.getClass());
          final List<Field> validFields = new ArrayList<Field>();
          final Account caller = CallContext.current().getCallingAccount();
  
          for (final Field field : allFields) {
              final Parameter parameterAnnotation = field.getAnnotation(Parameter.class);
  
              //TODO: Annotate @Validate on API Cmd classes, FIXME how to process Validate
              final RoleType[] allowedRoles = parameterAnnotation.authorized();
              boolean roleIsAllowed = true;
              if (allowedRoles.length > 0) {
                  roleIsAllowed = false;
                  for (final RoleType allowedRole : allowedRoles) {
                      if (allowedRole.getAccountType() == caller.getType()) {
                          roleIsAllowed = true;
                          break;
                      }
                  }
              }
  
              if (roleIsAllowed) {
                  validFields.add(field);
              } else {
                  logger.debug("Ignoring parameter " + parameterAnnotation.name() + " as the caller is not authorized to pass it in");
              }
          }
  
          return validFields;
      }
  
      public void setFullUrlParams(final Map<String, String> map) {
          fullUrlParams = map;
      }
  
      public Map<String, String> getFullUrlParams() {
          return fullUrlParams;
      }
  
      /**
       * To be overwritten by any class who needs specific validation
       */
      public void validateSpecificParameters(final Map<String, String> params){
          // To be overwritten by any class who needs specific validation
      }
  
      /**
       * display flag is used to control the display of the resource only to the end user. It doesn't affect Root Admin.
       * @return display flag
       */
      public boolean isDisplay(){
          CallContext context = CallContext.current();
          Map<Object, Object> contextMap = context.getContextParameters();
          boolean isDisplay = true;
  
          // Iterate over all the first class entities in context and check their display property.
          for(Map.Entry<Object, Object> entry : contextMap.entrySet()){
              try{
                  Object key = entry.getKey();
                  Class clz = Class.forName((String)key);
                  if(Displayable.class.isAssignableFrom(clz)){
                      final Object objVO = getEntityVO(clz, entry.getValue());
                      isDisplay = ((Displayable) objVO).isDisplay();
                  }
  
                  // If the flag is false break immediately
                  if(!isDisplay)
                      break;
              } catch (Exception e){
                  logger.trace("Caught exception while checking first class entities for display property, continuing on", e);
              }
          }
  
          context.setEventDisplayEnabled(isDisplay);
          return isDisplay;
  
      }
  
      private Object getEntityVO(Class entityType, Object entityId){
  
          // entityId can be internal db id or UUID so accordingly call findbyId or findByUUID
  
          if (entityId instanceof Long){
              // Its internal db id - use findById
              return _entityMgr.findById(entityType, (Long)entityId);
          } else if(entityId instanceof String){
              try{
                  // In case its an async job the internal db id would be a string because of json deserialization
                  Long internalId = Long.valueOf((String) entityId);
                  return _entityMgr.findById(entityType, internalId);
              } catch (NumberFormatException e){
                 // It is uuid - use findByUuid`
                 return _entityMgr.findByUuid(entityType, (String)entityId);
              }
          }
  
          return null;
      }
  
      /**
       * Commands that generate action events associated to a resource and
       * async commands that want to be tracked as part of the listXXX commands
       * need to provide implementations of the two following methods,
       * getApiResourceId() and getApiResourceType()
       *
       * getApiResourceId() should return the id of the object the async command is executing on
       * getApiResourceType() should return a type from the ApiCommandResourceType enumeration
       */
      public Long getApiResourceId() {
          return null;
      }
  
      public ApiCommandResourceType getApiResourceType() {
          return ApiCommandResourceType.None;
      }
  
      public Map<String, String> convertDetailsToMap(Map details) {
          Map<String, String> detailsMap = new HashMap<String, String>();
          if (MapUtils.isNotEmpty(details)) {
              Collection parameterCollection = details.values();
              Iterator iter = parameterCollection.iterator();
              while (iter.hasNext()) {
                  HashMap<String, String> value = (HashMap<String, String>)iter.next();
                  for (Map.Entry<String,String> entry: value.entrySet()) {
                      detailsMap.put(entry.getKey(),entry.getValue());
                  }
              }
          }
          return detailsMap;
      }
  
      public Map<String, String> convertExternalDetailsToMap(Map externalDetails) {
          Map<String, String> customparameterMap = convertDetailsToMap(externalDetails);
          Map<String, String> details = new HashMap<>();
          for (String key : customparameterMap.keySet()) {
              String value = customparameterMap.get(key);
              details.put(VmDetailConstants.EXTERNAL_DETAIL_PREFIX + key, value);
          }
          return details;
      }
  }
  