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.validation.impl;
017    
018    import java.util.Iterator;
019    import java.util.Map;
020    import java.util.Set;
021    
022    import org.apache.commons.lang.StringUtils;
023    import org.kuali.kfs.sys.KFSConstants;
024    import org.kuali.kfs.sys.KFSKeyConstants;
025    import org.kuali.kfs.sys.KFSPropertyConstants;
026    import org.kuali.kfs.sys.businessobject.AccountingLine;
027    import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader;
028    import org.kuali.kfs.sys.document.AccountingDocument;
029    import org.kuali.kfs.sys.document.Correctable;
030    import org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizer;
031    import org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase;
032    import org.kuali.kfs.sys.document.datadictionary.AccountingLineGroupDefinition;
033    import org.kuali.kfs.sys.document.datadictionary.FinancialSystemTransactionalDocumentEntry;
034    import org.kuali.kfs.sys.document.validation.GenericValidation;
035    import org.kuali.kfs.sys.document.validation.event.AddAccountingLineEvent;
036    import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent;
037    import org.kuali.kfs.sys.document.validation.event.DeleteAccountingLineEvent;
038    import org.kuali.kfs.sys.document.validation.event.UpdateAccountingLineEvent;
039    import org.kuali.rice.kim.bo.Person;
040    import org.kuali.rice.kns.rule.event.KualiDocumentEvent;
041    import org.kuali.rice.kns.service.DataDictionaryService;
042    import org.kuali.rice.kns.util.GlobalVariables;
043    
044    /**
045     * A validation that checks whether the given accounting line is accessible to the given user or not
046     */
047    public class AccountingLineAccessibleValidation extends GenericValidation {
048        protected DataDictionaryService dataDictionaryService;
049        protected AccountingDocument accountingDocumentForValidation;
050        protected AccountingLine accountingLineForValidation;
051        
052        /**
053         * Indicates what is being done to an accounting line. This allows the same method to be used for different actions.
054         */
055        public enum AccountingLineAction {
056            ADD(KFSKeyConstants.ERROR_ACCOUNTINGLINE_INACCESSIBLE_ADD), DELETE(KFSKeyConstants.ERROR_ACCOUNTINGLINE_INACCESSIBLE_DELETE), UPDATE(KFSKeyConstants.ERROR_ACCOUNTINGLINE_INACCESSIBLE_UPDATE);
057    
058            public final String accessibilityErrorKey;
059    
060            AccountingLineAction(String accessabilityErrorKey) {
061                this.accessibilityErrorKey = accessabilityErrorKey;
062            }
063        }
064    
065        /**
066         * Validates that the given accounting line is accessible for editing by the current user.
067         * <strong>This method expects a document as the first parameter and an accounting line as the second</strong>
068         * @see org.kuali.kfs.sys.document.validation.Validation#validate(java.lang.Object[])
069         */
070        public boolean validate(AttributedDocumentEvent event) {        
071            final Person currentUser = GlobalVariables.getUserSession().getPerson();
072            
073            if (accountingDocumentForValidation instanceof Correctable) {
074                final String errorDocumentNumber = ((FinancialSystemDocumentHeader)accountingDocumentForValidation.getDocumentHeader()).getFinancialDocumentInErrorNumber();
075                if (StringUtils.isNotBlank(errorDocumentNumber))
076                    return true;
077            }
078            
079            final AccountingLineAuthorizer accountingLineAuthorizer = lookupAccountingLineAuthorizer();
080            final boolean lineIsAccessible = accountingLineAuthorizer.hasEditPermissionOnAccountingLine(accountingDocumentForValidation, accountingLineForValidation, getAccountingLineCollectionProperty(), currentUser, true);
081            final boolean isAccessible = accountingLineAuthorizer.hasEditPermissionOnField(accountingDocumentForValidation, accountingLineForValidation, getAccountingLineCollectionProperty(), KFSPropertyConstants.ACCOUNT_NUMBER, lineIsAccessible, true, currentUser);
082    
083            // report errors
084            if (!isAccessible) {
085                final String principalName = currentUser.getPrincipalName();
086                
087                final String[] chartErrorParams = new String[] { getDataDictionaryService().getAttributeLabel(accountingLineForValidation.getClass(), KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE), accountingLineForValidation.getChartOfAccountsCode(),  principalName};
088                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, convertEventToMessage(event), chartErrorParams);
089                
090                final String[] accountErrorParams = new String[] { getDataDictionaryService().getAttributeLabel(accountingLineForValidation.getClass(), KFSPropertyConstants.ACCOUNT_NUMBER), accountingLineForValidation.getAccountNumber(), principalName };
091                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.ACCOUNT_NUMBER, convertEventToMessage(event), accountErrorParams);
092            }
093    
094            return isAccessible;
095        }
096        
097        /**
098         * Returns the name of the accounting line group which holds the proper authorizer to do the KIM check
099         * @return the name of the accouting line group to get the authorizer from
100         */
101        protected String getGroupName() {
102            return (accountingLineForValidation.isSourceAccountingLine() ? KFSConstants.SOURCE_ACCOUNTING_LINES_GROUP_NAME : KFSConstants.TARGET_ACCOUNTING_LINES_GROUP_NAME);
103        }
104        
105        /**
106         * @return hopefully, the best accounting line authorizer implementation to do the KIM check for to see if lines are accessible
107         */
108        protected AccountingLineAuthorizer lookupAccountingLineAuthorizer() {
109            final String groupName = getGroupName();
110            final Map<String, AccountingLineGroupDefinition> groups = ((FinancialSystemTransactionalDocumentEntry)dataDictionaryService.getDataDictionary().getDictionaryObjectEntry(accountingDocumentForValidation.getClass().getName())).getAccountingLineGroups();
111            
112            if (groups.isEmpty()) return new AccountingLineAuthorizerBase(); // no groups? just use the default...
113            if (groups.containsKey(groupName)) return groups.get(groupName).getAccountingLineAuthorizer(); // we've got the group
114    
115            final Set<String> groupNames = groups.keySet(); // we've got groups, just not the proper name; try our luck and get the first group iterator
116            final Iterator<String> groupNameIterator = groupNames.iterator();
117            final String firstGroupName = groupNameIterator.next();
118            return groups.get(firstGroupName).getAccountingLineAuthorizer();
119        }
120        
121        /**
122         * Determines the property of the accounting line collection from the error prefixes
123         * @return the accounting line collection property
124         */
125        protected String getAccountingLineCollectionProperty() {
126            String propertyName = null;
127            if (GlobalVariables.getMessageMap().getErrorPath().size() > 0) {
128                propertyName = ((String)GlobalVariables.getMessageMap().getErrorPath().get(0)).replaceFirst(".*?document\\.", "");
129            } else {
130                propertyName = accountingLineForValidation.isSourceAccountingLine() ? KFSConstants.PermissionAttributeValue.SOURCE_ACCOUNTING_LINES.value : KFSConstants.PermissionAttributeValue.TARGET_ACCOUNTING_LINES.value;
131            }
132            if (propertyName.equals("newSourceLine")) return KFSConstants.PermissionAttributeValue.SOURCE_ACCOUNTING_LINES.value;
133            if (propertyName.equals("newTargetLine")) return KFSConstants.PermissionAttributeValue.TARGET_ACCOUNTING_LINES.value;
134            return propertyName;
135        }
136        
137        /**
138         * Determines what error message should be shown based on the event that required this validation
139         * @param event the event to use to determine the error message
140         * @return the key of the error message to display
141         */
142        protected String convertEventToMessage(KualiDocumentEvent event) {
143            if (event instanceof AddAccountingLineEvent) {
144                return AccountingLineAction.ADD.accessibilityErrorKey;
145            } else if (event instanceof UpdateAccountingLineEvent) {
146                return AccountingLineAction.UPDATE.accessibilityErrorKey;
147            } else if (event instanceof DeleteAccountingLineEvent) {
148                return AccountingLineAction.DELETE.accessibilityErrorKey;
149            } else {
150                return "";
151            }
152        }
153    
154        /**
155         * Gets the accountingDocumentForValidation attribute. 
156         * @return Returns the accountingDocumentForValidation.
157         */
158        public AccountingDocument getAccountingDocumentForValidation() {
159            return accountingDocumentForValidation;
160        }
161    
162        /**
163         * Sets the accountingDocumentForValidation attribute value.
164         * @param accountingDocumentForValidation The accountingDocumentForValidation to set.
165         */
166        public void setAccountingDocumentForValidation(AccountingDocument accountingDocumentForValidation) {
167            this.accountingDocumentForValidation = accountingDocumentForValidation;
168        }
169    
170        /**
171         * Gets the accountingLineForValidation attribute. 
172         * @return Returns the accountingLineForValidation.
173         */
174        public AccountingLine getAccountingLineForValidation() {
175            return accountingLineForValidation;
176        }
177    
178        /**
179         * Sets the accountingLineForValidation attribute value.
180         * @param accountingLineForValidation The accountingLineForValidation to set.
181         */
182        public void setAccountingLineForValidation(AccountingLine accountingLineForValidation) {
183            this.accountingLineForValidation = accountingLineForValidation;
184        }
185    
186        /**
187         * Gets the dataDictionaryService attribute. 
188         * @return Returns the dataDictionaryService.
189         */
190        public DataDictionaryService getDataDictionaryService() {
191            return dataDictionaryService;
192        }
193    
194        /**
195         * Sets the dataDictionaryService attribute value.
196         * @param dataDictionaryService The dataDictionaryService to set.
197         */
198        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
199            this.dataDictionaryService = dataDictionaryService;
200        }
201        
202    }
203