App Engine 1.8.4.
[gae.git] / java / src / main / com / google / apphosting / utils / config / QueueXml.java
bloba55a5efc14a95163dd0f23acd0df4a901a2c5e8f
1 // Copyright 2009 Google Inc. All Rights Reserved.
3 package com.google.apphosting.utils.config;
5 import java.util.Collection;
6 import java.util.LinkedHashMap;
7 import java.util.List;
8 import java.util.regex.Matcher;
9 import java.util.regex.Pattern;
11 /**
12 * Parsed queue.xml file.
14 * Any additions to this class should also be made to the YAML
15 * version in QueueYamlReader.java.
18 public class QueueXml {
20 static final String RATE_REGEX = "([0-9]+(\\.[0-9]+)?)/([smhd])";
21 static final Pattern RATE_PATTERN = Pattern.compile(RATE_REGEX);
23 static final String TOTAL_STORAGE_LIMIT_REGEX = "^([0-9]+(\\.[0-9]*)?[BKMGT]?)";
24 static final Pattern TOTAL_STORAGE_LIMIT_PATTERN = Pattern.compile(TOTAL_STORAGE_LIMIT_REGEX);
26 private static final int MAX_QUEUE_NAME_LENGTH = 100;
27 private static final String QUEUE_NAME_REGEX = "[a-zA-Z\\d-]{1," + MAX_QUEUE_NAME_LENGTH + "}";
28 private static final Pattern QUEUE_NAME_PATTERN = Pattern.compile(QUEUE_NAME_REGEX);
30 private static final String TASK_AGE_LIMIT_REGEX =
31 "([0-9]+(?:\\.?[0-9]*(?:[eE][\\-+]?[0-9]+)?)?)([smhd])";
32 private static final Pattern TASK_AGE_LIMIT_PATTERN = Pattern.compile(TASK_AGE_LIMIT_REGEX);
34 private static final String MODE_REGEX = "push|pull";
35 private static final Pattern MODE_PATTERN = Pattern.compile(MODE_REGEX);
37 private static final int MAX_TARGET_LENGTH = 100;
38 private static final String TARGET_REGEX = "[a-z\\d\\-]{1," + MAX_TARGET_LENGTH + "}";
39 private static final Pattern TARGET_PATTERN = Pattern.compile(TARGET_REGEX);
41 /**
42 * The default queue name. Keep this in sync with
43 * {@link com.google.appengine.api.taskqueue.Queue#DEFAULT_QUEUE}.
45 private static final String DEFAULT_QUEUE = "default";
47 /**
48 * Enumerates the allowed units for Queue rate.
50 public enum RateUnit {
51 SECOND('s', 1),
52 MINUTE('m', SECOND.getSeconds() * 60),
53 HOUR('h', MINUTE.getSeconds() * 60),
54 DAY('d', HOUR.getSeconds() * 24);
56 final char ident;
57 final int seconds;
59 RateUnit(char ident, int seconds) {
60 this.ident = ident;
61 this.seconds = seconds;
64 static RateUnit valueOf(char unit) {
65 switch (unit) {
66 case 's' : return SECOND;
67 case 'm' : return MINUTE;
68 case 'h' : return HOUR;
69 case 'd' : return DAY;
71 throw new AppEngineConfigException("Invalid rate was specified.");
74 public char getIdent() {
75 return ident;
78 public int getSeconds() {
79 return seconds;
83 /**
84 * Access control list for a queue.
86 public static class AclEntry {
87 private String userEmail;
88 private String writerEmail;
90 public AclEntry() {
91 userEmail = null;
92 writerEmail = null;
95 public void setUserEmail(String userEmail) {
96 this.userEmail = userEmail;
99 public String getUserEmail() {
100 return userEmail;
103 public void setWriterEmail(String writerEmail) {
104 this.writerEmail = writerEmail;
107 public String getWriterEmail() {
108 return writerEmail;
111 @Override
112 public int hashCode() {
113 final int prime = 31;
114 int result = 1;
115 result = prime * result + ((userEmail == null) ? 0 : userEmail.hashCode());
116 result = prime * result + ((writerEmail == null) ? 0 : writerEmail.hashCode());
117 return result;
120 @Override
121 public boolean equals(Object obj) {
122 if (this == obj) return true;
123 if (obj == null) return false;
124 if (getClass() != obj.getClass()) return false;
125 AclEntry other = (AclEntry) obj;
126 if (userEmail == null) {
127 if (other.userEmail != null) return false;
128 } else if (!userEmail.equals(other.userEmail)) return false;
129 if (writerEmail == null) {
130 if (other.writerEmail != null) return false;
131 } else if (!writerEmail.equals(other.writerEmail)) return false;
132 return true;
137 * Describes a queue's optional retry parameters.
139 public static class RetryParameters {
140 private Integer retryLimit;
141 private Integer ageLimitSec;
142 private Double minBackoffSec;
143 private Double maxBackoffSec;
144 private Integer maxDoublings;
146 public RetryParameters() {
147 retryLimit = null;
148 ageLimitSec = null;
149 minBackoffSec = null;
150 maxBackoffSec = null;
151 maxDoublings = null;
154 public Integer getRetryLimit() {
155 return retryLimit;
158 public void setRetryLimit(int retryLimit) {
159 this.retryLimit = retryLimit;
162 public void setRetryLimit(String retryLimit) {
163 this.retryLimit = Integer.valueOf(retryLimit);
166 public Integer getAgeLimitSec() {
167 return ageLimitSec;
170 public void setAgeLimitSec(String ageLimitString) {
171 Matcher matcher = TASK_AGE_LIMIT_PATTERN.matcher(ageLimitString);
172 if (!matcher.matches() || matcher.groupCount() != 2) {
173 throw new AppEngineConfigException("Invalid task age limit was specified.");
175 double rateUnitSec = RateUnit.valueOf(matcher.group(2).charAt(0)).getSeconds();
176 Double ageLimit = Double.valueOf(matcher.group(1)) * rateUnitSec;
177 this.ageLimitSec = ageLimit.intValue();
180 public Double getMinBackoffSec() {
181 return minBackoffSec;
184 public void setMinBackoffSec(double minBackoffSec) {
185 this.minBackoffSec = minBackoffSec;
188 public void setMinBackoffSec(String minBackoffSec) {
189 this.minBackoffSec = Double.valueOf(minBackoffSec);
192 public Double getMaxBackoffSec() {
193 return maxBackoffSec;
196 public void setMaxBackoffSec(double maxBackoffSec) {
197 this.maxBackoffSec = maxBackoffSec;
200 public void setMaxBackoffSec(String maxBackoffSec) {
201 this.maxBackoffSec = Double.valueOf(maxBackoffSec);
204 public Integer getMaxDoublings() {
205 return maxDoublings;
208 public void setMaxDoublings(int maxDoublings) {
209 this.maxDoublings = maxDoublings;
212 public void setMaxDoublings(String maxDoublings) {
213 this.maxDoublings = Integer.valueOf(maxDoublings);
216 @Override
217 public int hashCode() {
218 final int prime = 31;
219 int result = 1;
220 result = prime * result + ((ageLimitSec == null) ? 0 : ageLimitSec.hashCode());
221 result = prime * result + ((maxBackoffSec == null) ? 0 : maxBackoffSec.hashCode());
222 result = prime * result + ((maxDoublings == null) ? 0 : maxDoublings.hashCode());
223 result = prime * result + ((minBackoffSec == null) ? 0 : minBackoffSec.hashCode());
224 result = prime * result + ((retryLimit == null) ? 0 : retryLimit.hashCode());
225 return result;
228 @Override
229 public boolean equals(Object obj) {
230 if (this == obj) return true;
231 if (obj == null) return false;
232 if (getClass() != obj.getClass()) return false;
233 RetryParameters other = (RetryParameters) obj;
234 if (ageLimitSec == null) {
235 if (other.ageLimitSec != null) return false;
236 } else if (!ageLimitSec.equals(other.ageLimitSec)) return false;
237 if (maxBackoffSec == null) {
238 if (other.maxBackoffSec != null) return false;
239 } else if (!maxBackoffSec.equals(other.maxBackoffSec)) return false;
240 if (maxDoublings == null) {
241 if (other.maxDoublings != null) return false;
242 } else if (!maxDoublings.equals(other.maxDoublings)) return false;
243 if (minBackoffSec == null) {
244 if (other.minBackoffSec != null) return false;
245 } else if (!minBackoffSec.equals(other.minBackoffSec)) return false;
246 if (retryLimit == null) {
247 if (other.retryLimit != null) return false;
248 } else if (!retryLimit.equals(other.retryLimit)) return false;
249 return true;
254 * Describes a single queue entry.
256 public static class Entry {
257 private String name;
258 private Double rate;
259 private RateUnit rateUnit;
260 private Integer bucketSize;
261 private Integer maxConcurrentRequests;
262 private RetryParameters retryParameters;
263 private String target;
264 private String mode;
265 private List<AclEntry> acl;
267 /** Create an empty queue entry. */
268 public Entry() {
269 name = null;
270 rate = null;
271 rateUnit = RateUnit.SECOND;
272 bucketSize = null;
273 maxConcurrentRequests = null;
274 retryParameters = null;
275 target = null;
276 mode = null;
277 acl = null;
280 public Entry(String name, double rate, RateUnit rateUnit, int bucketSize,
281 Integer maxConcurrentRequests, String target) {
282 this.name = name;
283 this.rate = rate;
284 this.rateUnit = rateUnit;
285 this.bucketSize = bucketSize;
286 this.maxConcurrentRequests = maxConcurrentRequests;
287 this.target = target;
290 public String getName() {
291 return name;
294 public void setName(String queueName) {
295 if (queueName == null || queueName.length() == 0 ||
296 !QUEUE_NAME_PATTERN.matcher(queueName).matches()) {
297 throw new AppEngineConfigException(
298 "Queue name does not match expression " + QUEUE_NAME_PATTERN +
299 "; found '" + queueName + "'");
301 this.name = queueName;
304 public void setMode(String mode) {
305 if (mode == null || mode.length() == 0 ||
306 !MODE_PATTERN.matcher(mode).matches()) {
307 throw new AppEngineConfigException(
308 "mode must be either 'push' or 'pull'");
310 this.mode = mode;
313 public String getMode() {
314 return mode;
317 public List<AclEntry> getAcl() {
318 return acl;
321 public void setAcl(List<AclEntry> acl) {
322 this.acl = acl;
325 public void addAcl(AclEntry aclEntry) {
326 this.acl.add(aclEntry);
329 public Double getRate() {
330 return rate;
333 public void setRate(double rate) {
334 this.rate = rate;
338 * Set rate and units based on a "number/unit" formatted string.
339 * @param rateString My be "0" or "number/unit" where unit is 's|m|h|d'.
341 public void setRate(String rateString) {
342 if (rateString.equals("0")) {
343 rate = 0.0;
344 rateUnit = RateUnit.SECOND;
345 return;
347 Matcher matcher = RATE_PATTERN.matcher(rateString);
348 if (!matcher.matches()) {
349 throw new AppEngineConfigException("Invalid queue rate was specified.");
351 String digits = matcher.group(1);
352 rateUnit = RateUnit.valueOf(matcher.group(3).charAt(0));
353 rate = Double.valueOf(digits);
356 public RateUnit getRateUnit() {
357 return rateUnit;
360 public void setRateUnit(RateUnit rateUnit) {
361 this.rateUnit = rateUnit;
364 public Integer getBucketSize() {
365 return bucketSize;
368 public void setBucketSize(int bucketSize) {
369 this.bucketSize = bucketSize;
372 public void setBucketSize(String bucketSize) {
373 try {
374 this.bucketSize = Integer.valueOf(bucketSize);
375 } catch (NumberFormatException exception) {
376 throw new AppEngineConfigException("Invalid bucket-size was specified.", exception);
380 public Integer getMaxConcurrentRequests() {
381 return maxConcurrentRequests;
384 public void setMaxConcurrentRequests(int maxConcurrentRequests) {
385 this.maxConcurrentRequests = maxConcurrentRequests;
388 public void setMaxConcurrentRequests(String maxConcurrentRequests) {
389 try {
390 this.maxConcurrentRequests = Integer.valueOf(maxConcurrentRequests);
391 } catch (NumberFormatException exception) {
392 throw new AppEngineConfigException("Invalid max-concurrent-requests was specified: '" +
393 maxConcurrentRequests + "'", exception);
397 public RetryParameters getRetryParameters() {
398 return retryParameters;
401 public void setRetryParameters(RetryParameters retryParameters) {
402 this.retryParameters = retryParameters;
405 public String getTarget() {
406 return target;
409 public void setTarget(String target) {
410 Matcher matcher = TARGET_PATTERN.matcher(target);
411 if (!matcher.matches()) {
412 throw new AppEngineConfigException("Invalid queue target was specified. Target: '" +
413 target + "'");
415 this.target = target;
418 @Override
419 public int hashCode() {
420 final int prime = 31;
421 int result = 1;
422 result = prime * result + ((acl == null) ? 0 : acl.hashCode());
423 result = prime * result + ((bucketSize == null) ? 0 : bucketSize.hashCode());
424 result =
425 prime * result + ((maxConcurrentRequests == null) ? 0 : maxConcurrentRequests.hashCode());
426 result = prime * result + ((mode == null) ? 0 : mode.hashCode());
427 result = prime * result + ((name == null) ? 0 : name.hashCode());
428 result = prime * result + ((rate == null) ? 0 : rate.hashCode());
429 result = prime * result + ((rateUnit == null) ? 0 : rateUnit.hashCode());
430 result = prime * result + ((target == null) ? 0 : target.hashCode());
431 result = prime * result + ((retryParameters == null) ? 0 : retryParameters.hashCode());
432 return result;
435 @Override
436 public boolean equals(Object obj) {
437 if (this == obj) return true;
438 if (obj == null) return false;
439 if (getClass() != obj.getClass()) return false;
440 Entry other = (Entry) obj;
441 if (acl == null) {
442 if (other.acl != null) return false;
443 } else if (!acl.equals(other.acl)) return false;
444 if (bucketSize == null) {
445 if (other.bucketSize != null) return false;
446 } else if (!bucketSize.equals(other.bucketSize)) return false;
447 if (maxConcurrentRequests == null) {
448 if (other.maxConcurrentRequests != null) return false;
449 } else if (!maxConcurrentRequests.equals(other.maxConcurrentRequests)) return false;
450 if (mode == null) {
451 if (other.mode != null) return false;
452 } else if (!mode.equals(other.mode)) return false;
453 if (name == null) {
454 if (other.name != null) return false;
455 } else if (!name.equals(other.name)) return false;
456 if (rate == null) {
457 if (other.rate != null) return false;
458 } else if (!rate.equals(other.rate)) return false;
459 if (rateUnit == null) {
460 if (other.rateUnit != null) return false;
461 } else if (!rateUnit.equals(other.rateUnit)) return false;
462 if (target == null) {
463 if (other.target != null) return false;
464 } else if (!target.equals(other.target)) return false;
465 if (retryParameters == null) {
466 if (other.retryParameters != null) return false;
467 } else if (!retryParameters.equals(other.retryParameters)) return false;
468 return true;
472 private final LinkedHashMap<String, Entry> entries = new LinkedHashMap<String, Entry>();
473 private Entry lastEntry;
475 private String totalStorageLimit = "";
478 * Return a new {@link Entry} describing the default queue.
480 public static Entry defaultEntry() {
481 return new Entry(DEFAULT_QUEUE, 5, RateUnit.SECOND, 5, null, null);
485 * Puts a new entry into the list defined by the queue.xml file.
487 * @throws AppEngineConfigException if the previously-last entry is still
488 * incomplete.
489 * @return the new entry
491 public Entry addNewEntry() {
492 validateLastEntry();
493 lastEntry = new Entry();
494 return lastEntry;
497 public void addEntry(Entry entry) {
498 validateLastEntry();
499 lastEntry = entry;
500 validateLastEntry();
504 * Get the entries.
506 public Collection<Entry> getEntries() {
507 validateLastEntry();
508 return entries.values();
512 * Check that the last entry defined is complete.
513 * @throws AppEngineConfigException if it is not.
515 public void validateLastEntry() {
516 if (lastEntry == null) {
517 return;
519 if (lastEntry.getName() == null) {
520 throw new AppEngineConfigException("Queue entry must have a name.");
522 if (entries.containsKey(lastEntry.getName())) {
523 throw new AppEngineConfigException("Queue entry has duplicate name.");
525 if ("pull".equals(lastEntry.getMode())) {
526 if (lastEntry.getRate() != null) {
527 throw new AppEngineConfigException("Rate must not be specified for pull queue.");
529 if (lastEntry.getBucketSize() != null) {
530 throw new AppEngineConfigException("Bucket size must not be specified for pull queue.");
532 if (lastEntry.getMaxConcurrentRequests() != null) {
533 throw new AppEngineConfigException(
534 "MaxConcurrentRequests must not be specified for pull queue.");
536 RetryParameters retryParameters = lastEntry.getRetryParameters();
537 if (retryParameters != null) {
538 if (retryParameters.getAgeLimitSec() != null) {
539 throw new AppEngineConfigException(
540 "Age limit must not be specified for pull queue.");
542 if (retryParameters.getMinBackoffSec() != null) {
543 throw new AppEngineConfigException(
544 "Min backoff must not be specified for pull queue.");
546 if (retryParameters.getMaxBackoffSec() != null) {
547 throw new AppEngineConfigException(
548 "Max backoff must not be specified for pull queue.");
550 if (retryParameters.getMaxDoublings() != null) {
551 throw new AppEngineConfigException(
552 "Max doublings must not be specified for pull queue.");
555 } else {
556 if (lastEntry.getRate() == null) {
557 throw new AppEngineConfigException("A queue rate is required for push queue.");
560 entries.put(lastEntry.getName(), lastEntry);
561 lastEntry = null;
564 public void setTotalStorageLimit(String s) {
565 totalStorageLimit = s;
568 public String getTotalStorageLimit() {
569 return totalStorageLimit;
573 * Get the YAML equivalent of this queue.xml file.
575 * @return contents of an equivalent {@code queue.yaml} file.
577 public String toYaml() {
578 StringBuilder builder = new StringBuilder();
579 if (getTotalStorageLimit().length() > 0) {
580 builder.append("total_storage_limit: " + getTotalStorageLimit() + "\n\n");
582 builder.append("queue:\n");
583 for (Entry ent : getEntries()) {
584 builder.append("- name: " + ent.getName() + "\n");
585 Double rate = ent.getRate();
586 if (rate != null) {
587 builder.append(
588 " rate: " + rate + '/' + ent.getRateUnit().getIdent() + "\n");
590 Integer bucketSize = ent.getBucketSize();
591 if (bucketSize != null) {
592 builder.append(" bucket_size: " + bucketSize + "\n");
594 Integer maxConcurrentRequests = ent.getMaxConcurrentRequests();
595 if (maxConcurrentRequests != null) {
596 builder.append(" max_concurrent_requests: " + maxConcurrentRequests + "\n");
598 RetryParameters retryParameters = ent.getRetryParameters();
599 if (retryParameters != null) {
600 builder.append(" retry_parameters:\n");
601 if (retryParameters.getRetryLimit() != null) {
602 builder.append(" task_retry_limit: " + retryParameters.getRetryLimit() + "\n");
604 if (retryParameters.getAgeLimitSec() != null) {
605 builder.append(" task_age_limit: " + retryParameters.getAgeLimitSec() + "s\n");
607 if (retryParameters.getMinBackoffSec() != null) {
608 builder.append(" min_backoff_seconds: " + retryParameters.getMinBackoffSec() + "\n");
610 if (retryParameters.getMaxBackoffSec() != null) {
611 builder.append(" max_backoff_seconds: " + retryParameters.getMaxBackoffSec() + "\n");
613 if (retryParameters.getMaxDoublings() != null) {
614 builder.append(" max_doublings: " + retryParameters.getMaxDoublings() + "\n");
617 String target = ent.getTarget();
618 if (target != null) {
619 builder.append(" target: " + target + "\n");
621 String mode = ent.getMode();
622 if (mode != null) {
623 builder.append(" mode: " + mode + "\n");
625 List<AclEntry> acl = ent.getAcl();
626 if (acl != null) {
627 builder.append(" acl:\n");
628 for (AclEntry aclEntry : acl) {
629 if (aclEntry.getUserEmail() != null) {
630 builder.append(" - user_email: " + aclEntry.getUserEmail() + "\n");
631 } else if (aclEntry.getWriterEmail() != null) {
632 builder.append(" - writer_email: " + aclEntry.getWriterEmail() + "\n");
637 return builder.toString();