View Javadoc

1   /*
2    * Copyright 2007 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 1.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/ecl1.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.krad.inquiry;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.CoreApiServiceLocator;
20  import org.kuali.rice.core.api.config.property.ConfigurationService;
21  import org.kuali.rice.core.api.encryption.EncryptionService;
22  import org.kuali.rice.krad.bo.BusinessObject;
23  import org.kuali.rice.krad.bo.BusinessObjectRelationship;
24  import org.kuali.rice.krad.bo.DocumentHeader;
25  import org.kuali.rice.krad.bo.ExternalizableBusinessObject;
26  import org.kuali.rice.krad.datadictionary.exception.UnknownBusinessClassAttributeException;
27  import org.kuali.rice.krad.service.BusinessObjectService;
28  import org.kuali.rice.krad.service.DataDictionaryService;
29  import org.kuali.rice.krad.service.DataObjectAuthorizationService;
30  import org.kuali.rice.krad.service.DataObjectMetaDataService;
31  import org.kuali.rice.krad.service.KRADServiceLocator;
32  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
33  import org.kuali.rice.krad.service.KualiModuleService;
34  import org.kuali.rice.krad.service.ModuleService;
35  import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl;
36  import org.kuali.rice.krad.uif.widget.Inquiry;
37  import org.kuali.rice.krad.util.ExternalizableBusinessObjectUtils;
38  import org.kuali.rice.krad.util.KRADConstants;
39  import org.kuali.rice.krad.util.ObjectUtils;
40  
41  import java.security.GeneralSecurityException;
42  import java.util.ArrayList;
43  import java.util.Collections;
44  import java.util.HashMap;
45  import java.util.List;
46  import java.util.Map;
47  
48  /**
49   * Implementation of the <code>Inquirable</code> interface that uses metadata
50   * from the data dictionary and performs a query against the database to retrieve
51   * the data object for inquiry
52   *
53   * <p>
54   * More advanced lookup operations or alternate ways of retrieving metadata can
55   * be implemented by extending this base implementation and configuring
56   * </p>
57   *
58   * @author Kuali Rice Team (rice.collab@kuali.org)
59   */
60  public class InquirableImpl extends ViewHelperServiceImpl implements Inquirable {
61      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(InquirableImpl.class);
62  
63      protected Class<?> dataObjectClass;
64  
65      /**
66       * A list that can be used to define classes that are superclasses or
67       * superinterfaces of kuali objects where those objects' inquiry URLs need
68       * to use the name of the superclass or superinterface as the business
69       * object class attribute (see
70       * {@link RiceConstants#BUSINESS_OBJECT_CLASS_ATTRIBUTE)
71       */
72      public static List<Class<?>> SUPER_CLASS_TRANSLATOR_LIST = new ArrayList<Class<?>>();
73  
74      private ConfigurationService configurationService;
75      private DataObjectMetaDataService dataObjectMetaDataService;
76      private KualiModuleService kualiModuleService;
77      private DataDictionaryService dataDictionaryService;
78      private DataObjectAuthorizationService dataObjectAuthorizationService;
79      private EncryptionService encryptionService;
80      private BusinessObjectService businessObjectService;
81  
82      /**
83       * Finds primary and alternate key sets configured for the configured data object class and
84       * then attempts to find a set with matching key/value pairs from the request, if a set is
85       * found then calls the module service (for EBOs) or business object service to retrieve
86       * the data object
87       *
88       * <p>
89       * Note at this point on business objects are supported by the default implementation
90       * </p>
91       *
92       * @see Inquirable#retrieveDataObject(java.util.Map<java.lang.String,java.lang.String>)
93       */
94      @Override
95      public Object retrieveDataObject(Map<String, String> parameters) {
96          if (dataObjectClass == null) {
97              LOG.error("Data object class must be set in inquirable before retrieving the object");
98              throw new RuntimeException("Data object class must be set in inquirable before retrieving the object");
99          }
100 
101         // build list of key values from the map parameters
102         List<String> pkPropertyNames = getDataObjectMetaDataService().listPrimaryKeyFieldNames(dataObjectClass);
103 
104         // some classes might have alternate keys defined for retrieving
105         List<List<String>> alternateKeyNames = this.getAlternateKeysForClass(dataObjectClass);
106 
107         // add pk set as beginning so it will be checked first for match
108         alternateKeyNames.add(0, pkPropertyNames);
109 
110         List<String> dataObjectKeySet = retrieveKeySetFromMap(alternateKeyNames, parameters);
111         if ((dataObjectKeySet == null) || dataObjectKeySet.isEmpty()) {
112             LOG.warn("Matching key set not found in request for class: " + getDataObjectClass());
113 
114             return null;
115         }
116 
117         // found key set, now build map of key values pairs we can use to retrieve the object
118         Map<String, Object> keyPropertyValues = new HashMap<String, Object>();
119         for (String keyPropertyName : dataObjectKeySet) {
120             String keyPropertyValue = parameters.get(keyPropertyName);
121 
122             // uppercase value if needed
123             Boolean forceUppercase = Boolean.FALSE;
124             try {
125                 forceUppercase = dataDictionaryService.getAttributeForceUppercase(dataObjectClass, keyPropertyName);
126             } catch (UnknownBusinessClassAttributeException ex) {
127                 // swallowing exception because this check for ForceUppercase would
128                 // require a DD entry for the attribute, and we will just set force uppercase to false
129                 LOG.warn("Data object class " + dataObjectClass + " property " + keyPropertyName +
130                         " should probably have a DD definition.", ex);
131             }
132 
133             if (forceUppercase.booleanValue()) {
134                 keyPropertyName = keyPropertyName.toUpperCase();
135             }
136 
137             // check security on key field
138             if (getDataObjectAuthorizationService()
139                     .attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObjectClass, keyPropertyName)) {
140                 try {
141                     keyPropertyValue = encryptionService.decrypt(keyPropertyValue);
142                 } catch (GeneralSecurityException e) {
143                     LOG.error("Data object class " + dataObjectClass + " property " + keyPropertyName +
144                             " should have been encrypted, but there was a problem decrypting it.", e);
145                     throw new RuntimeException("Data object class " + dataObjectClass + " property " + keyPropertyName +
146                             " should have been encrypted, but there was a problem decrypting it.", e);
147                 }
148             }
149 
150             keyPropertyValues.put(keyPropertyName, keyPropertyValue);
151         }
152 
153         // now retrieve the object based on the key set
154         Object dataObject = null;
155 
156         ModuleService moduleService =
157                 KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(getDataObjectClass());
158         if (moduleService != null && moduleService.isExternalizable(getDataObjectClass())) {
159             dataObject = moduleService.getExternalizableBusinessObject(
160                     getDataObjectClass().asSubclass(ExternalizableBusinessObject.class), keyPropertyValues);
161         } else if (BusinessObject.class.isAssignableFrom(getDataObjectClass())) {
162             dataObject = getBusinessObjectService()
163                     .findByPrimaryKey(getDataObjectClass().asSubclass(BusinessObject.class), keyPropertyValues);
164         }
165 
166         return dataObject;
167     }
168 
169     /**
170      * Iterates through the list of key sets looking for a set where the given map of parameters has
171      * all the key names and values are non-blank, first matched set is returned
172      *
173      * @param potentialKeySets - List of key sets to check for match
174      * @param parameters - map of parameter name/value pairs for matching key set
175      * @return List<String> key set that was matched, or null if none were matched
176      */
177     protected List<String> retrieveKeySetFromMap(List<List<String>> potentialKeySets, Map<String, String> parameters) {
178         List<String> foundKeySet = null;
179 
180         for (List<String> potentialKeySet : potentialKeySets) {
181             boolean keySetMatch = true;
182             for (String keyName : potentialKeySet) {
183                 if (!parameters.containsKey(keyName) || StringUtils.isBlank(parameters.get(keyName))) {
184                     keySetMatch = false;
185                 }
186             }
187 
188             if (keySetMatch) {
189                 foundKeySet = potentialKeySet;
190                 break;
191             }
192         }
193 
194         return foundKeySet;
195     }
196 
197     /**
198      * Invokes the module service to retrieve any alternate keys that have been
199      * defined for the given class
200      *
201      * @param clazz - class to find alternate keys for
202      * @return List<List<String>> list of alternate key sets, or empty list if none are found
203      */
204     protected List<List<String>> getAlternateKeysForClass(Class<?> clazz) {
205         KualiModuleService kualiModuleService = getKualiModuleService();
206         ModuleService moduleService = kualiModuleService.getResponsibleModuleService(clazz);
207 
208         List<List<String>> altKeys = null;
209         if (moduleService != null) {
210             altKeys = moduleService.listAlternatePrimaryKeyFieldNames(clazz);
211         }
212 
213         return altKeys != null ? altKeys : new ArrayList<List<String>>();
214     }
215 
216     /**
217      * @see Inquirable#buildInquirableLink(java.lang.Object,
218      *      java.lang.String, org.kuali.rice.krad.uif.widget.Inquiry)
219      */
220     @Override
221     public void buildInquirableLink(Object dataObject, String propertyName, Inquiry inquiry) {
222         Class<?> inquiryObjectClass = null;
223 
224         // inquiry into data object class if property is title attribute
225         Class<?> objectClass = ObjectUtils.materializeClassForProxiedObject(dataObject);
226         if (propertyName.equals(getDataObjectMetaDataService().getTitleAttribute(objectClass))) {
227             inquiryObjectClass = objectClass;
228         } else if (ObjectUtils.isNestedAttribute(propertyName)) {
229             String nestedPropertyName = ObjectUtils.getNestedAttributePrefix(propertyName);
230             Object nestedPropertyObject = ObjectUtils.getNestedValue(dataObject, nestedPropertyName);
231 
232             if (ObjectUtils.isNotNull(nestedPropertyObject)) {
233                 String nestedPropertyPrimitive = ObjectUtils.getNestedAttributePrimitive(propertyName);
234                 Class<?> nestedPropertyObjectClass = ObjectUtils.materializeClassForProxiedObject(nestedPropertyObject);
235 
236                 if (nestedPropertyPrimitive
237                         .equals(getDataObjectMetaDataService().getTitleAttribute(nestedPropertyObjectClass))) {
238                     inquiryObjectClass = nestedPropertyObjectClass;
239                 }
240             }
241         }
242 
243         // if not title, then get primary relationship
244         BusinessObjectRelationship relationship = null;
245         if (inquiryObjectClass == null) {
246             relationship = getDataObjectMetaDataService()
247                     .getDataObjectRelationship(dataObject, objectClass, propertyName, "", true, false, true);
248             if (relationship != null) {
249                 inquiryObjectClass = relationship.getRelatedClass();
250             }
251         }
252 
253         // if haven't found inquiry class, then no inquiry can be rendered
254         if (inquiryObjectClass == null) {
255             inquiry.setRender(false);
256 
257             return;
258         }
259 
260         if (DocumentHeader.class.isAssignableFrom(inquiryObjectClass)) {
261             String documentNumber = (String) ObjectUtils.getPropertyValue(dataObject, propertyName);
262             if (StringUtils.isNotBlank(documentNumber)) {
263                 inquiry.getInquiryLinkField().setHrefText(
264                         getConfigurationService().getPropertyString(KRADConstants.WORKFLOW_URL_KEY) +
265                                 KRADConstants.DOCHANDLER_DO_URL + documentNumber + KRADConstants.DOCHANDLER_URL_CHUNK);
266                 inquiry.getInquiryLinkField().setLinkLabel(documentNumber);
267                 inquiry.setRender(true);
268             }
269 
270             return;
271         }
272 
273         synchronized (SUPER_CLASS_TRANSLATOR_LIST) {
274             for (Class<?> clazz : SUPER_CLASS_TRANSLATOR_LIST) {
275                 if (clazz.isAssignableFrom(inquiryObjectClass)) {
276                     inquiryObjectClass = clazz;
277                     break;
278                 }
279             }
280         }
281 
282         if (!inquiryObjectClass.isInterface() &&
283                 ExternalizableBusinessObject.class.isAssignableFrom(inquiryObjectClass)) {
284             inquiryObjectClass = ExternalizableBusinessObjectUtils
285                     .determineExternalizableBusinessObjectSubInterface(inquiryObjectClass);
286         }
287 
288         // listPrimaryKeyFieldNames returns an unmodifiable list. So a copy is
289         // necessary.
290         List<String> keys =
291                 new ArrayList<String>(getDataObjectMetaDataService().listPrimaryKeyFieldNames(inquiryObjectClass));
292 
293         if (keys == null) {
294             keys = Collections.emptyList();
295         }
296 
297         // build inquiry parameter mappings
298         Map<String, String> inquiryParameters = new HashMap<String, String>();
299         for (String keyName : keys) {
300             String keyConversion = keyName;
301             if (relationship != null) {
302                 keyConversion = relationship.getChildAttributeForParentAttribute(keyName);
303             } else if (ObjectUtils.isNestedAttribute(propertyName)) {
304                 String nestedAttributePrefix = ObjectUtils.getNestedAttributePrefix(propertyName);
305                 keyConversion = nestedAttributePrefix + "." + keyName;
306             }
307 
308             inquiryParameters.put(keyConversion, keyName);
309         }
310 
311         inquiry.buildInquiryLink(dataObject, propertyName, inquiryObjectClass, inquiryParameters);
312     }
313 
314     /**
315      * @see Inquirable#setDataObjectClass(java.lang.Class)
316      */
317     @Override
318     public void setDataObjectClass(Class<?> dataObjectClass) {
319         this.dataObjectClass = dataObjectClass;
320     }
321 
322     /**
323      * Retrieves the data object class configured for this inquirable
324      *
325      * @return Class<?> of configured data object, or null if data object class not configured
326      */
327     protected Class<?> getDataObjectClass() {
328         return this.dataObjectClass;
329     }
330 
331     protected ConfigurationService getConfigurationService() {
332         if (configurationService == null) {
333             configurationService = KRADServiceLocator.getKualiConfigurationService();
334         }
335         return this.configurationService;
336     }
337 
338     public void setConfigurationService(ConfigurationService configurationService) {
339         this.configurationService = configurationService;
340     }
341 
342     protected DataObjectMetaDataService getDataObjectMetaDataService() {
343         if (dataObjectMetaDataService == null) {
344             this.dataObjectMetaDataService = KRADServiceLocatorWeb.getDataObjectMetaDataService();
345         }
346         return dataObjectMetaDataService;
347     }
348 
349     public void setDataObjectMetaDataService(DataObjectMetaDataService dataObjectMetaDataService) {
350         this.dataObjectMetaDataService = dataObjectMetaDataService;
351     }
352 
353     protected KualiModuleService getKualiModuleService() {
354         if (kualiModuleService == null) {
355             this.kualiModuleService = KRADServiceLocatorWeb.getKualiModuleService();
356         }
357         return kualiModuleService;
358     }
359 
360     public void setKualiModuleService(KualiModuleService kualiModuleService) {
361         this.kualiModuleService = kualiModuleService;
362     }
363 
364     protected DataDictionaryService getDataDictionaryService() {
365         if (dataDictionaryService == null) {
366             this.dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
367         }
368         return dataDictionaryService;
369     }
370 
371     public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
372         this.dataDictionaryService = dataDictionaryService;
373     }
374 
375     protected DataObjectAuthorizationService getDataObjectAuthorizationService() {
376         if (dataObjectAuthorizationService == null) {
377             this.dataObjectAuthorizationService = KRADServiceLocatorWeb.getDataObjectAuthorizationService();
378         }
379         return dataObjectAuthorizationService;
380     }
381 
382     public void setDataObjectAuthorizationService(DataObjectAuthorizationService dataObjectAuthorizationService) {
383         this.dataObjectAuthorizationService = dataObjectAuthorizationService;
384     }
385 
386     protected EncryptionService getEncryptionService() {
387         if (encryptionService == null) {
388             this.encryptionService = CoreApiServiceLocator.getEncryptionService();
389         }
390         return encryptionService;
391     }
392 
393     public void setEncryptionService(EncryptionService encryptionService) {
394         this.encryptionService = encryptionService;
395     }
396 
397     protected BusinessObjectService getBusinessObjectService() {
398         if (businessObjectService == null) {
399             this.businessObjectService = KRADServiceLocator.getBusinessObjectService();
400         }
401         return businessObjectService;
402     }
403 
404     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
405         this.businessObjectService = businessObjectService;
406     }
407 }