/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.module.purap.util;
import static org.kuali.kfs.module.purap.PurapKeyConstants.ERROR_ITEMPARSER_INVALID_FILE_FORMAT;
import static org.kuali.kfs.module.purap.PurapKeyConstants.ERROR_ITEMPARSER_INVALID_NUMERIC_VALUE;
import static org.kuali.kfs.module.purap.PurapKeyConstants.ERROR_ITEMPARSER_ITEM_LINE;
import static org.kuali.kfs.module.purap.PurapKeyConstants.ERROR_ITEMPARSER_ITEM_PROPERTY;
import static org.kuali.kfs.module.purap.PurapKeyConstants.ERROR_ITEMPARSER_WRONG_PROPERTY_NUMBER;
import static org.kuali.kfs.module.purap.PurapPropertyConstants.ITEM_CATALOG_NUMBER;
import static org.kuali.kfs.module.purap.PurapPropertyConstants.ITEM_COMMODITY_CODE;
import static org.kuali.kfs.module.purap.PurapPropertyConstants.ITEM_DESCRIPTION;
import static org.kuali.kfs.module.purap.PurapPropertyConstants.ITEM_QUANTITY;
import static org.kuali.kfs.module.purap.PurapPropertyConstants.ITEM_UNIT_PRICE;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang.StringUtils;
import org.apache.struts.upload.FormFile;
import org.kuali.kfs.module.purap.PurapConstants;
import org.kuali.kfs.module.purap.PurapParameterConstants;
import org.kuali.kfs.module.purap.businessobject.PurApItem;
import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
import org.kuali.kfs.module.purap.businessobject.RequisitionItem;
import org.kuali.kfs.module.purap.exception.ItemParserException;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.core.web.format.FormatException;
import org.kuali.rice.kns.service.DataDictionaryService;
import org.kuali.rice.krad.exception.InfrastructureException;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.ObjectUtils;
public class ItemParserBase implements ItemParser {
/**
* The default format defines the expected item property names and their order in the import file.
* Please update this if the import file format changes (i.e. adding/deleting item properties, changing their order).
*/
protected static final String[] DEFAULT_FORMAT = {ITEM_QUANTITY, KFSPropertyConstants.ITEM_UNIT_OF_MEASURE_CODE, ITEM_CATALOG_NUMBER, ITEM_COMMODITY_CODE, ITEM_DESCRIPTION, ITEM_UNIT_PRICE};
protected static final String[] COMMODITY_CODE_DISABLED_FORMAT = {ITEM_QUANTITY, KFSPropertyConstants.ITEM_UNIT_OF_MEASURE_CODE, ITEM_CATALOG_NUMBER, ITEM_DESCRIPTION, ITEM_UNIT_PRICE};
private Integer lineNo = 0;
/**
* @see org.kuali.kfs.module.purap.util.ItemParser#getItemFormat()
*/
public String[] getItemFormat() {
//Check the ENABLE_COMMODITY_CODE_IND system parameter. If it's Y then
//we should return the DEFAULT_FORMAT, otherwise
//we should return the COMMODITY_CODE_DISABLED_FORMAT
boolean enableCommodityCode = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_COMMODITY_CODE_IND);
if (enableCommodityCode) {
return DEFAULT_FORMAT;
}
return COMMODITY_CODE_DISABLED_FORMAT;
}
/**
* @see org.kuali.kfs.module.purap.util.ItemParser#getExpectedItemFormatAsString(java.lang.Class)
*/
public String getExpectedItemFormatAsString( Class<? extends PurApItem> itemClass ) {
checkItemClass( itemClass );
StringBuffer sb = new StringBuffer();
boolean first = true;
for (String attributeName : getItemFormat()) {
if (!first) {
sb.append(",");
}
else {
first = false;
}
sb.append( getAttributeLabel( itemClass, attributeName ) );
}
return sb.toString();
}
/**
* Retrieves the attribute label for the specified attribute.
*
* @param clazz the class in which the specified attribute is defined
* @param attributeName the name of the specified attribute
* @return the attribute label for the specified attribute
*/
@SuppressWarnings("rawtypes")
protected String getAttributeLabel( Class clazz, String attributeName ) {
String label = SpringContext.getBean(DataDictionaryService.class).getAttributeLabel(clazz, attributeName);
if (StringUtils.isBlank(label)) {
label = attributeName;
}
return label;
}
/**
* Checks whether the specified item class is a subclass of PurApItem;
* throws exceptions if not.
*
* @param itemClass the specified item class
*/
protected void checkItemClass(Class<? extends PurApItem> itemClass) {
if (!PurApItem.class.isAssignableFrom(itemClass)) {
throw new IllegalArgumentException("unknown item class: " + itemClass);
}
}
/**
* Checks whether the specified item import file is not null and of a valid format;
* throws exceptions if conditions not satisfied.
*
* @param itemClass the specified item import file
*/
protected void checkItemFile(FormFile itemFile) {
if (itemFile == null) {
throw new ItemParserException("invalid (null) item import file", KFSKeyConstants.ERROR_UPLOADFILE_NULL);
}
String fileName = itemFile.getFileName();
if (StringUtils.isNotBlank(fileName) && !StringUtils.lowerCase(fileName).endsWith(".csv") && !StringUtils.lowerCase(fileName).endsWith(".xls")) {
throw new ItemParserException("unsupported item import file format: " + fileName, ERROR_ITEMPARSER_INVALID_FILE_FORMAT, fileName);
}
}
/**
* Parses a line of item data from a csv file and retrieves the attributes as key-value string pairs into a map.
*
* @param itemLine a string read from a line in the item import file
* @return a map containing item attribute name-value string pairs
*/
protected Map<String, String> retrieveItemAttributes( String itemLine ) {
String[] attributeNames = getItemFormat();
String[] attributeValues = StringUtils.splitPreserveAllTokens(itemLine, ',');
if ( attributeNames.length != attributeValues.length ) {
String[] errorParams = { "" + attributeNames.length, "" + attributeValues.length, "" + lineNo };
GlobalVariables.getMessageMap().putError( PurapConstants.ITEM_TAB_ERRORS, ERROR_ITEMPARSER_WRONG_PROPERTY_NUMBER, errorParams );
throw new ItemParserException("wrong number of item properties: " + attributeValues.length + " exist, " + attributeNames.length + " expected (line " + lineNo + ")", ERROR_ITEMPARSER_WRONG_PROPERTY_NUMBER, errorParams);
}
Map<String, String> itemMap = new HashMap<String, String>();
for (int i=0; i < attributeNames.length; i++) {
itemMap.put( attributeNames[i], attributeValues[i] );
}
return itemMap;
}
/**
* Generates an item instance and populates it with the specified attribute map.
*
* @param itemMap the specified attribute map from which attributes are populated
* @param itemClass the class of which the new item instance shall be created
* @return the populated item
*/
protected PurApItem genItemWithRetrievedAttributes( Map<String, String> itemMap, Class<? extends PurApItem> itemClass ) {
PurApItem item;
try {
item = itemClass.newInstance();
}
catch (IllegalAccessException e) {
throw new InfrastructureException("unable to complete item line population.", e);
}
catch (InstantiationException e) {
throw new InfrastructureException("unable to complete item line population.", e);
}
boolean failed = false;
for (Entry<String, String> entry : itemMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
try {
/* removing this part as the checking are done in rule class later
if ((key.equals(ITEM_DESCRIPTION) || key.equals(ITEM_UNIT_PRICE)) && value.equals("")) {
String[] errorParams = { key, "" + lineNo };
throw new ItemParserException("empty property value for " + key + " (line " + lineNo + ")", ERROR_ITEMPARSER_EMPTY_PROPERTY_VALUE, errorParams);
}
else */
if (key.equals(KFSPropertyConstants.ITEM_UNIT_OF_MEASURE_CODE)) {
value = value.toUpperCase(); // force UOM code to uppercase
}
try {
ObjectUtils.setObjectProperty(item, key, value);
}
catch (FormatException e) {
String[] errorParams = { value, key, "" + lineNo };
throw new ItemParserException("invalid numeric property value: " + key + " = " + value + " (line " + lineNo + ")", ERROR_ITEMPARSER_INVALID_NUMERIC_VALUE, errorParams);
}
}
catch (ItemParserException e) {
// continue to parse the rest of the item properties after the current property fails
GlobalVariables.getMessageMap().putError( PurapConstants.ITEM_TAB_ERRORS, e.getErrorKey(), e.getErrorParameters() );
failed = true;
}
catch (IllegalAccessException e) {
throw new InfrastructureException("unable to complete item line population.", e);
}
catch (NoSuchMethodException e) {
throw new InfrastructureException("unable to complete item line population.", e);
}
catch (InvocationTargetException e) {
throw new InfrastructureException("unable to complete item line population.", e);
}
}
if (failed) {
throw new ItemParserException("empty or invalid item properties in line " + lineNo + ")", ERROR_ITEMPARSER_ITEM_PROPERTY, ""+lineNo);
}
return item;
}
/**
* Populates extra item attributes not contained in the imported item data to default values.
*
* @param item the item to be populated
* @param documentNumber the number of the docment that contains the item
*/
protected void populateExtraAttributes( PurApItem item, String documentNumber ) {
if (item.getItemQuantity() != null) {
String paramName = PurapParameterConstants.DEFAULT_QUANTITY_ITEM_TYPE;
String itemTypeCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(PurapConstants.PURAP_NAMESPACE, "Document", paramName);
item.setItemTypeCode(itemTypeCode);
}
else {
String paramName = PurapParameterConstants.DEFAULT_NON_QUANTITY_ITEM_TYPE;
String itemTypeCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(PurapConstants.PURAP_NAMESPACE, "Document", paramName);
item.setItemTypeCode(itemTypeCode);
}
if (item instanceof RequisitionItem)
((RequisitionItem)item).setItemRestrictedIndicator(false);
if (item instanceof PurchaseOrderItem)
((PurchaseOrderItem)item).setDocumentNumber(documentNumber);
}
/**
* @see org.kuali.kfs.module.purap.util.ItemParser#parseItem(java.lang.String,java.lang.Class,java.lang.String)
*/
public PurApItem parseItem( String itemLine, Class<? extends PurApItem> itemClass, String documentNumber ) {
Map<String, String> itemMap = retrieveItemAttributes( itemLine );
PurApItem item = genItemWithRetrievedAttributes( itemMap, itemClass );
populateExtraAttributes( item, documentNumber );
item.refresh();
return item;
}
/**
* @see org.kuali.kfs.module.purap.util.ItemParser#parseItem(org.apache.struts.upload.FormFile,java.lang.Class,java.lang.String)
*/
public List<PurApItem> importItems( FormFile itemFile, Class<? extends PurApItem> itemClass, String documentNumber ) {
// check input parameters
try {
checkItemClass( itemClass );
checkItemFile( itemFile );
}
catch (IllegalArgumentException e) {
throw new InfrastructureException("unable to import items.", e);
}
// open input stream
List<PurApItem> importedItems = new ArrayList<PurApItem>();
InputStream is;
BufferedReader br;
try {
is = itemFile.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
}
catch (IOException e) {
throw new InfrastructureException("unable to open import file in ItemParserBase.", e);
}
// parse items line by line
lineNo = 0;
boolean failed = false;
String itemLine = null;
try {
while ( (itemLine = br.readLine()) != null ) {
lineNo++;
if(StringUtils.isBlank(StringUtils.remove(StringUtils.deleteWhitespace(itemLine),KFSConstants.COMMA))) {
continue;
}
try {
PurApItem item = parseItem( itemLine, itemClass, documentNumber );
importedItems.add(item);
}
catch (ItemParserException e) {
// continue to parse the rest of the items after the current item fails
// error messages are already dealt with inside parseItem, so no need to do anything here
failed = true;
}
}
if (failed) {
throw new ItemParserException("errors in parsing item lines in file " + itemFile.getFileName(), ERROR_ITEMPARSER_ITEM_LINE, itemFile.getFileName());
}
}
catch (IOException e) {
throw new InfrastructureException("unable to read line from BufferReader in ItemParserBase", e);
}
finally {
try {
br.close();
}
catch (IOException e) {
throw new InfrastructureException("unable to close BufferReader in ItemParserBase", e);
}
}
return importedItems;
}
}