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