View Javadoc

1   /**
2    * Copyright 2005-2013 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.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.DataObjectRelationship;
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
70       */
71      public static List<Class<?>> SUPER_CLASS_TRANSLATOR_LIST = new ArrayList<Class<?>>();
72  
73      /**
74       * Finds primary and alternate key sets configured for the configured data object class and
75       * then attempts to find a set with matching key/value pairs from the request, if a set is
76       * found then calls the module service (for EBOs) or business object service to retrieve
77       * the data object
78       *
79       * <p>
80       * Note at this point on business objects are supported by the default implementation
81       * </p>
82       *
83       * @see Inquirable#retrieveDataObject(java.util.Map<java.lang.String,java.lang.String>)
84       */
85      @Override
86      public Object retrieveDataObject(Map<String, String> parameters) {
87          if (dataObjectClass == null) {
88              LOG.error("Data object class must be set in inquirable before retrieving the object");
89              throw new RuntimeException("Data object class must be set in inquirable before retrieving the object");
90          }
91  
92          // build list of key values from the map parameters
93          List<String> pkPropertyNames = getDataObjectMetaDataService().listPrimaryKeyFieldNames(dataObjectClass);
94  
95          // some classes might have alternate keys defined for retrieving
96          List<List<String>> alternateKeyNames = this.getAlternateKeysForClass(dataObjectClass);
97  
98          // add pk set as beginning so it will be checked first for match
99          alternateKeyNames.add(0, pkPropertyNames);
100 
101         List<String> dataObjectKeySet = retrieveKeySetFromMap(alternateKeyNames, parameters);
102         if ((dataObjectKeySet == null) || dataObjectKeySet.isEmpty()) {
103             LOG.warn("Matching key set not found in request for class: " + getDataObjectClass());
104 
105             return null;
106         }
107 
108         // found key set, now build map of key values pairs we can use to retrieve the object
109         Map<String, Object> keyPropertyValues = new HashMap<String, Object>();
110         for (String keyPropertyName : dataObjectKeySet) {
111             String keyPropertyValue = parameters.get(keyPropertyName);
112 
113             // uppercase value if needed
114             Boolean forceUppercase = Boolean.FALSE;
115             try {
116                 forceUppercase = getDataDictionaryService().getAttributeForceUppercase(dataObjectClass,
117                         keyPropertyName);
118             } catch (UnknownBusinessClassAttributeException ex) {
119                 // swallowing exception because this check for ForceUppercase would
120                 // require a DD entry for the attribute, and we will just set force uppercase to false
121                 LOG.warn("Data object class "
122                         + dataObjectClass
123                         + " property "
124                         + keyPropertyName
125                         + " should probably have a DD definition.", ex);
126             }
127 
128             if (forceUppercase.booleanValue() && (keyPropertyValue != null)) {
129                 keyPropertyValue = keyPropertyValue.toUpperCase();
130             }
131 
132             // check security on key field
133             if (getDataObjectAuthorizationService().attributeValueNeedsToBeEncryptedOnFormsAndLinks(dataObjectClass,
134                     keyPropertyName)) {
135                 try {
136                     if(CoreApiServiceLocator.getEncryptionService().isEnabled()) {
137                         keyPropertyValue = getEncryptionService().decrypt(keyPropertyValue);
138                     }
139                 } catch (GeneralSecurityException e) {
140                     LOG.error("Data object class "
141                             + dataObjectClass
142                             + " property "
143                             + keyPropertyName
144                             + " should have been encrypted, but there was a problem decrypting it.", e);
145                     throw new RuntimeException("Data object class "
146                             + dataObjectClass
147                             + " property "
148                             + keyPropertyName
149                             + " should have been encrypted, but there was a problem decrypting it.", e);
150                 }
151             }
152 
153             keyPropertyValues.put(keyPropertyName, keyPropertyValue);
154         }
155 
156         // now retrieve the object based on the key set
157         Object dataObject = null;
158 
159         ModuleService moduleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService(
160                 getDataObjectClass());
161         if (moduleService != null && moduleService.isExternalizable(getDataObjectClass())) {
162             dataObject = moduleService.getExternalizableBusinessObject(getDataObjectClass().asSubclass(
163                     ExternalizableBusinessObject.class), keyPropertyValues);
164         } else if (BusinessObject.class.isAssignableFrom(getDataObjectClass())) {
165             dataObject = getBusinessObjectService().findByPrimaryKey(getDataObjectClass().asSubclass(
166                     BusinessObject.class), keyPropertyValues);
167         }
168 
169         return dataObject;
170     }
171 
172     /**
173      * Iterates through the list of key sets looking for a set where the given map of parameters has
174      * all the key names and values are non-blank, first matched set is returned
175      *
176      * @param potentialKeySets - List of key sets to check for match
177      * @param parameters - map of parameter name/value pairs for matching key set
178      * @return List<String> key set that was matched, or null if none were matched
179      */
180     protected List<String> retrieveKeySetFromMap(List<List<String>> potentialKeySets, Map<String, String> parameters) {
181         List<String> foundKeySet = null;
182 
183         for (List<String> potentialKeySet : potentialKeySets) {
184             boolean keySetMatch = true;
185             for (String keyName : potentialKeySet) {
186                 if (!parameters.containsKey(keyName) || StringUtils.isBlank(parameters.get(keyName))) {
187                     keySetMatch = false;
188                 }
189             }
190 
191             if (keySetMatch) {
192                 foundKeySet = potentialKeySet;
193                 break;
194             }
195         }
196 
197         return foundKeySet;
198     }
199 
200     /**
201      * Invokes the module service to retrieve any alternate keys that have been
202      * defined for the given class
203      *
204      * @param clazz - class to find alternate keys for
205      * @return List<List<String>> list of alternate key sets, or empty list if none are found
206      */
207     protected List<List<String>> getAlternateKeysForClass(Class<?> clazz) {
208         KualiModuleService kualiModuleService = getKualiModuleService();
209         ModuleService moduleService = kualiModuleService.getResponsibleModuleService(clazz);
210 
211         List<List<String>> altKeys = null;
212         if (moduleService != null) {
213             altKeys = moduleService.listAlternatePrimaryKeyFieldNames(clazz);
214         }
215 
216         return altKeys != null ? altKeys : new ArrayList<List<String>>();
217     }
218 
219     /**
220      * @see Inquirable#buildInquirableLink(java.lang.Object,
221      *      java.lang.String, org.kuali.rice.krad.uif.widget.Inquiry)
222      */
223     @Override
224     public void buildInquirableLink(Object dataObject, String propertyName, Inquiry inquiry) {
225         Class<?> inquiryObjectClass = null;
226 
227         // inquiry into data object class if property is title attribute
228         Class<?> objectClass = ObjectUtils.materializeClassForProxiedObject(dataObject);
229         if (propertyName.equals(getDataObjectMetaDataService().getTitleAttribute(objectClass))) {
230             inquiryObjectClass = objectClass;
231         } else if (ObjectUtils.isNestedAttribute(propertyName)) {
232             String nestedPropertyName = ObjectUtils.getNestedAttributePrefix(propertyName);
233             Object nestedPropertyObject = ObjectUtils.getNestedValue(dataObject, nestedPropertyName);
234 
235             if (ObjectUtils.isNotNull(nestedPropertyObject)) {
236                 String nestedPropertyPrimitive = ObjectUtils.getNestedAttributePrimitive(propertyName);
237                 Class<?> nestedPropertyObjectClass = ObjectUtils.materializeClassForProxiedObject(nestedPropertyObject);
238 
239                 if (nestedPropertyPrimitive.equals(getDataObjectMetaDataService().getTitleAttribute(
240                         nestedPropertyObjectClass))) {
241                     inquiryObjectClass = nestedPropertyObjectClass;
242                 }
243             }
244         }
245 
246         // if not title, then get primary relationship
247         DataObjectRelationship relationship = null;
248         if (inquiryObjectClass == null) {
249             relationship = getDataObjectMetaDataService().getDataObjectRelationship(dataObject, objectClass,
250                     propertyName, "", true, false, true);
251             if (relationship != null) {
252                 inquiryObjectClass = relationship.getRelatedClass();
253             }
254         }
255 
256         // if haven't found inquiry class, then no inquiry can be rendered
257         if (inquiryObjectClass == null) {
258             inquiry.setRender(false);
259 
260             return;
261         }
262 
263         if (DocumentHeader.class.isAssignableFrom(inquiryObjectClass)) {
264             String documentNumber = (String) ObjectUtils.getPropertyValue(dataObject, propertyName);
265             if (StringUtils.isNotBlank(documentNumber)) {
266                 inquiry.getInquiryLinkField().setHrefText(getConfigurationService().getPropertyValueAsString(
267                         KRADConstants.WORKFLOW_URL_KEY)
268                         + KRADConstants.DOCHANDLER_DO_URL
269                         + documentNumber
270                         + KRADConstants.DOCHANDLER_URL_CHUNK);
271                 inquiry.getInquiryLinkField().setLinkLabel(documentNumber);
272                 inquiry.setRender(true);
273             }
274 
275             return;
276         }
277 
278         synchronized (SUPER_CLASS_TRANSLATOR_LIST) {
279             for (Class<?> clazz : SUPER_CLASS_TRANSLATOR_LIST) {
280                 if (clazz.isAssignableFrom(inquiryObjectClass)) {
281                     inquiryObjectClass = clazz;
282                     break;
283                 }
284             }
285         }
286 
287         if (!inquiryObjectClass.isInterface() && ExternalizableBusinessObject.class.isAssignableFrom(
288                 inquiryObjectClass)) {
289             inquiryObjectClass = ExternalizableBusinessObjectUtils.determineExternalizableBusinessObjectSubInterface(
290                     inquiryObjectClass);
291         }
292 
293         // listPrimaryKeyFieldNames returns an unmodifiable list. So a copy is necessary.
294         List<String> keys = new ArrayList<String>(getDataObjectMetaDataService().listPrimaryKeyFieldNames(
295                 inquiryObjectClass));
296 
297         if (keys == null) {
298             keys = Collections.emptyList();
299         }
300 
301         // build inquiry parameter mappings
302         Map<String, String> inquiryParameters = new HashMap<String, String>();
303         for (String keyName : keys) {
304             String keyConversion = keyName;
305             if (relationship != null) {
306                 keyConversion = relationship.getParentAttributeForChildAttribute(keyName);
307             } else if (ObjectUtils.isNestedAttribute(propertyName)) {
308                 String nestedAttributePrefix = ObjectUtils.getNestedAttributePrefix(propertyName);
309                 keyConversion = nestedAttributePrefix + "." + keyName;
310             }
311 
312             inquiryParameters.put(keyConversion, keyName);
313         }
314 
315         inquiry.buildInquiryLink(dataObject, propertyName, inquiryObjectClass, inquiryParameters);
316     }
317 
318     /**
319      * @see Inquirable#setDataObjectClass(java.lang.Class)
320      */
321     @Override
322     public void setDataObjectClass(Class<?> dataObjectClass) {
323         this.dataObjectClass = dataObjectClass;
324     }
325 
326     /**
327      * Retrieves the data object class configured for this inquirable
328      *
329      * @return Class<?> of configured data object, or null if data object class not configured
330      */
331     protected Class<?> getDataObjectClass() {
332         return this.dataObjectClass;
333     }
334 
335     protected ConfigurationService getConfigurationService() {
336         return KRADServiceLocator.getKualiConfigurationService();
337     }
338 
339     protected DataObjectMetaDataService getDataObjectMetaDataService() {
340         return KRADServiceLocatorWeb.getDataObjectMetaDataService();
341     }
342 
343     protected KualiModuleService getKualiModuleService() {
344         return KRADServiceLocatorWeb.getKualiModuleService();
345     }
346 
347     protected DataDictionaryService getDataDictionaryService() {
348         return KRADServiceLocatorWeb.getDataDictionaryService();
349     }
350 
351     protected DataObjectAuthorizationService getDataObjectAuthorizationService() {
352         return KRADServiceLocatorWeb.getDataObjectAuthorizationService();
353     }
354 
355     protected EncryptionService getEncryptionService() {
356         return CoreApiServiceLocator.getEncryptionService();
357     }
358 
359     protected BusinessObjectService getBusinessObjectService() {
360         return KRADServiceLocator.getBusinessObjectService();
361     }
362 
363 }