View Javadoc
1   /**
2    * Copyright 2005-2014 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 org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.krad.uif.UifConstants;
20  import org.kuali.rice.krad.uif.lifecycle.LifecycleElementState;
21  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
22  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleTaskBase;
23  import org.kuali.rice.krad.uif.util.LifecycleElement;
24  import org.kuali.rice.krad.uif.view.View;
25  import org.kuali.rice.krad.uif.view.ViewIndex;
26  
27  /**
28   * Assign a unique ID to the component, if one has not already been assigned.
29   * 
30   * @author Kuali Rice Team (rice.collab@kuali.org)
31   */
32  public class AssignIdsTask extends ViewLifecycleTaskBase<LifecycleElement> {
33  
34      /**
35       * Create a task to assign component IDs during the initialize phase.
36       * 
37       * @param elementState The initialize phase for the component.
38       */
39      public AssignIdsTask(LifecycleElementState elementState) {
40          super(elementState, LifecycleElement.class);
41      }
42  
43      /**
44       * Generate a new ID for a lifecycle element at the current phase.
45       * 
46       * <p>
47       * This method used a product of primes similar to the one used for generating String hash
48       * codes. In order to minimize to collisions a large prime is used, then when collisions are
49       * detected a different large prime is used to generate an alternate ID.
50       * </p>
51       * 
52       * <p>
53       * The hash code that the generated ID is based on is equivalent (though not identical) to
54       * taking the hash code of the string concenation of all class names, non-null IDs, and
55       * successor index positions in the lifecycle phase tree for all predecessors of the current
56       * phase. This technique leads to a reliably unique ID that is also repeatable across server
57       * instances and test runs.
58       * </p>
59       * 
60       * <p>
61       * The use of large primes by this method minimizes collisions, and therefore reduces the
62       * likelihood of a race condition causing components to come out with different IDs on different
63       * server instances and/or test runs.
64       * </p>
65       * 
66       * @param element The lifecycle element for which to generate an ID.
67       * @param view View containing the lifecycle element.
68       * @return An ID, unique within the current view, for the given element.
69       * 
70       * @see ViewIndex#observeAssignedId(String)
71       * @see String#hashCode() for the algorithm this method is based on.
72       */
73      public static String generateId(LifecycleElement element, View view) {
74          // Calculate a hash code based on the path to the top of the phase tree
75          // without building a string.
76          int prime = 6971;
77  
78          // Initialize hash to the class of the lifecycle element
79          int hash = element.getClass().getName().hashCode();
80          
81          // Add the element's path to the hash code.
82          hash += prime;
83          if (element.getViewPath() != null) {
84              hash += element.getViewPath().hashCode();
85          }
86          
87          // Eliminate negatives without losing precision, and express in base-36
88          String id = Long.toString(((long) hash) - ((long) Integer.MIN_VALUE), 36);
89          while (!view.getViewIndex().observeAssignedId(id)) {
90              // Iteratively take the product of the hash and another large prime
91              // until a unique ID has been generated.
92              hash *= 4507;
93              id = Long.toString(((long) hash) - ((long) Integer.MIN_VALUE), 36);
94          }
95          
96          return UifConstants.COMPONENT_ID_PREFIX + id;
97      }
98  
99      /**
100      * {@inheritDoc}
101      */
102     @Override
103     protected void performLifecycleTask() {
104         LifecycleElement element = getElementState().getElement();
105 
106         if (StringUtils.isBlank(element.getId())) {
107             element.setId(generateId(element, ViewLifecycle.getView()));
108         }
109     }
110 
111 }