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.util;
017
018 import static org.kuali.kfs.module.endow.EndowConstants.TRANSACTION_LINE_ERRORS;
019 import static org.kuali.kfs.module.endow.EndowPropertyConstants.KEMID;
020 import static org.kuali.kfs.module.endow.EndowPropertyConstants.TRANSACTION_LINE_DESCRIPTION;
021 import static org.kuali.kfs.module.endow.EndowPropertyConstants.TRANSACTION_LINE_ENDOWMENT_TRANSACTION_CODE;
022 import static org.kuali.kfs.module.endow.EndowPropertyConstants.TRANSACTION_LINE_IP_INDICATOR_CODE;
023 import static org.kuali.kfs.module.endow.EndowPropertyConstants.TRANSACTION_LINE_TRANSACTION_UNITS;
024 import static org.kuali.kfs.module.endow.EndowPropertyConstants.TRANSACTION_LINE_TRANSACTION_AMOUNT;
025 import static org.kuali.kfs.module.endow.EndowPropertyConstants.TRANSACTION_LINE_TRANSACTION_UNIT_ADJUSTMENT_AMOUNT;
026
027 import java.io.BufferedReader;
028 import java.io.IOException;
029 import java.io.InputStream;
030 import java.io.InputStreamReader;
031 import java.lang.reflect.InvocationTargetException;
032 import java.util.ArrayList;
033 import java.util.HashMap;
034 import java.util.List;
035 import java.util.Map;
036 import java.util.Map.Entry;
037
038 import org.apache.commons.lang.StringUtils;
039 import org.apache.struts.upload.FormFile;
040 import org.kuali.kfs.module.endow.EndowKeyConstants;
041 import org.kuali.kfs.module.endow.EndowKeyConstants.EndowmentTransactionDocumentConstants;
042 import org.kuali.kfs.module.endow.businessobject.EndowmentTransactionLine;
043 import org.kuali.kfs.module.endow.businessobject.EndowmentTransactionLineBase;
044 import org.kuali.kfs.module.endow.exception.LineParserException;
045 import org.kuali.kfs.sys.KFSKeyConstants;
046 import org.kuali.kfs.sys.context.SpringContext;
047 import org.kuali.rice.kns.exception.InfrastructureException;
048 import org.kuali.rice.kns.service.DataDictionaryService;
049 import org.kuali.rice.kns.util.GlobalVariables;
050 import org.kuali.rice.kns.util.ObjectUtils;
051 import org.kuali.rice.kns.web.format.FormatException;
052
053 public class LineParserBase implements LineParser {
054
055 /**
056 * The default format defines the expected line property names and their order in the import file. Please update this if the
057 * import file format changes (i.e. adding/deleting line properties, changing their order).
058 */
059 protected static final String[] DEFAULT_LINE_FORMAT = { KEMID, TRANSACTION_LINE_ENDOWMENT_TRANSACTION_CODE, TRANSACTION_LINE_DESCRIPTION, TRANSACTION_LINE_IP_INDICATOR_CODE, TRANSACTION_LINE_TRANSACTION_UNITS, TRANSACTION_LINE_TRANSACTION_AMOUNT, TRANSACTION_LINE_TRANSACTION_UNIT_ADJUSTMENT_AMOUNT };
060
061 private Integer lineNo = 0;
062
063 /**
064 * @see org.kuali.kfs.module.purap.util.ItemParser#getItemFormat()
065 */
066 public String[] getLineFormat() {
067 return DEFAULT_LINE_FORMAT;
068 }
069
070
071 /**
072 * Retrieves the attribute label for the specified attribute.
073 *
074 * @param clazz the class in which the specified attribute is defined
075 * @param attributeName the name of the specified attribute
076 * @return the attribute label for the specified attribute
077 */
078 protected String getAttributeLabel(Class clazz, String attributeName) {
079 String label = SpringContext.getBean(DataDictionaryService.class).getAttributeLabel(clazz, attributeName);
080 if (StringUtils.isBlank(label)) {
081 label = attributeName;
082 }
083 return label;
084 }
085
086 /**
087 * Checks whether the specified Line class is a subclass of EndowmentTransactionLine; throws exceptions if not.
088 *
089 * @param lineClass the specified line class
090 */
091 protected void checkLineClass(Class<? extends EndowmentTransactionLine> lineClass) {
092 if (!EndowmentTransactionLine.class.isAssignableFrom(lineClass))
093 throw new IllegalArgumentException("Unknown Line class: " + lineClass);
094 }
095
096 /**
097 * Checks whether the specified line import file is not null and of a valid format; throws exceptions if conditions not
098 * satisfied.
099 *
100 * @param lineClass the specified line import file
101 */
102 protected void checkLineFile(FormFile lineFile) {
103 if (lineFile == null)
104 throw new LineParserException("Invalid (null) Line import file", KFSKeyConstants.ERROR_UPLOADFILE_NULL);
105
106 if (lineFile.getFileSize() == 0)
107 throw new LineParserException("Invalid (null) Line import file", KFSKeyConstants.ERROR_UPLOADFILE_NULL);
108
109 String fileName = lineFile.getFileName();
110 if (StringUtils.isNotBlank(fileName) && !StringUtils.lowerCase(fileName).endsWith(".csv"))
111 throw new LineParserException("unsupported Line import file format: " + fileName, KFSKeyConstants.ERROR_LINEPARSER_INVALID_FILE_FORMAT, fileName);
112 }
113
114 /**
115 * Parses a line of transactions data from a csv file and retrieves the attributes as key-value string pairs into a map.
116 *
117 * @param line a string read from a line in the line import file
118 * @return a map containing line attribute name-value string pairs
119 */
120 protected Map<String, String> retrieveLineAttributes(String line, Class<? extends EndowmentTransactionLine> lineClass) {
121 String[] attributeNames = getLineFormat();
122 String[] attributeValues = StringUtils.splitPreserveAllTokens(line, ',');
123
124 if (attributeNames.length != attributeValues.length) {
125 String[] errorParams = { "" + attributeNames.length, "" + attributeValues.length, "" + lineNo };
126 GlobalVariables.getMessageMap().putError(TRANSACTION_LINE_ERRORS, EndowmentTransactionDocumentConstants.ERROR_LINEPARSER_WRONG_PROPERTY_NUMBER, errorParams);
127 throw new LineParserException("Wrong number of Line properties: " + attributeValues.length + " exist, " + attributeNames.length + " expected (line " + lineNo + ")", EndowmentTransactionDocumentConstants.ERROR_LINEPARSER_WRONG_PROPERTY_NUMBER, errorParams);
128 }
129
130 Map<String, String> lineMap = new HashMap<String, String>();
131 for (int i = 0; i < attributeNames.length; i++) {
132 String attributeName = attributeNames[i];
133 String attributeValue = attributeValues[i];
134 // if the attribute has an forceUpper = true in the data dictionary, convert the value to upper case...
135 if (SpringContext.getBean(DataDictionaryService.class).getAttributeForceUppercase(lineClass, attributeName)) {
136 attributeValue = attributeValue.toUpperCase();
137 }
138
139 lineMap.put(attributeName, attributeValue);
140 }
141 return lineMap;
142 }
143
144 /**
145 * Generates an line instance and populates it with the specified attribute map.
146 *
147 * @param lineMap the specified attribute map from which attributes are populated
148 * @param lineClass the class of which the new line instance shall be created
149 * @return the populated line
150 */
151 protected EndowmentTransactionLine genLineWithRetrievedAttributes(Map<String, String> lineMap, Class<? extends EndowmentTransactionLine> lineClass) {
152 EndowmentTransactionLine line;
153 try {
154 line = lineClass.newInstance();
155 }
156 catch (IllegalAccessException e) {
157 throw new InfrastructureException("Unable to complete line line population.", e);
158 }
159 catch (InstantiationException e) {
160 throw new InfrastructureException("Unable to complete line line population.", e);
161 }
162
163 boolean failed = false;
164 for (Entry<String, String> entry : lineMap.entrySet()) {
165 String key = entry.getKey();
166 String value = entry.getValue();
167 try {
168 try {
169 ObjectUtils.setObjectProperty(line, key, value);
170 }
171 catch (FormatException e) {
172 String[] errorParams = { value, key, "" + lineNo };
173 throw new LineParserException("Invalid property value: " + key + " = " + value + " (line " + lineNo + ")", EndowmentTransactionDocumentConstants.ERROR_LINEPARSER_INVALID_PROPERTY_VALUE, errorParams);
174 }
175 }
176 catch (LineParserException e) {
177 // Continue to parse the rest of the line properties after the current property fails
178 GlobalVariables.getMessageMap().putError(TRANSACTION_LINE_ERRORS, e.getErrorKey(), e.getErrorParameters());
179 failed = true;
180 }
181 catch (IllegalAccessException e) {
182 throw new InfrastructureException("unable to complete line line population.", e);
183 }
184 catch (NoSuchMethodException e) {
185 throw new InfrastructureException("unable to complete line line population.", e);
186 }
187 catch (InvocationTargetException e) {
188 throw new InfrastructureException("unable to complete line line population.", e);
189 }
190 }
191
192 if (failed) {
193 throw new LineParserException("empty or invalid line properties in line " + lineNo + ")", EndowmentTransactionDocumentConstants.ERROR_TRANSACTION_LINE_PARSE_INVALID, "" + lineNo);
194 }
195 return line;
196 }
197
198 /**
199 * @see org.kuali.kfs.module.purap.util.ItemParser#parseItem(org.apache.struts.upload.FormFile,java.lang.Class,java.lang.String)
200 */
201 public List<EndowmentTransactionLine> importLines(FormFile lineFile, Class<? extends EndowmentTransactionLine> lineClass, String documentNumber) {
202 InputStream is;
203 BufferedReader br = null;
204
205 // Open input stream
206 List<EndowmentTransactionLine> importedLines = new ArrayList<EndowmentTransactionLine>();
207
208 try {
209 // Check input File.
210 checkLineFile(lineFile);
211
212 is = lineFile.getInputStream();
213 br = new BufferedReader(new InputStreamReader(is));
214
215 // Parse lines line by line
216 lineNo = 0;
217 boolean failed = false;
218 String tranLine = null;
219 try {
220 while ((tranLine = br.readLine()) != null) {
221 lineNo++;
222 try {
223 EndowmentTransactionLine line = parseLine(tranLine, lineClass, documentNumber);
224 importedLines.add(line);
225 }
226 catch (RuntimeException e) {
227 // continue to parse the rest of the lines after the current line fails
228 // error messages are already dealt with inside parseItem, so no need to do anything here
229 failed = true;
230 }
231 }
232
233 if (failed) {
234 throw new LineParserException("Errors in parsing lines in file " + lineFile.getFileName(), EndowKeyConstants.EndowmentTransactionDocumentConstants.ERROR_TRANSACTION_LINE_PARSE_INVALID, lineFile.getFileName());
235 }
236 }
237 catch (IOException e) {
238 throw new InfrastructureException("Unable to read line from BufferReader in LineParserBase", e);
239 }
240 }
241 catch (IOException e) {
242 throw new InfrastructureException("Unable to read line from BufferReader in LineParserBase", e);
243 }
244 finally {
245 try {
246 if (null != br)
247 br.close();
248 }
249 catch (IOException e) {
250 throw new InfrastructureException("Unable to close BufferReader in LineParserBase", e);
251 }
252 }
253
254 return importedLines;
255 }
256
257 /**
258 * @see org.kuali.kfs.module.purap.util.ItemParser#parseItem(java.lang.String,java.lang.Class,java.lang.String)
259 */
260 public EndowmentTransactionLine parseLine(String transactionLine, Class<? extends EndowmentTransactionLine> lineClass, String documentNumber) {
261 Map<String, String> lineMap = retrieveLineAttributes(transactionLine, lineClass);
262 EndowmentTransactionLine line = genLineWithRetrievedAttributes(lineMap, lineClass);
263 // populateExtraAttributes( line, documentNumber );
264 line.refresh();
265 return line;
266 }
267 }