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

com.google.apphosting.utils.config.QueueXml Maven / Gradle / Ivy

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.apphosting.utils.config;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Parsed queue.xml file.
 *
 * Any additions to this class should also be made to the YAML
 * version in QueueYamlReader.java.
 *
 */
// Following the format of CronXml.
public class QueueXml {

  static final String RATE_REGEX = "([0-9]+(\\.[0-9]+)?)/([smhd])";
  static final Pattern RATE_PATTERN = Pattern.compile(RATE_REGEX);

  static final String TOTAL_STORAGE_LIMIT_REGEX = "^([0-9]+(\\.[0-9]*)?[BKMGT]?)";
  static final Pattern TOTAL_STORAGE_LIMIT_PATTERN = Pattern.compile(TOTAL_STORAGE_LIMIT_REGEX);

  // Keep these in sync with taskqueue.QueueConstants.
  private static final int MAX_QUEUE_NAME_LENGTH = 100;
  private static final String QUEUE_NAME_REGEX = "[a-zA-Z\\d-]{1," + MAX_QUEUE_NAME_LENGTH + "}";
  private static final Pattern QUEUE_NAME_PATTERN = Pattern.compile(QUEUE_NAME_REGEX);

  private static final String TASK_AGE_LIMIT_REGEX =
      "([0-9]+(?:\\.?[0-9]*(?:[eE][\\-+]?[0-9]+)?)?)([smhd])";
  private static final Pattern TASK_AGE_LIMIT_PATTERN = Pattern.compile(TASK_AGE_LIMIT_REGEX);

  private static final String MODE_REGEX = "push|pull";
  private static final Pattern MODE_PATTERN = Pattern.compile(MODE_REGEX);

  private static final int MAX_TARGET_LENGTH = 100;
  private static final String TARGET_REGEX = "[a-z\\d\\-\\.]{1," + MAX_TARGET_LENGTH + "}";
  private static final Pattern TARGET_PATTERN = Pattern.compile(TARGET_REGEX);

  /**
   * The default queue name.  Keep this in sync with
   * {@link com.google.appengine.api.taskqueue.Queue#DEFAULT_QUEUE}.
   */
  private static final String DEFAULT_QUEUE = "default";

  /**
   * Enumerates the allowed units for Queue rate.
   */
  public enum RateUnit {
    SECOND('s', 1),
    MINUTE('m', SECOND.getSeconds() * 60),
    HOUR('h', MINUTE.getSeconds() * 60),
    DAY('d', HOUR.getSeconds() * 24);

    final char ident;
    final int seconds;

    RateUnit(char ident, int seconds) {
      this.ident = ident;
      this.seconds = seconds;
    }

    static RateUnit valueOf(char unit) {
      switch (unit) {
        case 's' : return SECOND;
        case 'm' : return MINUTE;
        case 'h' : return HOUR;
        case 'd' : return DAY;
      }
      throw new AppEngineConfigException("Invalid rate was specified.");
    }

    public char getIdent() {
      return ident;
    }

    public int getSeconds() {
      return seconds;
    }
  }

  /**
   * Access control list for a queue.
   */
  public static class AclEntry {
    private String userEmail;
    private String writerEmail;

    public AclEntry() {
      userEmail = null;
      writerEmail = null;
    }

    public void setUserEmail(String userEmail) {
      this.userEmail = userEmail;
    }

    public String getUserEmail() {
      return userEmail;
    }

    public void setWriterEmail(String writerEmail) {
      this.writerEmail = writerEmail;
    }

    public String getWriterEmail() {
      return writerEmail;
    }

    // Generated by eclipse.
    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((userEmail == null) ? 0 : userEmail.hashCode());
      result = prime * result + ((writerEmail == null) ? 0 : writerEmail.hashCode());
      return result;
    }

    // Generated by eclipse.
    @Override
    public boolean equals(Object obj) {
      if (this == obj) return true;
      if (obj == null) return false;
      if (getClass() != obj.getClass()) return false;
      AclEntry other = (AclEntry) obj;
      if (userEmail == null) {
        if (other.userEmail != null) return false;
      } else if (!userEmail.equals(other.userEmail)) return false;
      if (writerEmail == null) {
        if (other.writerEmail != null) return false;
      } else if (!writerEmail.equals(other.writerEmail)) return false;
      return true;
    }
  }

  /**
   * Describes a queue's optional retry parameters.
   */
  public static class RetryParameters {
    private Integer retryLimit;
    private Integer ageLimitSec;
    private Double minBackoffSec;
    private Double maxBackoffSec;
    private Integer maxDoublings;

    public RetryParameters() {
      retryLimit = null;
      ageLimitSec = null;
      minBackoffSec = null;
      maxBackoffSec = null;
      maxDoublings = null;
    }

    public Integer getRetryLimit() {
      return retryLimit;
    }

    public void setRetryLimit(int retryLimit) {
      this.retryLimit = retryLimit;
    }

    public void setRetryLimit(String retryLimit) {
      this.retryLimit = Integer.valueOf(retryLimit);
    }

    public Integer getAgeLimitSec() {
      return ageLimitSec;
    }

    public void setAgeLimitSec(String ageLimitString) {
      Matcher matcher = TASK_AGE_LIMIT_PATTERN.matcher(ageLimitString);
      if (!matcher.matches() || matcher.groupCount() != 2) {
        throw new AppEngineConfigException("Invalid task age limit was specified.");
      }
      double rateUnitSec = RateUnit.valueOf(matcher.group(2).charAt(0)).getSeconds();
      Double ageLimit = Double.parseDouble(matcher.group(1)) * rateUnitSec;
      this.ageLimitSec = ageLimit.intValue();
    }

    public Double getMinBackoffSec() {
      return minBackoffSec;
    }

    public void setMinBackoffSec(double minBackoffSec) {
      this.minBackoffSec = minBackoffSec;
    }

    public void setMinBackoffSec(String minBackoffSec) {
      this.minBackoffSec = Double.valueOf(minBackoffSec);
    }

    public Double getMaxBackoffSec() {
      return maxBackoffSec;
    }

    public void setMaxBackoffSec(double maxBackoffSec) {
      this.maxBackoffSec = maxBackoffSec;
    }

    public void setMaxBackoffSec(String maxBackoffSec) {
      this.maxBackoffSec = Double.valueOf(maxBackoffSec);
    }

    public Integer getMaxDoublings() {
      return maxDoublings;
    }

    public void setMaxDoublings(int maxDoublings) {
      this.maxDoublings = maxDoublings;
    }

    public void setMaxDoublings(String maxDoublings) {
      this.maxDoublings = Integer.valueOf(maxDoublings);
    }

    // Generated by Eclipse
    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((ageLimitSec == null) ? 0 : ageLimitSec.hashCode());
      result = prime * result + ((maxBackoffSec == null) ? 0 : maxBackoffSec.hashCode());
      result = prime * result + ((maxDoublings == null) ? 0 : maxDoublings.hashCode());
      result = prime * result + ((minBackoffSec == null) ? 0 : minBackoffSec.hashCode());
      result = prime * result + ((retryLimit == null) ? 0 : retryLimit.hashCode());
      return result;
    }

    // Generated by Eclipse
    @Override
    public boolean equals(Object obj) {
      if (this == obj) return true;
      if (obj == null) return false;
      if (getClass() != obj.getClass()) return false;
      RetryParameters other = (RetryParameters) obj;
      if (ageLimitSec == null) {
        if (other.ageLimitSec != null) return false;
      } else if (!ageLimitSec.equals(other.ageLimitSec)) return false;
      if (maxBackoffSec == null) {
        if (other.maxBackoffSec != null) return false;
      } else if (!maxBackoffSec.equals(other.maxBackoffSec)) return false;
      if (maxDoublings == null) {
        if (other.maxDoublings != null) return false;
      } else if (!maxDoublings.equals(other.maxDoublings)) return false;
      if (minBackoffSec == null) {
        if (other.minBackoffSec != null) return false;
      } else if (!minBackoffSec.equals(other.minBackoffSec)) return false;
      if (retryLimit == null) {
        if (other.retryLimit != null) return false;
      } else if (!retryLimit.equals(other.retryLimit)) return false;
      return true;
    }
  }

  /**
   * Describes a single queue entry.
   */
  public static class Entry {
    private String name;
    private Double rate;
    private RateUnit rateUnit;
    private Integer bucketSize;
    private Integer maxConcurrentRequests;
    private RetryParameters retryParameters;
    private String target;
    private String mode;
    private List acl;

    /** Create an empty queue entry. */
    public Entry() {
      name = null;
      rate = null;
      rateUnit = RateUnit.SECOND;
      bucketSize = null;
      maxConcurrentRequests = null;
      retryParameters = null;
      target = null;
      mode = null;
      acl = null;
    }

    public Entry(String name, double rate, RateUnit rateUnit, int bucketSize,
                 Integer maxConcurrentRequests, String target) {
      this.name = name;
      this.rate = rate;
      this.rateUnit = rateUnit;
      this.bucketSize = bucketSize;
      this.maxConcurrentRequests = maxConcurrentRequests;
      this.target = target;
    }

    public String getName() {
      return name;
    }

    public void setName(String queueName) {
      if (queueName == null || queueName.length() == 0 ||
          !QUEUE_NAME_PATTERN.matcher(queueName).matches()) {
        throw new AppEngineConfigException(
            "Queue name does not match expression " + QUEUE_NAME_PATTERN +
            "; found '" + queueName + "'");
      }
      this.name = queueName;
    }

    public void setMode(String mode) {
      if (mode == null || mode.length() == 0 ||
          !MODE_PATTERN.matcher(mode).matches()) {
        throw new AppEngineConfigException(
            "mode must be either 'push' or 'pull'");
      }
      this.mode = mode;
    }

    public String getMode() {
      return mode;
    }

    public List getAcl() {
      return acl;
    }

    public void setAcl(List acl) {
      this.acl = acl;
    }

    public void addAcl(AclEntry aclEntry) {
      this.acl.add(aclEntry);
    }

    public Double getRate() {
      return rate;
    }

    public void setRate(double rate) {
      this.rate = rate;
    }

    /**
     * Set rate and units based on a "number/unit" formatted string.
     * @param rateString My be "0" or "number/unit" where unit is 's|m|h|d'.
     */
    public void setRate(String rateString) {
      if (rateString.equals("0")) {
        rate = 0.0;
        rateUnit = RateUnit.SECOND;
        return;
      }
      Matcher matcher = RATE_PATTERN.matcher(rateString);
      if (!matcher.matches()) {
        throw new AppEngineConfigException("Invalid queue rate was specified.");
      }
      String digits = matcher.group(1);
      rateUnit = RateUnit.valueOf(matcher.group(3).charAt(0));
      rate = Double.valueOf(digits);
    }

    public RateUnit getRateUnit() {
      return rateUnit;
    }

    public void setRateUnit(RateUnit rateUnit) {
      this.rateUnit = rateUnit;
    }

    public Integer getBucketSize() {
      return bucketSize;
    }

    public void setBucketSize(int bucketSize) {
      this.bucketSize = bucketSize;
    }

    public void setBucketSize(String bucketSize) {
      try {
        this.bucketSize = Integer.valueOf(bucketSize);
      } catch (NumberFormatException exception) {
        throw new AppEngineConfigException("Invalid bucket-size was specified.", exception);
      }
    }

    public Integer getMaxConcurrentRequests() {
      return maxConcurrentRequests;
    }

    public void setMaxConcurrentRequests(int maxConcurrentRequests) {
      this.maxConcurrentRequests = maxConcurrentRequests;
    }

    public void setMaxConcurrentRequests(String maxConcurrentRequests) {
      try {
        this.maxConcurrentRequests = Integer.valueOf(maxConcurrentRequests);
      } catch (NumberFormatException exception) {
        throw new AppEngineConfigException("Invalid max-concurrent-requests was specified: '" +
                                           maxConcurrentRequests + "'", exception);
      }
    }

    public RetryParameters getRetryParameters() {
      return retryParameters;
    }

    public void setRetryParameters(RetryParameters retryParameters) {
      this.retryParameters = retryParameters;
    }

    public String getTarget() {
      return target;
    }

    public void setTarget(String target) {
      Matcher matcher = TARGET_PATTERN.matcher(target);
      if (!matcher.matches()) {
        throw new AppEngineConfigException("Invalid queue target was specified. Target: '" +
                                           target + "'");
      }
      this.target = target;
    }

    // Generated by Eclipse.
    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((acl == null) ? 0 : acl.hashCode());
      result = prime * result + ((bucketSize == null) ? 0 : bucketSize.hashCode());
      result =
          prime * result + ((maxConcurrentRequests == null) ? 0 : maxConcurrentRequests.hashCode());
      result = prime * result + ((mode == null) ? 0 : mode.hashCode());
      result = prime * result + ((name == null) ? 0 : name.hashCode());
      result = prime * result + ((rate == null) ? 0 : rate.hashCode());
      result = prime * result + ((rateUnit == null) ? 0 : rateUnit.hashCode());
      result = prime * result + ((target == null) ? 0 : target.hashCode());
      result = prime * result + ((retryParameters == null) ? 0 : retryParameters.hashCode());
      return result;
    }

    // Generated by Eclipse
    @Override
    public boolean equals(Object obj) {
      if (this == obj) return true;
      if (obj == null) return false;
      if (getClass() != obj.getClass()) return false;
      Entry other = (Entry) obj;
      if (acl == null) {
        if (other.acl != null) return false;
      } else if (!acl.equals(other.acl)) return false;
      if (bucketSize == null) {
        if (other.bucketSize != null) return false;
      } else if (!bucketSize.equals(other.bucketSize)) return false;
      if (maxConcurrentRequests == null) {
        if (other.maxConcurrentRequests != null) return false;
      } else if (!maxConcurrentRequests.equals(other.maxConcurrentRequests)) return false;
      if (mode == null) {
        if (other.mode != null) return false;
      } else if (!mode.equals(other.mode)) return false;
      if (name == null) {
        if (other.name != null) return false;
      } else if (!name.equals(other.name)) return false;
      if (rate == null) {
        if (other.rate != null) return false;
      } else if (!rate.equals(other.rate)) return false;
      if (rateUnit == null) {
        if (other.rateUnit != null) return false;
      } else if (!rateUnit.equals(other.rateUnit)) return false;
      if (target == null) {
        if (other.target != null) return false;
      } else if (!target.equals(other.target)) return false;
      if (retryParameters == null) {
        if (other.retryParameters != null) return false;
      } else if (!retryParameters.equals(other.retryParameters)) return false;
      return true;
    }
  }

  private final LinkedHashMap entries = new LinkedHashMap();
  private Entry lastEntry;

  private String totalStorageLimit = "";

  /**
   * Return a new {@link Entry} describing the default queue.
   */
  public static Entry defaultEntry() {
    return new Entry(DEFAULT_QUEUE, 5, RateUnit.SECOND, 5, null, null);
  }

  /**
   * Puts a new entry into the list defined by the queue.xml file.
   *
   * @throws AppEngineConfigException if the previously-last entry is still
   *    incomplete.
   * @return the new entry
   */
  public Entry addNewEntry() {
    validateLastEntry();
    lastEntry = new Entry();
    return lastEntry;
  }

  public void addEntry(Entry entry) {
    validateLastEntry();
    lastEntry = entry;
    validateLastEntry();
  }

  /**
   * Get the entries.
   */
  public Collection getEntries() {
    validateLastEntry();
    return entries.values();
  }

  /**
   * Check that the last entry defined is complete.
   * @throws AppEngineConfigException if it is not.
   */
  public void validateLastEntry() {
    if (lastEntry == null) {
      return;
    }
    if (lastEntry.getName() == null) {
      throw new AppEngineConfigException("Queue entry must have a name.");
    }
    if (entries.containsKey(lastEntry.getName())) {
      throw new AppEngineConfigException("Queue entry has duplicate name.");
    }
    if ("pull".equals(lastEntry.getMode())) {
      if (lastEntry.getRate() != null) {
        throw new AppEngineConfigException("Rate must not be specified for pull queue.");
      }
      if (lastEntry.getBucketSize() != null) {
        throw new AppEngineConfigException("Bucket size must not be specified for pull queue.");
      }
      if (lastEntry.getMaxConcurrentRequests() != null) {
        throw new AppEngineConfigException(
            "MaxConcurrentRequests must not be specified for pull queue.");
      }
      RetryParameters retryParameters = lastEntry.getRetryParameters();
      if (retryParameters != null) {
        // Task retry limit is supported for pull queues, but no other retry parameters.
        if (retryParameters.getAgeLimitSec() != null) {
          throw new AppEngineConfigException(
              "Age limit must not be specified for pull queue.");
        }
        if (retryParameters.getMinBackoffSec() != null) {
          throw new AppEngineConfigException(
              "Min backoff must not be specified for pull queue.");
        }
        if (retryParameters.getMaxBackoffSec() != null) {
          throw new AppEngineConfigException(
              "Max backoff must not be specified for pull queue.");
        }
        if (retryParameters.getMaxDoublings() != null) {
          throw new AppEngineConfigException(
              "Max doublings must not be specified for pull queue.");
        }
      }
    } else {
      if (lastEntry.getRate() == null) {
        throw new AppEngineConfigException("A queue rate is required for push queue.");
      }
    }
    entries.put(lastEntry.getName(), lastEntry);
    lastEntry = null;
  }

  public void setTotalStorageLimit(String s) {
    totalStorageLimit = s;
  }

  public String getTotalStorageLimit() {
    return totalStorageLimit;
  }

  /**
   * Get the YAML equivalent of this queue.xml file.
   *
   * @return contents of an equivalent {@code queue.yaml} file.
   */
  public String toYaml() {
    StringBuilder builder = new StringBuilder();
    if (getTotalStorageLimit().length() > 0) {
      builder.append("total_storage_limit: " + getTotalStorageLimit() + "\n\n");
    }
    builder.append("queue:\n");
    for (Entry ent : getEntries()) {
      builder.append("- name: " + ent.getName() + "\n");
      Double rate = ent.getRate();
      if (rate != null) {
        builder.append(
            "  rate: " + rate + '/' + ent.getRateUnit().getIdent() + "\n");
      }
      Integer bucketSize = ent.getBucketSize();
      if (bucketSize != null) {
        builder.append("  bucket_size: " + bucketSize + "\n");
      }
      Integer maxConcurrentRequests = ent.getMaxConcurrentRequests();
      if (maxConcurrentRequests != null) {
        builder.append("  max_concurrent_requests: " + maxConcurrentRequests + "\n");
      }
      RetryParameters retryParameters = ent.getRetryParameters();
      if (retryParameters != null) {
        builder.append("  retry_parameters:\n");
        if (retryParameters.getRetryLimit() != null) {
          builder.append("    task_retry_limit: " + retryParameters.getRetryLimit() + "\n");
        }
        if (retryParameters.getAgeLimitSec() != null) {
          builder.append("    task_age_limit: " + retryParameters.getAgeLimitSec() + "s\n");
        }
        if (retryParameters.getMinBackoffSec() != null) {
          builder.append("    min_backoff_seconds: " + retryParameters.getMinBackoffSec() + "\n");
        }
        if (retryParameters.getMaxBackoffSec() != null) {
          builder.append("    max_backoff_seconds: " + retryParameters.getMaxBackoffSec() + "\n");
        }
        if (retryParameters.getMaxDoublings() != null) {
          builder.append("    max_doublings: " + retryParameters.getMaxDoublings() + "\n");
        }
      }
      String target = ent.getTarget();
      if (target != null) {
        builder.append("  target: " + target + "\n");
      }
      String mode = ent.getMode();
      if (mode != null) {
        builder.append("  mode: " + mode + "\n");
      }
      List acl = ent.getAcl();
      if (acl != null) {
        builder.append("  acl:\n");
        for (AclEntry aclEntry : acl) {
          if (aclEntry.getUserEmail() != null) {
            builder.append("  - user_email: " + aclEntry.getUserEmail() + "\n");
          } else if (aclEntry.getWriterEmail() != null) {
            builder.append("  - writer_email: " + aclEntry.getWriterEmail() + "\n");
          }
        }
      }
    }
    return builder.toString();
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy