View Javadoc

1   /**
2    * Copyright 2005-2013 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.lang.StringUtils;
19  import org.kuali.rice.core.api.CoreApiServiceLocator;
20  import org.kuali.rice.kns.authorization.AuthorizationConstants;
21  import org.kuali.rice.core.api.encryption.EncryptionService;
22  import org.kuali.rice.kns.datadictionary.BusinessObjectEntry;
23  import org.kuali.rice.kns.inquiry.Inquirable;
24  import org.kuali.rice.kns.service.BusinessObjectAuthorizationService;
25  import org.kuali.rice.kns.service.BusinessObjectMetaDataService;
26  import org.kuali.rice.kns.service.KNSServiceLocator;
27  import org.kuali.rice.krad.bo.Exporter;
28  import org.kuali.rice.krad.datadictionary.exception.UnknownBusinessClassAttributeException;
29  import org.kuali.rice.krad.service.DataDictionaryService;
30  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
31  import org.kuali.rice.krad.service.KualiModuleService;
32  import org.kuali.rice.krad.service.ModuleService;
33  import org.kuali.rice.krad.util.KRADConstants;
34  
35  import javax.servlet.http.HttpServletRequest;
36  import java.lang.reflect.Constructor;
37  import java.security.GeneralSecurityException;
38  import java.util.ArrayList;
39  import java.util.Enumeration;
40  import java.util.HashMap;
41  import java.util.List;
42  import java.util.Map;
43  
44  /**
45   * This class is the action form for inquiries.
46   */
47  public class InquiryForm extends KualiForm {
48      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(InquiryForm.class);
49  
50      private static final long serialVersionUID = 1L;
51      private String fieldConversions;
52      private List sections;
53      private String businessObjectClassName;
54      private Map editingMode;
55      private String formKey;
56      private boolean canExport;
57  
58      @Override
59      public void addRequiredNonEditableProperties(){
60      	super.addRequiredNonEditableProperties();
61      	registerRequiredNonEditableProperty(KRADConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE);
62      	registerRequiredNonEditableProperty(KRADConstants.DISPATCH_REQUEST_PARAMETER);
63      	registerRequiredNonEditableProperty(KRADConstants.DOC_FORM_KEY);
64      	registerRequiredNonEditableProperty(KRADConstants.FORM_KEY);
65      	registerRequiredNonEditableProperty(KRADConstants.FIELDS_CONVERSION_PARAMETER);
66      	registerRequiredNonEditableProperty(KRADConstants.BACK_LOCATION);
67      }
68  
69      /**
70       * 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
71       * if the value was passed in as encrypted
72       */
73      private Map<String, String> inquiryPrimaryKeys;
74  
75      private Map<String, String> inquiryDecryptedPrimaryKeys;
76  
77      /**
78       * A map of collection name -> Boolean mappings.  Used to denote whether a collection name is configured to show inactive records.
79       */
80      private Map<String, Boolean> inactiveRecordDisplay;
81  
82      private Inquirable inquirable;
83  
84      public InquiryForm() {
85          super();
86          this.editingMode = new HashMap();
87          this.editingMode.put(AuthorizationConstants.EditMode.VIEW_ONLY, "TRUE");
88          this.inactiveRecordDisplay = null;
89      }
90  
91      @Override
92      public void populate(HttpServletRequest request) {
93      // set to null for security reasons (so POJO form base can't access it), then we'll make an new instance of it after
94      // POJO form base is done
95          this.inquirable = null;
96          super.populate(request);
97          if (request.getParameter("returnLocation") != null) {
98              setBackLocation(request.getParameter("returnLocation"));
99          }
100         if (request.getParameter(KRADConstants.DOC_FORM_KEY) != null) {
101             setFormKey(request.getParameter(KRADConstants.DOC_FORM_KEY));
102         }
103         //if the action is download attachment then skip the following populate logic
104         if(!KRADConstants.DOWNLOAD_BO_ATTACHMENT_METHOD.equals(getMethodToCall())){
105         	inquirable = getInquirable(getBusinessObjectClassName());
106 
107         	// the following variable is true if the method to call is not start, meaning that we already called start
108         	boolean passedFromPreviousInquiry = !KRADConstants.START_METHOD.equals(getMethodToCall()) && !KRADConstants.CONTINUE_WITH_INQUIRY_METHOD_TO_CALL.equals(getMethodToCall()) && !KRADConstants
109                     .DOWNLOAD_CUSTOM_BO_ATTACHMENT_METHOD.equals(getMethodToCall());
110 
111         	// There is no requirement that an inquiry screen must display the primary key values.  However, when clicking on hide/show (without javascript) and
112         	// 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
113         	// 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
114         	// values will remain encrypted in this map.
115         	this.inquiryPrimaryKeys = new HashMap<String, String>();
116         	this.inquiryDecryptedPrimaryKeys = new HashMap<String, String>();
117 
118         	populatePKFieldValues(request, getBusinessObjectClassName(), passedFromPreviousInquiry);
119 
120         	populateInactiveRecordsInIntoInquirable(inquirable, request);
121         	populateExportCapabilities(request, getBusinessObjectClassName());
122         }
123     }
124 
125     protected Inquirable getInquirable(String boClassName) {
126         try {
127             Class customInquirableClass = null;
128 
129             try {
130                 BusinessObjectEntry entry = (BusinessObjectEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(boClassName);
131                 customInquirableClass = entry.getInquiryDefinition().getInquirableClass();
132             }
133             catch (Exception e) {
134                 LOG.error("Unable to correlate business object class with maintenance document entry");
135             }
136 
137             Inquirable kualiInquirable = KNSServiceLocator.getKualiInquirable(); // get inquirable impl from Spring
138 
139             if (customInquirableClass != null) {
140                 Class[] defaultConstructor = new Class[] {};
141                 Constructor cons = customInquirableClass.getConstructor(defaultConstructor);
142                 kualiInquirable = (Inquirable) cons.newInstance();
143             }
144 
145             kualiInquirable.setBusinessObjectClass(Class.forName(boClassName));
146 
147             return kualiInquirable;
148         }
149         catch (Exception e) {
150             LOG.error("Error attempting to retrieve inquirable.", e);
151             throw new RuntimeException("Error attempting to retrieve inquirable.");
152         }
153     }
154 
155     /**
156      * Gets the alt keys for a class.  Will not return null but and empty list if no keys exist.
157      *
158      * @param clazz the class.
159      * @return the alt keys
160      */
161     private List<List<String>> getAltkeys(Class<?> clazz) {
162     	final KualiModuleService kualiModuleService = KRADServiceLocatorWeb.getKualiModuleService();
163     	final ModuleService moduleService = kualiModuleService.getResponsibleModuleService(clazz);
164 
165         List<List<String>> altKeys = null;
166         if (moduleService != null) {
167         	altKeys = moduleService.listAlternatePrimaryKeyFieldNames(clazz);
168         }
169 
170         return altKeys != null ? altKeys : new ArrayList<List<String>>();
171     }
172 
173     protected void populatePKFieldValues(HttpServletRequest request, String boClassName, boolean passedFromPreviousInquiry) {
174         try {
175             EncryptionService encryptionService = CoreApiServiceLocator.getEncryptionService();
176             DataDictionaryService dataDictionaryService = KRADServiceLocatorWeb.getDataDictionaryService();
177             BusinessObjectAuthorizationService businessObjectAuthorizationService = KNSServiceLocator
178                     .getBusinessObjectAuthorizationService();
179             BusinessObjectMetaDataService businessObjectMetaDataService = KNSServiceLocator
180                     .getBusinessObjectMetaDataService();
181 
182             Class businessObjectClass = Class.forName(boClassName);
183 
184             // build list of key values from request, if all keys not given throw error
185             List<String> boPKeys = KRADServiceLocatorWeb.getLegacyDataAdapter().listPrimaryKeyFieldNames(businessObjectClass);
186             final List<List<String>> altKeys = this.getAltkeys(businessObjectClass);
187 
188             altKeys.add(boPKeys);
189             boolean bFound = false;
190             for(List<String> boKeys : altKeys ){
191             	if(bFound)
192             		break;
193 	            int keyCount = boKeys.size();
194 	            int foundCount = 0;
195                 for (String boKey : boKeys) {
196                     String pkParamName = boKey;
197 	                if (passedFromPreviousInquiry) {
198 	                    pkParamName = KRADConstants.INQUIRY_PK_VALUE_PASSED_FROM_PREVIOUS_REQUEST_PREFIX + pkParamName;
199 	                }
200 
201 	                if (request.getParameter(pkParamName) != null) {
202 	                	foundCount++;
203 	                	String parameter = request.getParameter(pkParamName);
204                         
205                         Boolean forceUppercase = Boolean.FALSE;
206                         try {
207                             forceUppercase = dataDictionaryService.getAttributeForceUppercase(businessObjectClass, boKey);
208                         } catch (UnknownBusinessClassAttributeException ex) {
209                             // swallowing exception because this check for ForceUppercase would
210                             // require a DD entry for the attribute.  it is only checking keys
211                             // so most likely there should be an entry.
212                             LOG.warn("BO class " + businessObjectClassName + " property " + boKey + " should probably have a DD definition.", ex);
213                         }
214                         String parameterCopy = parameter;
215                         if (forceUppercase) {
216 	                		parameter = parameter.toUpperCase();
217 	                	}
218 
219                         inquiryPrimaryKeys.put(boKey, parameter);
220                         if (businessObjectAuthorizationService.attributeValueNeedsToBeEncryptedOnFormsAndLinks(businessObjectClass, boKey)) {
221                             try {
222                                 if(CoreApiServiceLocator.getEncryptionService().isEnabled()) {
223                                     inquiryDecryptedPrimaryKeys.put(boKey, encryptionService.decrypt(parameterCopy));
224                                 }
225                             } catch (GeneralSecurityException e) {
226                                 LOG.error("BO class " + businessObjectClassName + " property " + boKey + " should have been encrypted, but there was a problem decrypting it.");
227                                 throw e;
228                             }
229                         }
230                         else {
231                             inquiryDecryptedPrimaryKeys.put(boKey, parameter);
232                         }
233                     }
234                 }
235                 if (foundCount == keyCount) {
236                     bFound = true;
237                 }
238             }
239             if(!bFound){
240                 LOG.error("All keys not given to lookup for bo class name " + businessObjectClass.getName());
241                 throw new RuntimeException("All keys not given to lookup for bo class name " + businessObjectClass.getName());
242             }
243         }
244         catch (ClassNotFoundException e) {
245         	LOG.error("Can't instantiate class: " + boClassName, e);
246         	throw new RuntimeException("Can't instantiate class: " + boClassName);
247         }
248         catch (GeneralSecurityException e) {
249         	LOG.error("Can't decrypt value", e);
250         	throw new RuntimeException("Can't decrypt value");
251         }
252     }
253 
254     /**
255      * Examines the BusinessObject's data dictionary entry to determine if it supports
256      * XML export or not and set's canExport appropriately.
257      */
258     protected void populateExportCapabilities(HttpServletRequest request, String boClassName) {
259     	setCanExport(false);
260     	BusinessObjectEntry businessObjectEntry = (BusinessObjectEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(boClassName);
261     	Class<? extends Exporter> exporterClass = businessObjectEntry.getExporterClass();
262     	if (exporterClass != null) {
263     		try {
264     			Exporter exporter = exporterClass.newInstance();
265     			if (exporter.getSupportedFormats(businessObjectEntry.getBusinessObjectClass()).contains(KRADConstants.XML_FORMAT)) {
266     				setCanExport(true);
267     			}
268     		} catch (Exception e) {
269     			LOG.error("Failed to locate or create exporter class: " + exporterClass, e);
270     			throw new RuntimeException("Failed to locate or create exporter class: " + exporterClass);
271     		}
272     	}
273     }
274 
275 
276     /**
277      * @return Returns the fieldConversions.
278      */
279     public String getFieldConversions() {
280         return fieldConversions;
281     }
282 
283 
284     /**
285      * @param fieldConversions The fieldConversions to set.
286      */
287     public void setFieldConversions(String fieldConversions) {
288         this.fieldConversions = fieldConversions;
289     }
290 
291 
292     /**
293      * @return Returns the inquiry sections.
294      */
295     public List getSections() {
296         return sections;
297     }
298 
299 
300     /**
301      * @param sections The sections to set.
302      */
303     public void setSections(List sections) {
304         this.sections = sections;
305     }
306 
307     /**
308      * @return Returns the businessObjectClassName.
309      */
310     public String getBusinessObjectClassName() {
311         return businessObjectClassName;
312     }
313 
314     /**
315      * @param businessObjectClassName The businessObjectClassName to set.
316      */
317     public void setBusinessObjectClassName(String businessObjectClassName) {
318         this.businessObjectClassName = businessObjectClassName;
319     }
320 
321     public Map getEditingMode() {
322         return editingMode;
323     }
324 
325     /**
326      * 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
327      * be encrypted in this map
328      *
329      * @return
330      */
331     public Map<String, String> getInquiryPrimaryKeys() {
332         return this.inquiryPrimaryKeys;
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 fields will be decrypted
337      *
338      * Purposely not named as a getter, to make it harder for POJOFormBase to access it
339      *
340      * @return
341      */
342     public Map<String, String> retrieveInquiryDecryptedPrimaryKeys() {
343         return this.inquiryDecryptedPrimaryKeys;
344     }
345 
346     /**
347      * Sets the map used to pass primary key values between invocations of the inquiry screens after the start method has been called.
348      *
349      * @param inquiryPrimaryKeys
350      */
351     public void setInquiryPrimaryKeys(Map<String, String> inquiryPrimaryKeys) {
352         this.inquiryPrimaryKeys = inquiryPrimaryKeys;
353     }
354 
355     /**
356      * Gets map of collection name -> Boolean mappings.  Used to denote whether a collection name is configured to show inactive records.
357      *
358      * @return
359      */
360     public Map<String, Boolean> getInactiveRecordDisplay() {
361         return getInquirable().getInactiveRecordDisplay();
362     }
363 
364     public Inquirable getInquirable() {
365         return inquirable;
366     }
367 
368     protected void populateInactiveRecordsInIntoInquirable(Inquirable inquirable, HttpServletRequest request) {
369         for (Enumeration e = request.getParameterNames(); e.hasMoreElements();) {
370             String paramName = (String) e.nextElement();
371             if (paramName.startsWith(KRADConstants.INACTIVE_RECORD_DISPLAY_PARAM_PREFIX)) {
372                 String collectionName = StringUtils.substringAfter(paramName, KRADConstants.INACTIVE_RECORD_DISPLAY_PARAM_PREFIX);
373                 Boolean showInactive = Boolean.parseBoolean(request.getParameter(paramName));
374                 inquirable.setShowInactiveRecords(collectionName, showInactive);
375             }
376         }
377     }
378 
379     public String getFormKey() {
380         return this.formKey;
381     }
382 
383     public void setFormKey(String formKey) {
384         this.formKey = formKey;
385     }
386 
387 	/**
388 	 * Returns true if this Inquiry supports XML export of the BusinessObject.
389 	 */
390 	public boolean isCanExport() {
391 		return this.canExport;
392 	}
393 
394 	/**
395 	 * Sets whether or not this Inquiry supports XML export of it's BusinessObject.
396 	 */
397 	public void setCanExport(boolean canExport) {
398 		this.canExport = canExport;
399 	}
400 
401 
402 }