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.ArrayList;
19 import java.util.HashSet;
20 import java.util.LinkedHashSet;
21 import java.util.LinkedList;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Queue;
25
26 import org.apache.commons.lang.StringUtils;
27 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
28 import org.kuali.rice.krad.uif.UifConstants;
29 import org.kuali.rice.krad.uif.component.Component;
30 import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle.LifecycleEvent;
31 import org.kuali.rice.krad.uif.util.CopyUtils;
32 import org.kuali.rice.krad.uif.util.LifecycleElement;
33 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
34 import org.kuali.rice.krad.uif.util.ProcessLogger;
35 import org.kuali.rice.krad.uif.util.RecycleUtils;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
38
39
40
41
42
43
44 public abstract class ViewLifecyclePhaseBase implements ViewLifecyclePhase {
45 private final Logger LOG = LoggerFactory.getLogger(ViewLifecyclePhaseBase.class);
46
47 private LifecycleElement element;
48 private Component parent;
49 private String viewPath;
50 private String path;
51 private int depth;
52
53 private List<String> refreshPaths;
54
55 private ViewLifecyclePhase predecessor;
56 private ViewLifecyclePhase nextPhase;
57
58 private boolean processed;
59 private boolean completed;
60
61 private HashSet<String> pendingSuccessors = new LinkedHashSet<String>();
62
63 private ViewLifecycleTask<?> currentTask;
64
65 private List<ViewLifecycleTask<?>> tasks;
66 private List<ViewLifecycleTask<?>> skipLifecycleTasks;
67
68
69
70
71 public void recycle() {
72 trace("recycle");
73
74 element = null;
75 path = null;
76 viewPath = null;
77 depth = 0;
78 predecessor = null;
79 nextPhase = null;
80 processed = false;
81 completed = false;
82 refreshPaths = null;
83 pendingSuccessors = new LinkedHashSet<String>();
84 }
85
86
87
88
89 @Override
90 public void prepare(LifecycleElement element, Component parent, String parentPath, List<String> refreshPaths) {
91 this.path = parentPath;
92
93 String parentViewPath = parent == null ? null : parent.getViewPath();
94 if (StringUtils.isEmpty(parentViewPath)) {
95 this.viewPath = path;
96 } else {
97 this.viewPath = parentViewPath + '.' + path;
98 }
99
100 this.element = CopyUtils.unwrap(element);
101 this.parent = parent;
102 this.refreshPaths = refreshPaths;
103
104 trace("prepare");
105 }
106
107
108
109
110
111
112
113
114 @Override
115 public final void run() {
116 try {
117 ViewLifecycleProcessorBase processor = (ViewLifecycleProcessorBase) ViewLifecycle.getProcessor();
118
119 validateBeforeProcessing();
120
121 boolean skipLifecycle = shouldSkipLifecycle();
122
123 String ntracePrefix = null;
124 String ntraceSuffix = null;
125 try {
126 if (ViewLifecycle.isTrace() && ProcessLogger.isTraceActive()) {
127 ntracePrefix = "lc-" + getStartViewStatus() + "-" + getEndViewStatus() + ":";
128 ntraceSuffix =
129 ":" + getElement().getClass().getSimpleName() + (getElement().isRender() ? ":render" :
130 ":no-render");
131
132 ProcessLogger.ntrace(ntracePrefix, ntraceSuffix, 1000);
133 ProcessLogger.countBegin(ntracePrefix + ntraceSuffix);
134 }
135
136 String viewStatus = element.getViewStatus();
137 if (viewStatus != null && !viewStatus.equals(getStartViewStatus())) {
138 trace("dup " + getStartViewStatus() + " " + getEndViewStatus() + " " + viewStatus);
139 }
140
141 processor.setActivePhase(this);
142
143 trace("path-update " + element.getViewPath());
144
145 element.setViewPath(getViewPath());
146 element.getPhasePathMapping().put(getViewPhase(), getViewPath());
147
148 List<ViewLifecycleTask<?>> pendingTasks = skipLifecycle ? skipLifecycleTasks : tasks;
149
150 StringBuilder trace;
151 if (ViewLifecycle.isTrace() && LOG.isDebugEnabled()) {
152 trace = new StringBuilder("Tasks");
153 } else {
154 trace = null;
155 }
156
157 for (ViewLifecycleTask<?> task : pendingTasks) {
158 if (trace != null) {
159 trace.append("\n ").append(task);
160 }
161
162 if (!task.getElementType().isInstance(element)) {
163 if (trace != null) {
164 trace.append(" skip");
165 }
166 continue;
167 }
168
169 if (trace != null) {
170 trace.append(" run");
171 }
172 currentTask = task;
173 task.run();
174 currentTask = null;
175 }
176
177 if (trace != null) {
178 LOG.debug(trace.toString());
179 }
180
181 element.setViewStatus(getEndViewStatus());
182 processed = true;
183
184 } finally {
185 processor.setActivePhase(null);
186
187 if (ViewLifecycle.isTrace() && ProcessLogger.isTraceActive()) {
188 ProcessLogger.countEnd(ntracePrefix + ntraceSuffix,
189 getElement().getClass() + " " + getElement().getId());
190 }
191 }
192
193 if (skipLifecycle) {
194 notifyCompleted();
195 } else {
196 assert pendingSuccessors.isEmpty() : pendingSuccessors;
197
198 Queue<ViewLifecyclePhase> successors = new LinkedList<ViewLifecyclePhase>();
199
200 initializeSuccessors(successors);
201 processSuccessors(successors);
202 }
203 } catch (Throwable t) {
204 trace("error");
205 LOG.warn("Error in lifecycle phase " + this, t);
206
207 if (t instanceof RuntimeException) {
208 throw (RuntimeException) t;
209 } else if (t instanceof Error) {
210 throw (Error) t;
211 } else {
212 throw new IllegalStateException("Unexpected error in lifecycle phase " + this, t);
213 }
214 }
215 }
216
217
218
219
220
221
222
223
224
225
226
227
228 protected boolean shouldSkipLifecycle() {
229 if (StringUtils.isBlank(getViewPath())) {
230 return false;
231 }
232
233
234 boolean isPreProcessPhase = getViewPhase().equals(UifConstants.ViewPhases.PRE_PROCESS);
235
236
237 boolean isRefreshComponent = ViewLifecycle.isRefreshComponent(getViewPhase(), getViewPath());
238
239
240 boolean includesRefreshComponent = false;
241 if (StringUtils.isNotBlank(ViewLifecycle.getRefreshComponentPhasePath(getViewPhase()))) {
242 includesRefreshComponent = ViewLifecycle.getRefreshComponentPhasePath(getViewPhase()).startsWith(
243 getViewPath());
244 }
245
246 boolean skipLifecycle = false;
247 if (!(isPreProcessPhase || isRefreshComponent || includesRefreshComponent)) {
248
249 skipLifecycle = element.skipLifecycle();
250 }
251
252 return skipLifecycle;
253 }
254
255
256
257
258
259
260 protected void validateBeforeProcessing() {
261 if (processed) {
262 throw new IllegalStateException("Lifecycle phase has already been processed " + this);
263 }
264
265 if (predecessor != null && !predecessor.isProcessed()) {
266 throw new IllegalStateException("Predecessor phase has not completely processed " + this);
267 }
268
269 if (!ViewLifecycle.isActive()) {
270 throw new IllegalStateException("No view lifecyle is not active on the current thread");
271 }
272
273 if (LOG.isDebugEnabled()) {
274 trace("ready " + getStartViewStatus() + " -> " + getEndViewStatus());
275 }
276 }
277
278
279
280
281
282
283
284 protected void processSuccessors(Queue<ViewLifecyclePhase> successors) {
285 for (ViewLifecyclePhase successor : successors) {
286 if (!pendingSuccessors.add(successor.getParentPath())) {
287 ViewLifecycle.reportIllegalState("Already pending " + successor + "\n" + this);
288 }
289 }
290
291 trace("processed " + pendingSuccessors);
292
293 if (pendingSuccessors.isEmpty()) {
294 notifyCompleted();
295 } else {
296 for (ViewLifecyclePhase successor : successors) {
297 assert successor.getPredecessor() == null : this + " " + successor;
298 successor.setPredecessor(this);
299
300 if (successor instanceof ViewLifecyclePhaseBase) {
301 ((ViewLifecyclePhaseBase) successor).trace("succ-pend");
302 }
303
304 ViewLifecycle.getProcessor().offerPendingPhase(successor);
305 }
306 }
307 }
308
309
310
311
312 @Override
313 public void setNextPhase(ViewLifecyclePhase nextPhase) {
314 if (this.nextPhase != null) {
315 throw new IllegalStateException("Next phase is already set " + nextPhase + "\n" + this);
316 }
317
318 if (nextPhase == null || !getEndViewStatus().equals(nextPhase.getStartViewStatus())) {
319 throw new IllegalStateException(
320 "Next phase is invalid for end phase " + getEndViewStatus() + " found " + nextPhase
321 .getStartViewStatus());
322 }
323
324 this.nextPhase = nextPhase;
325 trace("next-phase");
326 }
327
328
329
330
331
332
333 public void setTasks(List<ViewLifecycleTask<?>> tasks) {
334 for (ViewLifecycleTask<?> task : tasks) {
335 assert task.getElementState() == null : task.getElementState() + "\n" + this;
336 task.setElementState(this);
337 }
338
339 this.tasks = tasks;
340 }
341
342
343
344
345
346
347 public void setSkipLifecycleTasks(List<ViewLifecycleTask<?>> skipLifecycleTasks) {
348 for (ViewLifecycleTask<?> task : skipLifecycleTasks) {
349 assert task.getElementState() == null : task.getElementState() + "\n" + this;
350 task.setElementState(this);
351 }
352
353 this.skipLifecycleTasks = skipLifecycleTasks;
354 }
355
356
357
358
359
360
361
362
363
364
365
366 protected void initializeSuccessors(Queue<ViewLifecyclePhase> successors) {
367 if (ViewLifecycle.isRefreshLifecycle() && (refreshPaths != null)) {
368 String currentPath = getViewPath();
369
370 boolean withinRefreshComponent = currentPath.startsWith(ViewLifecycle.getRefreshComponentPhasePath(
371 getViewPhase()));
372 if (withinRefreshComponent) {
373 initializeAllLifecycleSuccessors(successors);
374 } else if (refreshPaths.contains(currentPath) || StringUtils.isBlank(currentPath)) {
375 initializeRefreshPathSuccessors(successors);
376 }
377
378 return;
379 }
380
381 initializeAllLifecycleSuccessors(successors);
382 }
383
384
385
386
387 @Override
388 public void setRefreshPaths(List<String> refreshPaths) {
389 this.refreshPaths = refreshPaths;
390 }
391
392 @Override
393 public List<String> getRefreshPaths() {
394 return this.refreshPaths;
395 }
396
397
398
399
400
401
402 protected void initializeRefreshPathSuccessors(Queue<ViewLifecyclePhase> successors) {
403 LifecycleElement element = getElement();
404
405 String nestedPathPrefix;
406 Component nestedParent;
407 if (element instanceof Component) {
408 nestedParent = (Component) element;
409 nestedPathPrefix = "";
410 } else {
411 nestedParent = getParent();
412 nestedPathPrefix = getParentPath() + ".";
413 }
414
415 List<String> nestedProperties = getNestedPropertiesForRefreshPath();
416
417 for (String nestedProperty : nestedProperties) {
418 String nestedPath = nestedPathPrefix + nestedProperty;
419
420 LifecycleElement nestedElement = ObjectPropertyUtils.getPropertyValue(element, nestedProperty);
421 if (nestedElement != null) {
422 ViewLifecyclePhase nestedPhase = initializeSuccessor(nestedElement, nestedPath, nestedParent);
423 successors.add(nestedPhase);
424 }
425 }
426 }
427
428
429
430
431
432
433
434 protected List<String> getNestedPropertiesForRefreshPath() {
435 List<String> nestedProperties = new ArrayList<String>();
436
437 String currentPath = getViewPath();
438 if (currentPath == null) {
439 currentPath = "";
440 }
441
442 if (StringUtils.isNotBlank(currentPath)) {
443 currentPath += ".";
444 }
445
446
447
448 for (String refreshPath : refreshPaths) {
449 if (!refreshPath.startsWith(currentPath)) {
450 continue;
451 }
452
453 String nestedProperty = StringUtils.substringAfter(refreshPath, currentPath);
454
455 if (StringUtils.isBlank(nestedProperty)) {
456 continue;
457 }
458
459 if (StringUtils.contains(nestedProperty, ".")) {
460 nestedProperty = StringUtils.substringBefore(nestedProperty, ".");
461 }
462
463 if (!nestedProperties.contains(nestedProperty)) {
464 nestedProperties.add(nestedProperty);
465 }
466 }
467
468 return nestedProperties;
469 }
470
471
472
473
474
475
476 protected void initializeAllLifecycleSuccessors(Queue<ViewLifecyclePhase> successors) {
477 LifecycleElement element = getElement();
478
479 String nestedPathPrefix;
480 Component nestedParent;
481 if (element instanceof Component) {
482 nestedParent = (Component) element;
483 nestedPathPrefix = "";
484 } else {
485 nestedParent = getParent();
486 nestedPathPrefix = getParentPath() + ".";
487 }
488
489 for (Map.Entry<String, LifecycleElement> nestedElementEntry : ViewLifecycleUtils.getElementsForLifecycle(
490 element, getViewPhase()).entrySet()) {
491 String nestedPath = nestedPathPrefix + nestedElementEntry.getKey();
492 LifecycleElement nestedElement = nestedElementEntry.getValue();
493
494 if (nestedElement != null && !getEndViewStatus().equals(nestedElement.getViewStatus())) {
495 ViewLifecyclePhase nestedPhase = initializeSuccessor(nestedElement, nestedPath, nestedParent);
496 successors.offer(nestedPhase);
497 }
498 }
499 }
500
501
502
503
504
505
506
507 protected void verifyCompleted() {
508 }
509
510
511
512
513
514
515
516
517
518
519 protected ViewLifecyclePhase initializeSuccessor(LifecycleElement nestedElement, String nestedPath,
520 Component nestedParent) {
521 ViewLifecyclePhase successorPhase = KRADServiceLocatorWeb.getViewLifecyclePhaseBuilder().buildPhase(
522 getViewPhase(), nestedElement, nestedParent, nestedPath, this.refreshPaths);
523
524 return successorPhase;
525 }
526
527
528
529
530 @Override
531 public final void notifyCompleted() {
532 trace("complete");
533
534 completed = true;
535
536 LifecycleEvent event = getEventToNotify();
537 if (event != null) {
538 ViewLifecycle.getActiveLifecycle().invokeEventListeners(event, ViewLifecycle.getView(),
539 ViewLifecycle.getModel(), element);
540 }
541
542 element.notifyCompleted(this);
543
544 if (nextPhase != null) {
545 assert nextPhase.getPredecessor() == null : this + " " + nextPhase;
546
547
548
549 if (predecessor != null) {
550
551
552
553 nextPhase.setPredecessor(predecessor);
554 } else {
555
556
557 nextPhase.setPredecessor(this);
558 synchronized (pendingSuccessors) {
559 pendingSuccessors.add(nextPhase.getParentPath());
560 }
561 }
562
563 ViewLifecycle.getProcessor().pushPendingPhase(nextPhase);
564 return;
565 }
566
567 synchronized (this) {
568 if (predecessor != null) {
569 synchronized (predecessor) {
570 predecessor.removePendingSuccessor(getParentPath());
571 if (!predecessor.hasPendingSuccessors()) {
572 predecessor.notifyCompleted();
573 }
574
575 recycle();
576 RecycleUtils.recycle(getViewPhase(), this, ViewLifecyclePhase.class);
577 }
578 } else {
579 trace("notify");
580 notifyAll();
581 }
582 }
583 }
584
585
586
587
588 @Override
589 public final LifecycleElement getElement() {
590 return element;
591 }
592
593
594
595
596 @Override
597 public final Component getParent() {
598 return this.parent;
599 }
600
601
602
603
604 @Override
605 public String getParentPath() {
606 return this.path;
607 }
608
609
610
611
612 @Override
613 public String getViewPath() {
614 return this.viewPath;
615 }
616
617
618
619
620 public void setViewPath(String viewPath) {
621 this.viewPath = viewPath;
622 }
623
624
625
626
627 @Override
628 public int getDepth() {
629 return this.depth;
630 }
631
632
633
634
635 @Override
636 public final boolean isProcessed() {
637 return processed;
638 }
639
640
641
642
643 @Override
644 public final boolean isComplete() {
645 return completed;
646 }
647
648
649
650
651 @Override
652 public ViewLifecyclePhase getPredecessor() {
653 return predecessor;
654 }
655
656
657
658
659 @Override
660 public void setPredecessor(ViewLifecyclePhase phase) {
661 if (this.predecessor != null) {
662 throw new IllegalStateException("Predecessor phase is already defined");
663 }
664
665 this.predecessor = phase;
666 }
667
668
669
670
671 @Override
672 public ViewLifecycleTask<?> getCurrentTask() {
673 return this.currentTask;
674 }
675
676
677
678
679 @Override
680 public boolean hasPendingSuccessors() {
681 return !pendingSuccessors.isEmpty();
682 }
683
684
685
686
687 @Override
688 public void removePendingSuccessor(String parentPath) {
689 if (!pendingSuccessors.remove(parentPath)) {
690 throw new IllegalStateException("Not a pending successor: " + parentPath);
691 }
692 }
693
694
695
696
697 @Override
698 public String toString() {
699 StringBuilder sb = new StringBuilder();
700 Queue<ViewLifecyclePhase> toPrint = new LinkedList<ViewLifecyclePhase>();
701 toPrint.offer(this);
702 while (!toPrint.isEmpty()) {
703 ViewLifecyclePhase tp = toPrint.poll();
704
705 if (tp.getElement() == null) {
706 sb.append("\n ");
707 sb.append(tp.getClass().getSimpleName());
708 sb.append(" (recycled)");
709 continue;
710 }
711
712 String indent;
713 if (tp == this) {
714 sb.append("\nProcessed? ");
715 sb.append(processed);
716 indent = "\n";
717 } else {
718 indent = "\n ";
719 }
720 sb.append(indent);
721
722 sb.append(tp.getClass().getSimpleName());
723 sb.append(" ");
724 sb.append(System.identityHashCode(tp));
725 sb.append(" ");
726 sb.append(tp.getViewPath());
727 sb.append(" ");
728 sb.append(tp.getElement().getClass().getSimpleName());
729 sb.append(" ");
730 sb.append(tp.getElement().getId());
731 sb.append(" ");
732 sb.append(pendingSuccessors);
733
734 if (tp == this) {
735 sb.append("\nPredecessor Phases:");
736 }
737
738 ViewLifecyclePhase tpredecessor = tp.getPredecessor();
739 if (tpredecessor != null) {
740 toPrint.add(tpredecessor);
741 }
742 }
743 return sb.toString();
744 }
745
746
747
748
749
750
751
752
753 protected void trace(String step) {
754 if (ViewLifecycle.isTrace() && LOG.isDebugEnabled()) {
755 String msg = System.identityHashCode(this) + " " + getClass() + " " + step + " " + path + " " +
756 (element == null ? "(recycled)" : element.getClass() + " " + element.getId());
757 LOG.debug(msg);
758 }
759 }
760
761 }