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.krad.uif.element;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.krad.datadictionary.parse.BeanTag;
20  import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
21  import org.kuali.rice.krad.uif.component.Component;
22  import org.kuali.rice.krad.uif.container.Container;
23  import org.kuali.rice.krad.uif.container.ContainerBase;
24  import org.kuali.rice.krad.uif.container.PageGroup;
25  import org.kuali.rice.krad.uif.field.FieldGroup;
26  import org.kuali.rice.krad.uif.field.InputField;
27  import org.kuali.rice.krad.uif.util.MessageStructureUtils;
28  import org.kuali.rice.krad.uif.view.View;
29  import org.kuali.rice.krad.util.ErrorMessage;
30  import org.kuali.rice.krad.util.GlobalVariables;
31  import org.kuali.rice.krad.util.KRADUtils;
32  import org.kuali.rice.krad.util.MessageMap;
33  import org.springframework.util.AutoPopulatingList;
34  
35  import java.beans.PropertyEditor;
36  import java.util.ArrayList;
37  import java.util.Arrays;
38  import java.util.Collection;
39  import java.util.HashSet;
40  import java.util.List;
41  import java.util.Map;
42  import java.util.Set;
43  
44  /**
45   * Field that displays error, warning, and info messages for the keys that are
46   * matched. By default, an ValidationMessages will match on id and bindingPath (if this
47   * ValidationMessages is for an InputField), but can be set to match on
48   * additionalKeys and nested components keys (of the its parentComponent).
49   *
50   * In addition, there are a variety of options which can be toggled to effect
51   * the display of these messages during both client and server side validation
52   * display. See documentation on each get method for more details on the effect
53   * of each option.
54   *
55   * @author Kuali Rice Team (rice.collab@kuali.org)
56   */
57  @BeanTag(name="validationMessages")
58  public class ValidationMessages extends ContentElementBase {
59      private static final long serialVersionUID = 780940788435330077L;
60  
61      private List<String> additionalKeysToMatch;
62  
63      private boolean displayMessages;
64  
65      // Error messages
66      private List<String> errors;
67      private List<String> warnings;
68      private List<String> infos;
69  
70      /**
71       * PerformFinalize will generate the messages and counts used by the
72       * errorsField based on the keys that were matched from the MessageMap for
73       * this ValidationMessages. It will also set up nestedComponents of its
74       * parentComponent correctly based on the flags that were chosen for this
75       * ValidationMessages.
76       *
77       * @see org.kuali.rice.krad.uif.field.FieldBase#performFinalize(org.kuali.rice.krad.uif.view.View,
78       *      java.lang.Object, org.kuali.rice.krad.uif.component.Component)
79       */
80      @Override
81      public void performFinalize(View view, Object model, Component parent) {
82          super.performFinalize(view, model, parent);
83  
84          generateMessages(true, view, model, parent);
85      }
86  
87      /**
88       * Generates the messages based on the content in the messageMap
89       *
90       * @param reset - true to reset the errors, warnings, and info lists
91       * @param view - the current View
92       * @param model - the current model
93       * @param parent - the parent of this ValidationMessages
94       */
95      public void generateMessages(boolean reset, View view, Object model, Component parent) {
96          if (reset) {
97              errors = new ArrayList<String>();
98              warnings = new ArrayList<String>();
99              infos = new ArrayList<String>();
100         }
101 
102         List<String> masterKeyList = getKeys(parent);
103         MessageMap messageMap = GlobalVariables.getMessageMap();
104 
105         String parentContainerId = "";
106         Object parentContainer = parent.getContext().get("parent");
107 
108         if (parentContainer != null && (parentContainer instanceof Container
109                 || parentContainer instanceof FieldGroup)) {
110             parentContainerId = ((Component) parentContainer).getId();
111         }
112 
113         //special message component case
114         if (parentContainer != null && parentContainer instanceof Message && ((Message) parentContainer)
115                 .isGenerateSpan()) {
116             parentContainerId = ((Component) parentContainer).getId();
117         }
118 
119         //Add identifying data attributes
120         this.addDataAttribute("messagesFor", parent.getId());
121 
122         if (parent.getDataAttributes().get("parent") == null) {
123             parent.addDataAttribute("parent", parentContainerId);
124         }
125 
126         //Handle the special FieldGroup case - adds the FieldGroup itself to ids handled by this group (this must
127         //be a group if its parent is FieldGroup)
128         if (parentContainer != null && parentContainer instanceof FieldGroup) {
129             masterKeyList.add(parentContainerId);
130         }
131 
132         //Check for message keys that are not matched anywhere on the page - these unmatched messages must still be
133         //displayed at the page level
134         if (parent instanceof PageGroup) {
135             Map<String, PropertyEditor> propertyEditors = view.getViewIndex().getFieldPropertyEditors();
136             Map<String, PropertyEditor> securePropertyEditors = view.getViewIndex().getSecureFieldPropertyEditors();
137             List<String> allPossibleKeys = new ArrayList<String>(propertyEditors.keySet());
138             allPossibleKeys.addAll(securePropertyEditors.keySet());
139 
140             this.addNestedGroupKeys(allPossibleKeys, parent);
141             if (additionalKeysToMatch != null) {
142                 allPossibleKeys.addAll(additionalKeysToMatch);
143             }
144             if (StringUtils.isNotBlank(parent.getId())) {
145                 allPossibleKeys.add(parent.getId());
146             }
147 
148             Set<String> messageKeys = new HashSet<String>();
149             messageKeys.addAll(messageMap.getAllPropertiesWithErrors());
150             messageKeys.addAll(messageMap.getAllPropertiesWithWarnings());
151             messageKeys.addAll(messageMap.getAllPropertiesWithInfo());
152 
153             messageKeys.removeAll(allPossibleKeys);
154 
155             masterKeyList.addAll(messageKeys);
156         }
157 
158         for (String key : masterKeyList) {
159             errors.addAll(getMessages(view, key, messageMap.getErrorMessagesForProperty(key, true)));
160             warnings.addAll(getMessages(view, key, messageMap.getWarningMessagesForProperty(key, true)));
161             infos.addAll(getMessages(view, key, messageMap.getInfoMessagesForProperty(key, true)));
162         }
163     }
164 
165     /**
166      * Gets all the messages from the list of lists passed in (which are
167      * lists of ErrorMessages associated to the key) and uses the configuration
168      * service to get the message String associated. This will also combine
169      * error messages per a field if that option is turned on. If
170      * displayFieldLabelWithMessages is turned on, it will also find the label
171      * by key passed in.
172      *
173      * @param view
174      * @param key
175      * @param lists
176      * @return
177      */
178     private List<String> getMessages(View view, String key, List<AutoPopulatingList<ErrorMessage>> lists) {
179         List<String> result = new ArrayList<String>();
180         for (List<ErrorMessage> errorList : lists) {
181             if (errorList != null && StringUtils.isNotBlank(key)) {
182                 for (ErrorMessage e : errorList) {
183                     String message = KRADUtils.getMessageText(e, true);
184                     message = MessageStructureUtils.translateStringMessage(message);
185 
186                     result.add(message);
187                 }
188             }
189         }
190 
191         return result;
192     }
193 
194     /**
195      * Gets all the keys associated to this ValidationMessages. This includes the id of
196      * the parent component, additional keys to match, and the bindingPath if
197      * this is an ValidationMessages for an InputField. These are the keys that are
198      * used to match errors with their component and display them as part of its
199      * ValidationMessages.
200      *
201      * @return
202      */
203     protected List<String> getKeys(Component parent) {
204         List<String> keyList = new ArrayList<String>();
205         if (additionalKeysToMatch != null) {
206             keyList.addAll(additionalKeysToMatch);
207         }
208         if (StringUtils.isNotBlank(parent.getId())) {
209             keyList.add(parent.getId());
210         }
211         if (parent instanceof InputField) {
212             if (((InputField) parent).getBindingInfo() != null && StringUtils.isNotEmpty(
213                     ((InputField) parent).getBindingInfo().getBindingPath())) {
214                 keyList.add(((InputField) parent).getBindingInfo().getBindingPath());
215             }
216         }
217 
218         return keyList;
219     }
220 
221     /**
222      * Adds all group keys of this component (starting from this component itself) by calling getKeys on each of
223      * its nested group's ValidationMessages and adding them to the list.
224      *
225      * @param keyList
226      * @param component
227      */
228     private void addNestedGroupKeys(Collection<String> keyList, Component component) {
229         for (Component c : component.getComponentsForLifecycle()) {
230             ValidationMessages ef = null;
231             if (c instanceof ContainerBase) {
232                 ef = ((ContainerBase) c).getValidationMessages();
233             } else if (c instanceof FieldGroup) {
234                 ef = ((FieldGroup) c).getGroup().getValidationMessages();
235             }
236             if (ef != null) {
237                 keyList.addAll(ef.getKeys(c));
238                 addNestedGroupKeys(keyList, c);
239             }
240         }
241     }
242 
243     /**
244      * AdditionalKeysToMatch is an additional list of keys outside of the
245      * default keys that will be matched when messages are returned after a form
246      * is submitted. These keys are only used for displaying messages generated
247      * by the server and have no effect on client side validation error display.
248      *
249      * @return the additionalKeysToMatch
250      */
251     @BeanTagAttribute(name="additionalKeysToMatch",type= BeanTagAttribute.AttributeType.LISTVALUE)
252     public List<String> getAdditionalKeysToMatch() {
253         return this.additionalKeysToMatch;
254     }
255 
256     /**
257      * Convenience setter for additional keys to match that takes a string argument and
258      * splits on comma to build the list
259      *
260      * @param additionalKeysToMatch String to parse
261      */
262     public void setAdditionalKeysToMatch(String additionalKeysToMatch) {
263         if (StringUtils.isNotBlank(additionalKeysToMatch)) {
264             this.additionalKeysToMatch = Arrays.asList(StringUtils.split(additionalKeysToMatch, ","));
265         }
266     }
267 
268     /**
269      * @param additionalKeysToMatch the additionalKeysToMatch to set
270      */
271     public void setAdditionalKeysToMatch(List<String> additionalKeysToMatch) {
272         this.additionalKeysToMatch = additionalKeysToMatch;
273     }
274 
275     /**
276      * <p>If true, error, warning, and info messages will be displayed (provided
277      * they are also set to display). Otherwise, no messages for this
278      * ValidationMessages container will be displayed (including ones set to display).
279      * This is a global display on/off switch for all messages.</p>
280      *
281      * <p>Other areas of the screen react to
282      * a display flag being turned off at a certain level, if display is off for a field, the next
283      * level up will display that fields full message text, and if display is off at a section the
284      * next section up will display those messages nested in a sublist.</p>
285      *
286      * @return the displayMessages
287      */
288     @BeanTagAttribute(name="displayMessages")
289     public boolean isDisplayMessages() {
290         return this.displayMessages;
291     }
292 
293     /**
294      * @param displayMessages the displayMessages to set
295      */
296     public void setDisplayMessages(boolean displayMessages) {
297         this.displayMessages = displayMessages;
298     }
299 
300     /**
301      * The list of error messages found for the keys that were matched on this
302      * ValidationMessages This is generated and cannot be set
303      *
304      * @return the errors
305      */
306     @BeanTagAttribute(name="errors",type= BeanTagAttribute.AttributeType.LISTVALUE)
307     public List<String> getErrors() {
308         return this.errors;
309     }
310 
311     /**
312      * The list of warning messages found for the keys that were matched on this
313      * ValidationMessages This is generated and cannot be set
314      *
315      * @return the warnings
316      */
317     @BeanTagAttribute(name="warnings",type= BeanTagAttribute.AttributeType.LISTVALUE)
318     public List<String> getWarnings() {
319         return this.warnings;
320     }
321 
322     /**
323      * The list of info messages found for the keys that were matched on this
324      * ValidationMessages This is generated and cannot be set
325      *
326      * @return the infos
327      */
328     @BeanTagAttribute(name="infos",type= BeanTagAttribute.AttributeType.LISTVALUE)
329     public List<String> getInfos() {
330         return this.infos;
331     }
332 
333 }