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
;
8 import java
.util
.regex
.Matcher
;
9 import java
.util
.regex
.Pattern
;
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
);
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";
48 * Enumerates the allowed units for Queue rate.
50 public enum RateUnit
{
52 MINUTE('m', SECOND
.getSeconds() * 60),
53 HOUR('h', MINUTE
.getSeconds() * 60),
54 DAY('d', HOUR
.getSeconds() * 24);
59 RateUnit(char ident
, int seconds
) {
61 this.seconds
= seconds
;
64 static RateUnit
valueOf(char 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() {
78 public int getSeconds() {
84 * Access control list for a queue.
86 public static class AclEntry
{
87 private String userEmail
;
88 private String writerEmail
;
95 public void setUserEmail(String userEmail
) {
96 this.userEmail
= userEmail
;
99 public String
getUserEmail() {
103 public void setWriterEmail(String writerEmail
) {
104 this.writerEmail
= writerEmail
;
107 public String
getWriterEmail() {
112 public int hashCode() {
113 final int prime
= 31;
115 result
= prime
* result
+ ((userEmail
== null) ?
0 : userEmail
.hashCode());
116 result
= prime
* result
+ ((writerEmail
== null) ?
0 : writerEmail
.hashCode());
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;
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() {
149 minBackoffSec
= null;
150 maxBackoffSec
= null;
154 public Integer
getRetryLimit() {
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() {
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() {
208 public void setMaxDoublings(int maxDoublings
) {
209 this.maxDoublings
= maxDoublings
;
212 public void setMaxDoublings(String maxDoublings
) {
213 this.maxDoublings
= Integer
.valueOf(maxDoublings
);
217 public int hashCode() {
218 final int prime
= 31;
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());
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;
254 * Describes a single queue entry.
256 public static class Entry
{
259 private RateUnit rateUnit
;
260 private Integer bucketSize
;
261 private Integer maxConcurrentRequests
;
262 private RetryParameters retryParameters
;
263 private String target
;
265 private List
<AclEntry
> acl
;
267 /** Create an empty queue entry. */
271 rateUnit
= RateUnit
.SECOND
;
273 maxConcurrentRequests
= null;
274 retryParameters
= null;
280 public Entry(String name
, double rate
, RateUnit rateUnit
, int bucketSize
,
281 Integer maxConcurrentRequests
, String target
) {
284 this.rateUnit
= rateUnit
;
285 this.bucketSize
= bucketSize
;
286 this.maxConcurrentRequests
= maxConcurrentRequests
;
287 this.target
= target
;
290 public String
getName() {
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'");
313 public String
getMode() {
317 public List
<AclEntry
> getAcl() {
321 public void setAcl(List
<AclEntry
> acl
) {
325 public void addAcl(AclEntry aclEntry
) {
326 this.acl
.add(aclEntry
);
329 public Double
getRate() {
333 public void setRate(double 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")) {
344 rateUnit
= RateUnit
.SECOND
;
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() {
360 public void setRateUnit(RateUnit rateUnit
) {
361 this.rateUnit
= rateUnit
;
364 public Integer
getBucketSize() {
368 public void setBucketSize(int bucketSize
) {
369 this.bucketSize
= bucketSize
;
372 public void setBucketSize(String bucketSize
) {
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
) {
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() {
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: '" +
415 this.target
= target
;
419 public int hashCode() {
420 final int prime
= 31;
422 result
= prime
* result
+ ((acl
== null) ?
0 : acl
.hashCode());
423 result
= prime
* result
+ ((bucketSize
== null) ?
0 : bucketSize
.hashCode());
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());
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
;
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;
451 if (other
.mode
!= null) return false;
452 } else if (!mode
.equals(other
.mode
)) return false;
454 if (other
.name
!= null) return false;
455 } else if (!name
.equals(other
.name
)) return false;
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;
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
489 * @return the new entry
491 public Entry
addNewEntry() {
493 lastEntry
= new Entry();
497 public void addEntry(Entry entry
) {
506 public Collection
<Entry
> getEntries() {
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) {
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.");
556 if (lastEntry
.getRate() == null) {
557 throw new AppEngineConfigException("A queue rate is required for push queue.");
560 entries
.put(lastEntry
.getName(), lastEntry
);
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();
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();
623 builder
.append(" mode: " + mode
+ "\n");
625 List
<AclEntry
> acl
= ent
.getAcl();
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();