View Javadoc

1   /**
2    * Copyright 2010 The Kuali Foundation Licensed under the
3    * Educational Community License, Version 2.0 (the "License"); you may
4    * not use this file except in compliance with the License. You may
5    * obtain a copy of the License at
6    *
7    * http://www.osedu.org/licenses/ECL-2.0
8    *
9    * Unless required by applicable law or agreed to in writing,
10   * software distributed under the License is distributed on an "AS IS"
11   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing
13   * permissions and limitations under the License.
14   */
15  
16  package org.kuali.student.common.messagebuilder.impl;
17  
18  import java.util.HashMap;
19  import java.util.List;
20  import java.util.Map;
21  
22  import org.apache.velocity.exception.VelocityException;
23  import org.kuali.student.common.messagebuilder.MessageTreeBuilder;
24  import org.kuali.student.common.messagebuilder.booleanmessage.BooleanMessage;
25  import org.kuali.student.common.messagebuilder.booleanmessage.ast.BinaryMessageTree;
26  import org.kuali.student.common.messagebuilder.booleanmessage.ast.BooleanFunction;
27  import org.kuali.student.common.messagebuilder.booleanmessage.ast.BooleanFunctionResult;
28  import org.kuali.student.common.messagebuilder.booleanmessage.ast.BooleanMessageImpl;
29  import org.kuali.student.common.messagebuilder.booleanmessage.ast.BooleanNode;
30  import org.kuali.student.common.messagebuilder.booleanmessage.ast.exceptions.BooleanFunctionException;
31  import org.kuali.student.common.messagebuilder.impl.exceptions.MessageBuilderException;
32  import org.kuali.student.common.util.VelocityTemplateEngine;
33  
34  /**
35   * This <code>AbstractMessageBuilder</code> class builds a summary message 
36   * from plain strings or Velocity template messages. Summary message is built 
37   * from analysing the outcome of a boolean expression. 
38   * If no language is specified then the default language locale is used. 
39   */
40  public abstract class AbstractMessageBuilder {
41      private final VelocityTemplateEngine templateEngine = new VelocityTemplateEngine();
42  
43      private String booleanExpression;
44      private Map<String, ? extends BooleanMessage> messageMap;
45      private Map<String, Object> messageContextMap;
46      private String language;
47      private MessageTreeBuilder treeNodeMessageBuilder;
48  
49      /**
50       * Constructor.
51       * 
52       * @param language Language
53       * @param treeNodeMessageBuilder AST tree node Message builder
54       */
55      public AbstractMessageBuilder(final String language, final MessageTreeBuilder treeNodeMessageBuilder) {
56          this.language = language; 
57          this.treeNodeMessageBuilder = treeNodeMessageBuilder;
58      }
59      
60  	/**
61       * <p>Builds and evaluates a boolean expression and returns the message and result 
62       * of the expression. Messages in the <code>messageMap</code> can 
63       * also contain VTL (Velocity Template Language) but without any VTL keys</p>
64       * <p><b>Note:</b> Order of boolean operation: ANDs before ORs and operations 
65       * inside parentheses before anything else.</p> 
66  	 * Example 1: 'A AND B OR C AND D' internally evaluates to '(A AND B) OR (C AND D)'
67  	 * <pre><code>booleanExpression</code> = "A*B+C*D"</pre>
68       * Example 2: '(M1 AND M2) OR M3' 
69       * <pre><code>booleanExpression</code> = "(M1*M2)+M3"</pre>
70       * 
71       * @param booleanExpression Boolean expression
72       * @param messageMap Contains a map of messages (or VTL)
73       * @return Boolean function result
74       */
75      public BooleanFunctionResult build(
76      		final String booleanExpression, 
77      		final Map<String, ? extends BooleanMessage> messageMap) {
78      	this.booleanExpression = booleanExpression;
79      	this.messageMap = messageMap;
80  
81      	return build();
82      }
83  
84      /**
85       * <p>Builds and evaluates a boolean expression and returns the message and result 
86       * of the expression. Messages in the <code>messageMap</code> can 
87       * also contain VTL (Velocity Template Language). 
88       * <code>messageContextMap</code> contains Velocity key/value entries 
89       * referenced in the <code>messageMap</code>.</p>
90       * <p><b>Note:</b> Order of boolean operation: ANDs before ORs and operations 
91       * inside parentheses before anything else.</p> 
92  	 * Example 1: 'A AND B OR C AND D' internally evaluates to '(A AND B) OR (C AND D)'
93  	 * <pre><code>booleanExpression</code> = "A*B+C*D"</pre>
94       * Example 2: '(M1 AND M2) OR M3' 
95       * <pre><code>booleanExpression</code> = "(M1*M2)+M3"</pre>
96       * 
97       * 
98       * @param booleanExpression Boolean expression
99       * @param messageMap Contains a map of messages (or VTL)
100      * @param messageContextMap Message contact map for Velocity Template Engine
101      * @return Boolean function result
102      * @throws MessageBuilderException Errors building message
103      */
104     public BooleanFunctionResult build(
105     		final String booleanExpression, 
106     		final Map<String, ? extends BooleanMessage> messageMap, 
107     		final Map<String, Object> messageContextMap) {
108     	this.booleanExpression = booleanExpression;
109     	this.messageMap = messageMap;
110     	this.messageContextMap = messageContextMap;
111 
112     	return build();
113     }
114     
115     public BooleanFunctionResult build() {
116     	BinaryMessageTree astTree = null;
117 
118         try {
119             // set the functionString and Maps from the proposition container
120         	Map<String, BooleanMessage> nodeMessageMap = buildMessageMap();
121 
122             // go parse function in buildTree
123             astTree = new BinaryMessageTree(this.language, nodeMessageMap);
124             BooleanNode root = astTree.buildTree(this.booleanExpression);
125             astTree.traverseTreePostOrder(root, null);
126 
127             List<BooleanNode> treeNodes = astTree.getAllNodes();
128             // tree node order in the list is important for building 
129             // the success and failure message
130             this.treeNodeMessageBuilder.buildMessage(treeNodes);
131         } catch(VelocityException e) {
132             throw new MessageBuilderException("Building Velocity message failed: " + e.getMessage(), e);
133     	} catch (BooleanFunctionException e) {
134             throw new MessageBuilderException("Building message failed: " + e.getMessage(), e);
135         }
136 
137         // This is the final rule report message summary
138         String message = astTree.getRoot().getNodeMessage();
139         Boolean result = astTree.getRoot().getValue();
140         
141 		// Removed starting and ending brackets if they are the only brackets in the message
142         if (message.startsWith("(") && message.endsWith(")") && 
143 				message.replaceAll("[^(]","").length() == 1 &&
144 				message.replaceAll("[^)]","").length() == 1) {
145 			message = message.substring(1, message.length()-1);
146 		}
147         
148         return new BooleanFunctionResult(this.booleanExpression, result, message);
149     }
150 
151     /**
152      * Builds message map. Also builds message map using velocity templates.
153      * 
154      * @return Boolean message map
155      */
156     private Map<String, BooleanMessage> buildMessageMap() {
157     	Map<String, BooleanMessage> nodeMessageMap = new HashMap<String, BooleanMessage>();
158 
159         if (this.booleanExpression == null || this.booleanExpression.isEmpty()) {
160         	throw new MessageBuilderException("Boolean expression is null");
161         }
162         
163         BooleanFunction func = new BooleanFunction(this.booleanExpression);
164         List<String> funcVars = func.getVariables();
165 
166     	if(funcVars == null || funcVars.isEmpty()) {
167     		throw new MessageBuilderException("Boolean function variables are null or empty. Boolean expression: " + this.booleanExpression);
168     	} 
169         
170         for (String id : funcVars) {
171         	if(id == null) {
172         		throw new MessageBuilderException("Boolean variable id is null or empty. Boolean variable ids: " + funcVars);
173         	} 
174 
175         	BooleanMessage message = this.messageMap.get(id);
176         	
177         	if (message == null) {
178         		throw new MessageBuilderException("Boolean message is null for id='" + id + "'");
179         	}
180         	
181         	BooleanMessage booleanMessage = buildMessage(message);
182             nodeMessageMap.put(id, booleanMessage);
183         }
184         
185         return nodeMessageMap;
186     }
187 
188     /**
189      * Builds a failure/success message using the Velocity template engine.
190      * 
191      * @param message Boolean failure/success message
192      * @return
193      */
194     private BooleanMessage buildMessage(BooleanMessage message) {
195 		String msg = message.getMessage();
196 
197 		if(msg != null) {
198     		msg = this.templateEngine.evaluate(this.messageContextMap, msg);
199 		}
200 
201     	return new BooleanMessageImpl(message.getMessageId(), message.isSuccesful(), msg);
202     }
203 }