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, " "));
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 <> are not part of the content below,
163 * they specify placeholders):
164 * <ul>
165 * <li>[id=<component id>] - 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>[<html tag>][/<html tag>] - insert html content directly into the message content at that
168 * location,
169 * without the need to escape the <> characters in xml</li>
170 * <li>[color=<html color code/name>][/color] - wrap content in color tags to make text that color
171 * in the message</li>
172 * <li>[css=<css classes>][/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 }