001/* 002 * Copyright 2008 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 */ 016package org.kuali.ole.module.purap.util; 017 018import org.apache.commons.lang.StringUtils; 019import org.apache.struts.upload.FormFile; 020import org.kuali.ole.module.purap.PurapConstants; 021import org.kuali.ole.module.purap.PurapParameterConstants; 022import org.kuali.ole.module.purap.businessobject.PurApItem; 023import org.kuali.ole.module.purap.businessobject.PurchaseOrderItem; 024import org.kuali.ole.module.purap.businessobject.RequisitionItem; 025import org.kuali.ole.module.purap.exception.ItemParserException; 026import org.kuali.ole.sys.OLEConstants; 027import org.kuali.ole.sys.OLEKeyConstants; 028import org.kuali.ole.sys.OLEPropertyConstants; 029import org.kuali.ole.sys.context.SpringContext; 030import org.kuali.ole.sys.service.impl.OleParameterConstants; 031import org.kuali.rice.core.web.format.FormatException; 032import org.kuali.rice.coreservice.framework.parameter.ParameterService; 033import org.kuali.rice.kns.service.DataDictionaryService; 034import org.kuali.rice.krad.exception.InfrastructureException; 035import org.kuali.rice.krad.util.GlobalVariables; 036import org.kuali.rice.krad.util.ObjectUtils; 037 038import java.io.BufferedReader; 039import java.io.IOException; 040import java.io.InputStream; 041import java.io.InputStreamReader; 042import java.lang.reflect.InvocationTargetException; 043import java.util.ArrayList; 044import java.util.HashMap; 045import java.util.List; 046import java.util.Map; 047import java.util.Map.Entry; 048 049import static org.kuali.ole.module.purap.PurapKeyConstants.*; 050import static org.kuali.ole.module.purap.PurapPropertyConstants.*; 051 052public class ItemParserBase implements ItemParser { 053 054 /** 055 * The default format defines the expected item property names and their order in the import file. 056 * Please update this if the import file format changes (i.e. adding/deleting item properties, changing their order). 057 */ 058 protected static final String[] DEFAULT_FORMAT = {ITEM_QUANTITY, OLEPropertyConstants.ITEM_UNIT_OF_MEASURE_CODE, ITEM_CATALOG_NUMBER, ITEM_COMMODITY_CODE, ITEM_DESCRIPTION, ITEM_UNIT_PRICE}; 059 protected static final String[] COMMODITY_CODE_DISABLED_FORMAT = {ITEM_QUANTITY, OLEPropertyConstants.ITEM_UNIT_OF_MEASURE_CODE, ITEM_CATALOG_NUMBER, ITEM_DESCRIPTION, ITEM_UNIT_PRICE}; 060 061 private Integer lineNo = 0; 062 063 /** 064 * @see org.kuali.ole.module.purap.util.ItemParser#getItemFormat() 065 */ 066 public String[] getItemFormat() { 067 //Check the ENABLE_COMMODITY_CODE_IND system parameter. If it's Y then 068 //we should return the DEFAULT_FORMAT, otherwise 069 //we should return the COMMODITY_CODE_DISABLED_FORMAT 070 boolean enableCommodityCode = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_COMMODITY_CODE_IND); 071 if (enableCommodityCode) { 072 return DEFAULT_FORMAT; 073 } 074 return COMMODITY_CODE_DISABLED_FORMAT; 075 } 076 077 /** 078 * @see org.kuali.ole.module.purap.util.ItemParser#getExpectedItemFormatAsString(java.lang.Class) 079 */ 080 public String getExpectedItemFormatAsString(Class<? extends PurApItem> itemClass) { 081 checkItemClass(itemClass); 082 StringBuffer sb = new StringBuffer(); 083 boolean first = true; 084 for (String attributeName : getItemFormat()) { 085 if (!first) { 086 sb.append(","); 087 } else { 088 first = false; 089 } 090 sb.append(getAttributeLabel(itemClass, attributeName)); 091 } 092 return sb.toString(); 093 } 094 095 /** 096 * Retrieves the attribute label for the specified attribute. 097 * 098 * @param clazz the class in which the specified attribute is defined 099 * @param attributeName the name of the specified attribute 100 * @return the attribute label for the specified attribute 101 */ 102 @SuppressWarnings("rawtypes") 103 protected String getAttributeLabel(Class clazz, String attributeName) { 104 String label = SpringContext.getBean(DataDictionaryService.class).getAttributeLabel(clazz, attributeName); 105 if (StringUtils.isBlank(label)) { 106 label = attributeName; 107 } 108 return label; 109 } 110 111 /** 112 * Checks whether the specified item class is a subclass of PurApItem; 113 * throws exceptions if not. 114 * 115 * @param itemClass the specified item class 116 */ 117 protected void checkItemClass(Class<? extends PurApItem> itemClass) { 118 if (!PurApItem.class.isAssignableFrom(itemClass)) { 119 throw new IllegalArgumentException("unknown item class: " + itemClass); 120 } 121 } 122 123 /** 124 * Checks whether the specified item import file is not null and of a valid format; 125 * throws exceptions if conditions not satisfied. 126 * 127 * @param itemClass the specified item import file 128 */ 129 protected void checkItemFile(FormFile itemFile) { 130 if (itemFile == null) { 131 throw new ItemParserException("invalid (null) item import file", OLEKeyConstants.ERROR_UPLOADFILE_NULL); 132 } 133 String fileName = itemFile.getFileName(); 134 if (StringUtils.isNotBlank(fileName) && !StringUtils.lowerCase(fileName).endsWith(".csv") && !StringUtils.lowerCase(fileName).endsWith(".xls")) { 135 throw new ItemParserException("unsupported item import file format: " + fileName, ERROR_ITEMPARSER_INVALID_FILE_FORMAT, fileName); 136 } 137 } 138 139 /** 140 * Parses a line of item data from a csv file and retrieves the attributes as key-value string pairs into a map. 141 * 142 * @param itemLine a string read from a line in the item import file 143 * @return a map containing item attribute name-value string pairs 144 */ 145 protected Map<String, String> retrieveItemAttributes(String itemLine) { 146 String[] attributeNames = getItemFormat(); 147 String[] attributeValues = StringUtils.splitPreserveAllTokens(itemLine, ','); 148 if (attributeNames.length != attributeValues.length) { 149 String[] errorParams = {"" + attributeNames.length, "" + attributeValues.length, "" + lineNo}; 150 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERRORS, ERROR_ITEMPARSER_WRONG_PROPERTY_NUMBER, errorParams); 151 throw new ItemParserException("wrong number of item properties: " + attributeValues.length + " exist, " + attributeNames.length + " expected (line " + lineNo + ")", ERROR_ITEMPARSER_WRONG_PROPERTY_NUMBER, errorParams); 152 } 153 154 Map<String, String> itemMap = new HashMap<String, String>(); 155 for (int i = 0; i < attributeNames.length; i++) { 156 itemMap.put(attributeNames[i], attributeValues[i]); 157 } 158 return itemMap; 159 } 160 161 /** 162 * Generates an item instance and populates it with the specified attribute map. 163 * 164 * @param itemMap the specified attribute map from which attributes are populated 165 * @param itemClass the class of which the new item instance shall be created 166 * @return the populated item 167 */ 168 protected PurApItem genItemWithRetrievedAttributes(Map<String, String> itemMap, Class<? extends PurApItem> itemClass) { 169 PurApItem item; 170 try { 171 item = itemClass.newInstance(); 172 } catch (IllegalAccessException e) { 173 throw new InfrastructureException("unable to complete item line population.", e); 174 } catch (InstantiationException e) { 175 throw new InfrastructureException("unable to complete item line population.", e); 176 } 177 178 boolean failed = false; 179 for (Entry<String, String> entry : itemMap.entrySet()) { 180 String key = entry.getKey(); 181 String value = entry.getValue(); 182 try { 183 /* removing this part as the checking are done in rule class later 184 if ((key.equals(ITEM_DESCRIPTION) || key.equals(ITEM_UNIT_PRICE)) && value.equals("")) { 185 String[] errorParams = { key, "" + lineNo }; 186 throw new ItemParserException("empty property value for " + key + " (line " + lineNo + ")", ERROR_ITEMPARSER_EMPTY_PROPERTY_VALUE, errorParams); 187 } 188 else */ 189 if (key.equals(OLEPropertyConstants.ITEM_UNIT_OF_MEASURE_CODE)) { 190 value = value.toUpperCase(); // force UOM code to uppercase 191 } 192 try { 193 ObjectUtils.setObjectProperty(item, key, value); 194 } catch (FormatException e) { 195 String[] errorParams = {value, key, "" + lineNo}; 196 throw new ItemParserException("invalid numeric property value: " + key + " = " + value + " (line " + lineNo + ")", ERROR_ITEMPARSER_INVALID_NUMERIC_VALUE, errorParams); 197 } 198 } catch (ItemParserException e) { 199 // continue to parse the rest of the item properties after the current property fails 200 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERRORS, e.getErrorKey(), e.getErrorParameters()); 201 failed = true; 202 } catch (IllegalAccessException e) { 203 throw new InfrastructureException("unable to complete item line population.", e); 204 } catch (NoSuchMethodException e) { 205 throw new InfrastructureException("unable to complete item line population.", e); 206 } catch (InvocationTargetException e) { 207 throw new InfrastructureException("unable to complete item line population.", e); 208 } 209 } 210 211 if (failed) { 212 throw new ItemParserException("empty or invalid item properties in line " + lineNo + ")", ERROR_ITEMPARSER_ITEM_PROPERTY, "" + lineNo); 213 } 214 return item; 215 } 216 217 /** 218 * Populates extra item attributes not contained in the imported item data to default values. 219 * 220 * @param item the item to be populated 221 * @param documentNumber the number of the docment that contains the item 222 */ 223 protected void populateExtraAttributes(PurApItem item, String documentNumber) { 224 if (item.getItemQuantity() != null) { 225 String paramName = PurapParameterConstants.DEFAULT_QUANTITY_ITEM_TYPE; 226 String itemTypeCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(PurapConstants.PURAP_NAMESPACE, "Document", paramName); 227 item.setItemTypeCode(itemTypeCode); 228 } else { 229 String paramName = PurapParameterConstants.DEFAULT_NON_QUANTITY_ITEM_TYPE; 230 String itemTypeCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(PurapConstants.PURAP_NAMESPACE, "Document", paramName); 231 item.setItemTypeCode(itemTypeCode); 232 } 233 if (item instanceof RequisitionItem) 234 ((RequisitionItem) item).setItemRestrictedIndicator(false); 235 if (item instanceof PurchaseOrderItem) 236 ((PurchaseOrderItem) item).setDocumentNumber(documentNumber); 237 } 238 239 /** 240 * @see org.kuali.ole.module.purap.util.ItemParser#parseItem(java.lang.String, java.lang.Class, java.lang.String) 241 */ 242 public PurApItem parseItem(String itemLine, Class<? extends PurApItem> itemClass, String documentNumber) { 243 Map<String, String> itemMap = retrieveItemAttributes(itemLine); 244 PurApItem item = genItemWithRetrievedAttributes(itemMap, itemClass); 245 populateExtraAttributes(item, documentNumber); 246 item.refresh(); 247 return item; 248 } 249 250 /** 251 * @see org.kuali.ole.module.purap.util.ItemParser#parseItem(org.apache.struts.upload.FormFile, java.lang.Class, java.lang.String) 252 */ 253 public List<PurApItem> importItems(FormFile itemFile, Class<? extends PurApItem> itemClass, String documentNumber) { 254 // check input parameters 255 try { 256 checkItemClass(itemClass); 257 checkItemFile(itemFile); 258 } catch (IllegalArgumentException e) { 259 throw new InfrastructureException("unable to import items.", e); 260 } 261 262 // open input stream 263 List<PurApItem> importedItems = new ArrayList<PurApItem>(); 264 InputStream is; 265 BufferedReader br; 266 try { 267 is = itemFile.getInputStream(); 268 br = new BufferedReader(new InputStreamReader(is)); 269 } catch (IOException e) { 270 throw new InfrastructureException("unable to open import file in ItemParserBase.", e); 271 } 272 273 // parse items line by line 274 lineNo = 0; 275 boolean failed = false; 276 String itemLine = null; 277 try { 278 while ((itemLine = br.readLine()) != null) { 279 lineNo++; 280 281 if (StringUtils.isBlank(StringUtils.remove(StringUtils.deleteWhitespace(itemLine), OLEConstants.COMMA))) { 282 continue; 283 } 284 285 try { 286 PurApItem item = parseItem(itemLine, itemClass, documentNumber); 287 importedItems.add(item); 288 } catch (ItemParserException e) { 289 // continue to parse the rest of the items after the current item fails 290 // error messages are already dealt with inside parseItem, so no need to do anything here 291 failed = true; 292 } 293 } 294 295 if (failed) { 296 throw new ItemParserException("errors in parsing item lines in file " + itemFile.getFileName(), ERROR_ITEMPARSER_ITEM_LINE, itemFile.getFileName()); 297 } 298 } catch (IOException e) { 299 throw new InfrastructureException("unable to read line from BufferReader in ItemParserBase", e); 300 } finally { 301 try { 302 br.close(); 303 } catch (IOException e) { 304 throw new InfrastructureException("unable to close BufferReader in ItemParserBase", e); 305 } 306 } 307 308 return importedItems; 309 } 310 311}