Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / taskqueue / TaskOptions.java
blob788b639b5f185d41070222cc74417de375113191
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;
19 import java.util.Map;
21 /**
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):
33 * <pre>
34 * import static com.google.appengine.api.taskqueue.TaskOptions.Builder.*;
36 * ...
37 * {@link QueueFactory#getDefaultQueue()}.add(withUrl(url).etaMillis(eta));
38 * </pre>
41 public final class TaskOptions implements Serializable {
42 private static final long serialVersionUID = -2702019046191004750L;
43 /**
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.
49 public enum Method {
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),
55 PULL(null, 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() {
66 return pbMethod;
69 boolean supportsBody() {
70 return isBodyMethod;
74 /**
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");
85 this.name = name;
88 @Override
89 public int hashCode() {
90 return name.hashCode();
93 protected static String encodeURLAsUtf8(String url) throws UnsupportedEncodingException {
94 return URLEncoder.encode(url, "UTF-8");
97 @Override
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) {
113 super(name);
115 if (value == null) {
116 throw new IllegalArgumentException("value must not be null");
118 this.value = value;
121 @Override
122 public String toString() {
123 return "StringParam(" + name + "=" + value + ")";
126 @Override
127 public boolean equals(Object o) {
128 if (this == o) {
129 return true;
132 if (!(o instanceof StringValueParam)) {
133 return false;
136 StringValueParam that = (StringValueParam) o;
138 return value.equals(that.value) && name.equals(that.name);
141 @Override
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) {
153 super(name);
155 if (value == null) {
156 throw new IllegalArgumentException("value must not be null");
158 this.value = value;
161 @Override
162 public String toString() {
163 return "ByteArrayParam(" + name + ": " + value.length + " bytes)";
166 @Override
167 public boolean equals(Object o) {
168 if (this == o) {
169 return true;
172 if (!(o instanceof ByteArrayValueParam)) {
173 return false;
176 ByteArrayValueParam that = (ByteArrayValueParam) o;
178 return Arrays.equals(value, that.value) && name.equals(that.name);
182 * {@inheritDoc}
183 * @throws UnsupportedEncodingException
185 @Override
186 public String getURLEncodedValue() throws UnsupportedEncodingException {
187 StringBuilder result = new StringBuilder();
188 for (int i = 0; i < value.length; i++) {
189 result.append("%");
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;
204 private String url;
205 private Long countdownMillis;
206 private Long etaMillis;
207 private RetryOptions retryOptions;
208 private byte[] tag;
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;
222 url = options.url;
223 countdownMillis = options.countdownMillis;
224 etaMillis = options.etaMillis;
225 tag = options.tag;
226 if (options.retryOptions != null) {
227 retryOptions = new RetryOptions(options.retryOptions);
228 } else {
229 retryOptions = null;
231 if (options.getPayload() != null) {
232 payload(options.getPayload());
233 } else {
234 payload = null;
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);
252 Method getMethod() {
253 return method;
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;
267 return this;
270 String getTaskName() {
271 return taskName;
275 * Returns the specified payload for a task. May be {@code null}.
277 public byte[] getPayload() {
278 return payload;
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();
290 return this;
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;
317 try {
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);
326 method(Method.POST);
327 if (getUrl() == null) {
328 url(DeferredTaskContext.DEFAULT_DEFERRED_URL);
331 return this;
335 * Sets the payload from a {@link String} given a specific character set.
336 * @throws UnsupportedTranslationException
338 public TaskOptions payload(String payload, String charset) {
339 try {
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() {
373 return headers;
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);
389 return this;
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.");
401 if (value == null) {
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);
409 return this;
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);
420 return this;
424 * Set the method used for this task. Defaults to {@link Method#POST}.
426 public TaskOptions method(Method method) {
427 this.method = method;
428 return this;
431 List<Param> getParams() {
432 return params;
435 TaskOptions param(Param param) {
436 params.add(param);
437 return this;
441 * Clears the parameters.
443 public TaskOptions clearParams() {
444 params.clear();
445 return this;
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)) {
484 paramsIter.remove();
488 return this;
491 public String getUrl() {
492 return url;
496 * Set the URL.
497 * <p>Default value is {@code null}.
498 * @param url String containing URL.
500 public TaskOptions url(String url) {
501 if (url == null) {
502 throw new IllegalArgumentException("null url is not allowed.");
504 this.url = url;
505 return this;
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;
520 return this;
524 * Returns the specified ETA for a task. May be {@code null} if not specified.
526 public Long getEtaMillis() {
527 return etaMillis;
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;
536 return this;
540 * Returns the retry options for a task. May be {@code null} if not specified.
542 RetryOptions getRetryOptions() {
543 return retryOptions;
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;
552 return this;
556 * Returns the tag for a task. May be {@code null} if tag is not specified.
558 public byte[] getTagAsBytes() {
559 return tag;
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 {
567 if (tag != null) {
568 return new String(tag, "UTF-8");
570 return null;
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();
579 } else {
580 this.tag = null;
582 return this;
586 * Sets the tag for a task. Ignores null or empty tags.
588 public TaskOptions tag(String tag) {
589 if (tag != null) {
590 return tag(tag.getBytes());
592 return this;
595 @Override
596 public int hashCode() {
597 final int prime = 31;
598 int result = 1;
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);
609 return result;
612 @Override
613 public boolean equals(Object obj) {
614 if (this == obj) {
615 return true;
617 if (obj == null) {
618 return false;
620 if (getClass() != obj.getClass()) {
621 return false;
623 TaskOptions other = (TaskOptions) obj;
624 if (countdownMillis == null) {
625 if (other.countdownMillis != null) {
626 return false;
628 } else if (!countdownMillis.equals(other.countdownMillis)) {
629 return false;
631 if (etaMillis == null) {
632 if (other.etaMillis != null) {
633 return false;
635 } else if (!etaMillis.equals(other.etaMillis)) {
636 return false;
638 if (headers == null) {
639 if (other.headers != null) {
640 return false;
642 } else if (!headers.equals(other.headers)) {
643 return false;
645 if (method == null) {
646 if (other.method != null) {
647 return false;
649 } else if (!method.equals(other.method)) {
650 return false;
652 if (params == null) {
653 if (other.params != null) {
654 return false;
656 } else if (!params.equals(other.params)) {
657 return false;
659 if (!Arrays.equals(payload, other.payload)) {
660 return false;
662 if (taskName == null) {
663 if (other.taskName != null) {
664 return false;
666 } else if (!taskName.equals(other.taskName)) {
667 return false;
669 if (url == null) {
670 if (other.url != null) {
671 return false;
673 } else if (!url.equals(other.url)) {
674 return false;
676 if (retryOptions == null) {
677 if (other.retryOptions != null) {
678 return false;
680 } else if (!retryOptions.equals(other.retryOptions)) {
681 return false;
683 if (!Arrays.equals(tag, other.tag)) {
684 return false;
686 return true;
689 @Override
690 public String toString() {
691 String tagString = null;
692 try {
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();
834 private Builder() {