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