App Engine Python SDK version 1.7.4 (2)
[gae.git] / java / src / main / com / google / appengine / api / taskqueue / TaskOptions.java
blob2717157ee8ef756eacda951add5557880081b743
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(header("X-HEADER", "value"));
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;
274 byte[] getPayload() {
275 return payload;
279 * Sets the payload directly without specifying the content-type. If this
280 * task is added to a push queue, the content-type will be set to
281 * 'application/octet-stream' by default.
282 * @param payload The bytes representing the paylaod.
283 * @return TaskOptions object for chaining.
285 public TaskOptions payload(byte[] payload) {
286 this.payload = payload.clone();
287 return this;
291 * Sets the payload to the serialized form of the deferredTask object.
292 * The payload will be generated by serializing the deferredTask object using
293 * {@link ObjectOutputStream#writeObject(Object)}. If the deferredTask's
294 * {@link Method} is not PULL, the content type will be set to
295 * {@link DeferredTaskContext#RUNNABLE_TASK_CONTENT_TYPE}, the method
296 * will be forced to {@link Method#POST} and if otherwise not specified, the
297 * url will be set to {@link DeferredTaskContext#DEFAULT_DEFERRED_URL}; the
298 * {@link DeferredTask} servlet is, by default, mapped to this url.
300 * <p>Note: While this may be a convenient API, it requires careful control of the
301 * serialization compatibility of objects passed into {@link #payload(DeferredTask)}
302 * method as objects placed in the task queue will survive revision updates
303 * of the application and hence may fail deserialization when the task is decoded
304 * with new revisions of the application. In particular, Java anonymous classes
305 * are convenient but may be particularly difficult to control or test for
306 * serialization compatibility.
308 * @param deferredTask The object to serialize into the payload.
309 * @throws DeferredTaskCreationException if there was an IOException serializing object.
311 public TaskOptions payload(DeferredTask deferredTask) {
312 ByteArrayOutputStream stream = new ByteArrayOutputStream(1024);
313 ObjectOutputStream objectStream = null;
314 try {
315 objectStream = new ObjectOutputStream(stream);
316 objectStream.writeObject(deferredTask);
317 } catch (IOException e) {
318 throw new DeferredTaskCreationException(e);
320 payload = stream.toByteArray();
321 if (getMethod() != Method.PULL) {
322 header("content-type", DeferredTaskContext.RUNNABLE_TASK_CONTENT_TYPE);
323 method(Method.POST);
324 if (getUrl() == null) {
325 url(DeferredTaskContext.DEFAULT_DEFERRED_URL);
328 return this;
332 * Sets the payload from a {@link String} given a specific character set.
333 * @throws UnsupportedTranslationException
335 public TaskOptions payload(String payload, String charset) {
336 try {
337 return payload(payload.getBytes(charset), "text/plain; charset=" + charset);
338 } catch (UnsupportedEncodingException exception) {
339 throw new UnsupportedTranslationException(
340 "Unsupported charset '" + charset + "' requested.", exception);
345 * Set the payload with the given content type.
346 * @param payload The bytes representing the paylaod.
347 * @param contentType The content-type of the bytes.
348 * @return TaskOptions object for chaining.
350 public TaskOptions payload(byte[] payload, String contentType) {
351 return payload(payload).
352 header("content-type", contentType);
356 * Set the payload by {@link String}. The charset to convert the
357 * String to will be UTF-8 unless the method is PULL, in which case the
358 * String's bytes will be used directly.
359 * @param payload The String to be used.
360 * @return TaskOptions object for chaining.
362 public TaskOptions payload(String payload) {
363 if (getMethod() == Method.PULL) {
364 return payload(payload.getBytes());
366 return payload(payload, "UTF-8");
369 HashMap<String, List<String>> getHeaders() {
370 return headers;
374 * Replaces the existing headers with the provided header {@code name/value} mapping.
375 * @param headers The headers to copy.
376 * @return TaskOptions object for chaining.
378 public TaskOptions headers(Map<String, String> headers) {
379 this.headers = new LinkedHashMap<String, List<String>>();
381 for (Map.Entry<String, String> entry : headers.entrySet()) {
382 List<String> values = new ArrayList<String>(1);
383 values.add(entry.getValue());
384 this.headers.put(entry.getKey(), values);
386 return this;
390 * Adds a header {@code name/value} pair.
392 * @throws IllegalArgumentException
394 public TaskOptions header(String headerName, String value) {
395 if (headerName == null || headerName.length() == 0) {
396 throw new IllegalArgumentException("headerName must not be null or empty.");
398 if (value == null) {
399 throw new IllegalArgumentException("header(name, <null>) is not allowed.");
402 if (!headers.containsKey(headerName)) {
403 headers.put(headerName, new ArrayList<String>());
405 headers.get(headerName).add(value);
406 return this;
410 * Remove all headers with the given name.
412 public TaskOptions removeHeader(String headerName) {
413 if (headerName == null || headerName.length() == 0) {
414 throw new IllegalArgumentException("headerName must not be null or empty.");
416 headers.remove(headerName);
417 return this;
421 * Set the method used for this task. Defaults to {@link Method#POST}.
423 public TaskOptions method(Method method) {
424 this.method = method;
425 return this;
428 List<Param> getParams() {
429 return params;
432 TaskOptions param(Param param) {
433 params.add(param);
434 return this;
438 * Clears the parameters.
440 public TaskOptions clearParams() {
441 params.clear();
442 return this;
446 * Add a named {@link String} parameter.
447 * @param name Name of the parameter. Must not be null or empty.
448 * @param value The value of the parameter will undergo a "UTF-8"
449 * character encoding transformation upon being added to the queue.
450 * {@code value} must not be {@code null}.
451 * @throws IllegalArgumentException
453 public TaskOptions param(String name, String value) {
454 return param(new StringValueParam(name, value));
458 * Add a named {@code byte} array parameter.
459 * @param name Name of the parameter. Must not be null or empty.
460 * @param value A byte array and encoded as-is
461 * (i.e. without character encoding transformations).
462 * {@code value} must not be {@code null}.
463 * @throws IllegalArgumentException
465 public TaskOptions param(String name, byte[] value) {
466 return param(new ByteArrayValueParam(name, value));
470 * Remove all parameters with the given name.
471 * @param paramName Name of the parameter. Must not be null or empty.
473 public TaskOptions removeParam(String paramName) {
474 if (paramName == null || paramName.length() == 0) {
475 throw new IllegalArgumentException("paramName must not be null or empty.");
478 Iterator<Param> paramsIter = params.iterator();
479 while (paramsIter.hasNext()) {
480 if (paramsIter.next().name == paramName) {
481 paramsIter.remove();
485 return this;
488 public String getUrl() {
489 return url;
493 * Set the URL.
494 * <p>Default value is {@code null}.
495 * @param url String containing URL.
497 public TaskOptions url(String url) {
498 if (url == null) {
499 throw new IllegalArgumentException("null url is not allowed.");
501 this.url = url;
502 return this;
506 * Delay to apply to the submitted time. May be {@code null}.
508 Long getCountdownMillis() {
509 return countdownMillis;
513 * Set the number of milliseconds delay before execution of the task.
515 public TaskOptions countdownMillis(long countdownMillis) {
516 this.countdownMillis = countdownMillis;
517 return this;
521 * Returns the specified ETA for a task. May be {@code null} if not specified.
523 public Long getEtaMillis() {
524 return etaMillis;
528 * Sets the approximate absolute time to execute. (i.e. etaMillis is comparable with
529 * {@link System#currentTimeMillis()}).
531 public TaskOptions etaMillis(long etaMillis) {
532 this.etaMillis = etaMillis;
533 return this;
537 * Returns the retry options for a task. May be {@code null} if not specified.
539 RetryOptions getRetryOptions() {
540 return retryOptions;
544 * Sets retry options for this task. Retry Options must be built with
545 * {@code RetryOptions.Builder}.
547 public TaskOptions retryOptions(RetryOptions retryOptions) {
548 this.retryOptions = retryOptions;
549 return this;
553 * Returns the tag for a task. May be {@code null} if tag is not specified.
555 public byte[] getTagAsBytes() {
556 return tag;
560 * Returns the tag for a task. May be {@code null} if tag is not specified.
561 * @throws UnsupportedEncodingException
563 public String getTag() throws UnsupportedEncodingException {
564 if (tag != null) {
565 return new String(tag, "UTF-8");
567 return null;
571 * Sets the tag for a task. Ignores null or zero-length tags.
573 public TaskOptions tag(byte[] tag) {
574 if (tag != null && tag.length > 0) {
575 this.tag = tag.clone();
576 } else {
577 this.tag = null;
579 return this;
583 * Sets the tag for a task. Ignores null or empty tags.
585 public TaskOptions tag(String tag) {
586 if (tag != null) {
587 return tag(tag.getBytes());
589 return this;
592 @Override
593 public int hashCode() {
594 final int prime = 31;
595 int result = 1;
596 result = prime * result + ((countdownMillis == null) ? 0 : countdownMillis.hashCode());
597 result = prime * result + ((etaMillis == null) ? 0 : etaMillis.hashCode());
598 result = prime * result + ((headers == null) ? 0 : headers.hashCode());
599 result = prime * result + ((method == null) ? 0 : method.hashCode());
600 result = prime * result + ((params == null) ? 0 : params.hashCode());
601 result = prime * result + Arrays.hashCode(payload);
602 result = prime * result + ((taskName == null) ? 0 : taskName.hashCode());
603 result = prime * result + ((url == null) ? 0 : url.hashCode());
604 result = prime * result + ((retryOptions == null) ? 0 : retryOptions.hashCode());
605 result = prime * result + Arrays.hashCode(tag);
606 return result;
609 @Override
610 public boolean equals(Object obj) {
611 if (this == obj) {
612 return true;
614 if (obj == null) {
615 return false;
617 if (getClass() != obj.getClass()) {
618 return false;
620 TaskOptions other = (TaskOptions) obj;
621 if (countdownMillis == null) {
622 if (other.countdownMillis != null) {
623 return false;
625 } else if (!countdownMillis.equals(other.countdownMillis)) {
626 return false;
628 if (etaMillis == null) {
629 if (other.etaMillis != null) {
630 return false;
632 } else if (!etaMillis.equals(other.etaMillis)) {
633 return false;
635 if (headers == null) {
636 if (other.headers != null) {
637 return false;
639 } else if (!headers.equals(other.headers)) {
640 return false;
642 if (method == null) {
643 if (other.method != null) {
644 return false;
646 } else if (!method.equals(other.method)) {
647 return false;
649 if (params == null) {
650 if (other.params != null) {
651 return false;
653 } else if (!params.equals(other.params)) {
654 return false;
656 if (!Arrays.equals(payload, other.payload)) {
657 return false;
659 if (taskName == null) {
660 if (other.taskName != null) {
661 return false;
663 } else if (!taskName.equals(other.taskName)) {
664 return false;
666 if (url == null) {
667 if (other.url != null) {
668 return false;
670 } else if (!url.equals(other.url)) {
671 return false;
673 if (retryOptions == null) {
674 if (other.retryOptions != null) {
675 return false;
677 } else if (!retryOptions.equals(other.retryOptions)) {
678 return false;
680 if (!Arrays.equals(tag, other.tag)) {
681 return false;
683 return true;
686 @Override
687 public String toString() {
688 String tagString = null;
689 try {
690 tagString = getTag();
691 } catch (UnsupportedEncodingException e) {
692 tagString = "not a utf-8 String";
694 return "TaskOptions[taskName=" + taskName + ", headers=" + headers
695 + ", method=" + method + ", params=" + params + ", url=" + url
696 + ", countdownMillis=" + countdownMillis + ", etaMillis=" + etaMillis
697 + ", retryOptions=" + retryOptions + ", tag=" + tagString + "]";
701 * Provides static creation methods for {@link TaskOptions}.
703 public static final class Builder {
705 * Returns default {@link TaskOptions} and calls {@link TaskOptions#taskName(String)}.
707 public static TaskOptions withTaskName(String taskName) {
708 return withDefaults().taskName(taskName);
712 * Returns default {@link TaskOptions} and calls {@link TaskOptions#payload(byte[])}.
714 static TaskOptions withPayload(byte[] payload) {
715 return withDefaults().payload(payload);
719 * Returns default {@link TaskOptions} and calls {@link TaskOptions#payload(String, String)}.
721 public static TaskOptions withPayload(String payload, String charset) {
722 return withDefaults().payload(payload, charset);
726 * Returns default {@link TaskOptions} and calls {@link TaskOptions#payload(DeferredTask)}.
728 public static TaskOptions withPayload(DeferredTask deferredTask) {
729 return withDefaults().payload(deferredTask);
733 * Returns default {@link TaskOptions} and calls {@link TaskOptions#payload(byte[], String)}.
735 public static TaskOptions withPayload(byte[] payload, String contentType) {
736 return withDefaults().payload(payload, contentType);
740 * Returns default {@link TaskOptions} and calls {@link TaskOptions#payload(String)}.
742 public static TaskOptions withPayload(String payload) {
743 return withDefaults().payload(payload);
747 * Returns default {@link TaskOptions} and calls {@link TaskOptions#headers(Map)}.
749 public static TaskOptions withHeaders(Map<String, String> headers) {
750 return withDefaults().headers(headers);
754 * Returns default {@link TaskOptions} and calls {@link TaskOptions#header(String, String)}.
756 public static TaskOptions withHeader(String headerName, String value) {
757 return withDefaults().header(headerName, value);
761 * Returns default {@link TaskOptions} and calls {@link TaskOptions#method(Method)}.
763 public static TaskOptions withMethod(Method method) {
764 return withDefaults().method(method);
768 * Returns default {@link TaskOptions} and calls {@link TaskOptions#param(String, String)}.
770 public static TaskOptions withParam(String paramName, String value) {
771 return withDefaults().param(paramName, value);
775 * Returns default {@link TaskOptions} and calls {@link TaskOptions#param(String, byte[])}.
777 public static TaskOptions withParam(String paramName, byte[] value) {
778 return withDefaults().param(paramName, value);
782 * Returns default {@link TaskOptions} and calls {@link TaskOptions#url(String)}.
784 public static TaskOptions withUrl(String url) {
785 return withDefaults().url(url);
789 * Returns default {@link TaskOptions} and calls {@link TaskOptions#countdownMillis(long)}.
791 public static TaskOptions withCountdownMillis(long countdownMillis) {
792 return withDefaults().countdownMillis(countdownMillis);
796 * Returns default {@link TaskOptions} and calls {@link TaskOptions#etaMillis(long)}.
798 public static TaskOptions withEtaMillis(long etaMillis) {
799 return withDefaults().etaMillis(etaMillis);
803 * Returns default {@link TaskOptions} and calls
804 * {@link TaskOptions#retryOptions(RetryOptions)}.
806 public static TaskOptions withRetryOptions(RetryOptions retryOptions) {
807 return withDefaults().retryOptions(retryOptions);
811 * Returns default {@link TaskOptions} and calls {@link TaskOptions#tag(byte[])}.
813 public static TaskOptions withTag(byte[] tag) {
814 return withDefaults().tag(tag);
818 * Returns default {@link TaskOptions} and calls {@link TaskOptions#tag(String)}.
820 public static TaskOptions withTag(String tag) {
821 return withDefaults().tag(tag);
825 * Returns default {@link TaskOptions} with default values.
827 public static TaskOptions withDefaults() {
828 return new TaskOptions();
831 private Builder() {