| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- <?php
- /*
- * Copyright 2016 Google LLC
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are
- * met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- * * Neither the name of Google Inc. nor the names of its
- * contributors may be used to endorse or promote products derived from
- * this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- namespace Google\ApiCore;
- use Google\LongRunning\Operation;
- use Google\Protobuf\Any;
- use Google\Protobuf\Internal\Message;
- use Google\Rpc\Status;
- use LogicException;
- /**
- * Response object from a long running API method.
- *
- * The OperationResponse object is returned by API methods that perform
- * a long running operation. It provides methods that can be used to
- * poll the status of the operation, retrieve the results, and cancel
- * the operation.
- *
- * To support a long running operation, the server must implement the
- * Operations API, which is used by the OperationResponse object. If
- * more control is required, it is possible to make calls against the
- * Operations API directly instead of via the OperationResponse object
- * using an Operations Client instance.
- */
- class OperationResponse
- {
- use PollingTrait;
- const DEFAULT_POLLING_INTERVAL = 1000;
- const DEFAULT_POLLING_MULTIPLIER = 2;
- const DEFAULT_MAX_POLLING_INTERVAL = 60000;
- const DEFAULT_MAX_POLLING_DURATION = 0;
- private string $operationName;
- private ?object $operationsClient;
- private ?string $operationReturnType;
- private ?string $metadataReturnType;
- private array $defaultPollSettings = [
- 'initialPollDelayMillis' => self::DEFAULT_POLLING_INTERVAL,
- 'pollDelayMultiplier' => self::DEFAULT_POLLING_MULTIPLIER,
- 'maxPollDelayMillis' => self::DEFAULT_MAX_POLLING_INTERVAL,
- 'totalPollTimeoutMillis' => self::DEFAULT_MAX_POLLING_DURATION,
- ];
- private ?object $lastProtoResponse;
- private bool $deleted = false;
- private array $additionalArgs;
- private string $getOperationMethod;
- private ?string $cancelOperationMethod;
- private ?string $deleteOperationMethod;
- private string $operationStatusMethod;
- /** @var mixed */
- private $operationStatusDoneValue;
- private ?string $operationErrorCodeMethod;
- private ?string $operationErrorMessageMethod;
- /**
- * OperationResponse constructor.
- *
- * @param string $operationName
- * @param object $operationsClient
- * @param array $options {
- * Optional. Options for configuring the operation response object.
- *
- * @type string $operationReturnType The return type of the longrunning operation.
- * @type string $metadataReturnType The type of the metadata returned in the operation response.
- * @type int $initialPollDelayMillis The initial polling interval to use, in milliseconds.
- * @type int $pollDelayMultiplier Multiplier applied to the polling interval on each retry.
- * @type int $maxPollDelayMillis The maximum polling interval to use, in milliseconds.
- * @type int $totalPollTimeoutMillis The maximum amount of time to continue polling.
- * @type object $lastProtoResponse A response already received from the server.
- * @type string $getOperationMethod The method on $operationsClient to get the operation.
- * @type string $cancelOperationMethod The method on $operationsClient to cancel the operation.
- * @type string $deleteOperationMethod The method on $operationsClient to delete the operation.
- * @type string $operationStatusMethod The method on the operation to get the status.
- * @type string $operationStatusDoneValue The method on the operation to determine if the status is done.
- * @type array $additionalOperationArguments Additional arguments to pass to $operationsClient methods.
- * @type string $operationErrorCodeMethod The method on the operation to get the error code
- * @type string $operationErrorMessageMethod The method on the operation to get the error status
- * }
- */
- public function __construct(string $operationName, $operationsClient, array $options = [])
- {
- $this->operationName = $operationName;
- $this->operationsClient = $operationsClient;
- $options += [
- 'operationReturnType' => null,
- 'metadataReturnType' => null,
- 'lastProtoResponse' => null,
- 'getOperationMethod' => 'getOperation',
- 'cancelOperationMethod' => 'cancelOperation',
- 'deleteOperationMethod' => 'deleteOperation',
- 'operationStatusMethod' => 'getDone',
- 'operationStatusDoneValue' => true,
- 'additionalOperationArguments' => [],
- 'operationErrorCodeMethod' => null,
- 'operationErrorMessageMethod' => null,
- ];
- $this->operationReturnType = $options['operationReturnType'];
- $this->metadataReturnType = $options['metadataReturnType'];
- $this->lastProtoResponse = $options['lastProtoResponse'];
- $this->getOperationMethod = $options['getOperationMethod'];
- $this->cancelOperationMethod = $options['cancelOperationMethod'];
- $this->deleteOperationMethod = $options['deleteOperationMethod'];
- $this->additionalArgs = $options['additionalOperationArguments'];
- $this->operationStatusMethod = $options['operationStatusMethod'];
- $this->operationStatusDoneValue = $options['operationStatusDoneValue'];
- $this->operationErrorCodeMethod = $options['operationErrorCodeMethod'];
- $this->operationErrorMessageMethod = $options['operationErrorMessageMethod'];
- if (isset($options['initialPollDelayMillis'])) {
- $this->defaultPollSettings['initialPollDelayMillis'] = $options['initialPollDelayMillis'];
- }
- if (isset($options['pollDelayMultiplier'])) {
- $this->defaultPollSettings['pollDelayMultiplier'] = $options['pollDelayMultiplier'];
- }
- if (isset($options['maxPollDelayMillis'])) {
- $this->defaultPollSettings['maxPollDelayMillis'] = $options['maxPollDelayMillis'];
- }
- if (isset($options['totalPollTimeoutMillis'])) {
- $this->defaultPollSettings['totalPollTimeoutMillis'] = $options['totalPollTimeoutMillis'];
- }
- }
- /**
- * Check whether the operation has completed.
- *
- * @return bool
- */
- public function isDone()
- {
- if (!$this->hasProtoResponse()) {
- return false;
- }
- $status = call_user_func([$this->lastProtoResponse, $this->operationStatusMethod]);
- if (is_null($status)) {
- return false;
- }
- return $status === $this->operationStatusDoneValue;
- }
- /**
- * Check whether the operation completed successfully. If the operation is not complete, or if the operation
- * failed, return false.
- *
- * @return bool
- */
- public function operationSucceeded()
- {
- if (!$this->hasProtoResponse()) {
- return false;
- }
- if (!$this->canHaveResult()) {
- // For Operations which do not have a result, we consider a successful
- // operation when the operation has completed without errors.
- return $this->isDone() && !$this->hasErrors();
- }
- return !is_null($this->getResult());
- }
- /**
- * Check whether the operation failed. If the operation is not complete, or if the operation
- * succeeded, return false.
- *
- * @return bool
- */
- public function operationFailed()
- {
- return $this->hasErrors();
- }
- /**
- * Get the formatted name of the operation
- *
- * @return string The formatted name of the operation
- */
- public function getName()
- {
- return $this->operationName;
- }
- /**
- * Poll the server in a loop until the operation is complete.
- *
- * Return true if the operation completed, otherwise return false. If the
- * $options['totalPollTimeoutMillis'] setting is not set (or set <= 0) then
- * pollUntilComplete will continue polling until the operation completes,
- * and therefore will always return true.
- *
- * @param array $options {
- * Options for configuring the polling behaviour.
- *
- * @type int $initialPollDelayMillis The initial polling interval to use, in milliseconds.
- * @type int $pollDelayMultiplier Multiplier applied to the polling interval on each retry.
- * @type int $maxPollDelayMillis The maximum polling interval to use, in milliseconds.
- * @type int $totalPollTimeoutMillis The maximum amount of time to continue polling, in milliseconds.
- * }
- * @throws ApiException If an API call fails.
- * @throws ValidationException
- * @return bool Indicates if the operation completed.
- */
- public function pollUntilComplete(array $options = [])
- {
- if ($this->isDone()) {
- return true;
- }
- $pollSettings = array_merge($this->defaultPollSettings, $options);
- return $this->poll(function () {
- $this->reload();
- return $this->isDone();
- }, $pollSettings);
- }
- /**
- * Reload the status of the operation with a request to the service.
- *
- * @throws ApiException If the API call fails.
- * @throws ValidationException If called on a deleted operation.
- */
- public function reload()
- {
- if ($this->deleted) {
- throw new ValidationException("Cannot call reload() on a deleted operation");
- }
- $this->lastProtoResponse = $this->operationsCall(
- $this->getOperationMethod,
- $this->getName(),
- $this->additionalArgs
- );
- }
- /**
- * Return the result of the operation. If operationSucceeded() is false, return null.
- *
- * @return mixed|null The result of the operation, or null if operationSucceeded() is false
- */
- public function getResult()
- {
- if (!$this->hasProtoResponse()) {
- return null;
- }
- if (!$this->canHaveResult()) {
- return null;
- }
- if (!$this->isDone()) {
- return null;
- }
- /** @var Any|null $anyResponse */
- $anyResponse = $this->lastProtoResponse->getResponse();
- if (is_null($anyResponse)) {
- return null;
- }
- if (is_null($this->operationReturnType)) {
- return $anyResponse;
- }
- $operationReturnType = $this->operationReturnType;
- /** @var Message $response */
- $response = new $operationReturnType();
- $response->mergeFromString($anyResponse->getValue());
- return $response;
- }
- /**
- * If the operation failed, return the status. If operationFailed() is false, return null.
- *
- * @return Status|null The status of the operation in case of failure, or null if
- * operationFailed() is false.
- */
- public function getError()
- {
- if (!$this->hasProtoResponse() || !$this->isDone()) {
- return null;
- }
- if ($this->operationErrorCodeMethod || $this->operationErrorMessageMethod) {
- $errorCode = $this->operationErrorCodeMethod
- ? call_user_func([$this->lastProtoResponse, $this->operationErrorCodeMethod])
- : null;
- $errorMessage = $this->operationErrorMessageMethod
- ? call_user_func([$this->lastProtoResponse, $this->operationErrorMessageMethod])
- : null;
- return (new Status())
- ->setCode(ApiStatus::rpcCodeFromHttpStatusCode($errorCode))
- ->setMessage($errorMessage);
- }
- if (method_exists($this->lastProtoResponse, 'getError')) {
- return $this->lastProtoResponse->getError();
- }
- return null;
- }
- /**
- * Get an array containing the values of 'operationReturnType', 'metadataReturnType', and
- * the polling options `initialPollDelayMillis`, `pollDelayMultiplier`, `maxPollDelayMillis`,
- * and `totalPollTimeoutMillis`. The array can be passed as the $options argument to the
- * constructor when creating another OperationResponse object.
- *
- * @return array
- */
- public function getDescriptorOptions()
- {
- return [
- 'operationReturnType' => $this->operationReturnType,
- 'metadataReturnType' => $this->metadataReturnType,
- ] + $this->defaultPollSettings;
- }
- /**
- * @return Operation|mixed|null The last Operation object received from the server.
- */
- public function getLastProtoResponse()
- {
- return $this->lastProtoResponse;
- }
- /**
- * @return object The OperationsClient object used to make
- * requests to the operations API.
- */
- public function getOperationsClient()
- {
- return $this->operationsClient;
- }
- /**
- * Cancel the long-running operation.
- *
- * For operations of type Google\LongRunning\Operation, this method starts
- * asynchronous cancellation on a long-running operation. The server
- * makes a best effort to cancel the operation, but success is not
- * guaranteed. If the server doesn't support this method, it will throw an
- * ApiException with code \Google\Rpc\Code::UNIMPLEMENTED. Clients can continue
- * to use reload and pollUntilComplete methods to check whether the cancellation
- * succeeded or whether the operation completed despite cancellation.
- * On successful cancellation, the operation is not deleted; instead, it becomes
- * an operation with a getError() value with a \Google\Rpc\Status code of 1,
- * corresponding to \Google\Rpc\Code::CANCELLED.
- *
- * @throws ApiException If the API call fails.
- * @throws LogicException If the API call method has not been configured
- */
- public function cancel()
- {
- if (is_null($this->cancelOperationMethod)) {
- throw new LogicException('The cancel operation is not supported by this API');
- }
- $this->operationsCall($this->cancelOperationMethod, $this->getName(), $this->additionalArgs);
- }
- /**
- * Delete the long-running operation.
- *
- * For operations of type Google\LongRunning\Operation, this method
- * indicates that the client is no longer interested in the operation result.
- * It does not cancel the operation. If the server doesn't support this method,
- * it will throw an ApiException with code \Google\Rpc\Code::UNIMPLEMENTED.
- *
- * @throws ApiException If the API call fails.
- * @throws LogicException If the API call method has not been configured
- */
- public function delete()
- {
- if (is_null($this->deleteOperationMethod)) {
- throw new LogicException('The delete operation is not supported by this API');
- }
- $this->operationsCall($this->deleteOperationMethod, $this->getName(), $this->additionalArgs);
- $this->deleted = true;
- }
- /**
- * Get the metadata returned with the last proto response. If a metadata type was provided, then
- * the return value will be of that type - otherwise, the return value will be of type Any. If
- * no metadata object is available, returns null.
- *
- * @return mixed The metadata returned from the server in the last response.
- */
- public function getMetadata()
- {
- if (!$this->hasProtoResponse()) {
- return null;
- }
- if (!method_exists($this->lastProtoResponse, 'getMetadata')) {
- // The call to getMetadata is only for OnePlatform LROs, and is not
- // supported by other LRO GAPIC clients (e.g. Compute)
- return null;
- }
- /** @var Any|null $any */
- $any = $this->lastProtoResponse->getMetadata();
- if (is_null($this->metadataReturnType)) {
- return $any;
- }
- if (is_null($any)) {
- return null;
- }
- // @TODO: This is probably not doing anything and can be removed in the next release.
- if (is_null($any->getValue())) {
- return null;
- }
- $metadataReturnType = $this->metadataReturnType;
- /** @var Message $metadata */
- $metadata = new $metadataReturnType();
- $metadata->mergeFromString($any->getValue());
- return $metadata;
- }
- private function operationsCall($method, $name, array $additionalArgs)
- {
- $args = array_merge([$name], $additionalArgs);
- return call_user_func_array([$this->operationsClient, $method], $args);
- }
- private function canHaveResult()
- {
- // The call to getResponse is only for OnePlatform LROs, and is not
- // supported by other LRO GAPIC clients (e.g. Compute)
- return method_exists($this->lastProtoResponse, 'getResponse');
- }
- private function hasErrors()
- {
- if (!$this->hasProtoResponse()) {
- return false;
- }
- if (method_exists($this->lastProtoResponse, 'getError')) {
- return !empty($this->lastProtoResponse->getError());
- }
- if ($this->operationErrorCodeMethod) {
- $errorCode = call_user_func([$this->lastProtoResponse, $this->operationErrorCodeMethod]);
- return !empty($errorCode);
- }
- // This should never happen unless an API is misconfigured
- throw new LogicException('Unable to determine operation error status for this service');
- }
- private function hasProtoResponse()
- {
- return !is_null($this->lastProtoResponse);
- }
- }
|