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 }