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