1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.krad.uif.lifecycle;
17
18 import java.util.Deque;
19 import java.util.IdentityHashMap;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Queue;
24 import java.util.concurrent.Callable;
25 import java.util.concurrent.ConcurrentLinkedQueue;
26 import java.util.concurrent.LinkedBlockingDeque;
27 import java.util.concurrent.ThreadFactory;
28 import java.util.concurrent.ThreadPoolExecutor;
29 import java.util.concurrent.TimeUnit;
30
31 import org.apache.log4j.Logger;
32 import org.kuali.rice.core.api.config.property.ConfigContext;
33 import org.kuali.rice.core.api.exception.RiceRuntimeException;
34 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
35 import org.kuali.rice.krad.uif.freemarker.LifecycleRenderingContext;
36 import org.kuali.rice.krad.uif.service.ViewHelperService;
37 import org.kuali.rice.krad.uif.util.LifecycleElement;
38 import org.kuali.rice.krad.uif.util.ProcessLogger;
39 import org.kuali.rice.krad.uif.util.RecycleUtils;
40 import org.kuali.rice.krad.uif.view.DefaultExpressionEvaluator;
41 import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
42 import org.kuali.rice.krad.uif.view.ExpressionEvaluatorFactory;
43 import org.kuali.rice.krad.util.GlobalVariables;
44 import org.kuali.rice.krad.util.KRADConstants;
45
46
47
48
49
50
51
52 public final class AsynchronousViewLifecycleProcessor extends ViewLifecycleProcessorBase {
53
54 private static final Logger LOG = Logger.getLogger(AsynchronousViewLifecycleProcessor.class);
55
56 private static final ThreadFactory LIFECYCLE_THREAD_FACTORY = new LifecycleThreadFactory();
57
58 private static final ThreadPoolExecutor LIFECYCLE_EXECUTOR = new ThreadPoolExecutor(
59 getMinThreads(), getMaxThreads(), getTimeout(), TimeUnit.MILLISECONDS,
60 new LinkedBlockingDeque<Runnable>(), LIFECYCLE_THREAD_FACTORY);
61
62 private static final Deque<AsynchronousLifecyclePhase> PENDING_PHASE_QUEUE =
63 new LinkedList<AsynchronousLifecyclePhase>();
64
65 private static final ThreadLocal<AsynchronousLifecyclePhase> ACTIVE_PHASE =
66 new ThreadLocal<AsynchronousLifecyclePhase>();
67
68 private static final Map<LifecycleElement, AsynchronousLifecyclePhase> BUSY_ELEMENTS =
69 new IdentityHashMap<LifecycleElement, AsynchronousLifecyclePhase>();
70
71 private static Integer minThreads;
72 private static Integer maxThreads;
73 private static Long timeout;
74
75 private final Queue<LifecycleRenderingContext> renderingContextPool =
76 ViewLifecycle.isRenderInLifecycle() ? new ConcurrentLinkedQueue<LifecycleRenderingContext>() : null;
77 private final Queue<ExpressionEvaluator> expressionEvaluatorPool =
78 new ConcurrentLinkedQueue<ExpressionEvaluator>();
79
80 private Throwable error;
81
82
83
84
85
86
87
88
89
90
91
92 public static int getMinThreads() {
93 if (minThreads == null) {
94 String propStr = ConfigContext.getCurrentContextConfig().getProperty(
95 KRADConstants.ConfigParameters.KRAD_VIEW_LIFECYCLE_MINTHREADS);
96 minThreads = propStr == null ? 4 : Integer.parseInt(propStr);
97 }
98
99 return minThreads;
100 }
101
102
103
104
105
106
107
108
109
110
111
112 public static int getMaxThreads() {
113 if (maxThreads == null) {
114 String propStr = ConfigContext.getCurrentContextConfig().getProperty(
115 KRADConstants.ConfigParameters.KRAD_VIEW_LIFECYCLE_MAXTHREADS);
116 maxThreads = propStr == null ? 48 : Integer.parseInt(propStr);
117 }
118
119 return maxThreads;
120 }
121
122
123
124
125
126
127
128
129
130
131
132 public static long getTimeout() {
133 if (timeout == null) {
134 String propStr = ConfigContext.getCurrentContextConfig().getProperty(
135 KRADConstants.ConfigParameters.KRAD_VIEW_LIFECYCLE_TIMEOUT);
136 timeout = propStr == null ? 30000 : Long.parseLong(propStr);
137 }
138
139 return timeout;
140 }
141
142
143
144
145
146
147 AsynchronousViewLifecycleProcessor(ViewLifecycle lifecycle) {
148 super(lifecycle);
149 }
150
151
152
153
154
155
156 private static class LifecycleThreadFactory implements ThreadFactory {
157
158 private static final ThreadGroup GROUP = new ThreadGroup("krad-lifecycle-group");
159
160 private int sequenceNumber = 0;
161
162 @Override
163 public Thread newThread(Runnable r) {
164 return new Thread(GROUP, r, "krad-lifecycle("
165 + Integer.toString(++sequenceNumber) + ")");
166 }
167 }
168
169
170
171
172 @Override
173 public ViewLifecyclePhase getActivePhase() {
174 AsynchronousLifecyclePhase aphase = ACTIVE_PHASE.get();
175
176 if (aphase == null) {
177 throw new IllegalStateException("No phase worker is active on this thread");
178 }
179
180 ViewLifecyclePhase phase = aphase.phase;
181 if (phase == null) {
182 throw new IllegalStateException("No lifecycle phase is active on this thread");
183 }
184
185 return phase;
186 }
187
188
189
190
191 @Override
192 void setActivePhase(ViewLifecyclePhase phase) {
193 AsynchronousLifecyclePhase aphase = ACTIVE_PHASE.get();
194
195 if (aphase == null) {
196 throw new IllegalStateException("No phase worker is active on this thread");
197 }
198
199 if (phase == null) {
200
201 return;
202 }
203
204 if (aphase.phase != phase) {
205 throw new IllegalStateException(
206 "Another lifecycle phase is already active on this thread "
207 + aphase.phase + ", setting " + phase);
208 }
209
210 aphase.phase = phase;
211 };
212
213
214
215
216 @Override
217 public LifecycleRenderingContext getRenderingContext() {
218 if (!ViewLifecycle.isRenderInLifecycle()) {
219 return null;
220 }
221
222 AsynchronousLifecyclePhase aphase = ACTIVE_PHASE.get();
223
224 if (aphase == null) {
225 throw new IllegalStateException("No phase worker is active on this thread");
226 }
227
228
229 LifecycleRenderingContext renderContext = aphase.renderingContext;
230 if (renderContext != null) {
231 return renderContext;
232 }
233
234
235 renderContext = renderingContextPool.poll();
236 if (renderContext == null) {
237
238 ViewLifecycle lifecycle = getLifecycle();
239 renderContext = new LifecycleRenderingContext(
240 lifecycle.model, lifecycle.request, lifecycle.response);
241 }
242
243
244 List<String> viewTemplates = ViewLifecycle.getView().getViewTemplates();
245 synchronized (viewTemplates) {
246 for (String viewTemplate : viewTemplates) {
247 renderContext.importTemplate(viewTemplate);
248 }
249 }
250
251
252 aphase.renderingContext = renderContext;
253 return renderContext;
254 }
255
256
257
258
259 @Override
260 public ExpressionEvaluator getExpressionEvaluator() {
261 AsynchronousLifecyclePhase aphase = ACTIVE_PHASE.get();
262
263
264 ExpressionEvaluator expressionEvaluator = aphase == null ? null : aphase.expressionEvaluator;
265 if (expressionEvaluator != null) {
266 return expressionEvaluator;
267 }
268
269
270 expressionEvaluator = expressionEvaluatorPool.poll();
271 if (expressionEvaluator == null) {
272
273 ExpressionEvaluatorFactory expressionEvaluatorFactory;
274 ViewHelperService helper = ViewLifecycle.getHelper();
275 if (helper != null) {
276 expressionEvaluatorFactory = helper.getExpressionEvaluatorFactory();
277 } else {
278 expressionEvaluatorFactory = KRADServiceLocatorWeb.getExpressionEvaluatorFactory();
279 }
280
281 if (expressionEvaluatorFactory == null) {
282 expressionEvaluator = new DefaultExpressionEvaluator();
283 } else {
284 expressionEvaluator = expressionEvaluatorFactory.createExpressionEvaluator();
285 }
286
287 if (ViewLifecycle.isActive()) {
288 try {
289 expressionEvaluator.initializeEvaluationContext(ViewLifecycle.getModel());
290 } catch (IllegalStateException e) {
291
292 LOG.warn("Model is not available", e);
293 }
294 }
295 }
296
297
298 if (aphase != null) {
299 aphase.expressionEvaluator = expressionEvaluator;
300 }
301
302 return expressionEvaluator;
303 }
304
305
306
307
308 @Override
309 public void pushPendingPhase(ViewLifecyclePhase phase) {
310 AsynchronousLifecyclePhase aphase = getAsynchronousPhase(phase);
311 if (phase.getStartViewStatus().equals(phase.getElement().getViewStatus())) {
312 synchronized (BUSY_ELEMENTS) {
313 BUSY_ELEMENTS.put(phase.getElement(), aphase);
314 }
315 }
316
317 synchronized (PENDING_PHASE_QUEUE) {
318 PENDING_PHASE_QUEUE.push(aphase);
319 PENDING_PHASE_QUEUE.notify();
320 }
321
322 spawnWorkers();
323 }
324
325
326
327
328 @Override
329 public void offerPendingPhase(ViewLifecyclePhase phase) {
330 AsynchronousLifecyclePhase aphase = getAsynchronousPhase(phase);
331 if (phase.getStartViewStatus().equals(phase.getElement().getViewStatus())) {
332 synchronized (BUSY_ELEMENTS) {
333 BUSY_ELEMENTS.put(phase.getElement(), aphase);
334 }
335 }
336
337 synchronized (PENDING_PHASE_QUEUE) {
338 PENDING_PHASE_QUEUE.offer(aphase);
339 PENDING_PHASE_QUEUE.notify();
340 }
341
342 spawnWorkers();
343 }
344
345
346
347
348
349
350 @Override
351 public void performPhase(ViewLifecyclePhase initialPhase) {
352 if (error != null) {
353 throw new RiceRuntimeException("Error performing view lifecycle", error);
354 }
355
356 long now = System.currentTimeMillis();
357 try {
358 AsynchronousLifecyclePhase aphase = getAsynchronousPhase(initialPhase);
359 aphase.initial = true;
360
361 synchronized (PENDING_PHASE_QUEUE) {
362 PENDING_PHASE_QUEUE.offer(aphase);
363 PENDING_PHASE_QUEUE.notify();
364 }
365
366 spawnWorkers();
367
368 while (System.currentTimeMillis() - now < getTimeout() &&
369 error == null && !initialPhase.isComplete()) {
370 synchronized (initialPhase) {
371
372 if (!initialPhase.isComplete()) {
373 LOG.info("Waiting for view lifecycle " + initialPhase);
374 initialPhase.wait(Math.min(5000L, getTimeout()));
375 }
376 }
377 }
378
379 if (error != null) {
380 throw new IllegalStateException("Error in lifecycle", error);
381 }
382
383 if (!initialPhase.isComplete()) {
384 error = new IllegalStateException("Time out waiting for lifecycle");
385 throw (IllegalStateException) error;
386 }
387
388 } catch (InterruptedException e) {
389 throw new IllegalStateException("Interrupted waiting for view lifecycle", e);
390 }
391 }
392
393
394
395
396
397
398
399
400 private AsynchronousLifecyclePhase getAsynchronousPhase(ViewLifecyclePhase phase) {
401 AsynchronousLifecyclePhase rv = RecycleUtils.getRecycledInstance(AsynchronousLifecyclePhase.class);
402 if (rv == null) {
403 rv = new AsynchronousLifecyclePhase();
404 }
405
406 rv.processor = this;
407 rv.globalVariables = GlobalVariables.getCurrentGlobalVariables();
408 rv.phase = phase;
409
410 return rv;
411 }
412
413
414
415
416
417
418
419 private static void recyclePhase(AsynchronousLifecyclePhase aphase) {
420 if (aphase.initial) {
421 return;
422 }
423
424 assert aphase.renderingContext == null;
425 aphase.processor = null;
426 aphase.phase = null;
427 aphase.globalVariables = null;
428 aphase.expressionEvaluator = null;
429 RecycleUtils.recycle(aphase);
430 }
431
432
433
434
435 private static void spawnWorkers() {
436 int active = LIFECYCLE_EXECUTOR.getActiveCount();
437 if (active < LIFECYCLE_EXECUTOR.getCorePoolSize() ||
438 (active * 16 < PENDING_PHASE_QUEUE.size() &&
439 active < LIFECYCLE_EXECUTOR.getMaximumPoolSize())) {
440 LIFECYCLE_EXECUTOR.submit(new AsynchronousLifecycleWorker());
441 }
442 }
443
444
445
446
447
448
449 private static class AsynchronousLifecyclePhase {
450 private boolean initial;
451 private GlobalVariables globalVariables;
452 private AsynchronousViewLifecycleProcessor processor;
453 private ViewLifecyclePhase phase;
454 private LifecycleRenderingContext renderingContext;
455 private ExpressionEvaluator expressionEvaluator;
456 }
457
458
459
460
461
462
463 private static class PhaseWorkerCall implements Callable<Void> {
464
465 @Override
466 public Void call() throws Exception {
467 while (!PENDING_PHASE_QUEUE.isEmpty()) {
468 AsynchronousLifecyclePhase aphase;
469 synchronized (PENDING_PHASE_QUEUE) {
470 aphase = PENDING_PHASE_QUEUE.poll();
471 }
472
473 if (aphase == null) {
474 continue;
475 }
476
477 AsynchronousViewLifecycleProcessor processor = aphase.processor;
478 ViewLifecyclePhase phase = aphase.phase;
479
480 if (processor.error != null) {
481 synchronized (phase) {
482 phase.notifyAll();
483 }
484
485 continue;
486 }
487
488 LifecycleElement element = phase.getElement();
489 AsynchronousLifecyclePhase busyPhase = BUSY_ELEMENTS.get(element);
490 if (busyPhase != null && busyPhase != aphase) {
491
492 synchronized (PENDING_PHASE_QUEUE) {
493 PENDING_PHASE_QUEUE.offer(aphase);
494 }
495
496 continue;
497 }
498
499 try {
500 assert ACTIVE_PHASE.get() == null;
501 ACTIVE_PHASE.set(aphase);
502 ViewLifecycle.setProcessor(aphase.processor);
503 GlobalVariables.injectGlobalVariables(aphase.globalVariables);
504
505 phase.run();
506
507 } catch (Throwable t) {
508 processor.error = t;
509
510 ViewLifecyclePhase topPhase = phase;
511 while (topPhase.getPredecessor() != null) {
512 topPhase = topPhase.getPredecessor();
513 }
514
515 synchronized (topPhase) {
516 topPhase.notifyAll();
517 }
518 } finally {
519 ACTIVE_PHASE.remove();
520 LifecycleRenderingContext renderingContext = aphase.renderingContext;
521 aphase.renderingContext = null;
522 if (renderingContext != null && aphase.processor != null) {
523 aphase.processor.renderingContextPool.offer(renderingContext);
524 }
525
526 ExpressionEvaluator expressionEvaluator = aphase.expressionEvaluator;
527 aphase.expressionEvaluator = null;
528 if (expressionEvaluator != null && aphase.processor != null) {
529 aphase.processor.expressionEvaluatorPool.offer(expressionEvaluator);
530 }
531
532 synchronized (BUSY_ELEMENTS) {
533 BUSY_ELEMENTS.remove(element);
534 }
535 GlobalVariables.popGlobalVariables();
536 ViewLifecycle.setProcessor(null);
537 }
538
539 recyclePhase(aphase);
540 }
541 return null;
542 }
543
544 }
545
546
547
548
549
550
551
552 private static class AsynchronousLifecycleWorker implements Runnable {
553
554 @Override
555 public void run() {
556 try {
557 PhaseWorkerCall call = new PhaseWorkerCall();
558 do {
559 if (PENDING_PHASE_QUEUE.isEmpty()) {
560 synchronized (PENDING_PHASE_QUEUE) {
561 PENDING_PHASE_QUEUE.wait(15000L);
562 }
563 } else if (ViewLifecycle.isTrace()) {
564 ProcessLogger.follow(
565 "view-lifecycle", "KRAD lifecycle worker", call);
566 } else {
567 call.call();
568 }
569 } while (LIFECYCLE_EXECUTOR.getActiveCount() <= getMinThreads());
570 } catch (Throwable t) {
571 LOG.fatal("Fatal error in View Lifecycle worker", t);
572 }
573 }
574
575 }
576
577 }