org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase.java

Source

/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 * 
 * Copyright 2005-2014 The Kuali Foundation
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kuali.kfs.sys.document.authorization;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.businessobject.AccountingLine;
import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AccountingDocument;
import org.kuali.kfs.sys.document.Correctable;
import org.kuali.kfs.sys.document.web.AccountingLineRenderingContext;
import org.kuali.kfs.sys.document.web.AccountingLineViewAction;
import org.kuali.kfs.sys.document.web.AccountingLineViewField;
import org.kuali.kfs.sys.identity.KfsKimAttributes;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.kew.api.WorkflowDocument;
import org.kuali.rice.kim.api.KimConstants;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
import org.kuali.rice.kns.document.authorization.DocumentAuthorizerBase;
import org.kuali.rice.kns.service.DocumentHelperService;
import org.kuali.rice.krad.document.Document;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.KRADConstants;
import org.kuali.rice.krad.util.ObjectUtils;

/**
 * The default implementation of AccountingLineAuthorizer
 */
public class AccountingLineAuthorizerBase implements AccountingLineAuthorizer {
    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
            .getLogger(AccountingLineAuthorizerBase.class);

    private static ConfigurationService kualiConfigurationService;
    protected static String riceImagePath;
    protected static String kfsImagePath;

    /**
     * Returns the basic actions - add for new lines, delete and balance inquiry for existing lines
     *
     * @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizer#getActions(org.kuali.kfs.sys.document.AccountingDocument,
     *      org.kuali.kfs.sys.businessobject.AccountingLine, java.lang.String, java.lang.Integer, org.kuali.rice.kim.api.identity.Person,
     *      java.lang.String)
     */
    @Override
    public List<AccountingLineViewAction> getActions(AccountingDocument accountingDocument,
            AccountingLineRenderingContext accountingLineRenderingContext, String accountingLinePropertyName,
            Integer accountingLineIndex, Person currentUser, String groupTitle) {
        List<AccountingLineViewAction> actions = new ArrayList<AccountingLineViewAction>();

        if (accountingLineRenderingContext.isEditableLine()
                || isMessageMapContainingErrorsOnLine(accountingLinePropertyName)) {
            Map<String, AccountingLineViewAction> actionMap = this.getActionMap(accountingLineRenderingContext,
                    accountingLinePropertyName, accountingLineIndex, groupTitle);
            actions.addAll(actionMap.values());
        }

        return actions;
    }

    /**
     * Determines if the error map contains any errors which exist on the currently rendered accounting line
     * @param accountingLinePropertyName the property name of the accounting line
     * @return true if there are errors on the line, false otherwise
     */
    protected boolean isMessageMapContainingErrorsOnLine(String accountingLinePropertyName) {
        for (Object errorKeyAsObject : GlobalVariables.getMessageMap().getPropertiesWithErrors()) {
            if (((String) errorKeyAsObject).startsWith(accountingLinePropertyName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns a new empty HashSet
     *
     * @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizer#getUnviewableBlocks(org.kuali.kfs.sys.document.AccountingDocument,
     *      org.kuali.kfs.sys.businessobject.AccountingLine, java.lang.String, boolean)
     */
    @Override
    public Set<String> getUnviewableBlocks(AccountingDocument accountingDocument, AccountingLine accountingLine,
            boolean newLine, Person currentUser) {
        return new HashSet<String>();
    }

    /**
     * @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizer#renderNewLine(org.kuali.kfs.sys.document.AccountingDocument,
     *      java.lang.String)
     */
    @Override
    public boolean renderNewLine(AccountingDocument accountingDocument, String accountingGroupProperty) {
        return (accountingDocument.getDocumentHeader().getWorkflowDocument().isInitiated()
                || accountingDocument.getDocumentHeader().getWorkflowDocument().isSaved()
                || accountingDocument.getDocumentHeader().getWorkflowDocument().isCompletionRequested());
    }

    /**
     * @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizer#isGroupEditable(org.kuali.kfs.sys.document.AccountingDocument,
     *      java.lang.String, org.kuali.rice.kim.api.identity.Person)
     */
    @Override
    public boolean isGroupEditable(AccountingDocument accountingDocument,
            List<? extends AccountingLineRenderingContext> accountingLineRenderingContexts, Person currentUser) {
        WorkflowDocument workflowDocument = accountingDocument.getDocumentHeader().getWorkflowDocument();
        if (workflowDocument.isInitiated() || workflowDocument.isSaved()
                || workflowDocument.isCompletionRequested()) {
            return StringUtils.equalsIgnoreCase(workflowDocument.getInitiatorPrincipalId(),
                    currentUser.getPrincipalId());
        }

        for (AccountingLineRenderingContext renderingContext : accountingLineRenderingContexts) {
            if (renderingContext.isEditableLine()) {
                return true;
            }
        }

        return false;
    }

    /**
     * collection the actions that are allowed for the given accounting line
     *
     * @param accountingLine the given accounting line
     * @param accountingLinePropertyName the property name of the given account line, typically, the form name
     * @param accountingLineIndex the index of the given accounting line in its accounting line group
     * @param groupTitle the title of the accounting line group
     * @return the actions that are allowed for the given accounting line
     */
    protected Map<String, AccountingLineViewAction> getActionMap(
            AccountingLineRenderingContext accountingLineRenderingContext, String accountingLinePropertyName,
            Integer accountingLineIndex, String groupTitle) {
        Map<String, AccountingLineViewAction> actionMap = new HashMap<String, AccountingLineViewAction>();

        if (accountingLineIndex == null || accountingLineIndex < 0) {
            AccountingLineViewAction addAction = this.getAddAction(
                    accountingLineRenderingContext.getAccountingLine(), accountingLinePropertyName, groupTitle);
            actionMap.put(KFSConstants.INSERT_METHOD, addAction);
        } else {
            if (accountingLineRenderingContext.allowDelete()) {
                AccountingLineViewAction deleteAction = this.getDeleteAction(
                        accountingLineRenderingContext.getAccountingLine(), accountingLinePropertyName,
                        accountingLineIndex, groupTitle);
                actionMap.put(KRADConstants.DELETE_METHOD, deleteAction);
            }

            AccountingLineViewAction balanceInquiryAction = this.getBalanceInquiryAction(
                    accountingLineRenderingContext.getAccountingLine(), accountingLinePropertyName,
                    accountingLineIndex, groupTitle);
            actionMap.put(KFSConstants.PERFORMANCE_BALANCE_INQUIRY_FOR_METHOD, balanceInquiryAction);
        }

        return actionMap;
    }

    /**
     * determine whether the current user has permission to edit the given field in the given accounting line
     *
     * @param accountingDocument the given accounting document
     * @param accountingLine the given accounting line in the document
     * @param fieldName the name of a field in the given accounting line
     * @param editableLine whether the parent line of this field is editable
     * @param editablePage whether the parent page of this field is editable
     * @param currentUser the current user
     * @return true if the the current user has permission to edit the given field in the given accounting line; otherwsie, false
     */
    @Override
    public boolean hasEditPermissionOnField(AccountingDocument accountingDocument, AccountingLine accountingLine,
            String accountingLineCollectionProperty, String fieldName, boolean editableLine, boolean editablePage,
            Person currentUser) {
        if (!determineEditPermissionOnField(accountingDocument, accountingLine, accountingLineCollectionProperty,
                fieldName, editablePage)) {
            return false;
        }

        // the fields in a new line should be always editable
        if (editableLine && accountingLine.getSequenceNumber() == null) {
            return true;
        }

        // examine whether the given field can be editable
        boolean hasEditPermissionOnField = editableLine || this.determineEditPermissionByFieldName(
                accountingDocument, accountingLine,
                getKimHappyPropertyNameForField(accountingLineCollectionProperty + "." + fieldName), currentUser);
        if (!hasEditPermissionOnField) {
            // kim check shows field should not be editable based on contents of field - check if line error message occurred on this line
            // if error message shows up, then the value must have changed recently so - we make it editable to allow user to correct it
            WorkflowDocument workflowDocument = accountingDocument.getDocumentHeader().getWorkflowDocument();
            if (workflowDocument.isEnroute()
                    && isMessageMapContainingErrorsOnLine(accountingLineCollectionProperty)) {
                return true;
            }
        }
        return hasEditPermissionOnField;
    }

    /**
     * Allows the overriding of whether a field on an accounting line is editable or not
     * @param accountingDocument the accounting document the line to test is on
     * @param accountingLine the accounting line to test
     * @param accountingLineCollectionProperty the property that the accounting line lives in
     * @param fieldName the name of the field we are testing
     * @param editableLine whether the parent line of this field is editable
     * @param editablePage whether the parent page of this field is editable
     * @return true if the field can be edited (subject to subsequence KIM check); false otherwise
     */
    public boolean determineEditPermissionOnField(AccountingDocument accountingDocument,
            AccountingLine accountingLine, String accountingLineCollectionProperty, String fieldName,
            boolean editablePage) {
        if (!editablePage) {
            return false; // no edits by default on non editable pages
        }

        final FinancialSystemDocumentHeader documentHeader = (FinancialSystemDocumentHeader) accountingDocument
                .getDocumentHeader();
        final WorkflowDocument workflowDocument = documentHeader.getWorkflowDocument();

        // if a document is cancelled or in error, all of its fields cannot be editable
        if (workflowDocument.isCanceled()
                || ObjectUtils.isNotNull(documentHeader.getFinancialDocumentInErrorNumber())) {
            return false;
        }

        return true;
    }

    /**
     * Determine whether the current user has permission to edit the given accounting line as a whole
     *
     * @param accountingDocument the given accounting document
     * @param accountingLine the given accounting line in the document
     * @param currentUser the current user
     * @return true if the the current user has permission to edit the given accounting line; otherwsie, false
     */
    @Override
    public boolean hasEditPermissionOnAccountingLine(AccountingDocument accountingDocument,
            AccountingLine accountingLine, String accountingLineCollectionProperty, Person currentUser,
            boolean pageIsEditable) {
        if (determineEditPermissionOnLine(accountingDocument, accountingLine, accountingLineCollectionProperty,
                StringUtils.equalsIgnoreCase(
                        accountingDocument.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId(),
                        currentUser.getPrincipalId()),
                pageIsEditable)) {

            if (approvedForUnqualifiedEditing(accountingDocument, accountingLine, accountingLineCollectionProperty,
                    StringUtils.equalsIgnoreCase(
                            accountingDocument.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId(),
                            currentUser.getPrincipalId()))) {
                return true; // don't do the KIM check, we're good
            }

            // examine whether the whole line can be editable via KIM check
            final String lineFieldName = getKimHappyPropertyNameForField(accountingLineCollectionProperty);
            return this.determineEditPermissionByFieldName(accountingDocument, accountingLine, lineFieldName,
                    currentUser);
        }
        return false;
    }

    /**
     * A hook to decide, pre-KIM check, if there's an edit permission on the given accounting line
     * @param accountingDocument the accounting document the line is or wants to be associated with
     * @param accountingLine the accounting line itself
     * @param accountingLineCollectionProperty the collection the accounting line is or would be part of
     * @param currentUserIsDocumentInitiator is the current user the initiator of the document?
     * @return true if the line as a whole can be edited, false otherwise
     */
    public boolean determineEditPermissionOnLine(AccountingDocument accountingDocument,
            AccountingLine accountingLine, String accountingLineCollectionProperty,
            boolean currentUserIsDocumentInitiator, boolean pageIsEditable) {
        if (accountingDocument instanceof Correctable) {
            String errorDocumentNumber = ((FinancialSystemDocumentHeader) accountingDocument.getDocumentHeader())
                    .getFinancialDocumentInErrorNumber();
            if (StringUtils.isNotBlank(errorDocumentNumber)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Determines if the given line is editable, no matter what a KIM check would say about line editability.  In the default case,
     * any accounting line is editable - minus KIM check - when the document is PreRoute, or if the line is a new line
     * @param accountingDocument the accounting document the line is or wants to be associated with
     * @param accountingLine the accounting line itself
     * @param accountingLineCollectionProperty the collection the accounting line is or would be part of
     * @param currentUserIsDocumentInitiator is the current user the initiator of the document?
     * @return true if the line as a whole can be edited without the KIM check, false otherwise
     */
    protected boolean approvedForUnqualifiedEditing(AccountingDocument accountingDocument,
            AccountingLine accountingLine, String accountingLineCollectionProperty,
            boolean currentUserIsDocumentInitiator) {
        // the fields in a new line should be always editable
        // KFSMI-9280 : Not sure if this is true.  After entering routing, the ability to add new lines is limited by permissions.
        //        if (accountingLine.getSequenceNumber() == null) {
        //            return true;
        //        }

        // check the initiation permission on the document if it is in the state of preroute
        WorkflowDocument workflowDocument = accountingDocument.getDocumentHeader().getWorkflowDocument();
        if (workflowDocument.isCompletionRequested()) {
            return true;
        }
        if (workflowDocument.isInitiated() || workflowDocument.isSaved()) {
            return currentUserIsDocumentInitiator;
        }
        return false;
    }

    /**
     * determine whether the current user has permission to edit the given field in the given accounting line
     *
     * @param accountingDocument the given accounting document
     * @param accountingLine the given accounting line in the document
     * @param fieldName the name of a field in the given accounting line
     * @param currentUser the current user
     * @return true if the the current user has permission to edit the given field in the given accounting line; otherwsie, false
     */
    protected boolean determineEditPermissionByFieldName(AccountingDocument accountingDocument,
            AccountingLine accountingLine, String fieldName, Person currentUser) {
        Map<String, String> roleQualifiers = getRoleQualifiers(accountingDocument, accountingLine);
        Map<String, String> permissionDetail = getPermissionDetails(accountingDocument, fieldName);

        return this.hasEditPermission(accountingDocument, currentUser, permissionDetail, roleQualifiers);
    }

    /**
     * determine whether the current user has modification permission on an accounting line with the given qualifications. The
     * permission template and namespace have been setup in the method.
     *
     * @param currentUser the current user
     * @param permissionDetails the given permission details
     * @param roleQualifiers the given role qualifications
     * @return true if the user has edit permission on an accounting line with the given qualifications; otherwise, false
     */
    protected boolean hasEditPermission(AccountingDocument accountingDocument, Person currentUser,
            Map<String, String> permissionDetails, Map<String, String> roleQualifiers) {
        String pricipalId = currentUser.getPrincipalId();
        DocumentAuthorizer accountingDocumentAuthorizer = this.getDocumentAuthorizer(accountingDocument);

        return accountingDocumentAuthorizer.isAuthorizedByTemplate(accountingDocument,
                KFSConstants.PermissionTemplate.MODIFY_ACCOUNTING_LINES.namespace,
                KFSConstants.PermissionTemplate.MODIFY_ACCOUNTING_LINES.name, pricipalId, permissionDetails,
                roleQualifiers);
    }

    /**
     * Gathers together all the information for a permission detail attribute set
     *
     * @param documentTypeName the document
     * @param fieldName the given field name
     * @return all the information for a permission detail attribute set
     */
    protected Map<String, String> getPermissionDetails(Document document, String fieldName) {
        Map<String, String> permissionDetails = new HashMap<String, String>();
        WorkflowDocument workflowDocument = document.getDocumentHeader().getWorkflowDocument();

        if (StringUtils.isNotBlank(workflowDocument.getDocumentTypeName())) {
            permissionDetails.put(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME,
                    workflowDocument.getDocumentTypeName());
        }

        if (workflowDocument.isEnroute() && !workflowDocument.isApproved()) {
            Set<String> currentNodes = workflowDocument.getCurrentNodeNames();
            if (CollectionUtils.isNotEmpty(currentNodes)) {
                String routeNode = currentNodes.iterator().next();
                if (StringUtils.isNotBlank(routeNode)) {
                    permissionDetails.put(KimConstants.AttributeConstants.ROUTE_NODE_NAME, routeNode);
                }
            }
        } else {
            // document has not been routed yet - use the "PreRoute" note
            permissionDetails.put(KimConstants.AttributeConstants.ROUTE_NODE_NAME,
                    DocumentAuthorizerBase.PRE_ROUTING_ROUTE_NAME);
        }

        if (StringUtils.isNotBlank(fieldName)) {
            permissionDetails.put(KimConstants.AttributeConstants.PROPERTY_NAME, fieldName);
        }

        return permissionDetails;
    }

    /**
     * Gathers together the role qualifiers for the KIM perm call
     *
     * @param accountingLine the accounting line to get role qualifiers from
     * @return the gathered Map<String,String> of role qualifiers
     */
    protected final Map<String, String> getRoleQualifiers(AccountingDocument accountingDocument,
            AccountingLine accountingLine) {
        Map<String, String> roleQualifiers = new HashMap<String, String>();

        if (accountingLine != null && StringUtils.isNotBlank(accountingLine.getAccountNumber())
                && StringUtils.isNotBlank(accountingLine.getChartOfAccountsCode())) {
            roleQualifiers.put(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE, accountingLine.getChartOfAccountsCode());
            roleQualifiers.put(KfsKimAttributes.ACCOUNT_NUMBER, accountingLine.getAccountNumber());
        }

        return roleQualifiers;
    }

    /**
     * @param field AccountingLineViewField to find KIM-happy property name for
     * @return a property name that KIM will like
     */
    protected String getKimHappyPropertyNameForField(String convertedName) {
        convertedName = stripDocumentPrefixFromName(convertedName);

        return replaceCollectionElementsWithPlurals(convertedName);
    }

    /**
     * get the full property name of the given field
     *
     * @param field the field to get the name from
     * @return the full property name of the given field, typically, a combination of property prefix and simple property name
     */
    protected String getFieldName(AccountingLineViewField field) {
        String propertyPrefix = field.getField().getPropertyPrefix();
        String propertyName = field.getField().getPropertyName();

        return StringUtils.isNotBlank(propertyPrefix) ? (propertyPrefix + "." + propertyName) : propertyName;
    }

    /**
     * Strips "document." and everything before from the property name
     *
     * @param name the property name to strip the document portion off of
     * @return the stripped name
     */
    protected String stripDocumentPrefixFromName(String name) {
        return name.replaceFirst("(.)*document\\.", StringUtils.EMPTY);
    }

    /**
     * Replaces references to collection elements to their respective plural names WARNING: this method is totally lame and I for
     * one wished it didn't have to exist
     *
     * @param name the property name with perhaps collection elements in
     * @return the corrected name
     */
    protected String replaceCollectionElementsWithPlurals(String name) {
        //KFSMI-9923 - modified to replace collection elements in each part of the name otherwise prefixes could end up
        //with the unwanted double "s" (ex: targetAccountingLiness.financialObjectCode)
        String newName = "";
        String[] names = name.split("\\.");
        for (int i = 0; i < names.length; i++) {
            String temp = names[i].replaceAll("\\[\\d+\\]", "s");
            // now - need to check if the property name ends with a double "s", which is incorrect
            if (temp.endsWith("ss")) {
                temp = StringUtils.chop(temp);
            }
            if (i > 0) {
                newName += "." + temp;
            } else {
                newName = temp;
            }
        }
        return newName;
    }

    /**
     * construct the balance inquiry action for the given accounting line
     *
     * @param accountingLine the given accounting line
     * @param accountingLinePropertyName the property name of the given account line, typically, the form name
     * @param accountingLineIndex the index of the given accounting line in its accounting line group
     * @param groupTitle the title of the accounting line group
     * @return the balance inquiry action for the given accounting line
     */
    protected AccountingLineViewAction getBalanceInquiryAction(AccountingLine accountingLine,
            String accountingLinePropertyName, Integer accountingLineIndex, String groupTitle) {
        String actionMethod = this.getBalanceInquiryMethod(accountingLine, accountingLinePropertyName,
                accountingLineIndex);
        String actionLabel = this.getActionLabel(
                KFSKeyConstants.AccountingLineViewRendering.ACCOUNTING_LINE_BALANCE_INQUIRY_ACTION_LABEL,
                groupTitle, accountingLineIndex + 1);

        String actionImageName = getKFSImagePath() + "tinybutton-balinquiry.gif";

        return new AccountingLineViewAction(actionMethod, actionLabel, actionImageName);
    }

    /**
     * construct the delete action for the given accounting line
     *
     * @param accountingLine the given accounting line
     * @param accountingLinePropertyName the property name of the given account line, typically, the form name
     * @param accountingLineIndex the index of the given accounting line in its accounting line group
     * @param groupTitle the title of the accounting line group
     * @return the delete action for the given accounting line
     */
    protected AccountingLineViewAction getDeleteAction(AccountingLine accountingLine,
            String accountingLinePropertyName, Integer accountingLineIndex, String groupTitle) {
        String actionMethod = this.getDeleteLineMethod(accountingLine, accountingLinePropertyName,
                accountingLineIndex);
        String actionLabel = this.getActionLabel(
                KFSKeyConstants.AccountingLineViewRendering.ACCOUNTING_LINE_DELETE_ACTION_LABEL, groupTitle,
                accountingLineIndex + 1);

        String actionImageName = getRiceImagePath() + "tinybutton-delete1.gif";

        return new AccountingLineViewAction(actionMethod, actionLabel, actionImageName);
    }

    /**
     * construct the add action for the given accounting line, typically, a new accounting line
     *
     * @param accountingLine the given accounting line
     * @param accountingLinePropertyName the property name of the given account line, typically, the form name
     * @param accountingLineIndex the index of the given accounting line in its accounting line group
     * @param groupTitle the title of the accounting line group
     * @return the add action for the given accounting line
     */
    protected AccountingLineViewAction getAddAction(AccountingLine accountingLine,
            String accountingLinePropertyName, String groupTitle) {
        String actionMethod = this.getAddMethod(accountingLine, accountingLinePropertyName);
        String actionLabel = this.getActionLabel(
                KFSKeyConstants.AccountingLineViewRendering.ACCOUNTING_LINE_ADD_ACTION_LABEL, groupTitle);

        String actionImageName = getRiceImagePath() + "tinybutton-add1.gif";

        return new AccountingLineViewAction(actionMethod, actionLabel, actionImageName);
    }

    /**
     * get a label for an action with the specified message key and values
     *
     * @param messageKey the given message key that points to the label
     * @param values the given values that would be displayed in label
     * @return a label for an action with the specified message key and values
     */
    protected String getActionLabel(String messageKey, Object... values) {
        String messageBody = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(messageKey);

        return MessageFormat.format(messageBody, values);
    }

    /**
     * Builds the action method name of the method that adds accounting lines for this group
     *
     * @param accountingLine the accounting line an action is being checked for
     * @param accountingLinePropertyName the property name of the accounting line
     * @return the action method name of the method that adds accounting lines for this group
     */
    protected String getAddMethod(AccountingLine accountingLine, String accountingLineProperty) {
        final String infix = getActionInfixForNewAccountingLine(accountingLine, accountingLineProperty);
        return KFSConstants.INSERT_METHOD + infix + "Line.anchoraccounting" + infix + "Anchor";
    }

    /**
     * Builds the action method name of the method that deletes accounting lines for this group
     *
     * @param accountingLine the accounting line an action is being checked for
     * @param accountingLinePropertyName the property name of the accounting line
     * @param accountingLineIndex the index of the given accounting line within the the group being rendered
     * @return the action method name of the method that deletes accounting lines for this group
     */
    protected String getDeleteLineMethod(AccountingLine accountingLine, String accountingLineProperty,
            Integer accountingLineIndex) {
        final String infix = getActionInfixForExtantAccountingLine(accountingLine, accountingLineProperty);
        return KRADConstants.DELETE_METHOD + infix + "Line.line" + accountingLineIndex + ".anchoraccounting" + infix
                + "Anchor";
    }

    /**
     * Builds the action method name of the method that performs a balance inquiry on accounting lines for this group
     *
     * @param accountingLine the accounting line an action is being checked for
     * @param accountingLinePropertyName the property name of the accounting line
     * @param accountingLineIndex the index of the given accounting line within the the group being rendered
     * @return the action method name of the method that performs a balance inquiry on accounting lines for this group
     */
    protected String getBalanceInquiryMethod(AccountingLine accountingLine, String accountingLineProperty,
            Integer accountingLineIndex) {
        final String infix = getActionInfixForExtantAccountingLine(accountingLine, accountingLineProperty);
        return KFSConstants.PERFORMANCE_BALANCE_INQUIRY_FOR_METHOD + infix + "Line.line" + accountingLineIndex
                + ".anchoraccounting" + infix + "existingLineLineAnchor" + accountingLineIndex;
    }

    /**
     * Gets the "action infix" for the given accounting line, so that the action knows it is supposed to add to source vs. target
     *
     * @param accountingLine the accounting line an action is being checked for
     * @param accountingLinePropertyName the property name of the accounting line
     * @return the name of the action infix
     */
    protected String getActionInfixForNewAccountingLine(AccountingLine accountingLine,
            String accountingLinePropertyName) {
        if (accountingLine.isSourceAccountingLine()) {
            return KFSConstants.SOURCE;
        }

        if (accountingLine.isTargetAccountingLine()) {
            return KFSConstants.TARGET;
        }

        return KFSConstants.EMPTY_STRING;
    }

    /**
     * Gets the "action infix" for the given accounting line which already exists on the document, so that the action knows it is
     * supposed to add to source vs. target
     *
     * @param accountingLine the accounting line an action is being checked for
     * @param accountingLinePropertyName the property name of the accounting line
     * @return the name of the action infix
     */
    protected String getActionInfixForExtantAccountingLine(AccountingLine accountingLine,
            String accountingLinePropertyName) {
        if (accountingLine.isSourceAccountingLine()) {
            return KFSConstants.SOURCE;
        }

        if (accountingLine.isTargetAccountingLine()) {
            return KFSConstants.TARGET;
        }

        return KFSConstants.EMPTY_STRING;
    }

    /**
     * get the document authorizer of the given accounting document
     *
     * @param accountingDocument the given accounting document
     * @return the document authorizer of the given accounting document
     */
    protected DocumentAuthorizer getDocumentAuthorizer(AccountingDocument accountingDocument) {
        return SpringContext.getBean(DocumentHelperService.class).getDocumentAuthorizer(accountingDocument);
    }

    /**
     * @return the path to rice images
     */
    protected String getRiceImagePath() {
        if (riceImagePath == null) {
            riceImagePath = getConfigurationService()
                    .getPropertyValueAsString(KRADConstants.EXTERNALIZABLE_IMAGES_URL_KEY);
        }
        return riceImagePath;
    }

    /**
     * @return the path to KFS images
     */
    protected String getKFSImagePath() {
        if (kfsImagePath == null) {
            kfsImagePath = getConfigurationService()
                    .getPropertyValueAsString(KRADConstants.APPLICATION_EXTERNALIZABLE_IMAGES_URL_KEY);
        }
        return kfsImagePath;
    }

    protected ConfigurationService getConfigurationService() {
        if (kualiConfigurationService == null) {
            kualiConfigurationService = SpringContext.getBean(ConfigurationService.class);
        }
        return kualiConfigurationService;
    }
}