/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package jhydra.core.scripting; import jhydra.core.config.IRuntimeConfig; import jhydra.core.exceptions.FatalException; import jhydra.core.exceptions.RecoverableException; import jhydra.core.logging.ILog; import jhydra.core.scripting.exceptions.ScriptExecutionException; import jhydra.core.scripting.exceptions.ScriptOtherFatalException; import sun.reflect.Reflection; /** * * @author jantic */ /*Used to encapsulate scripts in a more robust execution cycle. * Multiple attemps will be made when errors are thrown before giving up * and rethrowing errors to the parent test case. */ public class RobustScript implements IScript{ private final IScript script; private final IRuntimeConfig config; private final ILog log; public RobustScript(IScript script, IRuntimeConfig config, ILog log){ this.script = script; this.config = config; this.log = log; } @Override public void execute() throws RecoverableException, FatalException { final Integer maxNumberOfTries = config.getScriptMaxNumTries(); final Integer timeInBetweenAttempts = config.getScriptWaitSecondsBetweenAttempts(); Integer numberOfTries = 1; while (numberOfTries <= maxNumberOfTries) { if(attemptExecution()){return;} numberOfTries++; if (numberOfTries <= maxNumberOfTries) { final String message = "Attempt on script failed. Attempt number " + numberOfTries.toString() + " coming up, after a " + timeInBetweenAttempts.toString() + " second pause."; log.warn(message); waitForNextAttempt(timeInBetweenAttempts * 1000); } } final ScriptExecutionException ex = new ScriptExecutionException(this.getName(), ""); log.error(ex.getMessage()); throw ex; } private Boolean attemptExecution() throws FatalException, RecoverableException{ try { log.info("\t\tSCRIPT: " + this.getName()); this.script.execute(); return true; } catch (ScriptExecutionException ex){ //We only want to retry on RecoverableExceptions that are not ScriptExecutionExceptions, // because we don't want these retries cascading to each script in the parent call chain. // Instead, only the parent test case itself shall retry based on this exception. throw ex; } catch (RecoverableException ex) { //Only retry in this case if this script is embedded in another script. Otherwise, the script //is being called directly by a test case, and that test case itself will be retried. if(isParentCallingObjectAScript()){ log.warn(ex.getMessage(), ex); return false; } else{ log.error(ex.getMessage(), ex); throw new ScriptExecutionException(this.getName(), ex.getMessage(), ex); } } catch (FatalException ex) { logFatalException(ex); throw ex; } catch (Exception ex){ //Default to fatal, as no other categorization can be assumed at this point. logFatalException(ex); throw new ScriptOtherFatalException(this.getName(), ex); } catch (Throwable ex){ //Default to fatal, as no other categorization can be assumed at this point. logFatalError(ex); throw new ScriptOtherFatalException(this.getName(), ex); } } private void logFatalException(Exception ex){ final String message = "Script named " + this.getName() + " encountered a fatal error: " + ex.getMessage(); log.error(message, ex); } private void logFatalError(Throwable ex){ final String message = "Script named " + this.getName() + " encountered a fatal error: " + ex.getMessage(); log.error(message); } private Boolean isParentCallingObjectAScript(){ final StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); //Iterate up the stack until the calling object isn't this one Boolean executeMethodFound = false; for(int index = 0; index < stackTraceElements.length; index++){ final StackTraceElement stackTraceElement = stackTraceElements[index]; if(executeMethodFound){ return containsScriptInterface(Reflection.getCallerClass(index).getInterfaces()); } if(stackTraceElement.getMethodName().equalsIgnoreCase("execute")){ executeMethodFound = true; } } return false; } private Boolean containsScriptInterface(Class<?>[] classes){ for(Class<?> classObject : classes){ if(classObject == IScript.class){ return true; } } return false; } private void waitForNextAttempt(Integer milliseconds){ try{ Thread.sleep(milliseconds); } catch(Exception e){ final String message = "Sleep attempt failed: " + e.getMessage(); log.warn(message, e); } } @Override public String getName() { return script.getName(); } }