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.module.endow.document; 017 018 import static org.kuali.kfs.module.endow.EndowKeyConstants.EndowmentAccountingLineParser.ERROR_INVALID_FILE_FORMAT; 019 import static org.kuali.kfs.module.endow.EndowKeyConstants.EndowmentAccountingLineParser.ERROR_INVALID_PROPERTY_VALUE; 020 import static org.kuali.kfs.module.endow.EndowPropertyConstants.ENDOWMENT_ACCOUNTING_LINE_ACCT_NBR; 021 import static org.kuali.kfs.module.endow.EndowPropertyConstants.ENDOWMENT_ACCOUNTING_LINE_AMOUNT; 022 import static org.kuali.kfs.module.endow.EndowPropertyConstants.ENDOWMENT_ACCOUNTING_LINE_CHART_CD; 023 import static org.kuali.kfs.module.endow.EndowPropertyConstants.ENDOWMENT_ACCOUNTING_LINE_NBR; 024 import static org.kuali.kfs.module.endow.EndowPropertyConstants.ENDOWMENT_ACCOUNTING_LINE_OBJECT_CD; 025 import static org.kuali.kfs.module.endow.EndowPropertyConstants.ENDOWMENT_ACCOUNTING_LINE_ORG_REF_ID; 026 import static org.kuali.kfs.module.endow.EndowPropertyConstants.ENDOWMENT_ACCOUNTING_LINE_PROJECT_CD; 027 import static org.kuali.kfs.module.endow.EndowPropertyConstants.ENDOWMENT_ACCOUNTING_LINE_SUBACCT_NBR; 028 import static org.kuali.kfs.module.endow.EndowPropertyConstants.ENDOWMENT_ACCOUNTING_LINE_SUBOBJ_CD; 029 030 import java.io.BufferedReader; 031 import java.io.IOException; 032 import java.io.InputStream; 033 import java.io.InputStreamReader; 034 import java.lang.reflect.InvocationTargetException; 035 import java.util.ArrayList; 036 import java.util.HashMap; 037 import java.util.List; 038 import java.util.Map; 039 import java.util.Map.Entry; 040 041 import org.apache.commons.lang.StringUtils; 042 import org.kuali.kfs.module.endow.EndowConstants; 043 import org.kuali.kfs.module.endow.EndowKeyConstants; 044 import org.kuali.kfs.module.endow.EndowPropertyConstants; 045 import org.kuali.kfs.module.endow.businessobject.EndowmentAccountingLine; 046 import org.kuali.kfs.module.endow.businessobject.EndowmentAccountingLineParser; 047 import org.kuali.kfs.module.endow.businessobject.SourceEndowmentAccountingLine; 048 import org.kuali.kfs.module.endow.businessobject.TargetEndowmentAccountingLine; 049 import org.kuali.kfs.sys.KFSPropertyConstants; 050 import org.kuali.kfs.sys.context.SpringContext; 051 import org.kuali.kfs.sys.exception.AccountingLineParserException; 052 import org.kuali.rice.kns.exception.InfrastructureException; 053 import org.kuali.rice.kns.service.BusinessObjectDictionaryService; 054 import org.kuali.rice.kns.service.DataDictionaryService; 055 import org.kuali.rice.kns.util.GlobalVariables; 056 import org.kuali.rice.kns.util.ObjectUtils; 057 import org.kuali.rice.kns.web.format.FormatException; 058 059 public class EndowmentAccountingLineParserBase implements EndowmentAccountingLineParser { 060 061 protected static final String[] DEFAULT_FORMAT = { ENDOWMENT_ACCOUNTING_LINE_CHART_CD, ENDOWMENT_ACCOUNTING_LINE_ACCT_NBR, ENDOWMENT_ACCOUNTING_LINE_SUBACCT_NBR, ENDOWMENT_ACCOUNTING_LINE_OBJECT_CD, ENDOWMENT_ACCOUNTING_LINE_SUBOBJ_CD, ENDOWMENT_ACCOUNTING_LINE_PROJECT_CD, ENDOWMENT_ACCOUNTING_LINE_ORG_REF_ID, ENDOWMENT_ACCOUNTING_LINE_AMOUNT }; 062 private String fileName; 063 private Integer lineNo = 0; 064 065 066 /** 067 * @see org.kuali.kfs.module.endow.businessobject.EndowmentAccountingLineParser#getSourceEndowmentAccountingLineFormat() 068 */ 069 public String[] getSourceEndowmentAccountingLineFormat() { 070 return DEFAULT_FORMAT; 071 } 072 073 /** 074 * @see org.kuali.kfs.module.endow.businessobject.EndowmentAccountingLineParser#getTargetEndowmentAccountingLineFormat() 075 */ 076 public String[] getTargetEndowmentAccountingLineFormat() { 077 return DEFAULT_FORMAT; 078 } 079 080 /** 081 * @see org.kuali.kfs.module.endow.businessobject.EndowmentAccountingLineParser#getExpectedEndowmentAccountingLineFormatAsString(java.lang.Class) 082 */ 083 public String getExpectedEndowmentAccountingLineFormatAsString(Class<? extends EndowmentAccountingLine> accountingLineClass) { 084 StringBuffer sb = new StringBuffer(); 085 boolean first = true; 086 for (String attributeName : chooseFormat(accountingLineClass)) { 087 if (!first) { 088 sb.append(","); 089 } 090 else { 091 first = false; 092 } 093 sb.append(retrieveAttributeLabel(accountingLineClass, attributeName)); 094 } 095 return sb.toString(); 096 } 097 098 /** 099 * @see org.kuali.kfs.module.endow.businessobject.EndowmentAccountingLineParser#parseSourceEndowmentAccountingLine(org.kuali.kfs.module.endow.document.EndowmentAccountingLinesDocument, 100 * java.lang.String) 101 */ 102 public SourceEndowmentAccountingLine parseSourceEndowmentAccountingLine(EndowmentAccountingLinesDocument transactionalDocument, String sourceAccountingLineString) { 103 Class sourceAccountingLineClass = getSourceEndowmentAccountingLineClass(transactionalDocument); 104 SourceEndowmentAccountingLine sourceAccountingLine = (SourceEndowmentAccountingLine) populateAccountingLine(transactionalDocument, sourceAccountingLineClass, sourceAccountingLineString, parseAccountingLine(sourceAccountingLineClass, sourceAccountingLineString), transactionalDocument.getNextSourceLineNumber()); 105 return sourceAccountingLine; 106 } 107 108 /** 109 * Given a document, determines what class the source lines of that document uses 110 * 111 * @param accountingDocument the document to find the class of the source lines for 112 * @return the class of the source lines 113 */ 114 protected Class getSourceEndowmentAccountingLineClass(final EndowmentAccountingLinesDocument accountingDocument) { 115 return accountingDocument.getSourceAccountingLineClass(); 116 } 117 118 /** 119 * @see org.kuali.kfs.module.endow.businessobject.EndowmentAccountingLineParser#parseTargetEndowmentAccountingLine(org.kuali.kfs.module.endow.document.EndowmentAccountingLinesDocument, 120 * java.lang.String) 121 */ 122 public TargetEndowmentAccountingLine parseTargetEndowmentAccountingLine(EndowmentAccountingLinesDocument transactionalDocument, String targetAccountingLineString) { 123 Class targetAccountingLineClass = getTargetEndowmentAccountingLineClass(transactionalDocument); 124 TargetEndowmentAccountingLine targetAccountingLine = (TargetEndowmentAccountingLine) populateAccountingLine(transactionalDocument, targetAccountingLineClass, targetAccountingLineString, parseAccountingLine(targetAccountingLineClass, targetAccountingLineString), transactionalDocument.getNextTargetLineNumber()); 125 return targetAccountingLine; 126 } 127 128 /** 129 * Given a document, determines what class that document uses for target accounting lines 130 * 131 * @param accountingDocument the document to determine the target accounting line class for 132 * @return the class of the target lines for the given document 133 */ 134 protected Class getTargetEndowmentAccountingLineClass(final EndowmentAccountingLinesDocument accountingDocument) { 135 return accountingDocument.getTargetAccountingLineClass(); 136 } 137 138 /** 139 * Populates a source/target line with values 140 * 141 * @param transactionalDocument 142 * @param accountingLineClass 143 * @param accountingLineAsString 144 * @param attributeValueMap 145 * @param sequenceNumber 146 * @return AccountingLine 147 */ 148 protected EndowmentAccountingLine populateAccountingLine(EndowmentAccountingLinesDocument transactionalDocument, Class<? extends EndowmentAccountingLine> accountingLineClass, String accountingLineAsString, Map<String, String> attributeValueMap, Integer sequenceNumber) { 149 150 putCommonAttributesInMap(attributeValueMap, transactionalDocument, sequenceNumber); 151 152 // create line and populate fields 153 EndowmentAccountingLine accountingLine; 154 155 try { 156 accountingLine = (EndowmentAccountingLine) accountingLineClass.newInstance(); 157 158 // perform custom line population 159 if (SourceEndowmentAccountingLine.class.isAssignableFrom(accountingLineClass)) { 160 performCustomSourceAccountingLinePopulation(attributeValueMap, (SourceEndowmentAccountingLine) accountingLine, accountingLineAsString); 161 } 162 else if (TargetEndowmentAccountingLine.class.isAssignableFrom(accountingLineClass)) { 163 performCustomTargetAccountingLinePopulation(attributeValueMap, (TargetEndowmentAccountingLine) accountingLine, accountingLineAsString); 164 } 165 else { 166 throw new IllegalArgumentException("invalid (unknown) endowment accounting line type: " + accountingLineClass); 167 } 168 169 for (Entry<String, String> entry : attributeValueMap.entrySet()) { 170 try { 171 try { 172 Class entryType = ObjectUtils.easyGetPropertyType(accountingLine, entry.getKey()); 173 if (String.class.isAssignableFrom(entryType)) { 174 entry.setValue(entry.getValue().toUpperCase()); 175 } 176 ObjectUtils.setObjectProperty(accountingLine, entry.getKey(), entryType, entry.getValue()); 177 } 178 catch (IllegalArgumentException e) { 179 throw new InfrastructureException("unable to complete endowment accounting line population.", e); 180 } 181 } 182 catch (FormatException e) { 183 String[] errorParameters = { entry.getValue().toString(), retrieveAttributeLabel(accountingLine.getClass(), entry.getKey()), accountingLineAsString }; 184 GlobalVariables.getMessageMap().putError(EndowConstants.ACCOUNTING_LINE_ERRORS, ERROR_INVALID_PROPERTY_VALUE, entry.getValue().toString(), entry.getKey(), accountingLineAsString + " : Line Number " + lineNo.toString()); 185 throw new AccountingLineParserException("invalid '" + entry.getKey() + "=" + entry.getValue() + " for " + accountingLineAsString, ERROR_INVALID_PROPERTY_VALUE, errorParameters); 186 } 187 } 188 189 // override chart code if accounts can't cross charts 190 // TODO: check if this is needed 191 // SpringContext.getBean(AccountService.class).populateAccountingLineChartIfNeeded(accountingLine); 192 } 193 catch (SecurityException e) { 194 throw new InfrastructureException("unable to complete endowment accounting line population.", e); 195 } 196 catch (NoSuchMethodException e) { 197 throw new InfrastructureException("unable to complete endowment accounting line population.", e); 198 } 199 catch (IllegalAccessException e) { 200 throw new InfrastructureException("unable to complete endowment accounting line population.", e); 201 } 202 catch (InvocationTargetException e) { 203 throw new InfrastructureException("unable to complete endowment accounting line population.", e); 204 } 205 catch (InstantiationException e) { 206 throw new InfrastructureException("unable to complete endowment accounting line population.", e); 207 } 208 209 // force input to uppercase 210 SpringContext.getBean(BusinessObjectDictionaryService.class).performForceUppercase(accountingLine); 211 accountingLine.refresh(); 212 213 return accountingLine; 214 } 215 216 /** 217 * Places fields common to both source/target endowment accounting lines in the attribute map 218 * 219 * @param attributeValueMap 220 * @param document 221 * @param sequenceNumber 222 */ 223 protected void putCommonAttributesInMap(Map<String, String> attributeValueMap, EndowmentAccountingLinesDocument document, Integer sequenceNumber) { 224 attributeValueMap.put(KFSPropertyConstants.DOCUMENT_NUMBER, document.getDocumentNumber()); 225 attributeValueMap.put(ENDOWMENT_ACCOUNTING_LINE_NBR, sequenceNumber.toString()); 226 } 227 228 /** 229 * Parses the csv line 230 * 231 * @param accountingLineClass 232 * @param lineToParse 233 * @return Map containing accounting line attribute,value pairs 234 */ 235 protected Map<String, String> parseAccountingLine(Class<? extends EndowmentAccountingLine> accountingLineClass, String lineToParse) { 236 if (StringUtils.isNotBlank(fileName) && !StringUtils.lowerCase(fileName).endsWith(".csv")) { 237 throw new AccountingLineParserException("unsupported file format: " + fileName, ERROR_INVALID_FILE_FORMAT, fileName); 238 } 239 String[] attributes = chooseFormat(accountingLineClass); 240 String[] attributeValues = StringUtils.splitPreserveAllTokens(lineToParse, ","); 241 242 Map<String, String> attributeValueMap = new HashMap<String, String>(); 243 244 for (int i = 0; i < Math.min(attributeValues.length, attributes.length); i++) { 245 attributeValueMap.put(attributes[i], attributeValues[i]); 246 } 247 248 return attributeValueMap; 249 } 250 251 /** 252 * Should be overriden by documents to perform any additional <code>SourceAccountingLine</code> population 253 * 254 * @param attributeValueMap 255 * @param sourceAccountingLine 256 * @param accountingLineAsString 257 */ 258 protected void performCustomSourceAccountingLinePopulation(Map<String, String> attributeValueMap, SourceEndowmentAccountingLine sourceAccountingLine, String accountingLineAsString) { 259 } 260 261 /** 262 * Should be overridden by documents to perform any additional <code>TargetAccountingLine</code> attribute population 263 * 264 * @param attributeValueMap 265 * @param targetAccountingLine 266 * @param accountingLineAsString 267 */ 268 protected void performCustomTargetAccountingLinePopulation(Map<String, String> attributeValueMap, TargetEndowmentAccountingLine targetAccountingLine, String accountingLineAsString) { 269 } 270 271 /** 272 * Calls the appropriate parseAccountingLine method 273 * 274 * @param stream 275 * @param transactionalDocument 276 * @param isSource 277 * @return List 278 */ 279 protected List<EndowmentAccountingLine> importAccountingLines(String fileName, InputStream stream, EndowmentAccountingLinesDocument transactionalDocument, boolean isSource) { 280 List<EndowmentAccountingLine> importedAccountingLines = new ArrayList<EndowmentAccountingLine>(); 281 this.fileName = fileName; 282 BufferedReader br = new BufferedReader(new InputStreamReader(stream)); 283 284 try { 285 String accountingLineAsString = null; 286 lineNo = 0; 287 while ((accountingLineAsString = br.readLine()) != null) { 288 lineNo++; 289 EndowmentAccountingLine accountingLine = null; 290 291 try { 292 if (isSource) { 293 accountingLine = parseSourceEndowmentAccountingLine(transactionalDocument, accountingLineAsString); 294 } 295 else { 296 accountingLine = parseTargetEndowmentAccountingLine(transactionalDocument, accountingLineAsString); 297 } 298 299 importedAccountingLines.add(accountingLine); 300 } 301 catch (AccountingLineParserException e) { 302 GlobalVariables.getMessageMap().putError((isSource ? EndowPropertyConstants.EXISTING_SOURCE_ACCT_LINE_PREFIX : EndowPropertyConstants.EXISTING_TARGET_ACCT_LINE_PREFIX), EndowKeyConstants.ERROR_ENDOW_ACCOUNTING_LINES_DOCUMENT_ACCOUNTING_LINE_IMPORT_GENERAL, new String[] { e.getMessage() }); 303 } 304 } 305 } 306 catch (IOException e) { 307 throw new InfrastructureException("unable to readLine from bufferReader in endowmentAccountingLineParserBase", e); 308 } 309 finally { 310 try { 311 br.close(); 312 } 313 catch (IOException e) { 314 throw new InfrastructureException("unable to close bufferReader in endowmentAccountingLineParserBase", e); 315 } 316 } 317 318 return importedAccountingLines; 319 } 320 321 /** 322 * @see org.kuali.kfs.module.endow.businessobject.EndowmentAccountingLineParser#importSourceEndowmentAccountingLines(java.lang.String, 323 * java.io.InputStream, org.kuali.kfs.module.endow.document.EndowmentAccountingLinesDocument) 324 */ 325 public final List importSourceEndowmentAccountingLines(String fileName, InputStream stream, EndowmentAccountingLinesDocument document) { 326 return importAccountingLines(fileName, stream, document, true); 327 } 328 329 /** 330 * @see org.kuali.kfs.module.endow.businessobject.EndowmentAccountingLineParser#importTargetEndowmentAccountingLines(java.lang.String, 331 * java.io.InputStream, org.kuali.kfs.module.endow.document.EndowmentAccountingLinesDocument) 332 */ 333 public final List importTargetEndowmentAccountingLines(String fileName, InputStream stream, EndowmentAccountingLinesDocument document) { 334 return importAccountingLines(fileName, stream, document, false); 335 } 336 337 338 /** 339 * Retrieves label for given attribute. 340 * 341 * @param clazz 342 * @param attributeName 343 * @return 344 */ 345 protected String retrieveAttributeLabel(Class clazz, String attributeName) { 346 String label = SpringContext.getBean(DataDictionaryService.class).getAttributeLabel(clazz, attributeName); 347 if (StringUtils.isBlank(label)) { 348 label = attributeName; 349 } 350 return label; 351 } 352 353 /** 354 * Gets the accounting line format. 355 * 356 * @param accountingLineClass 357 * @return 358 */ 359 protected String[] chooseFormat(Class<? extends EndowmentAccountingLine> accountingLineClass) { 360 String[] format = null; 361 if (SourceEndowmentAccountingLine.class.isAssignableFrom(accountingLineClass)) { 362 format = getSourceEndowmentAccountingLineFormat(); 363 } 364 else if (TargetEndowmentAccountingLine.class.isAssignableFrom(accountingLineClass)) { 365 format = getTargetEndowmentAccountingLineFormat(); 366 } 367 else { 368 throw new IllegalStateException("unknow endowment accounting line class: " + accountingLineClass); 369 } 370 return format; 371 } 372 373 }