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 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.datadictionary.parse.BeanTags;
22  import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
23  import org.kuali.rice.krad.datadictionary.validator.Validator;
24  import org.kuali.rice.krad.uif.UifConstants;
25  import org.kuali.rice.krad.uif.component.Component;
26  import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
27  import org.kuali.rice.krad.uif.util.ComponentUtils;
28  import org.kuali.rice.krad.uif.util.LifecycleElement;
29  import org.kuali.rice.krad.uif.util.MessageStructureUtils;
30  import org.kuali.rice.krad.util.KRADConstants;
31  
32  import java.util.List;
33  import java.util.regex.Matcher;
34  import java.util.regex.Pattern;
35  
36  /**
37   * Encapsulates a text message to be displayed
38   *
39   * <p>
40   * The <code>Message</code> is used to display static text in the user
41   * interface
42   * </p>
43   *
44   * @author Kuali Rice Team (rice.collab@kuali.org)
45   */
46  @BeanTags({@BeanTag(name = "message-bean", parent = "Uif-Message"),
47          @BeanTag(name = "instructionalMessage-bean", parent = "Uif-InstructionalMessage"),
48          @BeanTag(name = "constraintMessage-bean", parent = "Uif-ConstraintMessage"),
49          @BeanTag(name = "requiredMessage-bean", parent = "Uif-RequiredMessage"),
50          @BeanTag(name = "requiredInstructionsMessage-bean", parent = "Uif-RequiredInstructionsMessage"),
51          @BeanTag(name = "stateBased-requiredInstructionsMessage-bean",
52                  parent = "Uif-StateBased-RequiredInstructionsMessage"),
53          @BeanTag(name = "dialogPrompt-bean", parent = "Uif-DialogPrompt"),
54          @BeanTag(name = "imageCutineMessage-bean", parent = "Uif-ImageCutineMessage")})
55  public class Message extends ContentElementBase {
56      private static final long serialVersionUID = 4090058533452450395L;
57  
58      // This regex is a check to see if the message is a rich message and it contains potential non-inline elements
59      private static Pattern blockElementCheck = Pattern.compile(
60              "[\\[|\\<](?!color|action|link|css|button|input|label|select|textarea|abbr"
61                      + "|strong|img|a[\\s\\]]|span[\\s\\]]|b[\\s\\]]|i[\\s\\]]|br[\\s\\]/])[^/]*?/?[\\]|\\>]");
62  
63      private String messageText;
64      private String wrapperTag;
65      private boolean renderWrapperTag;
66  
67      private List<Component> inlineComponents;
68      private List<Component> messageComponentStructure;
69  
70      private boolean parseComponents;
71      private boolean richMessage;
72      private boolean containsBlockElements;
73  
74      public Message() {
75          super();
76  
77          renderWrapperTag = true;
78          parseComponents = true;
79      }
80  
81      /**
82       * Message perfom apply model parses message text for rich text functionality if the messageText contains
83       * [ or ] special characters
84       *
85       * {@inheritDoc}
86       */
87      @Override
88      public void performApplyModel(Object model, LifecycleElement parent) {
89          super.performApplyModel(model, parent);
90  
91          //if messageText contains the special characters [] then parse and fill in the messageComponentStructure
92          //but if messageComponentStructure has already been set it overrides messageText by default
93          if (messageText != null && messageText.contains(KRADConstants.MessageParsing.LEFT_TOKEN) &&
94                  messageText.contains(KRADConstants.MessageParsing.RIGHT_TOKEN) &&
95                  (messageComponentStructure == null || messageComponentStructure.isEmpty())) {
96              richMessage = true;
97  
98              // Check to see if message contains pontential block elements (non-inline)
99              Matcher matcher = blockElementCheck.matcher(messageText);
100             containsBlockElements = matcher.find();
101 
102             if (StringUtils.isBlank(wrapperTag) && containsBlockElements) {
103                 wrapperTag = UifConstants.WrapperTags.DIV;
104             }
105 
106             messageComponentStructure = MessageStructureUtils.parseMessage(this.getId(), this.getMessageText(),
107                     this.getInlineComponents(), ViewLifecycle.getView(), parseComponents);
108         } else if (messageText != null && messageText.contains("<") && messageText.contains(">")) {
109             // Straight inline html case
110             // Check to see if message contains pontential block elements (non-inline)
111             Matcher matcher = blockElementCheck.matcher(messageText);
112             containsBlockElements = matcher.find();
113 
114             // Must be in a div it contains potential block elements
115             if (StringUtils.isBlank(wrapperTag) && containsBlockElements) {
116                 wrapperTag = UifConstants.WrapperTags.DIV;
117             }
118         }
119 
120         // If the wrapper element is not set by the bean def or the above logic to check for block elements, default
121         // to the p tag
122         if (StringUtils.isBlank(wrapperTag)) {
123             wrapperTag = UifConstants.WrapperTags.P;
124         }
125     }
126 
127     /**
128      * {@inheritDoc}
129      */
130     @Override
131     public void performFinalize(Object model, LifecycleElement parent) {
132         super.performFinalize(model, parent);
133 
134         if (messageComponentStructure != null && !messageComponentStructure.isEmpty()) {
135             // Message needs to be aware of its own parent because it now contains content that can have validation
136             this.addDataAttribute(UifConstants.DataAttributes.PARENT, parent.getId());
137         }
138     }
139 
140     /**
141      * Override to render only if the message text has been given or there is a conditional expression on the
142      * message text
143      *
144      * {@inheritDoc}
145      */
146     @Override
147     public boolean isRender() {
148         boolean render = super.isRender();
149 
150         if (render) {
151             render = getPropertyExpressions().containsKey("messageText") || (StringUtils.isNotBlank(messageText)
152                     && !StringUtils.equals(messageText, "&nbsp;"));
153         }
154 
155         return render;
156     }
157 
158     /**
159      * Text that makes up the message that will be displayed.
160      *
161      * <p>If special characters [] are detected the message inserts special content at that location.
162      * The types of features supported are (note that &lt;&gt; are not part of the content below,
163      * they specify placeholders):
164      * <ul>
165      * <li>[id=&lt;component id&gt;] - insert component with id specified at that location in the message</li>
166      * <li>[n] - insert component at index n from the inlineComponent list</li>
167      * <li>[&lt;html tag&gt;][/&lt;html tag&gt;] - insert html content directly into the message content at that
168      * location,
169      * without the need to escape the &lt;&gt; characters in xml</li>
170      * <li>[color=&lt;html color code/name&gt;][/color] - wrap content in color tags to make text that color
171      * in the message</li>
172      * <li>[css=&lt;css classes&gt;][/css] - apply css classes specified to the wrapped content - same as wrapping
173      * the content in span with class property set</li>
174      * </ul>
175      * If the [] characters are needed in message text, they need to be declared with an escape character: \\[ \\]
176      * </p>
177      *
178      * @return message text
179      */
180     @BeanTagAttribute(name = "messageText")
181     public String getMessageText() {
182         return this.messageText;
183     }
184 
185     /**
186      * Setter for the message text
187      *
188      * @param messageText
189      */
190     public void setMessageText(String messageText) {
191         this.messageText = messageText;
192     }
193 
194     /**
195      * Defines the html tag that will wrap this group, if left blank, this will automatically be set by the framework
196      * to the appropriate tag (in most cases p or div)
197      *
198      * @return the html tag used to wrap this group
199      */
200     @BeanTagAttribute(name = "wrapperTag")
201     public String getWrapperTag() {
202         return wrapperTag;
203     }
204 
205     /**
206      * @see org.kuali.rice.krad.uif.element.Message#getWrapperTag()
207      */
208     public void setWrapperTag(String wrapperTag) {
209         this.wrapperTag = wrapperTag;
210     }
211 
212     /**
213      * If true, render the wrapper element (p or div) around this message (default true).
214      *
215      * <p>The wrapper will be either a p tag, for when the element only contains inline elements, or a div tag, for
216      * when the message might contain block level elements or undetermined html tags resulting from rich message
217      * functionality.  When false, skips the wrapper generation for this
218      * message - this has the additional effect the css classes/style classes will be lost for this message. </p>
219      *
220      * @return true if generating a wrapping span, false otherwise
221      */
222     @BeanTagAttribute(name = "renderWrapperTag")
223     public boolean isRenderWrapperTag() {
224         return renderWrapperTag && wrapperTag != null;
225     }
226 
227     /**
228      * Sets the generate wrapper element flag
229      *
230      * @param renderWrapperTag
231      */
232     public void setRenderWrapperTag(boolean renderWrapperTag) {
233         this.renderWrapperTag = renderWrapperTag;
234     }
235 
236     /**
237      * The message component structure is a list of components which represent the components that make up a message
238      * when using rich message functionality.
239      *
240      * <p>The structure represents the parsed messageText when not set. Normally this structure is setup by the Message
241      * class and <b>SHOULD NOT BE SET</b> in xml, unless full control over the structure is needed.  </p>
242      *
243      * @return list of components which represent the message structure
244      */
245     public List<Component> getMessageComponentStructure() {
246         return messageComponentStructure;
247     }
248 
249     /**
250      * Set the message component structure.  This will override/ignore messageText when set. Normally
251      * this <b>SHOULD NOT BE SET</b> by the xml configuration.
252      *
253      * @param messageComponentStructure list of components which represent the message structure
254      */
255     public void setMessageComponentStructure(List<Component> messageComponentStructure) {
256         this.messageComponentStructure = messageComponentStructure;
257     }
258 
259     /**
260      * The inlineComponents are a list of components in order by index.
261      *
262      * <p>inlineComponents is only used when the message is using rich message functionality.  A message
263      * with [0] will reference component at index 0 of this list and insert it at that place in the message,
264      * and likewise [1] will reference item 1, etc.  If the index referenced is out of bounds (or list doesnt exist),
265      * an error will be thrown during message parse.</p>
266      *
267      * @return the inlineComponents to be filled in at indexes referenced by [n] in the message
268      */
269     @BeanTagAttribute(name = "inlineComponents", type = BeanTagAttribute.AttributeType.LISTBEAN)
270     public List<Component> getInlineComponents() {
271         return inlineComponents;
272     }
273 
274     /**
275      * Set the inlineComponents to be filled in at indexes referenced by [n] in the message
276      *
277      * @param inlineComponents the inlineComponents to be filled in at indexes referenced by [n] in the message
278      */
279     public void setInlineComponents(List<Component> inlineComponents) {
280         this.inlineComponents = inlineComponents;
281     }
282 
283     /**
284      * Indicates if the inline components must be parsed for rich messages
285      *
286      * @return boolean
287      */
288     @BeanTagAttribute(name = "parseComponents")
289     public boolean isParseComponents() {
290         return parseComponents;
291     }
292 
293     /**
294      * Sets the parse components flag to indicate if inline components must be parsed for rich messages
295      *
296      * @param parseComponents
297      */
298     public void setParseComponents(boolean parseComponents) {
299         this.parseComponents = parseComponents;
300     }
301 
302     /**
303      * If this message is considered a rich message (is using some rich message functionality with by using
304      * the special [] tags), returns true
305      *
306      * @return return true if this message contains rich message content
307      */
308     public boolean isRichMessage() {
309         return richMessage;
310     }
311 
312     /**
313      * True if the message contains block elements, or when it contains an unknown tag that may be a block element.
314      *
315      * @return true when the message contains block elements (non-inline elements), false otherwise
316      */
317     public boolean isContainsBlockElements() {
318         return containsBlockElements;
319     }
320 
321     /**
322      * {@inheritDoc}
323      */
324     @Override
325     public void completeValidation(ValidationTrace tracer) {
326         tracer.addBean(this);
327 
328         // Checks that text is set
329         if (getMessageText() == null) {
330             if (Validator.checkExpressions(this, "messageText")) {
331                 String currentValues[] = {"messageText  =" + getMessageText()};
332                 tracer.createWarning("MessageText should be set", currentValues);
333             }
334         }
335 
336         super.completeValidation(tracer.getCopy());
337     }
338 }