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 }