Expand my Community achievements bar.

SOLVED

How to run workflow from Scheduled Task

Avatar

Level 2

I am trying to run an existing workflow from a Scheduled Task, it wouldn't start workflow and throws an exception:

java.lang.NullPointerException: null

...............
at org.apache.sling.commons.scheduler.impl.QuartzJobExecutor.execute(QuartzJobExecutor.java:349) [org.apache.sling.commons.scheduler:2.7.12]
at org.quartz.core.JobRunShell.run(JobRunShell.java:202) [org.apache.sling.commons.scheduler:2.7.12]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)

 But if I copy codes to servlet with the same system user which has permission to run workflow, it works fine without any problems. Is there any trick to run workflow from Scheduled Task?

 

Thanks for your help.

1 Accepted Solution

Avatar

Correct answer by
Employee Advisor

Here is my sample code -

 

 

/**
 * 
 */
package com.aem.demo.core.services;

import org.apache.sling.api.resource.ResourceResolver;

/**
 * @author debal
 * 
 *         This service will be used as a utility and it will help us to get
 *         resource resolver object , JCR session and close resource resolver
 *
 */
public interface JcrUtility {

	public ResourceResolver getResourceResolver();

	public void closeResourceResolver(ResourceResolver resourceResolver);
}

Implementation class [OSGi component] -

 

 

/**
 * 
 */
package com.aem.demo.core.services.impl;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.aem.demo.core.services.JcrUtility;

/**
 * @author debal
 *
 */
@Component(service = JcrUtility.class, immediate = true) 
public class JcrUtilityImpl implements JcrUtility {

	private final Logger logger = LoggerFactory.getLogger(JcrUtilityImpl.class);

	@Reference
	ResourceResolverFactory resourceResolverFactory;

	@Override
	public ResourceResolver getResourceResolver() {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put(resourceResolverFactory.SUBSERVICE, "readWriteService");
		ResourceResolver serviceResourceResolver = null;
		try {

			serviceResourceResolver = resourceResolverFactory.getServiceResourceResolver(map);
		} catch (LoginException e) {
			logger.error("Could not get service user [ {} ]", "demoSystemUser", e.getMessage());
		}
		return serviceResourceResolver;

	}

	@Override
	public void closeResourceResolver(ResourceResolver resourceResolver) {
		if (Objects.nonNull(resourceResolver)) {
			resourceResolver.close();
		}

	}

}

Bundle symbolic name , subservice name and system user mapping as shown below -

 

 

DEBAL_DAS_0-1659787620595.png

 

OSGi configuration associates with Workflow Scheduler -

 

package com.aem.demo.core.configurations;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

@ObjectClassDefinition(name = "Workflow Scheduler Configuration", description = "Workflow Scheduler Configuration")
public @interface WorkflowSchedulerConfiguration {

	/**
	 * This method will return the name of the Scheduler
	 * 
	 * @return {@link String}
	 */
	@AttributeDefinition(name = "Scheduler name", description = "Name of the scheduler", type = AttributeType.STRING)
	public String schdulerName() default "Workflow Scheduler configuration";

	/**
	 * This method will set flag to enable the scheduler
	 * 
	 * @return {@link Boolean}
	 */

	@AttributeDefinition(name = "Enabled", description = "True, if scheduler service is enabled", type = AttributeType.BOOLEAN)
	public boolean enabled() default false;

	/**
	 * This method returns the Cron expression which will decide how the scheduler
	 * will run
	 * 
	 * @return {@link String}
	 */

	@AttributeDefinition(name = "Cron Expression", description = "Cron expression used by the scheduler", type = AttributeType.STRING)
	public String cronExpression() default "0 * * * * ?";

	/**
	 * This method returns the Webpage Path
	 * 
	 * 
	 * @return {@link String}
	 */

	@AttributeDefinition(name = "Webpage Path", description = "Webpage Path", type = AttributeType.STRING)
	public String pagePath() default "/content/we-retail/language-masters/en";
	
	@AttributeDefinition(name = "Workflow Model", description = "Workflow Model", type = AttributeType.STRING)
	public String model() default "";

}

Scheduler code -

 

package com.aem.demo.core.schedulers;

import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.commons.scheduler.ScheduleOptions;
import org.apache.sling.commons.scheduler.Scheduler;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkflowData;
import com.adobe.granite.workflow.model.WorkflowModel;
import com.aem.demo.core.configurations.WorkflowSchedulerConfiguration;
import com.aem.demo.core.services.JcrUtility;
import com.google.common.base.Strings;

@Component(service = Runnable.class, immediate = true)
@Designate(ocd = WorkflowSchedulerConfiguration.class)
public class WorkflowScheduler implements Runnable {

	private final Logger logger = LoggerFactory.getLogger(WorkflowScheduler.class);

	@Reference
	JcrUtility jcrUtility;

	@Reference
	Scheduler scheduler;

	private String webpagePath;
	private String schedulerName;
	private String model;

	@Activate
	private void activate(WorkflowSchedulerConfiguration configuration) {

		this.webpagePath = configuration.pagePath();
		this.schedulerName = configuration.schdulerName();
		logger.info("**** Workflow Scheduler ****");
		// This scheduler will continue to run automatically even after the server
		// reboot, otherwise the scheduled tasks will stop running after the server
		// reboot.
		addScheduler(configuration);
	}

	@Modified
	protected void modified(WorkflowSchedulerConfiguration configuration) {
		// Remove the scheduler registered with old configuration
		removeScheduler(configuration);

		webpagePath = configuration.pagePath();
		model = configuration.model();
		// Add the scheduler registered with new configuration
		addScheduler(configuration);

	}

	private void addScheduler(WorkflowSchedulerConfiguration configuration) {

		boolean enabled = configuration.enabled();
		if (enabled) {
			ScheduleOptions scheduleOptions = scheduler.EXPR(configuration.cronExpression());

			if (!Strings.isNullOrEmpty(schedulerName)) {
				scheduleOptions.name(schedulerName);
				scheduleOptions.canRunConcurrently(false);
				scheduler.schedule(this, scheduleOptions);
				logger.info("****** Workflow Scheduler has been added successfully ******");

			}

		} else {
			logger.info("****** Workflow Scheduler is in disable state ******");
		}

	}

	@Deactivate
	protected void deactivated(WorkflowSchedulerConfiguration configuration) {
		logger.info("**** Removing Workflow Scheduler Successfully on deactivation ****");
		removeScheduler(configuration);
	}

	private void removeScheduler(WorkflowSchedulerConfiguration configuration) {

		logger.info("**** Removing Workflow Scheduler Successfully **** {}", schedulerName);
		scheduler.unschedule(schedulerName);

	}

	@Override
	public void run() {
		ResourceResolver resourceResolver = jcrUtility.getResourceResolver();
		WorkflowSession workflowSession = resourceResolver.adaptTo(WorkflowSession.class);
		try {
			WorkflowModel workflowModel = workflowSession.getModel(model);
			WorkflowData workflowData = workflowSession.newWorkflowData("JCR_PATH", webpagePath);
			workflowSession.startWorkflow(workflowModel, workflowData);
			logger.info("******Workflow Scheduler has been started ******{}", workflowModel.getTitle());
		} catch (WorkflowException e) {

			e.printStackTrace();
		}
          finally {
			jcrUtility.closeResourceResolver(resourceResolver);
		}
	}

}

Scheduler OSGi configuration at /system/console/configMgr -

 

DEBAL_DAS_1-1659787908307.png

Scheduler has been trigerred at configured time [captured in log file]-

06.08.2022 17:37:00.225 *INFO* [sling-default-4-Workflow Scheduler configuration] com.aem.demo.core.schedulers.WorkflowScheduler ******Workflow Scheduler has been started ******AEM Page Lock

 

Hope this will help.

View solution in original post

4 Replies

Avatar

Community Advisor

@jamesc25111500  There could be multiple issues for this error . Out of which one could be that the the default thread pool the scheduler is using has just 5 threads. That means if all of these 5 threads are busy, no new thread is scheduled. On top: Other parts of the product are using this threadpool too.

 

Option 1: You create a dedicated Sling Pool (by OSGI configuration) of appropriate size, this at least separates your operation from the rest of the system

Option 2: disable concurrent execution (https://sling.apache.org/documentation/bundles/scheduler-service-commons-scheduler.html#preventing-c...), so that only 1 of these scheduled jobs runs at once, and not many.

 

 

Avatar

Community Advisor

Hi,

You need to get the resource resolver first in Schedule task using subservice . I think resourceresolver is null in your case.

			paramMap.put(ResourceResolverFactory.SUBSERVICE, "readService");
			resourceResolver = resourceFactory.getServiceResourceResolver(paramMap);
WorkflowSession wfSession = (WorkflowSession) resourceResolver.adaptTo(WorkflowSession.class);

 



Arun Patidar

Avatar

Correct answer by
Employee Advisor

Here is my sample code -

 

 

/**
 * 
 */
package com.aem.demo.core.services;

import org.apache.sling.api.resource.ResourceResolver;

/**
 * @author debal
 * 
 *         This service will be used as a utility and it will help us to get
 *         resource resolver object , JCR session and close resource resolver
 *
 */
public interface JcrUtility {

	public ResourceResolver getResourceResolver();

	public void closeResourceResolver(ResourceResolver resourceResolver);
}

Implementation class [OSGi component] -

 

 

/**
 * 
 */
package com.aem.demo.core.services.impl;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import org.apache.sling.api.resource.LoginException;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.aem.demo.core.services.JcrUtility;

/**
 * @author debal
 *
 */
@Component(service = JcrUtility.class, immediate = true) 
public class JcrUtilityImpl implements JcrUtility {

	private final Logger logger = LoggerFactory.getLogger(JcrUtilityImpl.class);

	@Reference
	ResourceResolverFactory resourceResolverFactory;

	@Override
	public ResourceResolver getResourceResolver() {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put(resourceResolverFactory.SUBSERVICE, "readWriteService");
		ResourceResolver serviceResourceResolver = null;
		try {

			serviceResourceResolver = resourceResolverFactory.getServiceResourceResolver(map);
		} catch (LoginException e) {
			logger.error("Could not get service user [ {} ]", "demoSystemUser", e.getMessage());
		}
		return serviceResourceResolver;

	}

	@Override
	public void closeResourceResolver(ResourceResolver resourceResolver) {
		if (Objects.nonNull(resourceResolver)) {
			resourceResolver.close();
		}

	}

}

Bundle symbolic name , subservice name and system user mapping as shown below -

 

 

DEBAL_DAS_0-1659787620595.png

 

OSGi configuration associates with Workflow Scheduler -

 

package com.aem.demo.core.configurations;

import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;

@ObjectClassDefinition(name = "Workflow Scheduler Configuration", description = "Workflow Scheduler Configuration")
public @interface WorkflowSchedulerConfiguration {

	/**
	 * This method will return the name of the Scheduler
	 * 
	 * @return {@link String}
	 */
	@AttributeDefinition(name = "Scheduler name", description = "Name of the scheduler", type = AttributeType.STRING)
	public String schdulerName() default "Workflow Scheduler configuration";

	/**
	 * This method will set flag to enable the scheduler
	 * 
	 * @return {@link Boolean}
	 */

	@AttributeDefinition(name = "Enabled", description = "True, if scheduler service is enabled", type = AttributeType.BOOLEAN)
	public boolean enabled() default false;

	/**
	 * This method returns the Cron expression which will decide how the scheduler
	 * will run
	 * 
	 * @return {@link String}
	 */

	@AttributeDefinition(name = "Cron Expression", description = "Cron expression used by the scheduler", type = AttributeType.STRING)
	public String cronExpression() default "0 * * * * ?";

	/**
	 * This method returns the Webpage Path
	 * 
	 * 
	 * @return {@link String}
	 */

	@AttributeDefinition(name = "Webpage Path", description = "Webpage Path", type = AttributeType.STRING)
	public String pagePath() default "/content/we-retail/language-masters/en";
	
	@AttributeDefinition(name = "Workflow Model", description = "Workflow Model", type = AttributeType.STRING)
	public String model() default "";

}

Scheduler code -

 

package com.aem.demo.core.schedulers;

import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.commons.scheduler.ScheduleOptions;
import org.apache.sling.commons.scheduler.Scheduler;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.metatype.annotations.Designate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkflowData;
import com.adobe.granite.workflow.model.WorkflowModel;
import com.aem.demo.core.configurations.WorkflowSchedulerConfiguration;
import com.aem.demo.core.services.JcrUtility;
import com.google.common.base.Strings;

@Component(service = Runnable.class, immediate = true)
@Designate(ocd = WorkflowSchedulerConfiguration.class)
public class WorkflowScheduler implements Runnable {

	private final Logger logger = LoggerFactory.getLogger(WorkflowScheduler.class);

	@Reference
	JcrUtility jcrUtility;

	@Reference
	Scheduler scheduler;

	private String webpagePath;
	private String schedulerName;
	private String model;

	@Activate
	private void activate(WorkflowSchedulerConfiguration configuration) {

		this.webpagePath = configuration.pagePath();
		this.schedulerName = configuration.schdulerName();
		logger.info("**** Workflow Scheduler ****");
		// This scheduler will continue to run automatically even after the server
		// reboot, otherwise the scheduled tasks will stop running after the server
		// reboot.
		addScheduler(configuration);
	}

	@Modified
	protected void modified(WorkflowSchedulerConfiguration configuration) {
		// Remove the scheduler registered with old configuration
		removeScheduler(configuration);

		webpagePath = configuration.pagePath();
		model = configuration.model();
		// Add the scheduler registered with new configuration
		addScheduler(configuration);

	}

	private void addScheduler(WorkflowSchedulerConfiguration configuration) {

		boolean enabled = configuration.enabled();
		if (enabled) {
			ScheduleOptions scheduleOptions = scheduler.EXPR(configuration.cronExpression());

			if (!Strings.isNullOrEmpty(schedulerName)) {
				scheduleOptions.name(schedulerName);
				scheduleOptions.canRunConcurrently(false);
				scheduler.schedule(this, scheduleOptions);
				logger.info("****** Workflow Scheduler has been added successfully ******");

			}

		} else {
			logger.info("****** Workflow Scheduler is in disable state ******");
		}

	}

	@Deactivate
	protected void deactivated(WorkflowSchedulerConfiguration configuration) {
		logger.info("**** Removing Workflow Scheduler Successfully on deactivation ****");
		removeScheduler(configuration);
	}

	private void removeScheduler(WorkflowSchedulerConfiguration configuration) {

		logger.info("**** Removing Workflow Scheduler Successfully **** {}", schedulerName);
		scheduler.unschedule(schedulerName);

	}

	@Override
	public void run() {
		ResourceResolver resourceResolver = jcrUtility.getResourceResolver();
		WorkflowSession workflowSession = resourceResolver.adaptTo(WorkflowSession.class);
		try {
			WorkflowModel workflowModel = workflowSession.getModel(model);
			WorkflowData workflowData = workflowSession.newWorkflowData("JCR_PATH", webpagePath);
			workflowSession.startWorkflow(workflowModel, workflowData);
			logger.info("******Workflow Scheduler has been started ******{}", workflowModel.getTitle());
		} catch (WorkflowException e) {

			e.printStackTrace();
		}
          finally {
			jcrUtility.closeResourceResolver(resourceResolver);
		}
	}

}

Scheduler OSGi configuration at /system/console/configMgr -

 

DEBAL_DAS_1-1659787908307.png

Scheduler has been trigerred at configured time [captured in log file]-

06.08.2022 17:37:00.225 *INFO* [sling-default-4-Workflow Scheduler configuration] com.aem.demo.core.schedulers.WorkflowScheduler ******Workflow Scheduler has been started ******AEM Page Lock

 

Hope this will help.

Avatar

Level 2

Thanks for all inputs. After I changed input library from com.day.cq.workflow.* to com.adobe.granite.workflow.*, it works prefect now.