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 }