001 /** 002 * Copyright 2005-2011 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.kns.lookup; 017 018 import org.apache.commons.beanutils.PropertyUtils; 019 import org.apache.commons.lang.StringUtils; 020 import org.kuali.rice.core.api.search.SearchOperator; 021 import org.kuali.rice.core.web.format.Formatter; 022 import org.kuali.rice.kns.service.KNSServiceLocator; 023 import org.kuali.rice.krad.bo.BusinessObject; 024 import org.kuali.rice.krad.datadictionary.AttributeDefinition; 025 import org.kuali.rice.kns.datadictionary.BusinessObjectEntry; 026 import org.kuali.rice.krad.service.DataDictionaryService; 027 import org.kuali.rice.krad.service.KRADServiceLocator; 028 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 029 030 import java.lang.reflect.InvocationTargetException; 031 import java.util.ArrayList; 032 import java.util.Collection; 033 import java.util.HashMap; 034 import java.util.List; 035 import java.util.Map; 036 import java.util.Set; 037 038 /** 039 * LookupResults support strategy which uses the primary keys and lookupable defined in a business object's data dictionary file to support the multivalue lookup 040 * 041 * @author Kuali Rice Team (rice.collab@kuali.org) 042 * 043 */ 044 public class DataDictionaryLookupResultsSupportStrategy implements 045 LookupResultsSupportStrategyService { 046 047 private DataDictionaryService dataDictionaryService; 048 049 /** 050 * Builds a lookup id for the given business object 051 * @see LookupResultsSupportStrategyService#getLookupIdForBusinessObject(org.kuali.rice.krad.bo.BusinessObject) 052 */ 053 public String getLookupIdForBusinessObject(BusinessObject businessObject) { 054 final List<String> pkFieldNames = getPrimaryKeyFieldsForBusinessObject(businessObject.getClass()); 055 return convertPKFieldMapToLookupId(pkFieldNames, businessObject); 056 } 057 058 /** 059 * Determines if both the primary keys and Lookupable are present in the data dictionary definition; if so, the BO qualifies, but if either are missing, it does not 060 * @see LookupResultsSupportStrategyService#qualifiesForStrategy(java.lang.Class) 061 */ 062 public boolean qualifiesForStrategy(Class<? extends BusinessObject> boClass) { 063 if (getLookupableForBusinessObject(boClass) == null) { 064 return false; // this probably isn't going to happen, but still... 065 } 066 final List<String> pkFields = getPrimaryKeyFieldsForBusinessObject(boClass); 067 return pkFields != null && pkFields.size() > 0; 068 } 069 070 /** 071 * Uses the Lookupable associated with the given BusinessObject to search for business objects 072 * @see LookupResultsSupportStrategyService#retrieveSelectedResultBOs(java.lang.String, java.lang.Class, java.lang.String, org.kuali.rice.krad.lookup.LookupResultsService) 073 */ 074 public <T extends BusinessObject> Collection<T> retrieveSelectedResultBOs(Class<T> boClass, Set<String> lookupIds) throws Exception { 075 076 List<T> retrievedBusinessObjects = new ArrayList<T>(); 077 final org.kuali.rice.kns.lookup.Lookupable lookupable = getLookupableForBusinessObject(boClass); 078 for (String lookupId : lookupIds) { 079 final Map<String, String> lookupKeys = convertLookupIdToPKFieldMap(lookupId, boClass); 080 List<BusinessObject> bos = lookupable.getSearchResults(lookupKeys); 081 082 // we should only get one business object...but let's put them all in... 083 for (BusinessObject bo : bos) { 084 retrievedBusinessObjects.add((T)bo); 085 } 086 } 087 088 return retrievedBusinessObjects; 089 } 090 091 /** 092 * Retrieves the Lookupable for the given business object class 093 * 094 * @param businessObjectClass the class to find the Lookupable for 095 * @return the Lookupable, or null if nothing could be found 096 */ 097 protected org.kuali.rice.kns.lookup.Lookupable getLookupableForBusinessObject(Class<? extends BusinessObject> businessObjectClass) { 098 final BusinessObjectEntry boEntry = getBusinessObjectEntry(businessObjectClass); 099 if (boEntry == null) { 100 return null; 101 } 102 103 final String lookupableId = boEntry.getLookupDefinition().getLookupableID(); 104 return KNSServiceLocator.getLookupable(lookupableId); 105 } 106 107 /** 108 * Returns the data dictionary defined primary keys for the given BusinessObject 109 * 110 * @param businessObjectClass the business object to get DataDictionary defined primary keys for 111 * @return the List of primary key property names, or null if nothing could be found 112 */ 113 protected List<String> getPrimaryKeyFieldsForBusinessObject(Class<? extends BusinessObject> businessObjectClass) { 114 final BusinessObjectEntry boEntry = getBusinessObjectEntry(businessObjectClass); 115 if (boEntry == null) { 116 return null; 117 } 118 return boEntry.getPrimaryKeys(); 119 } 120 121 /** 122 * Converts a lookup id into a PK field map to use to search in a lookupable 123 * 124 * @param lookupId the id returned by the lookup 125 * @param businessObjectClass the class of the business object getting the primary key 126 * @return a Map of field names and values which can be profitably used to search for matching business objects 127 */ 128 protected Map<String, String> convertLookupIdToPKFieldMap(String lookupId, Class<? extends BusinessObject> businessObjectClass) { 129 Map<String, String> pkFields = new HashMap<String, String>(); 130 if (!StringUtils.isBlank(lookupId)) { 131 final String[] pkValues = lookupId.split("\\|"); 132 for (String pkValue : pkValues) { 133 if (!StringUtils.isBlank(pkValue)) { 134 final String[] pkPieces = pkValue.split("-"); 135 if (!StringUtils.isBlank(pkPieces[0]) && !StringUtils.isBlank(pkPieces[1])) { 136 pkFields.put(pkPieces[0], pkPieces[1]); 137 } 138 } 139 } 140 } 141 return pkFields; 142 } 143 144 /** 145 * Converts a Map of PKFields into a String lookup ID 146 * @param pkFieldNames the name of the PK fields, which should be converted to the given lookupId 147 * @param businessObjectClass the class of the business object getting the primary key 148 * @return the String lookup id 149 */ 150 protected String convertPKFieldMapToLookupId(List<String> pkFieldNames, BusinessObject businessObject) { 151 StringBuilder lookupId = new StringBuilder(); 152 for (String pkFieldName : pkFieldNames) { 153 try { 154 final Object value = PropertyUtils.getProperty(businessObject, pkFieldName); 155 156 if (value != null) { 157 lookupId.append(pkFieldName); 158 lookupId.append("-"); 159 final Formatter formatter = retrieveBestFormatter(pkFieldName, businessObject.getClass()); 160 final String formattedValue = (formatter != null) ? formatter.format(value).toString() : value.toString(); 161 162 lookupId.append(formattedValue); 163 } 164 lookupId.append(SearchOperator.OR.op()); 165 } catch (IllegalAccessException iae) { 166 throw new RuntimeException("Could not retrieve pk field value "+pkFieldName+" from business object "+businessObject.getClass().getName(), iae); 167 } catch (InvocationTargetException ite) { 168 throw new RuntimeException("Could not retrieve pk field value "+pkFieldName+" from business object "+businessObject.getClass().getName(), ite); 169 } catch (NoSuchMethodException nsme) { 170 throw new RuntimeException("Could not retrieve pk field value "+pkFieldName+" from business object "+businessObject.getClass().getName(), nsme); 171 } 172 } 173 return lookupId.substring(0, lookupId.length() - 1); // kill the last "|" 174 } 175 176 /** 177 * Like when you're digging through your stuff drawer, you know the one in the kitchen with all the batteries and lint in it, this method 178 * goes through the stuff drawer of KNS formatters and attempts to return you a good one 179 * 180 * @param propertyName the name of the property to retrieve 181 * @param boClass the class of the BusinessObject the property is on 182 * @return a Formatter, or null if we were unsuccessful in finding 183 */ 184 protected Formatter retrieveBestFormatter(String propertyName, Class<? extends BusinessObject> boClass) { 185 Formatter formatter = null; 186 187 try { 188 Class<? extends Formatter> formatterClass = null; 189 190 final BusinessObjectEntry boEntry = getBusinessObjectEntry(boClass); 191 if (boEntry != null) { 192 final AttributeDefinition attributeDefinition = boEntry.getAttributeDefinition(propertyName); 193 if (attributeDefinition != null && attributeDefinition.hasFormatterClass()) { 194 formatterClass = (Class<? extends Formatter>)Class.forName(attributeDefinition.getFormatterClass()); 195 } 196 } 197 if (formatterClass == null) { 198 final java.lang.reflect.Field propertyField = boClass.getDeclaredField(propertyName); 199 if (propertyField != null) { 200 formatterClass = Formatter.findFormatter(propertyField.getType()); 201 } 202 } 203 204 if (formatterClass != null) { 205 formatter = formatterClass.newInstance(); 206 } 207 } catch (SecurityException se) { 208 throw new RuntimeException("Could not retrieve good formatter", se); 209 } catch (ClassNotFoundException cnfe) { 210 throw new RuntimeException("Could not retrieve good formatter", cnfe); 211 } catch (NoSuchFieldException nsfe) { 212 throw new RuntimeException("Could not retrieve good formatter", nsfe); 213 } catch (IllegalAccessException iae) { 214 throw new RuntimeException("Could not retrieve good formatter", iae); 215 } catch (InstantiationException ie) { 216 throw new RuntimeException("Could not retrieve good formatter", ie); 217 } 218 219 return formatter; 220 } 221 222 /** 223 * Looks up the DataDictionary BusinessObjectEntry for the given class 224 * 225 * @param boClass the class of the BusinessObject to find a BusinessObjectEntry for 226 * @return the entry from the data dictionary, or null if nothing was found 227 */ 228 protected BusinessObjectEntry getBusinessObjectEntry(Class<? extends BusinessObject> boClass) { 229 return (BusinessObjectEntry) getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(boClass.getName()); 230 } 231 232 /** 233 * @return the dataDictionaryService 234 */ 235 public DataDictionaryService getDataDictionaryService() { 236 return this.dataDictionaryService; 237 } 238 239 /** 240 * @param dataDictionaryService the dataDictionaryService to set 241 */ 242 public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { 243 this.dataDictionaryService = dataDictionaryService; 244 } 245 }