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.krad.inquiry; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.kuali.rice.core.api.CoreApiServiceLocator; 020 import org.kuali.rice.core.api.config.property.ConfigurationService; 021 import org.kuali.rice.core.api.encryption.EncryptionService; 022 import org.kuali.rice.krad.bo.BusinessObject; 023 import org.kuali.rice.krad.bo.DataObjectRelationship; 024 import org.kuali.rice.krad.bo.DocumentHeader; 025 import org.kuali.rice.krad.bo.ExternalizableBusinessObject; 026 import org.kuali.rice.krad.datadictionary.exception.UnknownBusinessClassAttributeException; 027 import org.kuali.rice.krad.service.BusinessObjectService; 028 import org.kuali.rice.krad.service.DataDictionaryService; 029 import org.kuali.rice.krad.service.DataObjectAuthorizationService; 030 import org.kuali.rice.krad.service.DataObjectMetaDataService; 031 import org.kuali.rice.krad.service.KRADServiceLocator; 032 import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 033 import org.kuali.rice.krad.service.KualiModuleService; 034 import org.kuali.rice.krad.service.ModuleService; 035 import org.kuali.rice.krad.uif.service.impl.ViewHelperServiceImpl; 036 import org.kuali.rice.krad.uif.widget.Inquiry; 037 import org.kuali.rice.krad.util.ExternalizableBusinessObjectUtils; 038 import org.kuali.rice.krad.util.KRADConstants; 039 import org.kuali.rice.krad.util.ObjectUtils; 040 041 import java.security.GeneralSecurityException; 042 import java.util.ArrayList; 043 import java.util.Collections; 044 import java.util.HashMap; 045 import java.util.List; 046 import 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 */ 060 public 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 keyPropertyValue = getEncryptionService().decrypt(keyPropertyValue); 137 } catch (GeneralSecurityException e) { 138 LOG.error("Data object class " 139 + dataObjectClass 140 + " property " 141 + keyPropertyName 142 + " should have been encrypted, but there was a problem decrypting it.", e); 143 throw new RuntimeException("Data object class " 144 + dataObjectClass 145 + " property " 146 + keyPropertyName 147 + " should have been encrypted, but there was a problem decrypting it.", e); 148 } 149 } 150 151 keyPropertyValues.put(keyPropertyName, keyPropertyValue); 152 } 153 154 // now retrieve the object based on the key set 155 Object dataObject = null; 156 157 ModuleService moduleService = KRADServiceLocatorWeb.getKualiModuleService().getResponsibleModuleService( 158 getDataObjectClass()); 159 if (moduleService != null && moduleService.isExternalizable(getDataObjectClass())) { 160 dataObject = moduleService.getExternalizableBusinessObject(getDataObjectClass().asSubclass( 161 ExternalizableBusinessObject.class), keyPropertyValues); 162 } else if (BusinessObject.class.isAssignableFrom(getDataObjectClass())) { 163 dataObject = getBusinessObjectService().findByPrimaryKey(getDataObjectClass().asSubclass( 164 BusinessObject.class), keyPropertyValues); 165 } 166 167 return dataObject; 168 } 169 170 /** 171 * Iterates through the list of key sets looking for a set where the given map of parameters has 172 * all the key names and values are non-blank, first matched set is returned 173 * 174 * @param potentialKeySets - List of key sets to check for match 175 * @param parameters - map of parameter name/value pairs for matching key set 176 * @return List<String> key set that was matched, or null if none were matched 177 */ 178 protected List<String> retrieveKeySetFromMap(List<List<String>> potentialKeySets, Map<String, String> parameters) { 179 List<String> foundKeySet = null; 180 181 for (List<String> potentialKeySet : potentialKeySets) { 182 boolean keySetMatch = true; 183 for (String keyName : potentialKeySet) { 184 if (!parameters.containsKey(keyName) || StringUtils.isBlank(parameters.get(keyName))) { 185 keySetMatch = false; 186 } 187 } 188 189 if (keySetMatch) { 190 foundKeySet = potentialKeySet; 191 break; 192 } 193 } 194 195 return foundKeySet; 196 } 197 198 /** 199 * Invokes the module service to retrieve any alternate keys that have been 200 * defined for the given class 201 * 202 * @param clazz - class to find alternate keys for 203 * @return List<List<String>> list of alternate key sets, or empty list if none are found 204 */ 205 protected List<List<String>> getAlternateKeysForClass(Class<?> clazz) { 206 KualiModuleService kualiModuleService = getKualiModuleService(); 207 ModuleService moduleService = kualiModuleService.getResponsibleModuleService(clazz); 208 209 List<List<String>> altKeys = null; 210 if (moduleService != null) { 211 altKeys = moduleService.listAlternatePrimaryKeyFieldNames(clazz); 212 } 213 214 return altKeys != null ? altKeys : new ArrayList<List<String>>(); 215 } 216 217 /** 218 * @see Inquirable#buildInquirableLink(java.lang.Object, 219 * java.lang.String, org.kuali.rice.krad.uif.widget.Inquiry) 220 */ 221 @Override 222 public void buildInquirableLink(Object dataObject, String propertyName, Inquiry inquiry) { 223 Class<?> inquiryObjectClass = null; 224 225 // inquiry into data object class if property is title attribute 226 Class<?> objectClass = ObjectUtils.materializeClassForProxiedObject(dataObject); 227 if (propertyName.equals(getDataObjectMetaDataService().getTitleAttribute(objectClass))) { 228 inquiryObjectClass = objectClass; 229 } else if (ObjectUtils.isNestedAttribute(propertyName)) { 230 String nestedPropertyName = ObjectUtils.getNestedAttributePrefix(propertyName); 231 Object nestedPropertyObject = ObjectUtils.getNestedValue(dataObject, nestedPropertyName); 232 233 if (ObjectUtils.isNotNull(nestedPropertyObject)) { 234 String nestedPropertyPrimitive = ObjectUtils.getNestedAttributePrimitive(propertyName); 235 Class<?> nestedPropertyObjectClass = ObjectUtils.materializeClassForProxiedObject(nestedPropertyObject); 236 237 if (nestedPropertyPrimitive.equals(getDataObjectMetaDataService().getTitleAttribute( 238 nestedPropertyObjectClass))) { 239 inquiryObjectClass = nestedPropertyObjectClass; 240 } 241 } 242 } 243 244 // if not title, then get primary relationship 245 DataObjectRelationship relationship = null; 246 if (inquiryObjectClass == null) { 247 relationship = getDataObjectMetaDataService().getDataObjectRelationship(dataObject, objectClass, 248 propertyName, "", true, false, true); 249 if (relationship != null) { 250 inquiryObjectClass = relationship.getRelatedClass(); 251 } 252 } 253 254 // if haven't found inquiry class, then no inquiry can be rendered 255 if (inquiryObjectClass == null) { 256 inquiry.setRender(false); 257 258 return; 259 } 260 261 if (DocumentHeader.class.isAssignableFrom(inquiryObjectClass)) { 262 String documentNumber = (String) ObjectUtils.getPropertyValue(dataObject, propertyName); 263 if (StringUtils.isNotBlank(documentNumber)) { 264 inquiry.getInquiryLinkField().setHrefText(getConfigurationService().getPropertyValueAsString( 265 KRADConstants.WORKFLOW_URL_KEY) 266 + KRADConstants.DOCHANDLER_DO_URL 267 + documentNumber 268 + KRADConstants.DOCHANDLER_URL_CHUNK); 269 inquiry.getInquiryLinkField().setLinkLabel(documentNumber); 270 inquiry.setRender(true); 271 } 272 273 return; 274 } 275 276 synchronized (SUPER_CLASS_TRANSLATOR_LIST) { 277 for (Class<?> clazz : SUPER_CLASS_TRANSLATOR_LIST) { 278 if (clazz.isAssignableFrom(inquiryObjectClass)) { 279 inquiryObjectClass = clazz; 280 break; 281 } 282 } 283 } 284 285 if (!inquiryObjectClass.isInterface() && ExternalizableBusinessObject.class.isAssignableFrom( 286 inquiryObjectClass)) { 287 inquiryObjectClass = ExternalizableBusinessObjectUtils.determineExternalizableBusinessObjectSubInterface( 288 inquiryObjectClass); 289 } 290 291 // listPrimaryKeyFieldNames returns an unmodifiable list. So a copy is necessary. 292 List<String> keys = new ArrayList<String>(getDataObjectMetaDataService().listPrimaryKeyFieldNames( 293 inquiryObjectClass)); 294 295 if (keys == null) { 296 keys = Collections.emptyList(); 297 } 298 299 // build inquiry parameter mappings 300 Map<String, String> inquiryParameters = new HashMap<String, String>(); 301 for (String keyName : keys) { 302 String keyConversion = keyName; 303 if (relationship != null) { 304 keyConversion = relationship.getParentAttributeForChildAttribute(keyName); 305 } else if (ObjectUtils.isNestedAttribute(propertyName)) { 306 String nestedAttributePrefix = ObjectUtils.getNestedAttributePrefix(propertyName); 307 keyConversion = nestedAttributePrefix + "." + keyName; 308 } 309 310 inquiryParameters.put(keyConversion, keyName); 311 } 312 313 inquiry.buildInquiryLink(dataObject, propertyName, inquiryObjectClass, inquiryParameters); 314 } 315 316 /** 317 * @see Inquirable#setDataObjectClass(java.lang.Class) 318 */ 319 @Override 320 public void setDataObjectClass(Class<?> dataObjectClass) { 321 this.dataObjectClass = dataObjectClass; 322 } 323 324 /** 325 * Retrieves the data object class configured for this inquirable 326 * 327 * @return Class<?> of configured data object, or null if data object class not configured 328 */ 329 protected Class<?> getDataObjectClass() { 330 return this.dataObjectClass; 331 } 332 333 protected ConfigurationService getConfigurationService() { 334 return KRADServiceLocator.getKualiConfigurationService(); 335 } 336 337 protected DataObjectMetaDataService getDataObjectMetaDataService() { 338 return KRADServiceLocatorWeb.getDataObjectMetaDataService(); 339 } 340 341 protected KualiModuleService getKualiModuleService() { 342 return KRADServiceLocatorWeb.getKualiModuleService(); 343 } 344 345 protected DataDictionaryService getDataDictionaryService() { 346 return KRADServiceLocatorWeb.getDataDictionaryService(); 347 } 348 349 protected DataObjectAuthorizationService getDataObjectAuthorizationService() { 350 return KRADServiceLocatorWeb.getDataObjectAuthorizationService(); 351 } 352 353 protected EncryptionService getEncryptionService() { 354 return CoreApiServiceLocator.getEncryptionService(); 355 } 356 357 protected BusinessObjectService getBusinessObjectService() { 358 return KRADServiceLocator.getBusinessObjectService(); 359 } 360 361 }