View Javadoc
1   /*
2    * Copyright 2012-2025 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.helper;
17  
18  import java.util.HashMap;
19  import java.util.Map;
20  
21  import org.apache.logging.log4j.LogManager;
22  import org.apache.logging.log4j.Logger;
23  import org.codelibs.core.lang.StringUtil;
24  import org.codelibs.core.timer.TimeoutManager;
25  import org.codelibs.core.timer.TimeoutTarget;
26  import org.codelibs.core.timer.TimeoutTask;
27  import org.codelibs.fess.Constants;
28  import org.codelibs.fess.exception.ScheduledJobException;
29  import org.codelibs.fess.mylasta.direction.FessConfig;
30  import org.codelibs.fess.opensearch.config.exbhv.JobLogBhv;
31  import org.codelibs.fess.opensearch.config.exbhv.ScheduledJobBhv;
32  import org.codelibs.fess.opensearch.config.exentity.JobLog;
33  import org.codelibs.fess.opensearch.config.exentity.ScheduledJob;
34  import org.codelibs.fess.util.ComponentUtil;
35  import org.dbflute.optional.OptionalThing;
36  import org.lastaflute.job.JobManager;
37  import org.lastaflute.job.LaCron;
38  import org.lastaflute.job.LaJobRuntime;
39  import org.lastaflute.job.LaScheduledJob;
40  import org.lastaflute.job.key.LaJobUnique;
41  import org.lastaflute.job.subsidiary.CronParamsSupplier;
42  
43  /**
44   * Helper class for managing scheduled jobs within the Fess system.
45   * This class provides functionality for registering, unregistering, and monitoring scheduled jobs.
46   */
47  public class JobHelper {
48      /** Logger instance for this class */
49      private static final Logger logger = LogManager.getLogger(JobHelper.class);
50  
51      /**
52       * Default constructor.
53       */
54      public JobHelper() {
55          // Default constructor
56      }
57  
58      /** Monitor interval in seconds (default: 1 hour) */
59      protected int monitorInterval = 60 * 60;// 1hour
60  
61      /** Thread-local storage for job runtime information */
62      protected ThreadLocal<LaJobRuntime> jobRuntimeLocal = new ThreadLocal<>();
63  
64      /**
65       * Registers a scheduled job with the job manager.
66       *
67       * @param scheduledJob the scheduled job to register
68       */
69      public void register(final ScheduledJob scheduledJob) {
70          final JobManager jobManager = ComponentUtil.getJobManager();
71          jobManager.schedule(cron -> register(cron, scheduledJob));
72      }
73  
74      /**
75       * Registers a scheduled job with the specified cron scheduler.
76       *
77       * @param cron the cron scheduler to use
78       * @param scheduledJob the scheduled job to register
79       */
80      public void register(final LaCron cron, final ScheduledJob scheduledJob) {
81          if (scheduledJob == null) {
82              throw new ScheduledJobException("scheduledJob parameter is null. Cannot register a null job.");
83          }
84  
85          final String id = scheduledJob.getId();
86          if (!Constants.T.equals(scheduledJob.getAvailable())) {
87              logger.info("Inactive Job: id={}, name={}", id, scheduledJob.getName());
88              try {
89                  unregister(scheduledJob);
90              } catch (final Exception e) {
91                  if (logger.isDebugEnabled()) {
92                      logger.debug("Failed to delete Job: job={}", scheduledJob, e);
93                  }
94              }
95              return;
96          }
97  
98          final FessConfig fessConfig = ComponentUtil.getFessConfig();
99          final CronParamsSupplier paramsOp = () -> {
100             final Map<String, Object> params = new HashMap<>();
101             ComponentUtil.getComponent(ScheduledJobBhv.class)
102                     .selectByPK(scheduledJob.getId())
103                     .ifPresent(e -> params.put(Constants.SCHEDULED_JOB, e))
104                     .orElse(() -> {
105                         logger.warn("Job {} is not found.", scheduledJob.getId());
106                     });
107             return params;
108         };
109         findJobByUniqueOf(LaJobUnique.of(id)).ifPresent(job -> {
110             if (!job.isUnscheduled()) {
111                 if (StringUtil.isNotBlank(scheduledJob.getCronExpression())) {
112                     logger.info("Starting Job {}:{}", id, scheduledJob.getName());
113                     final String cronExpression = scheduledJob.getCronExpression();
114                     job.reschedule(cronExpression, op -> op.changeNoticeLogToDebug().params(paramsOp));
115                 } else {
116                     logger.info("Inactive Job: id={}, name={}", id, scheduledJob.getName());
117                     job.becomeNonCron();
118                 }
119             } else if (StringUtil.isNotBlank(scheduledJob.getCronExpression())) {
120                 logger.info("Starting Job {}:{}", id, scheduledJob.getName());
121                 final String cronExpression = scheduledJob.getCronExpression();
122                 job.reschedule(cronExpression, op -> op.changeNoticeLogToDebug().params(paramsOp));
123             }
124         }).orElse(() -> {
125             if (StringUtil.isNotBlank(scheduledJob.getCronExpression())) {
126                 logger.info("Starting Job {}:{}", id, scheduledJob.getName());
127                 final String cronExpression = scheduledJob.getCronExpression();
128                 cron.register(cronExpression, fessConfig.getSchedulerJobClassAsClass(), fessConfig.getSchedulerConcurrentExecModeAsEnum(),
129                         op -> op.uniqueBy(id).changeNoticeLogToDebug().params(paramsOp));
130             } else {
131                 logger.info("Inactive Job: id={}, name={}", id, scheduledJob.getName());
132                 cron.registerNonCron(fessConfig.getSchedulerJobClassAsClass(), fessConfig.getSchedulerConcurrentExecModeAsEnum(),
133                         op -> op.uniqueBy(id).changeNoticeLogToDebug().params(paramsOp));
134             }
135         });
136     }
137 
138     /**
139      * Finds a scheduled job by its unique identifier.
140      *
141      * @param jobUnique the unique identifier of the job
142      * @return an optional containing the scheduled job if found, empty otherwise
143      */
144     private OptionalThing<LaScheduledJob> findJobByUniqueOf(final LaJobUnique jobUnique) {
145         final JobManager jobManager = ComponentUtil.getJobManager();
146         try {
147             return jobManager.findJobByUniqueOf(jobUnique);
148         } catch (final Exception e) {
149             return OptionalThing.empty();
150         }
151     }
152 
153     /**
154      * Unregisters a scheduled job from the job manager.
155      *
156      * @param scheduledJob the scheduled job to unregister
157      * @throws ScheduledJobException if the job cannot be unregistered
158      */
159     public void unregister(final ScheduledJob scheduledJob) {
160         try {
161             final JobManager jobManager = ComponentUtil.getJobManager();
162             if (jobManager.isSchedulingDone()) {
163                 jobManager.findJobByUniqueOf(LaJobUnique.of(scheduledJob.getId())).ifPresent(job -> {
164                     job.unschedule();
165                 }).orElse(() -> logger.debug("Job {} is not scheduled.", scheduledJob.getId()));
166             }
167         } catch (final Exception e) {
168             throw new ScheduledJobException("Failed to delete Job: " + scheduledJob, e);
169         }
170     }
171 
172     /**
173      * Removes a scheduled job completely from the job manager.
174      *
175      * @param scheduledJob the scheduled job to remove
176      * @throws ScheduledJobException if the job cannot be removed
177      */
178     public void remove(final ScheduledJob scheduledJob) {
179         try {
180             final JobManager jobManager = ComponentUtil.getJobManager();
181             if (jobManager.isSchedulingDone()) {
182                 jobManager.findJobByUniqueOf(LaJobUnique.of(scheduledJob.getId())).ifPresent(job -> {
183                     job.disappear();
184                 }).orElse(() -> logger.debug("Job {} is not scheduled.", scheduledJob.getId()));
185             }
186         } catch (final Exception e) {
187             throw new ScheduledJobException("Failed to delete Job: " + scheduledJob, e);
188         }
189     }
190 
191     /**
192      * Checks if a job with the specified ID is available.
193      *
194      * @param id the job ID to check
195      * @return true if the job is available, false otherwise
196      */
197     public boolean isAvailable(final String id) {
198         return ComponentUtil.getComponent(ScheduledJobBhv.class)
199                 .selectByPK(id)
200                 .filter(e -> Boolean.TRUE.equals(e.getAvailable()))
201                 .isPresent();
202     }
203 
204     /**
205      * Stores a job log entry in the database.
206      *
207      * @param jobLog the job log entry to store
208      */
209     public void store(final JobLog jobLog) {
210         ComponentUtil.getComponent(JobLogBhv.class).insertOrUpdate(jobLog, op -> {
211             op.setRefreshPolicy(Constants.TRUE);
212         });
213     }
214 
215     /**
216      * Starts a monitor task for tracking job execution.
217      *
218      * @param jobLog the job log to monitor
219      * @return the timeout task for monitoring
220      */
221     public TimeoutTask startMonitorTask(final JobLog jobLog) {
222         final TimeoutTarget target = new MonitorTarget(jobLog);
223         return TimeoutManager.getInstance().addTimeoutTarget(target, monitorInterval, true);
224     }
225 
226     /**
227      * Sets the monitor interval for job monitoring.
228      *
229      * @param monitorInterval the monitor interval in seconds
230      */
231     public void setMonitorInterval(final int monitorInterval) {
232         this.monitorInterval = monitorInterval;
233     }
234 
235     /**
236      * Inner class that implements TimeoutTarget for monitoring job execution.
237      */
238     static class MonitorTarget implements TimeoutTarget {
239 
240         /** The job log being monitored */
241         private final JobLog jobLog;
242 
243         /**
244          * Constructor for MonitorTarget.
245          *
246          * @param jobLog the job log to monitor
247          */
248         public MonitorTarget(final JobLog jobLog) {
249             this.jobLog = jobLog;
250         }
251 
252         /**
253          * Called when the timeout expires. Updates the job log if the job is still running.
254          */
255         @Override
256         public void expired() {
257             if (jobLog.getEndTime() == null) {
258                 jobLog.setLastUpdated(ComponentUtil.getSystemHelper().getCurrentTimeAsLong());
259                 if (logger.isDebugEnabled()) {
260                     logger.debug("Update {}", jobLog);
261                 }
262                 ComponentUtil.getComponent(JobLogBhv.class).insertOrUpdate(jobLog, op -> {
263                     op.setRefreshPolicy(Constants.TRUE);
264                 });
265             }
266         }
267 
268     }
269 
270     /**
271      * Sets the job runtime for the current thread.
272      *
273      * @param runtime the job runtime to set
274      */
275     public void setJobRuntime(final LaJobRuntime runtime) {
276         jobRuntimeLocal.set(runtime);
277     }
278 
279     /**
280      * Gets the job runtime for the current thread.
281      *
282      * @return the job runtime for the current thread
283      */
284     public LaJobRuntime getJobRuntime() {
285         return jobRuntimeLocal.get();
286     }
287 }