001/** 002 * Copyright 2005-2016 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.krad.inquiry; 017 018import org.apache.commons.lang.StringUtils; 019import org.kuali.rice.core.api.CoreApiServiceLocator; 020import org.kuali.rice.core.api.config.property.ConfigurationService; 021import org.kuali.rice.core.api.encryption.EncryptionService; 022import org.kuali.rice.krad.bo.BusinessObject; 023import org.kuali.rice.krad.bo.DataObjectRelationship; 024import org.kuali.rice.krad.bo.DocumentHeader; 025import org.kuali.rice.krad.bo.ExternalizableBusinessObject; 026import org.kuali.rice.krad.datadictionary.exception.UnknownBusinessClassAttributeException; 027import org.kuali.rice.krad.service.BusinessObjectService; 028import org.kuali.rice.krad.service.DataDictionaryService; 029import org.kuali.rice.krad.service.DataObjectAuthorizationService; 030import org.kuali.rice.krad.service.DataObjectMetaDataService; 031import org.kuali.rice.krad.service.KRADServiceLocator; 032import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 033import org.kuali.rice.krad.service.KualiModuleService; 034import org.kuali.rice.krad.service.ModuleService; 035import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl; 036import org.kuali.rice.krad.uif.widget.Inquiry; 037import org.kuali.rice.krad.util.ExternalizableBusinessObjectUtils; 038import org.kuali.rice.krad.util.KRADConstants; 039import org.kuali.rice.krad.util.ObjectUtils; 040 041import java.security.GeneralSecurityException; 042import java.util.ArrayList; 043import java.util.Collections; 044import java.util.HashMap; 045import java.util.List; 046import java.util.Map; 047 048/** 049 * Implementation of the <code>Inquirable</code> interface that uses metadata 050 * from the data dictionary and performs a query against the database to retrieve 051 * the data object for inquiry 052 * 053 * <p> 054 * More advanced lookup operations or alternate ways of retrieving metadata can 055 * be implemented by extending this base implementation and configuring 056 * </p> 057 * 058 * @author Kuali Rice Team (rice.collab@kuali.org) 059 */ 060public class InquirableImpl extends ViewHelperServiceImpl implements Inquirable { 061 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(InquirableImpl.class); 062 063 protected Class<?> dataObjectClass; 064 065 /** 066 * A list that can be used to define classes that are superclasses or 067 * superinterfaces of kuali objects where those objects' inquiry URLs need 068 * to use the name of the superclass or superinterface as the business 069 * object class attribute 070 */ 071 public static List<Class<?>> SUPER_CLASS_TRANSLATOR_LIST = new ArrayList<Class<?>>(); 072 073 /** 074 * Finds primary and alternate key sets configured for the configured data object class and 075 * then attempts to find a set with matching key/value pairs from the request, if a set is 076 * found then calls the module service (for EBOs) or business object service to retrieve 077 * the data object 078 * 079 * <p> 080 * Note at this point on business objects are supported by the default implementation 081 * </p> 082 * 083 * @see Inquirable#retrieveDataObject(java.util.Map<java.lang.String,java.lang.String>) 084 */ 085 @Override 086 public Object retrieveDataObject(Map<String, String> parameters) { 087 if (dataObjectClass == null) { 088 LOG.error("Data object class must be set in inquirable before retrieving the object"); 089 throw new RuntimeException("Data object class must be set in inquirable before retrieving the object"); 090 } 091 092 // build list of key values from the map parameters 093 List<String> pkPropertyNames = getDataObjectMetaDataService().listPrimaryKeyFieldNames(dataObjectClass); 094 095 // some classes might have alternate keys defined for retrieving 096 List<List<String>> alternateKeyNames = this.getAlternateKeysForClass(dataObjectClass); 097 098 // add pk set as beginning so it will be checked first for match 099 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}