1
1
package dev .vml .es .acm .core .script ;
2
2
3
- import dev .vml .es .acm .core .AcmConstants ;
4
3
import dev .vml .es .acm .core .code .*;
5
4
import dev .vml .es .acm .core .code .schedule .BootSchedule ;
6
5
import dev .vml .es .acm .core .code .schedule .CronSchedule ;
15
14
import dev .vml .es .acm .core .repo .Repo ;
16
15
import dev .vml .es .acm .core .util .ChecksumUtils ;
17
16
import dev .vml .es .acm .core .util .ResolverUtils ;
17
+ import java .util .Arrays ;
18
+ import java .util .Collection ;
19
+ import java .util .Collections ;
20
+ import java .util .Date ;
21
+ import java .util .HashMap ;
18
22
import java .util .List ;
19
23
import java .util .Map ;
20
24
import java .util .concurrent .ConcurrentHashMap ;
21
- import java .util .concurrent .CopyOnWriteArrayList ;
25
+ import java .util .stream .Stream ;
26
+
22
27
import org .apache .commons .lang3 .StringUtils ;
23
28
import org .apache .sling .api .resource .LoginException ;
24
29
import org .apache .sling .api .resource .ResourceResolver ;
25
30
import org .apache .sling .api .resource .ResourceResolverFactory ;
26
31
import org .apache .sling .api .resource .observation .ResourceChange ;
27
32
import org .apache .sling .api .resource .observation .ResourceChangeListener ;
28
- import org .apache .sling .commons .scheduler .Job ;
29
- import org .apache .sling .commons .scheduler .ScheduleOptions ;
30
- import org .apache .sling .commons .scheduler .Scheduler ;
33
+ import org .apache .sling .event .jobs .Job ;
34
+ import org .apache .sling .event .jobs .JobBuilder ;
35
+ import org .apache .sling .event .jobs .JobManager ;
36
+ import org .apache .sling .event .jobs .ScheduledJobInfo ;
37
+ import org .apache .sling .event .jobs .consumer .JobConsumer ;
31
38
import org .osgi .service .component .annotations .*;
32
39
import org .osgi .service .metatype .annotations .AttributeDefinition ;
33
40
import org .osgi .service .metatype .annotations .Designate ;
36
43
import org .slf4j .LoggerFactory ;
37
44
38
45
@ Component (
39
- service = {ResourceChangeListener .class , EventListener .class },
46
+ service = {ResourceChangeListener .class , EventListener .class , JobConsumer . class },
40
47
immediate = true ,
41
48
property = {
42
49
ResourceChangeListener .PATHS + "=glob:" + ScriptRepository .ROOT + "/automatic/**/*.groovy" ,
43
50
ResourceChangeListener .CHANGES + "=ADDED" ,
44
51
ResourceChangeListener .CHANGES + "=CHANGED" ,
45
- ResourceChangeListener .CHANGES + "=REMOVED"
52
+ ResourceChangeListener .CHANGES + "=REMOVED" ,
53
+ JobConsumer .PROPERTY_TOPICS + "=" + ScriptScheduler .JOB_TOPIC
46
54
})
47
- @ Designate (ocd = AutomaticScriptScheduler .Config .class )
48
- public class AutomaticScriptScheduler implements ResourceChangeListener , EventListener {
55
+ @ Designate (ocd = ScriptScheduler .Config .class )
56
+ public class ScriptScheduler implements ResourceChangeListener , EventListener , JobConsumer {
57
+
58
+ private static final Logger LOG = LoggerFactory .getLogger (ScriptScheduler .class );
59
+
60
+ public static final String JOB_TOPIC = "dev/vml/es/acm/ScriptScheduler" ;
61
+
62
+ public static final String JOB_PROP_TYPE = "type" ;
49
63
50
- private static final Logger LOG = LoggerFactory . getLogger ( AutomaticScriptScheduler . class ) ;
64
+ public static final String JOB_PROP_SCRIPT_PATH = "scriptPath" ;
51
65
52
- private static final String BOOT_JOB_NAME = AcmConstants .CODE + "-boot" ;
66
+ public enum JobType {
67
+ BOOT ,
68
+ CRON ;
69
+
70
+ public static JobType of (String value ) {
71
+ return Arrays .stream (values ())
72
+ .filter (v -> StringUtils .equalsIgnoreCase (v .name (), value ))
73
+ .findFirst ()
74
+ .orElseThrow (
75
+ () -> new IllegalArgumentException ("Script scheduler job type is unsupported: " + value ));
76
+ }
77
+ }
53
78
54
79
@ ObjectClassDefinition (
55
- name = "AEM Content Manager - Automatic Script Scheduler" ,
80
+ name = "AEM Content Manager - Script Scheduler" ,
56
81
description = "Schedules automatic scripts on instance up and script changes" )
57
82
public @interface Config {
58
83
84
+ @ AttributeDefinition (name = "Boot Delay" , description = "Time in milliseconds to wait before booting scripts" )
85
+ long bootDelay () default 1000 * 10 ; // 10 seconds
86
+
59
87
@ AttributeDefinition (
60
88
name = "User Impersonation ID" ,
61
89
description =
@@ -81,9 +109,7 @@ public class AutomaticScriptScheduler implements ResourceChangeListener, EventLi
81
109
82
110
private Boolean instanceReady ;
83
111
84
- private final Map <String , String > booted = new ConcurrentHashMap <>();
85
-
86
- private final List <String > scheduled = new CopyOnWriteArrayList <>();
112
+ private final Map <String , String > bootedScripts = new ConcurrentHashMap <>();
87
113
88
114
@ Reference
89
115
private ResourceResolverFactory resourceResolverFactory ;
@@ -95,7 +121,7 @@ public class AutomaticScriptScheduler implements ResourceChangeListener, EventLi
95
121
private Executor executor ;
96
122
97
123
@ Reference
98
- private Scheduler scheduler ;
124
+ private JobManager jobManager ;
99
125
100
126
@ Reference
101
127
private HealthChecker healthChecker ;
@@ -123,7 +149,7 @@ protected void modify(Config config) {
123
149
protected void deactivate () {
124
150
unscheduleBoot ();
125
151
unscheduleScripts ();
126
- booted .clear ();
152
+ bootedScripts .clear ();
127
153
instanceReady = null ;
128
154
}
129
155
@@ -137,8 +163,19 @@ public void onChange(List<ResourceChange> changes) {
137
163
@ Override
138
164
public void onEvent (Event event ) {
139
165
EventType eventType = EventType .of (event .getName ()).orElse (null );
140
- if (eventType == EventType .SCRIPT_SCHEDULER_BOOT ) {
141
- bootOnDemand ();
166
+ if (eventType == null ) {
167
+ return ;
168
+ }
169
+ switch (eventType ) {
170
+ case SCRIPT_SCHEDULER_BOOT :
171
+ bootOnDemand ();
172
+ break ;
173
+ case EXECUTOR_RESET :
174
+ reset ();
175
+ break ;
176
+ default :
177
+ // ignore else
178
+ break ;
142
179
}
143
180
}
144
181
@@ -149,7 +186,6 @@ public void bootOnDemand() {
149
186
LOG .info ("Automatic scripts booting on demand - job scheduled" );
150
187
}
151
188
152
- // TODO on AEMaaCS scheduler refuses to schedule job during activate
153
189
private void bootWhenInstanceUp () {
154
190
LOG .info ("Automatic scripts booting on instance up - job scheduling" );
155
191
unscheduleBoot ();
@@ -164,19 +200,35 @@ private void bootWhenScriptsChanged() {
164
200
LOG .info ("Automatic scripts booting on script changes - job scheduled" );
165
201
}
166
202
203
+ @ SuppressWarnings ("unchecked" )
167
204
private void unscheduleBoot () {
168
- scheduler .unschedule (BOOT_JOB_NAME );
205
+ Collection <ScheduledJobInfo > jobInfos = jobManager .getScheduledJobs (
206
+ JOB_TOPIC , -1 , Collections .singletonMap (JOB_PROP_TYPE , JobType .BOOT .name ()));
207
+ for (ScheduledJobInfo jobInfo : jobInfos ) {
208
+ jobInfo .unschedule ();
209
+ }
169
210
}
170
211
171
212
private void scheduleBoot () {
172
- scheduler .schedule (bootJob (), configureScheduleOptions (BOOT_JOB_NAME , scheduler .NOW ()));
213
+ JobBuilder jobBuilder = jobManager .createJob (JOB_TOPIC );
214
+ jobBuilder .properties (Collections .singletonMap (JOB_PROP_TYPE , JobType .BOOT .name ()));
215
+ JobBuilder .ScheduleBuilder scheduleBuilder = jobBuilder .schedule ();
216
+ scheduleBuilder .at (new Date (System .currentTimeMillis () + config .bootDelay ()));
217
+ scheduleBuilder .add ();
173
218
}
174
219
175
- private ScheduleOptions configureScheduleOptions (String name , ScheduleOptions options ) {
176
- options .name (name );
177
- options .canRunConcurrently (false );
178
- options .onSingleInstanceOnly (true );
179
- return options ;
220
+ @ Override
221
+ public JobResult process (Job job ) {
222
+ switch (JobType .of (job .getProperty (JOB_PROP_TYPE , String .class ))) {
223
+ case BOOT :
224
+ bootJob ();
225
+ break ;
226
+ case CRON :
227
+ String scriptPath = job .getProperty (JOB_PROP_SCRIPT_PATH , String .class );
228
+ cronJob (scriptPath );
229
+ break ;
230
+ }
231
+ return JobResult .OK ;
180
232
}
181
233
182
234
private ScheduleResult determineSchedule (Script script , ResourceResolver resourceResolver ) {
@@ -186,18 +238,16 @@ private ScheduleResult determineSchedule(Script script, ResourceResolver resourc
186
238
}
187
239
}
188
240
189
- private Job bootJob () {
190
- return context -> {
191
- LOG .info ("Automatic scripts booting - job started" );
192
- unscheduleScripts ();
193
- if (awaitInstanceHealthy (
194
- "Automatic scripts queueing and scheduling" ,
195
- config .healthCheckRetryCountBoot (),
196
- config .healthCheckRetryInterval ())) {
197
- queueAndScheduleScripts ();
198
- }
199
- LOG .info ("Automatic scripts booting - job finished" );
200
- };
241
+ private void bootJob () {
242
+ LOG .info ("Automatic scripts booting - job started" );
243
+ unscheduleScripts ();
244
+ if (awaitInstanceHealthy (
245
+ "Automatic scripts queueing and scheduling" ,
246
+ config .healthCheckRetryCountBoot (),
247
+ config .healthCheckRetryInterval ())) {
248
+ queueAndScheduleScripts ();
249
+ }
250
+ LOG .info ("Automatic scripts booting - job finished" );
201
251
}
202
252
203
253
private boolean checkInstanceReady () {
@@ -216,15 +266,13 @@ private boolean checkInstanceReady() {
216
266
return instanceReady ;
217
267
}
218
268
269
+ @ SuppressWarnings ("unchecked" )
219
270
private void unscheduleScripts () {
220
- for (String scriptPath : scheduled ) {
221
- try {
222
- scheduler .unschedule (scriptPath );
223
- } catch (Exception e ) {
224
- LOG .error ("Cron schedule script '{}' cannot be unscheduled!" , scriptPath , e );
225
- }
271
+ Collection <ScheduledJobInfo > jobInfos = jobManager .getScheduledJobs (
272
+ JOB_TOPIC , -1 , Collections .singletonMap (JOB_PROP_TYPE , JobType .CRON .name ()));
273
+ for (ScheduledJobInfo jobInfo : jobInfos ) {
274
+ jobInfo .unschedule ();
226
275
}
227
- scheduled .clear ();
228
276
}
229
277
230
278
private void queueAndScheduleScripts () {
@@ -257,11 +305,11 @@ private void queueAndScheduleScripts() {
257
305
258
306
private void queueBootScript (Script script , ResourceResolver resourceResolver ) {
259
307
String checksum = ChecksumUtils .calculate (script .getContent ());
260
- String previousChecksum = booted .get (script .getId ());
308
+ String previousChecksum = bootedScripts .get (script .getId ());
261
309
if (previousChecksum == null || !StringUtils .equals (previousChecksum , checksum )) {
262
310
if (checkScript (script , resourceResolver )) {
263
311
queueScript (script );
264
- booted .put (script .getId (), checksum );
312
+ bootedScripts .put (script .getId (), checksum );
265
313
LOG .info ("Boot script '{}' queued" , script .getId ());
266
314
} else {
267
315
LOG .info ("Boot script '{}' not eligible for queueing!" , script .getId ());
@@ -271,10 +319,14 @@ private void queueBootScript(Script script, ResourceResolver resourceResolver) {
271
319
272
320
private void scheduleCronScript (Script script , CronSchedule schedule ) {
273
321
if (StringUtils .isNotBlank (schedule .getExpression ())) {
274
- scheduler .schedule (
275
- cronJob (script .getPath ()),
276
- configureScheduleOptions (script .getPath (), scheduler .EXPR (schedule .getExpression ())));
277
- scheduled .add (script .getPath ());
322
+ JobBuilder jobBuilder = jobManager .createJob (JOB_TOPIC );
323
+ Map <String , Object > properties = new HashMap <>();
324
+ properties .put (JOB_PROP_TYPE , JobType .CRON .name ());
325
+ properties .put (JOB_PROP_SCRIPT_PATH , script .getPath ());
326
+ jobBuilder .properties (properties );
327
+ JobBuilder .ScheduleBuilder scheduleBuilder = jobBuilder .schedule ();
328
+ scheduleBuilder .cron (schedule .getExpression ());
329
+ scheduleBuilder .add ();
278
330
LOG .info (
279
331
"Cron schedule script '{}' scheduled with expression '{}'" ,
280
332
script .getId (),
@@ -284,31 +336,29 @@ private void scheduleCronScript(Script script, CronSchedule schedule) {
284
336
}
285
337
}
286
338
287
- private Job cronJob (String scriptPath ) {
288
- return context -> {
289
- LOG .info ("Cron schedule script '{}' - job started" , scriptPath );
290
- if (awaitInstanceHealthy (
291
- String .format ("Cron schedule script '%s' queueing" , scriptPath ),
292
- config .healthCheckRetryCountCron (),
293
- config .healthCheckRetryInterval ())) {
294
- try (ResourceResolver resourceResolver = ResolverUtils .contentResolver (resourceResolverFactory , null )) {
295
- ScriptRepository scriptRepository = new ScriptRepository (resourceResolver );
296
- Script script = scriptRepository .read (scriptPath ).orElse (null );
297
- if (script == null ) {
298
- LOG .error ("Cron schedule script '{}' not found in repository!" , scriptPath );
339
+ private void cronJob (String scriptPath ) {
340
+ LOG .info ("Cron schedule script '{}' - job started" , scriptPath );
341
+ if (awaitInstanceHealthy (
342
+ String .format ("Cron schedule script '%s' queueing" , scriptPath ),
343
+ config .healthCheckRetryCountCron (),
344
+ config .healthCheckRetryInterval ())) {
345
+ try (ResourceResolver resourceResolver = ResolverUtils .contentResolver (resourceResolverFactory , null )) {
346
+ ScriptRepository scriptRepository = new ScriptRepository (resourceResolver );
347
+ Script script = scriptRepository .read (scriptPath ).orElse (null );
348
+ if (script == null ) {
349
+ LOG .error ("Cron schedule script '{}' not found in repository!" , scriptPath );
350
+ } else {
351
+ if (checkScript (script , resourceResolver )) {
352
+ queueScript (script );
299
353
} else {
300
- if (checkScript (script , resourceResolver )) {
301
- queueScript (script );
302
- } else {
303
- LOG .info ("Cron schedule script '{}' not eligible for queueing!" , scriptPath );
304
- }
354
+ LOG .info ("Cron schedule script '{}' not eligible for queueing!" , scriptPath );
305
355
}
306
- } catch (LoginException e ) {
307
- LOG .error ("Cannot access repository while queueing cron schedule script '{}'!" , scriptPath , e );
308
356
}
357
+ } catch (LoginException e ) {
358
+ LOG .error ("Cannot access repository while queueing cron schedule script '{}'!" , scriptPath , e );
309
359
}
310
- LOG . info ( "Cron schedule script '{}' - job finished" , scriptPath );
311
- } ;
360
+ }
361
+ LOG . info ( "Cron schedule script '{}' - job finished" , scriptPath ) ;
312
362
}
313
363
314
364
private boolean checkScript (Script script , ResourceResolver resourceResolver ) {
@@ -387,4 +437,13 @@ private boolean awaitInstanceHealthy(String operation, long retryMaxCount, long
387
437
}
388
438
return true ;
389
439
}
440
+
441
+ public void reset () {
442
+ findJobs ().forEach (job -> jobManager .removeJobById (job .getId ()));
443
+ }
444
+
445
+ @ SuppressWarnings ("unchecked" )
446
+ private Stream <Job > findJobs () {
447
+ return jobManager .findJobs (JobManager .QueryType .ALL , JOB_TOPIC , -1 , Collections .emptyMap ()).stream ();
448
+ }
390
449
}
0 commit comments