package net.conselldemallorca.helium.jbpm3.spring; import java.io.PrintWriter; import java.io.StringWriter; import java.security.Principal; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jbpm.JbpmConfiguration; import org.jbpm.JbpmException; import org.jbpm.graph.exe.ProcessInstanceExpedient; import org.jbpm.job.ExecuteActionJob; import org.jbpm.job.ExecuteNodeJob; import org.jbpm.job.Job; import org.jbpm.job.Timer; import org.jbpm.job.executor.JobExecutor; import org.jbpm.job.executor.JobExecutorThread; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import com.codahale.metrics.Counter; import com.codahale.metrics.MetricRegistry; import net.conselldemallorca.helium.jbpm3.integracio.Jbpm3HeliumBridge; /** * Extension of the default jBPM Job executor thread. * * The only difference is that operations which need to use the data store, are now wrapped in a Spring transaction by using a transaction template. * * @author Joram Barrez */ public class SpringJobExecutorThread extends JobExecutorThread { private static final Log logger = LogFactory.getLog(SpringJobExecutorThread.class); private TransactionTemplate transactionTemplate; public SpringJobExecutorThread(String name, JobExecutor jobExecutor, JbpmConfiguration jbpmConfiguration, TransactionTemplate transactionTemplate, int idleInterval, int maxIdleInterval, long maxLockTime, int maxHistory ) { super(name, jobExecutor, jbpmConfiguration, idleInterval, maxIdleInterval, maxLockTime, maxHistory); this.transactionTemplate = transactionTemplate; } /* WRAPPED OPERATIONS */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected Collection acquireJobs() { return (Collection) transactionTemplate.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus transactionStatus) { return SpringJobExecutorThread.super.acquireJobs(); } }); } @SuppressWarnings({ "unchecked", "rawtypes" }) protected void executeJob(final Job job) { final String jName = (String) transactionTemplate.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus transactionStatus) { String jobName = "JOB"; if (job instanceof Timer) { if (((Timer) job).getName() != null) jobName += " " + ((Timer) job).getName(); } else if (job instanceof ExecuteActionJob) { if (((ExecuteActionJob) job).getAction() != null && ((ExecuteActionJob) job).getAction().getName() != null) jobName += " " + ((ExecuteActionJob) job).getAction().getName(); } else if (job instanceof ExecuteNodeJob) { if (((ExecuteNodeJob) job).getNode() != null && ((ExecuteNodeJob) job).getNode().getName() != null) jobName += " " + ((ExecuteNodeJob) job).getNode().getName(); } return jobName; } }); final ProcessInstanceExpedient expedient = job.getProcessInstance().getExpedient(); try { transactionTemplate.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus transactionStatus) { MetricRegistry metricRegistry = Jbpm3HeliumBridge.getInstanceService().getMetricRegistry(); final com.codahale.metrics.Timer timerTotal = metricRegistry.timer( MetricRegistry.name( JobExecutorThread.class, "job")); final com.codahale.metrics.Timer.Context contextTotal = timerTotal.time(); Counter countTotal = metricRegistry.counter( MetricRegistry.name( JobExecutorThread.class, "job.count")); countTotal.inc(); final com.codahale.metrics.Timer timerEntorn = metricRegistry.timer( MetricRegistry.name( JobExecutorThread.class, "job", expedient.getEntorn().getCodi())); final com.codahale.metrics.Timer.Context contextEntorn = timerEntorn.time(); Counter countEntorn = metricRegistry.counter( MetricRegistry.name( JobExecutorThread.class, "job.count", expedient.getEntorn().getCodi())); countEntorn.inc(); final com.codahale.metrics.Timer timerTipexp = metricRegistry.timer( MetricRegistry.name( JobExecutorThread.class, "job", expedient.getEntorn().getCodi(), expedient.getTipus().getCodi())); final com.codahale.metrics.Timer.Context contextTipexp = timerTipexp.time(); Counter countTipexp = metricRegistry.counter( MetricRegistry.name( JobExecutorThread.class, "job.count", expedient.getEntorn().getCodi(), expedient.getTipus().getCodi())); countTipexp.inc(); Authentication orgAuthentication = SecurityContextHolder.getContext().getAuthentication(); try { final String user = jobUser.get(job.getId()); if (user != null) { Principal principal = new Principal() { public String getName() { return user; } }; Authentication authentication = new UsernamePasswordAuthenticationToken(principal, null); Object credentials = "N/A"; List<String> rols = Jbpm3HeliumBridge.getInstanceService().getRolsByCodi(user); List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); if (rols != null && !rols.isEmpty()) { for (String rol: rols) { authorities.add(new SimpleGrantedAuthority(rol)); } authentication = new UsernamePasswordAuthenticationToken(principal, credentials, authorities); } SecurityContextHolder.getContext().setAuthentication(authentication); } if (Jbpm3HeliumBridge.getInstanceService().mesuraIsActiu()) { Jbpm3HeliumBridge.getInstanceService().mesuraIniciar(jName, "timer", expedient.getTipus().getNom(), null, null); } SpringJobExecutorThread.super.executeJob(job); String processInstanceId = new Long(job.getProcessInstance().getId()).toString(); Jbpm3HeliumBridge.getInstanceService().expedientReindexar(processInstanceId); } catch (Exception ex) { saveJobError(job.getId(), ex, "S'ha produït un error al executar el timer " + jName + "."); // Vaig a provocar la excepció des d'aquí, per a forçar el rollback... JbpmException e = new JbpmException(ex.getMessage(), ex.getCause()); throw e; } finally { SecurityContextHolder.getContext().setAuthentication(orgAuthentication); contextTotal.stop(); contextEntorn.stop(); contextTipexp.stop(); } return null; } }); } catch (Exception ex) { String[] error = jobErrors.get(job.getId()); String errorDesc = ""; String errorFull = ""; if (error == null || error.length < 2) { errorDesc = "S'ha produït un error al executar la transacció del timer " + jName + "."; errorFull = saveJobError(job.getId(), ex, errorDesc); } else { errorDesc = error[0]; errorFull = error[1]; } Jbpm3HeliumBridge.getInstanceService().updateExpedientError( job.getId(), expedient.getId(), errorDesc, errorFull); String msgError = "Error al executar la transaccio del job '" + jName + "' con processInstanceId=" + job.getProcessInstance().getId() + " de l'expedient (id=" + expedient.getId() + ", identificador=" + expedient.getIdentificador() + ")"; logger.error(msgError, ex); JbpmException e = new JbpmException(ex.getMessage(), ex.getCause()); throw e; } Jbpm3HeliumBridge.getInstanceService().mesuraCalcular(jName, "timer", expedient.getTipus().getNom(), null, null); } public void decrementJobRetries(Job job) { // TODO Auto-generated method stub } @SuppressWarnings({ "unchecked", "rawtypes" }) protected Date getNextDueDate() { return (Date) transactionTemplate.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus transactionStatus) { return SpringJobExecutorThread.super.getNextDueDate(); } }); } private String saveJobError(Long jobId, Throwable ex, String errorDesc) { StringWriter errors = new StringWriter(); ex.printStackTrace(new PrintWriter(errors)); String errorFull = errors.toString(); errorFull = errorFull.replace("'", "’").replace("\"", "“").replace("\n", "<br>").replace("\t", "    "); errorFull = StringEscapeUtils.escapeJavaScript(errorFull); jobErrors.put(jobId, new String[] {errorDesc, errorFull}); return errorFull; } }