All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.appengine.api.taskqueue.TaskHandle Maven / Gradle / Ivy

Go to download

API for Google App Engine standard environment with some of the dependencies shaded (repackaged)

There is a newer version: 2.0.27
Show newest version
/*
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.appengine.api.taskqueue;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * Created from {@link Queue#add(TaskOptions)}. Contains the task name (generated if otherwise
 * unspecified), task ETA (computed if not specified) and queue name. The queue name and task name
 * uniquely identify the task for an application.
 *
 */
public final class TaskHandle implements Serializable {
  private static final long serialVersionUID = -2578988193753847512L;
  private String taskName;
  private String queueName;
  // etaUsec is used to track the eta to the same precision that is associated
  // with the task in bigtable. Currently this is required when modifying the
  // task lease as there is a current owner check  that compares the
  // eta in bigtable with the eta associated with the task.
  // etaMillis is deprecated and is instead stored in TaskOptions. etaUsec is not
  // stored in task options as it is only required for tasks that have been
  // retrieved from the queue - and these tasks are represented by TaskHandles.
  private long etaUsec;
  private long etaMillis;
  private Integer retryCount;
  private @Nullable TaskOptions options;

  TaskHandle(TaskOptions options, String queueName, @Nullable Integer retryCount) {
    validateTaskName(options.getTaskName());
    QueueApiHelper.validateQueueName(queueName);
    this.queueName = queueName;
    this.retryCount = retryCount;
    this.taskName = null; // We now use taskName inside options.
    this.etaMillis = 0; // Ditto for etaMillis.
    this.options = new TaskOptions(options); // Defensive copy
    setEtaUsecFromOptions(this.options);
  }

  public TaskHandle(TaskOptions options, String queueName) {
    this(options, queueName, 0);
  }

  /** @deprecated Use {@link TaskHandle#TaskHandle(TaskOptions, String)} */
  @Deprecated
  public TaskHandle(String name, String queueName, long etaMillis) {
    this(TaskOptions.Builder.withTaskName(name).etaMillis(etaMillis), queueName, null);
  }

  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    if (options == null) {
      // Legacy serialized TaskHandle objects do not have options.
      // We need to create them.
      options = TaskOptions.Builder.withTaskName(taskName).etaMillis(etaMillis);
      taskName = null;
      etaMillis = 0;
    }
    if (etaUsec == 0 && options.getEtaMillis() != null) {
      setEtaUsecFromOptions(options);
    }
  }

  @Override
  public int hashCode() {
    final int prime = 31;
    int result = options.hashCode();
    result = result * prime + queueName.hashCode();
    result = result * prime + (retryCount == null ? 0 : retryCount.intValue());
    result = result * prime + (int) (etaUsec ^ (etaUsec >>> 32));
    return result;
  }

  @Override
  public boolean equals(Object obj) {
    if (this == obj) return true;
    if (obj == null) return false;
    if (getClass() != obj.getClass()) return false;
    TaskHandle other = (TaskHandle) obj;
    if (options == null) {
      if (other.options != null) return false;
    } else if (!options.equals(other.options)) return false;
    if (queueName == null) {
      if (other.queueName != null) return false;
    } else if (!queueName.equals(other.queueName)) return false;
    if (retryCount == null) {
      if (other.retryCount != null) return false;
    } else if (!retryCount.equals(other.retryCount)) return false;
    if (etaUsec != other.etaUsec) {
      return false;
    }
    return true;
  }

  @Override
  public String toString() {
    return "TaskHandle[options="
        + options
        + "queueName="
        + queueName
        + ", retryCount="
        + retryCount
        + ", etaMillis = "
        + etaMillis
        + ", etaUsec = "
        + etaUsec
        + "]";
  }

  /**
   * Checks the name of this task matches QueueConstants.TASK_NAME_PATTERN
   *
   * @throws IllegalArgumentException
   */
  static void validateTaskName(String taskName) {
    // Verify task name matches RE specification.
    if (taskName == null
        || taskName.length() == 0
        || !QueueConstants.TASK_NAME_PATTERN.matcher(taskName).matches()) {
      throw new IllegalArgumentException(
          "Task name does not match expression "
              + QueueConstants.TASK_NAME_REGEX
              + "; given taskname: '"
              + taskName
              + "'");
    }
  }

  /**
   * Returns the name of this task. This may have been generated by a call to {@link Queue#add()} if
   * the name was not otherwise specified.
   */
  public String getName() {
    return options.getTaskName();
  }

  /** Returns the name of the queue that this task was submitted into. */
  public String getQueueName() {
    return queueName;
  }

  /**
   * Returns a time comparable to {@link System#currentTimeMillis()} when this task is scheduled for
   * execution.
   */
  public long getEtaMillis() {
    return options.getEtaMillis();
  }

  /**
   * Set the time comparable to {@link System#currentTimeMillis()} when this task is scheduled for
   * execution. For pull tasks this value specifies the lease expiration time.
   */
  void etaMillis(long etaMillis) {
    options.etaMillis(etaMillis);
    etaUsec = etaMillis * 1000;
  }

  /**
   * Returns the time when the task is scheduled for execution to microsecond precision. For pull
   * tasks this value specifies the lease expiration time. Microsecond precision is required for the
   * lease verification check when modifying the task lease.
   */
  long getEtaUsec() {
    return etaUsec;
  }

  /**
   * Set the time for when this task is scheduled for execution to microsecond precision. For pull
   * tasks this value specifies the lease expiration time.
   */
  TaskHandle etaUsec(long etaUsec) {
    this.etaUsec = etaUsec;
    options.etaMillis(etaUsec / 1000);
    return this;
  }

  /** Returns number of leases that had been performed on this task. Can return {@code null}. */
  public Integer getRetryCount() {
    return retryCount;
  }

  /** Returns binary payload data of this task. Can return {@code null}. */
  public byte[] getPayload() {
    return options.getPayload();
  }

  /**
   * Returns tag of this task. Can return {@code null}.
   *
   * @throws UnsupportedEncodingException
   */
  public String getTag() throws UnsupportedEncodingException {
    return options.getTag();
  }

  /** Returns tag of this task. Can return {@code null}. */
  public byte[] getTagAsBytes() {
    return options.getTagAsBytes();
  }

  private void setEtaUsecFromOptions(TaskOptions options) {
    if (options != null && options.getEtaMillis() != null) {
      etaUsec = options.getEtaMillis() * 1000;
    } else {
      etaUsec = 0;
    }
  }

  static final class KeyValuePair implements Map.Entry {
    private final String key;
    private String value;

    public KeyValuePair(String key, String value) {
      this.key = key;
      this.value = value;
    }

    @Override
    public String getKey() {
      return key;
    }

    @Override
    public String getValue() {
      return value;
    }

    @Override
    public String setValue(String v) {
      String old = value;
      value = v;
      return old;
    }

    @Override
    public int hashCode() {
      int prime = 31;
      int result = 0;
      result = prime * result + key.hashCode();
      result = prime * result + value.hashCode();
      return result;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
      if (!(o instanceof TaskHandle.KeyValuePair)) {
        return false;
      }
      KeyValuePair that = (KeyValuePair) o;
      return key.equals(that.key) && value.equals(that.value);
    }
  }

  /**
   * Attempts to decode the {@code payload} byte array in our {@code options} into a list of {@code
   * Map.Entry}.
   *
   * @throws UnsupportedEncodingException if the payload cannot be decoded as a {@code
   *     application/x-www-form-urlencoded} string.
   * @throws UnsupportedOperationException if the {@code options} has no payload or the payload
   *     bytes could not be interpreted as application/x-www-form-urlencoded key-value pairs.
   */
  public List> extractParams()
      throws UnsupportedEncodingException, UnsupportedOperationException {
    String payload = new String(getPayload());
    String[] paramStrings = payload.split("&");

    List> result = new ArrayList>();
    for (String param : paramStrings) {
      String[] kv = param.split("=", 2);
      if (kv.length != 2) {
        throw new UnsupportedOperationException(
            "Payload "
                + payload
                + " failed to decode as application/x-www-form-urlencoded pairs. "
                + param
                + " Length"
                + kv.length);
      }
      result.add(
          new KeyValuePair(URLDecoder.decode(kv[0], "UTF-8"), URLDecoder.decode(kv[1], "UTF-8")));
    }
    return result;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy