001 /** 002 * Copyright 2005-2013 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 */ 016 package org.kuali.rice.krad.datadictionary; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.apache.commons.logging.Log; 020 import org.apache.commons.logging.LogFactory; 021 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 022 import org.kuali.rice.krad.uif.UifConstants; 023 import org.kuali.rice.krad.uif.component.Component; 024 import org.kuali.rice.krad.uif.util.ComponentUtils; 025 import org.kuali.rice.krad.uif.util.ViewModelUtils; 026 import org.kuali.rice.krad.uif.view.View; 027 import org.kuali.rice.krad.uif.service.ViewTypeService; 028 import org.springframework.beans.PropertyValues; 029 import org.springframework.beans.factory.config.BeanDefinition; 030 import org.springframework.beans.factory.support.KualiDefaultListableBeanFactory; 031 import org.kuali.rice.krad.uif.UifConstants.ViewType; 032 033 import java.util.ArrayList; 034 import java.util.HashMap; 035 import java.util.List; 036 import java.util.Map; 037 import java.util.Map.Entry; 038 039 /** 040 * Indexes <code>View</code> bean entries for retrieval 041 * 042 * <p> 043 * This is used to retrieve a <code>View</code> instance by its unique id. 044 * Furthermore, view of certain types (that have a <code>ViewTypeService</code> 045 * are indexed by their type to support retrieval of views based on parameters. 046 * </p> 047 * 048 * @author Kuali Rice Team (rice.collab@kuali.org) 049 */ 050 public class UifDictionaryIndex implements Runnable { 051 private static final Log LOG = LogFactory.getLog(UifDictionaryIndex.class); 052 053 private KualiDefaultListableBeanFactory ddBeans; 054 055 // view entries keyed by view id with value the spring bean name 056 private Map<String, String> viewBeanEntriesById; 057 058 // view entries indexed by type 059 private Map<String, ViewTypeDictionaryIndex> viewEntriesByType; 060 061 public UifDictionaryIndex(KualiDefaultListableBeanFactory ddBeans) { 062 this.ddBeans = ddBeans; 063 } 064 065 public void run() { 066 LOG.info("Starting View Index Building"); 067 buildViewIndicies(); 068 LOG.info("Completed View Index Building"); 069 } 070 071 /** 072 * Retrieves the View instance with the given id from the bean factory. 073 * Since View instances are stateful, we need to get a new instance from 074 * Spring each time. 075 * 076 * @param viewId - the unique id for the view 077 * @return <code>View</code> instance 078 */ 079 public View getViewById(String viewId) { 080 String beanName = viewBeanEntriesById.get(viewId); 081 if (StringUtils.isBlank(beanName)) { 082 throw new DataDictionaryException("Unable to find View with id: " + viewId); 083 } 084 085 return ddBeans.getBean(beanName, View.class); 086 } 087 088 /** 089 * Retrieves a <code>View</code> instance that is of the given type based on 090 * the index key 091 * 092 * @param viewTypeName - type name for the view 093 * @param indexKey - Map of index key parameters, these are the parameters the 094 * indexer used to index the view initially and needs to identify 095 * an unique view instance 096 * @return View instance that matches the given index or Null if one is not 097 * found 098 */ 099 public View getViewByTypeIndex(ViewType viewTypeName, Map<String, String> indexKey) { 100 String index = buildTypeIndex(indexKey); 101 102 ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewTypeName); 103 104 String beanName = typeIndex.get(index); 105 if (StringUtils.isNotBlank(beanName)) { 106 return ddBeans.getBean(beanName, View.class); 107 } 108 109 return null; 110 } 111 112 /** 113 * Indicates whether a <code>View</code> exists for the given view type and index information 114 * 115 * @param viewTypeName - type name for the view 116 * @param indexKey - Map of index key parameters, these are the parameters the indexer used to index 117 * the view initially and needs to identify an unique view instance 118 * @return boolean true if view exists, false if not 119 */ 120 public boolean viewByTypeExist(ViewType viewTypeName, Map<String, String> indexKey) { 121 boolean viewExist = false; 122 123 String index = buildTypeIndex(indexKey); 124 ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewTypeName); 125 126 String beanName = typeIndex.get(index); 127 if (StringUtils.isNotBlank(beanName)) { 128 viewExist = true; 129 } 130 131 return viewExist; 132 } 133 134 /** 135 * Retrieves the configured property values for the view bean definition associated with the given id 136 * 137 * <p> 138 * Since constructing the View object can be expensive, when metadata only is needed this method can be used 139 * to retrieve the configured property values. Note this looks at the merged bean definition 140 * </p> 141 * 142 * @param viewId - id for the view to retrieve 143 * @return PropertyValues configured on the view bean definition, or null if view is not found 144 */ 145 public PropertyValues getViewPropertiesById(String viewId) { 146 String beanName = viewBeanEntriesById.get(viewId); 147 if (StringUtils.isBlank(beanName)) { 148 BeanDefinition beanDefinition = ddBeans.getMergedBeanDefinition(beanName); 149 150 return beanDefinition.getPropertyValues(); 151 } 152 153 return null; 154 } 155 156 /** 157 * Retrieves the configured property values for the view bean definition associated with the given type and 158 * index 159 * 160 * <p> 161 * Since constructing the View object can be expensive, when metadata only is needed this method can be used 162 * to retrieve the configured property values. Note this looks at the merged bean definition 163 * </p> 164 * 165 * @param viewTypeName - type name for the view 166 * @param indexKey - Map of index key parameters, these are the parameters the indexer used to index 167 * the view initially and needs to identify an unique view instance 168 * @return PropertyValues configured on the view bean definition, or null if view is not found 169 */ 170 public PropertyValues getViewPropertiesByType(ViewType viewTypeName, Map<String, String> indexKey) { 171 String index = buildTypeIndex(indexKey); 172 173 ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewTypeName); 174 175 String beanName = typeIndex.get(index); 176 if (StringUtils.isNotBlank(beanName)) { 177 BeanDefinition beanDefinition = ddBeans.getMergedBeanDefinition(beanName); 178 179 return beanDefinition.getPropertyValues(); 180 } 181 182 return null; 183 } 184 185 /** 186 * Gets all <code>View</code> prototypes configured for the given view type 187 * name 188 * 189 * @param viewTypeName - view type name to retrieve 190 * @return List<View> view prototypes with the given type name, or empty 191 * list 192 */ 193 public List<View> getViewsForType(ViewType viewTypeName) { 194 List<View> typeViews = new ArrayList<View>(); 195 196 // get view ids for the type 197 if (viewEntriesByType.containsKey(viewTypeName.name())) { 198 ViewTypeDictionaryIndex typeIndex = viewEntriesByType.get(viewTypeName.name()); 199 for (Entry<String, String> typeEntry : typeIndex.getViewIndex().entrySet()) { 200 View typeView = ddBeans.getBean(typeEntry.getValue(), View.class); 201 typeViews.add(typeView); 202 } 203 } else { 204 throw new DataDictionaryException("Unable to find view index for type: " + viewTypeName); 205 } 206 207 return typeViews; 208 } 209 210 /** 211 * Initializes the view index <code>Map</code> then iterates through all the 212 * beans in the factory that implement <code>View</code>, adding them to the 213 * index 214 */ 215 protected void buildViewIndicies() { 216 viewBeanEntriesById = new HashMap<String, String>(); 217 viewEntriesByType = new HashMap<String, ViewTypeDictionaryIndex>(); 218 219 String[] beanNames = ddBeans.getBeanNamesForType(View.class); 220 for (int i = 0; i < beanNames.length; i++) { 221 String beanName = beanNames[i]; 222 BeanDefinition beanDefinition = ddBeans.getMergedBeanDefinition(beanName); 223 PropertyValues propertyValues = beanDefinition.getPropertyValues(); 224 225 String id = ViewModelUtils.getStringValFromPVs(propertyValues, "id"); 226 if (StringUtils.isBlank(id)) { 227 id = beanName; 228 } 229 230 if (viewBeanEntriesById.containsKey(id)) { 231 throw new DataDictionaryException("Two views must not share the same id. Found duplicate id: " + id); 232 } 233 viewBeanEntriesById.put(id, beanName); 234 235 indexViewForType(propertyValues, beanName); 236 } 237 } 238 239 /** 240 * Performs additional indexing based on the view type associated with the view instance. The 241 * <code>ViewTypeService</code> associated with the view type name on the instance is invoked to retrieve 242 * the parameter key/value pairs from the configured property values, which are then used to build up an index 243 * used to key the entry 244 * 245 * @param propertyValues - property values configured on the view bean definition 246 * @param beanName - name of the view's bean in Spring 247 */ 248 protected void indexViewForType(PropertyValues propertyValues, String beanName) { 249 String viewTypeName = ViewModelUtils.getStringValFromPVs(propertyValues, "viewTypeName"); 250 if (StringUtils.isBlank(viewTypeName)) { 251 return; 252 } 253 254 UifConstants.ViewType viewType = ViewType.valueOf(viewTypeName); 255 256 ViewTypeService typeService = KRADServiceLocatorWeb.getViewService().getViewTypeService(viewType); 257 if (typeService == null) { 258 // don't do any further indexing 259 return; 260 } 261 262 // invoke type service to retrieve it parameter name/value pairs 263 Map<String, String> typeParameters = typeService.getParametersFromViewConfiguration(propertyValues); 264 265 // build the index string from the parameters 266 String index = buildTypeIndex(typeParameters); 267 268 // get the index for the type and add the view entry 269 ViewTypeDictionaryIndex typeIndex = getTypeIndex(viewType); 270 271 typeIndex.put(index, beanName); 272 } 273 274 /** 275 * Retrieves the <code>ViewTypeDictionaryIndex</code> instance for the given 276 * view type name. If one does not exist yet for the given name, a new 277 * instance is created 278 * 279 * @param viewType - name of the view type to retrieve index for 280 * @return ViewTypeDictionaryIndex instance 281 */ 282 protected ViewTypeDictionaryIndex getTypeIndex(UifConstants.ViewType viewType) { 283 ViewTypeDictionaryIndex typeIndex = null; 284 285 if (viewEntriesByType.containsKey(viewType.name())) { 286 typeIndex = viewEntriesByType.get(viewType.name()); 287 } else { 288 typeIndex = new ViewTypeDictionaryIndex(); 289 viewEntriesByType.put(viewType.name(), typeIndex); 290 } 291 292 return typeIndex; 293 } 294 295 /** 296 * Builds up an index string from the given Map of parameters 297 * 298 * @param typeParameters - Map of parameters to use for index 299 * @return String index 300 */ 301 protected String buildTypeIndex(Map<String, String> typeParameters) { 302 String index = ""; 303 304 for (String parameterName : typeParameters.keySet()) { 305 if (StringUtils.isNotBlank(index)) { 306 index += "|||"; 307 } 308 index += parameterName + "^^" + typeParameters.get(parameterName); 309 } 310 311 return index; 312 } 313 314 }