1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.kuali.ole.sys.businessobject;
18
19 import static org.kuali.ole.sys.OLEKeyConstants.AccountingLineParser.ERROR_INVALID_FILE_FORMAT;
20 import static org.kuali.ole.sys.OLEKeyConstants.AccountingLineParser.ERROR_INVALID_PROPERTY_VALUE;
21 import static org.kuali.ole.sys.OLEPropertyConstants.ACCOUNT_NUMBER;
22 import static org.kuali.ole.sys.OLEPropertyConstants.AMOUNT;
23 import static org.kuali.ole.sys.OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE;
24 import static org.kuali.ole.sys.OLEPropertyConstants.FINANCIAL_OBJECT_CODE;
25 import static org.kuali.ole.sys.OLEPropertyConstants.FINANCIAL_SUB_OBJECT_CODE;
26 import static org.kuali.ole.sys.OLEPropertyConstants.ORGANIZATION_REFERENCE_ID;
27 import static org.kuali.ole.sys.OLEPropertyConstants.OVERRIDE_CODE;
28 import static org.kuali.ole.sys.OLEPropertyConstants.POSTING_YEAR;
29 import static org.kuali.ole.sys.OLEPropertyConstants.PROJECT_CODE;
30 import static org.kuali.ole.sys.OLEPropertyConstants.SEQUENCE_NUMBER;
31 import static org.kuali.ole.sys.OLEPropertyConstants.SUB_ACCOUNT_NUMBER;
32
33 import java.io.BufferedReader;
34 import java.io.IOException;
35 import java.io.InputStream;
36 import java.io.InputStreamReader;
37 import java.lang.reflect.InvocationTargetException;
38 import java.util.ArrayList;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Map.Entry;
43
44 import org.apache.commons.lang.StringUtils;
45 import org.kuali.ole.coa.service.AccountService;
46 import org.kuali.ole.sys.OLEConstants;
47 import org.kuali.ole.sys.OLEKeyConstants;
48 import org.kuali.ole.sys.OLEPropertyConstants;
49 import org.kuali.ole.sys.context.SpringContext;
50 import org.kuali.ole.sys.document.AccountingDocument;
51 import org.kuali.ole.sys.exception.AccountingLineParserException;
52 import org.kuali.rice.core.web.format.FormatException;
53 import org.kuali.rice.kns.service.BusinessObjectDictionaryService;
54 import org.kuali.rice.kns.service.DataDictionaryService;
55 import org.kuali.rice.krad.util.GlobalVariables;
56 import org.kuali.rice.krad.util.ObjectUtils;
57
58
59
60
61 public class AccountingLineParserBase implements AccountingLineParser {
62 protected static final String[] DEFAULT_FORMAT = { CHART_OF_ACCOUNTS_CODE, ACCOUNT_NUMBER, SUB_ACCOUNT_NUMBER, FINANCIAL_OBJECT_CODE, FINANCIAL_SUB_OBJECT_CODE, PROJECT_CODE, ORGANIZATION_REFERENCE_ID, AMOUNT };
63 private String fileName;
64 private Integer lineNo = 0;
65
66
67
68
69 public String[] getSourceAccountingLineFormat() {
70 return removeChartFromFormatIfNeeded(DEFAULT_FORMAT);
71 }
72
73
74
75
76 public String[] getTargetAccountingLineFormat() {
77 return removeChartFromFormatIfNeeded(DEFAULT_FORMAT);
78 }
79
80
81
82
83
84 public String[] removeChartFromFormatIfNeeded(String[] format) {
85 if (SpringContext.getBean(AccountService.class).accountsCanCrossCharts()) {
86 return format;
87 }
88
89
90 String[] formatNoChart = new String[format.length-1];
91 int idx = 0;
92 for (int i=0; i<format.length; i++) {
93 if (format[i].equals(CHART_OF_ACCOUNTS_CODE))
94 continue;
95 else {
96 formatNoChart[idx] = format[i];
97 idx++;
98 }
99 }
100 return formatNoChart;
101 }
102
103
104
105
106 public String getExpectedAccountingLineFormatAsString(Class<? extends AccountingLine> accountingLineClass) {
107 StringBuffer sb = new StringBuffer();
108 boolean first = true;
109 for (String attributeName : chooseFormat(accountingLineClass)) {
110 if (!first) {
111 sb.append(",");
112 }
113 else {
114 first = false;
115 }
116 sb.append(retrieveAttributeLabel(accountingLineClass, attributeName));
117 }
118 return sb.toString();
119 }
120
121
122
123
124
125 public SourceAccountingLine parseSourceAccountingLine(AccountingDocument transactionalDocument, String sourceAccountingLineString) {
126 Class sourceAccountingLineClass = getSourceAccountingLineClass(transactionalDocument);
127 SourceAccountingLine sourceAccountingLine = (SourceAccountingLine) populateAccountingLine(transactionalDocument, sourceAccountingLineClass, sourceAccountingLineString, parseAccountingLine(sourceAccountingLineClass, sourceAccountingLineString), transactionalDocument.getNextSourceLineNumber());
128 return sourceAccountingLine;
129 }
130
131
132
133
134
135
136 protected Class getSourceAccountingLineClass(final AccountingDocument accountingDocument) {
137 return accountingDocument.getSourceAccountingLineClass();
138 }
139
140
141
142
143
144 public TargetAccountingLine parseTargetAccountingLine(AccountingDocument transactionalDocument, String targetAccountingLineString) {
145 Class targetAccountingLineClass = getTargetAccountingLineClass(transactionalDocument);
146 TargetAccountingLine targetAccountingLine = (TargetAccountingLine) populateAccountingLine(transactionalDocument, targetAccountingLineClass, targetAccountingLineString, parseAccountingLine(targetAccountingLineClass, targetAccountingLineString), transactionalDocument.getNextTargetLineNumber());
147 return targetAccountingLine;
148 }
149
150
151
152
153
154
155 protected Class getTargetAccountingLineClass(final AccountingDocument accountingDocument) {
156 return accountingDocument.getTargetAccountingLineClass();
157 }
158
159
160
161
162
163
164
165
166
167
168
169 protected AccountingLine populateAccountingLine(AccountingDocument transactionalDocument, Class<? extends AccountingLine> accountingLineClass, String accountingLineAsString, Map<String, String> attributeValueMap, Integer sequenceNumber) {
170
171 putCommonAttributesInMap(attributeValueMap, transactionalDocument, sequenceNumber);
172
173
174 AccountingLine accountingLine;
175
176 try {
177 accountingLine = (AccountingLine) accountingLineClass.newInstance();
178
179
180 if (SourceAccountingLine.class.isAssignableFrom(accountingLineClass)) {
181 performCustomSourceAccountingLinePopulation(attributeValueMap, (SourceAccountingLine) accountingLine, accountingLineAsString);
182 }
183 else if (TargetAccountingLine.class.isAssignableFrom(accountingLineClass)) {
184 performCustomTargetAccountingLinePopulation(attributeValueMap, (TargetAccountingLine) accountingLine, accountingLineAsString);
185 }
186 else {
187 throw new IllegalArgumentException("invalid (unknown) accounting line type: " + accountingLineClass);
188 }
189
190 for (Entry<String, String> entry : attributeValueMap.entrySet()) {
191 try {
192 try {
193 Class entryType = ObjectUtils.easyGetPropertyType(accountingLine, entry.getKey());
194 if (String.class.isAssignableFrom(entryType)) {
195 entry.setValue(entry.getValue().toUpperCase());
196 }
197 ObjectUtils.setObjectProperty(accountingLine, entry.getKey(), entryType, entry.getValue());
198 }
199 catch (IllegalArgumentException e) {
200 throw new IllegalArgumentException("unable to complete accounting line population.", e);
201 }
202 }
203 catch (FormatException e) {
204 String[] errorParameters = { entry.getValue().toString(), retrieveAttributeLabel(accountingLine.getClass(), entry.getKey()), accountingLineAsString };
205
206 GlobalVariables.getMessageMap().putError(OLEConstants.ACCOUNTING_LINE_ERRORS, ERROR_INVALID_PROPERTY_VALUE, entry.getValue().toString(), entry.getKey(), accountingLineAsString + " : Line Number " + lineNo.toString());
207 throw new AccountingLineParserException("invalid '" + entry.getKey() + "=" + entry.getValue() + "for " + accountingLineAsString, ERROR_INVALID_PROPERTY_VALUE, errorParameters);
208 }
209 }
210
211
212 SpringContext.getBean(AccountService.class).populateAccountingLineChartIfNeeded(accountingLine);
213 }
214 catch (SecurityException e) {
215 throw new IllegalArgumentException("unable to complete accounting line population.", e);
216 }
217 catch (NoSuchMethodException e) {
218 throw new IllegalArgumentException("unable to complete accounting line population.", e);
219 }
220 catch (IllegalAccessException e) {
221 throw new IllegalArgumentException("unable to complete accounting line population.", e);
222 }
223 catch (InvocationTargetException e) {
224 throw new IllegalArgumentException("unable to complete accounting line population.", e);
225 }
226 catch (InstantiationException e) {
227 throw new IllegalArgumentException("unable to complete accounting line population.", e);
228 }
229
230
231 SpringContext.getBean(BusinessObjectDictionaryService.class).performForceUppercase(accountingLine);
232 accountingLine.refresh();
233
234 return accountingLine;
235 }
236
237
238
239
240
241
242
243
244 protected void putCommonAttributesInMap(Map<String, String> attributeValueMap, AccountingDocument document, Integer sequenceNumber) {
245 attributeValueMap.put(OLEPropertyConstants.DOCUMENT_NUMBER, document.getDocumentNumber());
246 attributeValueMap.put(POSTING_YEAR, document.getPostingYear().toString());
247 attributeValueMap.put(SEQUENCE_NUMBER, sequenceNumber.toString());
248 }
249
250
251
252
253
254
255
256
257 protected Map<String, String> parseAccountingLine(Class<? extends AccountingLine> accountingLineClass, String lineToParse) {
258 if (StringUtils.isNotBlank(fileName) && !StringUtils.lowerCase(fileName).endsWith(".csv")) {
259 throw new AccountingLineParserException("unsupported file format: " + fileName, ERROR_INVALID_FILE_FORMAT, fileName);
260 }
261 String[] attributes = chooseFormat(accountingLineClass);
262 String[] attributeValues = StringUtils.splitPreserveAllTokens(lineToParse, ",");
263
264 Map<String, String> attributeValueMap = new HashMap<String, String>();
265
266 for (int i = 0; i < Math.min(attributeValues.length, attributes.length); i++) {
267 attributeValueMap.put(attributes[i], attributeValues[i]);
268 }
269
270 return attributeValueMap;
271 }
272
273
274
275
276
277
278
279
280 protected void performCustomSourceAccountingLinePopulation(Map<String, String> attributeValueMap, SourceAccountingLine sourceAccountingLine, String accountingLineAsString) {
281 }
282
283
284
285
286
287
288
289
290 protected void performCustomTargetAccountingLinePopulation(Map<String, String> attributeValueMap, TargetAccountingLine targetAccountingLine, String accountingLineAsString) {
291 }
292
293
294
295
296
297
298
299
300
301 protected List<AccountingLine> importAccountingLines(String fileName, InputStream stream, AccountingDocument transactionalDocument, boolean isSource) {
302 List<AccountingLine> importedAccountingLines = new ArrayList<AccountingLine>();
303 this.fileName = fileName;
304 BufferedReader br = new BufferedReader(new InputStreamReader(stream));
305
306 try {
307 String accountingLineAsString = null;
308 lineNo = 0;
309 while ((accountingLineAsString = br.readLine()) != null) {
310 lineNo++;
311
312 if (StringUtils.isBlank(StringUtils.remove(StringUtils.deleteWhitespace(accountingLineAsString),OLEConstants.COMMA))) {
313 continue;
314 }
315
316 AccountingLine accountingLine = null;
317
318 try {
319 if (isSource) {
320 accountingLine = parseSourceAccountingLine(transactionalDocument, accountingLineAsString);
321 }
322 else {
323 accountingLine = parseTargetAccountingLine(transactionalDocument, accountingLineAsString);
324 }
325
326 validateImportedAccountingLine(accountingLine, accountingLineAsString);
327 importedAccountingLines.add(accountingLine);
328 }
329 catch (AccountingLineParserException e) {
330 GlobalVariables.getMessageMap().putError((isSource ? "sourceAccountingLines" : "targetAccountingLines"), OLEKeyConstants.ERROR_ACCOUNTING_DOCUMENT_ACCOUNTING_LINE_IMPORT_GENERAL, new String[] { e.getMessage() });
331 }
332 }
333 }
334 catch (IOException e) {
335 throw new IllegalArgumentException("unable to readLine from bufferReader in accountingLineParserBase", e);
336 }
337 finally {
338 try {
339 br.close();
340 }
341 catch (IOException e) {
342 throw new IllegalArgumentException("unable to close bufferReader in accountingLineParserBase", e);
343 }
344 }
345
346 return importedAccountingLines;
347 }
348
349
350
351
352
353 public final List importSourceAccountingLines(String fileName, InputStream stream, AccountingDocument document) {
354 return importAccountingLines(fileName, stream, document, true);
355 }
356
357
358
359
360
361 public final List importTargetAccountingLines(String fileName, InputStream stream, AccountingDocument document) {
362 return importAccountingLines(fileName, stream, document, false);
363 }
364
365
366
367
368
369
370
371
372 protected void validateImportedAccountingLine(AccountingLine line, String accountingLineAsString) throws AccountingLineParserException {
373
374
375
376 String overrideCode = line.getOverrideCode();
377 if (!AccountingLineOverride.isValidCode(overrideCode)) {
378 String[] errorParameters = { overrideCode, retrieveAttributeLabel(line.getClass(), OVERRIDE_CODE), accountingLineAsString };
379 throw new AccountingLineParserException("invalid overrride code '" + overrideCode + "' for:" + accountingLineAsString, ERROR_INVALID_PROPERTY_VALUE, errorParameters);
380 }
381 }
382
383 protected String retrieveAttributeLabel(Class clazz, String attributeName) {
384 String label = SpringContext.getBean(DataDictionaryService.class).getAttributeLabel(clazz, attributeName);
385 if (StringUtils.isBlank(label)) {
386 label = attributeName;
387 }
388 return label;
389 }
390
391 protected String[] chooseFormat(Class<? extends AccountingLine> accountingLineClass) {
392 String[] format = null;
393 if (SourceAccountingLine.class.isAssignableFrom(accountingLineClass)) {
394 format = getSourceAccountingLineFormat();
395 }
396 else if (TargetAccountingLine.class.isAssignableFrom(accountingLineClass)) {
397 format = getTargetAccountingLineFormat();
398 }
399 else {
400 throw new IllegalStateException("unknow accounting line class: " + accountingLineClass);
401 }
402 return format;
403 }
404 }