diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/annotation/CronMethodParser.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/annotation/CronMethodParser.java index acc368d69..8119c636e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/annotation/CronMethodParser.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/annotation/CronMethodParser.java @@ -19,27 +19,28 @@ import org.springframework.util.ClassUtils; /** * Parser to find method which is marked with CronTargetMethod annotation - * */ public class CronMethodParser { + public static class ClassMethodNamesPair { + + public String className; + public String methodName; + } + private static final String SEARCH_PACKAGE = "org.mifosplatform."; private static final String CRON_ANNOTATION_ATTRIBUTE_NAME = "jobName"; private static final String RESOURCE_PATTERN = "**/*.class"; - private static final Map targetMethosMap = new HashMap<>(); + private static final Map targetMethosMap = new HashMap<>(); private static final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); private static final MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourcePatternResolver); - public static final int CLASS_INDEX = 0; - - public static final int METHOD_INDEX = 1; - - public static String[] findTargetMethodDetails(final String attributeValue) throws IOException { + public static ClassMethodNamesPair findTargetMethodDetails(final String attributeValue) throws IOException { if (!targetMethosMap.containsKey(attributeValue)) { findAnnotationMethods(CronTarget.class, CRON_ANNOTATION_ATTRIBUTE_NAME); } @@ -66,8 +67,10 @@ public class CronMethodParser { final Map attributes = metadata.getAnnotationAttributes(annotationClass.getName()); final JobName attributeValue = (JobName) attributes.get(attributeName); final String className = metadata.getDeclaringClassName(); - final String[] mapVal = { className, metadata.getMethodName() }; - targetMethosMap.put(attributeValue.toString(), mapVal); + final ClassMethodNamesPair pair = new ClassMethodNamesPair(); + pair.className = className; + pair.methodName = metadata.getMethodName(); + targetMethosMap.put(attributeValue.toString(), pair); } } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobRegisterServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobRegisterServiceImpl.java index f9d8179e3..8dc16c66a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobRegisterServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/JobRegisterServiceImpl.java @@ -13,6 +13,7 @@ import org.mifosplatform.infrastructure.core.domain.MifosPlatformTenant; import org.mifosplatform.infrastructure.core.exception.PlatformInternalServerException; import org.mifosplatform.infrastructure.core.service.ThreadLocalContextUtil; import org.mifosplatform.infrastructure.jobs.annotation.CronMethodParser; +import org.mifosplatform.infrastructure.jobs.annotation.CronMethodParser.ClassMethodNamesPair; import org.mifosplatform.infrastructure.jobs.domain.ScheduledJobDetail; import org.mifosplatform.infrastructure.jobs.domain.SchedulerDetail; import org.mifosplatform.infrastructure.jobs.exception.JobNotFoundException; @@ -38,29 +39,57 @@ import org.springframework.stereotype.Service; * Service class to create and load batch jobs to Scheduler using * {@link SchedulerFactoryBean} ,{@link MethodInvokingJobDetailFactoryBean} and * {@link CronTriggerFactoryBean} - * */ @Service public class JobRegisterServiceImpl implements JobRegisterService { private final static Logger logger = LoggerFactory.getLogger(JobRegisterServiceImpl.class); - private final ApplicationContext applicationContext; + // MIFOSX-1184: This class cannot use constructor injection, because one of + // its dependencies (SchedulerStopListener) has a circular dependency to + // itself. So, slightly differently from how it's done elsewhere in this + // code base, the following fields are not final, and there is no + // constructor, but setters. - private final SchedularWritePlatformService schedularWritePlatformService; - - private final TenantDetailsService tenantDetailsService; + private ApplicationContext applicationContext; + private SchedularWritePlatformService schedularWritePlatformService; + private TenantDetailsService tenantDetailsService; + private SchedulerJobListener schedulerJobListener; + private SchedulerStopListener schedulerStopListener; + private SchedulerTriggerListener globalSchedulerTriggerListener; private final HashMap schedulers = new HashMap<>(4); @Autowired - public JobRegisterServiceImpl(final ApplicationContext applicationContext, final SchedularWritePlatformService schedularService, - final TenantDetailsService tenantDetailsService) { + public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; - this.schedularWritePlatformService = schedularService; + } + + @Autowired + public void setSchedularWritePlatformService(SchedularWritePlatformService schedularWritePlatformService) { + this.schedularWritePlatformService = schedularWritePlatformService; + } + + @Autowired + public void setTenantDetailsService(TenantDetailsService tenantDetailsService) { this.tenantDetailsService = tenantDetailsService; } + @Autowired + public void setSchedulerJobListener(SchedulerJobListener schedulerJobListener) { + this.schedulerJobListener = schedulerJobListener; + } + + @Autowired + public void setSchedulerStopListener(SchedulerStopListener schedulerStopListener) { + this.schedulerStopListener = schedulerStopListener; + } + + @Autowired + public void setGlobalTriggerListener(SchedulerTriggerListener globalTriggerListener) { + this.globalSchedulerTriggerListener = globalTriggerListener; + } + @PostConstruct public void loadAllJobs() { final List allTenants = this.tenantDetailsService.findAllTenants(); @@ -95,10 +124,7 @@ public class JobRegisterServiceImpl implements JobRegisterService { if (scheduler == null || !scheduler.checkExists(jobKey)) { final JobDetail jobDetail = createJobDetail(scheduledJobDetail); final String tempSchedulerName = "temp" + scheduledJobDetail.getId(); - final List> listenerClasses = new ArrayList<>(2); - listenerClasses.add(SchedulerJobListener.class); - listenerClasses.add(SchedulerStopListener.class); - final Scheduler tempScheduler = createScheduler(tempSchedulerName, 1, listenerClasses); + final Scheduler tempScheduler = createScheduler(tempSchedulerName, 1, schedulerJobListener, schedulerStopListener); tempScheduler.addJob(jobDetail, true); jobDataMap.put(SchedulerServiceConstants.SCHEDULER_NAME, tempSchedulerName); this.schedulers.put(tempSchedulerName, tempScheduler); @@ -228,7 +254,7 @@ public class JobRegisterServiceImpl implements JobRegisterService { if (scheduledJobDetail.getSchedulerGroup() > 0) { noOfThreads = SchedulerServiceConstants.GROUP_THREAD_COUNT; } - scheduler = createScheduler(schedulername, noOfThreads, null); + scheduler = createScheduler(schedulername, noOfThreads, schedulerJobListener); this.schedulers.put(schedulername, scheduler); } return scheduler; @@ -254,17 +280,11 @@ public class JobRegisterServiceImpl implements JobRegisterService { return sb.toString(); } - private Scheduler createScheduler(final String name, final int noOfThreads, List> listenerClasses) - throws Exception { + private Scheduler createScheduler(final String name, final int noOfThreads, JobListener... jobListeners) throws Exception { final SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); schedulerFactoryBean.setSchedulerName(name); - if (listenerClasses == null) { - listenerClasses = new ArrayList<>(1); - listenerClasses.add(SchedulerJobListener.class); - } - schedulerFactoryBean.setGlobalJobListeners(getGlobalListener(listenerClasses)); - final TriggerListener globalTriggerListener = this.applicationContext.getBean("schedulerTriggerListener", TriggerListener.class); - final TriggerListener[] globalTriggerListeners = { globalTriggerListener }; + schedulerFactoryBean.setGlobalJobListeners(jobListeners); + final TriggerListener[] globalTriggerListeners = { globalSchedulerTriggerListener }; schedulerFactoryBean.setGlobalTriggerListeners(globalTriggerListeners); final Properties quartzProperties = new Properties(); quartzProperties.put(SchedulerFactoryBean.PROP_THREAD_COUNT, Integer.toString(noOfThreads)); @@ -274,35 +294,20 @@ public class JobRegisterServiceImpl implements JobRegisterService { return schedulerFactoryBean.getScheduler(); } - private JobListener[] getGlobalListener(final List> listenerClasses) throws ClassNotFoundException { - final List listeners = new ArrayList<>(listenerClasses.size()); - for (final Class listenerClass : listenerClasses) { - final JobListener listener = (JobListener) getBeanObject(listenerClass); - listeners.add(listener); - } - final JobListener[] listenerArray = new JobListener[listeners.size()]; - return listeners.toArray(listenerArray); - } - private JobDetail createJobDetail(final ScheduledJobDetail scheduledJobDetail) throws Exception { final MifosPlatformTenant tenant = ThreadLocalContextUtil.getTenant(); - final String[] jobDetails = CronMethodParser.findTargetMethodDetails(scheduledJobDetail.getJobName()); - final Object targetObject = getBeanObject(Class.forName(jobDetails[CronMethodParser.CLASS_INDEX])); + final ClassMethodNamesPair jobDetails = CronMethodParser.findTargetMethodDetails(scheduledJobDetail.getJobName()); + final Object targetObject = getBeanObject(Class.forName(jobDetails.className)); final MethodInvokingJobDetailFactoryBean jobDetailFactoryBean = new MethodInvokingJobDetailFactoryBean(); jobDetailFactoryBean.setName(scheduledJobDetail.getJobName() + "JobDetail" + tenant.getId()); jobDetailFactoryBean.setTargetObject(targetObject); - jobDetailFactoryBean.setTargetMethod(jobDetails[CronMethodParser.METHOD_INDEX]); + jobDetailFactoryBean.setTargetMethod(jobDetails.methodName); jobDetailFactoryBean.setGroup(scheduledJobDetail.getGroupName()); jobDetailFactoryBean.setConcurrent(false); jobDetailFactoryBean.afterPropertiesSet(); return jobDetailFactoryBean.getObject(); } - /** - * @param jobDetails - * @return - * @throws ClassNotFoundException - */ private Object getBeanObject(final Class classType) throws ClassNotFoundException { final List> typesList = new ArrayList<>(); final Class[] interfaceType = classType.getInterfaces(); @@ -332,10 +337,6 @@ public class JobRegisterServiceImpl implements JobRegisterService { return targetObject; } - /** - * @param scheduledJobDetails - * @return - */ private Trigger createTrigger(final ScheduledJobDetail scheduledJobDetails, final JobDetail jobDetail) { final MifosPlatformTenant tenant = ThreadLocalContextUtil.getTenant(); final CronTriggerFactoryBean cronTriggerFactoryBean = new CronTriggerFactoryBean(); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedulerStopListener.java b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedulerStopListener.java index 1b3a80135..881c917d7 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedulerStopListener.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/infrastructure/jobs/service/SchedulerStopListener.java @@ -15,10 +15,16 @@ public class SchedulerStopListener implements JobListener { private static final String name = "Singlr Trigger Global Listner"; - private final JobRegisterService jobRegisterService; + // MIFOSX-1184: This class cannot use constructor injection, because one of + // its dependencies (SchedulerStopListener) has a circular dependency to + // itself. So, slightly differently from how it's done elsewhere in this + // code base, the following fields are not final, and there is no + // constructor, but setters. + + private JobRegisterService jobRegisterService; @Autowired - public SchedulerStopListener(final JobRegisterService jobRegisterService) { + public void setJobRegisterService(JobRegisterService jobRegisterService) { this.jobRegisterService = jobRegisterService; }