com.applitools.eyes.visualgridclient.model.RenderingTask Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of eyes-common-java3 Show documentation
Show all versions of eyes-common-java3 Show documentation
Common code for Applitools Eyes Java SDK projects
package com.applitools.eyes.visualgridclient.model;
import com.applitools.ICheckSettings;
import com.applitools.ICheckSettingsInternal;
import com.applitools.eyes.Logger;
import com.applitools.eyes.visualgridclient.services.IEyesConnector;
import com.applitools.eyes.visualgridclient.services.IResourceFuture;
import com.applitools.eyes.visualgridclient.services.VisualGridRunner;
import com.applitools.eyes.visualgridclient.services.VisualGridTask;
import com.applitools.utils.GeneralUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.helger.commons.collection.impl.ICommonsList;
import com.helger.css.ECSSVersion;
import com.helger.css.decl.*;
import com.helger.css.reader.CSSReader;
import org.apache.commons.codec.binary.Base64;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public class RenderingTask implements Callable, CompletableTask {
private static final int MAX_FETCH_FAILS = 62;
private static final int MAX_ITERATIONS = 100;
public static final String CDT = "x-applitools-html/cdt";
private final List listeners = new ArrayList<>();
private IEyesConnector eyesConnector;
private String scriptResult;
private ICheckSettings checkSettings;
private List visualGridTaskList;
private List openVisualGridTaskList;
private RenderingInfo renderingInfo;
private final Map fetchedCacheMap;
private final Map putResourceCache;
private Logger logger;
private AtomicBoolean isTaskComplete = new AtomicBoolean(false);
private AtomicBoolean isForcePutNeeded;
private final List regionSelectors;
private IDebugResourceWriter debugResourceWriter;
private HashMap result = null;
private AtomicInteger framesLevel = new AtomicInteger();
private RGridDom dom = null;
private final boolean forceFullPageScreenshot;
private boolean isTaskStarted = false;
private boolean isTaskCompleted = false;
private boolean isTaskInException = false;
public interface RenderTaskListener {
void onRenderSuccess();
void onRenderFailed(Exception e);
}
public RenderingTask(IEyesConnector eyesConnector, String scriptResult, ICheckSettings checkSettings,
List visualGridTaskList, List openVisualGridTasks, VisualGridRunner renderingGridManager,
IDebugResourceWriter debugResourceWriter, RenderTaskListener listener, List regionSelectors, boolean forceFullPageScreenshot) {
this.eyesConnector = eyesConnector;
this.scriptResult = scriptResult;
this.checkSettings = checkSettings;
this.visualGridTaskList = visualGridTaskList;
this.openVisualGridTaskList = openVisualGridTasks;
this.renderingInfo = renderingGridManager.getRenderingInfo();
this.fetchedCacheMap = renderingGridManager.getCachedResources();
this.putResourceCache = renderingGridManager.getPutResourceCache();
this.logger = renderingGridManager.getLogger();
this.debugResourceWriter = debugResourceWriter;
this.regionSelectors = regionSelectors;
this.forceFullPageScreenshot = forceFullPageScreenshot;
this.listeners.add(listener);
String renderingGridForcePut = System.getenv("APPLITOOLS_RENDERING_GRID_FORCE_PUT");
this.isForcePutNeeded = new AtomicBoolean(renderingGridForcePut != null && renderingGridForcePut.equalsIgnoreCase("true"));
}
@Override
public RenderStatusResults call() {
try {
this.isTaskStarted = true;
addRenderingTaskToOpenTasks();
logger.verbose("enter");
boolean isSecondRequestAlreadyHappened = false;
logger.verbose("step 1");
//Parse to Map
result = GeneralUtils.parseJsonToObject(scriptResult);
logger.verbose("step 2");
//Build RenderRequests
RenderRequest[] requests = prepareDataForRG(result);
logger.verbose("step 3");
boolean stillRunning = true;
int fetchFails = 0;
boolean isForcePutAlreadyDone = false;
List runningRenders = null;
do {
try {
runningRenders = this.eyesConnector.render(requests);
} catch (Exception e) {
Thread.sleep(1500);
logger.verbose("/render throws exception... sleeping for 1.5s");
GeneralUtils.logExceptionStackTrace(logger, e);
if (e.getMessage().contains("Second request, yet still some resources were not PUT in renderId")) {
if (isSecondRequestAlreadyHappened) {
logger.verbose("Second request already happened");
}
isSecondRequestAlreadyHappened = true;
}
logger.verbose("ERROR " + e.getMessage());
fetchFails++;
}
logger.verbose("step 4.1");
if (runningRenders == null) {
logger.verbose("ERROR - runningRenders is null.");
continue;
}
for (int i = 0; i < requests.length; i++) {
RenderRequest request = requests[i];
request.setRenderId(runningRenders.get(i).getRenderId());
}
logger.verbose("step 4.2");
RunningRender runningRender = runningRenders.get(0);
RenderStatus worstStatus = runningRender.getRenderStatus();
worstStatus = calcWorstStatus(runningRenders, worstStatus);
boolean isNeedMoreDom = runningRender.isNeedMoreDom();
if (isForcePutNeeded.get() && !isForcePutAlreadyDone) {
forcePutAllResources(requests[0].getResources(), runningRender);
isForcePutAlreadyDone = true;
}
logger.verbose("step 4.3");
stillRunning = worstStatus == RenderStatus.NEED_MORE_RESOURCE || isNeedMoreDom || fetchFails > MAX_FETCH_FAILS;
if (stillRunning) {
sendMissingResources(runningRenders, requests[0].getDom(), requests[0].getResources(), isNeedMoreDom);
}
logger.verbose("step 4.4");
} while (stillRunning);
Map mapping = mapRequestToRunningRender(runningRenders, requests);
logger.verbose("step 5");
try {
pollRenderingStatus(mapping);
} catch (Exception e) {
this.isTaskInException = true;
GeneralUtils.logExceptionStackTrace(logger, e);
}
isTaskCompleted = true;
} catch (IOException | ExecutionException | InterruptedException e) {
GeneralUtils.logExceptionStackTrace(logger, e);
}
logger.verbose("Finished rendering task - exit");
return null;
}
private void addRenderingTaskToOpenTasks() {
if (this.openVisualGridTaskList != null) {
for (VisualGridTask visualGridTask : openVisualGridTaskList) {
visualGridTask.setRenderingTask(this);
}
}
}
private void forcePutAllResources(Map resources, RunningRender runningRender) {
RGridResource resource;
List allPuts = new ArrayList<>();
for (String url : resources.keySet()) {
try {
logger.verbose("trying to get url from map - " + url);
IResourceFuture resourceFuture = fetchedCacheMap.get(url);
if (resourceFuture == null) {
logger.verbose("fetchedCacheMap.get(url) == null trying dom");
if (url.equals(this.dom.getUrl())) {
resource = this.dom.asResource();
} else {
logger.verbose("Resource not found Exiting...");
return;
}
} else {
resource = resourceFuture.get();
PutFuture future = this.eyesConnector.renderPutResource(runningRender, resource);
logger.verbose("locking putResourceCache");
synchronized (putResourceCache) {
if (!resource.getContentType().equalsIgnoreCase(CDT)) {
putResourceCache.put(dom.getUrl(), future);
}
allPuts.add(future);
}
}
} catch (Exception e) {
GeneralUtils.logExceptionStackTrace(logger, e);
}
}
for (PutFuture put : allPuts) {
put.get();
}
}
private void notifySuccessAllListeners() {
for (RenderTaskListener listener : listeners) {
listener.onRenderSuccess();
}
}
private Map mapRequestToRunningRender(List runningRenders, RenderRequest[] requests) {
Map mapping = new HashMap<>();
for (int i = 0; i < requests.length; i++) {
RenderRequest request = requests[i];
RunningRender runningRender = runningRenders.get(i);
mapping.put(runningRender, request);
}
return mapping;
}
private RenderStatus calcWorstStatus(List runningRenders, RenderStatus worstStatus) {
LOOP:
for (RunningRender runningRender : runningRenders) {
switch (runningRender.getRenderStatus()) {
case NEED_MORE_RESOURCE:
if (worstStatus == RenderStatus.RENDERED || worstStatus == RenderStatus.RENDERING) {
worstStatus = RenderStatus.NEED_MORE_RESOURCE;
}
break;
case ERROR:
worstStatus = RenderStatus.ERROR;
break LOOP;
}
}
return worstStatus;
}
private List getRenderIds(Collection runningRenders) {
List ids = new ArrayList<>();
for (RunningRender runningRender : runningRenders) {
ids.add(runningRender.getRenderId());
}
return ids;
}
private void sendMissingResources(List runningRenders, RGridDom dom, Map resources, boolean isNeedMoreDom) {
logger.verbose("enter");
List allPuts = new ArrayList<>();
if (isNeedMoreDom) {
RunningRender runningRender = runningRenders.get(0);
PutFuture future = null;
try {
future = this.eyesConnector.renderPutResource(runningRender, dom.asResource());
} catch (JsonProcessingException e) {
GeneralUtils.logExceptionStackTrace(logger, e);
}
logger.verbose("locking putResourceCache");
allPuts.add(future);
logger.verbose("releasing putResourceCache");
}
logger.verbose("creating PutFutures for " + runningRenders.size() + " runningRenders");
for (RunningRender runningRender : runningRenders) {
createPutFutures(allPuts, runningRender, resources);
}
logger.verbose("calling future.get on " + allPuts.size() + " PutFutures");
for (PutFuture future : allPuts) {
logger.verbose("calling future.get on " + future.toString());
try {
future.get(20, TimeUnit.SECONDS);
} catch (Exception e) {
GeneralUtils.logExceptionStackTrace(logger, e);
}
}
logger.verbose("exit");
}
private void createPutFutures(List allPuts, RunningRender runningRender, Map resources) {
List needMoreResources = runningRender.getNeedMoreResources();
RGridResource resource;
for (String url : needMoreResources) {
if (putResourceCache.containsKey(url)) {
PutFuture putFuture = putResourceCache.get(url);
if (!allPuts.contains(putFuture)) {
allPuts.add(putFuture);
}
continue;
}
// logger.verbose("trying to get url from map - " + url);
IResourceFuture resourceFuture = fetchedCacheMap.get(url);
if (resourceFuture == null) {
logger.verbose("fetchedCacheMap.get(url) == null - " + url);
logger.verbose("Resource put requested but never downloaded(maybe a Frame)");
resource = resources.get(url);
} else {
try {
resource = resourceFuture.get();
} catch (InterruptedException | ExecutionException e) {
GeneralUtils.logExceptionStackTrace(logger, e);
continue;
}
//
}
logger.verbose("resource(" + resource.getUrl() + ") hash : " + resource.getSha256());
PutFuture future = this.eyesConnector.renderPutResource(runningRender, resource);
if (!putResourceCache.containsKey(url) && !resource.getContentType().equalsIgnoreCase(CDT)) {
synchronized (putResourceCache) {
putResourceCache.put(url, future);
}
}
allPuts.add(future);
}
}
private RenderRequest[] prepareDataForRG(HashMap result) throws ExecutionException, InterruptedException, JsonProcessingException {
final Map allBlobs = Collections.synchronizedMap(new HashMap());
Set resourceUrls = new HashSet<>();
parseScriptResult(result, allBlobs, resourceUrls);
logger.verbose("fetching " + resourceUrls.size() + " resources...");
//Fetch all resources
fetchAllResources(allBlobs, resourceUrls);
if (!resourceUrls.isEmpty()) {
logger.verbose("ERROR resourceUrl is not empty!!!!!***************************");
}
logger.verbose("done fetching resources.");
int written = addBlobsToCache(allBlobs);
logger.verbose("written " + written + " blobs to cache.");
//Create RenderingRequest
//Parse allBlobs to mapping
Map resourceMapping = new HashMap<>();
for (String url : allBlobs.keySet()) {
resourceMapping.put(url, this.fetchedCacheMap.get(url).get());
}
buildAllRGDoms(resourceMapping, result);
List allRequestsForRG = buildRenderRequests(result, resourceMapping);
RenderRequest[] asArray = allRequestsForRG.toArray(new RenderRequest[0]);
if (debugResourceWriter != null && !(debugResourceWriter instanceof NullDebugResourceWriter)) {
for (RenderRequest renderRequest : asArray) {
for (RGridResource value : renderRequest.getResources().values()) {
this.debugResourceWriter.write(value);
}
}
}
logger.verbose("exit - returning renderRequest array of length: " + asArray.length);
return asArray;
}
private void buildAllRGDoms(Map resourceMapping, Map result) {
String url = (String) result.get("url");
URL baseUrl = null;
try {
baseUrl = new URL(url);
} catch (MalformedURLException e) {
GeneralUtils.logExceptionStackTrace(logger, e);
}
logger.verbose("baseUrl: " + baseUrl);
for (String key : result.keySet()) {
if ("frames".equals(key)) {
@SuppressWarnings("unchecked")
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy