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