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.ArrayList;
19  import java.util.Calendar;
20  import java.util.List;
21  
22  import org.codelibs.core.lang.ThreadUtil;
23  import org.codelibs.fess.exception.FessSystemException;
24  import org.codelibs.fess.util.ComponentUtil;
25  
26  /**
27   * Helper class for controlling crawler execution intervals and timing.
28   * This class manages crawler execution timing based on configurable rules
29   * that can specify different delays for different time periods and days.
30   */
31  public class IntervalControlHelper {
32  
33      /** Flag indicating whether the crawler is currently running */
34      protected volatile boolean crawlerRunning = true;
35  
36      /**
37       * Default constructor.
38       */
39      public IntervalControlHelper() {
40          // Default constructor
41      }
42  
43      /** Wait time in milliseconds when crawler is not running */
44      protected long crawlerWaitMillis = 10000;
45  
46      /** List of interval rules for controlling crawler timing */
47      protected List<IntervalRule> ruleList = new ArrayList<>();
48  
49      /**
50       * Checks the crawler status and waits if the crawler is not running.
51       * This method blocks until the crawler is running again.
52       */
53      public void checkCrawlerStatus() {
54          while (!crawlerRunning) {
55              ThreadUtil.sleepQuietly(crawlerWaitMillis);
56          }
57      }
58  
59      /**
60       * Applies delay based on the configured interval rules.
61       * This method calculates the appropriate delay for the current time
62       * and applies it by sleeping the current thread.
63       */
64      public void delayByRules() {
65          final long delay = getDelay();
66          if (delay > 0) {
67              ThreadUtil.sleep(delay);
68          }
69      }
70  
71      /**
72       * Calculates the delay in milliseconds based on current time and configured rules.
73       * The method checks each rule to see if it applies to the current time and day.
74       *
75       * @return the delay in milliseconds, or 0 if no rules apply
76       */
77      protected long getDelay() {
78          if (ruleList.isEmpty()) {
79              return 0;
80          }
81          final Calendar cal = getCurrentCal();
82          final int h = cal.get(Calendar.HOUR_OF_DAY);
83          final int m = cal.get(Calendar.MINUTE);
84          final int d = cal.get(Calendar.DAY_OF_WEEK); // SUN(1) - SAT(7)
85          for (final IntervalRule rule : ruleList) {
86              if (rule.isTarget(h, m, d)) {
87                  return rule.getDelay();
88              }
89          }
90          return 0;
91      }
92  
93      /**
94       * Gets the current calendar instance set to the system time.
95       *
96       * @return the current calendar instance
97       */
98      protected Calendar getCurrentCal() {
99          final Calendar cal = Calendar.getInstance();
100         cal.setTimeInMillis(ComponentUtil.getSystemHelper().getCurrentTimeAsLong());
101         return cal;
102     }
103 
104     /**
105      * Adds a new interval rule to the rule list.
106      *
107      * @param from the start time in HH:MM format
108      * @param to the end time in HH:MM format
109      * @param days comma-separated list of days (1=Sunday, 7=Saturday)
110      * @param delay the delay in milliseconds to apply during this interval
111      */
112     public void addIntervalRule(final String from, final String to, final String days, final long delay) {
113         ruleList.add(new IntervalRule(from, to, days, delay));
114     }
115 
116     /**
117      * Checks if the crawler is currently running.
118      *
119      * @return true if the crawler is running, false otherwise
120      */
121     public boolean isCrawlerRunning() {
122         return crawlerRunning;
123     }
124 
125     /**
126      * Sets the crawler running status.
127      *
128      * @param crawlerRunning true to indicate the crawler is running, false otherwise
129      */
130     public void setCrawlerRunning(final boolean crawlerRunning) {
131         this.crawlerRunning = crawlerRunning;
132     }
133 
134     /**
135      * Represents a rule for controlling crawler intervals.
136      * Each rule defines a time range, applicable days, and delay amount.
137      */
138     public static class IntervalRule {
139         /** Starting hour of the interval */
140         protected int fromHours;
141 
142         /** Starting minute of the interval */
143         protected int fromMinutes;
144 
145         /** Ending hour of the interval */
146         protected int toHours;
147 
148         /** Ending minute of the interval */
149         protected int toMinutes;
150 
151         /** Delay in milliseconds to apply during this interval */
152         protected long delay;
153 
154         /** Array of days when this rule applies (1=Sunday, 7=Saturday) */
155         protected int[] days;
156 
157         /** Flag indicating if the interval spans across midnight */
158         protected boolean reverse;
159 
160         /**
161          * Creates a new interval rule.
162          *
163          * @param from the start time in HH:MM format
164          * @param to the end time in HH:MM format
165          * @param days comma-separated list of days (1=Sunday, 7=Saturday)
166          * @param delay the delay in milliseconds to apply during this interval
167          */
168         public IntervalRule(final String from, final String to, final String days, final long delay) {
169             final int[] fints = parseTime(from);
170             fromHours = fints[0];
171             fromMinutes = fints[1];
172             final int[] tints = parseTime(to);
173             toHours = tints[0];
174             toMinutes = tints[1];
175             final String[] values = days.split(",");
176             final List<Integer> list = new ArrayList<>();
177             for (final String value : values) {
178                 try {
179                     list.add(Integer.parseInt(value.trim()));
180                 } catch (final NumberFormatException e) {}
181             }
182             this.days = new int[list.size()];
183             for (int i = 0; i < list.size(); i++) {
184                 this.days[i] = list.get(i);
185             }
186             this.delay = delay;
187             reverse = compareTime(fromHours, fromMinutes, toHours, toMinutes) < 0;
188         }
189 
190         /**
191          * Gets the delay amount for this rule.
192          *
193          * @return the delay in milliseconds
194          */
195         public long getDelay() {
196             return delay;
197         }
198 
199         /**
200          * Checks if this rule applies to the given time and day.
201          *
202          * @param hours the hour of the day (0-23)
203          * @param minutes the minute of the hour (0-59)
204          * @param day the day of the week (1=Sunday, 7=Saturday)
205          * @return true if this rule applies, false otherwise
206          */
207         public boolean isTarget(final int hours, final int minutes, final int day) {
208             if (!reverse) {
209                 return compareTime(fromHours, fromMinutes, hours, minutes) >= 0 && compareTime(hours, minutes, toHours, toMinutes) >= 0
210                         && isInDays(day);
211             }
212             if (compareTime(hours, minutes, toHours, toMinutes) >= 0 && isInDays(day + 1)
213                     || compareTime(fromHours, fromMinutes, hours, minutes) >= 0 && isInDays(day)) {
214                 return true;
215             }
216             return false;
217         }
218 
219         /**
220          * Checks if the given day is included in this rule's applicable days.
221          *
222          * @param day the day of the week (1=Sunday, 7=Saturday)
223          * @return true if the day is included, false otherwise
224          */
225         private boolean isInDays(final int day) {
226             if (days.length == 0) {
227                 return true;
228             }
229             int value;
230             if (day == 8) {
231                 value = 1;
232             } else {
233                 value = day;
234             }
235             for (final int d : days) {
236                 if (d == value) {
237                     return true;
238                 }
239             }
240             return false;
241         }
242 
243         /**
244          * Compares two times.
245          *
246          * @param h1 the first hour
247          * @param m1 the first minute
248          * @param h2 the second hour
249          * @param m2 the second minute
250          * @return positive if first time is earlier, 0 if equal, negative if later
251          */
252         protected int compareTime(final int h1, final int m1, final int h2, final int m2) {
253             if (h1 < h2) {
254                 return 1;
255             }
256             if (h1 == h2) {
257                 if (m1 == m2) {
258                     return 0;
259                 }
260                 if (m1 < m2) {
261                     return 1;
262                 }
263             }
264             return -1;
265         }
266     }
267 
268     /**
269      * Parses a time string in HH:MM format.
270      *
271      * @param time the time string to parse
272      * @return an array containing [hour, minute]
273      * @throws FessSystemException if the time format is invalid
274      */
275     protected static int[] parseTime(final String time) {
276         final String[] froms = time.split(":");
277         if (froms.length != 2) {
278             throw new FessSystemException("Invalid time format: " + time + ". Expected format: HH:MM");
279         }
280         final int[] values = new int[2];
281         values[0] = Integer.parseInt(froms[0]);
282         if (values[0] < 0 || values[0] > 23) {
283             throw new FessSystemException("Invalid hour value: " + values[0] + " in time: " + time + ". Hour must be between 0 and 23");
284         }
285         values[1] = Integer.parseInt(froms[1]);
286         if (values[1] < 0 || values[1] > 59) {
287             throw new FessSystemException("Invalid minute value: " + values[1] + " in time: " + time + ". Minute must be between 0 and 59");
288         }
289         return values;
290     }
291 
292     /**
293      * Sets the wait time in milliseconds when the crawler is not running.
294      *
295      * @param crawlerWaitMillis the wait time in milliseconds
296      */
297     public void setCrawlerWaitMillis(final long crawlerWaitMillis) {
298         this.crawlerWaitMillis = crawlerWaitMillis;
299     }
300 
301 }