cancel
Showing results for 
Search instead for 
Did you mean: 

[resolved] Scheduling jobs and Spring context

zomurn
Champ in-the-making
Champ in-the-making
Hello,

I coded a scheduled job like this :

<bean id="fromTraiterToArchiverScheduler" class="org.springframework.scheduling.quartz.JobDetailBean">
        <property name="jobClass">
           <value>org.alfresco.module.msc.action.executer.MscArchiverExecuter</value>
        </property>
    </bean>
   
    <bean id="fromTraiterToArchiverEveryOneHundredDays" class="org.alfresco.util.CronTriggerBean">
        <property name="jobDetail">
            <ref bean="fromTraiterToArchiverScheduler" />
        </property>
        <property name="scheduler">
            <ref bean="schedulerFactory" />
        </property>
        <property name="cronExpression">
            <value>0/10 * * * * ?</value>
        </property>
    </bean>

And the problem is that the class MscArchiverExecuter has some spring beans which need to be initialized through the context loader.
The big problem is that the scheduled job starts BEFORE spring context (cf. article below).
What do I need to do so to wire my spring beans in MscArchiverExecuter ?

PS : extending "ApplicationContextAware" is useless since Spring context is still not loaded !


Spring InvervalJobs and scheduling in Liferay 5

If you have a Liferay portlet that requires some scheduling you can easily use Liferay's built-in Scheduler to add an IntervalJob to the job list, like this. However, what if your IntervalJob is a Spring bean and has dependencies on other Spring beans in the portlet? Unfortunately, at the time of this writing (Liferay 5.1.2), the hot deploy code invokes the Scheduler configuration and execution before the context is initialized–which means you're up a creek when Spring is setup to be initialized with the context (happens to be my case).

An alternative approach is to extend com.liferay.portal.job.JobSchedulerImpl with a Spring singleton and configure the jobs via Spring. While this is very flexible, the singleton is now operating outside the Liferay Quartz realm and therefore will not be subject to the lifecycle of the portlet. That is to say, when you redeploy the portlet the jobs stay scheduled. A more annoying aspect to this is that if you try to shutdown Liferay it appears to hang. Sure the log says that Coyote is stopped, but that's not the case and the process appears to be waiting on a thread. This in turn requires manually killing every time. During development this is such a pain. My guess, without significant research into the bowels of the Liferay Quartz integration, is that the Spring singleton hasn't been properly disposed of.

One solution to this situation is to extend org.springframework.web.context.ContextLoaderListener with something like this:


public class SpringSchedulerContextLoaderListener extends org.springframework.web.context.ContextLoaderListener{
  private static final Logger logger = Logger.getLogger(SpringSchedulerContextLoaderListener.class);
  public void contextInitialized(ServletContextEvent event) {
    super.contextInitialized(event);
  }

  public void contextDestroyed(ServletContextEvent event) {

    JobScheduler j = (JobScheduler) StaticApplicationContextHolder.getApplicationContext().getBean("jobScheduler");
    j.shutdown();
  
    super.contextDestroyed(event);
  }
}




This will ensure that the Spring singleton JobScheduler will unschedule the registered IntervalJobs when the context is destroyed. You're good to go once this entry replaces org.springframework.web.context.ContextLoaderListener in web.xml.

There may be a more efficient way to do this, but for now this works.
2 REPLIES 2

zomurn
Champ in-the-making
Champ in-the-making
no idea ?
The solution might be with "copyAction" in scheduled-action-services-context.xml.sample , isn't it ?

zomurn
Champ in-the-making
Champ in-the-making
We need to do like this :

<bean id="MscArchiverExecuter"
      class="org.alfresco.module.msc.action.executer.MscArchiverExecuter" parent="action-executer">
      <property name="nodeService">
         <ref bean="NodeService" />
      </property>
      <property name="fileFolderService">
         <ref bean="FileFolderService" />
      </property>
      <property name="transactionService">
         <ref bean="TransactionService" />
      </property>
      <property name="mscSearchService">
         <ref bean="MscSearchService" />
      </property>
      <property name="mscNodeService">
         <ref bean="MscNodeService" />
      </property>
   </bean>
   
    <bean id="fromTraiterToArchiverAction" class="org.alfresco.repo.action.scheduled.SimpleTemplateActionDefinition">
        <property name="actionName">
            <value>MscArchiverExecuter</value>
        </property>
        <property name="parameterTemplates">
            <map>
                <entry>
                    <key>
                        <value>destination-folder</value>
                    </key>
                    <value>${selectSingleNode('workspace://SpacesStore', 'lucene', 'PATH:"/app:company_home/cm:Archiver"' )}</value>
                </entry>
                <entry>
                    <key>
                        <value>assoc-type</value>
                    </key>
                    <value>${node.primaryParentAssoc.typeQName}</value>
                </entry>
                <entry>
                    <key>
                        <value>assoc-name</value>
                    </key>
                    <value>${node.primaryParentAssoc.QName}</value>
                </entry>
                <entry>
                    <key>
                        <value>deep-copy</value>
                    </key>
                    <value>false</value>
                </entry>
            </map>
        </property>
        <property name="templateActionModelFactory">
            <ref bean="templateActionModelFactory"/>
        </property>
        <property name="dictionaryService">
            <ref bean="DictionaryService"/>
        </property>
        <property name="actionService">
            <ref bean="ActionService"/>
        </property>
        <property name="templateService">
            <ref bean="TemplateService"/>
        </property>
    </bean>
   
    <bean id="fromTraiterToArchiverEveryOneHundredDays" class="org.alfresco.repo.action.scheduled.CronScheduledQueryBasedTemplateActionDefinition">
        <property name="transactionMode">
            <value>UNTIL_FIRST_FAILURE</value>
        </property>
        <property name="compensatingActionMode">
            <value>IGNORE</value>
        </property>
        <property name="searchService">
            <ref bean="SearchService"/>
        </property>
        <property name="templateService">
            <ref bean="TemplateService"/>
        </property>
        <property name="queryLanguage">
            <value>lucene</value>
        </property>
        <property name="stores">
            <list>
                <value>workspace://SpacesStore</value>
            </list>
        </property>
        <property name="queryTemplate">
            <value>+PATH:"/app:company_home/cm:Traiter/*"</value>
        </property>
        <property name="cronExpression">
            <value>0/10 * * * * ?</value>
        </property>
        <property name="jobName">
            <value>jobC</value>
        </property>
        <property name="jobGroup">
            <value>jobGroup</value>
        </property>
        <property name="triggerName">
            <value>triggerC</value>
        </property>
        <property name="triggerGroup">
            <value>triggerGroup</value>
        </property>
        <property name="scheduler">
            <ref bean="schedulerFactory"/>
        </property>
        <property name="actionService">
            <ref bean="ActionService"/>
        </property>
        <property name="templateActionModelFactory">
            <ref bean="templateActionModelFactory"/>
        </property>
        <property name="templateActionDefinition">
            <ref bean="fromTraiterToArchiverAction"/>
        </property>
        <property name="transactionService">
            <ref bean="TransactionService"/>
        </property>
        <property name="runAsUser">
            <value>System</value>
        </property>
    </bean>

MscArchiverExecuter.java :

public class MscArchiverExecuter extends ActionExecuterAbstractBase {

   public static final String   WHO                     = "automate";

   public static final String   NAME                  = "MscArchiverExecuter";

   public static final String   PARAM_DESTINATION_FOLDER   = "destination-folder";

   public static final String   PARAM_ASSOC_TYPE_QNAME      = "assoc-type";

   public static final String   PARAM_ASSOC_QNAME         = "assoc-name";

   public static final String   PARAM_DEEP_COPY            = "deep-copy";

   public static final String   PARAM_OVERWRITE_COPY      = "overwrite-copy";

   private static Log         log                     = LogFactory
                                                   .getLog( MscArchiverExecuter.class );

   private FileFolderService   fileFolderService;

   private NodeService         nodeService;

   private MscSearchService   mscSearchService;

   private TransactionService   transactionService;

   private MscNodeService      mscNodeService;

@Override
   protected void addParameterDefinitions(List<ParameterDefinition> pParamList) {

      pParamList.add( new ParameterDefinitionImpl( PARAM_DESTINATION_FOLDER,
            DataTypeDefinition.NODE_REF, true,
            getParamDisplayLabel( PARAM_DESTINATION_FOLDER ) ) );
      pParamList.add( new ParameterDefinitionImpl( PARAM_ASSOC_TYPE_QNAME,
            DataTypeDefinition.QNAME, true,
            getParamDisplayLabel( PARAM_ASSOC_TYPE_QNAME ) ) );
      pParamList.add( new ParameterDefinitionImpl( PARAM_ASSOC_QNAME,
            DataTypeDefinition.QNAME, true,
            getParamDisplayLabel( PARAM_ASSOC_QNAME ) ) );
      pParamList.add( new ParameterDefinitionImpl( PARAM_DEEP_COPY,
            DataTypeDefinition.BOOLEAN, false,
            getParamDisplayLabel( PARAM_DEEP_COPY ) ) );
      pParamList.add( new ParameterDefinitionImpl( PARAM_OVERWRITE_COPY,
            DataTypeDefinition.BOOLEAN, false,
            getParamDisplayLabel( PARAM_OVERWRITE_COPY ) ) );

   }

@Override
   protected void executeImpl(Action pAction, NodeRef pActionedUponNodeRef) {

//process
}

//add bean setters spring injection
}