/**
* Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.apphosting.tests.usercode.testservlets;
import com.google.appengine.api.NamespaceManager;
import com.google.appengine.api.taskqueue.DeferredTask;
import com.google.appengine.api.taskqueue.DeferredTaskContext;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import com.google.appengine.api.taskqueue.TaskHandle;
import com.google.appengine.api.taskqueue.TaskOptions;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.Environment;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.Double;
import java.lang.String;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* See {@link com.google.appengine.tools.development.DevAppServerTaskQueueIntegrationTest}
* for information on how we use this servlet in our tests.
*
* All doXXX methods are synchronized to prevent lastRequestData from
* getting reset while we're processing.
*
*/
public class TaskQueueServlet extends HttpServlet {
// Keep this in sync with X_APPENGINE_DEFAULT_NAMESPACE in
// http_proto.cc and
// com.google.appengine.tools.development.LocalHttpRequestEnvironment.DEFAULT_NAMESPACE_HEADER
// com.google.appengine.api.NamespaceManager.DEFAULT_API_NAMESPACE_KEY
/**
* The name of the HTTP header specifying the default namespace
* for API calls.
*/
// (Not private so that tests can use it.)
static final String DEFAULT_NAMESPACE_HEADER = "X-AppEngine-Default-Namespace";
static final String CURRENT_NAMESPACE_HEADER = "X-AppEngine-Current-Namespace";
static final String CURRENT_NAMESPACE_KEY =
"com.google.appengine.api.NamespaceManager.currentNamespace";
private static volatile LastRequestData lastRequestData;
private static CountDownLatch latch;
@Override
protected synchronized void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
if (req.getParameter("getLastPost") != null) {
printLastRequest(resp);
} else if (req.getParameter("resetLastRequest") != null) {
lastRequestData = null;
} else if (req.getParameter("addTask") != null) {
addTask(req, resp);
} else if (req.getParameter("addTasks") != null) {
addTasks(req);
} else if (req.getParameter("purgeQueue") != null) {
purgeQueue(req);
} else if (req.getParameter("deferredTask") != null) {
deferredTask(req, resp);
} else if (req.getParameter("addPullTasks") != null) {
addPullTasks(req, resp);
} else if (req.getParameter("leaseTasks") != null) {
leaseAndDeleteTasks(req, resp);
} else {
handleTaskRequest(req);
}
}
private static void gotCalledBack(String data) {
try {
lastRequestData = new LastRequestData(DeferredTaskContext.getCurrentRequest(), data);
} catch (IOException e) {
throw new RuntimeException(e);
}
latch.countDown();
}
private static void deferredTask(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
String queue = req.getParameter("queue");
Queue q;
if (queue == null) {
q = QueueFactory.getDefaultQueue();
} else {
q = QueueFactory.getQueue(queue);
}
final String data = req.getParameter("deferredData");
TaskOptions opts =
TaskOptions.Builder.withPayload(
new DeferredTask() {
@Override
public void run() {
gotCalledBack(data);
}
});
latch = new CountDownLatch(1);
TaskHandle handle = q.add(opts);
resp.getWriter().print(handle.getQueueName());
}
private void handleTaskRequest(HttpServletRequest req) throws IOException {
lastRequestData = new LastRequestData(req, null);
latch.countDown();
}
private void purgeQueue(HttpServletRequest req) throws ServletException {
String queue = req.getParameter("queue");
Queue q;
if (queue == null) {
q = QueueFactory.getDefaultQueue();
} else {
q = QueueFactory.getQueue(queue);
}
q.purge();
}
private void addTask(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String queue = req.getParameter("queue");
Queue q;
if (queue == null) {
q = QueueFactory.getDefaultQueue();
} else {
q = QueueFactory.getQueue(queue);
}
TaskOptions.Method method = TaskOptions.Method.valueOf(req.getParameter("httpmethod"));
String url = req.getParameter("taskUrl");
if (url == null) {
url = req.getServletPath();
}
TaskOptions opts =
TaskOptions.Builder.withUrl(url).header("myheader", "blarg29").method(method);
if (method == TaskOptions.Method.POST || method == TaskOptions.Method.PUT) {
opts.payload("this=that");
} else {
opts.param("myparam", "yam28");
}
String execTimeMs = req.getParameter("execTimeMs");
if (execTimeMs != null) {
opts.etaMillis(Long.valueOf(execTimeMs));
}
String requestNamespace = req.getParameter("requestNamespace");
if (requestNamespace != null) {
/* We could override the current environment and set the request namespace
* but that's a little overkill and already tested in
* com.google.appengine.api.taskqueue.TaskQueueTest .
*/
opts.header(DEFAULT_NAMESPACE_HEADER, requestNamespace);
}
String currentNamespace = req.getParameter("currentNamespace");
if (currentNamespace != null) {
/* We could also do this:
* opts.header(CURRENT_NAMESPACE_HEADER, currentNamespace);
*/
NamespaceManager.set(currentNamespace);
}
latch = new CountDownLatch(1);
TaskHandle handle = q.add(opts);
resp.getWriter().print(handle.getQueueName());
}
private void addTasks(HttpServletRequest req) throws ServletException {
int numTasks = Integer.parseInt(req.getParameter("numTasks"));
Queue q = QueueFactory.getDefaultQueue();
latch = new CountDownLatch(numTasks);
q.add(Collections.nCopies(numTasks, TaskOptions.Builder.withUrl(req.getServletPath())));
}
private void addPullTasks(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
int numTasks = Integer.parseInt(req.getParameter("numTasks"));
String queue = req.getParameter("queue");
Queue q = QueueFactory.getQueue(queue);
List<TaskOptions> options = new ArrayList<TaskOptions>();
for (int i = 0; i < numTasks; i++) {
options.add(
TaskOptions.Builder.withMethod(TaskOptions.Method.PULL)
.payload(String.format("payload-%03d", i).getBytes()));
}
List<TaskHandle> tasks = q.add(options);
resp.getWriter().print(queue + "," + tasks.size());
}
private void leaseAndDeleteTasks(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
int numTasks = Integer.parseInt(req.getParameter("numTasks"));
Double lease = Double.parseDouble(req.getParameter("lease"));
String queue = req.getParameter("queue");
Queue q = QueueFactory.getQueue(queue);
Boolean doDelete = Boolean.parseBoolean(req.getParameter("doDelete"));
List<TaskHandle> tasks = q.leaseTasks(lease.intValue() * 1000, TimeUnit.MILLISECONDS, numTasks);
for (TaskHandle task : tasks) {
if (doDelete) {
q.deleteTask(task.getName());
}
}
resp.getWriter().print(queue + "," + tasks.size());
}
@Override
protected synchronized void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
handleTaskRequest(req);
}
@Override
protected synchronized void doHead(
HttpServletRequest req, HttpServletResponse httpServletResponse)
throws ServletException, IOException {
handleTaskRequest(req);
}
@Override
protected synchronized void doPut(HttpServletRequest req, HttpServletResponse httpServletResponse)
throws ServletException, IOException {
handleTaskRequest(req);
}
@Override
protected synchronized void doDelete(
HttpServletRequest req, HttpServletResponse httpServletResponse)
throws ServletException, IOException {
handleTaskRequest(req);
}
private static final String SEPARATOR = "_____";
private static void printLastRequest(HttpServletResponse resp) throws IOException {
if (lastRequestData == null) {
resp.setStatus(503);
return;
}
PrintWriter pw = resp.getWriter();
pw.println(lastRequestData.method);
pw.println(toString(lastRequestData.headerMap));
pw.println(toString(lastRequestData.paramMap));
pw.println(lastRequestData.payload);
if (!lastRequestData.requestNamespace.equals("")) {
pw.println("requestNamespace:" + lastRequestData.requestNamespace);
} else {
pw.println("requestNamespace");
}
if (lastRequestData.currentNamespace != null) {
pw.println("currentNamespace:" + lastRequestData.currentNamespace);
} else {
pw.println("currentNamespace");
}
if (lastRequestData.deferredData != null) {
pw.println("deferredData:" + lastRequestData.deferredData);
} else {
pw.println("deferredData");
}
}
private static final class LastRequestData {
private final String method;
private final Map<String, String> headerMap;
private final Map<String, String> paramMap;
private final String payload;
private final String requestNamespace;
private final String currentNamespace;
private final String deferredData;
private LastRequestData(HttpServletRequest req, String deferredData) throws IOException {
Environment environment = ApiProxy.getCurrentEnvironment();
requestNamespace = NamespaceManager.getGoogleAppsNamespace();
currentNamespace = NamespaceManager.get();
method = req.getMethod();
headerMap = new HashMap<String, String>();
for (String key : asIterable((Enumeration<String>) req.getHeaderNames())) {
headerMap.put(key, req.getHeader(key));
}
paramMap = new HashMap<String, String>();
for (String key : asIterable((Enumeration<String>) req.getParameterNames())) {
paramMap.put(key, req.getParameter(key));
}
BufferedReader reader = new BufferedReader(new InputStreamReader(req.getInputStream()));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
if (sb.length() == 0) {
payload = " ";
} else {
payload = sb.toString();
}
this.deferredData = deferredData;
}
}
private static String toString(Map<String, String> map) {
StringBuilder sb = new StringBuilder();
if (map.isEmpty()) {
return " ";
}
for (Map.Entry<String, String> entry : map.entrySet()) {
sb
.append(entry.getKey())
.append(SEPARATOR)
.append(entry.getValue())
.append(SEPARATOR);
}
return sb.toString();
}
private static <T> Iterable<T> asIterable(Enumeration<T> e) {
final List<T> list = new ArrayList<T>();
for (; e.hasMoreElements(); ) {
list.add(e.nextElement());
}
return new Iterable<T>() {
public Iterator<T> iterator() {
return list.iterator();
}
};
}
public static CountDownLatch getLatch() {
return latch;
}
}