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.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 = businessObjectMetaDataService.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                                 inquiryDecryptedPrimaryKeys.put(boKey, encryptionService.decrypt(parameterCopy));
223                             } catch (GeneralSecurityException e) {
224                                 LOG.error("BO class " + businessObjectClassName + " property " + boKey + " should have been encrypted, but there was a problem decrypting it.");
225                                 throw e;
226                             }
227                         }
228                         else {
229                             inquiryDecryptedPrimaryKeys.put(boKey, parameter);
230                         }
231                     }
232                 }
233                 if (foundCount == keyCount) {
234                     bFound = true;
235                 }
236             }
237             if(!bFound){
238                 LOG.error("All keys not given to lookup for bo class name " + businessObjectClass.getName());
239                 throw new RuntimeException("All keys not given to lookup for bo class name " + businessObjectClass.getName());
240             }
241         }
242         catch (ClassNotFoundException e) {
243         	LOG.error("Can't instantiate class: " + boClassName, e);
244         	throw new RuntimeException("Can't instantiate class: " + boClassName);
245         }
246         catch (GeneralSecurityException e) {
247         	LOG.error("Can't decrypt value", e);
248         	throw new RuntimeException("Can't decrypt value");
249         }
250     }
251 
252     /**
253      * Examines the BusinessObject's data dictionary entry to determine if it supports
254      * XML export or not and set's canExport appropriately.
255      */
256     protected void populateExportCapabilities(HttpServletRequest request, String boClassName) {
257     	setCanExport(false);
258     	BusinessObjectEntry businessObjectEntry = (BusinessObjectEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(boClassName);
259     	Class<? extends Exporter> exporterClass = businessObjectEntry.getExporterClass();
260     	if (exporterClass != null) {
261     		try {
262     			Exporter exporter = exporterClass.newInstance();
263     			if (exporter.getSupportedFormats(businessObjectEntry.getBusinessObjectClass()).contains(KRADConstants.XML_FORMAT)) {
264     				setCanExport(true);
265     			}
266     		} catch (Exception e) {
267     			LOG.error("Failed to locate or create exporter class: " + exporterClass, e);
268     			throw new RuntimeException("Failed to locate or create exporter class: " + exporterClass);
269     		}
270     	}
271     }
272 
273 
274     /**
275      * @return Returns the fieldConversions.
276      */
277     public String getFieldConversions() {
278         return fieldConversions;
279     }
280 
281 
282     /**
283      * @param fieldConversions The fieldConversions to set.
284      */
285     public void setFieldConversions(String fieldConversions) {
286         this.fieldConversions = fieldConversions;
287     }
288 
289 
290     /**
291      * @return Returns the inquiry sections.
292      */
293     public List getSections() {
294         return sections;
295     }
296 
297 
298     /**
299      * @param sections The sections to set.
300      */
301     public void setSections(List sections) {
302         this.sections = sections;
303     }
304 
305     /**
306      * @return Returns the businessObjectClassName.
307      */
308     public String getBusinessObjectClassName() {
309         return businessObjectClassName;
310     }
311 
312     /**
313      * @param businessObjectClassName The businessObjectClassName to set.
314      */
315     public void setBusinessObjectClassName(String businessObjectClassName) {
316         this.businessObjectClassName = businessObjectClassName;
317     }
318 
319     public Map getEditingMode() {
320         return editingMode;
321     }
322 
323     /**
324      * 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
325      * be encrypted in this map
326      *
327      * @return
328      */
329     public Map<String, String> getInquiryPrimaryKeys() {
330         return this.inquiryPrimaryKeys;
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 fields will be decrypted
335      *
336      * Purposely not named as a getter, to make it harder for POJOFormBase to access it
337      *
338      * @return
339      */
340     public Map<String, String> retrieveInquiryDecryptedPrimaryKeys() {
341         return this.inquiryDecryptedPrimaryKeys;
342     }
343 
344     /**
345      * Sets the map used to pass primary key values between invocations of the inquiry screens after the start method has been called.
346      *
347      * @param inquiryPrimaryKeys
348      */
349     public void setInquiryPrimaryKeys(Map<String, String> inquiryPrimaryKeys) {
350         this.inquiryPrimaryKeys = inquiryPrimaryKeys;
351     }
352 
353     /**
354      * Gets map of collection name -> Boolean mappings.  Used to denote whether a collection name is configured to show inactive records.
355      *
356      * @return
357      */
358     public Map<String, Boolean> getInactiveRecordDisplay() {
359         return getInquirable().getInactiveRecordDisplay();
360     }
361 
362     public Inquirable getInquirable() {
363         return inquirable;
364     }
365 
366     protected void populateInactiveRecordsInIntoInquirable(Inquirable inquirable, HttpServletRequest request) {
367 	for (Enumeration e = request.getParameterNames(); e.hasMoreElements();) {
368 	    String paramName = (String) e.nextElement();
369 	    if (paramName.startsWith(KRADConstants.INACTIVE_RECORD_DISPLAY_PARAM_PREFIX)) {
370 		String collectionName = StringUtils.substringAfter(paramName, KRADConstants.INACTIVE_RECORD_DISPLAY_PARAM_PREFIX);
371 		Boolean showInactive = Boolean.parseBoolean(request.getParameter(paramName));
372 		inquirable.setShowInactiveRecords(collectionName, showInactive);
373 	    }
374 	}
375     }
376 
377     public String getFormKey() {
378         return this.formKey;
379     }
380 
381     public void setFormKey(String formKey) {
382         this.formKey = formKey;
383     }
384 
385 	/**
386 	 * Returns true if this Inquiry supports XML export of the BusinessObject.
387 	 */
388 	public boolean isCanExport() {
389 		return this.canExport;
390 	}
391 
392 	/**
393 	 * Sets whether or not this Inquiry supports XML export of it's BusinessObject.
394 	 */
395 	public void setCanExport(boolean canExport) {
396 		this.canExport = canExport;
397 	}
398 
399 
400 }