View Javadoc
1   /*
2    * Copyright 2007 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.gl.batch.service.impl;
17  
18  import java.io.BufferedReader;
19  import java.io.IOException;
20  import java.io.Reader;
21  import java.util.StringTokenizer;
22  
23  import org.apache.commons.lang.StringUtils;
24  import org.kuali.ole.gl.GeneralLedgerConstants;
25  import org.kuali.ole.gl.batch.service.ReconciliationParserService;
26  import org.kuali.rice.core.api.util.type.KualiDecimal;
27  
28  /**
29   * Format of the reconciliation file:
30   * 
31   * <pre>
32   *  C tableid rowcount ; 
33   *  S field1 dollaramount ; 
34   *  S field2 dollaramount ; 
35   *  E checksum ;
36   * </pre>
37   * 
38   * The character '#' and everything following it on that line is ignored. Whitespace characters are tab and space.<br>
39   * <br>
40   * A 'C' 'S' or 'E' must be the first character on a line unless the line is entirely whitespace or a comment. The case of these
41   * three codes is not significant.<br>
42   * <br>
43   * Semi-colons are required before any possible comments on C S or E lines. Any amount of whitespace delimits the elements of C, S
44   * and E lines. (If an S line contains field1+field2 for the field element, take care NOT to put any whitespace between the
45   * 'field1', the '+' and the 'field2'.) <br>
46   * <br>
47   * Tableid is an arbitrary identifier for the record<br>
48   * <br>
49   * Rowcount must be a non-negative integer. Fieldn is the technical fieldname(s) in the target database. Case *is* significant,
50   * since this must match the database name(s) exactly.<br>
51   * <br>
52   * Dollaramount may be negative; the check is significant to 4 decimal places.<br>
53   * <br>
54   * The checksum on line E is the number of C and S lines. A C line and a terminating E line are mandatory; S lines are optional.<br>
55   * <br>
56   * There may be more than one C-E block per metadata file.<br>
57   * <br>
58   * In general, this implementation of the parser attempts to be error tolerant. It primarily looks at the C-E block that is being
59   * looked for, by ignoring all other C-E blocks. A C-E block is "looked for" when the table ID of the C line is passed in as a
60   * parameter of {@link #parseReconciliationData(Reader, String)}. However, if the C lines of any blocks before the looked for block
61   * are incorrect, then it is likely to cause undesired behavior.
62   */
63  public class ReconciliationParserServiceImpl implements ReconciliationParserService {
64      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(FileEnterpriseFeederHelperServiceImpl.class);
65      private enum ParseState {
66          INIT, TABLE_DEF, COLUMN_DEF, CHECKSUM_DEF;
67      };
68  
69  
70  
71      /**
72       * Parses a reconciliation file
73       * 
74       * @param reader a source of data from which to build a reconciliation
75       * @param tableId defined within the reconciliation file; defines which block to parse
76       * @return parsed reconciliation data
77       * @throws IOException thrown if the file cannot be written for any reason
78       * @see org.kuali.ole.gl.batch.service.ReconciliationParserService#parseReconciliatioData(java.io.Reader)
79       */
80      public ReconciliationBlock parseReconciliationBlock(Reader reader, String tableId) throws IOException {
81          BufferedReader bufReader;
82          if (reader instanceof BufferedReader) {
83              bufReader = (BufferedReader) reader;
84          }
85          else {
86              bufReader = new BufferedReader(reader);
87          }
88  
89          // this variable is not null when we find the C line corresponding to the param table ID
90          ReconciliationBlock reconciliationBlock = null;
91  
92          int linesInBlock = 0;
93  
94          // find the first "C" line of the C-E block by matching the table Id
95          String line = bufReader.readLine();
96          while (line != null && reconciliationBlock == null) {
97              line = stripCommentsAndTrim(line);
98              if (StringUtils.isBlank(line)) {
99                  line = bufReader.readLine();
100                 continue;
101             }
102 
103             StringTokenizer strTok = new StringTokenizer(line);
104             if (!strTok.hasMoreTokens()) {
105                 LOG.error("Cannot find TABLE_DEF_STRING");
106                 throw new RuntimeException();
107             }
108             String command = strTok.nextToken();
109             if (command.equalsIgnoreCase(GeneralLedgerConstants.Reconciliation.TABLE_DEF_STRING)) {
110                 if (!strTok.hasMoreTokens()) {
111                     LOG.error("Cannot find TABLE_DEF_STRING");
112                     throw new RuntimeException();
113                 }
114                 String parsedTableId = strTok.nextToken();
115                 if (parsedTableId.equalsIgnoreCase(tableId)) {
116                     if (!strTok.hasMoreTokens()) {
117                         LOG.error("Cannot find Parsed Table Id");
118                         throw new RuntimeException();
119                     }
120                     String parsedRowCountStr = StringUtils.removeEnd(strTok.nextToken(), ";");
121                     parsedRowCountStr = StringUtils.removeEnd(parsedRowCountStr, ".00");
122                     int parsedRowCount = Integer.parseInt(parsedRowCountStr);
123 
124                     reconciliationBlock = new ReconciliationBlock();
125                     reconciliationBlock.setTableId(parsedTableId);
126                     reconciliationBlock.setRowCount(parsedRowCount);
127 
128                     linesInBlock++;
129 
130                     break;
131                 }
132             }
133             line = bufReader.readLine();
134         }
135 
136         if (reconciliationBlock == null) {
137             return null;
138         }
139 
140         boolean endBlockLineEncountered = false;
141         line = bufReader.readLine();
142         while (line != null && !endBlockLineEncountered) {
143             line = stripCommentsAndTrim(line);
144             if (StringUtils.isBlank(line)) {
145                 continue;
146             }
147 
148             StringTokenizer strTok = new StringTokenizer(line);
149             if (!strTok.hasMoreTokens()) {
150                 LOG.error("Cannot find COLUMN_DEF_STRING");
151                 throw new RuntimeException();
152             }
153 
154             String command = strTok.nextToken();
155             if (command.equalsIgnoreCase(GeneralLedgerConstants.Reconciliation.COLUMN_DEF_STRING)) {
156                 if (!strTok.hasMoreTokens()) {
157                     LOG.error("Cannot find COLUMN_DEF_STRING");
158                     throw new RuntimeException();
159                 }
160                 String fieldName = strTok.nextToken();
161                 if (!strTok.hasMoreTokens()) {
162                     LOG.error("Cannot find COLUMN_DEF_STRING");
163                     throw new RuntimeException();
164                 }
165                 String columnAmountStr = strTok.nextToken();
166                 columnAmountStr = StringUtils.removeEnd(columnAmountStr, ";");
167 
168                 KualiDecimal columnAmount = new KualiDecimal(columnAmountStr);
169                 ColumnReconciliation columnReconciliation = new ColumnReconciliation();
170                 columnReconciliation.setFieldName(fieldName);
171                 columnReconciliation.setDollarAmount(columnAmount);
172                 reconciliationBlock.addColumn(columnReconciliation);
173                 linesInBlock++;
174             }
175             else if (command.equalsIgnoreCase(GeneralLedgerConstants.Reconciliation.CHECKSUM_DEF_STRING)) {
176                 if (!strTok.hasMoreTokens()) {
177                     LOG.error("Cannot find CHECKSUM_DEF_STRING");
178                     throw new RuntimeException();
179                 }
180                 String checksumStr = strTok.nextToken();
181                 checksumStr = StringUtils.removeEnd(checksumStr, ";");
182 
183                 int checksum = Integer.parseInt(checksumStr);
184 
185                 if (checksum != linesInBlock) {
186                     LOG.error("Check Sum String is not same as Lines in Block");
187                     throw new RuntimeException();
188                 }
189                 break;
190             }
191             else {
192                 LOG.error("Cannot find any fields");
193                 throw new RuntimeException();
194             }
195 
196             line = bufReader.readLine();
197         }
198         return reconciliationBlock;
199     }
200 
201     /**
202      * Removes comments and trims whitespace
203      * 
204      * @param line the line
205      * @return stripped and trimmed line
206      */
207     protected String stripCommentsAndTrim(String line) {
208         int commentIndex = line.indexOf(GeneralLedgerConstants.Reconciliation.COMMENT_STRING);
209         if (commentIndex > -1) {
210             // chop off comments
211             line = line.substring(0, commentIndex);
212         }
213 
214         line = line.trim();
215         return line;
216     }
217 }