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 }