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    }