001/** 002 * Copyright 2005-2014 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.uif.lifecycle.initialize; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.krad.uif.UifConstants; 020import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle; 021import org.kuali.rice.krad.uif.lifecycle.ViewLifecycleTaskBase; 022import org.kuali.rice.krad.uif.util.LifecycleElement; 023import org.kuali.rice.krad.uif.view.View; 024import org.kuali.rice.krad.uif.view.ViewIndex; 025 026/** 027 * Assign a unique ID to the component, if one has not already been assigned. 028 * 029 * @author Kuali Rice Team (rice.collab@kuali.org) 030 */ 031public class AssignIdsTask extends ViewLifecycleTaskBase<LifecycleElement> { 032 033 /** 034 * Create a task to assign component IDs during the initialize phase. 035 */ 036 public AssignIdsTask() { 037 super(LifecycleElement.class); 038 } 039 040 /** 041 * Generate a new ID for a lifecycle element at the current phase. 042 * 043 * <p> 044 * This method used a product of primes similar to the one used for generating String hash 045 * codes. In order to minimize to collisions a large prime is used, then when collisions are 046 * detected a different large prime is used to generate an alternate ID. 047 * </p> 048 * 049 * <p> 050 * The hash code that the generated ID is based on is equivalent (though not identical) to 051 * taking the hash code of the string concenation of all class names, non-null IDs, and 052 * successor index positions in the lifecycle phase tree for all predecessors of the current 053 * phase. This technique leads to a reliably unique ID that is also repeatable across server 054 * instances and test runs. 055 * </p> 056 * 057 * <p> 058 * The use of large primes by this method minimizes collisions, and therefore reduces the 059 * likelihood of a race condition causing components to come out with different IDs on different 060 * server instances and/or test runs. 061 * </p> 062 * 063 * @param element The lifecycle element for which to generate an ID. 064 * @param view View containing the lifecycle element. 065 * @return An ID, unique within the current view, for the given element. 066 * 067 * @see ViewIndex#observeAssignedId(String) 068 * @see String#hashCode() for the algorithm this method is based on. 069 */ 070 public static String generateId(LifecycleElement element, View view) { 071 // Calculate a hash code based on the path to the top of the phase tree 072 // without building a string. 073 int prime = 6971; 074 075 // Initialize hash to the class of the lifecycle element 076 int hash = element.getClass().getName().hashCode(); 077 078 // Add the element's path to the hash code. 079 hash += prime; 080 if (element.getViewPath() != null) { 081 hash += element.getViewPath().hashCode(); 082 } 083 084 // Eliminate negatives without losing precision, and express in base-36 085 String id = Long.toString(((long) hash) - ((long) Integer.MIN_VALUE), 36); 086 while (!view.getViewIndex().observeAssignedId(id)) { 087 // Iteratively take the product of the hash and another large prime 088 // until a unique ID has been generated. 089 hash *= 4507; 090 id = Long.toString(((long) hash) - ((long) Integer.MIN_VALUE), 36); 091 } 092 093 return UifConstants.COMPONENT_ID_PREFIX + id; 094 } 095 096 /** 097 * {@inheritDoc} 098 */ 099 @Override 100 protected void performLifecycleTask() { 101 LifecycleElement element = getElementState().getElement(); 102 103 if (StringUtils.isBlank(element.getId())) { 104 element.setId(generateId(element, ViewLifecycle.getView())); 105 } 106 } 107 108}