001 /**
002 * Copyright 2005-2012 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<? extends 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 }