package org.ovirt.engine.core.utils.timer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.ovirt.engine.core.utils.CorrelationIdTracker;
import org.ovirt.engine.core.utils.log.LoggedUtils;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The JobWrapper gives 2 functionalities: 1. Enable running a method within an
* instance. 2. Enable running more than one method within a Class as a
* scheduled method.
*/
public class JobWrapper implements Job {
// static data members
private static ConcurrentMap<String, Method> cachedMethods = new ConcurrentHashMap<>();
private final Logger log = LoggerFactory.getLogger(SchedulerUtilQuartzImpl.class);
/**
* execute a method within an instance. The instance and the method name are
* expected to be in the context given object.
*
* @param context
* the context for this job.
*/
@SuppressWarnings("unchecked")
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
String methodName = null;
try {
JobDataMap data = context.getJobDetail().getJobDataMap();
Map paramsMap = data.getWrappedMap();
methodName = (String) paramsMap.get(SchedulerUtilBaseImpl.RUN_METHOD_NAME);
final Object instance = getInstanceToRun(paramsMap);
final Object[] methodParams = (Object[]) paramsMap.get(SchedulerUtilBaseImpl.RUN_METHOD_PARAM);
String methodKey = getMethodKey(instance.getClass().getName(), methodName);
final Method methodToRun;
if (!cachedMethods.containsKey(methodKey)) {
cachedMethods.putIfAbsent(methodKey, getMethodToRun(instance, methodName));
}
methodToRun = cachedMethods.get(methodKey);
String correlationId = LoggedUtils.getObjectId(this);
CorrelationIdTracker.setCorrelationId(correlationId);
invokeMethod(instance, methodToRun, methodParams);
} catch (Exception e) {
log.error("Failed to invoke scheduled method {}: {}", methodName, e.getMessage());
log.debug("Exception", e);
JobExecutionException jee = new JobExecutionException("failed to execute job");
jee.setStackTrace(e.getStackTrace());
throw jee;
} finally {
CorrelationIdTracker.setCorrelationId(null);
}
}
private void invokeMethod(final Object instance, final Method methodToRun, final Object[] methodParams)
throws Exception, IllegalAccessException, InvocationTargetException {
OnTimerMethodAnnotation annotation = methodToRun.getAnnotation(OnTimerMethodAnnotation.class);
if (annotation.transactional()) {
Exception e = TransactionSupport.executeInNewTransaction(() -> {
try {
methodToRun.invoke(instance, methodParams);
} catch (Exception e1) {
return e1;
}
return null;
});
if (e != null) {
throw e;
}
} else {
methodToRun.invoke(instance, methodParams);
}
}
protected Object getInstanceToRun(Map paramsMap) {
return paramsMap.get(SchedulerUtilBaseImpl.RUNNABLE_INSTANCE);
}
/**
* go over the class methods and find the method with the
* OnTimerMethodAnnotation and the methodId
*
* @param instance
* the instance of the class to look the methods on
* @param methodId
* the id of the method as stated in the OnTimerMethodAnnotation
* annotation
* @return the Method to run
*/
protected static Method getMethodToRun(Object instance, String methodId) {
Method methodToRun = null;
Method[] methods = instance.getClass().getMethods();
for (Method method : methods) {
OnTimerMethodAnnotation annotation = method.getAnnotation(OnTimerMethodAnnotation.class);
if (annotation != null && methodId.equals(annotation.value())) {
methodToRun = method;
break;
}
}
return methodToRun;
}
/**
* Check whether the specified timed method (id must match the OnTimerMethodAnnotation)
* allows concurrent execution.
*
* @param instance
* the instance of the class to look the methods on
* @param methodId
* the id of the method as stated in the OnTimerMethodAnnotation
* annotation
* @return true when concurrent executions are allowed or false when they are not
*/
protected static boolean methodAllowsConcurrent(Object instance, String methodId) {
Method method = getMethodToRun(instance, methodId);
OnTimerMethodAnnotation methodToRunAnnotation = method.getAnnotation(OnTimerMethodAnnotation.class);
return methodToRunAnnotation == null || methodToRunAnnotation.allowsConcurrent();
}
/*
* returns the key of the given method in the given class
*/
private String getMethodKey(String className, String methodName) {
return className + "." + methodName;
}
}