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