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.kns.web.struts.form; 017 018import org.apache.commons.beanutils.PropertyUtils; 019import org.apache.commons.lang.StringUtils; 020import org.apache.struts.upload.FormFile; 021import org.kuali.rice.core.api.CoreApiServiceLocator; 022import org.kuali.rice.core.api.config.ConfigurationException; 023import org.kuali.rice.kns.authorization.AuthorizationConstants; 024import org.kuali.rice.core.api.encryption.EncryptionService; 025import org.kuali.rice.kns.datadictionary.BusinessObjectEntry; 026import org.kuali.rice.kns.document.MaintenanceDocumentBase; 027import org.kuali.rice.kns.inquiry.Inquirable; 028import org.kuali.rice.kns.service.BusinessObjectAuthorizationService; 029import org.kuali.rice.kns.service.BusinessObjectMetaDataService; 030import org.kuali.rice.kns.service.KNSServiceLocator; 031import org.kuali.rice.krad.bo.Exporter; 032import org.kuali.rice.krad.bo.PersistableBusinessObject; 033import org.kuali.rice.krad.datadictionary.exception.UnknownBusinessClassAttributeException; 034import org.kuali.rice.krad.datadictionary.exception.UnknownDocumentTypeException; 035import org.kuali.rice.krad.document.Document; 036import org.kuali.rice.krad.service.DataDictionaryService; 037import org.kuali.rice.krad.service.KRADServiceLocatorWeb; 038import org.kuali.rice.krad.service.KualiModuleService; 039import org.kuali.rice.krad.service.ModuleService; 040import org.kuali.rice.krad.util.KRADConstants; 041import org.kuali.rice.krad.util.ObjectUtils; 042 043import javax.servlet.http.HttpServletRequest; 044import java.lang.reflect.Constructor; 045import java.lang.reflect.InvocationTargetException; 046import java.security.GeneralSecurityException; 047import java.util.ArrayList; 048import java.util.Enumeration; 049import java.util.HashMap; 050import java.util.List; 051import java.util.Map; 052import java.util.regex.Matcher; 053 054/** 055 * This class is the action form for inquiries. 056 */ 057public class InquiryForm extends KualiForm { 058 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(InquiryForm.class); 059 060 private static final long serialVersionUID = 1L; 061 private String fieldConversions; 062 private List sections; 063 private String businessObjectClassName; 064 private Map editingMode; 065 private String formKey; 066 private boolean canExport; 067 068 @Override 069 public void addRequiredNonEditableProperties(){ 070 super.addRequiredNonEditableProperties(); 071 registerRequiredNonEditableProperty(KRADConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE); 072 registerRequiredNonEditableProperty(KRADConstants.DISPATCH_REQUEST_PARAMETER); 073 registerRequiredNonEditableProperty(KRADConstants.DOC_FORM_KEY); 074 registerRequiredNonEditableProperty(KRADConstants.FORM_KEY); 075 registerRequiredNonEditableProperty(KRADConstants.FIELDS_CONVERSION_PARAMETER); 076 registerRequiredNonEditableProperty(KRADConstants.BACK_LOCATION); 077 } 078 079 /** 080 * The following map is used to pass primary key values between invocations of the inquiry screens after the start method has been called. Values in this map will remain encrypted 081 * if the value was passed in as encrypted 082 */ 083 private Map<String, String> inquiryPrimaryKeys; 084 085 private Map<String, String> inquiryDecryptedPrimaryKeys; 086 087 /** 088 * A map of collection name -> Boolean mappings. Used to denote whether a collection name is configured to show inactive records. 089 */ 090 private Map<String, Boolean> inactiveRecordDisplay; 091 092 private Inquirable inquirable; 093 094 public InquiryForm() { 095 super(); 096 this.editingMode = new HashMap(); 097 this.editingMode.put(AuthorizationConstants.EditMode.VIEW_ONLY, "TRUE"); 098 this.inactiveRecordDisplay = null; 099 } 100 101 @Override 102 public void populate(HttpServletRequest request) { 103 // set to null for security reasons (so POJO form base can't access it), then we'll make an new instance of it after 104 // POJO form base is done 105 this.inquirable = null; 106 super.populate(request); 107 if (request.getParameter("returnLocation") != null) { 108 setBackLocation(request.getParameter("returnLocation")); 109 } 110 if (request.getParameter(KRADConstants.DOC_FORM_KEY) != null) { 111 setFormKey(request.getParameter(KRADConstants.DOC_FORM_KEY)); 112 } 113 //if the action is download attachment then skip the following populate logic 114 if(!KRADConstants.DOWNLOAD_BO_ATTACHMENT_METHOD.equals(getMethodToCall())){ 115 inquirable = getInquirable(getBusinessObjectClassName()); 116 117 // the following variable is true if the method to call is not start, meaning that we already called start 118 boolean passedFromPreviousInquiry = !KRADConstants.START_METHOD.equals(getMethodToCall()) && !KRADConstants.CONTINUE_WITH_INQUIRY_METHOD_TO_CALL.equals(getMethodToCall()) && !KRADConstants 119 .DOWNLOAD_CUSTOM_BO_ATTACHMENT_METHOD.equals(getMethodToCall()); 120 121 // There is no requirement that an inquiry screen must display the primary key values. However, when clicking on hide/show (without javascript) and 122 // hide/show inactive, the PK values are needed to allow the server to properly render results after the user clicks on a hide/show button that results 123 // in server processing. This line will populate the form with the PK values so that they may be used in subsequent requests. Note that encrypted 124 // values will remain encrypted in this map. 125 this.inquiryPrimaryKeys = new HashMap<String, String>(); 126 this.inquiryDecryptedPrimaryKeys = new HashMap<String, String>(); 127 128 populatePKFieldValues(request, getBusinessObjectClassName(), passedFromPreviousInquiry); 129 130 populateInactiveRecordsInIntoInquirable(inquirable, request); 131 populateExportCapabilities(request, getBusinessObjectClassName()); 132 } 133 } 134 135 protected Inquirable getInquirable(String boClassName) { 136 try { 137 Class customInquirableClass = null; 138 139 try { 140 BusinessObjectEntry entry = (BusinessObjectEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(boClassName); 141 customInquirableClass = entry.getInquiryDefinition().getInquirableClass(); 142 } 143 catch (Exception e) { 144 LOG.error("Unable to correlate business object class with maintenance document entry"); 145 } 146 147 Inquirable kualiInquirable = KNSServiceLocator.getKualiInquirable(); // get inquirable impl from Spring 148 149 if (customInquirableClass != null) { 150 Class[] defaultConstructor = new Class[] {}; 151 Constructor cons = customInquirableClass.getConstructor(defaultConstructor); 152 kualiInquirable = (Inquirable) cons.newInstance(); 153 } 154 155 kualiInquirable.setBusinessObjectClass(Class.forName(boClassName)); 156 157 return kualiInquirable; 158 } 159 catch (Exception e) { 160 LOG.error("Error attempting to retrieve inquirable.", e); 161 throw new RuntimeException("Error attempting to retrieve inquirable."); 162 } 163 } 164 165 /** 166 * Gets the alt keys for a class. Will not return null but and empty list if no keys exist. 167 * 168 * @param clazz the class. 169 * @return the alt keys 170 */ 171 private List<List<String>> getAltkeys(Class<?> clazz) { 172 final KualiModuleService kualiModuleService = KRADServiceLocatorWeb.getKualiModuleService(); 173 final ModuleService moduleService = kualiModuleService.getResponsibleModuleService(clazz); 174 175 List<List<String>> altKeys = null; 176 if (moduleService != null) { 177 altKeys = moduleService.listAlternatePrimaryKeyFieldNames(clazz); 178 } 179 180 return altKeys != null ? altKeys : new ArrayList<List<String>>(); 181 } 182 183 protected void populatePKFieldValues(HttpServletRequest request, String boClassName, boolean passedFromPreviousInquiry) { 184 try { 185 EncryptionService encryptionService = CoreApiServiceLocator.getEncryptionService(); 186 DataDictionaryService dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService(); 187 BusinessObjectAuthorizationService businessObjectAuthorizationService = KNSServiceLocator 188 .getBusinessObjectAuthorizationService(); 189 BusinessObjectMetaDataService businessObjectMetaDataService = KNSServiceLocator 190 .getBusinessObjectMetaDataService(); 191 192 Class businessObjectClass = Class.forName(boClassName); 193 194 // build list of key values from request, if all keys not given throw error 195 List<String> boPKeys = businessObjectMetaDataService.listPrimaryKeyFieldNames(businessObjectClass); 196 final List<List<String>> altKeys = this.getAltkeys(businessObjectClass); 197 198 altKeys.add(boPKeys); 199 boolean bFound = false; 200 for(List<String> boKeys : altKeys ){ 201 if(bFound) 202 break; 203 int keyCount = boKeys.size(); 204 int foundCount = 0; 205 for (String boKey : boKeys) { 206 String pkParamName = boKey; 207 if (passedFromPreviousInquiry) { 208 pkParamName = KRADConstants.INQUIRY_PK_VALUE_PASSED_FROM_PREVIOUS_REQUEST_PREFIX + pkParamName; 209 } 210 211 if (request.getParameter(pkParamName) != null) { 212 foundCount++; 213 String parameter = request.getParameter(pkParamName); 214 215 Boolean forceUppercase = Boolean.FALSE; 216 try { 217 forceUppercase = dataDictionaryService.getAttributeForceUppercase(businessObjectClass, boKey); 218 } catch (UnknownBusinessClassAttributeException ex) { 219 // swallowing exception because this check for ForceUppercase would 220 // require a DD entry for the attribute. it is only checking keys 221 // so most likely there should be an entry. 222 LOG.warn("BO class " + businessObjectClassName + " property " + boKey + " should probably have a DD definition.", ex); 223 } 224 String parameterCopy = parameter; 225 if (forceUppercase) { 226 parameter = parameter.toUpperCase(); 227 } 228 229 inquiryPrimaryKeys.put(boKey, parameter); 230 if (businessObjectAuthorizationService.attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, boKey)) { 231 try { 232 if(CoreApiServiceLocator.getEncryptionService().isEnabled()) { 233 inquiryDecryptedPrimaryKeys.put(boKey, encryptionService.decrypt(parameterCopy)); 234 } 235 } catch (GeneralSecurityException e) { 236 LOG.error("BO class " + businessObjectClassName + " property " + boKey + " should have been encrypted, but there was a problem decrypting it."); 237 throw e; 238 } 239 } 240 else { 241 inquiryDecryptedPrimaryKeys.put(boKey, parameter); 242 } 243 } 244 } 245 if (foundCount == keyCount) { 246 bFound = true; 247 } 248 } 249 if(!bFound){ 250 LOG.error("All keys not given to lookup for bo class name " + businessObjectClass.getName()); 251 throw new RuntimeException("All keys not given to lookup for bo class name " + businessObjectClass.getName()); 252 } 253 } 254 catch (ClassNotFoundException e) { 255 LOG.error("Can't instantiate class: " + boClassName, e); 256 throw new RuntimeException("Can't instantiate class: " + boClassName); 257 } 258 catch (GeneralSecurityException e) { 259 LOG.error("Can't decrypt value", e); 260 throw new RuntimeException("Can't decrypt value"); 261 } 262 } 263 264 /** 265 * Examines the BusinessObject's data dictionary entry to determine if it supports 266 * XML export or not and set's canExport appropriately. 267 */ 268 protected void populateExportCapabilities(HttpServletRequest request, String boClassName) { 269 setCanExport(false); 270 BusinessObjectEntry businessObjectEntry = (BusinessObjectEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(boClassName); 271 Class<? extends Exporter> exporterClass = businessObjectEntry.getExporterClass(); 272 if (exporterClass != null) { 273 try { 274 Exporter exporter = exporterClass.newInstance(); 275 if (exporter.getSupportedFormats(businessObjectEntry.getBusinessObjectClass()).contains(KRADConstants.XML_FORMAT)) { 276 setCanExport(true); 277 } 278 } catch (Exception e) { 279 LOG.error("Failed to locate or create exporter class: " + exporterClass, e); 280 throw new RuntimeException("Failed to locate or create exporter class: " + exporterClass); 281 } 282 } 283 } 284 285 286 /** 287 * @return Returns the fieldConversions. 288 */ 289 public String getFieldConversions() { 290 return fieldConversions; 291 } 292 293 294 /** 295 * @param fieldConversions The fieldConversions to set. 296 */ 297 public void setFieldConversions(String fieldConversions) { 298 this.fieldConversions = fieldConversions; 299 } 300 301 302 /** 303 * @return Returns the inquiry sections. 304 */ 305 public List getSections() { 306 return sections; 307 } 308 309 310 /** 311 * @param sections The sections to set. 312 */ 313 public void setSections(List sections) { 314 this.sections = sections; 315 } 316 317 /** 318 * @return Returns the businessObjectClassName. 319 */ 320 public String getBusinessObjectClassName() { 321 return businessObjectClassName; 322 } 323 324 /** 325 * @param businessObjectClassName The businessObjectClassName to set. 326 */ 327 public void setBusinessObjectClassName(String businessObjectClassName) { 328 this.businessObjectClassName = businessObjectClassName; 329 } 330 331 public Map getEditingMode() { 332 return editingMode; 333 } 334 335 /** 336 * Gets the map used to pass primary key values between invocations of the inquiry screens after the start method has been called. All field values that were passed in encrypted will 337 * be encrypted in this map 338 * 339 * @return 340 */ 341 public Map<String, String> getInquiryPrimaryKeys() { 342 return this.inquiryPrimaryKeys; 343 } 344 345 /** 346 * Gets the map used to pass primary key values between invocations of the inquiry screens after the start method has been called. All fields will be decrypted 347 * 348 * Purposely not named as a getter, to make it harder for POJOFormBase to access it 349 * 350 * @return 351 */ 352 public Map<String, String> retrieveInquiryDecryptedPrimaryKeys() { 353 return this.inquiryDecryptedPrimaryKeys; 354 } 355 356 /** 357 * Sets the map used to pass primary key values between invocations of the inquiry screens after the start method has been called. 358 * 359 * @param inquiryPrimaryKeys 360 */ 361 public void setInquiryPrimaryKeys(Map<String, String> inquiryPrimaryKeys) { 362 this.inquiryPrimaryKeys = inquiryPrimaryKeys; 363 } 364 365 /** 366 * Gets map of collection name -> Boolean mappings. Used to denote whether a collection name is configured to show inactive records. 367 * 368 * @return 369 */ 370 public Map<String, Boolean> getInactiveRecordDisplay() { 371 return getInquirable().getInactiveRecordDisplay(); 372 } 373 374 public Inquirable getInquirable() { 375 return inquirable; 376 } 377 378 protected void populateInactiveRecordsInIntoInquirable(Inquirable inquirable, HttpServletRequest request) { 379 for (Enumeration e = request.getParameterNames(); e.hasMoreElements();) { 380 String paramName = (String) e.nextElement(); 381 if (paramName.startsWith(KRADConstants.INACTIVE_RECORD_DISPLAY_PARAM_PREFIX)) { 382 String collectionName = StringUtils.substringAfter(paramName, KRADConstants.INACTIVE_RECORD_DISPLAY_PARAM_PREFIX); 383 Boolean showInactive = Boolean.parseBoolean(request.getParameter(paramName)); 384 inquirable.setShowInactiveRecords(collectionName, showInactive); 385 } 386 } 387 } 388 389 public String getFormKey() { 390 return this.formKey; 391 } 392 393 public void setFormKey(String formKey) { 394 this.formKey = formKey; 395 } 396 397 /** 398 * Returns true if this Inquiry supports XML export of the BusinessObject. 399 */ 400 public boolean isCanExport() { 401 return this.canExport; 402 } 403 404 /** 405 * Sets whether or not this Inquiry supports XML export of it's BusinessObject. 406 */ 407 public void setCanExport(boolean canExport) { 408 this.canExport = canExport; 409 } 410 411 412}