View Javadoc
1   /*
2    * Copyright 2008 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.module.purap.util;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.struts.upload.FormFile;
20  import org.kuali.ole.module.purap.PurapConstants;
21  import org.kuali.ole.module.purap.PurapParameterConstants;
22  import org.kuali.ole.module.purap.businessobject.PurApItem;
23  import org.kuali.ole.module.purap.businessobject.PurchaseOrderItem;
24  import org.kuali.ole.module.purap.businessobject.RequisitionItem;
25  import org.kuali.ole.module.purap.exception.ItemParserException;
26  import org.kuali.ole.sys.OLEConstants;
27  import org.kuali.ole.sys.OLEKeyConstants;
28  import org.kuali.ole.sys.OLEPropertyConstants;
29  import org.kuali.ole.sys.context.SpringContext;
30  import org.kuali.ole.sys.service.impl.OleParameterConstants;
31  import org.kuali.rice.core.web.format.FormatException;
32  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
33  import org.kuali.rice.kns.service.DataDictionaryService;
34  import org.kuali.rice.krad.util.GlobalVariables;
35  import org.kuali.rice.krad.util.ObjectUtils;
36  
37  import java.io.BufferedReader;
38  import java.io.IOException;
39  import java.io.InputStream;
40  import java.io.InputStreamReader;
41  import java.lang.reflect.InvocationTargetException;
42  import java.util.ArrayList;
43  import java.util.HashMap;
44  import java.util.List;
45  import java.util.Map;
46  import java.util.Map.Entry;
47  
48  import static org.kuali.ole.module.purap.PurapKeyConstants.*;
49  import static org.kuali.ole.module.purap.PurapPropertyConstants.*;
50  
51  public class ItemParserBase implements ItemParser {
52  
53      /**
54       * The default format defines the expected item property names and their order in the import file.
55       * Please update this if the import file format changes (i.e. adding/deleting item properties, changing their order).
56       */
57      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};
58      protected static final String[] COMMODITY_CODE_DISABLED_FORMAT = {ITEM_QUANTITY, OLEPropertyConstants.ITEM_UNIT_OF_MEASURE_CODE, ITEM_CATALOG_NUMBER, ITEM_DESCRIPTION, ITEM_UNIT_PRICE};
59  
60      private Integer lineNo = 0;
61  
62      /**
63       * @see org.kuali.ole.module.purap.util.ItemParser#getItemFormat()
64       */
65      public String[] getItemFormat() {
66          //Check the ENABLE_COMMODITY_CODE_IND system parameter. If it's Y then 
67          //we should return the DEFAULT_FORMAT, otherwise
68          //we should return the COMMODITY_CODE_DISABLED_FORMAT
69          boolean enableCommodityCode = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(OleParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_COMMODITY_CODE_IND);
70          if (enableCommodityCode) {
71              return DEFAULT_FORMAT;
72          }
73          return COMMODITY_CODE_DISABLED_FORMAT;
74      }
75  
76      /**
77       * @see org.kuali.ole.module.purap.util.ItemParser#getExpectedItemFormatAsString(java.lang.Class)
78       */
79      public String getExpectedItemFormatAsString(Class<? extends PurApItem> itemClass) {
80          checkItemClass(itemClass);
81          StringBuffer sb = new StringBuffer();
82          boolean first = true;
83          for (String attributeName : getItemFormat()) {
84              if (!first) {
85                  sb.append(",");
86              } else {
87                  first = false;
88              }
89              sb.append(getAttributeLabel(itemClass, attributeName));
90          }
91          return sb.toString();
92      }
93  
94      /**
95       * Retrieves the attribute label for the specified attribute.
96       *
97       * @param clazz         the class in which the specified attribute is defined
98       * @param attributeName the name of the specified attribute
99       * @return the attribute label for the specified attribute
100      */
101     @SuppressWarnings("rawtypes")
102     protected String getAttributeLabel(Class clazz, String attributeName) {
103         String label = SpringContext.getBean(DataDictionaryService.class).getAttributeLabel(clazz, attributeName);
104         if (StringUtils.isBlank(label)) {
105             label = attributeName;
106         }
107         return label;
108     }
109 
110     /**
111      * Checks whether the specified item class is a subclass of PurApItem;
112      * throws exceptions if not.
113      *
114      * @param itemClass the specified item class
115      */
116     protected void checkItemClass(Class<? extends PurApItem> itemClass) {
117         if (!PurApItem.class.isAssignableFrom(itemClass)) {
118             throw new IllegalArgumentException("unknown item class: " + itemClass);
119         }
120     }
121 
122     /**
123      * Checks whether the specified item import file is not null and of a valid format;
124      * throws exceptions if conditions not satisfied.
125      *
126      * @param itemClass the specified item import file
127      */
128     protected void checkItemFile(FormFile itemFile) {
129         if (itemFile == null) {
130             throw new ItemParserException("invalid (null) item import file", OLEKeyConstants.ERROR_UPLOADFILE_NULL);
131         }
132         String fileName = itemFile.getFileName();
133         if (StringUtils.isNotBlank(fileName) && !StringUtils.lowerCase(fileName).endsWith(".csv") && !StringUtils.lowerCase(fileName).endsWith(".xls")) {
134             throw new ItemParserException("unsupported item import file format: " + fileName, ERROR_ITEMPARSER_INVALID_FILE_FORMAT, fileName);
135         }
136     }
137 
138     /**
139      * Parses a line of item data from a csv file and retrieves the attributes as key-value string pairs into a map.
140      *
141      * @param itemLine a string read from a line in the item import file
142      * @return a map containing item attribute name-value string pairs
143      */
144     protected Map<String, String> retrieveItemAttributes(String itemLine) {
145         String[] attributeNames = getItemFormat();
146         String[] attributeValues = StringUtils.splitPreserveAllTokens(itemLine, ',');
147         if (attributeNames.length != attributeValues.length) {
148             String[] errorParams = {"" + attributeNames.length, "" + attributeValues.length, "" + lineNo};
149             GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERRORS, ERROR_ITEMPARSER_WRONG_PROPERTY_NUMBER, errorParams);
150             throw new ItemParserException("wrong number of item properties: " + attributeValues.length + " exist, " + attributeNames.length + " expected (line " + lineNo + ")", ERROR_ITEMPARSER_WRONG_PROPERTY_NUMBER, errorParams);
151         }
152 
153         Map<String, String> itemMap = new HashMap<String, String>();
154         for (int i = 0; i < attributeNames.length; i++) {
155             itemMap.put(attributeNames[i], attributeValues[i]);
156         }
157         return itemMap;
158     }
159 
160     /**
161      * Generates an item instance and populates it with the specified attribute map.
162      *
163      * @param itemMap   the specified attribute map from which attributes are populated
164      * @param itemClass the class of which the new item instance shall be created
165      * @return the populated item
166      */
167     protected PurApItem genItemWithRetrievedAttributes(Map<String, String> itemMap, Class<? extends PurApItem> itemClass) {
168         PurApItem item;
169         try {
170             item = itemClass.newInstance();
171         } catch (IllegalAccessException e) {
172             throw new IllegalArgumentException("unable to complete item line population.", e);
173         } catch (InstantiationException e) {
174             throw new IllegalArgumentException("unable to complete item line population.", e);
175         }
176 
177         boolean failed = false;
178         for (Entry<String, String> entry : itemMap.entrySet()) {
179             String key = entry.getKey();
180             String value = entry.getValue();
181             try {
182                 /* removing this part as the checking are done in rule class later
183                 if ((key.equals(ITEM_DESCRIPTION) || key.equals(ITEM_UNIT_PRICE)) && value.equals("")) {
184                     String[] errorParams = { key, "" + lineNo };
185                     throw new ItemParserException("empty property value for " + key + " (line " + lineNo + ")", ERROR_ITEMPARSER_EMPTY_PROPERTY_VALUE, errorParams);                    
186                 }
187                 else */
188                 if (key.equals(OLEPropertyConstants.ITEM_UNIT_OF_MEASURE_CODE)) {
189                     value = value.toUpperCase(); // force UOM code to uppercase
190                 }
191                 try {
192                     ObjectUtils.setObjectProperty(item, key, value);
193                 } catch (FormatException e) {
194                     String[] errorParams = {value, key, "" + lineNo};
195                     throw new ItemParserException("invalid numeric property value: " + key + " = " + value + " (line " + lineNo + ")", ERROR_ITEMPARSER_INVALID_NUMERIC_VALUE, errorParams);
196                 }
197             } catch (ItemParserException e) {
198                 // continue to parse the rest of the item properties after the current property fails
199                 GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERRORS, e.getErrorKey(), e.getErrorParameters());
200                 failed = true;
201             } catch (IllegalAccessException e) {
202                 throw new IllegalArgumentException("unable to complete item line population.", e);
203             } catch (NoSuchMethodException e) {
204                 throw new IllegalArgumentException("unable to complete item line population.", e);
205             } catch (InvocationTargetException e) {
206                 throw new IllegalArgumentException("unable to complete item line population.", e);
207             }
208         }
209 
210         if (failed) {
211             throw new ItemParserException("empty or invalid item properties in line " + lineNo + ")", ERROR_ITEMPARSER_ITEM_PROPERTY, "" + lineNo);
212         }
213         return item;
214     }
215 
216     /**
217      * Populates extra item attributes not contained in the imported item data to default values.
218      *
219      * @param item           the item to be populated
220      * @param documentNumber the number of the docment that contains the item
221      */
222     protected void populateExtraAttributes(PurApItem item, String documentNumber) {
223         if (item.getItemQuantity() != null) {
224             String paramName = PurapParameterConstants.DEFAULT_QUANTITY_ITEM_TYPE;
225             String itemTypeCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(PurapConstants.PURAP_NAMESPACE, "Document", paramName);
226             item.setItemTypeCode(itemTypeCode);
227         } else {
228             String paramName = PurapParameterConstants.DEFAULT_NON_QUANTITY_ITEM_TYPE;
229             String itemTypeCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(PurapConstants.PURAP_NAMESPACE, "Document", paramName);
230             item.setItemTypeCode(itemTypeCode);
231         }
232         if (item instanceof RequisitionItem)
233             ((RequisitionItem) item).setItemRestrictedIndicator(false);
234         if (item instanceof PurchaseOrderItem)
235             ((PurchaseOrderItem) item).setDocumentNumber(documentNumber);
236     }
237 
238     /**
239      * @see org.kuali.ole.module.purap.util.ItemParser#parseItem(java.lang.String, java.lang.Class, java.lang.String)
240      */
241     public PurApItem parseItem(String itemLine, Class<? extends PurApItem> itemClass, String documentNumber) {
242         Map<String, String> itemMap = retrieveItemAttributes(itemLine);
243         PurApItem item = genItemWithRetrievedAttributes(itemMap, itemClass);
244         populateExtraAttributes(item, documentNumber);
245         item.refresh();
246         return item;
247     }
248 
249     /**
250      * @see org.kuali.ole.module.purap.util.ItemParser#parseItem(org.apache.struts.upload.FormFile, java.lang.Class, java.lang.String)
251      */
252     public List<PurApItem> importItems(FormFile itemFile, Class<? extends PurApItem> itemClass, String documentNumber) {
253         // check input parameters
254         try {
255             checkItemClass(itemClass);
256             checkItemFile(itemFile);
257         } catch (IllegalArgumentException e) {
258             throw new IllegalArgumentException("unable to import items.", e);
259         }
260 
261         // open input stream
262         List<PurApItem> importedItems = new ArrayList<PurApItem>();
263         InputStream is;
264         BufferedReader br;
265         try {
266             is = itemFile.getInputStream();
267             br = new BufferedReader(new InputStreamReader(is));
268         } catch (IOException e) {
269             throw new IllegalArgumentException("unable to open import file in ItemParserBase.", e);
270         }
271 
272         // parse items line by line
273         lineNo = 0;
274         boolean failed = false;
275         String itemLine = null;
276         try {
277             while ((itemLine = br.readLine()) != null) {
278                 lineNo++;
279 
280                 if (StringUtils.isBlank(StringUtils.remove(StringUtils.deleteWhitespace(itemLine), OLEConstants.COMMA))) {
281                     continue;
282                 }
283 
284                 try {
285                     PurApItem item = parseItem(itemLine, itemClass, documentNumber);
286                     importedItems.add(item);
287                 } catch (ItemParserException e) {
288                     // continue to parse the rest of the items after the current item fails
289                     // error messages are already dealt with inside parseItem, so no need to do anything here
290                     failed = true;
291                 }
292             }
293 
294             if (failed) {
295                 throw new ItemParserException("errors in parsing item lines in file " + itemFile.getFileName(), ERROR_ITEMPARSER_ITEM_LINE, itemFile.getFileName());
296             }
297         } catch (IOException e) {
298             throw new IllegalArgumentException("unable to read line from BufferReader in ItemParserBase", e);
299         } finally {
300             try {
301                 br.close();
302             } catch (IOException e) {
303                 throw new IllegalArgumentException("unable to close BufferReader in ItemParserBase", e);
304             }
305         }
306 
307         return importedItems;
308     }
309 
310 }