/* * Copyright 2009 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.gson.reflect.TypeToken; import java.io.IOException; import java.io.PrintWriter; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Observable; import java.util.Observer; import java.util.Set; import java.util.Stack; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author jeremiele@google.com (Jeremie Lenfant-Engelmann) */ public class FileSetServlet extends HttpServlet implements Observer { private static final int HEARTBEAT_TIMEOUT = 2000; private final Gson gson = new Gson(); private final CapturedBrowsers capturedBrowsers; private final Map<String, String> files; private final Map<String, Lock> locks = new ConcurrentHashMap<String, Lock>(); public FileSetServlet(CapturedBrowsers capturedBrowsers, Map<String, String> files) { this.capturedBrowsers = capturedBrowsers; this.files = files; this.capturedBrowsers.addObserver(this); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { String id = req.getParameter("id"); String session = req.getParameter("session"); String sessionId = req.getParameter("sessionId"); if (session == null && sessionId != null) { sessionHeartBeat(id, sessionId); } else { if (session.equals("start")) { startSession(id, resp.getWriter()); } else if (session.equals("stop")) { stopSession(id, sessionId, resp.getWriter()); } } } private void sessionHeartBeat(String id, String sessionId) { Lock lock = locks.get(id); if (lock.getSessionId().equals(sessionId)) { lock.setLastHeartBeat(new Date().getTime()); } else { // who are you?? } } public void stopSession(String id, String sessionId, PrintWriter writer) { Lock lock = locks.get(id); lock.unlock(sessionId); writer.flush(); } public void startSession(String id, PrintWriter writer) { SlaveBrowser browser = capturedBrowsers.getBrowser(id); Lock lock = locks.get(id); String sessionId = UUID.randomUUID().toString(); if (lock.tryLock(sessionId)) { writer.write(sessionId); } else { // session is probably staled if ((!browser.isCommandRunning() && browser.peekCommand() == null) || ((new Date().getTime() - lock.getLastHeartBeat()) > HEARTBEAT_TIMEOUT)) { lock.forceUnlock(); SlaveBrowser slaveBrowser = capturedBrowsers.getBrowser(id); slaveBrowser.clearCommandRunning(); slaveBrowser.clearResponseQueue(); files.clear(); writer.write(lock.tryLock(sessionId) ? sessionId : "FAILED"); } else { writer.write("FAILED"); } } writer.flush(); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { String data = req.getParameter("data"); String id = req.getParameter("id"); if (data != null) { uploadFiles(id, data); } else { checkFileSet(req.getParameter("fileSet"), id, resp.getWriter()); } } public void checkFileSet(String fileSet, String browserId, PrintWriter writer) { Collection<FileInfo> clientFileSet = gson.fromJson(fileSet, new TypeToken<Collection<FileInfo>>() {}.getType()); SlaveBrowser browser = capturedBrowsers.getBrowser(browserId); Set<FileInfo> browserFileSet = browser.getFileSet(); Set<String> filesToRequest = new LinkedHashSet<String>(); if (browserFileSet.isEmpty() || !clientFileSet.containsAll(browserFileSet)) { for (FileInfo info : clientFileSet) { filesToRequest.add(info.getFileName()); } } else { Set<FileInfo> diff = new LinkedHashSet<FileInfo>(clientFileSet); diff.removeAll(browserFileSet); for (FileInfo info : diff) { filesToRequest.add(info.getFileName()); } for (FileInfo browserFileInfo : browserFileSet) { for (FileInfo clientFileInfo : clientFileSet) { if (clientFileInfo.equals(browserFileInfo) && clientFileInfo.getTimestamp() > browserFileInfo.getTimestamp()) { filesToRequest.add(clientFileInfo.getFileName()); break; } } } } if (!filesToRequest.isEmpty()) { if (browser.getBrowserInfo().getName().contains("Safari")) { filesToRequest.clear(); for (FileInfo info : clientFileSet) { filesToRequest.add(info.getFileName()); } } writer.write(gson.toJson(filesToRequest)); } writer.flush(); } public void update(Observable o, Object arg) { SlaveBrowser browser = (SlaveBrowser) arg; locks.put(browser.getId(), new Lock()); } public void uploadFiles(String id, String data) { SlaveBrowser browser = capturedBrowsers.getBrowser(id); Collection<FileData> filesData = gson.fromJson(data, new TypeToken<Collection<FileData>>() {}.getType()); LinkedHashSet<FileInfo> filesUploaded = new LinkedHashSet<FileInfo>(); for (FileData f : filesData) { String path = f.getFile(); String fileData = f.getData(); files.put(path, fileData); files.put(resolvePath(path), fileData); filesUploaded.add(new FileInfo(f.getFile(), f.getTimestamp(), false)); } browser.addFiles(filesUploaded); } private String resolvePath(String path) { Stack<String> resolvedPath = new Stack<String>(); String[] tokenizedPath = path.split("/"); for (String token : tokenizedPath) { if (token.equals("..")) { if (!resolvedPath.isEmpty()) { resolvedPath.pop(); continue; } } resolvedPath.push(token); } return join(resolvedPath); } private String join(Collection<String> collection) { StringBuilder sb = new StringBuilder(); Iterator<String> iterator = collection.iterator(); if (iterator.hasNext()) { sb.append(iterator.next()); while (iterator.hasNext()) { sb.append("/"); sb.append(iterator.next()); } } return sb.toString(); } }