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.datadictionary;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.exception.RiceRuntimeException;
20  import org.kuali.rice.core.api.util.KeyValue;
21  import org.kuali.rice.krad.messages.Message;
22  import org.kuali.rice.krad.messages.MessageService;
23  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
24  import org.kuali.rice.krad.uif.UifConstants;
25  import org.kuali.rice.krad.uif.component.DataBinding;
26  import org.kuali.rice.krad.uif.element.Action;
27  import org.kuali.rice.krad.uif.field.ActionField;
28  import org.kuali.rice.krad.util.KRADConstants;
29  import org.kuali.rice.krad.util.KRADPropertyConstants;
30  import org.springframework.beans.MutablePropertyValues;
31  import org.springframework.beans.PropertyValue;
32  import org.springframework.beans.factory.config.BeanDefinition;
33  import org.springframework.beans.factory.config.BeanDefinitionHolder;
34  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
35  import org.springframework.beans.factory.config.ListFactoryBean;
36  
37  import java.text.MessageFormat;
38  import java.util.Collection;
39  import java.util.List;
40  import java.util.Map;
41  import java.util.Set;
42  import java.util.Stack;
43  
44  /**
45   * Dictionary bean processor that retrieves external messages for bean definitions and alters the bean
46   * definitions to use the external text
47   *
48   * @author Kuali Rice Team (rice.collab@kuali.org)
49   */
50  public class MessageBeanProcessor extends DictionaryBeanProcessorBase {
51  
52      private ConfigurableListableBeanFactory beanFactory;
53      private DataDictionary dataDictionary;
54  
55      public MessageBeanProcessor(DataDictionary dataDictionary, ConfigurableListableBeanFactory beanFactory) {
56          this.dataDictionary = dataDictionary;
57          this.beanFactory = beanFactory;
58      }
59  
60      /**
61       * @see DictionaryBeanProcessor#processRootBeanDefinition(java.lang.String,
62       *      org.springframework.beans.factory.config.BeanDefinition)
63       */
64      public void processRootBeanDefinition(String beanName, BeanDefinition beanDefinition) {
65          processBeanMessages(beanName, beanDefinition, null);
66      }
67  
68      /**
69       * @see DictionaryBeanProcessor#processNestedBeanDefinition(java.lang.String,
70       *      org.springframework.beans.factory.config.BeanDefinition, java.lang.String,
71       *      java.util.Stack<org.springframework.beans.factory.config.BeanDefinitionHolder>)
72       */
73      public void processNestedBeanDefinition(String beanName, BeanDefinition beanDefinition, String propertyName,
74              Stack<BeanDefinitionHolder> nestedBeanStack) {
75          processBeanMessages(beanName, beanDefinition, nestedBeanStack);
76      }
77  
78      /**
79       * @see DictionaryBeanProcessor#processStringPropertyValue(java.lang.String, java.lang.String,
80       *      java.util.Stack<org.springframework.beans.factory.config.BeanDefinitionHolder>)
81       */
82      public String processStringPropertyValue(String propertyName, String propertyValue,
83              Stack<BeanDefinitionHolder> nestedBeanStack) {
84          return processMessagePlaceholders(propertyValue, nestedBeanStack);
85      }
86  
87      /**
88       * @see DictionaryBeanProcessor#processCollectionBeanDefinition(java.lang.String,
89       *      org.springframework.beans.factory.config.BeanDefinition, java.lang.String,
90       *      java.util.Stack<org.springframework.beans.factory.config.BeanDefinitionHolder>)
91       */
92      public void processCollectionBeanDefinition(String beanName, BeanDefinition beanDefinition, String propertyName,
93              Stack<BeanDefinitionHolder> nestedBeanStack) {
94          processBeanMessages(beanName, beanDefinition, nestedBeanStack);
95      }
96  
97      /**
98       * @see DictionaryBeanProcessor#processArrayStringPropertyValue(java.lang.String, java.lang.Object[],
99       *      java.lang.String, int, java.util.Stack<org.springframework.beans.factory.config.BeanDefinitionHolder>)
100      */
101     public String processArrayStringPropertyValue(String propertyName, Object[] propertyValue, String elementValue,
102             int elementIndex, Stack<BeanDefinitionHolder> nestedBeanStack) {
103         return processMessagePlaceholders(elementValue, nestedBeanStack);
104     }
105 
106     /**
107      * @see DictionaryBeanProcessor#processListStringPropertyValue(java.lang.String, java.util.List<?>,
108      *      java.lang.String, int, java.util.Stack<org.springframework.beans.factory.config.BeanDefinitionHolder>)
109      */
110     public String processListStringPropertyValue(String propertyName, List<?> propertyValue, String elementValue,
111             int elementIndex, Stack<BeanDefinitionHolder> nestedBeanStack) {
112         return processMessagePlaceholders(elementValue, nestedBeanStack);
113     }
114 
115     /**
116      * @see DictionaryBeanProcessor#processSetStringPropertyValue(java.lang.String, java.util.Set<?>, java.lang.String,
117      *      java.util.Stack<org.springframework.beans.factory.config.BeanDefinitionHolder>)
118      */
119     public String processSetStringPropertyValue(String propertyName, Set<?> propertyValue, String elementValue,
120             Stack<BeanDefinitionHolder> nestedBeanStack) {
121         return processMessagePlaceholders(elementValue, nestedBeanStack);
122     }
123 
124     /**
125      * @see DictionaryBeanProcessor#processMapStringPropertyValue(java.lang.String, java.util.Map<?,?>,
126      *      java.lang.String, java.lang.Object, java.util.Stack<org.springframework.beans.factory.config.BeanDefinitionHolder>)
127      */
128     public String processMapStringPropertyValue(String propertyName, Map<?, ?> propertyValue, String elementValue,
129             Object elementKey, Stack<BeanDefinitionHolder> nestedBeanStack) {
130         return processMessagePlaceholders(elementValue, nestedBeanStack);
131     }
132 
133     /**
134      * Retrieves external messages whose namespace and component matches the bean definition and applies
135      * the message text to the bean property values
136      *
137      * @param beanName name of the bean to process
138      * @param beanDefinition bean definition to process
139      * @param nestedBeanStack stack of beans that contain the given bean, used for finding a namespace
140      */
141     protected void processBeanMessages(String beanName, BeanDefinition beanDefinition,
142             Stack<BeanDefinitionHolder> nestedBeanStack) {
143         Class<?> beanClass = getBeanClass(beanDefinition, beanFactory);
144         if ((beanClass == null) || !(DictionaryBean.class.isAssignableFrom(beanClass) || ListFactoryBean.class
145                 .isAssignableFrom(beanClass))) {
146             return;
147         }
148 
149         String namespace = getNamespaceForBean(beanName, beanDefinition);
150         if (StringUtils.isBlank(namespace)) {
151             namespace = getNamespaceForBeanInStack(nestedBeanStack);
152         }
153 
154         String componentCode = getComponentForBean(beanName, beanDefinition);
155         if (StringUtils.equals(componentCode, beanName)) {
156             // check if there is a parent bean in the factory using the standard suffix, if so we will skip this
157             // bean as messages will be picked up by that parent bean definition. Note this is not for all parents,
158             // just where the convention has been setup for extension (ex. 'bean' and 'bean-parentName')
159             String extensionParentBeanName = beanName + KRADConstants.DICTIONARY_BEAN_PARENT_SUFFIX;
160             if (beanFactory.containsBean(extensionParentBeanName)) {
161                 return;
162             }
163         }
164 
165         // if a namespace and component was found retrieve all messages associated with them
166         if (StringUtils.isNotBlank(namespace) && StringUtils.isNotBlank(componentCode)) {
167             Collection<Message> beanMessages = getMessageService().getAllMessagesForComponent(namespace, componentCode);
168             for (Message beanMessage : beanMessages) {
169                 applyMessageToBean(beanMessage, beanDefinition, beanClass);
170             }
171         }
172     }
173 
174     /**
175      * Applies the text for a given message to the associated bean definition property
176      *
177      * @param message message instance to apply
178      * @param beanDefinition bean definition the message should be applied to
179      * @param beanClass class for the bean definition
180      */
181     protected void applyMessageToBean(Message message, BeanDefinition beanDefinition, Class<?> beanClass) {
182         String key = message.getKey().trim();
183 
184         // if message doesn't start with path indicator, it will be an explicit key that is matched when
185         // iterating over the property values, so we will just return in that case
186         if (!key.startsWith(KRADConstants.MESSAGE_KEY_PATH_INDICATOR)) {
187             return;
188         }
189 
190         // if here dealing with a path key, strip off indicator and then process as a property path
191         key = StringUtils.stripStart(key, KRADConstants.MESSAGE_KEY_PATH_INDICATOR);
192 
193         // list factory beans just have the one list property (with no name)
194         if (ListFactoryBean.class.isAssignableFrom(beanClass)) {
195             MutablePropertyValues pvs = beanDefinition.getPropertyValues();
196 
197             PropertyValue propertyValue = pvs.getPropertyValueList().get(0);
198             List<?> listValue = (List<?>) propertyValue.getValue();
199 
200             applyMessageToNestedListBean(message, listValue, key);
201         } else if (StringUtils.contains(key, ".")) {
202             applyMessageToNestedBean(message, beanDefinition, key);
203         } else {
204             applyMessageTextToPropertyValue(key, message.getText(), beanDefinition);
205         }
206     }
207 
208     /**
209      * Applies the given message text to the property within the given bean definition
210      *
211      * <p>
212      * The message text is applied to the bean definiton based on the property path. The path could be nested
213      * in which case the property values of the bean definition are traversed to find the nested bean definition
214      * that should hold the property value. The path could also represent a nested list bean. In this case a
215      * helper method is called to find the correct list bean to apply the message to
216      * </p>
217      *
218      * @param message message containing the text to apply
219      * @param beanDefinition bean definition containing the property
220      * @param propertyPath path to property within the bean definition the message should be applied to
221      */
222     protected void applyMessageToNestedBean(Message message, BeanDefinition beanDefinition, String propertyPath) {
223         MutablePropertyValues pvs = beanDefinition.getPropertyValues();
224 
225         String beanPath = StringUtils.substringBefore(propertyPath, ".");
226         String nestedPropertyPath = StringUtils.substringAfter(propertyPath, ".");
227 
228         boolean foundNestedBean = false;
229         while (StringUtils.isNotBlank(nestedPropertyPath)) {
230             if (pvs.contains(beanPath)) {
231                 PropertyValue propertyValue = pvs.getPropertyValue(beanPath);
232 
233                 BeanDefinition propertyBeanDefinition = getPropertyValueBeanDefinition(propertyValue);
234                 if (propertyBeanDefinition != null) {
235                     applyMessageToNestedBean(message, propertyBeanDefinition, nestedPropertyPath);
236 
237                     foundNestedBean = true;
238                     break;
239                 } else if (propertyValue.getValue() instanceof List) {
240                     applyMessageToNestedListBean(message, (List<?>) propertyValue.getValue(), nestedPropertyPath);
241 
242                     foundNestedBean = true;
243                     break;
244                 }
245             }
246 
247             beanPath += "." + StringUtils.substringBefore(nestedPropertyPath, ".");
248             nestedPropertyPath = StringUtils.substringAfter(nestedPropertyPath, ".");
249         }
250 
251         if (!foundNestedBean) {
252             applyMessageTextToPropertyValue(propertyPath, message.getText(), beanDefinition);
253         }
254     }
255 
256     /**
257      * Applies a message to a nested list bean definition
258      *
259      * <p>
260      * Here the property path first gives an identifier value (such as property name) to use for finding
261      * the bean definition with the list the message should apply to. Any part of the path after the identifier
262      * value is treated like a nested property path
263      * </p>
264      *
265      * @param message message instance that contains the text to apply
266      * @param listPropertyValue property value list that should contain the bean definition the message
267      * will be applied to
268      * @param propertyPath path to the bean definition the message should apply to
269      */
270     protected void applyMessageToNestedListBean(Message message, List<?> listPropertyValue, String propertyPath) {
271         // property path must be nested, with first part giving the list bean identifier value
272         if (!StringUtils.contains(propertyPath, ".")) {
273             throw new RiceRuntimeException(
274                     "Key for nested list bean must contain the identifer value followed by the path.");
275         }
276 
277         String listIdentifierPropertyValue = StringUtils.substringBefore(propertyPath, ".");
278         String listBeanPropertyPath = StringUtils.substringAfter(propertyPath, ".");
279 
280         // iterate through list and find beans that match the given identifier
281         for (int i = 0; i < listPropertyValue.size(); i++) {
282             Object elem = listPropertyValue.get(i);
283 
284             if ((elem instanceof BeanDefinition) || (elem instanceof BeanDefinitionHolder)) {
285                 BeanDefinition beanDefinition;
286                 if (elem instanceof BeanDefinition) {
287                     beanDefinition = (BeanDefinition) elem;
288                 } else {
289                     beanDefinition = ((BeanDefinitionHolder) elem).getBeanDefinition();
290                 }
291 
292                 boolean isMatch = isBeanMessageMatch(listIdentifierPropertyValue, beanDefinition);
293                 if (isMatch) {
294                     if (StringUtils.contains(listBeanPropertyPath, ".")) {
295                         applyMessageToNestedBean(message, beanDefinition, listBeanPropertyPath);
296                     } else {
297                         applyMessageTextToPropertyValue(listBeanPropertyPath, message.getText(), beanDefinition);
298                     }
299                 }
300             }
301         }
302     }
303 
304     /**
305      * Determines whether the given bean definition is a matched based on the given identifier value
306      *
307      * <p>
308      * Based on the class for the bean definition an identifier property name is used, the corresponding value
309      * from the bean definition is then retrieved and compared to the given value to determine whether the
310      * bean definition is a match
311      * </p>
312      *
313      * @param matchListIdentifierPropertyValue property value to match bean definitions on
314      * @param beanDefinition bean definition to determine the match for
315      * @return boolean true if the bean definition is a match, false if not
316      */
317     protected boolean isBeanMessageMatch(String matchListIdentifierPropertyValue, BeanDefinition beanDefinition) {
318         boolean isMatch = false;
319 
320         String listIdentifierPropertyName = null;
321 
322         Class<?> beanClass = getBeanClass(beanDefinition, beanFactory);
323         if (DataBinding.class.isAssignableFrom(beanClass)) {
324             listIdentifierPropertyName = KRADPropertyConstants.PROPERTY_NAME;
325         } else if (Action.class.isAssignableFrom(beanClass) || ActionField.class.isAssignableFrom(beanClass)) {
326             listIdentifierPropertyName = KRADPropertyConstants.METHOD_TO_CALL;
327         } else if (KeyValue.class.isAssignableFrom(beanClass)) {
328             listIdentifierPropertyName = KRADPropertyConstants.KEY;
329         }
330 
331         if (StringUtils.isNotBlank(listIdentifierPropertyName)) {
332             String listIdentifierPropertyValue = findPropertyValueInBeanDefinition(beanDefinition,
333                     listIdentifierPropertyName);
334             if (listIdentifierPropertyValue != null) {
335                 isMatch = StringUtils.equals(listIdentifierPropertyValue, matchListIdentifierPropertyValue);
336             }
337         }
338 
339         return isMatch;
340     }
341 
342     /**
343      * Attempts to find a property value for the given property name within the bean definition, if the property
344      * does not exist in the bean and there is a parent, the parent is checked for containing the property. This
345      * continues until a property value is found or all the parents have been traversed
346      *
347      * @param beanDefinition bean definition to find property value in
348      * @param propertyName name of the property to find the value for
349      * @return String value for property in the bean definition or null if the property was not found
350      */
351     protected String findPropertyValueInBeanDefinition(BeanDefinition beanDefinition, String propertyName) {
352         String beanPropertyValue = null;
353 
354         MutablePropertyValues pvs = beanDefinition.getPropertyValues();
355         if (pvs.contains(propertyName)) {
356             PropertyValue propertyValue = pvs.getPropertyValue(propertyName);
357             if (propertyValue.getValue() != null) {
358                 beanPropertyValue = propertyValue.getValue().toString();
359             }
360         } else {
361             if (StringUtils.isNotBlank(beanDefinition.getParentName())) {
362                 BeanDefinition parentBeanDefinition = beanFactory.getBeanDefinition(beanDefinition.getParentName());
363 
364                 beanPropertyValue = findPropertyValueInBeanDefinition(parentBeanDefinition, propertyName);
365             }
366         }
367 
368         return beanPropertyValue;
369     }
370 
371     /**
372      * Checks a string property value for a message placeholder and if found the message is retrieved and updated
373      * in the property value
374      *
375      * @param propertyValue string value to process for message placeholders
376      * @param nestedBeanStack stack of bean definitions that contain the property, used to determine the namespace
377      * and component for the message retrieval
378      * @return String new value for the property (possibly modified from an external message)
379      */
380     protected String processMessagePlaceholders(String propertyValue, Stack<BeanDefinitionHolder> nestedBeanStack) {
381         String trimmedPropertyValue = StringUtils.stripStart(propertyValue, " ");
382         if (StringUtils.isBlank(trimmedPropertyValue)) {
383             return propertyValue;
384         }
385 
386         String newPropertyValue = propertyValue;
387 
388         // first check for a replacement message key
389         if (trimmedPropertyValue.startsWith(KRADConstants.MESSAGE_KEY_PLACEHOLDER_PREFIX) && StringUtils.contains(
390                 trimmedPropertyValue, KRADConstants.MESSAGE_KEY_PLACEHOLDER_SUFFIX)) {
391             String messageKeyStr = StringUtils.substringBetween(trimmedPropertyValue,
392                     KRADConstants.MESSAGE_KEY_PLACEHOLDER_PREFIX, KRADConstants.MESSAGE_KEY_PLACEHOLDER_SUFFIX);
393 
394             // get any default specified value (given after the message key)
395             String messageKeyWithPlaceholder = KRADConstants.MESSAGE_KEY_PLACEHOLDER_PREFIX + messageKeyStr +
396                     KRADConstants.MESSAGE_KEY_PLACEHOLDER_SUFFIX;
397 
398             String defaultPropertyValue = StringUtils.substringAfter(trimmedPropertyValue, messageKeyWithPlaceholder);
399 
400             // set the new property value to the message text (if found), or the default value if a message was not found
401             // note the message text could be an empty string, in which case it will override the default
402             String messageText = getMessageTextForKey(messageKeyStr, nestedBeanStack);
403             if (messageText != null) {
404                 // if default value set then we need to merge any expressions
405                 if (StringUtils.isNotBlank(defaultPropertyValue)) {
406                     newPropertyValue = getMergedMessageText(messageText, defaultPropertyValue);
407                 } else {
408                     newPropertyValue = messageText;
409                 }
410             } else {
411                 newPropertyValue = defaultPropertyValue;
412             }
413         }
414         // now check for message keys within an expression
415         else if (StringUtils.contains(trimmedPropertyValue, KRADConstants.EXPRESSION_MESSAGE_PLACEHOLDER_PREFIX)) {
416             String[] expressionMessageKeys = StringUtils.substringsBetween(newPropertyValue,
417                     KRADConstants.EXPRESSION_MESSAGE_PLACEHOLDER_PREFIX,
418                     KRADConstants.EXPRESSION_MESSAGE_PLACEHOLDER_SUFFIX);
419 
420             for (String expressionMessageKey : expressionMessageKeys) {
421                 String expressionMessageText = getMessageTextForKey(expressionMessageKey, nestedBeanStack);
422                 newPropertyValue = StringUtils.replace(newPropertyValue,
423                         KRADConstants.EXPRESSION_MESSAGE_PLACEHOLDER_PREFIX + expressionMessageKey +
424                                 KRADConstants.EXPRESSION_MESSAGE_PLACEHOLDER_SUFFIX, expressionMessageText);
425             }
426         }
427 
428         return newPropertyValue;
429     }
430 
431     /**
432      * Retrieves the test associated with the message give by the message key string
433      *
434      * @param messageKeyStr key string for the message, can contain just the key, or also the component and/or
435      * namespace. If component or namespace not given it is determined from the bean stack
436      * @param nestedBeanStack bean stack that contains the property for which the message applies
437      * @return String test associated with the message
438      */
439     protected String getMessageTextForKey(String messageKeyStr, Stack<BeanDefinitionHolder> nestedBeanStack) {
440         String namespace = null;
441         String componentCode = null;
442         String key = null;
443 
444         // check for specification of namespace and component
445         if (StringUtils.contains(messageKeyStr, ":")) {
446             String[] messageParams = StringUtils.split(messageKeyStr, ":");
447 
448             if (messageParams.length == 3) {
449                 namespace = messageParams[0];
450                 componentCode = messageParams[1];
451                 key = messageParams[2];
452             } else if (messageParams.length == 2) {
453                 componentCode = messageParams[0];
454                 key = messageParams[1];
455             } else {
456                 throw new RiceRuntimeException("Message key '" + messageKeyStr + "' has an invalid format");
457             }
458         } else {
459             key = messageKeyStr;
460         }
461 
462         if (StringUtils.isBlank(namespace)) {
463             namespace = getNamespaceForBeanInStack(nestedBeanStack);
464         }
465 
466         if (StringUtils.isBlank(componentCode)) {
467             for (int i = nestedBeanStack.size() - 1; i >= 0; i--) {
468                 BeanDefinitionHolder definitionHolder = nestedBeanStack.get(i);
469                 componentCode = getComponentForBean(definitionHolder.getBeanName(),
470                         definitionHolder.getBeanDefinition());
471                 if (StringUtils.isNotBlank(componentCode)) {
472                     break;
473                 }
474             }
475         }
476 
477         String messageText = null;
478         if (StringUtils.isNotBlank(namespace) && StringUtils.isNotBlank(componentCode) && StringUtils.isNotBlank(key)) {
479             messageText = getMessageService().getMessageText(namespace, componentCode, key);
480         }
481 
482         return messageText;
483     }
484 
485     /**
486      * Applies the given message text to the bean definition with the given property name, if a current
487      * value exists for the property name the value is checked for expressions which are then merged with
488      * the message
489      *
490      * @param propertyName - name of the property to set on the bean definition
491      * @param messageText - message text that will be the property value
492      * @param beanDefinition - bean definition to set property on
493      */
494     protected void applyMessageTextToPropertyValue(String propertyName, String messageText,
495             BeanDefinition beanDefinition) {
496         String newPropertyValue = messageText;
497 
498         MutablePropertyValues pvs = beanDefinition.getPropertyValues();
499         if (pvs.contains(propertyName)) {
500             PropertyValue propertyValue = pvs.getPropertyValue(propertyName);
501 
502             String stringPropertyValue = getStringValue(propertyValue.getValue());
503             if (StringUtils.isNotBlank(stringPropertyValue)) {
504                 newPropertyValue = getMergedMessageText(messageText, stringPropertyValue);
505             }
506         }
507 
508         applyPropertyValueToBean(propertyName, newPropertyValue, pvs);
509     }
510 
511     /**
512      * Prepares the message text that will replace the property value checking for any expression placeholders
513      *
514      * <p>
515      * The message text may contain placeholders (using brace delimiters) for expression placement. It is
516      * expected when these placeholders are given the property value contains the expressions (using the
517      * expression placeholders) that will be inserted into the message text
518      * </p>
519      *
520      * @param messageText - raw text of the message
521      * @param propertyValue - current value for the property
522      * @return String the message text with expressions inserted (if any expressions were found)
523      */
524     protected String getMergedMessageText(String messageText, String propertyValue) {
525         String mergedText = messageText;
526 
527         String[] expressions = StringUtils.substringsBetween(propertyValue, UifConstants.EL_PLACEHOLDER_PREFIX,
528                 UifConstants.EL_PLACEHOLDER_SUFFIX);
529         if ((expressions != null) && expressions.length > 0) {
530             // add expression placeholders back on
531             String[] messageParameters = new String[expressions.length];
532             for (int i = 0; i < expressions.length; i++) {
533                 String expression = expressions[i];
534 
535                 expression = UifConstants.EL_PLACEHOLDER_PREFIX + expression + UifConstants.EL_PLACEHOLDER_SUFFIX;
536                 messageParameters[i] = expression;
537             }
538 
539             // escape single quotes for message format process
540             messageText = messageText.replace("'", "''");
541             try {
542                 mergedText = MessageFormat.format(messageText, messageParameters);
543             } catch (IllegalArgumentException e) {
544                 throw new RiceRuntimeException(
545                         "Unable to merge expressions with message text. Expression count is: " + expressions.length, e);
546             }
547         }
548 
549         return mergedText;
550     }
551 
552     /**
553      * Walks up the stack of bean definitions until a namespace is found and returns that namespace
554      *
555      * @param nestedBeanStack stack of bean definitions to find namespace for
556      * @return String namespace found in stack or null if one was not found
557      */
558     protected String getNamespaceForBeanInStack(Stack<BeanDefinitionHolder> nestedBeanStack) {
559         String namespace = null;
560 
561         if (nestedBeanStack != null) {
562             for (int i = nestedBeanStack.size() - 1; i >= 0; i--) {
563                 BeanDefinitionHolder definitionHolder = nestedBeanStack.get(i);
564                 namespace = getNamespaceForBean(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
565                 if (StringUtils.isNotBlank(namespace)) {
566                     break;
567                 }
568             }
569         }
570 
571         return namespace;
572     }
573 
574     /**
575      * Retrieves the namespace associated with the bean definition
576      *
577      * @param beanName name of the bean to find namespace for
578      * @param beanDefinition bean definition to find namespace for
579      * @return String namespace for bean or null if a namespace was not found
580      */
581     protected String getNamespaceForBean(String beanName, BeanDefinition beanDefinition) {
582         String namespace = null;
583 
584         MutablePropertyValues pvs = beanDefinition.getPropertyValues();
585         if (pvs.contains(KRADPropertyConstants.NAMESPACE_CODE)) {
586             PropertyValue propertyValue = pvs.getPropertyValue(KRADPropertyConstants.NAMESPACE_CODE);
587             namespace = getStringValue(propertyValue.getValue());
588         } else if (StringUtils.isNotBlank(beanName) && !isGeneratedBeanName(beanName)) {
589             // if not on bean definition, get from associated module in the dictionary
590             namespace = dataDictionary.getNamespaceForBeanDefinition(beanName);
591         }
592 
593         return namespace;
594     }
595 
596     /**
597      * Retrieves the component code associated with the bean definition
598      *
599      * @param beanName name of the bean to find component code for
600      * @param beanDefinition bean definition to find component code for
601      * @return String component code for bean or null if a component code was not found
602      */
603     protected String getComponentForBean(String beanName, BeanDefinition beanDefinition) {
604         String componentCode = null;
605 
606         MutablePropertyValues pvs = beanDefinition.getPropertyValues();
607         if (pvs.contains(KRADPropertyConstants.COMPONENT_CODE)) {
608             PropertyValue propertyValue = pvs.getPropertyValue(KRADPropertyConstants.COMPONENT_CODE);
609 
610             componentCode = getStringValue(propertyValue.getValue());
611         }
612 
613         if ((componentCode == null) && StringUtils.isNotBlank(beanName) && !isGeneratedBeanName(beanName)) {
614             componentCode = beanName;
615         }
616 
617         if (StringUtils.isNotBlank(componentCode)) {
618             componentCode = StringUtils.removeEnd(componentCode, KRADConstants.DICTIONARY_BEAN_PARENT_SUFFIX);
619         }
620 
621         return componentCode;
622     }
623 
624     /**
625      * Returns instance of the Message Service
626      *
627      * @return MessageService isntanc
628      */
629     protected MessageService getMessageService() {
630         return KRADServiceLocatorWeb.getMessageService();
631     }
632 }