1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.kuali.kfs.module.ld.batch.service.impl;
20
21 import java.util.ArrayList;
22 import java.util.Iterator;
23 import java.util.List;
24
25 import org.apache.commons.lang.StringUtils;
26 import org.kuali.kfs.gl.batch.dataaccess.ReconciliationDao;
27 import org.kuali.kfs.gl.batch.service.impl.ColumnReconciliation;
28 import org.kuali.kfs.gl.batch.service.impl.ReconciliationBlock;
29 import org.kuali.kfs.gl.businessobject.OriginEntryFull;
30 import org.kuali.kfs.gl.exception.LoadException;
31 import org.kuali.kfs.module.ld.batch.service.ReconciliationService;
32 import org.kuali.kfs.module.ld.businessobject.LaborOriginEntry;
33 import org.kuali.kfs.sys.Message;
34 import org.kuali.rice.core.api.util.type.KualiDecimal;
35 import org.kuali.rice.core.api.util.type.TypeUtils;
36 import org.springframework.transaction.annotation.Transactional;
37
38
39
40
41 @Transactional
42 public class ReconciliationServiceImpl implements ReconciliationService {
43 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ReconciliationServiceImpl.class);
44
45 private ReconciliationDao reconciliationDao;
46 private Class<? extends OriginEntryFull> originEntryClass;
47
48
49
50
51
52
53
54
55
56
57
58 protected class JavaAttributeAugmentedColumnReconciliation {
59 protected ColumnReconciliation columnReconciliation;
60 protected List<String> javaAttributeNames;
61
62 protected JavaAttributeAugmentedColumnReconciliation() {
63 columnReconciliation = null;
64 javaAttributeNames = null;
65 }
66
67
68
69
70
71
72 protected ColumnReconciliation getColumnReconciliation() {
73 return columnReconciliation;
74 }
75
76
77
78
79
80
81 protected void setColumnReconciliation(ColumnReconciliation columnReconciliation) {
82 this.columnReconciliation = columnReconciliation;
83 }
84
85
86
87
88
89
90 protected void setJavaAttributeNames(List<String> javaAttributeNames) {
91 this.javaAttributeNames = javaAttributeNames;
92 }
93
94 protected String getJavaAttributeName(int index) {
95 return javaAttributeNames.get(index);
96 }
97
98
99
100
101
102
103 protected int size() {
104 return javaAttributeNames.size();
105 }
106 }
107
108
109
110
111
112
113
114
115
116
117 public void reconcile(Iterator<LaborOriginEntry> entries, ReconciliationBlock reconBlock, List<Message> errorMessages) {
118 List<ColumnReconciliation> columns = reconBlock.getColumns();
119
120 int numEntriesSuccessfullyLoaded = 0;
121
122
123 int numEntriesAttemptedToLoad = 1;
124
125
126 List<JavaAttributeAugmentedColumnReconciliation> javaAttributeNames = resolveJavaAttributeNames(columns);
127 KualiDecimal[] columnSums = createColumnSumsArray(columns.size());
128
129
130
131
132
133
134
135
136
137
138 boolean entriesFullyIterated = false;
139
140
141 boolean loadExceptionEncountered = false;
142
143 while (!entriesFullyIterated) {
144 try {
145 while (entries.hasNext()) {
146 numEntriesAttemptedToLoad++;
147 OriginEntryFull entry = entries.next();
148 for (int c = 0; c < columns.size(); c++) {
149
150 KualiDecimal columnValue = KualiDecimal.ZERO;
151
152 for (int f = 0; f < javaAttributeNames.get(c).size(); f++) {
153 String javaAttributeName = javaAttributeNames.get(c).getJavaAttributeName(f);
154 Object fieldValue = entry.getFieldValue(javaAttributeName);
155
156 if (fieldValue == null) {
157
158 }
159 else {
160 if (TypeUtils.isIntegralClass(fieldValue.getClass()) || TypeUtils.isDecimalClass(fieldValue.getClass())) {
161 KualiDecimal castValue;
162 if (fieldValue instanceof KualiDecimal) {
163 castValue = (KualiDecimal) fieldValue;
164 }
165 else {
166 castValue = new KualiDecimal(fieldValue.toString());
167 }
168 columnValue = columnValue.add(castValue);
169 }
170 else {
171 throw new LoadException("The value for " + columns.get(c).getTokenizedFieldNames()[f] + " is not a numeric value.");
172 }
173 }
174 }
175 columnSums[c] = columnSums[c].add(columnValue);
176 }
177 numEntriesSuccessfullyLoaded++;
178 }
179 }
180 catch (LoadException e) {
181 loadExceptionEncountered = true;
182 LOG.error("Line " + numEntriesAttemptedToLoad + " parse error: " + e.getMessage(), e);
183 Message newMessage = new Message("Line " + numEntriesAttemptedToLoad + " parse error: " + e.getMessage(), Message.TYPE_FATAL);
184 errorMessages.add(newMessage);
185
186 numEntriesAttemptedToLoad++;
187 continue;
188 }
189 catch (Exception e) {
190
191
192
193 LOG.error("Error encountered trying to iterate through origin entry iterator", e);
194
195 Message newMessage = new Message(e.getMessage(), Message.TYPE_FATAL);
196 errorMessages.add(newMessage);
197
198 break;
199 }
200 entriesFullyIterated = true;
201 }
202
203 if (entriesFullyIterated) {
204 if (loadExceptionEncountered) {
205
206 LOG.error("Reconciliation check failed because some origin entry lines could not be parsed.");
207 Message newMessage = new Message("Reconciliation check failed because some origin entry lines could not be parsed.", Message.TYPE_FATAL);
208 errorMessages.add(newMessage);
209 }
210 else {
211
212 if (numEntriesSuccessfullyLoaded != reconBlock.getRowCount()) {
213 Message newMessage = generateRowCountMismatchMessage(reconBlock, numEntriesSuccessfullyLoaded);
214 errorMessages.add(newMessage);
215 }
216
217
218
219
220
221 for (int i = 0; i < columns.size(); i++) {
222 if (!columnSums[i].equals(columns.get(i).getDollarAmount())) {
223 Message newMessage = generateColumnSumErrorMessage(columns.get(i), columnSums[i]);
224 errorMessages.add(newMessage);
225 }
226 }
227 }
228 }
229 }
230
231
232
233
234
235
236
237
238 protected Message generateColumnSumErrorMessage(ColumnReconciliation column, KualiDecimal actualValue) {
239
240
241 StringBuilder buf = new StringBuilder();
242 buf.append("Reconciliation failed for field value(s) \"");
243 buf.append(column.getFieldName());
244 buf.append("\", expected ");
245 buf.append(column.getDollarAmount());
246 buf.append(", found value ");
247 buf.append(actualValue);
248 buf.append(".");
249
250 Message newMessage = new Message(buf.toString(), Message.TYPE_FATAL);
251 return newMessage;
252 }
253
254
255
256
257
258
259
260
261 protected Message generateRowCountMismatchMessage(ReconciliationBlock block, int actualRowCount) {
262
263
264 StringBuilder buf = new StringBuilder();
265 buf.append("Reconciliation failed because an incorrect number of origin entry rows were successfully parsed. Expected ");
266 buf.append(block.getRowCount());
267 buf.append(" row(s), parsed ");
268 buf.append(actualRowCount);
269 buf.append(" row(s).");
270
271 Message newMessage = new Message(buf.toString(), Message.TYPE_FATAL);
272 return newMessage;
273 }
274
275
276
277
278
279
280
281
282
283
284
285 protected boolean performSanityChecks(List<ColumnReconciliation> columns, List<JavaAttributeAugmentedColumnReconciliation> javaAttributeNames, KualiDecimal[] columnSums, List<Message> errorMessages) {
286 boolean success = true;
287
288 if (javaAttributeNames.size() != columnSums.length || javaAttributeNames.size() != columns.size()) {
289
290 errorMessages.add(new Message("Reconciliation error: Sizes of lists do not match", Message.TYPE_FATAL));
291 success = false;
292 }
293 for (int i = 0; i < columns.size(); i++) {
294 if (columns.get(i).getTokenizedFieldNames().length != javaAttributeNames.get(i).size()) {
295 errorMessages.add(new Message("Reconciliation error: Error tokenizing column elements. The number of database fields and java fields do not match.", Message.TYPE_FATAL));
296 success = false;
297 }
298 for (int fieldIdx = 0; fieldIdx < javaAttributeNames.get(i).size(); i++) {
299 if (StringUtils.isBlank(javaAttributeNames.get(i).getJavaAttributeName(fieldIdx))) {
300 errorMessages.add(new Message("Reconciliation error: javaAttributeName is blank for DB column: " + columns.get(i).getTokenizedFieldNames()[fieldIdx], Message.TYPE_FATAL));
301 success = false;
302 }
303 }
304 }
305 return success;
306 }
307
308
309
310
311
312
313
314 protected KualiDecimal[] createColumnSumsArray(int size) {
315 KualiDecimal[] array = new KualiDecimal[size];
316 for (int i = 0; i < array.length; i++) {
317 array[i] = KualiDecimal.ZERO;
318 }
319 return array;
320 }
321
322
323
324
325
326
327
328
329
330 protected List<JavaAttributeAugmentedColumnReconciliation> resolveJavaAttributeNames(List<ColumnReconciliation> columns) {
331 List<JavaAttributeAugmentedColumnReconciliation> attributes = new ArrayList<JavaAttributeAugmentedColumnReconciliation>();
332 for (ColumnReconciliation column : columns) {
333 JavaAttributeAugmentedColumnReconciliation c = new JavaAttributeAugmentedColumnReconciliation();
334 c.setColumnReconciliation(column);
335 c.setJavaAttributeNames(reconciliationDao.convertDBColumnNamesToJavaName(getOriginEntryClass(), column.getTokenizedFieldNames(), true));
336 attributes.add(c);
337 }
338 return attributes;
339 }
340
341
342
343
344
345
346 protected ReconciliationDao getReconciliationDao() {
347 return reconciliationDao;
348 }
349
350
351
352
353
354
355 public void setReconciliationDao(ReconciliationDao reconciliationDao) {
356 this.reconciliationDao = reconciliationDao;
357 }
358
359
360
361
362
363
364 protected Class<? extends OriginEntryFull> getOriginEntryClass() {
365 return originEntryClass;
366 }
367
368
369
370
371
372
373 public void setOriginEntryClass(Class<? extends OriginEntryFull> originEntryClass) {
374 this.originEntryClass = originEntryClass;
375 }
376 }