View Javadoc

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 }