class VolumeApiServiceImpl extends ManagerBase implements VolumeApiService, VmWorkJobHandler {

  @Override
  @ActionEvent(eventType = EventTypes.EVENT_VOLUME_DETACH, eventDescription = "detaching volume", async = true)
  public Volume detachVolumeFromVM(DetachVolumeCmd cmmd) {
    final Account caller = CallContext.current().getCallingAccount();
    if (cmmd.getId() == null && cmmd.getDeviceId() == null && cmmd.getVirtualMachineId() == null
        || cmmd.getId() != null && (cmmd.getDeviceId() != null || cmmd.getVirtualMachineId() != null)
        || cmmd.getId() == null && (cmmd.getDeviceId() == null || cmmd.getVirtualMachineId() == null)) {
      throw new InvalidParameterValueException("Please provide either a volume id, or a tuple(device id, instance id)");
    }

    final Long volumeId = cmmd.getId();
    VolumeVO volume = null;

    if (volumeId != null) {
      volume = _volsDao.findById(volumeId);
    } else {
      volume = _volsDao.findByInstanceAndDeviceId(cmmd.getVirtualMachineId(), cmmd.getDeviceId()).get(0);
    }

    // Check that the volume ID is valid
    if (volume == null) {
      throw new InvalidParameterValueException("Unable to find volume with ID: " + volumeId);
    }

    Long vmId = null;

    if (cmmd.getVirtualMachineId() == null) {
      vmId = volume.getInstanceId();
    } else {
      vmId = cmmd.getVirtualMachineId();
    }

    // Permissions check
    _accountMgr.checkAccess(caller, null, true, volume);

    // Check that the volume is currently attached to a VM
    if (vmId == null) {
      throw new InvalidParameterValueException("The specified volume is not attached to a VM.");
    }

    // Check that the VM is in the correct state
    final UserVmVO vm = _userVmDao.findById(vmId);
    if (vm.getState() != State.Running && vm.getState() != State.Stopped && vm.getState() != State.Destroyed) {
      throw new InvalidParameterValueException("Please specify a VM that is either running or stopped.");
    }

    // Check that the volume is a data/root volume
    if (!(volume.getVolumeType() == Volume.Type.ROOT || volume.getVolumeType() == Volume.Type.DATADISK)) {
      throw new InvalidParameterValueException("Please specify volume of type " + Volume.Type.DATADISK.toString() + " or " + Volume.Type.ROOT.toString());
    }

    // Root volume detach is allowed for following hypervisors: Xen/KVM/VmWare
    if (volume.getVolumeType() == Volume.Type.ROOT) {
      validateRootVolumeDetachAttach(volume, vm);
    }

    // Don't allow detach if target VM has associated VM snapshots
    final List<VMSnapshotVO> vmSnapshots = _vmSnapshotDao.findByVm(vmId);
    if (vmSnapshots.size() > 0) {
      throw new InvalidParameterValueException("Unable to detach volume, please specify a VM that does not have VM snapshots");
    }

    final AsyncJobExecutionContext asyncExecutionContext = AsyncJobExecutionContext.getCurrentExecutionContext();
    if (asyncExecutionContext != null) {
      final AsyncJob job = asyncExecutionContext.getJob();

      if (s_logger.isInfoEnabled()) {
        s_logger.info("Trying to attaching volume " + volumeId + "to vm instance:" + vm.getId() + ", update async job-" + job.getId() + " progress status");
      }

      _jobMgr.updateAsyncJobAttachment(job.getId(), "Volume", volumeId);
    }

    final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext();
    if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) {
      // avoid re-entrance
      VmWorkJobVO placeHolder = null;
      placeHolder = createPlaceHolderWork(vmId);
      try {
        return orchestrateDetachVolumeFromVM(vmId, volumeId);
      } finally {
        _workJobDao.expunge(placeHolder.getId());
      }
    } else {
      final Outcome<Volume> outcome = detachVolumeFromVmThroughJobQueue(vmId, volumeId);

      Volume vol = null;
      try {
        outcome.get();
      } catch (final InterruptedException e) {
        throw new RuntimeException("Operation is interrupted", e);
      } catch (final java.util.concurrent.ExecutionException e) {
        throw new RuntimeException("Execution excetion", e);
      }

      final Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob());
      if (jobResult != null) {
        if (jobResult instanceof ConcurrentOperationException) {
          throw (ConcurrentOperationException)jobResult;
        } else if (jobResult instanceof RuntimeException) {
          throw (RuntimeException)jobResult;
        } else if (jobResult instanceof Throwable) {
          throw new RuntimeException("Unexpected exception", (Throwable)jobResult);
        } else if (jobResult instanceof Long) {
          vol = _volsDao.findById((Long) jobResult);
        }
      }
      return vol;
    }
  }

  protected  VolumeApiServiceImpl();

}