1 // Copyright 2010 Google Inc. All rights reserved.
2 package com
.google
.appengine
.api
.taskqueue
;
4 import com
.google
.appengine
.api
.taskqueue
.TaskQueuePb
.TaskQueueAddRequest
.RequestMethod
;
6 import java
.io
.ByteArrayOutputStream
;
7 import java
.io
.IOException
;
8 import java
.io
.ObjectOutputStream
;
9 import java
.io
.Serializable
;
10 import java
.io
.UnsupportedEncodingException
;
11 import java
.net
.URLEncoder
;
12 import java
.util
.ArrayList
;
13 import java
.util
.Arrays
;
14 import java
.util
.HashMap
;
15 import java
.util
.Iterator
;
16 import java
.util
.LinkedHashMap
;
17 import java
.util
.LinkedList
;
18 import java
.util
.List
;
22 * Contains various options for a task following the builder pattern.
23 * Calls to {@link TaskOptions} methods may be chained to specify
24 * multiple options in the one {@link TaskOptions} object.
25 * <p>taskOptions can have either {@link TaskOptions#Method} PULL or a
26 * PUSH-related method, e.g. POST, GET, ... Tasks with PULL method can
27 * only be added into a PULL queue and PUSH tasks can only be added
28 * into a PUSH queue. <p>Notes on usage:<br> The recommended way to
29 * instantiate a {@link TaskOptions} object is to statically import
30 * {@link Builder}.* and invoke a static creation method followed by
31 * an instance mutator (if needed):
34 * import static com.google.appengine.api.taskqueue.TaskOptions.Builder.*;
37 * {@link QueueFactory#getDefaultQueue()}.add(withUrl(url).etaMillis(eta));
41 public final class TaskOptions
implements Serializable
{
42 private static final long serialVersionUID
= -2702019046191004750L;
44 * Methods supported by {@link Queue}.
45 * Tasks added to pull queues must have the {@link #Method} PULL.
46 * All other methods are appropriate for push queues.
47 * See {@link Queue} for more detail on pull and push mode queues.
50 DELETE(RequestMethod
.DELETE
, false),
51 GET(RequestMethod
.GET
, false),
52 HEAD(RequestMethod
.HEAD
, false),
53 POST(RequestMethod
.POST
, true),
54 PUT(RequestMethod
.PUT
, true),
57 private RequestMethod pbMethod
;
58 private boolean isBodyMethod
;
60 Method(RequestMethod pbMethod
, boolean isBodyMethod
) {
61 this.pbMethod
= pbMethod
;
62 this.isBodyMethod
= isBodyMethod
;
65 RequestMethod
getPbMethod() {
69 boolean supportsBody() {
75 * Params are currently immutable and need to remain that way to avoid the need to clone them
76 * in the TaskOptions copy constructor.
78 abstract static class Param
implements Serializable
{
79 protected final String name
;
81 public Param(String name
) {
82 if (name
== null || name
.length() == 0) {
83 throw new IllegalArgumentException("name must not be null or empty");
89 public int hashCode() {
90 return name
.hashCode();
93 protected static String
encodeURLAsUtf8(String url
) throws UnsupportedEncodingException
{
94 return URLEncoder
.encode(url
, "UTF-8");
98 public abstract boolean equals(Object o
);
100 public String
getURLEncodedName() throws UnsupportedEncodingException
{
101 return encodeURLAsUtf8(name
);
104 abstract String
getURLEncodedValue() throws UnsupportedEncodingException
;
107 static class StringValueParam
extends Param
{
108 private static final long serialVersionUID
= -2306561754387422446L;
110 protected final String value
;
112 StringValueParam(String name
, String value
) {
116 throw new IllegalArgumentException("value must not be null");
122 public String
toString() {
123 return "StringParam(" + name
+ "=" + value
+ ")";
127 public boolean equals(Object o
) {
132 if (!(o
instanceof StringValueParam
)) {
136 StringValueParam that
= (StringValueParam
) o
;
138 return value
.equals(that
.value
) && name
.equals(that
.name
);
142 public String
getURLEncodedValue() throws UnsupportedEncodingException
{
143 return encodeURLAsUtf8(value
);
147 static class ByteArrayValueParam
extends Param
{
148 private static final long serialVersionUID
= 1420600427192025644L;
150 protected final byte[] value
;
152 ByteArrayValueParam(String name
, byte[] value
) {
156 throw new IllegalArgumentException("value must not be null");
162 public String
toString() {
163 return "ByteArrayParam(" + name
+ ": " + value
.length
+ " bytes)";
167 public boolean equals(Object o
) {
172 if (!(o
instanceof ByteArrayValueParam
)) {
176 ByteArrayValueParam that
= (ByteArrayValueParam
) o
;
178 return Arrays
.equals(value
, that
.value
) && name
.equals(that
.name
);
183 * @throws UnsupportedEncodingException
186 public String
getURLEncodedValue() throws UnsupportedEncodingException
{
187 StringBuilder result
= new StringBuilder();
188 for (int i
= 0; i
< value
.length
; i
++) {
190 char character
= Character
.toUpperCase(Character
.forDigit((value
[i
] >> 4) & 0xF, 16));
191 result
.append(character
);
192 character
= Character
.toUpperCase(Character
.forDigit(value
[i
] & 0xF, 16));
193 result
.append(character
);
195 return result
.toString();
199 private String taskName
;
200 private byte[] payload
;
201 private HashMap
<String
, List
<String
>> headers
;
202 private Method method
;
203 private List
<Param
> params
;
205 private Long countdownMillis
;
206 private Long etaMillis
;
207 private RetryOptions retryOptions
;
210 private TaskOptions() {
211 this.method
= Method
.POST
;
212 this.headers
= new LinkedHashMap
<String
, List
<String
>>();
213 this.params
= new LinkedList
<Param
>();
217 * A copy constructor for {@link TaskOptions}.
219 public TaskOptions(TaskOptions options
) {
220 taskName
= options
.taskName
;
221 method
= options
.method
;
223 countdownMillis
= options
.countdownMillis
;
224 etaMillis
= options
.etaMillis
;
226 if (options
.retryOptions
!= null) {
227 retryOptions
= new RetryOptions(options
.retryOptions
);
231 if (options
.getPayload() != null) {
232 payload(options
.getPayload());
236 initializeHeaders(options
.getHeaders());
237 initializeParams(options
.getParams());
240 private void initializeHeaders(Map
<String
, List
<String
>> headers
) {
241 this.headers
= new LinkedHashMap
<String
, List
<String
>>();
243 for (Map
.Entry
<String
, List
<String
>> entry
: headers
.entrySet()) {
244 this.headers
.put(entry
.getKey(), new ArrayList
<String
>(entry
.getValue()));
248 private void initializeParams(List
<Param
> params
) {
249 this.params
= new LinkedList
<Param
>(params
);
257 * Sets the task name.
259 * @throws IllegalArgumentException The provided name is null, empty or doesn't match the regular
260 * expression {@link QueueConstants#TASK_NAME_REGEX}
262 public TaskOptions
taskName(String taskName
) {
263 if (taskName
!= null && taskName
.length() != 0) {
264 TaskHandle
.validateTaskName(taskName
);
266 this.taskName
= taskName
;
270 String
getTaskName() {
275 * Returns the specified payload for a task. May be {@code null}.
277 public byte[] getPayload() {
282 * Sets the payload directly without specifying the content-type. If this
283 * task is added to a push queue, the content-type will be set to
284 * 'application/octet-stream' by default.
285 * @param payload The bytes representing the paylaod.
286 * @return TaskOptions object for chaining.
288 public TaskOptions
payload(byte[] payload
) {
289 this.payload
= payload
.clone();
294 * Sets the payload to the serialized form of the deferredTask object.
295 * The payload will be generated by serializing the deferredTask object using
296 * {@link ObjectOutputStream#writeObject(Object)}. If the deferredTask's
297 * {@link Method} is not PULL, the content type will be set to
298 * {@link DeferredTaskContext#RUNNABLE_TASK_CONTENT_TYPE}, the method
299 * will be forced to {@link Method#POST} and if otherwise not specified, the
300 * url will be set to {@link DeferredTaskContext#DEFAULT_DEFERRED_URL}; the
301 * {@link DeferredTask} servlet is, by default, mapped to this url.
303 * <p>Note: While this may be a convenient API, it requires careful control of the
304 * serialization compatibility of objects passed into {@link #payload(DeferredTask)}
305 * method as objects placed in the task queue will survive revision updates
306 * of the application and hence may fail deserialization when the task is decoded
307 * with new revisions of the application. In particular, Java anonymous classes
308 * are convenient but may be particularly difficult to control or test for
309 * serialization compatibility.
311 * @param deferredTask The object to serialize into the payload.
312 * @throws DeferredTaskCreationException if there was an IOException serializing object.
314 public TaskOptions
payload(DeferredTask deferredTask
) {
315 ByteArrayOutputStream stream
= new ByteArrayOutputStream(1024);
316 ObjectOutputStream objectStream
= null;
318 objectStream
= new ObjectOutputStream(stream
);
319 objectStream
.writeObject(deferredTask
);
320 } catch (IOException e
) {
321 throw new DeferredTaskCreationException(e
);
323 payload
= stream
.toByteArray();
324 if (getMethod() != Method
.PULL
) {
325 header("content-type", DeferredTaskContext
.RUNNABLE_TASK_CONTENT_TYPE
);
327 if (getUrl() == null) {
328 url(DeferredTaskContext
.DEFAULT_DEFERRED_URL
);
335 * Sets the payload from a {@link String} given a specific character set.
336 * @throws UnsupportedTranslationException
338 public TaskOptions
payload(String payload
, String charset
) {
340 return payload(payload
.getBytes(charset
), "text/plain; charset=" + charset
);
341 } catch (UnsupportedEncodingException exception
) {
342 throw new UnsupportedTranslationException(
343 "Unsupported charset '" + charset
+ "' requested.", exception
);
348 * Set the payload with the given content type.
349 * @param payload The bytes representing the paylaod.
350 * @param contentType The content-type of the bytes.
351 * @return TaskOptions object for chaining.
353 public TaskOptions
payload(byte[] payload
, String contentType
) {
354 return payload(payload
).
355 header("content-type", contentType
);
359 * Set the payload by {@link String}. The charset to convert the
360 * String to will be UTF-8 unless the method is PULL, in which case the
361 * String's bytes will be used directly.
362 * @param payload The String to be used.
363 * @return TaskOptions object for chaining.
365 public TaskOptions
payload(String payload
) {
366 if (getMethod() == Method
.PULL
) {
367 return payload(payload
.getBytes());
369 return payload(payload
, "UTF-8");
372 HashMap
<String
, List
<String
>> getHeaders() {
377 * Replaces the existing headers with the provided header {@code name/value} mapping.
378 * @param headers The headers to copy.
379 * @return TaskOptions object for chaining.
381 public TaskOptions
headers(Map
<String
, String
> headers
) {
382 this.headers
= new LinkedHashMap
<String
, List
<String
>>();
384 for (Map
.Entry
<String
, String
> entry
: headers
.entrySet()) {
385 List
<String
> values
= new ArrayList
<String
>(1);
386 values
.add(entry
.getValue());
387 this.headers
.put(entry
.getKey(), values
);
393 * Adds a header {@code name/value} pair.
395 * @throws IllegalArgumentException
397 public TaskOptions
header(String headerName
, String value
) {
398 if (headerName
== null || headerName
.length() == 0) {
399 throw new IllegalArgumentException("headerName must not be null or empty.");
402 throw new IllegalArgumentException("header(name, <null>) is not allowed.");
405 if (!headers
.containsKey(headerName
)) {
406 headers
.put(headerName
, new ArrayList
<String
>());
408 headers
.get(headerName
).add(value
);
413 * Remove all headers with the given name.
415 public TaskOptions
removeHeader(String headerName
) {
416 if (headerName
== null || headerName
.length() == 0) {
417 throw new IllegalArgumentException("headerName must not be null or empty.");
419 headers
.remove(headerName
);
424 * Set the method used for this task. Defaults to {@link Method#POST}.
426 public TaskOptions
method(Method method
) {
427 this.method
= method
;
431 List
<Param
> getParams() {
435 TaskOptions
param(Param param
) {
441 * Clears the parameters.
443 public TaskOptions
clearParams() {
449 * Add a named {@link String} parameter.
450 * @param name Name of the parameter. Must not be null or empty.
451 * @param value The value of the parameter will undergo a "UTF-8"
452 * character encoding transformation upon being added to the queue.
453 * {@code value} must not be {@code null}.
454 * @throws IllegalArgumentException
456 public TaskOptions
param(String name
, String value
) {
457 return param(new StringValueParam(name
, value
));
461 * Add a named {@code byte} array parameter.
462 * @param name Name of the parameter. Must not be null or empty.
463 * @param value A byte array and encoded as-is
464 * (i.e. without character encoding transformations).
465 * {@code value} must not be {@code null}.
466 * @throws IllegalArgumentException
468 public TaskOptions
param(String name
, byte[] value
) {
469 return param(new ByteArrayValueParam(name
, value
));
473 * Remove all parameters with the given name.
474 * @param paramName Name of the parameter. Must not be null or empty.
476 public TaskOptions
removeParam(String paramName
) {
477 if (paramName
== null || paramName
.length() == 0) {
478 throw new IllegalArgumentException("paramName must not be null or empty.");
481 Iterator
<Param
> paramsIter
= params
.iterator();
482 while (paramsIter
.hasNext()) {
483 if (paramName
.equals(paramsIter
.next().name
)) {
491 public String
getUrl() {
497 * <p>Default value is {@code null}.
498 * @param url String containing URL.
500 public TaskOptions
url(String url
) {
502 throw new IllegalArgumentException("null url is not allowed.");
509 * Delay to apply to the submitted time. May be {@code null}.
511 Long
getCountdownMillis() {
512 return countdownMillis
;
516 * Set the number of milliseconds delay before execution of the task.
518 public TaskOptions
countdownMillis(long countdownMillis
) {
519 this.countdownMillis
= countdownMillis
;
524 * Returns the specified ETA for a task. May be {@code null} if not specified.
526 public Long
getEtaMillis() {
531 * Sets the approximate absolute time to execute. (i.e. etaMillis is comparable with
532 * {@link System#currentTimeMillis()}).
534 public TaskOptions
etaMillis(long etaMillis
) {
535 this.etaMillis
= etaMillis
;
540 * Returns the retry options for a task. May be {@code null} if not specified.
542 RetryOptions
getRetryOptions() {
547 * Sets retry options for this task. Retry Options must be built with
548 * {@code RetryOptions.Builder}.
550 public TaskOptions
retryOptions(RetryOptions retryOptions
) {
551 this.retryOptions
= retryOptions
;
556 * Returns the tag for a task. May be {@code null} if tag is not specified.
558 public byte[] getTagAsBytes() {
563 * Returns the tag for a task. May be {@code null} if tag is not specified.
564 * @throws UnsupportedEncodingException
566 public String
getTag() throws UnsupportedEncodingException
{
568 return new String(tag
, "UTF-8");
574 * Sets the tag for a task. Ignores null or zero-length tags.
576 public TaskOptions
tag(byte[] tag
) {
577 if (tag
!= null && tag
.length
> 0) {
578 this.tag
= tag
.clone();
586 * Sets the tag for a task. Ignores null or empty tags.
588 public TaskOptions
tag(String tag
) {
590 return tag(tag
.getBytes());
596 public int hashCode() {
597 final int prime
= 31;
599 result
= prime
* result
+ ((countdownMillis
== null) ?
0 : countdownMillis
.hashCode());
600 result
= prime
* result
+ ((etaMillis
== null) ?
0 : etaMillis
.hashCode());
601 result
= prime
* result
+ ((headers
== null) ?
0 : headers
.hashCode());
602 result
= prime
* result
+ ((method
== null) ?
0 : method
.hashCode());
603 result
= prime
* result
+ ((params
== null) ?
0 : params
.hashCode());
604 result
= prime
* result
+ Arrays
.hashCode(payload
);
605 result
= prime
* result
+ ((taskName
== null) ?
0 : taskName
.hashCode());
606 result
= prime
* result
+ ((url
== null) ?
0 : url
.hashCode());
607 result
= prime
* result
+ ((retryOptions
== null) ?
0 : retryOptions
.hashCode());
608 result
= prime
* result
+ Arrays
.hashCode(tag
);
613 public boolean equals(Object obj
) {
620 if (getClass() != obj
.getClass()) {
623 TaskOptions other
= (TaskOptions
) obj
;
624 if (countdownMillis
== null) {
625 if (other
.countdownMillis
!= null) {
628 } else if (!countdownMillis
.equals(other
.countdownMillis
)) {
631 if (etaMillis
== null) {
632 if (other
.etaMillis
!= null) {
635 } else if (!etaMillis
.equals(other
.etaMillis
)) {
638 if (headers
== null) {
639 if (other
.headers
!= null) {
642 } else if (!headers
.equals(other
.headers
)) {
645 if (method
== null) {
646 if (other
.method
!= null) {
649 } else if (!method
.equals(other
.method
)) {
652 if (params
== null) {
653 if (other
.params
!= null) {
656 } else if (!params
.equals(other
.params
)) {
659 if (!Arrays
.equals(payload
, other
.payload
)) {
662 if (taskName
== null) {
663 if (other
.taskName
!= null) {
666 } else if (!taskName
.equals(other
.taskName
)) {
670 if (other
.url
!= null) {
673 } else if (!url
.equals(other
.url
)) {
676 if (retryOptions
== null) {
677 if (other
.retryOptions
!= null) {
680 } else if (!retryOptions
.equals(other
.retryOptions
)) {
683 if (!Arrays
.equals(tag
, other
.tag
)) {
690 public String
toString() {
691 String tagString
= null;
693 tagString
= getTag();
694 } catch (UnsupportedEncodingException e
) {
695 tagString
= "not a utf-8 String";
697 return "TaskOptions[taskName=" + taskName
+ ", headers=" + headers
698 + ", method=" + method
+ ", params=" + params
+ ", url=" + url
699 + ", countdownMillis=" + countdownMillis
+ ", etaMillis=" + etaMillis
700 + ", retryOptions=" + retryOptions
+ ", tag=" + tagString
+ "]";
704 * Provides static creation methods for {@link TaskOptions}.
706 public static final class Builder
{
708 * Returns default {@link TaskOptions} and calls {@link TaskOptions#taskName(String)}.
710 public static TaskOptions
withTaskName(String taskName
) {
711 return withDefaults().taskName(taskName
);
715 * Returns default {@link TaskOptions} and calls {@link TaskOptions#payload(byte[])}.
717 static TaskOptions
withPayload(byte[] payload
) {
718 return withDefaults().payload(payload
);
722 * Returns default {@link TaskOptions} and calls {@link TaskOptions#payload(String, String)}.
724 public static TaskOptions
withPayload(String payload
, String charset
) {
725 return withDefaults().payload(payload
, charset
);
729 * Returns default {@link TaskOptions} and calls {@link TaskOptions#payload(DeferredTask)}.
731 public static TaskOptions
withPayload(DeferredTask deferredTask
) {
732 return withDefaults().payload(deferredTask
);
736 * Returns default {@link TaskOptions} and calls {@link TaskOptions#payload(byte[], String)}.
738 public static TaskOptions
withPayload(byte[] payload
, String contentType
) {
739 return withDefaults().payload(payload
, contentType
);
743 * Returns default {@link TaskOptions} and calls {@link TaskOptions#payload(String)}.
745 public static TaskOptions
withPayload(String payload
) {
746 return withDefaults().payload(payload
);
750 * Returns default {@link TaskOptions} and calls {@link TaskOptions#headers(Map)}.
752 public static TaskOptions
withHeaders(Map
<String
, String
> headers
) {
753 return withDefaults().headers(headers
);
757 * Returns default {@link TaskOptions} and calls {@link TaskOptions#header(String, String)}.
759 public static TaskOptions
withHeader(String headerName
, String value
) {
760 return withDefaults().header(headerName
, value
);
764 * Returns default {@link TaskOptions} and calls {@link TaskOptions#method(Method)}.
766 public static TaskOptions
withMethod(Method method
) {
767 return withDefaults().method(method
);
771 * Returns default {@link TaskOptions} and calls {@link TaskOptions#param(String, String)}.
773 public static TaskOptions
withParam(String paramName
, String value
) {
774 return withDefaults().param(paramName
, value
);
778 * Returns default {@link TaskOptions} and calls {@link TaskOptions#param(String, byte[])}.
780 public static TaskOptions
withParam(String paramName
, byte[] value
) {
781 return withDefaults().param(paramName
, value
);
785 * Returns default {@link TaskOptions} and calls {@link TaskOptions#url(String)}.
787 public static TaskOptions
withUrl(String url
) {
788 return withDefaults().url(url
);
792 * Returns default {@link TaskOptions} and calls {@link TaskOptions#countdownMillis(long)}.
794 public static TaskOptions
withCountdownMillis(long countdownMillis
) {
795 return withDefaults().countdownMillis(countdownMillis
);
799 * Returns default {@link TaskOptions} and calls {@link TaskOptions#etaMillis(long)}.
801 public static TaskOptions
withEtaMillis(long etaMillis
) {
802 return withDefaults().etaMillis(etaMillis
);
806 * Returns default {@link TaskOptions} and calls
807 * {@link TaskOptions#retryOptions(RetryOptions)}.
809 public static TaskOptions
withRetryOptions(RetryOptions retryOptions
) {
810 return withDefaults().retryOptions(retryOptions
);
814 * Returns default {@link TaskOptions} and calls {@link TaskOptions#tag(byte[])}.
816 public static TaskOptions
withTag(byte[] tag
) {
817 return withDefaults().tag(tag
);
821 * Returns default {@link TaskOptions} and calls {@link TaskOptions#tag(String)}.
823 public static TaskOptions
withTag(String tag
) {
824 return withDefaults().tag(tag
);
828 * Returns default {@link TaskOptions} with default values.
830 public static TaskOptions
withDefaults() {
831 return new TaskOptions();