001/**
002 * Copyright 2005-2018 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.krad.web.form;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.CoreApiServiceLocator;
020import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
021import org.kuali.rice.krad.uif.util.ObjectPropertyUtils;
022import org.kuali.rice.krad.uif.util.SessionTransient;
023
024import java.io.Serializable;
025import java.lang.annotation.Annotation;
026import java.lang.reflect.Field;
027import java.util.ArrayList;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.List;
031import java.util.Map;
032import java.util.Set;
033import java.util.Vector;
034
035/**
036 * Manages Uif form objects for a session.
037 *
038 * @author Kuali Rice Team (rice.collab@kuali.org)
039 */
040public class UifFormManager implements Serializable {
041    private static final long serialVersionUID = -6323378881342207080L;
042
043    private int maxNumberOfSessionForms = 5;
044
045    protected Vector accessedFormKeys;
046
047    protected Map<String, UifFormBase> sessionForms;
048
049    /**
050     * Create a new form manager with an empty list of forms for the session.
051     */
052    public UifFormManager() {
053        this.accessedFormKeys = new Vector();
054        this.sessionForms = new HashMap<String, UifFormBase>();
055
056        if (CoreApiServiceLocator.getKualiConfigurationService() != null) {
057            String maxNumberOfSessionFormsConfig =
058                    CoreApiServiceLocator.getKualiConfigurationService().getPropertyValueAsString(
059                            "maxNumberOfSessionForms");
060            if (StringUtils.isNotBlank(maxNumberOfSessionFormsConfig)) {
061                maxNumberOfSessionForms = Integer.parseInt(maxNumberOfSessionFormsConfig);
062            }
063        }
064    }
065
066    /**
067     * Add a form to the session.
068     *
069     * @param form to be added to the session
070     */
071    public synchronized void addSessionForm(UifFormBase form) {
072        if (form == null || StringUtils.isBlank(form.getFormKey())) {
073            throw new RiceIllegalArgumentException("Form or form key was null");
074        }
075
076        sessionForms.put(form.getFormKey(), form);
077
078        // add form key to top of vector indicating it is most recent
079        if (accessedFormKeys.contains(form.getFormKey())) {
080            accessedFormKeys.removeElement(form.getFormKey());
081        }
082        accessedFormKeys.add(form.getFormKey());
083
084        // check if we have too many forms and need to remove an old one
085        if (sessionForms.size() > maxNumberOfSessionForms) {
086            // get the oldest form we have
087            String removeFormKey = (String) accessedFormKeys.get(0);
088            if (sessionForms.containsKey(removeFormKey)) {
089                sessionForms.remove(removeFormKey);
090            }
091            accessedFormKeys.removeElementAt(0);
092        }
093    }
094
095    /**
096     * Retrieve a form from the session.
097     *
098     * @param formKey of the form to retrieve from the session
099     * @return UifFormBase
100     */
101    public UifFormBase getSessionForm(String formKey) {
102        if (sessionForms.containsKey(formKey)) {
103            return sessionForms.get(formKey);
104        }
105
106        return null;
107    }
108
109    /**
110     * Removes the stored form data and the forms from the breadcrumb history from the session.
111     *
112     * @param form to be removed
113     */
114    public void removeSessionForm(UifFormBase form) {
115        if (form == null || StringUtils.isBlank(form.getFormKey())) {
116            return;
117        }
118
119        removeSessionFormByKey(form.getFormKey());
120    }
121
122    /**
123     * Removes the stored form data and the forms from the breadcrumb history from the session.
124     *
125     * @param formKey of the form to be removed
126     */
127    public void removeFormWithHistoryFormsByKey(String formKey) {
128        if (sessionForms.containsKey(formKey)) {
129            removeSessionFormByKey(formKey);
130        }
131    }
132
133    /**
134     * Removes the stored form data from the session.
135     *
136     * @param formKey of the form to be removed
137     */
138    public void removeSessionFormByKey(String formKey) {
139        if (accessedFormKeys.contains(formKey)) {
140            accessedFormKeys.removeElement(formKey);
141        }
142
143        if (sessionForms.containsKey(formKey)) {
144            sessionForms.remove(formKey);
145        }
146    }
147
148    /**
149     * Indicates whether the form manager has a session form with the given key.
150     *
151     * @param formKey key of the form in session to check for
152     * @return true if the manager contains the session form, false if not
153     */
154    public boolean hasSessionForm(String formKey) {
155        return sessionForms.containsKey(formKey);
156    }
157
158    /**
159     * Retrieves the session form based on the formkey and updates the non session transient
160     * variables on the request form from the session form.
161     *
162     * @param requestForm
163     * @param formKey
164     */
165    public void updateFormWithSession(UifFormBase requestForm, String formKey) {
166        UifFormBase sessionForm = sessionForms.get(formKey);
167        if (sessionForm == null) {
168            return;
169        }
170
171        if (!sessionForm.getClass().isAssignableFrom(requestForm.getClass())) {
172            throw new RuntimeException(
173                    "Session form mismatch, session form class not assignable from request form class");
174        }
175
176        List<Field> fields = new ArrayList<Field>();
177        fields = getAllFields(fields, sessionForm.getClass(), UifFormBase.class);
178        for (Field field : fields) {
179            boolean copyValue = true;
180            for (Annotation an : field.getAnnotations()) {
181                if (an instanceof SessionTransient) {
182                    copyValue = false;
183                }
184            }
185
186            if (copyValue && ObjectPropertyUtils.isReadableProperty(sessionForm, field.getName()) && ObjectPropertyUtils
187                    .isWritableProperty(sessionForm, field.getName())) {
188                Object fieldValue = ObjectPropertyUtils.getPropertyValue(sessionForm, field.getName());
189                ObjectPropertyUtils.setPropertyValue(requestForm, field.getName(), fieldValue);
190            }
191        }
192    }
193
194    /**
195     * Removes the values that are marked @SessionTransient from the form.
196     *
197     * @param form the form from which the session transient values have been purged
198     */
199    public void purgeForm(UifFormBase form) {
200        List<Field> fields = new ArrayList<Field>();
201        fields = getAllFields(fields, form.getClass(), UifFormBase.class);
202        for (Field field : fields) {
203            boolean purgeValue = false;
204
205            if (!field.getType().isPrimitive()) {
206                for (Annotation an : field.getAnnotations()) {
207                    if (an instanceof SessionTransient) {
208                        purgeValue = true;
209                    }
210                }
211            }
212
213            if (purgeValue && ObjectPropertyUtils.isWritableProperty(form, field.getName())) {
214                ObjectPropertyUtils.setPropertyValue(form, field.getName(), null);
215            }
216        }
217    }
218
219    private List<Field> getAllFields(List<Field> fields, Class<?> type, Class<?> stopAt) {
220        for (Field field : type.getDeclaredFields()) {
221            fields.add(field);
222        }
223
224        if (type.getSuperclass() != null && !type.getName().equals(stopAt.getName())) {
225            fields = getAllFields(fields, type.getSuperclass(), stopAt);
226        }
227
228        return fields;
229    }
230
231    /**
232     * Internal vector maintained to keep track of accessed form and the order in which they were accessed.
233     *
234     * <p>Used for the form clearing process. When forms are added to the manager their key is added to the top of
235     * the vector. When a form needs to be cleared, the form identified by the key at the botton of this vector
236     * is removed</p>
237     *
238     * @return Vector instance holding form key strings
239     */
240    protected Vector getAccessedFormKeys() {
241        return accessedFormKeys;
242    }
243
244    /**
245     * Maximum number of forms that can be stored at one time by the manager.
246     *
247     * @return int max number of forms
248     */
249    public int getMaxNumberOfSessionForms() {
250        return maxNumberOfSessionForms;
251    }
252
253    /**
254     * @see UifFormManager#getMaxNumberOfSessionForms()
255     */
256    public void setMaxNumberOfSessionForms(int maxNumberOfSessionForms) {
257        this.maxNumberOfSessionForms = maxNumberOfSessionForms;
258    }
259
260}