View Javadoc

1   /**
2    * Copyright 2005-2012 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.kns.web.struts.form;
17  
18  import org.apache.commons.beanutils.PropertyUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.struts.upload.FormFile;
21  import org.kuali.rice.core.api.CoreApiServiceLocator;
22  import org.kuali.rice.core.api.config.ConfigurationException;
23  import org.kuali.rice.kns.authorization.AuthorizationConstants;
24  import org.kuali.rice.core.api.encryption.EncryptionService;
25  import org.kuali.rice.kns.datadictionary.BusinessObjectEntry;
26  import org.kuali.rice.kns.document.MaintenanceDocumentBase;
27  import org.kuali.rice.kns.inquiry.Inquirable;
28  import org.kuali.rice.kns.service.BusinessObjectAuthorizationService;
29  import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
30  import org.kuali.rice.kns.service.KNSServiceLocator;
31  import org.kuali.rice.krad.bo.Exporter;
32  import org.kuali.rice.krad.bo.PersistableBusinessObject;
33  import org.kuali.rice.krad.datadictionary.exception.UnknownBusinessClassAttributeException;
34  import org.kuali.rice.krad.datadictionary.exception.UnknownDocumentTypeException;
35  import org.kuali.rice.krad.document.Document;
36  import org.kuali.rice.krad.service.DataDictionaryService;
37  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
38  import org.kuali.rice.krad.service.KualiModuleService;
39  import org.kuali.rice.krad.service.ModuleService;
40  import org.kuali.rice.krad.util.KRADConstants;
41  import org.kuali.rice.krad.util.ObjectUtils;
42  
43  import javax.servlet.http.HttpServletRequest;
44  import java.lang.reflect.Constructor;
45  import java.lang.reflect.InvocationTargetException;
46  import java.security.GeneralSecurityException;
47  import java.util.ArrayList;
48  import java.util.Enumeration;
49  import java.util.HashMap;
50  import java.util.List;
51  import java.util.Map;
52  import java.util.regex.Matcher;
53  
54  /**
55   * This class is the action form for inquiries.
56   */
57  public class InquiryForm extends KualiForm {
58      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(InquiryForm.class);
59  
60      private static final long serialVersionUID = 1L;
61      private String fieldConversions;
62      private List sections;
63      private String businessObjectClassName;
64      private Map editingMode;
65      private String formKey;
66      private boolean canExport;
67  
68      @Override
69      public void addRequiredNonEditableProperties(){
70      	super.addRequiredNonEditableProperties();
71      	registerRequiredNonEditableProperty(KRADConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE);
72      	registerRequiredNonEditableProperty(KRADConstants.DISPATCH_REQUEST_PARAMETER);
73      	registerRequiredNonEditableProperty(KRADConstants.DOC_FORM_KEY);
74      	registerRequiredNonEditableProperty(KRADConstants.FORM_KEY);
75      	registerRequiredNonEditableProperty(KRADConstants.FIELDS_CONVERSION_PARAMETER);
76      	registerRequiredNonEditableProperty(KRADConstants.BACK_LOCATION);
77      }
78  
79      /**
80       * 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
81       * if the value was passed in as encrypted
82       */
83      private Map<String, String> inquiryPrimaryKeys;
84  
85      private Map<String, String> inquiryDecryptedPrimaryKeys;
86  
87      /**
88       * A map of collection name -> Boolean mappings.  Used to denote whether a collection name is configured to show inactive records.
89       */
90      private Map<String, Boolean> inactiveRecordDisplay;
91  
92      private Inquirable inquirable;
93  
94      public InquiryForm() {
95          super();
96          this.editingMode = new HashMap();
97          this.editingMode.put(AuthorizationConstants.EditMode.VIEW_ONLY, "TRUE");
98          this.inactiveRecordDisplay = null;
99      }
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                                 inquiryDecryptedPrimaryKeys.put(boKey, encryptionService.decrypt(parameterCopy));
233                             } catch (GeneralSecurityException e) {
234                                 LOG.error("BO class " + businessObjectClassName + " property " + boKey + " should have been encrypted, but there was a problem decrypting it.");
235                                 throw e;
236                             }
237                         }
238                         else {
239                             inquiryDecryptedPrimaryKeys.put(boKey, parameter);
240                         }
241                     }
242                 }
243                 if (foundCount == keyCount) {
244                     bFound = true;
245                 }
246             }
247             if(!bFound){
248                 LOG.error("All keys not given to lookup for bo class name " + businessObjectClass.getName());
249                 throw new RuntimeException("All keys not given to lookup for bo class name " + businessObjectClass.getName());
250             }
251         }
252         catch (ClassNotFoundException e) {
253         	LOG.error("Can't instantiate class: " + boClassName, e);
254         	throw new RuntimeException("Can't instantiate class: " + boClassName);
255         }
256         catch (GeneralSecurityException e) {
257         	LOG.error("Can't decrypt value", e);
258         	throw new RuntimeException("Can't decrypt value");
259         }
260     }
261 
262     /**
263      * Examines the BusinessObject's data dictionary entry to determine if it supports
264      * XML export or not and set's canExport appropriately.
265      */
266     protected void populateExportCapabilities(HttpServletRequest request, String boClassName) {
267     	setCanExport(false);
268     	BusinessObjectEntry businessObjectEntry = (BusinessObjectEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(boClassName);
269     	Class<? extends Exporter> exporterClass = businessObjectEntry.getExporterClass();
270     	if (exporterClass != null) {
271     		try {
272     			Exporter exporter = exporterClass.newInstance();
273     			if (exporter.getSupportedFormats(businessObjectEntry.getBusinessObjectClass()).contains(KRADConstants.XML_FORMAT)) {
274     				setCanExport(true);
275     			}
276     		} catch (Exception e) {
277     			LOG.error("Failed to locate or create exporter class: " + exporterClass, e);
278     			throw new RuntimeException("Failed to locate or create exporter class: " + exporterClass);
279     		}
280     	}
281     }
282 
283 
284     /**
285      * @return Returns the fieldConversions.
286      */
287     public String getFieldConversions() {
288         return fieldConversions;
289     }
290 
291 
292     /**
293      * @param fieldConversions The fieldConversions to set.
294      */
295     public void setFieldConversions(String fieldConversions) {
296         this.fieldConversions = fieldConversions;
297     }
298 
299 
300     /**
301      * @return Returns the inquiry sections.
302      */
303     public List getSections() {
304         return sections;
305     }
306 
307 
308     /**
309      * @param sections The sections to set.
310      */
311     public void setSections(List sections) {
312         this.sections = sections;
313     }
314 
315     /**
316      * @return Returns the businessObjectClassName.
317      */
318     public String getBusinessObjectClassName() {
319         return businessObjectClassName;
320     }
321 
322     /**
323      * @param businessObjectClassName The businessObjectClassName to set.
324      */
325     public void setBusinessObjectClassName(String businessObjectClassName) {
326         this.businessObjectClassName = businessObjectClassName;
327     }
328 
329     public Map getEditingMode() {
330         return editingMode;
331     }
332 
333     /**
334      * 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
335      * be encrypted in this map
336      *
337      * @return
338      */
339     public Map<String, String> getInquiryPrimaryKeys() {
340         return this.inquiryPrimaryKeys;
341     }
342 
343     /**
344      * 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
345      *
346      * Purposely not named as a getter, to make it harder for POJOFormBase to access it
347      *
348      * @return
349      */
350     public Map<String, String> retrieveInquiryDecryptedPrimaryKeys() {
351         return this.inquiryDecryptedPrimaryKeys;
352     }
353 
354     /**
355      * Sets the map used to pass primary key values between invocations of the inquiry screens after the start method has been called.
356      *
357      * @param inquiryPrimaryKeys
358      */
359     public void setInquiryPrimaryKeys(Map<String, String> inquiryPrimaryKeys) {
360         this.inquiryPrimaryKeys = inquiryPrimaryKeys;
361     }
362 
363     /**
364      * Gets map of collection name -> Boolean mappings.  Used to denote whether a collection name is configured to show inactive records.
365      *
366      * @return
367      */
368     public Map<String, Boolean> getInactiveRecordDisplay() {
369         return getInquirable().getInactiveRecordDisplay();
370     }
371 
372     public Inquirable getInquirable() {
373         return inquirable;
374     }
375 
376     protected void populateInactiveRecordsInIntoInquirable(Inquirable inquirable, HttpServletRequest request) {
377         for (Enumeration e = request.getParameterNames(); e.hasMoreElements();) {
378             String paramName = (String) e.nextElement();
379             if (paramName.startsWith(KRADConstants.INACTIVE_RECORD_DISPLAY_PARAM_PREFIX)) {
380                 String collectionName = StringUtils.substringAfter(paramName, KRADConstants.INACTIVE_RECORD_DISPLAY_PARAM_PREFIX);
381                 Boolean showInactive = Boolean.parseBoolean(request.getParameter(paramName));
382                 inquirable.setShowInactiveRecords(collectionName, showInactive);
383             }
384         }
385     }
386 
387     public String getFormKey() {
388         return this.formKey;
389     }
390 
391     public void setFormKey(String formKey) {
392         this.formKey = formKey;
393     }
394 
395 	/**
396 	 * Returns true if this Inquiry supports XML export of the BusinessObject.
397 	 */
398 	public boolean isCanExport() {
399 		return this.canExport;
400 	}
401 
402 	/**
403 	 * Sets whether or not this Inquiry supports XML export of it's BusinessObject.
404 	 */
405 	public void setCanExport(boolean canExport) {
406 		this.canExport = canExport;
407 	}
408 
409 
410 }