/**
   * Creates bucket.
   *
   * <p>NOTE: This is an asynchronous operation.
   *
   * @param param Bucket parameters
   * @param id the URN of a ViPR Project
   * @brief Create Bucket
   * @return Task resource representation
   * @throws InternalException
   */
  @POST
  @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
  @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
  @CheckPermission(
      roles = {Role.TENANT_ADMIN},
      acls = {ACL.OWN, ACL.ALL})
  public TaskResourceRep createBucket(BucketParam param, @QueryParam("project") URI id)
      throws InternalException {
    // check project
    ArgValidator.checkFieldUriType(id, Project.class, "project");

    // Check for all mandatory field
    ArgValidator.checkFieldNotNull(param.getLabel(), "name");

    Project project = _permissionsHelper.getObjectById(id, Project.class);
    ArgValidator.checkEntity(project, id, isIdEmbeddedInURL(id));
    ArgValidator.checkFieldNotNull(project.getTenantOrg(), "project");
    TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, project.getTenantOrg().getURI());

    final String namespace = tenant.getNamespace();
    if (null == namespace || namespace.isEmpty()) {
      throw APIException.badRequests.objNoNamespaceForTenant(tenant.getId());
    }

    // Check if there already exist a bucket with same name in a Project.
    checkForDuplicateName(
        param.getLabel().replaceAll(SPECIAL_CHAR_REGEX, ""),
        Bucket.class,
        id,
        "project",
        _dbClient);

    return initiateBucketCreation(param, project, tenant, null);
  }
  /**
   * Validates vCenter user credentials from create or update parameters.
   *
   * @param param either vCenter create or update param.
   * @param vcenter vCenter object.
   */
  private void validateVcenterCredentials(VcenterParam param, Vcenter vcenter) {
    if (StringUtils.isBlank(param.getPassword()) && vcenter != null) {
      param.setPassword(StringUtils.trimToNull(vcenter.getPassword()));
    }

    if (StringUtils.isBlank(param.getUserName()) && vcenter != null) {
      param.setUserName(StringUtils.trimToNull(vcenter.getUsername()));
    }

    ArgValidator.checkFieldNotNull(param.getUserName(), "username");
    ArgValidator.checkFieldNotNull(param.getPassword(), "password");
  }
  /**
   * Deactivate Quota directory of file system, this will move the Quota directory to a
   * "marked-for-delete" state
   *
   * <p>NOTE: This is an asynchronous operation.
   *
   * @param id the URN of the QuotaDirectory
   * @param param QuotaDirectory delete param for optional force delete
   * @brief Delete file system Quota Dir
   * @return Task resource representation
   * @throws com.emc.storageos.svcs.errorhandling.resources.InternalException
   */
  @POST
  @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
  @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
  @Path("/{id}/deactivate")
  @CheckPermission(
      roles = {Role.TENANT_ADMIN},
      acls = {ACL.OWN, ACL.ALL})
  public TaskResourceRep deactivateQuotaDirectory(
      @PathParam("id") URI id, QuotaDirectoryDeleteParam param) throws InternalException {

    _log.info("FileService::deactivateQtree Request recieved {}", id);
    String task = UUID.randomUUID().toString();
    ArgValidator.checkFieldUriType(id, QuotaDirectory.class, "id");
    QuotaDirectory quotaDirectory = queryResource(id);
    FileShare fs = queryFileShareResource(quotaDirectory.getParent().getURI());
    ArgValidator.checkFieldNotNull(fs, "filesystem");

    // <TODO> Implement Force delete option when shares and exports for Quota Directory are
    // supported

    Operation op = new Operation();
    op.setResourceType(ResourceOperationTypeEnum.DELETE_FILE_SYSTEM_QUOTA_DIR);
    quotaDirectory.getOpStatus().createTaskStatus(task, op);
    fs.setOpStatus(new OpStatusMap());
    fs.getOpStatus().createTaskStatus(task, op);
    _dbClient.persistObject(fs);
    _dbClient.persistObject(quotaDirectory);

    // Now get ready to make calls into the controller
    StorageSystem device = _dbClient.queryObject(StorageSystem.class, fs.getStorageDevice());
    FileController controller = getController(FileController.class, device.getSystemType());
    try {
      controller.deleteQuotaDirectory(device.getId(), quotaDirectory.getId(), fs.getId(), task);
      // If delete operation is successful, then remove obj from ViPR db by setting inactive=true
      quotaDirectory.setInactive(true);
      _dbClient.persistObject(quotaDirectory);

    } catch (InternalException e) {
      // treating all controller exceptions as internal error for now. controller
      // should discriminate between validation problems vs. internal errors

      throw e;
    }

    auditOp(
        OperationTypeEnum.DELETE_FILE_SYSTEM_QUOTA_DIR,
        true,
        AuditLogManager.AUDITOP_BEGIN,
        quotaDirectory.getLabel(),
        quotaDirectory.getId().toString(),
        fs.getId().toString());

    fs = _dbClient.queryObject(FileShare.class, fs.getId());
    _log.debug(
        "FileService::Quota directory Before sending response, FS ID : {}, Taks : {} ; Status {}",
        fs.getOpStatus().get(task),
        fs.getOpStatus().get(task).getStatus());

    return toTask(quotaDirectory, task, op);
  }
  /**
   * Undo the release of a file system
   *
   * @param id the URN of a ViPR file system to undo
   * @return the updated file system
   * @throws InternalException
   */
  @POST
  @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
  @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
  @Path("/{id}/release/undo")
  public FileShareRestRep undoReleaseFileSystemInternal(@PathParam("id") URI id)
      throws InternalException {

    ArgValidator.checkFieldUriType(id, FileShare.class, "id");
    FileShare fs = _fileService.queryResource(id);
    checkFileShareInternal(fs);

    URI releasedProject = fs.getOriginalProject();
    if (releasedProject == null) {
      throw APIException.forbidden.onlyPreviouslyReleasedFileSystemsCanBeUndone();
    }

    Project project = _permissionsHelper.getObjectById(releasedProject, Project.class);
    ArgValidator.checkEntity(project, releasedProject, false);
    ArgValidator.checkFieldNotNull(project.getTenantOrg(), "tenantOrg");
    ArgValidator.checkFieldNotNull(project.getTenantOrg().getURI(), "tenantOrg");

    fs.setTenant(new NamedURI(project.getTenantOrg().getURI(), fs.getLabel()));
    fs.setProject(new NamedURI(releasedProject, fs.getLabel()));
    fs.setOriginalProject(null);
    fs.clearInternalFlags(INTERNAL_FILESHARE_FLAGS);
    _dbClient.updateAndReindexObject(fs);

    // audit against the new project, not the old dummy internal project
    auditOp(
        OperationTypeEnum.UNDO_RELEASE_FILE_SYSTEM,
        true,
        null,
        fs.getId().toString(),
        project.getId().toString());

    return map(fs);
  }
  @POST
  @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
  @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
  @CheckPermission(roles = {Role.SYSTEM_ADMIN, Role.RESTRICTED_SYSTEM_ADMIN})
  public TaskResourceRep registerStorageProvider(StorageProviderCreateParam param)
      throws ControllerException {
    ArgValidator.checkFieldNotEmpty(param.getName(), "name");
    checkForDuplicateName(param.getName(), StorageProvider.class);

    ArgValidator.checkFieldNotEmpty(param.getIpAddress(), "ip_address");
    ArgValidator.checkFieldNotNull(param.getPortNumber(), "port_number");
    ArgValidator.checkFieldNotEmpty(param.getUserName(), "user_name");
    ArgValidator.checkFieldNotEmpty(param.getPassword(), "password");
    ArgValidator.checkFieldRange(param.getPortNumber(), 1, 65535, "port_number");
    ArgValidator.checkFieldValueFromEnum(
        param.getInterfaceType(), "interface_type", StorageProvider.InterfaceType.class);
    String providerKey = param.getIpAddress() + "-" + param.getPortNumber();
    List<StorageProvider> providers =
        CustomQueryUtility.getActiveStorageProvidersByProviderId(_dbClient, providerKey);
    if (providers != null && !providers.isEmpty()) {
      throw APIException.badRequests.invalidParameterStorageProviderAlreadyRegistered(providerKey);
    }

    // Set the SSL parameter
    Boolean useSSL = param.getUseSSL();
    if (useSSL == null) {
      useSSL = StorageProviderCreateParam.USE_SSL_DEFAULT;
    }

    StorageProvider provider = new StorageProvider();
    provider.setId(URIUtil.createId(StorageProvider.class));
    provider.setLabel(param.getName());
    provider.setIPAddress(param.getIpAddress());
    provider.setPortNumber(param.getPortNumber());
    provider.setUserName(param.getUserName());
    provider.setPassword(param.getPassword());
    provider.setUseSSL(useSSL);
    provider.setInterfaceType(param.getInterfaceType());
    provider.setRegistrationStatus(RegistrationStatus.REGISTERED.toString());
    provider.setConnectionStatus(ConnectionStatus.INITIALIZING.name());
    provider.setSecondaryUsername(param.getSecondaryUsername());
    provider.setSecondaryPassword(param.getSecondaryPassword());
    provider.setElementManagerURL(param.getElementManagerURL());
    if (param.getSioCLI() != null) {
      // TODO: Validate the input?
      provider.addKey(StorageProvider.GlobalKeys.SIO_CLI.name(), param.getSioCLI());
    }

    if (StorageProvider.InterfaceType.ibmxiv.name().equalsIgnoreCase(provider.getInterfaceType())) {
      provider.setManufacturer("IBM");
    }

    _dbClient.createObject(provider);

    auditOp(
        OperationTypeEnum.REGISTER_STORAGEPROVIDER,
        true,
        null,
        provider.getLabel(),
        provider.getId().toString(),
        provider.getIPAddress(),
        provider.getPortNumber(),
        provider.getUserName(),
        provider.getInterfaceType());

    ArrayList<AsyncTask> tasks = new ArrayList<AsyncTask>(1);
    String taskId = UUID.randomUUID().toString();
    tasks.add(new AsyncTask(StorageProvider.class, provider.getId(), taskId));

    BlockController controller = getController(BlockController.class, provider.getInterfaceType());
    log.debug("controller.getClass().getName() :{}", controller.getClass().getName());
    log.debug("controller.getClass().getSimpleName() :{}", controller.getClass().getSimpleName());
    /**
     * Creates MonitoringJob token for vnxblock/vmax, hds, cinder and IBM XIV device on zooKeeper
     * queue
     */
    // TODO : If all interface types have monitoring impl class added (scaleIO is missing),
    // this check can be removed.
    String interfaceType = provider.getInterfaceType();
    if (StorageProvider.InterfaceType.hicommand.name().equalsIgnoreCase(interfaceType)
        || StorageProvider.InterfaceType.smis.name().equalsIgnoreCase(interfaceType)
        || StorageProvider.InterfaceType.cinder.name().equalsIgnoreCase(interfaceType)
        || StorageProvider.InterfaceType.ibmxiv.name().equalsIgnoreCase(interfaceType)) {
      controller.startMonitoring(
          new AsyncTask(StorageProvider.class, provider.getId(), taskId),
          getSystemTypeByInterface(interfaceType));
    }

    DiscoveredObjectTaskScheduler scheduler =
        new DiscoveredObjectTaskScheduler(_dbClient, new ScanJobExec(controller));
    TaskList taskList = scheduler.scheduleAsyncTasks(tasks);
    return taskList.getTaskList().listIterator().next();
  }
  /**
   * Update Quota Directory for a file share
   *
   * <p>NOTE: This is an asynchronous operation.
   *
   * @param id the URN of a ViPR Quota directory
   * @param param File system Quota directory update parameters
   * @brief Update file system Quota directory
   * @return Task resource representation
   * @throws com.emc.storageos.svcs.errorhandling.resources.InternalException
   */
  @POST
  @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
  @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
  @Path("/{id}")
  @CheckPermission(
      roles = {Role.TENANT_ADMIN},
      acls = {ACL.OWN, ACL.ALL})
  public TaskResourceRep updateQuotaDirectory(
      @PathParam("id") URI id, QuotaDirectoryUpdateParam param) throws InternalException {
    _log.info("FileService::Update Quota directory Request recieved {}", id);

    QuotaDirectory quotaDir = queryResource(id);

    String task = UUID.randomUUID().toString();

    if (param.getSecurityStyle() != null) {
      ArgValidator.checkFieldValueFromEnum(
          param.getSecurityStyle(),
          "security_style",
          EnumSet.allOf(QuotaDirectory.SecurityStyles.class));
    }

    // Get the FileSystem object
    FileShare fs = queryFileShareResource(quotaDir.getParent().getURI());
    ArgValidator.checkFieldNotNull(fs, "filesystem");

    // Update the quota directory object to store in ViPR database
    quotaDir.setOpStatus(new OpStatusMap());

    // Set all other optional parameters too.
    if (param.getOpLock() != null) {
      quotaDir.setOpLock(param.getOpLock());
    }

    if (param.getSecurityStyle() != null) {
      quotaDir.setSecurityStyle(param.getSecurityStyle());
    }

    if (param.getSize() != null) {
      Long quotaSize = SizeUtil.translateSize(param.getSize());
      if (quotaSize > 0) {
        ArgValidator.checkFieldMaximum(quotaSize, fs.getCapacity(), " Bytes", "size");
        quotaDir.setSize(quotaSize);
      }
    }

    Operation op = new Operation();
    op.setResourceType(ResourceOperationTypeEnum.UPDATE_FILE_SYSTEM_QUOTA_DIR);
    quotaDir.getOpStatus().createTaskStatus(task, op);
    fs.setOpStatus(new OpStatusMap());
    fs.getOpStatus().createTaskStatus(task, op);
    _dbClient.persistObject(fs);
    _dbClient.persistObject(quotaDir);

    // Create an object of type "FileShareQtree" to be passed into the south-bound layers.
    FileShareQuotaDirectory qt = new FileShareQuotaDirectory(quotaDir);

    // Now get ready to make calls into the controller
    StorageSystem device = _dbClient.queryObject(StorageSystem.class, fs.getStorageDevice());
    FileController controller = getController(FileController.class, device.getSystemType());
    try {
      controller.updateQuotaDirectory(device.getId(), qt, fs.getId(), task);
    } catch (InternalException e) {
      _log.error("Error during update of Quota Directory {}", e);

      // treating all controller exceptions as internal error for now. controller
      // should discriminate between validation problems vs. internal errors
      throw e;
    }

    auditOp(
        OperationTypeEnum.UPDATE_FILE_SYSTEM_QUOTA_DIR,
        true,
        AuditLogManager.AUDITOP_BEGIN,
        quotaDir.getLabel(),
        quotaDir.getId().toString(),
        fs.getId().toString());

    fs = _dbClient.queryObject(FileShare.class, fs.getId());
    _log.debug(
        "FileService::Quota directory Before sending response, FS ID : {}, Taks : {} ; Status {}",
        fs.getOpStatus().get(task),
        fs.getOpStatus().get(task).getStatus());

    return toTask(quotaDir, task, op);
  }
  /**
   * Release a file system from its current tenant & project for internal object usage
   *
   * @param id the URN of a ViPR file system to be released
   * @return the updated file system
   * @throws InternalException
   */
  @POST
  @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
  @Path("/{id}/release")
  public FileShareRestRep releaseFileSystemInternal(@PathParam("id") URI id)
      throws InternalException {

    ArgValidator.checkFieldUriType(id, FileShare.class, "id");
    FileShare fs = _fileService.queryResource(id);

    // if the FS is already marked as internal, we can skip all this logic
    // and just return success down at the bottom
    if (!fs.checkInternalFlags(Flag.INTERNAL_OBJECT)) {
      URI tenantURI = fs.getTenant().getURI();
      if (!_permissionsHelper.userHasGivenRole(
          getUserFromContext(), tenantURI, Role.TENANT_ADMIN)) {
        throw APIException.forbidden.onlyAdminsCanReleaseFileSystems(Role.TENANT_ADMIN.toString());
      }

      // we can't release a fs that has exports
      FSExportMap exports = fs.getFsExports();
      if ((exports != null) && (!exports.isEmpty())) {
        throw APIException.badRequests.cannotReleaseFileSystemExportExists(
            exports.keySet().toString());
      }

      // we can't release a fs that has shares
      SMBShareMap shares = fs.getSMBFileShares();
      if ((shares != null) && (!shares.isEmpty())) {
        throw APIException.badRequests.cannotReleaseFileSystemSharesExists(
            shares.keySet().toString());
      }

      // files systems with pending operations can't be released
      if (fs.getOpStatus() != null) {
        for (String opId : fs.getOpStatus().keySet()) {
          Operation op = fs.getOpStatus().get(opId);
          if (Operation.Status.pending.name().equals(op.getStatus())) {
            throw APIException.badRequests.cannotReleaseFileSystemWithTasksPending();
          }
        }
      }

      // file systems with snapshots can't be released
      Integer snapCount = _fileService.getNumSnapshots(fs);
      if (snapCount > 0) {
        throw APIException.badRequests.cannotReleaseFileSystemSnapshotExists(snapCount);
      }

      TenantOrg rootTenant = _permissionsHelper.getRootTenant();

      // we can't release the file system to the root tenant if the root tenant has no access
      // to the filesystem's virtual pool
      ArgValidator.checkFieldNotNull(fs.getVirtualPool(), "virtualPool");
      VirtualPool virtualPool =
          _permissionsHelper.getObjectById(fs.getVirtualPool(), VirtualPool.class);
      ArgValidator.checkEntity(virtualPool, fs.getVirtualPool(), false);
      if (!_permissionsHelper.tenantHasUsageACL(rootTenant.getId(), virtualPool)) {
        throw APIException.badRequests.cannotReleaseFileSystemRootTenantLacksVPoolACL(
            virtualPool.getId().toString());
      }

      fs.setOriginalProject(fs.getProject().getURI());
      fs.setTenant(new NamedURI(rootTenant.getId(), fs.getLabel()));
      fs.setProject(new NamedURI(_internalProject.getId(), fs.getLabel()));
      fs.addInternalFlags(INTERNAL_FILESHARE_FLAGS);
      _dbClient.updateAndReindexObject(fs);

      // audit against the source project, not the new dummy internal project
      auditOp(
          OperationTypeEnum.RELEASE_FILE_SYSTEM,
          true,
          null,
          fs.getId().toString(),
          fs.getOriginalProject().toString());
    }

    return map(fs);
  }