tapis_job-1.4.1-alpha1/src/TapisProvider/TapisJobProvider.php

src/TapisProvider/TapisJobProvider.php
<?php

namespace Drupal\tapis_job\TapisProvider;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Core\Database\Database;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Url;
use Drupal\jwt\JsonWebToken\JsonWebToken;
use Drupal\jwt\Transcoder\JwtTranscoderInterface;
use Drupal\tapis_app\DrupalIds as AppDrupalIds;
use Drupal\tapis_app\TapisProvider\TapisAppProviderInterface;
use Drupal\tapis_auth\TapisProvider\TapisTokenProviderInterface;
use Drupal\tapis_job\Entity\TapisJob;
use Drupal\tapis_job\Exception\TapisJobException;
use Drupal\tapis_system\DrupalIds as SystemDrupalIds;
use Drupal\tapis_system\TapisProvider\TapisSystemProviderInterface;
use Drupal\tapis_tenant\TapisProvider\TapisSiteTenantProviderInterface;
use GuzzleHttp\ClientInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides functionality to interact with the Tapis API for Tapis Job.
 *
 * This class is responsible for creating, updating, and deleting Tapis Job
 * through the Tapis API.
 *
 * @package Drupal\tapis_app\TapisProvider
 */
class TapisJobProvider implements TapisJobProviderInterface {

  const TAPIS_API_VERSION = 'v3';

  /**
   * The HTTP client used for making requests.
   *
   * @var \GuzzleHttp\ClientInterface
   */
  protected ClientInterface $httpClient;

  /**
   * The Tapis token provider.
   *
   * @var \Drupal\tapis_auth\TapisProvider\TapisTokenProviderInterface
   */
  protected TapisTokenProviderInterface $tapisTokenProvider;

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected AccountProxyInterface $currentUser;

  /**
   * The Tapis system provider.
   *
   * @var \Drupal\tapis_system\TapisProvider\TapisSystemProviderInterface
   */
  protected TapisSystemProviderInterface $tapisSystemProvider;

  /**
   * The Tapis tenant provider.
   *
   * @var \Drupal\tapis_tenant\TapisProvider\TapisSiteTenantProviderInterface
   */
  protected TapisSiteTenantProviderInterface $tapisSiteTenantProvider;

  /**
   * The configuration factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface|\Drupal\Core\Config\ImmutableConfig
   */
  protected ConfigFactoryInterface|ImmutableConfig $config;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected EntityTypeManagerInterface $entityTypeManager;

  /**
   * The JWT transcoder service.
   *
   * @var \Drupal\jwt\Transcoder\JwtTranscoderInterface
   */
  protected JwtTranscoderInterface $jwtTranscoder;

  /**
   * The logger.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

  /**
   * The Tapis app provider.
   *
   * @var \Drupal\tapis_app\TapisProvider\TapisAppProviderInterface
   */
  protected TapisAppProviderInterface $tapisAppProvider;

  /**
   * TapisJobProvider constructor.
   *
   * @param \GuzzleHttp\ClientInterface $httpClient
   *   The HTTP client.
   * @param \Drupal\tapis_auth\TapisProvider\TapisTokenProviderInterface $tapisTokenProvider
   *   The Tapis token provider.
   * @param \Drupal\Core\Session\AccountProxyInterface $currentUser
   *   The current user.
   * @param \Drupal\tapis_tenant\TapisProvider\TapisSiteTenantProviderInterface $tapisSiteTenantProvider
   *   The Tapis site tenant provider.
   * @param \Drupal\tapis_system\TapisProvider\TapisSystemProviderInterface $tapisSystemProvider
   *   The Tapis system provider.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The configuration factory.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\jwt\Transcoder\JwtTranscoderInterface $jwt_transcoder
   *   The JWT transcoder service.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerFactory
   *   The logger factory service.
   * @param \Drupal\tapis_app\TapisProvider\TapisAppProviderInterface $tapisAppProvider
   *   The Tapis app provider.
   */
  public function __construct(
    ClientInterface $httpClient,
    TapisTokenProviderInterface $tapisTokenProvider,
    AccountProxyInterface $currentUser,
    TapisSiteTenantProviderInterface $tapisSiteTenantProvider,
    TapisSystemProviderInterface $tapisSystemProvider,
    ConfigFactoryInterface $config_factory,
    EntityTypeManagerInterface $entityTypeManager,
    JwtTranscoderInterface $jwt_transcoder,
    LoggerChannelFactoryInterface $loggerFactory,
    TapisAppProviderInterface $tapisAppProvider
  ) {
    $this->httpClient = $httpClient;
    $this->tapisTokenProvider = $tapisTokenProvider;
    $this->currentUser = $currentUser;
    $this->tapisSiteTenantProvider = $tapisSiteTenantProvider;
    $this->tapisSystemProvider = $tapisSystemProvider;
    $this->config = $config_factory->get('tapis_job.config');
    $this->entityTypeManager = $entityTypeManager;
    $this->jwtTranscoder = $jwt_transcoder;
    $this->logger = $loggerFactory->get('tapis_job');
    $this->tapisAppProvider = $tapisAppProvider;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('http_client'),
      $container->get('tapis_auth.tapis_token_provider'),
      $container->get("current_user"),
      $container->get("tapis_tenant.tapis_site_tenant_provider"),
      $container->get("tapis_system.tapis_system_provider"),
      $container->get('config.factory'),
      $container->get('entity_type.manager'),
      $container->get('jwt.transcoder'),
      $container->get('logger.factory'),
      $container->get('tapis_app.tapis_app_provider'),
    );
  }

  /**
   * {@inheritDoc}
   * @throws \Drupal\tapis_job\Exception\TapisJobException|\GuzzleHttp\Exception\GuzzleException
   */
  public function backupJobFilesBeforeRestart($tenantId, $systemTapisId, $originalJobUUID, $originalJobExecDir, $uid = -1) {
    if ($uid === -1) {
      $uid = $this->currentUser->id();
    }
    $userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $uid);

    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];
    $tapis_tenant_id = $tenantInfo['tapis_id'];

    // first, make the backup directory for the original job
    // (ignore if it exists)
    $originalJobBackupDir = rtrim($originalJobExecDir, '/') . '/tapis_job_history/' . $originalJobUUID;
    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/files/ops/$systemTapisId";
    $response = $this->httpClient->request('POST', $tapis_api_url, [
      'json' => [
        'path' => $originalJobBackupDir,
      ],
      'headers' => ['X-Tapis-Token' => $userJWT],
      'http_errors' => FALSE,
    ]);
    $response_body = json_decode($response->getBody(), TRUE);
    if ($response->getStatusCode() !== 200) {
      if (is_array($response_body) && isset($response_body['message'])) {
        $message = $response_body['message'];
      } else {
        // Optionally, include the raw body or a default message
        $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
      }
      throw new TapisJobException("An error occurred when backing up the original Tapis job (uuid: $originalJobUUID) before restarting it, in the '$tapis_tenant_id' tenant: $message");
    }

    // now, move the following files from
    // the original job directory to the backup directory
    // note: each of these files may or may not exist
    // the mapping is from src path to dest path
    // (relative to the backup directory)
    $files_to_backup = [
      'job_proxy_info.txt' => 'ospjob_proxy_info.txt',
      'job.log' => 'ospjob.log',
      'outputs/ospjob.log' => 'ospjob.log',
      'outputs/tapisjob.out' => 'tapisjob.out',
      'ospjob_proxy_info.txt' => 'ospjob_proxy_info.txt',
      'tapisjob.sh' => 'tapisjob.sh',
      'tapisjob.env' => 'tapisjob.env',
    ];
    foreach ($files_to_backup as $src_file => $dest_file) {
      $file_src_path = rtrim($originalJobExecDir, '/') . '/' . $src_file;
      $file_dest_path = rtrim($originalJobBackupDir, '/') . '/' . $dest_file;
      $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/files/ops/$systemTapisId/$file_src_path";
      $response = $this->httpClient->request('PUT', $tapis_api_url, [
        'json' => [
          'operation' => 'MOVE',
          'newPath' => $file_dest_path,
        ],
        'headers' => ['X-Tapis-Token' => $userJWT],
        'http_errors' => FALSE,
      ]);
      $response_body = json_decode($response->getBody(), TRUE);
      // Ignore 404 errors, since the source file may not always exist.
      if ($response->getStatusCode() !== 200 && $response->getStatusCode() !== 404) {
        if (is_array($response_body) && isset($response_body['message'])) {
          $message = $response_body['message'];
        } else {
          // Optionally, include the raw body or a default message
          $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
        }
        throw new TapisJobException("An error occurred when backing up a file ($src_file => $dest_file) from the original Tapis job (uuid: $originalJobUUID) before restarting it, in the '$tapis_tenant_id' tenant: $message");
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public function generateJWTForJob($tenantId, array $claims) {
    // $config = \Drupal::config('tapis_job.config');
    $jwt_secret_key_id = $this->config->get("webhook_jwt_secret_key_id");

    if (!$jwt_secret_key_id) {
      return NULL;
    }

    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];
    $tapis_tenant_id = $tenantInfo['tapis_id'];

    // A JWT secret key has been configured, so use it to sign the JWT token.
    // $jwt_secret_key = \Drupal::entityTypeManager()->getStorage('key')->load($jwt_secret_key_id);
    /** @var \Drupal\key\Entity\Key $jwt_secret_key */
    $jwt_secret_key = $this->entityTypeManager->getStorage('key')->load($jwt_secret_key_id);
    $jwt_secret_key_value = $jwt_secret_key->getKeyValue();

    // Sign the JWT token.
    // $jwt_transcoder = \Drupal::service('jwt.transcoder');.
    $this->jwtTranscoder->setKey($jwt_secret_key);
    $raw_json_web_token = new JsonWebToken();
    $raw_json_web_token->setClaim('iss', $tapis_api_endpoint);
    $raw_json_web_token->setClaim('aud', $tapis_api_endpoint);
    $raw_json_web_token->setClaim('iat', time());
    $raw_json_web_token->setClaim('jti', uniqid());
    $raw_json_web_token->setClaim('tenantId', $tapis_tenant_id);

    foreach ($claims as $claim_key => $claim_value) {
      $raw_json_web_token->setClaim($claim_key, $claim_value);
    }

    $jwt_token = $this->jwtTranscoder->encode($raw_json_web_token);
    return $jwt_token;
  }

  /**
   * {@inheritDoc}
   * @throws \Drupal\tapis_job\Exception\TapisJobException|\GuzzleHttp\Exception\GuzzleException
   */
  public function subscribeToJobEvents($tenantId, $jobUuid, $uid = -1) {
    if ($uid === -1) {
      $uid = $this->currentUser->id();
    }
    $userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $uid);

    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];
    $tapis_tenant_id = $tenantInfo['tapis_id'];

    // Get the Drupal absolute url for the 'tapis_job.tapis_webhook' route.
    $webhook_query_params = [];
    $jwt_token = $this->generateJWTForJob($tenantId,
      [
        'jobUuid' => $jobUuid,
        'sub' => $jobUuid,
      ]
    );
    if ($jwt_token) {
      // A JWT secret key has been configured, so include it in the webhook URL
      // that Tapis will use to send job status updates to us.
      $webhook_query_params['token'] = $jwt_token;
    }

    $webhook_route_url = Url::fromRoute('tapis_job.tapis_webhook', [],
      [
        'https' => TRUE,
        'absolute' => TRUE,
      ]
    )->toString();
    if (!empty($webhook_query_params)) {
      $webhook_route_url .= '?' . http_build_query($webhook_query_params);
    }

    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/jobs/subscribe/$jobUuid";

    // Include an empty 'file' parameter string
    // within the multipart/form-data request.
    $response = $this->httpClient->request('POST', $tapis_api_url, [
      'headers' => [
        'X-Tapis-Token' => $userJWT,
      ],
      'http_errors' => FALSE,
      'json' => [
        'description' => 'Tapis Job Event Subscription',
        'enabled' => TRUE,
        'eventCategoryFilter' => 'JOB_NEW_STATUS',
        'deliveryTargets' => [
          [
            'deliveryMethod' => 'WEBHOOK',
            'deliveryAddress' => $webhook_route_url,
          ],
        ],
      ],
    ]);

    $response_body = json_decode($response->getBody(), TRUE);

    // Log the response array as a string.
    // \Drupal::logger('tapis_job')->debug(print_r($response_body, TRUE));.
    $this->logger->debug(print_r($response_body, TRUE));

    // @todo Figure out how to handle Tapis api call errors
    if ($response->getStatusCode() !== 200) {
      if (is_array($response_body) && isset($response_body['message'])) {
        $message = $response_body['message'];
      } else {
        // Optionally, include the raw body or a default message
        $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
      }
      throw new TapisJobException("An error occurred when subscribing to the status of the Tapis job (uuid: $jobUuid) in the '$tapis_tenant_id' tenant: $message");
    }
  }

  /**
   * {@inheritDoc}
   * @throws \Drupal\tapis_job\Exception\TapisJobException|\GuzzleHttp\Exception\GuzzleException
   */
  public function uploadFileToSystem($tenantId, $systemTapisId, $filepath, $file_content, int $uid = -1, int $systemOwnerUid = -1) {
    if ($uid === -1) {
      $uid = $this->currentUser->id();
    }
    $userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $uid);

    if ($systemOwnerUid === -1) {
      $systemOwnerUid = $this->currentUser->id();
    }
    $systemOwnerJWT = $this->tapisTokenProvider->getUserToken($tenantId, $systemOwnerUid);

    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];
    $tapis_tenant_id = $tenantInfo['tapis_id'];

    // First make an api call on behalf of the system owner
    // to grant read & modify permissions to the user if uid != systemOwnerUid.
    if ($uid !== $systemOwnerUid) {
      $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/files/permissions/$systemTapisId/$filepath";
      $response = $this->httpClient->request(
        'POST',
        $tapis_api_url,
        [
          'headers' => ['X-Tapis-Token' => $systemOwnerJWT],
          'json' => [
            'username' => $this->tapisTokenProvider->getTapisUsername($tenantId, $uid),
            "permission" => "MODIFY",
          ],
          "http_errors" => FALSE,
        ]
      );
      if ($response->getStatusCode() !== 200) {
        $response_body = json_decode($response->getBody(), TRUE);
        if (is_array($response_body) && isset($response_body['message'])) {
          $message = $response_body['message'];
        } else {
          // Optionally, include the raw body or a default message
          $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
        }
        throw new TapisJobException("An error occurred when granting permissions for uploading a file to the system (system id: $systemTapisId) in the '$tapis_tenant_id' tenant: $message");
      }
    }

    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/files/ops/$systemTapisId/$filepath";
    // Include an empty 'file' parameter string
    // within the multipart/form-data request.
    $response = $this->httpClient->request('POST', $tapis_api_url, [
      'headers' => [
        'X-Tapis-Token' => $userJWT,
      ],
      'http_errors' => FALSE,
      'multipart' => [
        [
          'Content-Type' => 'multipart/form-data',
          'name' => 'file',
          'contents' => $file_content,
        ],
      ],
    ]);
    $response_body = json_decode($response->getBody(), TRUE);

    // Log the response array as a string.
    // \Drupal::logger('tapis_job')->debug(print_r($response_body, TRUE));.
    $this->logger->debug(print_r($response_body, TRUE));

    // @todo Figure out how to handle Tapis api call errors
    if ($response->getStatusCode() !== 200) {
      if (is_array($response_body) && isset($response_body['message'])) {
        $message = $response_body['message'];
      } else {
        // Optionally, include the raw body or a default message
        $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
      }
      throw new TapisJobException("An error occurred when uploading a file to the system (system id: $systemTapisId) in the '$tapis_tenant_id' tenant: $message");
    }

    return $response_body;
  }

  /**
   * {@inheritDoc}
   * @throws \Drupal\tapis_job\Exception\TapisJobException|\GuzzleHttp\Exception\GuzzleException
   */
  public function submitJob($tenantId, array $tapisDefinition, $uid = -1) {
    // @todo Need to figure out which user we should use to create the app under, within Tapis (e.g., service account vs per-user account)
    if ($uid === -1) {
      $uid = $this->currentUser->id();
    }

    $userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $uid);
    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_tenant_id = $tenantInfo['tapis_id'];
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];

    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/jobs/submit";

    $response = $this->httpClient->request('POST', $tapis_api_url,
      [
        'headers' => ['X-Tapis-Token' => $userJWT],
        'json' => $tapisDefinition,
        "http_errors" => FALSE,
      ]);

    $response_body = json_decode($response->getBody(), TRUE);
    // Log the response array as a string.
    // \Drupal::logger('tapis_job')->debug(print_r($response_body, TRUE));.
    $this->logger->debug(print_r($response_body, TRUE));

    // @todo Figure out how to handle Tapis api call errors
    if ($response->getStatusCode() !== 200 && $response->getStatusCode() !== 201) {
      if (is_array($response_body) && isset($response_body['message'])) {
        $message = $response_body['message'];
      } else {
        // Optionally, include the raw body or a default message
        $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
      }
      throw new TapisJobException("An error occurred when submitting a job in the Tapis '$tapis_tenant_id' tenant: $message");
    }

    return $response_body['result'];
  }

  /**
   * {@inheritDoc}
   * @throws \Drupal\tapis_job\Exception\TapisJobException|\GuzzleHttp\Exception\GuzzleException
   */
  public function getJobSystemOutputList($tenantId, $jobUuid, $outputPath = "", $jobOwnerUid = -1, $systemOwnerUid = -1) {
    if ($jobOwnerUid === -1) {
      $jobOwnerUid = $this->currentUser->id();
    }

    if ($systemOwnerUid === -1) {
      $systemOwnerUid = $this->currentUser->id();
    }

    $userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $systemOwnerUid);

    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];

    // First, get the job's execSystemOutputDir.
    $job = $this->getJob($tenantId, $jobUuid, $jobOwnerUid);
    $system_jobs_directory = rtrim($job['execSystemOutputDir'], '/') . '/' . ltrim($outputPath, '/');
    $systemTapisId = $job['execSystemId'];

    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/files/ops/$systemTapisId/$system_jobs_directory";
    $response = $this->httpClient->request('GET', $tapis_api_url,
      [
        'headers' => ['X-Tapis-Token' => $userJWT],
        'http_errors' => FALSE,
      ]
    );
    $response_body = json_decode($response->getBody(), TRUE);

    // Log the response array as a string.
    // \Drupal::logger('tapis_job')->debug(print_r($response_body, TRUE));.
    $this->logger->debug(print_r($response_body, TRUE));

    // @todo Figure out how to handle Tapis api call errors
    if ($response->getStatusCode() !== 200) {
      if (is_array($response_body) && isset($response_body['message'])) {
        $message = $response_body['message'];
      } else {
        // Optionally, include the raw body or a default message
        $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
      }
      throw new TapisJobException("An error occurred when getting the output file list ($outputPath) from the Tapis job ($jobUuid) in the Tapis '$tenantId' tenant: $message");
    }
    return $response_body['result'];
  }

  /**
   * {@inheritDoc}
   * @throws \Drupal\tapis_job\Exception\TapisJobException|\GuzzleHttp\Exception\GuzzleException
   */
  public function getJob($tenantId, $jobUuid, $uid = -1) {
    if ($uid === -1) {
      $uid = $this->currentUser->id();
    }
    $userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $uid);

    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];
    $tenant_tapis_id = $tenantInfo['tapis_id'];

    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/jobs/$jobUuid";

    $response = $this->httpClient->request('GET', $tapis_api_url,
      [
        'headers' => ['X-Tapis-Token' => $userJWT],
        "http_errors" => FALSE,
      ]
    );
    $response_body = json_decode($response->getBody(), TRUE);

    // Log the response array as a string.
    // $this->logger->debug(print_r($response_body, TRUE));

    // @todo Figure out how to handle Tapis api call errors
    if ($response->getStatusCode() !== 200) {
      if (is_array($response_body) && isset($response_body['message'])) {
        $message = $response_body['message'];
      } else {
        // Optionally, include the raw body or a default message
        $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
      }
      throw new TapisJobException("An error occurred when getting a Tapis job ($jobUuid) from the Tapis '$tenant_tapis_id' tenant: $message");
    }

    return $response_body['result'];
  }

  /**
   * {@inheritDoc}
   * @throws \Drupal\tapis_job\Exception\TapisJobException|\GuzzleHttp\Exception\GuzzleException
   */
  public function getJobStatus($tenantId, $jobUuid, $uid = -1) {
    if ($uid === -1) {
      $uid = $this->currentUser->id();
    }
    $userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $uid);

    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];
    $tapis_tenant_id = $tenantInfo['tapis_id'];

    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/jobs/$jobUuid/status";

    $response = $this->httpClient->request('GET', $tapis_api_url,
      [
        'headers' => ['X-Tapis-Token' => $userJWT],
        "http_errors" => FALSE,
      ]
    );
    $response_body = json_decode($response->getBody(), TRUE);

    // Log the response array as a string.
    // \Drupal::logger('tapis_job')->debug(print_r($response_body, TRUE));.
    $this->logger->debug(print_r($response_body, TRUE));

    // @todo Figure out how to handle Tapis api call errors
    if ($response->getStatusCode() !== 200) {
      if (is_array($response_body) && isset($response_body['message'])) {
        $message = $response_body['message'];
      } else {
        // Optionally, include the raw body or a default message
        $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
      }
      throw new TapisJobException("An error occurred when getting the status for Tapis job ($jobUuid) from the Tapis '$tapis_tenant_id' tenant: $message");
    }

    return $response_body['result']['status'];
  }

  /**
   * {@inheritDoc}
   * @throws \Drupal\tapis_job\Exception\TapisJobException|\GuzzleHttp\Exception\GuzzleException
   */
  public function getJobHistory($tenantId, $jobUuid, $uid = -1) {
    if ($uid === -1) {
      $uid = $this->currentUser->id();
    }
    $userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $uid);

    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];
    $tapis_tenant_id = $tenantInfo['tapis_id'];

    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/jobs/$jobUuid/history";

    $response = $this->httpClient->request('GET', $tapis_api_url,
      [
        'headers' => ['X-Tapis-Token' => $userJWT],
        "http_errors" => FALSE,
      ]
    );
    $response_body = json_decode($response->getBody(), TRUE);

    // Log the response array as a string.
    // \Drupal::logger('tapis_job')->debug(print_r($response_body, TRUE));.
    $this->logger->debug(print_r($response_body, TRUE));

    // @todo Figure out how to handle Tapis api call errors
    if ($response->getStatusCode() !== 200) {
      if (is_array($response_body) && isset($response_body['message'])) {
        $message = $response_body['message'];
      } else {
        // Optionally, include the raw body or a default message
        $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
      }
      throw new TapisJobException("An error occurred when getting the history for Tapis job ($jobUuid) from the Tapis '$tapis_tenant_id' tenant: $message");
    }

    return $response_body['result'];
  }

  /**
   * {@inheritDoc}
   * @throws \Drupal\tapis_job\Exception\TapisJobException|\GuzzleHttp\Exception\GuzzleException
   */
  public function cancelJob($tenantId, $jobUuid, $uid = -1) {
    // @todo Implement cancelJob() method.
    if ($uid === -1) {
      $uid = $this->currentUser->id();
    }
    $userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $uid);

    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];
    $tenant_tapis_id = $tenantInfo['tapis_id'];

    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/jobs/$jobUuid/cancel";

    $response = $this->httpClient->request('POST', $tapis_api_url,
      [
        'http_errors' => FALSE,
        'headers' => ['X-Tapis-Token' => $userJWT],
      ]
    );
    $response_body = json_decode($response->getBody(), TRUE);

    // Log the response array as a string.
    // \Drupal::logger('tapis_job')->debug(print_r($response_body, TRUE));.
    $this->logger->debug(print_r($response_body, TRUE));

    // @todo Figure out how to handle Tapis api call errors
    if ($response->getStatusCode() !== 200 && $response->getStatusCode() !== 409) {
      if (is_array($response_body) && isset($response_body['message'])) {
        $message = $response_body['message'];
      } else {
        // Optionally, include the raw body or a default message
        $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
      }
      throw new TapisJobException("An error occurred when cancelling the Tapis job ($jobUuid) in the Tapis '$tenant_tapis_id' tenant: $message");
    }

    return $response_body['result'];
  }

  /**
   * {@inheritDoc}
   * @throws \Drupal\tapis_job\Exception\TapisJobException|\GuzzleHttp\Exception\GuzzleException
   */
  public function getJobOutputDownload($tenantId, $jobUuid, $outputPath = "", $compress = FALSE, $uid = -1) {
    if ($uid === -1) {
      $uid = $this->currentUser->id();
    }
    $userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $uid);

    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];
    $tenant_tapis_id = $tenantInfo['tapis_id'];

    // Remove any leading slashes, since we'll add it ourselves in the URL.
    if (str_starts_with($outputPath, "/")) {
      $outputPath = substr($outputPath, 1);
    }

    $compress_param = "false";
    if ($compress) {
      $compress_param = "true";
    }

    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/jobs/$jobUuid/output/download/$outputPath?compress=$compress_param&allowIfRunning=true";

    $response = $this->httpClient->request('GET', $tapis_api_url,
      [
        'headers' => ['X-Tapis-Token' => $userJWT],
        'stream' => TRUE,
        'http_errors' => FALSE,
      ]
    );

    // @todo Figure out how to handle Tapis api call errors
    if ($response->getStatusCode() !== 200) {
      throw new TapisJobException("An error occurred when downloading an output file ($outputPath) from the Tapis job ($jobUuid) in the Tapis '$tenant_tapis_id' tenant.");
    }

    return $response->getBody();
  }

  /**
   * {@inheritDoc}
   * @throws \Drupal\tapis_job\Exception\TapisJobException|\GuzzleHttp\Exception\GuzzleException
   */
  public function getJobOutputList($tenantId, $jobUuid, $outputPath = "", $uid = -1, $skip = -1, $limit = -1) {
    if ($uid === -1) {
      $uid = $this->currentUser->id();
    }
    // \Drupal::logger('tapis_job')->debug("User $uid for getJobOutputList");
    $this->logger->debug("User $uid for getJobOutputList");

    $userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $uid);

    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];
    $tenant_tapis_id = $tenantInfo['tapis_id'];

    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/jobs/$jobUuid/output/list/$outputPath?allowIfRunning=true";
    if ($skip !== -1) {
      $tapis_api_url .= "&skip=$skip";
    }
    if ($limit !== -1) {
      $tapis_api_url .= "&limit=$limit";
    }
    // \Drupal::logger('tapis_job')->debug($tapis_api_url);
    $this->logger->debug($tapis_api_url);

    $response = $this->httpClient->request('GET', $tapis_api_url,
      [
        'headers' => ['X-Tapis-Token' => $userJWT],
        'http_errors' => FALSE,
      ]
    );

    $response_body = json_decode($response->getBody(), TRUE);

    // Log the response array as a string.
    // \Drupal::logger('tapis_job')->debug(print_r($response_body, TRUE));.
    $this->logger->debug(print_r($response_body, TRUE));

    // @todo Figure out how to handle Tapis api call errors
    if ($response->getStatusCode() !== 200) {
      if (is_array($response_body) && isset($response_body['message'])) {
        $message = $response_body['message'];
      } else {
        // Optionally, include the raw body or a default message
        $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
      }
      throw new TapisJobException("An error occurred when getting the output file list ($outputPath) from the Tapis job ($jobUuid) in the Tapis '$tenant_tapis_id' tenant: $message");
    }

    return $response_body['result'];
  }

  /**
   * {@inheritDoc}
   *
   * @throws \GuzzleHttp\Exception\GuzzleException
   *   Throws the exception for the tapis job and http client.
   */
  public function getJobOutputs($tenantId,
                                $execSystemId,
                                $outputPath = "",
                                $recurse = "true",
                                $uid = -1,
                                $skip = -1,
                                $limit = -1) {
    if ($uid === -1) {
      $uid = $this->currentUser->id();
    }
    $this->logger->debug("User $uid for getJobOutputs");

    $userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $uid);

    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];
    $tenant_tapis_id = $tenantInfo['tapis_id'];

    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/files/ops/" .
      $execSystemId . "/" . $outputPath . "?recurse=$recurse";
    if ($skip !== -1) {
      $tapis_api_url .= "&offset=$skip";
    }
    if ($limit !== -1) {
      $tapis_api_url .= "&limit=$limit";
    }
    $response = $this->httpClient->request('GET', $tapis_api_url,
      [
        'headers' => ['X-Tapis-Token' => $userJWT],
        'http_errors' => FALSE,
        'timeout' => 0,
      ]
    );

    $response_body = json_decode($response->getBody(), TRUE);

    // Log the response array as a string.
    $this->logger->debug(print_r($response_body, TRUE));
    $response_body['status_code'] = $response->getStatusCode();
    return $response_body;
  }

  /**
   * {@inheritDoc}
   *
   * @throws \Drupal\tapis_job\Exception\TapisJobException
   * @throws \GuzzleHttp\Exception\GuzzleException
   */
  public function getJobOutputFileDownload($tenantId,
                                           $jobUuid,
                                           $execSystemId,
                                           $outputPath = "",
  $zip = FALSE,
  $uid = -1) {
    if ($uid === -1) {
      $uid = $this->currentUser->id();
    }
    $userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $uid);

    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];
    $tenant_tapis_id = $tenantInfo['tapis_id'];

    // Remove any leading slashes, since we'll add it ourselves in the URL.
    if (str_starts_with($outputPath, "/")) {
      $outputPath = substr($outputPath, 1);
    }

    $zip_param = "false";
    if ($zip) {
      $zip_param = "true";
    }

    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION .
      "/files/content/$execSystemId/$outputPath?zip=$zip_param";

    $response = $this->httpClient->request('GET', $tapis_api_url,
      [
        'headers' => ['X-Tapis-Token' => $userJWT],
        // 'stream' => TRUE,
        'http_errors' => FALSE,
        'timeout' => 0,
      ]
    );

    // @todo Figure out how to handle Tapis api call errors
    if ($response->getStatusCode() !== 200) {
      throw new TapisJobException("An error occurred when downloading an output file ($outputPath) from the Tapis job ($jobUuid) in the Tapis '$tenant_tapis_id' tenant.");
    }

    return $response->getBody();
  }

  /**
   * {@inheritDoc}
   * @throws \Drupal\tapis_job\Exception\TapisJobException|\GuzzleHttp\Exception\GuzzleException
   */
  public function redeemSatelliteToken($tenantId, $jobUuid, $token, int $jobOwnerUid = -1, int $systemOwnerUid = -1) {
    if ($jobOwnerUid === -1) {
      $jobOwnerUid = $this->currentUser->id();
    }

    if ($systemOwnerUid === -1) {
      $systemOwnerUid = $this->currentUser->id();
    }

    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $tapis_tenant_id = $tenantInfo['tapis_id'];

    $jobOwnerJWT = $this->tapisTokenProvider->getUserToken($tenantId, $jobOwnerUid);
    $systemOwnerJWT = $this->tapisTokenProvider->getUserToken($tenantId, $systemOwnerUid);

    // Step 1. get the job metadata from tapis using the uuid.
    $job_metadata = $this->getJob($tenantId, $jobUuid);

    // Ensure that it's in the RUNNING state.
    if ($job_metadata['status'] !== 'RUNNING') {
      throw new TapisJobException("The job ($jobUuid) in the '$tapis_tenant_id' tenant is not in the RUNNING state, so cannot create a new access link for it.");
    }

    // Step 2. get the job's execSystemId and execSystemOutputDir
    // and compute the path to the token file.
    $execSystemId = $job_metadata['execSystemId'];
    $execSystemInputDir = $job_metadata['execSystemInputDir'];
    $token_filepath = rtrim($execSystemInputDir, '/') . '/' . ltrim(".revssh/$token.token", '/');

    // Step 3. make the system owner grant MODIFY permissions to the job owner
    // on the token file path.
    $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];
    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/files/ops/$execSystemId/$token_filepath";

    if ($jobOwnerUid !== $systemOwnerUid) {
      $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/files/permissions/$execSystemId/$token_filepath";
      $response = $this->httpClient->request(
        'POST',
        $tapis_api_url,
        [
          'headers' => ['X-Tapis-Token' => $systemOwnerJWT],
          'json' => [
            'username' => $this->tapisTokenProvider->getTapisUsername($tenantId, $jobOwnerUid),
            "permission" => "MODIFY",
          ],
          "http_errors" => FALSE,
        ]
      );
      if ($response->getStatusCode() !== 200) {
        $response_body = json_decode($response->getBody(), TRUE);
        if (is_array($response_body) && isset($response_body['message'])) {
          $message = $response_body['message'];
        } else {
          // Optionally, include the raw body or a default message
          $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
        }
        throw new TapisJobException("An error occurred when redeeming a new access link for Tapis job ($jobUuid) from the Tapis '$tapis_tenant_id' tenant: $message");
      }
    }

    // Step 4. Use the Tapis Files API to insert a new empty $token.token file
    // within "$execSystemInputDir/.revssh".
    $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/files/ops/$execSystemId/$token_filepath";

    // Include an empty 'file' parameter string
    // within the multipart/form-data request.
    $response = $this->httpClient->request('POST', $tapis_api_url, [
      'headers' => ['X-Tapis-Token' => $jobOwnerJWT],
      'http_errors' => FALSE,
      'multipart' => [
        [
          'name' => 'file',
          'contents' => '',
        ],
      ],
    ]);
    $response_body = json_decode($response->getBody(), TRUE);

    // Log the response array as a string.
    // \Drupal::logger('tapis_job')->debug(print_r($response_body, TRUE));.
    $this->logger->debug(print_r($response_body, TRUE));

    // @todo Figure out how to handle Tapis api call errors
    if ($response->getStatusCode() !== 200) {
      if (is_array($response_body) && isset($response_body['message'])) {
        $message = $response_body['message'];
      } else {
        // Optionally, include the raw body or a default message
        $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
      }
      throw new TapisJobException("An error occurred when redeeming a new access link for Tapis job ($jobUuid) from the Tapis '$tapis_tenant_id' tenant: $message");
    }
  }

  /**
   * {@inheritDoc}
   * @throws \Drupal\tapis_job\Exception\TapisJobException|\GuzzleHttp\Exception\GuzzleException
   */
  public function deleteSatelliteToken($tenantId, $token, $jobUuid = NULL, $jobOwnerUid = -1, $systemOwnerUid = -1) {
    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $satellite_proxy = $tenantInfo['satellite_proxy_url'];
    $tapis_tenant_id = $tenantInfo['tapis_id'];

    // 1. if jobUuid is provided, delete the token file
    // from the job's input directory/.revssh using the Tapis Files API
    if ($jobUuid !== NULL) {
      if ($jobOwnerUid === -1) {
        $jobOwnerUid = $this->currentUser->id();
      }
      if ($systemOwnerUid === -1) {
        $systemOwnerUid = $this->currentUser->id();
      }

      $job_userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $jobOwnerUid);
      $system_userJWT = $this->tapisTokenProvider->getUserToken($tenantId, $systemOwnerUid);

      $job_metadata = $this->getJob($tenantId, $jobUuid, $jobOwnerUid);

      $execSystemId = $job_metadata['execSystemId'];
      $execSystemInputDir = $job_metadata['execSystemInputDir'];

      $token_filepath = rtrim($execSystemInputDir, '/') . '/' . ltrim(".revssh/$token.token", '/');

      $tapis_api_endpoint = $tenantInfo['tapis_api_endpoint'];
      if ($jobOwnerUid !== $systemOwnerUid) {
        $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/files/permissions/$execSystemId/$token_filepath";
        $response = $this->httpClient->request(
          'POST',
          $tapis_api_url,
          [
            'headers' => ['X-Tapis-Token' => $system_userJWT],
            'json' => [
              'username' => $this->tapisTokenProvider->getTapisUsername($tenantId, $jobOwnerUid),
              "permission" => "MODIFY",
            ],
            "http_errors" => FALSE,
          ]
        );
        if ($response->getStatusCode() !== 200) {
          $response_body = json_decode($response->getBody(), TRUE);
          if (is_array($response_body) && isset($response_body['message'])) {
            $message = $response_body['message'];
          } else {
            // Optionally, include the raw body or a default message
            $message = $response->getBody()->getContents() ?: '<br>Please try again later.';
          }
          throw new TapisJobException("An error occurred when redeeming a new access link for Tapis job ($jobUuid) from the Tapis '$tapis_tenant_id' tenant: $message");
        }
      }

      $tapis_api_url = "$tapis_api_endpoint/" . self::TAPIS_API_VERSION . "/files/ops/$execSystemId/$token_filepath";

      $response = $this->httpClient->request('DELETE', $tapis_api_url,
        [
          'headers' => ['X-Tapis-Token' => $system_userJWT],
          "http_errors" => FALSE,
        ]
      );
      $response_body = json_decode($response->getBody(), TRUE);

      // Log the response array as a string.
      // \Drupal::logger('tapis_job')->debug(print_r($response_body, TRUE));.
      $this->logger->debug(print_r($response_body, TRUE));

      // @todo Figure out how to handle Tapis api call errors
    }

    $delete_link_url = "$satellite_proxy/destroytoken.cgi";
    $response = $this->httpClient->request('GET', $delete_link_url, [
      'query' => [
        'token' => $token,
      ],
      'http_errors' => FALSE,
    ]);
    // @todo Figure out how to handle api call errors
    //   if ($response->getStatusCode() !== 200) {
    //   throw new TapisJobException("An error occurred when unregistering
    //   a job proxy id ('$jobProxyId') with the Satellite proxy
    //   in the tenant '$tenantId'.");
    //   }.
  }

  /**
   * {@inheritDoc}
   */
  public function deleteAllAccessLinksForJob($jobId) {
    // $jobAccessLinkStorage = \Drupal::entityTypeManager()
    // ->getStorage('job_access_link');
    $jobAccessLinkStorage = $this->entityTypeManager->getStorage('job_access_link');

    $ids = $jobAccessLinkStorage->getQuery()
      ->accessCheck(FALSE)
      ->condition('job.target_id', $jobId)
      ->execute();

    foreach ($ids as $id) {
      $jobAccessLinkStorage->load($id)->delete();
    }
  }

  /**
   * {@inheritDoc}
   */
  public function deleteAccessLink($jobAccessLinkId) {
    // $jobAccessLink = \Drupal::entityTypeManager()
    // ->getStorage('job_access_link')->load($jobAccessLinkId);
    $jobAccessLink = $this->entityTypeManager->getStorage('job_access_link')->load($jobAccessLinkId);
    $jobAccessLink->delete();
  }

  /**
   * {@inheritDoc}
   */
  public function createNewAccessLink($tenantId, TapisJob $job, $uid = -1) {
    if ($uid === -1) {
      // $uid = \Drupal::currentUser()->id();
      $uid = $this->currentUser->id();
    }

    $app = $job->get("app")->first();
    if ($app) {
      $proxyId = $this->createNewSatelliteToken($tenantId);

      // $jobAccessLink = \Drupal::entityTypeManager()
      $jobAccessLink = $this->entityTypeManager
        ->getStorage('job_access_link')
        ->create(
          [
            'job' => ['target_id' => $job->id()],
            'proxyId' => $proxyId,
            'uid' => ['target_id' => $uid],
          ]
        );
      $jobAccessLink->save();

      return $jobAccessLink;
    }
    return NULL;
  }

  /**
   * {@inheritDoc}
   */
  public function createNewSatelliteToken($tenantId) {
    $tenantInfo = $this->tapisSiteTenantProvider->getTenantInfo($tenantId);
    $satellite_proxy_url = $tenantInfo['satellite_proxy_url'];
    $tenant_tapis_id = $tenantInfo['tapis_id'];

    $create_job_proxyid_url = "$satellite_proxy_url/getlink_v2.cgi?prefix=app-$tenant_tapis_id-";
    $response = $this->httpClient->request('GET', $create_job_proxyid_url, [
      'http_errors' => FALSE,
    ]);

    // The response will be a plain text of the form:
    // Your token is
    // <token>
    // get the plain text response body and parse out the token.
    $response_body = $response->getBody()->getContents();
    $token = trim(str_replace("Your token is", "", $response_body));
    return $token;
  }

  /**
   * {@inheritDoc}
   */
  public function createAccessLink($tenantId, $job, $proxyId, $uid = -1) {
    if ($uid === -1) {
      // $uid = \Drupal::currentUser()->id();
      $uid = $this->currentUser->id();
    }

    $app = $job->get("app")->first();
    if ($app) {
      // $jobAccessLink = \Drupal::entityTypeManager()
      $jobAccessLink = $this->entityTypeManager
        ->getStorage('job_access_link')
        ->create(
          [
            'job' => ['target_id' => $job->id()],
            'proxyId' => $proxyId,
            'uid' => ['target_id' => $uid],
          ]
        );
      $jobAccessLink->save();

      return $jobAccessLink;
    }
    return NULL;
  }

  /**
   * {@inheritDoc}
   */
  public function getJobAccessLinksForJob($job) {
    // $jobAccessLinkStorage = \Drupal::entityTypeManager()
    // ->getStorage('job_access_link');
    $jobAccessLinkStorage = $this->entityTypeManager->getStorage('job_access_link');

    $ids = $jobAccessLinkStorage->getQuery()
      ->accessCheck(FALSE)
      // Type = bundle id (machine name)
      ->condition('job.target_id', $job->id())
      // Sorted by time of creation.
      ->sort('created', 'ASC')
      ->execute();

    if (count($ids) <= 0) {
      // No job access links are available for this job!
      return NULL;
    }

    return $jobAccessLinkStorage->loadMultiple($ids);
  }

  /**
   * {@inheritDoc}
   */
  public function getAJobAccessLinkForJob($job) {
    // $jobAccessLinkStorage = \Drupal::entityTypeManager()
    // ->getStorage('job_access_link');
    $jobAccessLinkStorage = $this->entityTypeManager->getStorage('job_access_link');

    $ids = $jobAccessLinkStorage->getQuery()
      ->accessCheck(FALSE)
      // Type = bundle id (machine name)
      ->condition('job.target_id', $job->id())
      // Sorted by time of creation.
      ->sort('created', 'ASC')
      ->execute();

    if (count($ids) <= 0) {
      // No job access links are available for this job!
      return NULL;
    }
    $jobAccessLinkId = current($ids);
    return $jobAccessLinkStorage->load($jobAccessLinkId);
  }

  /**
   * {@inheritDoc}
   */
  public function getAllJobNames($uid = -1) {
    if ($uid === -1) {
      $uid = $this->currentUser->id();
    }

    $jobNames = [];
    $connection = Database::getConnection();
    $select = $connection->select('tapis_job', 't');
    $select->addField('t', 'label', 'label');
    $select->condition('t.uid', $uid, '=');
    $result = $select->execute()->fetchAll();
    if (count($result) > 0) {
      foreach ($result as $item) {
        $jobNames[] = $item->label;
      }
    }

    return $jobNames;
  }

  /**
   * Custom function to check if a value is an integer.
   */
  private function isIntVal($data): bool {
    if (is_int($data) === TRUE) {
      return TRUE;
    }
    if (is_string($data) === TRUE && is_numeric($data) === TRUE) {
      return (!str_contains($data, '.'));
    }
    return FALSE;
  }

  /**
   * {@inheritDoc}
   */
  private function incrementLastNumber($prefix, $jobNames) {
    $numbers = [];
    // Automatically escape special characters in the prefix.
    $escapedPrefix = preg_quote($prefix, '/');

    // Iterate through the job names.
    foreach ($jobNames as $jobName) {
      // Check if the job name starts with "Job_".
      if (preg_match('/^' . $escapedPrefix . '/', $jobName)) {
        // Split the job name by "Job_".
        $parts = preg_split('/^' . $escapedPrefix . '/', $jobName);
        // Get the second value (the part after "Job_")
        $secondValue = $parts[1];
        // Check if the second value is an integer.
        if ($this->isIntVal($secondValue)) {
          $numbers[] = intval($secondValue);
        }
      }
    }

    // Find the highest number
    // Increment the highest number and append it to the base name.
    if (!empty($numbers)) {
      $highestNumber = max($numbers) + 1;
    }
    else {
      $highestNumber = 1;
    }

    return $highestNumber;
  }

  /**
   * Function to generate a unique job name.
   */
  public function generateUniqueJobName($userJobName) {
    $jobNames = $this->getAllJobNames();
    $userJobName = trim($userJobName);
    if (in_array($userJobName, $jobNames)) {
      // Extract the base job name and the number from the user's job name
      // Split the string by the last underscore.
      $parts = preg_split('/.*\K_/', $userJobName);
      $baseName = $parts[0];
      $number = 1;
      if (count($parts) === 2) {
        // Output the parts.
        if (!$this->isIntVal($parts[1])) {
          $baseName = $userJobName;
        }
        if (in_array($baseName, $jobNames)) {
          // Find the highest number appended to the job name.
          $number = $this->incrementLastNumber($baseName . "_", $jobNames);
        }
        else {
          $baseName = $userJobName;
        }
      }
      else {
        // Find the highest number appended to the job name.
        $number = $this->incrementLastNumber($baseName . "_", $jobNames);
      }
      return $baseName . "_" . $number;
    }
    return $userJobName;
  }

  /**
   * {@inheritDoc}
   */
  public function checkAccessForAppAndSystem($node) {
    $tapis_app_system_id = $node->get(AppDrupalIds::APP_DEFAULT_SYSTEM)
      ->getValue()[0]['target_id'];
    $tapis_app_system_node = $this->entityTypeManager->getStorage('node')
      ->load($tapis_app_system_id);
    $tenantId = $node->get(AppDrupalIds::APP_TENANT)
      ->first()
      ->getValue()['target_id'];
    $tapisSystemId = $tapis_app_system_node->get(SystemDrupalIds::SYSTEM_TAPIS_ID)
      ->first()
      ->getValue()['value'];
    $tapisSystemOwner = $tapis_app_system_node->get(SystemDrupalIds::SYSTEM_OWNER)
      ->first()
      ->getValue()['target_id'];

    // Get the system credential to use for this user and system,
    // get its loginUser value, and replace [loginUser] with it.
    $systemCredentialIds = $this->entityTypeManager->getStorage("tapis_system_credential")
      ->getQuery()
      ->condition("uid", $this->currentUser->id())
      ->condition("system", $tapis_app_system_id)
      ->accessCheck(FALSE)
      ->execute();

    $system = $this->tapisSystemProvider->getSystem($tenantId, $tapisSystemId, $tapisSystemOwner);
    if ($system['isDynamicEffectiveUser'] && empty($systemCredentialIds)) {
      // If there are no system credentials for this user and system,
      // display an error message and set the form state to rebuild and return.
      return FALSE;
    }

    $isPublic = $system['isPublic'];
    $sharedWithUsers = $system['sharedWithUsers'];
    $tapisAppOwner = $node->get(AppDrupalIds::APP_OWNER)
      ->first()
      ->getValue()['target_id'];
    // Added the app owner into the shared list for the system.
    $userName = $this->tapisTokenProvider->getTapisUsername($tenantId, $tapisAppOwner);
    if (!$isPublic && !in_array($userName, $sharedWithUsers)) {
      $this->tapisSystemProvider->shareSystemWithUser($tenantId, $tapisSystemId, $tapisAppOwner, $tapisSystemOwner);
    }
    // Added the current user into the shared list for the system.
    $userName = $this->tapisTokenProvider->getTapisUsername($tenantId, $this->currentUser->id());
    if (!$isPublic && !in_array($userName, $sharedWithUsers)) {
      $this->tapisSystemProvider->shareSystemWithUser($tenantId, $tapisSystemId, $this->currentUser->id(), $tapisSystemOwner);
    }
    $tapisAppId = $node->get(AppDrupalIds::APP_TAPIS_ID)
      ->first()
      ->getValue()['value'];
    $tapisAppVersion = $node->get(AppDrupalIds::APP_VERSION)
      ->first()
      ->getValue()['value'];
    $tapisAppObject = $this->tapisAppProvider->getAppVersion($tenantId, $tapisAppId, $tapisAppVersion, $tapisAppOwner);
    $isPublic = $tapisAppObject['isPublic'];
    $sharedWithUsers = $tapisAppObject['sharedWithUsers'];
    if (!$isPublic && !in_array($userName, $sharedWithUsers)) {
      $this->tapisAppProvider->shareAppWithUser($tenantId, $tapisAppId, $this->currentUser->id(), $tapisAppOwner);
    }
    return TRUE;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc