001    /**
002     * Copyright 2005-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.krad.uif.element;
017    
018    import com.google.common.collect.Lists;
019    import org.apache.commons.lang.StringUtils;
020    import org.kuali.rice.krad.datadictionary.parse.BeanTag;
021    import org.kuali.rice.krad.datadictionary.parse.BeanTagAttribute;
022    import org.kuali.rice.krad.datadictionary.parse.BeanTags;
023    import org.kuali.rice.krad.datadictionary.validator.ValidationTrace;
024    import org.kuali.rice.krad.datadictionary.validator.Validator;
025    import org.kuali.rice.krad.uif.UifConstants;
026    import org.kuali.rice.krad.uif.component.Component;
027    import org.kuali.rice.krad.uif.util.MessageStructureUtils;
028    import org.kuali.rice.krad.uif.view.View;
029    import org.kuali.rice.krad.util.KRADConstants;
030    
031    import java.util.List;
032    
033    /**
034     * Encapsulates a text message to be displayed
035     *
036     * <p>
037     * The <code>Message</code> is used to display static text in the user
038     * interface
039     * </p>
040     *
041     * @author Kuali Rice Team (rice.collab@kuali.org)
042     */
043    @BeanTags({@BeanTag(name = "message-bean", parent = "Uif-Message"),
044            @BeanTag(name = "instructionalMessage-bean", parent = "Uif-InstructionalMessage"),
045            @BeanTag(name = "constraintMessage-bean", parent = "Uif-ConstraintMessage"),
046            @BeanTag(name = "requiredMessage-bean", parent = "Uif-RequiredMessage"),
047            @BeanTag(name = "requiredInstructionsMessage-bean", parent = "Uif-RequiredInstructionsMessage"),
048            @BeanTag(name = "stateBased-requiredInstructionsMessage-bean",
049                    parent = "Uif-StateBased-RequiredInstructionsMessage"),
050            @BeanTag(name = "dialogPrompt-bean", parent = "Uif-DialogPrompt"),
051            @BeanTag(name = "imageCutineMessage-bean", parent = "Uif-ImageCutineMessage")})
052    public class Message extends ContentElementBase {
053        private static final long serialVersionUID = 4090058533452450395L;
054    
055        private String messageText;
056        private boolean generateSpan;
057    
058        private List<Component> inlineComponents;
059        private List<Component> messageComponentStructure;
060    
061        private boolean parseComponents;
062    
063        public Message() {
064            super();
065    
066            generateSpan = true;
067            parseComponents = true;
068        }
069    
070        /**
071         * Message perfom apply model parses message text for rich text functionality if the messageText contains
072         * [ or ] special characters
073         *
074         * @see Component#performApplyModel(org.kuali.rice.krad.uif.view.View, Object, org.kuali.rice.krad.uif.component.Component)
075         */
076        @Override
077        public void performApplyModel(View view, Object model, Component parent) {
078            super.performApplyModel(view, model, parent);
079    
080            //if messageText contains the special characters [] then parse and fill in the messageComponentStructure
081            //but if messageComponentStructure has already been set it overrides messageText by default
082            if (messageText != null && messageText.contains(KRADConstants.MessageParsing.LEFT_TOKEN) &&
083                    messageText.contains(KRADConstants.MessageParsing.RIGHT_TOKEN) &&
084                    (messageComponentStructure == null || messageComponentStructure.isEmpty())) {
085    
086                messageComponentStructure = MessageStructureUtils.parseMessage(this.getId(), this.getMessageText(),
087                        this.getInlineComponents(), view, parseComponents);
088    
089                if (messageComponentStructure != null) {
090                    for (Component component : messageComponentStructure) {
091                        view.getViewHelperService().performComponentInitialization(view, model, component);
092                    }
093                }
094            }
095        }
096    
097        /**
098         * @see Component#performFinalize(org.kuali.rice.krad.uif.view.View, Object, org.kuali.rice.krad.uif.component.Component)
099         */
100        @Override
101        public void performFinalize(View view, Object model, Component parent) {
102            super.performFinalize(view, model, parent);
103    
104            // message needs to be aware of its own parent because it now contains content that can have validation
105            this.addDataAttribute(UifConstants.DataAttributes.PARENT, parent.getId());
106        }
107    
108        /**
109         * @see org.kuali.rice.krad.uif.component.ComponentBase#getComponentsForLifecycle()
110         */
111        @Override
112        public List<Component> getComponentsForLifecycle() {
113            List<Component> components = super.getComponentsForLifecycle();
114    
115            if (messageComponentStructure != null) {
116                for (Component component : messageComponentStructure) {
117                    components.add(component);
118                }
119            }
120    
121            return components;
122        }
123    
124        /**
125         * Override to render only if the message text has been given or there is a conditional expression on the
126         * message text
127         *
128         * @see org.kuali.rice.krad.uif.component.ComponentBase#isRender()
129         */
130        @Override
131        public boolean isRender() {
132            boolean render = super.isRender();
133    
134            if (render) {
135                render = getPropertyExpressions().containsKey("messageText") || (StringUtils.isNotBlank(messageText)
136                        && !StringUtils.equals(messageText, "&nbsp;"));
137            }
138    
139            return render;
140        }
141    
142        /**
143         * Text that makes up the message that will be displayed.
144         *
145         * <p>If special characters [] are detected the message inserts special content at that location.
146         * The types of features supported are (note that &lt;&gt; are not part of the content below,
147         * they specify placeholders):
148         * <ul>
149         * <li>[id=&lt;component id&gt;] - insert component with id specified at that location in the message</li>
150         * <li>[n] - insert component at index n from the inlineComponent list</li>
151         * <li>[&lt;html tag&gt;][/&lt;html tag&gt;] - insert html content directly into the message content at that
152         * location,
153         * without the need to escape the &lt;&gt; characters in xml</li>
154         * <li>[color=&lt;html color code/name&gt;][/color] - wrap content in color tags to make text that color
155         * in the message</li>
156         * <li>[css=&lt;css classes&gt;][/css] - apply css classes specified to the wrapped content - same as wrapping
157         * the content in span with class property set</li>
158         * </ul>
159         * If the [] characters are needed in message text, they need to be declared with an escape character: \\[ \\]
160         * </p>
161         *
162         * @return message text
163         */
164        @BeanTagAttribute(name = "messageText")
165        public String getMessageText() {
166            return this.messageText;
167        }
168    
169        /**
170         * Setter for the message text
171         *
172         * @param messageText
173         */
174        public void setMessageText(String messageText) {
175            this.messageText = messageText;
176        }
177    
178        /**
179         * If true, generate the span around this message (default).  When false, skip span generation for this
180         * message - this has the additional effect the css classes/style classes will be lost for this message.
181         *
182         * @return true if generating a wrapping span, false otherwise
183         */
184        @BeanTagAttribute(name = "generateSpan")
185        public boolean isGenerateSpan() {
186            return generateSpan;
187        }
188    
189        /**
190         * Sets the generate span flag
191         *
192         * @param generateSpan
193         */
194        public void setGenerateSpan(boolean generateSpan) {
195            this.generateSpan = generateSpan;
196        }
197    
198        /**
199         * The message component structure is a list of components which represent the components that make up a message
200         * when using rich message functionality.
201         *
202         * <p>The structure represents the parsed messageText when not set. Normally this structure is setup by the Message
203         * class and <b>SHOULD NOT BE SET</b> in xml, unless full control over the structure is needed.  </p>
204         *
205         * @return list of components which represent the message structure
206         */
207        public List<Component> getMessageComponentStructure() {
208            return messageComponentStructure;
209        }
210    
211        /**
212         * Set the message component structure.  This will override/ignore messageText when set. Normally
213         * this <b>SHOULD NOT BE SET</b> by the xml configuration.
214         *
215         * @param messageComponentStructure list of components which represent the message structure
216         */
217        public void setMessageComponentStructure(List<Component> messageComponentStructure) {
218            this.messageComponentStructure = messageComponentStructure;
219        }
220    
221        /**
222         * The inlineComponents are a list of components in order by index.
223         *
224         * <p>inlineComponents is only used when the message is using rich message functionality.  A message
225         * with [0] will reference component at index 0 of this list and insert it at that place in the message,
226         * and likewise [1] will reference item 1, etc.  If the index referenced is out of bounds (or list doesnt exist),
227         * an error will be thrown during message parse.</p>
228         *
229         * @return the inlineComponents to be filled in at indexes referenced by [n] in the message
230         */
231        @BeanTagAttribute(name = "inlineComponents", type = BeanTagAttribute.AttributeType.LISTBEAN)
232        public List<Component> getInlineComponents() {
233            return inlineComponents;
234        }
235    
236        /**
237         * Set the inlineComponents to be filled in at indexes referenced by [n] in the message
238         *
239         * @param inlineComponents the inlineComponents to be filled in at indexes referenced by [n] in the message
240         */
241        public void setInlineComponents(List<Component> inlineComponents) {
242            this.inlineComponents = inlineComponents;
243        }
244    
245        /**
246         * @see org.kuali.rice.krad.uif.component.Component#completeValidation
247         */
248        @Override
249        public void completeValidation(ValidationTrace tracer) {
250            tracer.addBean(this);
251    
252            // Checks that text is set
253            if (getMessageText() == null) {
254                if (Validator.checkExpressions(this, "messageText")) {
255                    String currentValues[] = {"messageText  =" + getMessageText()};
256                    tracer.createWarning("MessageText should be set", currentValues);
257                }
258            }
259    
260            super.completeValidation(tracer.getCopy());
261        }
262    
263        /**
264         * Indicates if the inline components must be parsed for rich messages
265         *
266         * @return boolean
267         */
268        @BeanTagAttribute(name = "parseComponents")
269        public boolean isParseComponents() {
270            return parseComponents;
271        }
272    
273        /**
274         * Sets the parse components flag to indicate if inline components must be parsed for rich messages
275         *
276         * @param parseComponents
277         */
278        public void setParseComponents(boolean parseComponents) {
279            this.parseComponents = parseComponents;
280        }
281    
282        /**
283         * @see org.kuali.rice.krad.uif.component.ComponentBase#copy()
284         */
285        @Override
286        protected <T> void copyProperties(T component) {
287            super.copyProperties(component);
288            Message messageCopy = (Message) component;
289            messageCopy.setGenerateSpan(this.generateSpan);
290    
291            if (this.inlineComponents != null) {
292                List<Component> inlineComponents = Lists.newArrayListWithExpectedSize(this.inlineComponents.size());
293    
294                for (Component inlineComponent : this.inlineComponents) {
295                    inlineComponents.add((Component)inlineComponent.copy());
296                }
297    
298                messageCopy.setInlineComponents(inlineComponents);
299            }
300    
301            if (this.messageComponentStructure != null) {
302                List<Component> messageComponentStructure = Lists.newArrayListWithExpectedSize(
303                        this.messageComponentStructure.size());
304    
305                for (Component messageComponentStructureItem : this.messageComponentStructure) {
306                    messageComponentStructure.add((Component)messageComponentStructureItem.copy());
307                }
308    
309                messageCopy.setMessageComponentStructure(messageComponentStructure);
310            }
311    
312            messageCopy.setMessageText(this.messageText);
313            messageCopy.setParseComponents(this.parseComponents);
314        }
315    }