View Javadoc

1   /*
2    * Copyright 2007-2010 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 java.lang.reflect.InvocationTargetException;
19  import java.util.ArrayList;
20  import java.util.Collection;
21  import java.util.HashMap;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.Set;
25  
26  import org.apache.commons.beanutils.PropertyUtils;
27  import org.apache.commons.lang.StringUtils;
28  import org.kuali.rice.kns.bo.BusinessObject;
29  import org.kuali.rice.kns.bo.SelectedObjectIds;
30  import org.kuali.rice.kns.datadictionary.AttributeDefinition;
31  import org.kuali.rice.kns.datadictionary.BusinessObjectEntry;
32  import org.kuali.rice.kns.exception.AuthorizationException;
33  import org.kuali.rice.kns.service.DataDictionaryService;
34  import org.kuali.rice.kns.service.KNSServiceLocator;
35  import org.kuali.rice.kns.web.format.Formatter;
36  
37  /**
38   * LookupResults support strategy which uses the primary keys and lookupable defined in a business object's data dictionary file to support the multivalue lookup 
39   * 
40   * @author Kuali Rice Team (rice.collab@kuali.org)
41   *
42   */
43  public class DataDictionaryLookupResultsSupportStrategy implements
44  		LookupResultsSupportStrategyService {
45  	
46  	private DataDictionaryService dataDictionaryService;
47  
48  	/**
49  	 * Builds a lookup id for the given business object
50  	 * @see org.kuali.rice.kns.lookup.LookupResultsSupportStrategyService#getLookupIdForBusinessObject(org.kuali.rice.kns.bo.BusinessObject)
51  	 */
52  	public String getLookupIdForBusinessObject(BusinessObject businessObject) {
53  		final List<String> pkFieldNames = getPrimaryKeyFieldsForBusinessObject(businessObject.getClass());
54  		return convertPKFieldMapToLookupId(pkFieldNames, businessObject);
55  	}
56  
57  	/**
58  	 * 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
59  	 * @see org.kuali.rice.kns.lookup.LookupResultsSupportStrategyService#qualifiesForStrategy(java.lang.Class)
60  	 */
61  	public boolean qualifiesForStrategy(Class<? extends BusinessObject> boClass) {
62  		if (getLookupableForBusinessObject(boClass) == null) {
63  			return false; // this probably isn't going to happen, but still...
64  		}
65  		final List<String> pkFields = getPrimaryKeyFieldsForBusinessObject(boClass);
66  		return pkFields != null && pkFields.size() > 0;
67  	}
68  
69  	/**
70  	 * Uses the Lookupable associated with the given BusinessObject to search for business objects
71  	 * @see org.kuali.rice.kns.lookup.LookupResultsSupportStrategyService#retrieveSelectedResultBOs(java.lang.String, java.lang.Class, java.lang.String, org.kuali.rice.kns.lookup.LookupResultsService)
72  	 */
73  	public <T extends BusinessObject> Collection<T> retrieveSelectedResultBOs(Class<T> boClass, Set<String> lookupIds) throws Exception {
74          
75          List<T> retrievedBusinessObjects = new ArrayList<T>();
76          final Lookupable lookupable = getLookupableForBusinessObject(boClass);
77          for (String lookupId : lookupIds) {
78          	final Map<String, String> lookupKeys = convertLookupIdToPKFieldMap(lookupId, boClass);
79          	List<BusinessObject> bos = lookupable.getSearchResults(lookupKeys);
80          	
81          	// we should only get one business object...but let's put them all in...
82          	for (BusinessObject bo : bos) {
83          		retrievedBusinessObjects.add((T)bo);
84          	}
85          }
86  		
87  		return retrievedBusinessObjects;
88  	}
89  	
90  	/**
91  	 * Retrieves the Lookupable for the given business object class
92  	 * 
93  	 * @param businessObjectClass the class to find the Lookupable for
94  	 * @return the Lookupable, or null if nothing could be found
95  	 */
96  	protected Lookupable getLookupableForBusinessObject(Class<? extends BusinessObject> businessObjectClass) {
97  		final BusinessObjectEntry boEntry = getBusinessObjectEntry(businessObjectClass);
98  		if (boEntry == null) {
99  			return null;
100 		}
101 		
102 		final String lookupableId = boEntry.getLookupDefinition().getLookupableID();
103 		return KNSServiceLocator.getLookupable(lookupableId);
104 	}
105 	
106 	/**
107 	 * Returns the data dictionary defined primary keys for the given BusinessObject
108 	 * 
109 	 * @param businessObjectClass the business object to get DataDictionary defined primary keys for
110 	 * @return the List of primary key property names, or null if nothing could be found
111 	 */
112 	protected List<String> getPrimaryKeyFieldsForBusinessObject(Class<? extends BusinessObject> businessObjectClass) {
113 		final BusinessObjectEntry boEntry = getBusinessObjectEntry(businessObjectClass);
114 		if (boEntry == null) {
115 			return null;
116 		}
117 		return boEntry.getPrimaryKeys();
118 	}
119 	
120 	/**
121 	 * Converts a lookup id into a PK field map to use to search in a lookupable
122 	 * 
123 	 * @param lookupId the id returned by the lookup
124 	 * @param businessObjectClass the class of the business object getting the primary key
125 	 * @return a Map of field names and values which can be profitably used to search for matching business objects
126 	 */
127 	protected Map<String, String> convertLookupIdToPKFieldMap(String lookupId, Class<? extends BusinessObject> businessObjectClass) {
128 		Map<String, String> pkFields = new HashMap<String, String>();
129 		if (!StringUtils.isBlank(lookupId)) {
130 			final String[] pkValues = lookupId.split("\\|");
131 			for (String pkValue : pkValues) {
132 				if (!StringUtils.isBlank(pkValue)) {
133 					final String[] pkPieces = pkValue.split("-");
134 					if (!StringUtils.isBlank(pkPieces[0]) && !StringUtils.isBlank(pkPieces[1])) {
135 						pkFields.put(pkPieces[0], pkPieces[1]);
136 					}
137 				}
138 			}
139 		}
140 		return pkFields;
141 	}
142 	
143 	/**
144 	 * Converts a Map of PKFields into a String lookup ID
145 	 * @param pkFieldNames the name of the PK fields, which should be converted to the given lookupId
146 	 * @param businessObjectClass the class of the business object getting the primary key
147 	 * @return the String lookup id
148 	 */
149 	protected String convertPKFieldMapToLookupId(List<String> pkFieldNames, BusinessObject businessObject) {
150 		StringBuilder lookupId = new StringBuilder();
151 		for (String pkFieldName : pkFieldNames) {
152 			try {
153 				final Object value = PropertyUtils.getProperty(businessObject, pkFieldName);
154 				
155 				if (value != null) {
156 					lookupId.append(pkFieldName);
157 					lookupId.append("-");
158 					final Formatter formatter = retrieveBestFormatter(pkFieldName, businessObject.getClass());
159 					final String formattedValue = (formatter != null) ? formatter.format(value).toString() : value.toString();
160 					
161 					lookupId.append(formattedValue);
162 				}
163 				lookupId.append("|");
164 			} catch (IllegalAccessException iae) {
165 				throw new RuntimeException("Could not retrieve pk field value "+pkFieldName+" from business object "+businessObject.getClass().getName(), iae);
166 			} catch (InvocationTargetException ite) {
167 				throw new RuntimeException("Could not retrieve pk field value "+pkFieldName+" from business object "+businessObject.getClass().getName(), ite);
168 			} catch (NoSuchMethodException nsme) {
169 				throw new RuntimeException("Could not retrieve pk field value "+pkFieldName+" from business object "+businessObject.getClass().getName(), nsme);
170 			}
171 		}
172 		return lookupId.substring(0, lookupId.length() - 1); // kill the last "|"
173 	}
174 	
175 	/**
176 	 * 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
177 	 * goes through the stuff drawer of KNS formatters and attempts to return you a good one
178 	 * 
179 	 * @param propertyName the name of the property to retrieve
180 	 * @param boClass the class of the BusinessObject the property is on
181 	 * @return a Formatter, or null if we were unsuccessful in finding
182 	 */
183 	protected Formatter retrieveBestFormatter(String propertyName, Class<? extends BusinessObject> boClass) {
184 		Formatter formatter = null;
185 		
186 		try {
187 			Class<? extends Formatter> formatterClass = null;
188 			
189 			final BusinessObjectEntry boEntry = getBusinessObjectEntry(boClass);
190 			if (boEntry != null) {
191 				final AttributeDefinition attributeDefinition = boEntry.getAttributeDefinition(propertyName);
192 				if (attributeDefinition != null && attributeDefinition.hasFormatterClass()) {
193 					formatterClass = (Class<? extends Formatter>)Class.forName(attributeDefinition.getFormatterClass());
194 				}
195 			}
196 			if (formatterClass == null) {
197 				final java.lang.reflect.Field propertyField = boClass.getDeclaredField(propertyName);
198 				if (propertyField != null) {
199 					formatterClass = Formatter.findFormatter(propertyField.getType());
200 				}
201 			}
202 			
203 			if (formatterClass != null) {
204 				formatter = formatterClass.newInstance();
205 			}
206 		} catch (SecurityException se) {
207 			throw new RuntimeException("Could not retrieve good formatter", se);
208 		} catch (ClassNotFoundException cnfe) {
209 			throw new RuntimeException("Could not retrieve good formatter", cnfe);
210 		} catch (NoSuchFieldException nsfe) {
211 			throw new RuntimeException("Could not retrieve good formatter", nsfe);
212 		} catch (IllegalAccessException iae) {
213 			throw new RuntimeException("Could not retrieve good formatter", iae);
214 		} catch (InstantiationException ie) {
215 			throw new RuntimeException("Could not retrieve good formatter", ie);
216 		}
217 		
218 		return formatter;
219 	}
220 	
221 	/**
222 	 * Looks up the DataDictionary BusinessObjectEntry for the given class
223 	 * 
224 	 * @param boClass the class of the BusinessObject to find a BusinessObjectEntry for
225 	 * @return the entry from the data dictionary, or null if nothing was found
226 	 */
227 	protected BusinessObjectEntry getBusinessObjectEntry(Class<? extends BusinessObject> boClass) {
228 		return getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(boClass.getName());
229 	}
230 
231 	/**
232 	 * @return the dataDictionaryService
233 	 */
234 	public DataDictionaryService getDataDictionaryService() {
235 		return this.dataDictionaryService;
236 	}
237 
238 	/**
239 	 * @param dataDictionaryService the dataDictionaryService to set
240 	 */
241 	public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
242 		this.dataDictionaryService = dataDictionaryService;
243 	}
244 }