/*
* Copyright 2008 Google Inc.
*
* 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.jstestdriver;
import com.google.gson.Gson;
import com.google.jstestdriver.protocol.BrowserStreamAcknowledged;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author jeremiele@google.com (Jeremie Lenfant-Engelmann)
*/
public class BrowserQueryResponseServlet extends HttpServlet {
private static final Logger logger =
LoggerFactory.getLogger(BrowserQueryResponseServlet.class);
private static final long serialVersionUID = 995720234973219411L;
/** for something completely unrelated see: http://noop.googlecode.com/ */
private static final String NOOP = "noop";
private final Gson gson = new Gson();
private final CapturedBrowsers browsers;
private final URLTranslator urlTranslator;
private final ForwardingMapper forwardingMapper;
// TODO(corysmith): factor out a streaming session class.
private final ConcurrentMap<SlaveBrowser, List<String>> streamedResponses =
new ConcurrentHashMap<SlaveBrowser, List<String>>();
public BrowserQueryResponseServlet(CapturedBrowsers browsers, URLTranslator urlTranslator,
ForwardingMapper forwardingMapper) {
this.browsers = browsers;
this.urlTranslator = urlTranslator;
this.forwardingMapper = forwardingMapper;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
logger.trace("Browser Query Post:\n\tpath:{}\n\tresponse:{}\n\tdone:{}\n\tresponseId:{}",
new Object[] {
req.getPathInfo().substring(1),
req.getParameter("response"),
req.getParameter("done"),
req.getParameter("responseId")
});
service(req.getPathInfo().substring(1),
req.getParameter("response"),
req.getParameter("done"),
req.getParameter("responseId"),
resp.getWriter());
}
public void service(String id,
String response,
String done,
String responseId,
PrintWriter writer) {
SlaveBrowser browser = browsers.getBrowser(id);
if (browser != null) {
boolean isLast = Boolean.parseBoolean(done);
serviceBrowser(response, isLast, responseId, writer, browser);
} else {
logger.warn("Unknown browser {}", id);
}
writer.flush();
}
private void serviceBrowser(String response, Boolean done, String responseId, PrintWriter writer,
SlaveBrowser browser) {
addResponseId(responseId, browser);
browser.heartBeat();
Command command = null;
if (isResponseValid(response) && browser.isCommandRunning()) {
Response res = gson.fromJson(response, Response.class);
// TODO (corysmith): Replace this with polymorphism,
// using the response type to create disposable actions.
switch (res.getResponseType()) {
case FILE_LOAD_RESULT:
LoadedFiles loadedFiles = gson.fromJson(res.getResponse(), res.getGsonType());
Collection<FileResult> allLoadedFiles = loadedFiles.getLoadedFiles();
if (!allLoadedFiles.isEmpty()) {
LinkedHashSet<FileInfo> fileInfos = new LinkedHashSet<FileInfo>();
Collection<FileSource> errorFiles = new LinkedHashSet<FileSource>();
for (FileResult fileResult : allLoadedFiles) {
FileSource fileSource = fileResult.getFileSource();
if (fileResult.isSuccess()) {
fileInfos.add(new FileInfo(fileSource.getBasePath(), fileSource.getTimestamp(),
false, false, null));
} else {
errorFiles.add(fileSource);
}
}
browser.addFiles(fileInfos);
if (errorFiles.size() > 0) {
browser.removeFiles(errorFiles);
}
}
break;
// reset the browsers fileset.
case RESET_RESULT:
Command commandRunning = browser.getCommandRunning();
if (commandRunning != null) {
JsonCommand jsonCommand = gson.fromJson(commandRunning.getCommand(), JsonCommand.class);
if (jsonCommand.getCommand().equals(JsonCommand.CommandType.RESET.getCommand())) {
command = browser.getLastDequeuedCommand();
}
}
//$FALL-THROUGH$
case BROWSER_READY:
browser.resetFileSet();
urlTranslator.clear();
forwardingMapper.clear();
break;
}
//logger.trace("Received:\n done: {} \n res:\n {}\n", new Object[] {done, res});
browser.addResponse(res, done);
}
if (isResponseIdValid(responseId) && !done && !isResponseValid(response)) {
logger.debug("Streaming query for ids {} from {}", streamedResponses.get(browser), browser);
}
// TODO(corysmith): What do we do?
if (!isResponseValid(response) && done && browser.isCommandRunning()) {
logger.error("Streaming ending, but no response sent for {} while running {}",
browser,
browser.getCommandRunning());
}
// TODO(corysmith): Refactoring the streaming into a separate layer.
if (!done) { // we are still streaming, so we respond with the streaming
// acknowledge.
// this is independent of receiving an actual response.
writer.print(gson.toJson(new BrowserStreamAcknowledged(streamedResponses.get(browser))));
writer.flush();
return;
} else {
streamedResponses.clear();
}
if(command == null) {
command = browser.dequeueCommand();
}
writer.print(command != null ? command.getCommand() : NOOP);
}
private boolean isResponseValid(String response) {
return response != null && !"null".equals(response) && response.length() > 0;
}
private void addResponseId(String responseId, SlaveBrowser browser) {
if (!streamedResponses.containsKey(browser)) {
streamedResponses.put(browser, new CopyOnWriteArrayList<String>());
}
if (isResponseIdValid(responseId)) {
return;
}
streamedResponses.get(browser).add(responseId);
}
private boolean isResponseIdValid(String responseId) {
return responseId == null || "".equals(responseId);
}
}