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