001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.sys.document.authorization;
017    
018    import java.text.MessageFormat;
019    import java.util.ArrayList;
020    import java.util.HashMap;
021    import java.util.HashSet;
022    import java.util.List;
023    import java.util.Map;
024    import java.util.Set;
025    
026    import org.apache.commons.lang.StringUtils;
027    import org.kuali.kfs.sys.KFSConstants;
028    import org.kuali.kfs.sys.KFSKeyConstants;
029    import org.kuali.kfs.sys.businessobject.AccountingLine;
030    import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader;
031    import org.kuali.kfs.sys.context.SpringContext;
032    import org.kuali.kfs.sys.document.AccountingDocument;
033    import org.kuali.kfs.sys.document.Correctable;
034    import org.kuali.kfs.sys.document.web.AccountingLineRenderingContext;
035    import org.kuali.kfs.sys.document.web.AccountingLineViewAction;
036    import org.kuali.kfs.sys.document.web.AccountingLineViewField;
037    import org.kuali.kfs.sys.identity.KfsKimAttributes;
038    import org.kuali.rice.kim.bo.Person;
039    import org.kuali.rice.kim.bo.types.dto.AttributeSet;
040    import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
041    import org.kuali.rice.kns.service.DocumentHelperService;
042    import org.kuali.rice.kns.service.KualiConfigurationService;
043    import org.kuali.rice.kns.util.GlobalVariables;
044    import org.kuali.rice.kns.util.KNSConstants;
045    import org.kuali.rice.kns.util.ObjectUtils;
046    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
047    
048    /**
049     * The default implementation of AccountingLineAuthorizer
050     */
051    public class AccountingLineAuthorizerBase implements AccountingLineAuthorizer {
052        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountingLineAuthorizerBase.class);
053    
054        private KualiConfigurationService kualiConfigurationService = SpringContext.getBean(KualiConfigurationService.class);
055        private static String riceImagePath;
056        private static String kfsImagePath;
057    
058        /**
059         * Returns the basic actions - add for new lines, delete and balance inquiry for existing lines
060         * 
061         * @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizer#getActions(org.kuali.kfs.sys.document.AccountingDocument,
062         *      org.kuali.kfs.sys.businessobject.AccountingLine, java.lang.String, java.lang.Integer, org.kuali.rice.kim.bo.Person,
063         *      java.lang.String)
064         */
065        public final List<AccountingLineViewAction> getActions(AccountingDocument accountingDocument, AccountingLineRenderingContext accountingLineRenderingContext, String accountingLinePropertyName, Integer accountingLineIndex, Person currentUser, String groupTitle) {
066            List<AccountingLineViewAction> actions = new ArrayList<AccountingLineViewAction>();
067    
068            if (accountingLineRenderingContext.isEditableLine() || isErrorMapContainingErrorsOnLine(accountingLinePropertyName)) {
069                Map<String, AccountingLineViewAction> actionMap = this.getActionMap(accountingLineRenderingContext, accountingLinePropertyName, accountingLineIndex, groupTitle);
070                actions.addAll(actionMap.values());
071            }
072    
073            return actions;
074        }
075        
076        /**
077         * Determines if the error map contains any errors which exist on the currently rendered accounting line
078         * @param accountingLinePropertyName the property name of the accounting line
079         * @return true if there are errors on the line, false otherwise
080         */
081        protected boolean isErrorMapContainingErrorsOnLine(String accountingLinePropertyName) {
082            for (Object errorKeyAsObject : GlobalVariables.getMessageMap().getPropertiesWithErrors()) {
083                if (((String)errorKeyAsObject).startsWith(accountingLinePropertyName)) return true;
084            }
085            return false;
086        }
087    
088        /**
089         * Returns a new empty HashSet
090         * 
091         * @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizer#getUnviewableBlocks(org.kuali.kfs.sys.document.AccountingDocument,
092         *      org.kuali.kfs.sys.businessobject.AccountingLine, java.lang.String, boolean)
093         */
094        public Set<String> getUnviewableBlocks(AccountingDocument accountingDocument, AccountingLine accountingLine, boolean newLine, Person currentUser) {
095            return new HashSet<String>();
096        }
097    
098        /**
099         * @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizer#renderNewLine(org.kuali.kfs.sys.document.AccountingDocument,
100         *      java.lang.String)
101         */
102        public boolean renderNewLine(AccountingDocument accountingDocument, String accountingGroupProperty) {
103            return (accountingDocument.getDocumentHeader().getWorkflowDocument().stateIsInitiated() || accountingDocument.getDocumentHeader().getWorkflowDocument().stateIsSaved());
104        }
105    
106        /**
107         * @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizer#isGroupEditable(org.kuali.kfs.sys.document.AccountingDocument,
108         *      java.lang.String, org.kuali.rice.kim.bo.Person)
109         */
110        public boolean isGroupEditable(AccountingDocument accountingDocument, List<? extends AccountingLineRenderingContext> accountingLineRenderingContexts, Person currentUser) {
111            KualiWorkflowDocument workflowDocument = accountingDocument.getDocumentHeader().getWorkflowDocument();
112            if (workflowDocument.stateIsInitiated() || workflowDocument.stateIsSaved()) {
113                return workflowDocument.userIsInitiator(currentUser);
114            }
115            
116            for (AccountingLineRenderingContext renderingContext : accountingLineRenderingContexts) {
117                if (renderingContext.isEditableLine()) return true;
118            }
119    
120            return false;
121        }
122    
123        /**
124         * collection the actions that are allowed for the given accounting line
125         * 
126         * @param accountingLine the given accounting line
127         * @param accountingLinePropertyName the property name of the given account line, typically, the form name
128         * @param accountingLineIndex the index of the given accounting line in its accounting line group
129         * @param groupTitle the title of the accounting line group
130         * @return the actions that are allowed for the given accounting line
131         */
132        protected Map<String, AccountingLineViewAction> getActionMap(AccountingLineRenderingContext accountingLineRenderingContext, String accountingLinePropertyName, Integer accountingLineIndex, String groupTitle) {
133            Map<String, AccountingLineViewAction> actionMap = new HashMap<String, AccountingLineViewAction>();
134    
135            if (accountingLineIndex == null || accountingLineIndex < 0) {
136                AccountingLineViewAction addAction = this.getAddAction(accountingLineRenderingContext.getAccountingLine(), accountingLinePropertyName, groupTitle);
137                actionMap.put(KFSConstants.INSERT_METHOD, addAction);
138            }
139            else {
140                if (accountingLineRenderingContext.allowDelete()) {
141                    AccountingLineViewAction deleteAction = this.getDeleteAction(accountingLineRenderingContext.getAccountingLine(), accountingLinePropertyName, accountingLineIndex, groupTitle);
142                    actionMap.put(KNSConstants.DELETE_METHOD, deleteAction);
143                }
144    
145                AccountingLineViewAction balanceInquiryAction = this.getBalanceInquiryAction(accountingLineRenderingContext.getAccountingLine(), accountingLinePropertyName, accountingLineIndex, groupTitle);
146                actionMap.put(KFSConstants.PERFORMANCE_BALANCE_INQUIRY_FOR_METHOD, balanceInquiryAction);
147            }
148    
149            return actionMap;
150        }
151    
152        /**
153         * determine whether the current user has permission to edit the given field in the given accounting line
154         * 
155         * @param accountingDocument the given accounting document
156         * @param accountingLine the given accounting line in the document
157         * @param fieldName the name of a field in the given accounting line
158         * @param editableLine whether the parent line of this field is editable
159         * @param editablePage whether the parent page of this field is editable
160         * @param currentUser the current user
161         * @return true if the the current user has permission to edit the given field in the given accounting line; otherwsie, false
162         */
163        public final boolean hasEditPermissionOnField(AccountingDocument accountingDocument, AccountingLine accountingLine, String accountingLineCollectionProperty, String fieldName, boolean editableLine, boolean editablePage, Person currentUser) {
164            if (!determineEditPermissionOnField(accountingDocument, accountingLine, accountingLineCollectionProperty, fieldName, editablePage)) {
165                return false;
166            }
167            
168            // the fields in a new line should be always editable
169            if (accountingLine.getSequenceNumber() == null) {
170                return true;
171            }
172    
173            // examine whether the given field can be editable
174            boolean hasEditPermissionOnField = editableLine || this.determineEditPermissionByFieldName(accountingDocument, accountingLine, getKimHappyPropertyNameForField(accountingLineCollectionProperty+"."+fieldName), currentUser);
175            if (hasEditPermissionOnField == false) {
176                // kim check shows field should not be editable based on contents of field - check if line error message occurred on this line
177                // if error message shows up, then the value must have changed recently so - we make it editable to allow user to correct it
178                KualiWorkflowDocument workflowDocument = accountingDocument.getDocumentHeader().getWorkflowDocument();
179                if (workflowDocument.stateIsEnroute() && isErrorMapContainingErrorsOnLine(accountingLineCollectionProperty)) return true;
180             }
181            return hasEditPermissionOnField;
182        }
183     
184        /**
185         * Allows the overriding of whether a field on an accounting line is editable or not
186         * @param accountingDocument the accounting document the line to test is on
187         * @param accountingLine the accounting line to test
188         * @param accountingLineCollectionProperty the property that the accounting line lives in
189         * @param fieldName the name of the field we are testing
190         * @param editableLine whether the parent line of this field is editable
191         * @param editablePage whether the parent page of this field is editable
192         * @return true if the field can be edited (subject to subsequence KIM check); false otherwise
193         */
194        public boolean determineEditPermissionOnField(AccountingDocument accountingDocument, AccountingLine accountingLine, String accountingLineCollectionProperty, String fieldName, boolean editablePage) {
195            if (!editablePage) return false; // no edits by default on non editable pages
196            
197            final FinancialSystemDocumentHeader documentHeader = (FinancialSystemDocumentHeader) accountingDocument.getDocumentHeader();
198            final KualiWorkflowDocument workflowDocument = documentHeader.getWorkflowDocument();
199    
200            // if a document is cancelled or in error, all of its fields cannot be editable
201            if (workflowDocument.stateIsCanceled() || ObjectUtils.isNotNull(documentHeader.getFinancialDocumentInErrorNumber())) {
202                return false;
203            }
204    
205            return true;
206        }
207    
208        /**
209         * Determine whether the current user has permission to edit the given accounting line as a whole
210         * 
211         * @param accountingDocument the given accounting document
212         * @param accountingLine the given accounting line in the document
213         * @param currentUser the current user
214         * @return true if the the current user has permission to edit the given accounting line; otherwsie, false
215         */
216        public final boolean hasEditPermissionOnAccountingLine(AccountingDocument accountingDocument, AccountingLine accountingLine, String accountingLineCollectionProperty, Person currentUser, boolean pageIsEditable) {        
217            if (determineEditPermissionOnLine(accountingDocument, accountingLine, accountingLineCollectionProperty, accountingDocument.getDocumentHeader().getWorkflowDocument().userIsInitiator(currentUser), pageIsEditable)) {
218                
219                if (approvedForUnqualifiedEditing(accountingDocument, accountingLine, accountingLineCollectionProperty, accountingDocument.getDocumentHeader().getWorkflowDocument().userIsInitiator(currentUser))) {
220                    return true;  // don't do the KIM check, we're good
221                }
222                
223                // examine whether the whole line can be editable via KIM check
224                final String lineFieldName = getKimHappyPropertyNameForField(accountingLineCollectionProperty);
225                return this.determineEditPermissionByFieldName(accountingDocument, accountingLine, lineFieldName, currentUser);
226            }
227            return false;
228        }
229     
230        /**
231         * A hook to decide, pre-KIM check, if there's an edit permission on the given accounting line
232         * @param accountingDocument the accounting document the line is or wants to be associated with
233         * @param accountingLine the accounting line itself
234         * @param accountingLineCollectionProperty the collection the accounting line is or would be part of
235         * @param currentUserIsDocumentInitiator is the current user the initiator of the document?
236         * @return true if the line as a whole can be edited, false otherwise
237         */
238        public boolean determineEditPermissionOnLine(AccountingDocument accountingDocument, AccountingLine accountingLine, String accountingLineCollectionProperty, boolean currentUserIsDocumentInitiator, boolean pageIsEditable) {
239            if (accountingDocument instanceof Correctable) {
240                String errorDocumentNumber = ((FinancialSystemDocumentHeader)accountingDocument.getDocumentHeader()).getFinancialDocumentInErrorNumber();
241                if (StringUtils.isNotBlank(errorDocumentNumber))
242                    return false;
243            }
244            
245            return true;
246        }
247        
248        /**
249         * Determines if the given line is editable, no matter what a KIM check would say about line editability.  In the default case,
250         * any accounting line is editable - minus KIM check - when the document is PreRoute, or if the line is a new line
251         * @param accountingDocument the accounting document the line is or wants to be associated with
252         * @param accountingLine the accounting line itself
253         * @param accountingLineCollectionProperty the collection the accounting line is or would be part of
254         * @param currentUserIsDocumentInitiator is the current user the initiator of the document?
255         * @return true if the line as a whole can be edited without the KIM check, false otherwise
256         */
257        protected boolean approvedForUnqualifiedEditing(AccountingDocument accountingDocument, AccountingLine accountingLine, String accountingLineCollectionProperty, boolean currentUserIsDocumentInitiator) {
258            // the fields in a new line should be always editable
259            if (accountingLine.getSequenceNumber() == null) {
260                return true;
261            }
262            
263            // check the initiation permission on the document if it is in the state of preroute
264            KualiWorkflowDocument workflowDocument = accountingDocument.getDocumentHeader().getWorkflowDocument();
265            if (workflowDocument.stateIsInitiated() || workflowDocument.stateIsSaved()) {
266                return currentUserIsDocumentInitiator;
267            }
268            return false;
269        }
270    
271        /**
272         * determine whether the current user has permission to edit the given field in the given accounting line
273         * 
274         * @param accountingDocument the given accounting document
275         * @param accountingLine the given accounting line in the document
276         * @param fieldName the name of a field in the given accounting line
277         * @param currentUser the current user
278         * @return true if the the current user has permission to edit the given field in the given accounting line; otherwsie, false
279         */    
280        private boolean determineEditPermissionByFieldName(AccountingDocument accountingDocument, AccountingLine accountingLine, String fieldName, Person currentUser) {        
281            final AttributeSet roleQualifiers = this.getRoleQualifiers(accountingDocument, accountingLine);
282            final String documentTypeName = accountingDocument.getDocumentHeader().getWorkflowDocument().getDocumentType();
283            final AttributeSet permissionDetail = this.getPermissionDetails(documentTypeName, fieldName);
284    
285            return this.hasEditPermission(accountingDocument, currentUser, permissionDetail, roleQualifiers);       
286        }
287    
288        /**
289         * determine whether the current user has modification permission on an accounting line with the given qualifications. The
290         * permission template and namespace have been setup in the method.
291         * 
292         * @param currentUser the current user
293         * @param permissionDetails the given permission details
294         * @param roleQualifiers the given role qualifications
295         * @return true if the user has edit permission on an accounting line with the given qualifications; otherwise, false
296         */
297        private boolean hasEditPermission(AccountingDocument accountingDocument, Person currentUser, AttributeSet permissionDetails, AttributeSet roleQualifiers) {
298            String pricipalId = currentUser.getPrincipalId();
299            DocumentAuthorizer accountingDocumentAuthorizer = this.getDocumentAuthorizer(accountingDocument);
300            
301            return accountingDocumentAuthorizer.isAuthorizedByTemplate(accountingDocument, KFSConstants.ParameterNamespaces.KFS, KFSConstants.PermissionTemplate.MODIFY_ACCOUNTING_LINES.name, pricipalId, permissionDetails, roleQualifiers);
302        }
303    
304        /**
305         * Gathers together all the information for a permission detail attribute set
306         * 
307         * @param documentTypeName the document
308         * @param fieldName the given field name
309         * @return all the information for a permission detail attribute set
310         */
311        private AttributeSet getPermissionDetails(String documentTypeName, String fieldName) {
312            AttributeSet permissionDetails = new AttributeSet();
313    
314            if (StringUtils.isNotBlank(documentTypeName)) {
315                permissionDetails.put(KfsKimAttributes.DOCUMENT_TYPE_NAME, documentTypeName);
316            }
317    
318            if (StringUtils.isNotBlank(fieldName)) {
319                permissionDetails.put(KfsKimAttributes.PROPERTY_NAME, fieldName);
320            }
321    
322            return permissionDetails;
323        }
324    
325        /**
326         * Gathers together the role qualifiers for the KIM perm call
327         * 
328         * @param accountingLine the accounting line to get role qualifiers from
329         * @return the gathered AttributeSet of role qualifiers
330         */
331        private final AttributeSet getRoleQualifiers(AccountingDocument accountingDocument, AccountingLine accountingLine) {
332            AttributeSet roleQualifiers = new AttributeSet();
333    
334            if (accountingLine != null) {
335                roleQualifiers.put(KfsKimAttributes.CHART_OF_ACCOUNTS_CODE, accountingLine.getChartOfAccountsCode());
336                roleQualifiers.put(KfsKimAttributes.ACCOUNT_NUMBER, accountingLine.getAccountNumber());
337            }
338    
339            return roleQualifiers;
340        }
341    
342        /**
343         * @param field AccountingLineViewField to find KIM-happy property name for
344         * @return a property name that KIM will like
345         */
346        protected String getKimHappyPropertyNameForField(String convertedName) {
347            convertedName = stripDocumentPrefixFromName(convertedName);
348    
349            return replaceCollectionElementsWithPlurals(convertedName);
350        }
351    
352        /**
353         * get the full property name of the given field
354         * 
355         * @param field the field to get the name from
356         * @return the full property name of the given field, typically, a combination of property prefix and simple property name
357         */
358        protected String getFieldName(AccountingLineViewField field) {
359            String propertyPrefix = field.getField().getPropertyPrefix();
360            String propertyName = field.getField().getPropertyName();
361    
362            return StringUtils.isNotBlank(propertyPrefix) ? (propertyPrefix + "." + propertyName) : propertyName;
363        }
364    
365        /**
366         * Strips "document." and everything before from the property name
367         * 
368         * @param name the property name to strip the document portion off of
369         * @return the stripped name
370         */
371        protected String stripDocumentPrefixFromName(String name) {
372            return name.replaceFirst("(.)*document\\.", StringUtils.EMPTY);
373        }
374    
375        /**
376         * Replaces references to collection elements to their respective plural names WARNING: this method is totally lame and I for
377         * one wished it didn't have to exist
378         * 
379         * @param name the property name with perhaps collection elements in
380         * @return the corrected name
381         */
382        protected String replaceCollectionElementsWithPlurals(String name) {
383            String temp = name.replaceAll("\\[\\d+\\]", "s");
384            // now - need to check if the property name ends with a double "s", which is incorrect
385            if ( temp.endsWith( "ss" ) ) {
386                temp = StringUtils.chop(temp);
387            }
388            return temp;
389        }
390    
391        /**
392         * construct the balance inquiry action for the given accounting line
393         * 
394         * @param accountingLine the given accounting line
395         * @param accountingLinePropertyName the property name of the given account line, typically, the form name
396         * @param accountingLineIndex the index of the given accounting line in its accounting line group
397         * @param groupTitle the title of the accounting line group
398         * @return the balance inquiry action for the given accounting line
399         */
400        protected AccountingLineViewAction getBalanceInquiryAction(AccountingLine accountingLine, String accountingLinePropertyName, Integer accountingLineIndex, String groupTitle) {
401            String actionMethod = this.getBalanceInquiryMethod(accountingLine, accountingLinePropertyName, accountingLineIndex);
402            String actionLabel = this.getActionLabel(KFSKeyConstants.AccountingLineViewRendering.ACCOUNTING_LINE_BALANCE_INQUIRY_ACTION_LABEL, groupTitle, accountingLineIndex + 1);
403    
404            String actionImageName = getKFSImagePath() + "tinybutton-balinquiry.gif";
405    
406            return new AccountingLineViewAction(actionMethod, actionLabel, actionImageName);
407        }
408    
409        /**
410         * construct the delete action for the given accounting line
411         * 
412         * @param accountingLine the given accounting line
413         * @param accountingLinePropertyName the property name of the given account line, typically, the form name
414         * @param accountingLineIndex the index of the given accounting line in its accounting line group
415         * @param groupTitle the title of the accounting line group
416         * @return the delete action for the given accounting line
417         */
418        protected AccountingLineViewAction getDeleteAction(AccountingLine accountingLine, String accountingLinePropertyName, Integer accountingLineIndex, String groupTitle) {
419            String actionMethod = this.getDeleteLineMethod(accountingLine, accountingLinePropertyName, accountingLineIndex);
420            String actionLabel = this.getActionLabel(KFSKeyConstants.AccountingLineViewRendering.ACCOUNTING_LINE_DELETE_ACTION_LABEL, groupTitle, accountingLineIndex + 1);
421    
422            String actionImageName = getRiceImagePath() + "tinybutton-delete1.gif";
423    
424            return new AccountingLineViewAction(actionMethod, actionLabel, actionImageName);
425        }
426    
427        /**
428         * construct the add action for the given accounting line, typically, a new accounting line
429         * 
430         * @param accountingLine the given accounting line
431         * @param accountingLinePropertyName the property name of the given account line, typically, the form name
432         * @param accountingLineIndex the index of the given accounting line in its accounting line group
433         * @param groupTitle the title of the accounting line group
434         * @return the add action for the given accounting line
435         */
436        protected AccountingLineViewAction getAddAction(AccountingLine accountingLine, String accountingLinePropertyName, String groupTitle) {
437            String actionMethod = this.getAddMethod(accountingLine, accountingLinePropertyName);
438            String actionLabel = this.getActionLabel(KFSKeyConstants.AccountingLineViewRendering.ACCOUNTING_LINE_ADD_ACTION_LABEL, groupTitle);
439    
440            String actionImageName = getRiceImagePath() + "tinybutton-add1.gif";
441    
442            return new AccountingLineViewAction(actionMethod, actionLabel, actionImageName);
443        }
444    
445        /**
446         * get a label for an action with the specified message key and values
447         * 
448         * @param messageKey the given message key that points to the label
449         * @param values the given values that would be displayed in label
450         * @return a label for an action with the specified message key and values
451         */
452        protected String getActionLabel(String messageKey, Object... values) {
453            String messageBody = kualiConfigurationService.getPropertyString(messageKey);
454    
455            return MessageFormat.format(messageBody, values);
456        }
457    
458        /**
459         * Builds the action method name of the method that adds accounting lines for this group
460         * 
461         * @param accountingLine the accounting line an action is being checked for
462         * @param accountingLinePropertyName the property name of the accounting line
463         * @return the action method name of the method that adds accounting lines for this group
464         */
465        protected String getAddMethod(AccountingLine accountingLine, String accountingLineProperty) {
466            final String infix = getActionInfixForNewAccountingLine(accountingLine, accountingLineProperty);
467            return KFSConstants.INSERT_METHOD + infix + "Line.anchoraccounting" + infix + "Anchor";
468        }
469    
470        /**
471         * Builds the action method name of the method that deletes accounting lines for this group
472         * 
473         * @param accountingLine the accounting line an action is being checked for
474         * @param accountingLinePropertyName the property name of the accounting line
475         * @param accountingLineIndex the index of the given accounting line within the the group being rendered
476         * @return the action method name of the method that deletes accounting lines for this group
477         */
478        protected String getDeleteLineMethod(AccountingLine accountingLine, String accountingLineProperty, Integer accountingLineIndex) {
479            final String infix = getActionInfixForExtantAccountingLine(accountingLine, accountingLineProperty);
480            return KNSConstants.DELETE_METHOD + infix + "Line.line" + accountingLineIndex + ".anchoraccounting" + infix + "Anchor";
481        }
482    
483        /**
484         * Builds the action method name of the method that performs a balance inquiry on accounting lines for this group
485         * 
486         * @param accountingLine the accounting line an action is being checked for
487         * @param accountingLinePropertyName the property name of the accounting line
488         * @param accountingLineIndex the index of the given accounting line within the the group being rendered
489         * @return the action method name of the method that performs a balance inquiry on accounting lines for this group
490         */
491        protected String getBalanceInquiryMethod(AccountingLine accountingLine, String accountingLineProperty, Integer accountingLineIndex) {
492            final String infix = getActionInfixForExtantAccountingLine(accountingLine, accountingLineProperty);
493            return KFSConstants.PERFORMANCE_BALANCE_INQUIRY_FOR_METHOD + infix + "Line.line" + accountingLineIndex + ".anchoraccounting" + infix + "existingLineLineAnchor" + accountingLineIndex;
494        }
495    
496        /**
497         * Gets the "action infix" for the given accounting line, so that the action knows it is supposed to add to source vs. target
498         * 
499         * @param accountingLine the accounting line an action is being checked for
500         * @param accountingLinePropertyName the property name of the accounting line
501         * @return the name of the action infix
502         */
503        protected String getActionInfixForNewAccountingLine(AccountingLine accountingLine, String accountingLinePropertyName) {
504            if (accountingLine.isSourceAccountingLine()) {
505                return KFSConstants.SOURCE;
506            }
507    
508            if (accountingLine.isTargetAccountingLine()) {
509                return KFSConstants.TARGET;
510            }
511    
512            return KFSConstants.EMPTY_STRING;
513        }
514    
515        /**
516         * Gets the "action infix" for the given accounting line which already exists on the document, so that the action knows it is
517         * supposed to add to source vs. target
518         * 
519         * @param accountingLine the accounting line an action is being checked for
520         * @param accountingLinePropertyName the property name of the accounting line
521         * @return the name of the action infix
522         */
523        protected String getActionInfixForExtantAccountingLine(AccountingLine accountingLine, String accountingLinePropertyName) {
524            if (accountingLine.isSourceAccountingLine()) {
525                return KFSConstants.SOURCE;
526            }
527    
528            if (accountingLine.isTargetAccountingLine()) {
529                return KFSConstants.TARGET;
530            }
531    
532            return KFSConstants.EMPTY_STRING;
533        }
534    
535        /**
536         * get the document authorizer of the given accounting document
537         * 
538         * @param accountingDocument the given accounting document
539         * @return the document authorizer of the given accounting document
540         */
541        private DocumentAuthorizer getDocumentAuthorizer(AccountingDocument accountingDocument) {
542            return SpringContext.getBean(DocumentHelperService.class).getDocumentAuthorizer(accountingDocument);
543        }
544        
545        /**
546         * @return the path to rice images
547         */
548        protected String getRiceImagePath() {
549            if (riceImagePath == null) {
550                riceImagePath = kualiConfigurationService.getPropertyString(KNSConstants.EXTERNALIZABLE_IMAGES_URL_KEY);
551            }
552            return riceImagePath;
553        }
554        
555        /**
556         * @return the path to KFS images
557         */
558        protected String getKFSImagePath() {
559            if (kfsImagePath == null) {
560                kfsImagePath = kualiConfigurationService.getPropertyString(KNSConstants.APPLICATION_EXTERNALIZABLE_IMAGES_URL_KEY);
561            }
562            return kfsImagePath;
563        }
564    }