package com.aimmac23.hub; import java.net.URL; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.util.EntityUtils; import org.openqa.grid.internal.ExternalSessionKey; import org.openqa.grid.internal.TestSession; import org.openqa.selenium.remote.internal.HttpClientFactory; import com.aimmac23.hub.videostorage.IVideoStore; import com.aimmac23.hub.videostorage.LocalTempFileStore; import com.aimmac23.hub.videostorage.SessionInfoBean; import com.aimmac23.hub.videostorage.StoredVideoDownloadContext; import com.aimmac23.hub.videostorage.StoredVideoInfoContext; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; @SuppressWarnings("unchecked") public class HubVideoRegistry { private static final Logger log = Logger.getLogger(HubVideoRegistry.class.getName()); // 20 seconds private static final long DEFAULT_DOWNLOAD_WAIT_TIMEOUT = 20000; private static IVideoStore videoStore; private static Cache<String, VideoFuture> stoppingSessions; private static long downloadWaitTimeout; static { try { Class<? extends IVideoStore> storageClass = LocalTempFileStore.class; String classname = System.getProperty("video.storage"); if(classname != null) { storageClass = (Class<? extends IVideoStore>) Class.forName(classname); } videoStore = storageClass.newInstance(); log.info("Using " + storageClass + " to store videos"); } catch (Exception e) { // throw a nasty error to hopefully prevent the Hub from trying to continue without this throw new Error("Could not initialize video store due to exception", e); } stoppingSessions = CacheBuilder.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES).build(); String downloadTimeoutString = System.getProperty("video.downloadTimeout"); if(downloadTimeoutString == null) { downloadWaitTimeout = DEFAULT_DOWNLOAD_WAIT_TIMEOUT; } else { downloadWaitTimeout = Long.parseLong(downloadTimeoutString); } log.info("Download wait timeout currently set to " + downloadWaitTimeout + " milliseconds"); } public static void declareSessionStopping(TestSession session) { stoppingSessions.put(session.getExternalKey().getKey(), new VideoFuture()); } public static void copyVideoToHub(TestSession session, String pathKey, URL remoteHost) { String serviceUrl = remoteHost + "/extra/VideoRecordingControlServlet"; SessionInfoBean infoBean = new SessionInfoBean(session); ExternalSessionKey key = session.getExternalKey(); HttpHost remote = new HttpHost(remoteHost.getHost(), remoteHost.getPort()); HttpClientFactory httpClientFactory = new HttpClientFactory(); HttpClient client = httpClientFactory.getHttpClient(); HttpGet r = new HttpGet(serviceUrl + "?command=download&filekey=" + pathKey); try { HttpResponse response = client.execute(remote, r); if(response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { log.warning("Could not download video: " + EntityUtils.toString(response.getEntity())); return; } Header contentType = response.getFirstHeader("Content-Type"); if(contentType != null && !"video/webm".equals(contentType.getValue())) { log.log(Level.SEVERE, "Incorrect 'Content-Type' header when downloading video - " + "check that the control servlet is correctly setup for node: " + remoteHost); } else { videoStore.storeVideo(response.getEntity().getContent(), response.getEntity().getContentLength(), "video/webm", key.toString(), infoBean); } } catch(Exception e) { log.warning("Could not download video, exception caught: " + e.getMessage()); e.printStackTrace(); } finally { r.releaseConnection(); VideoFuture videoFuture = stoppingSessions.getIfPresent(key.getKey()); if(videoFuture != null) { videoFuture.setDone(); } } } private static void checkVideoIsDone(ExternalSessionKey key) throws Exception { VideoFuture videoFuture = stoppingSessions.getIfPresent(key.toString()); if(videoFuture != null) { videoFuture.get(downloadWaitTimeout, TimeUnit.MILLISECONDS); } } public static StoredVideoDownloadContext getVideoForSession(ExternalSessionKey key) throws Exception { checkVideoIsDone(key); return videoStore.retrieveVideo(key.toString()); } public static StoredVideoInfoContext getVideoInfoForSession(ExternalSessionKey key) throws Exception { checkVideoIsDone(key); return videoStore.getVideoInformation(key.toString()); } public static String getVideoStoreType() { return videoStore.getVideoStoreTypeIdentifier(); } private static class VideoFuture implements Future<Void> { private boolean isDone = false; public synchronized void setDone() { this.isDone = true; this.notifyAll(); } @Override public boolean cancel(boolean mayInterruptIfRunning) { // cancelling not allowed return false; } @Override public boolean isCancelled() { // we never cancel this return false; } @Override public boolean isDone() { return isDone; } @Override public synchronized Void get() throws InterruptedException, ExecutionException { while(!isDone) { this.wait(); } return null; } @Override public synchronized Void get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { if(!isDone) { this.wait(unit.toMillis(timeout)); } return null; } } }