View Javadoc
1   /**
2    * Copyright 2005-2015 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.kns.lookup;
17  
18  import org.apache.commons.beanutils.PropertyUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.search.SearchOperator;
21  import org.kuali.rice.core.web.format.Formatter;
22  import org.kuali.rice.kns.service.KNSServiceLocator;
23  import org.kuali.rice.krad.bo.BusinessObject;
24  import org.kuali.rice.krad.datadictionary.AttributeDefinition;
25  import org.kuali.rice.kns.datadictionary.BusinessObjectEntry;
26  import org.kuali.rice.krad.service.DataDictionaryService;
27  import org.kuali.rice.krad.service.KRADServiceLocator;
28  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
29  
30  import java.lang.reflect.InvocationTargetException;
31  import java.util.ArrayList;
32  import java.util.Collection;
33  import java.util.HashMap;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.Set;
37  
38  /**
39   * LookupResults support strategy which uses the primary keys and lookupable defined in a business object's data dictionary file to support the multivalue lookup 
40   * 
41   * @author Kuali Rice Team (rice.collab@kuali.org)
42   *
43   */
44  public class DataDictionaryLookupResultsSupportStrategy implements
45  		LookupResultsSupportStrategyService {
46  	
47  	private DataDictionaryService dataDictionaryService;
48  
49  	/**
50  	 * Builds a lookup id for the given business object
51  	 * @see LookupResultsSupportStrategyService#getLookupIdForBusinessObject(org.kuali.rice.krad.bo.BusinessObject)
52  	 */
53  	public String getLookupIdForBusinessObject(BusinessObject businessObject) {
54  		final List<String> pkFieldNames = getPrimaryKeyFieldsForBusinessObject(businessObject.getClass());
55  		return convertPKFieldMapToLookupId(pkFieldNames, businessObject);
56  	}
57  
58  	/**
59  	 * 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
60  	 * @see LookupResultsSupportStrategyService#qualifiesForStrategy(java.lang.Class)
61  	 */
62  	public boolean qualifiesForStrategy(Class<? extends BusinessObject> boClass) {
63  		if (getLookupableForBusinessObject(boClass) == null) {
64  			return false; // this probably isn't going to happen, but still...
65  		}
66  		final List<String> pkFields = getPrimaryKeyFieldsForBusinessObject(boClass);
67  		return pkFields != null && pkFields.size() > 0;
68  	}
69  
70  	/**
71  	 * Uses the Lookupable associated with the given BusinessObject to search for business objects
72  	 * @see LookupResultsSupportStrategyService#retrieveSelectedResultBOs(java.lang.String, java.lang.Class, java.lang.String, org.kuali.rice.krad.lookup.LookupResultsService)
73  	 */
74  	public <T extends BusinessObject> Collection<T> retrieveSelectedResultBOs(Class<T> boClass, Set<String> lookupIds) throws Exception {
75          
76          List<T> retrievedBusinessObjects = new ArrayList<T>();
77          final org.kuali.rice.kns.lookup.Lookupable lookupable = getLookupableForBusinessObject(boClass);
78          for (String lookupId : lookupIds) {
79          	final Map<String, String> lookupKeys = convertLookupIdToPKFieldMap(lookupId, boClass);
80          	List<? extends BusinessObject> bos = lookupable.getSearchResults(lookupKeys);
81          	
82          	// we should only get one business object...but let's put them all in...
83          	for (BusinessObject bo : bos) {
84          		retrievedBusinessObjects.add((T)bo);
85          	}
86          }
87  		
88  		return retrievedBusinessObjects;
89  	}
90  	
91  	/**
92  	 * Retrieves the Lookupable for the given business object class
93  	 * 
94  	 * @param businessObjectClass the class to find the Lookupable for
95  	 * @return the Lookupable, or null if nothing could be found
96  	 */
97  	protected org.kuali.rice.kns.lookup.Lookupable getLookupableForBusinessObject(Class<? extends BusinessObject> businessObjectClass) {
98  		final BusinessObjectEntry boEntry = getBusinessObjectEntry(businessObjectClass);
99  		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 }