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.lang.reflect.Array;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.LinkedHashMap;
25 import java.util.LinkedList;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.Queue;
30 import java.util.Set;
31 import java.util.WeakHashMap;
32
33 import org.apache.commons.lang.StringUtils;
34 import org.apache.log4j.Logger;
35 import org.kuali.rice.core.api.CoreApiServiceLocator;
36 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
37 import org.kuali.rice.krad.uif.UifConstants;
38 import org.kuali.rice.krad.uif.component.Component;
39 import org.kuali.rice.krad.uif.util.CopyUtils;
40 import org.kuali.rice.krad.uif.util.LifecycleElement;
41 import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
42 import org.kuali.rice.krad.uif.util.RecycleUtils;
43 import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
44 import org.kuali.rice.krad.uif.view.ExpressionEvaluatorFactory;
45 import org.kuali.rice.krad.uif.view.View;
46 import org.kuali.rice.krad.util.GlobalVariables;
47 import org.kuali.rice.krad.util.KRADConstants;
48
49
50
51
52
53
54 public final class ViewLifecycleUtils {
55 private static final Logger LOG = Logger.getLogger(ViewLifecycleUtils.class);
56
57 private static final String COMPONENT_CONTEXT_PREFIX = '#' + UifConstants.ContextVariableNames.COMPONENT + '.';
58 private static final String PARENT_CONTEXT_PREFIX = '#' + UifConstants.ContextVariableNames.PARENT + '.';
59
60
61
62
63
64
65
66
67
68 public static Set<String> getLifecycleRestrictedProperties(LifecycleElement element, String viewPhase) {
69 Set<String> restrictedPropertyNames = getMetadata(element.getClass()).lifecycleRestrictedProperties.get(
70 viewPhase);
71 if (restrictedPropertyNames == null) {
72 return Collections.emptySet();
73 } else {
74 return restrictedPropertyNames;
75 }
76 }
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106 public static String getNextLifecyclePhase(LifecycleElement element) {
107 if (element == null) {
108 return UifConstants.ViewPhases.INITIALIZE;
109 }
110
111 String viewStatus = element.getViewStatus();
112
113 if (viewStatus == null || UifConstants.ViewStatus.CACHED.equals(viewStatus) || UifConstants.ViewStatus.CREATED
114 .equals(viewStatus)) {
115 return UifConstants.ViewPhases.INITIALIZE;
116
117 } else if (UifConstants.ViewStatus.INITIALIZED.equals(viewStatus)) {
118 return UifConstants.ViewPhases.APPLY_MODEL;
119
120 } else if (UifConstants.ViewStatus.MODEL_APPLIED.equals(viewStatus)) {
121 return UifConstants.ViewPhases.FINALIZE;
122
123 } else if (UifConstants.ViewStatus.FINAL.equals(viewStatus) || UifConstants.ViewStatus.RENDERED.equals(
124 viewStatus)) {
125 return UifConstants.ViewPhases.RENDER;
126
127 } else {
128 ViewLifecycle.reportIllegalState("Invalid view status " + viewStatus);
129 return UifConstants.ViewPhases.INITIALIZE;
130 }
131 }
132
133
134
135
136
137
138
139 public static Map<String, LifecycleElement> getElementsForLifecycle(LifecycleElement element) {
140 return getElementsForLifecycle(element, getNextLifecyclePhase(element));
141 }
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157 private static Map<String, LifecycleElement> addElementToLifecycleMap(Map<String, LifecycleElement> map,
158 String propertyName, LifecycleElement nestedElement) {
159 if (nestedElement == null) {
160 return map;
161 }
162
163 Map<String, LifecycleElement> returnMap = map;
164 if (returnMap == Collections.EMPTY_MAP) {
165 returnMap = RecycleUtils.getInstance(LinkedHashMap.class);
166 }
167
168 returnMap.put(propertyName, CopyUtils.unwrap((LifecycleElement) nestedElement));
169 return returnMap;
170 }
171
172
173
174
175
176
177
178
179 public static Map<String, LifecycleElement> getElementsForLifecycle(LifecycleElement element, String viewPhase) {
180 if (element == null) {
181 return Collections.emptyMap();
182 }
183
184 Set<String> nestedElementProperties = ObjectPropertyUtils.getReadablePropertyNamesByType(element,
185 LifecycleElement.class);
186 Set<String> nestedElementCollectionProperties = ObjectPropertyUtils.getReadablePropertyNamesByCollectionType(
187 element, LifecycleElement.class);
188 if (nestedElementProperties.isEmpty() && nestedElementCollectionProperties.isEmpty()) {
189 return Collections.emptyMap();
190 }
191
192 Set<String> restrictedPropertyNames = getLifecycleRestrictedProperties(element, viewPhase);
193 Map<String, String> conditionalPropertyRestrictions = getMetadata(
194 element.getClass()).conditionalPropertyRestrictions;
195
196 Map<String, LifecycleElement> elements = Collections.emptyMap();
197 for (String propertyName : nestedElementProperties) {
198 if (conditionalPropertyRestrictions.containsKey(propertyName) && ViewLifecycle.isActive() && !UifConstants
199 .ViewPhases.PRE_PROCESS.equals(viewPhase)) {
200 boolean conditionResult = evaluateLifecycleCondition(conditionalPropertyRestrictions.get(propertyName));
201 if (!conditionResult) {
202 continue;
203 }
204 }
205
206 if (restrictedPropertyNames.contains(propertyName)) {
207 continue;
208 }
209
210 Object propertyValue = ObjectPropertyUtils.getPropertyValue(element, propertyName);
211 elements = addElementToLifecycleMap(elements, propertyName, (LifecycleElement) propertyValue);
212 }
213
214 for (String propertyName : nestedElementCollectionProperties) {
215 if (conditionalPropertyRestrictions.containsKey(propertyName) && ViewLifecycle.isActive() && !UifConstants
216 .ViewPhases.PRE_PROCESS.equals(viewPhase)) {
217 boolean conditionResult = evaluateLifecycleCondition(conditionalPropertyRestrictions.get(propertyName));
218 if (!conditionResult) {
219 continue;
220 }
221 }
222
223 if (restrictedPropertyNames.contains(propertyName)) {
224 continue;
225 }
226
227 Object nestedElementCollection = ObjectPropertyUtils.getPropertyValue(element, propertyName);
228 if (element.getClass().isArray()) {
229 for (int i = 0; i < Array.getLength(nestedElementCollection); i++) {
230 elements = addElementToLifecycleMap(elements, propertyName + "[" + i + "]",
231 (LifecycleElement) Array.get(nestedElementCollection, i));
232 }
233 } else if (nestedElementCollection instanceof List) {
234 for (int i = 0; i < ((List<?>) nestedElementCollection).size(); i++) {
235 elements = addElementToLifecycleMap(elements, propertyName + "[" + i + "]",
236 (LifecycleElement) ((List<?>) nestedElementCollection).get(i));
237 }
238 } else if (nestedElementCollection instanceof Map) {
239 for (Entry<?, ?> entry : ((Map<?, ?>) nestedElementCollection).entrySet()) {
240 elements = addElementToLifecycleMap(elements, propertyName + "[" + entry.getKey() + "]",
241 (LifecycleElement) entry.getValue());
242 }
243 }
244 }
245
246 return elements == Collections.EMPTY_MAP ? elements : Collections.unmodifiableMap(elements);
247 }
248
249
250
251
252
253
254
255 private static boolean evaluateLifecycleCondition(String condition) {
256 ExpressionEvaluator expressionEvaluator =
257 KRADServiceLocatorWeb.getExpressionEvaluatorFactory().createExpressionEvaluator();
258 expressionEvaluator.initializeEvaluationContext(ViewLifecycle.getModel());
259
260 Map<String, Object> context = new HashMap<String, Object>();
261
262 Map<String, String> properties = CoreApiServiceLocator.getKualiConfigurationService().getAllProperties();
263 context.put(UifConstants.ContextVariableNames.CONFIG_PROPERTIES, properties);
264 context.put(UifConstants.ContextVariableNames.CONSTANTS, KRADConstants.class);
265 context.put(UifConstants.ContextVariableNames.UIF_CONSTANTS, UifConstants.class);
266 context.put(UifConstants.ContextVariableNames.USER_SESSION, GlobalVariables.getUserSession());
267
268 Boolean result = (Boolean) expressionEvaluator.evaluateExpression(context, condition);
269
270 return result.booleanValue();
271 }
272
273
274
275
276
277
278 public static void recycleElementMap(Map<?, ?> elementMap) {
279 if (elementMap instanceof LinkedHashMap) {
280 elementMap.clear();
281 RecycleUtils.recycle(elementMap);
282 }
283 }
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299 public static <T extends LifecycleElement> List<T> getElementsOfTypeDeep(
300 Collection<? extends LifecycleElement> items, Class<T> elementType) {
301 if (items == null) {
302 return Collections.emptyList();
303 }
304
305 List<T> elements = Collections.emptyList();
306
307 @SuppressWarnings("unchecked") Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance(
308 LinkedList.class);
309 elementQueue.addAll(items);
310
311 try {
312 while (!elementQueue.isEmpty()) {
313 LifecycleElement currentElement = elementQueue.poll();
314 if (currentElement == null) {
315 continue;
316 }
317
318 if (elementType.isInstance(currentElement)) {
319 if (elements.isEmpty()) {
320 elements = new ArrayList<T>();
321 }
322
323 elements.add(elementType.cast(currentElement));
324 }
325
326 elementQueue.addAll(getElementsForLifecycle(currentElement).values());
327 }
328 } finally {
329 elementQueue.clear();
330 RecycleUtils.recycle(elementQueue);
331 }
332 return elements;
333 }
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349 public static <T extends LifecycleElement> List<T> getElementsOfTypeDeep(LifecycleElement element,
350 Class<T> elementType) {
351 return getElementsOfTypeDeep(Collections.singletonList(element), elementType);
352 }
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367 public static <T extends LifecycleElement> List<T> getElementsOfTypeShallow(LifecycleElement element,
368 Class<T> elementType) {
369 if (element == null) {
370 return Collections.emptyList();
371 }
372
373 List<T> typeElements = getNestedElementsOfTypeShallow(element, elementType);
374
375 if (elementType.isInstance(element)) {
376 if (typeElements.isEmpty()) {
377 typeElements = Collections.singletonList(elementType.cast(element));
378 } else {
379 typeElements.add(0, elementType.cast(element));
380 }
381 }
382
383 return typeElements;
384 }
385
386
387
388
389
390
391
392
393
394
395
396 public static <T extends LifecycleElement> List<T> getNestedElementsOfTypeShallow(LifecycleElement element,
397 Class<T> elementType) {
398 if (element == null) {
399 return Collections.emptyList();
400 }
401
402 List<T> elements = Collections.emptyList();
403
404 @SuppressWarnings("unchecked") Queue<LifecycleElement> elementQueue = RecycleUtils.getInstance(
405 LinkedList.class);
406 try {
407 elementQueue.add(element);
408
409 while (!elementQueue.isEmpty()) {
410 LifecycleElement currentElement = elementQueue.poll();
411 if (currentElement == null) {
412 continue;
413 }
414
415 if (elementType.isInstance(currentElement) && currentElement != element) {
416 if (elements.isEmpty()) {
417 elements = new ArrayList<T>();
418 }
419
420 elements.add(elementType.cast(currentElement));
421 }
422
423 for (LifecycleElement nestedElement : getElementsForLifecycle(currentElement).values()) {
424 if (!(nestedElement instanceof Component)) {
425 elementQueue.offer(nestedElement);
426 }
427 }
428 }
429 } finally {
430 elementQueue.clear();
431 RecycleUtils.recycle(elementQueue);
432 }
433 return elements;
434 }
435
436
437
438
439 private ViewLifecycleUtils() {}
440
441
442
443
444 private static final Map<Class<?>, ElementMetadata> METADATA_CACHE = Collections.synchronizedMap(
445 new WeakHashMap<Class<?>, ElementMetadata>(2048));
446
447
448
449
450
451
452
453 private static ElementMetadata getMetadata(Class<?> elementClass) {
454 ElementMetadata metadata = METADATA_CACHE.get(elementClass);
455
456 if (metadata == null) {
457 metadata = new ElementMetadata(elementClass);
458 METADATA_CACHE.put(elementClass, metadata);
459 }
460
461 return metadata;
462 }
463
464
465
466
467 private static class ElementMetadata {
468
469
470 private final Map<String, Set<String>> lifecycleRestrictedProperties;
471
472
473
474 private final Map<String, String> conditionalPropertyRestrictions;
475
476
477
478
479
480
481 private ElementMetadata(Class<?> elementClass) {
482 Set<String> restrictedPropertyNames = ObjectPropertyUtils.getReadablePropertyNamesByAnnotationType(
483 elementClass, ViewLifecycleRestriction.class);
484
485 if (restrictedPropertyNames.isEmpty()) {
486 lifecycleRestrictedProperties = Collections.emptyMap();
487 conditionalPropertyRestrictions = Collections.emptyMap();
488
489 return;
490 }
491
492 Map<String, Set<String>> mutableLifecycleRestrictedProperties = new HashMap<String, Set<String>>();
493
494 mutableLifecycleRestrictedProperties.put(UifConstants.ViewPhases.FINALIZE, new HashSet<String>(
495 restrictedPropertyNames));
496 mutableLifecycleRestrictedProperties.put(UifConstants.ViewPhases.APPLY_MODEL, new HashSet<String>(
497 restrictedPropertyNames));
498 mutableLifecycleRestrictedProperties.put(UifConstants.ViewPhases.INITIALIZE, new HashSet<String>(
499 restrictedPropertyNames));
500 mutableLifecycleRestrictedProperties.put(UifConstants.ViewPhases.PRE_PROCESS, new HashSet<String>(
501 restrictedPropertyNames));
502
503 Map<String, String> mutableConditionalLifecycleProperties = new HashMap<>();
504
505
506 for (String restrictedPropertyName : restrictedPropertyNames) {
507 ViewLifecycleRestriction restriction = ObjectPropertyUtils.getReadMethod(elementClass,
508 restrictedPropertyName).getAnnotation(ViewLifecycleRestriction.class);
509
510 if (restriction.value().length > 0) {
511 removedRestrictionsForPrecedingPhases(mutableLifecycleRestrictedProperties, restrictedPropertyName,
512 restriction.value()[0]);
513 } else if (restriction.exclude().length > 0) {
514
515 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, restrictedPropertyName,
516 UifConstants.ViewPhases.PRE_PROCESS);
517 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, restrictedPropertyName,
518 UifConstants.ViewPhases.INITIALIZE);
519 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, restrictedPropertyName,
520 UifConstants.ViewPhases.APPLY_MODEL);
521 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, restrictedPropertyName,
522 UifConstants.ViewPhases.FINALIZE);
523 }
524
525
526 if (restriction.exclude().length > 0) {
527 for (String excludePhase : restriction.exclude()) {
528 Set<String> restrictedProperties = mutableLifecycleRestrictedProperties.get(excludePhase);
529
530 restrictedProperties.add(restrictedPropertyName);
531 }
532 }
533
534 if (StringUtils.isNotBlank(restriction.condition())) {
535 mutableConditionalLifecycleProperties.put(restrictedPropertyName, restriction.condition());
536 }
537 }
538
539 lifecycleRestrictedProperties = Collections.unmodifiableMap(mutableLifecycleRestrictedProperties);
540 conditionalPropertyRestrictions = Collections.unmodifiableMap(mutableConditionalLifecycleProperties);
541 }
542
543 private void removedRestrictionsForPrecedingPhases(
544 Map<String, Set<String>> mutableLifecycleRestrictedProperties, String propertyName, String phase) {
545 if (phase.equals(UifConstants.ViewPhases.FINALIZE)) {
546 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
547 UifConstants.ViewPhases.FINALIZE);
548 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
549 UifConstants.ViewPhases.APPLY_MODEL);
550 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
551 UifConstants.ViewPhases.INITIALIZE);
552 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
553 UifConstants.ViewPhases.PRE_PROCESS);
554 } else if (phase.equals(UifConstants.ViewPhases.APPLY_MODEL)) {
555 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
556 UifConstants.ViewPhases.APPLY_MODEL);
557 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
558 UifConstants.ViewPhases.INITIALIZE);
559 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
560 UifConstants.ViewPhases.PRE_PROCESS);
561 } else if (phase.equals(UifConstants.ViewPhases.INITIALIZE)) {
562 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
563 UifConstants.ViewPhases.INITIALIZE);
564 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
565 UifConstants.ViewPhases.PRE_PROCESS);
566 } else if (phase.equals(UifConstants.ViewPhases.PRE_PROCESS)) {
567 removedRestrictionsForPhase(mutableLifecycleRestrictedProperties, propertyName,
568 UifConstants.ViewPhases.PRE_PROCESS);
569 }
570 }
571
572 private void removedRestrictionsForPhase(Map<String, Set<String>> mutableLifecycleRestrictedProperties,
573 String propertyName, String phase) {
574 Set<String> restrictedProperties = mutableLifecycleRestrictedProperties.get(phase);
575
576 restrictedProperties.remove(propertyName);
577 }
578
579 private void addRestrictionForAllPhases(Map<String, Set<String>> mutableLifecycleRestrictedProperties,
580 String propertyName) {
581 mutableLifecycleRestrictedProperties.get(UifConstants.ViewPhases.PRE_PROCESS).add(propertyName);
582 mutableLifecycleRestrictedProperties.get(UifConstants.ViewPhases.INITIALIZE).add(propertyName);
583 mutableLifecycleRestrictedProperties.get(UifConstants.ViewPhases.APPLY_MODEL).add(propertyName);
584 mutableLifecycleRestrictedProperties.get(UifConstants.ViewPhases.FINALIZE).add(propertyName);
585 }
586 }
587
588
589
590
591
592
593 public static boolean isExcluded(Component component) {
594 String excludeUnless = component.getExcludeUnless();
595 if (StringUtils.isNotBlank(excludeUnless) &&
596 !resolvePropertyPath(excludeUnless, component)) {
597 return true;
598 }
599
600 return resolvePropertyPath(component.getExcludeIf(), component);
601 }
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618 private static boolean resolvePropertyPath(String path, Component component) {
619 if (StringUtils.isBlank(path)) {
620 return false;
621 }
622
623 Object root;
624 if (path.startsWith(COMPONENT_CONTEXT_PREFIX)) {
625 root = component;
626 path = path.substring(COMPONENT_CONTEXT_PREFIX.length());
627 } else if (path.startsWith(PARENT_CONTEXT_PREFIX)) {
628 root = ViewLifecycle.getPhase().getParent();
629 path = path.substring(PARENT_CONTEXT_PREFIX.length());
630 } else if (path.charAt(0) == '#') {
631 Map<String, Object> context = ViewLifecycle.getView().getPreModelContext();
632
633 int iod = path.indexOf('.');
634 if (iod == -1) {
635 return Boolean.TRUE.equals(context.get(path.substring(1)));
636 }
637
638 String contextVariable = path.substring(1, iod);
639 root = context.get(contextVariable);
640 path = path.substring(iod + 1);
641 } else {
642 root = ViewLifecycle.getModel();
643 }
644
645 return Boolean.TRUE.equals(ObjectPropertyUtils.getPropertyValue(root, path));
646 }
647
648 }