View Javadoc

1   /**
2    * Copyright 2005-2014 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                                 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 }