1 /**
2 * Copyright 2005-2013 The Kuali Foundation
3 *
4 * Licensed under the Educational Community License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.opensource.org/licenses/ecl2.php
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.kuali.rice.krad.uif.lifecycle.initialize;
17
18 import java.util.LinkedList;
19 import java.util.Queue;
20
21 import org.apache.commons.lang.StringUtils;
22 import org.kuali.rice.krad.uif.UifConstants;
23 import org.kuali.rice.krad.uif.component.Component;
24 import org.kuali.rice.krad.uif.container.Container;
25 import org.kuali.rice.krad.uif.layout.LayoutManager;
26 import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleTaskBase;
27 import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
28 import org.kuali.rice.krad.uif.lifecycle.ViewLifecyclePhase;
29 import org.kuali.rice.krad.uif.util.LifecycleElement;
30 import org.kuali.rice.krad.uif.view.ViewIndex;
31
32 /**
33 * Assign a unique ID to the component, if one has not already been assigned.
34 *
35 * @author Kuali Rice Team (rice.collab@kuali.org)
36 */
37 public class AssignIdsTask extends ViewLifecycleTaskBase {
38
39 /**
40 * Reusable linked queue for walking the lifecycle phase tree while generating an ID, without
41 * object creation.
42 */
43 private static final ThreadLocal<Queue<ViewLifecyclePhase>> TAIL_QUEUE = new ThreadLocal<Queue<ViewLifecyclePhase>>() {
44 @Override
45 protected Queue<ViewLifecyclePhase> initialValue() {
46 return new LinkedList<ViewLifecyclePhase>();
47 }
48 };
49
50 /**
51 * Create a task to assign component IDs during the initialize phase.
52 *
53 * @param phase The initialize phase for the component.
54 */
55 public AssignIdsTask(ViewLifecyclePhase phase) {
56 super(phase);
57 }
58
59 /**
60 * Generate a new ID for a lifecycle element at the current phase.
61 *
62 * <p>
63 * This method used a product of primes similar to the one used for generating String hash
64 * codes. In order to minimize to collisions a large prime is used, then when collisions are
65 * detected a different large prime is used to generate an alternate ID.
66 * </p>
67 *
68 * <p>
69 * The hash code that the generated ID is based on is equivalent (though not identical) to
70 * taking the hash code of the string concenation of all class names, non-null IDs, and
71 * successor index positions in the lifecycle phase tree for all predecessors of the current
72 * phase. This technique leads to a reliably unique ID that is also repeatable across server
73 * instances and test runs.
74 * </p>
75 *
76 * @param element The lifecycle element for which to generate an ID.
77 * @return An ID, unique within the current view, for the given element.
78 *
79 * @see ViewIndex#observeAssignedId(String)
80 * @see String#hashCode() for the algorithm this method is based on.
81 */
82 private String generateId(LifecycleElement element) {
83 // Calculate a hash code based on the path to the top of the phase tree
84 // without building a string.
85
86 final int prime = 6971; // Seed prime for hashing
87
88 // Initialize hash to the class of the lifecycle element
89 int hash = element.getClass().getName().hashCode();
90
91 // Reuse a tail recursion queue to avoid object creation overhead.
92 Queue<ViewLifecyclePhase> phaseQueue = TAIL_QUEUE.get();
93 try {
94 // Start with the current phase.
95 phaseQueue.offer(getPhase());
96 while (!phaseQueue.isEmpty()) {
97
98 // Poll the queue for the next phase to calculate
99 ViewLifecyclePhase phase = phaseQueue.poll();
100
101 // Include the class name and ID of the component
102 // at the current phase to the hash
103 Component component = phase.getComponent();
104 hash *= prime;
105 if (component != null) {
106 hash += component.getClass().getName().hashCode();
107
108 String id = component.getId();
109 hash *= prime;
110 if (id != null) {
111 hash += id.hashCode();
112 }
113 }
114
115 // Include the index of the current phase in the successor
116 // list of the predecessor phase that defined it.
117 hash = prime * hash + phase.getIndex();
118
119 // Include the predecessors in the hash.
120 ViewLifecyclePhase predecessor = phase.getPredecessor();
121 if (predecessor != null) {
122 phaseQueue.add(predecessor);
123 }
124 }
125 } finally {
126 // Ensure that the recursion queue is clear to prevent
127 // corruption by pooled thread reuse.
128 phaseQueue.clear();
129 }
130
131 String id;
132 do {
133 // Iteratively take the product of the hash and another large prime
134 hash *= 4507; // until a unique ID has been generated.
135 // The use of large primes will minimize collisions, reducing the
136 // likelihood of race conditions leading to components coming out
137 // with different IDs on different server instances and/or test runs.
138
139 // Eliminate negatives without losing precision, and express in base-36
140 id = Long.toString(((long) hash) - ((long) Integer.MIN_VALUE), 36);
141
142 // Use the view index to detect collisions, keep looping until an
143 // id unique to the current view has been generated.
144 } while (!ViewLifecycle.getView().getViewIndex().observeAssignedId(id));
145
146 return id;
147 }
148
149 /**
150 * @see org.kuali.rice.krad.uif.lifecycle.ViewLifecycleTaskBase#performLifecycleTask()
151 */
152 @Override
153 protected void performLifecycleTask() {
154 Component component = getPhase().getComponent();
155
156 if (StringUtils.isBlank(component.getId())) {
157 component.setId(UifConstants.COMPONENT_ID_PREFIX + generateId(component));
158 }
159
160 if (component instanceof Container) {
161 LayoutManager layoutManager = ((Container) component).getLayoutManager();
162
163 if ((layoutManager != null) && StringUtils.isBlank(layoutManager.getId())) {
164 layoutManager.setId(UifConstants.COMPONENT_ID_PREFIX + generateId(layoutManager));
165 }
166 }
167 }
168
169 }