32
32
33
33
import javax .annotation .PostConstruct ;
34
34
import javax .annotation .Resource ;
35
- import javax .ejb .ConcurrencyManagement ;
36
- import javax .ejb .ConcurrencyManagementType ;
37
35
import javax .ejb .Lock ;
38
36
import javax .ejb .LockType ;
39
37
import javax .ejb .NoSuchObjectLocalException ;
38
+ import javax .ejb .SessionContext ;
40
39
import javax .ejb .Singleton ;
41
40
import javax .ejb .Startup ;
42
41
import javax .ejb .Timeout ;
43
42
import javax .ejb .Timer ;
44
43
import javax .ejb .TimerConfig ;
45
44
import javax .ejb .TimerHandle ;
46
- import javax .ejb .TransactionManagement ;
47
- import javax .ejb .TransactionManagementType ;
48
- import javax .transaction .RollbackException ;
49
- import javax .transaction .UserTransaction ;
45
+ import javax .ejb .TransactionAttribute ;
46
+ import javax .ejb .TransactionAttributeType ;
50
47
51
48
import org .drools .core .time .JobHandle ;
52
49
import org .drools .core .time .impl .TimerJobInstance ;
58
55
59
56
@ Singleton
60
57
@ Startup
61
- @ ConcurrencyManagement (ConcurrencyManagementType .CONTAINER )
62
- @ TransactionManagement (TransactionManagementType .BEAN )
63
58
@ Lock (LockType .READ )
64
59
public class EJBTimerScheduler {
65
60
@@ -70,17 +65,19 @@ public class EJBTimerScheduler {
70
65
private static final Integer TIMER_RETRY_LIMIT = Integer .parseInt (System .getProperty ("org.kie.jbpm.timer.retry.limit" , "3" ));
71
66
72
67
private static final Integer OVERDUE_WAIT_TIME = Integer .parseInt (System .getProperty ("org.jbpm.overdue.timer.wait" , "20000" ));
68
+
69
+ private static final Integer OVERDUE_CHECK_TIME = Integer .parseInt (System .getProperty ("org.jbpm.overdue.timer.check" , "200" ));
73
70
74
71
private boolean useLocalCache = Boolean .parseBoolean (System .getProperty ("org.jbpm.ejb.timer.local.cache" , "false" ));
75
72
76
73
private ConcurrentMap <String , TimerJobInstance > localCache = new ConcurrentHashMap <String , TimerJobInstance >();
77
74
78
75
@ Resource
79
76
protected javax .ejb .TimerService timerService ;
80
-
77
+
81
78
@ Resource
82
- protected UserTransaction utx ;
83
-
79
+ protected SessionContext ctx ;
80
+
84
81
public void setUseLocalCache (boolean useLocalCache ) {
85
82
this .useLocalCache = useLocalCache ;
86
83
}
@@ -97,112 +94,78 @@ public void executeTimerJob(Timer timer) {
97
94
EjbTimerJob timerJob = (EjbTimerJob ) timer .getInfo ();
98
95
TimerJobInstance timerJobInstance = timerJob .getTimerJobInstance ();
99
96
logger .debug ("About to execute timer for job {}" , timerJob );
100
-
101
- String timerServiceId = ((EjbGlobalJobHandle ) timerJobInstance .getJobHandle ()).getDeploymentId ();
102
-
103
97
// handle overdue timers as ejb timer service might start before all deployments are ready
104
98
long time = 0 ;
105
- while (TimerServiceRegistry .getInstance ().get (timerServiceId ) == null ) {
106
- logger .debug ("waiting for timer service to be available, elapsed time {} ms" , time );
107
- try {
108
- Thread .sleep (500 );
109
- } catch (InterruptedException e ) {
110
- e .printStackTrace ();
111
- }
112
- time += 500 ;
113
-
114
- if (time > OVERDUE_WAIT_TIME ) {
115
- logger .debug ("No timer service found after waiting {} ms" , time );
116
- break ;
99
+ try {
100
+ while (TimerServiceRegistry .getInstance ().get (((EjbGlobalJobHandle ) timerJobInstance .getJobHandle ()).getDeploymentId ()) == null ) {
101
+ logger .debug ("waiting for timer service to be available, elapsed time {} ms" , time );
102
+ Thread .sleep (OVERDUE_CHECK_TIME );
103
+ time += OVERDUE_CHECK_TIME ;
104
+ if (time > OVERDUE_WAIT_TIME ) {
105
+ logger .debug ("No timer service found after waiting {} ms" , time );
106
+ break ;
107
+ }
117
108
}
118
- }
109
+ } catch (InterruptedException e ) {
110
+ logger .warn ("Thread has been interrupted" , e );
111
+ Thread .currentThread ().interrupt ();
112
+ }
119
113
try {
120
- transaction (this ::executeTimerJobInstance , timerJobInstance );
121
- } catch (SessionNotFoundException e ) {
122
- logger .warn ("Process instance is not found. More likely already completed. Timer {} won't be recovered" , timerJobInstance , e );
123
- removeUnrecoverableTimer (timerJob , timer );
114
+ invokeTransaction (this ::executeTimerJobInstance , timerJobInstance );
124
115
} catch (Exception e ) {
125
116
recoverTimerJobInstance (timerJob , timer , e );
126
117
}
127
118
}
128
119
129
120
private void executeTimerJobInstance (TimerJobInstance timerJobInstance ) throws Exception {
130
- try {
131
- ((Callable <?>) timerJobInstance ).call ();
132
- } catch (Exception e ) {
133
- throw e ;
134
- }
121
+ ((Callable <?>) timerJobInstance ).call ();
135
122
}
136
123
137
- private void removeUnrecoverableTimer (EjbTimerJob ejbTimerJob , Timer timer ) {
138
- try {
139
- Transaction <TimerJobInstance > tx = timerJobInstance -> {
140
- if (!this .removeJob (timerJobInstance .getJobHandle (), timer )) {
141
- logger .warn ("Session not found for timer {}. Timer could not removed." , ejbTimerJob .getTimerJobInstance ());
142
- }
143
- };
144
- transaction (tx , ejbTimerJob .getTimerJobInstance ());
145
- } catch (Exception e1 ) {
146
- logger .warn ("There was a problem during timer removal {}" , ejbTimerJob .getTimerJobInstance (), e1 );
147
- }
148
- }
149
-
150
- private void recoverTimerJobInstance (EjbTimerJob ejbTimerJob , Timer timer , Exception e ) {
151
- if (isSessionNotFound (e )) {
152
- logger .warn ("Trying to recover timer. Not possible due to process instance is not found. More likely already completed. Timer {} won't be recovered" , ejbTimerJob .getTimerJobInstance (), e );
124
+ private void recoverTimerJobInstance (EjbTimerJob ejbTimerJob , Timer timer , Exception cause ) {
125
+ Transaction <TimerJobInstance > tx ;
126
+ if (isSessionNotFound (cause )) {
153
127
// if session is not found means the process has already finished. In this case we just need to remove
154
128
// the timer and avoid any recovery as it should not trigger any more timers.
155
- removeUnrecoverableTimer (ejbTimerJob , timer );
156
- return ;
129
+ tx = timerJobInstance -> {
130
+ logger .warn ("Trying to recover timer. Not possible due to process instance is not found. More likely already completed. Timer {} won't be recovered" , timerJobInstance , cause );
131
+ if (!removeJob (timerJobInstance .getJobHandle (), timer )) {
132
+ logger .warn ("Session not found for timer {}. Timer could not removed." , timerJobInstance );
133
+ }
134
+ };
157
135
}
158
-
159
- if (ejbTimerJob .getTimerJobInstance ().getTrigger ().hasNextFireTime () != null ) {
136
+ else if (ejbTimerJob .getTimerJobInstance ().getTrigger ().hasNextFireTime () != null ) {
160
137
// this is an interval trigger. Problem here is that the timer scheduled by DefaultTimerJobInstance is lost
161
138
// because of the transaction, so we need to do this here.
162
- try {
163
-
164
- logger .warn ("Execution of time failed Interval Trigger failed. Skipping {}" , ejbTimerJob .getTimerJobInstance ());
165
- Transaction <TimerJobInstance > tx = timerJobInstance -> {
166
- if (this .removeJob (timerJobInstance .getJobHandle (), null )) {
167
- this .internalSchedule (timerJobInstance );
168
- } else {
169
- logger .debug ("Interval trigger {} was removed before rescheduling" , ejbTimerJob .getTimerJobInstance ());
170
- }
171
- };
172
- transaction (tx , ejbTimerJob .getTimerJobInstance ());
173
- } catch (Exception e1 ) {
174
- logger .warn ("Could not schedule the interval trigger {}" , ejbTimerJob .getTimerJobInstance (), e1 );
175
- }
176
- return ;
139
+ tx = timerJobInstance -> {
140
+ logger .warn ("Execution of time failed Interval Trigger failed. Skipping {}" , timerJobInstance );
141
+ if (removeJob (timerJobInstance .getJobHandle (), null )) {
142
+ internalSchedule (timerJobInstance );
143
+ } else {
144
+ logger .debug ("Interval trigger {} was removed before rescheduling" , timerJobInstance );
145
+ }
146
+ };
147
+ }
148
+ else {
149
+ // if there is not next date to be fired, we need to apply policy otherwise will be lost
150
+ tx = timerJobInstance -> {
151
+ logger .warn ("Execution of time failed. The timer will be retried {}" , timerJobInstance );
152
+ ZonedDateTime nextRetry = ZonedDateTime .now ().plus (TIMER_RETRY_INTERVAL , ChronoUnit .MILLIS );
153
+ EjbTimerJobRetry info = ejbTimerJob instanceof EjbTimerJobRetry ? ((EjbTimerJobRetry ) ejbTimerJob ).next () : new EjbTimerJobRetry (timerJobInstance );
154
+ if (TIMER_RETRY_LIMIT > 0 && info .getRetry () > TIMER_RETRY_LIMIT ) {
155
+ logger .warn ("The timer {} reached retry limit {}. It won't be retried again" , timerJobInstance , TIMER_RETRY_LIMIT );
156
+ } else {
157
+ TimerConfig config = new TimerConfig (info , true );
158
+ Timer newTimer = timerService .createSingleActionTimer (Date .from (nextRetry .toInstant ()), config );
159
+ ((GlobalJpaTimerJobInstance ) timerJobInstance ).setTimerInfo (newTimer .getHandle ());
160
+ ((GlobalJpaTimerJobInstance ) timerJobInstance ).setExternalTimerId (getPlatformTimerId (newTimer ));
161
+ }
162
+ };
177
163
}
178
-
179
- // if there is not next date to be fired, we need to apply policy otherwise will be lost
180
-
181
- logger .warn ("Execution of time failed. The timer will be retried {}" , ejbTimerJob .getTimerJobInstance ());
182
- Transaction <TimerJobInstance > operation = (instance ) -> {
183
- ZonedDateTime nextRetry = ZonedDateTime .now ().plus (TIMER_RETRY_INTERVAL , ChronoUnit .MILLIS );
184
- EjbTimerJobRetry info = null ;
185
- if (ejbTimerJob instanceof EjbTimerJobRetry ) {
186
- info = ((EjbTimerJobRetry ) ejbTimerJob ).next ();
187
- } else {
188
- info = new EjbTimerJobRetry (instance );
189
- }
190
- if (TIMER_RETRY_LIMIT > 0 && info .getRetry () > TIMER_RETRY_LIMIT ) {
191
- logger .warn ("The timer {} reached retry limit {}. It won't be retried again" , instance , TIMER_RETRY_LIMIT );
192
- return ;
193
- }
194
- TimerConfig config = new TimerConfig (info , true );
195
- Timer newTimer = timerService .createSingleActionTimer (Date .from (nextRetry .toInstant ()), config );
196
- TimerHandle handler = newTimer .getHandle ();
197
- ((GlobalJpaTimerJobInstance ) ejbTimerJob .getTimerJobInstance ()).setTimerInfo (handler );
198
- ((GlobalJpaTimerJobInstance ) ejbTimerJob .getTimerJobInstance ()).setExternalTimerId (getPlatformTimerId (newTimer ));
199
- };
200
164
try {
201
- transaction ( operation , ejbTimerJob .getTimerJobInstance ());
202
- } catch (Exception e1 ) {
203
- logger .error ("Failed to executed timer recovery {} " , e1 . getMessage (), e1 );
165
+ invokeTransaction ( tx , ejbTimerJob .getTimerJobInstance ());
166
+ } catch (Exception e ) {
167
+ logger .error ("Failed to executed timer recovery" , e );
204
168
}
205
-
206
169
}
207
170
208
171
private boolean isSessionNotFound (Exception e ) {
@@ -218,30 +181,16 @@ private boolean isSessionNotFound(Exception e) {
218
181
219
182
@ FunctionalInterface
220
183
private interface Transaction <I > {
221
-
222
184
void doWork (I item ) throws Exception ;
223
185
}
224
186
225
- private <I > void transaction (Transaction <I > operation , I item ) throws Exception {
226
- try {
227
- utx .begin ();
228
- operation .doWork (item );
229
- utx .commit ();
230
- } catch (RollbackException e ) {
231
- logger .warn ("Transaction was rolled back for {} with status {}" , item , utx .getStatus ());
232
- if (utx .getStatus () == javax .transaction .Status .STATUS_ACTIVE ) {
233
- utx .rollback ();
234
- }
235
- throw new RuntimeException ("jbpm timer has been rolledback" , e );
236
- } catch (Exception e ) {
237
- try {
238
- utx .rollback ();
239
- } catch (Exception re ) {
240
- logger .error ("transaction could not be rolled back" , re );
241
- }
242
-
243
- throw e ;
244
- }
187
+ @ TransactionAttribute (value = TransactionAttributeType .REQUIRES_NEW )
188
+ public <I > void transaction (Transaction <I > operation , I item ) throws Exception {
189
+ operation .doWork (item );
190
+ }
191
+
192
+ private <I > void invokeTransaction (Transaction <I > operation , I item ) throws Exception {
193
+ ctx .getBusinessObject (EJBTimerScheduler .class ).transaction (operation ,item );
245
194
}
246
195
247
196
public void internalSchedule (TimerJobInstance timerJobInstance ) {
0 commit comments