/****************************************************************************** * Product: Adempiere ERP & CRM Smart Business Solution * * Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved. * * This program is free software; you can redistribute it and/or modify it * * under the terms version 2 of the GNU General Public License as published * * by the Free Software Foundation. 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * * with this program; if not, write to the Free Software Foundation, Inc., * * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * * For the text or an alternative of this public license, you may reach us * * ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA * * or via info@compiere.org or http://www.compiere.org/license.html * *****************************************************************************/ package org.compiere.impexp; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Properties; import java.util.logging.Level; import org.compiere.model.I_AD_ImpFormat; import org.compiere.model.I_I_BPartner; import org.compiere.model.I_I_ElementValue; import org.compiere.model.I_I_Product; import org.compiere.model.I_I_ReportLine; import org.compiere.model.X_AD_ImpFormat; import org.compiere.model.X_I_GLJournal; import org.compiere.util.CLogger; import org.compiere.util.DB; import org.compiere.util.Env; /** * Import implementation using {@link MImpFormat} and {@link MImpFormatRow}. * * @author Jorg Janke * @author Trifon Trifonov, Catura AG (www.catura.de) *
  • FR [ 3010957 ] Custom Separator Character, https://sourceforge.net/p/adempiere/feature-requests/975/
  • * @author eugen.hanussek@klst.com *
  • BF [ 3564464 ] Import File Loader discards input records , https://sourceforge.net/p/adempiere/bugs/2727/
  • * * @version $Id$ */ public final class ImpFormat { /** * Format * @param name name * @param AD_Table_ID table * @param formatType format type */ public ImpFormat (String name, int AD_Table_ID, String formatType) { setName(name); setTable(AD_Table_ID); setFormatType(formatType); } // ImpFormat /** Logger */ private static final CLogger log = CLogger.getCLogger(ImpFormat.class); private String m_name; private String m_formatType; /** The Table to be imported */ private int m_AD_Table_ID; private String m_tableName; private String m_tablePK; private String m_tableUnique1; private String m_tableUnique2; private String m_tableUniqueParent; private String m_tableUniqueChild; // private String m_BPartner; private ArrayList m_rows = new ArrayList(); // private String separatorChar; /** * Set Name * @param newName new name */ public void setName(String newName) { if (newName == null || newName.length() == 0) throw new IllegalArgumentException("Name must be at least 1 char"); else m_name = newName; } /** * Get Name * @return name */ public String getName() { return m_name; } // getName /** * Set separator character for column * @param newChar */ public void setSeparatorChar(String newChar) { if (newChar == null || newChar.length() == 0) { throw new IllegalArgumentException("Separator Character must be 1 char"); } else { separatorChar = newChar; } } /** * @return separator character for column */ public String getSeparatorChar() { return separatorChar; } /** * Set Import Table * @param AD_Table_ID table */ public void setTable (int AD_Table_ID) { m_AD_Table_ID = AD_Table_ID; m_tableName = null; m_tablePK = null; String sql = "SELECT t.TableName,c.ColumnName " + "FROM AD_Table t INNER JOIN AD_Column c ON (t.AD_Table_ID=c.AD_Table_ID AND c.IsKey='Y') " + "WHERE t.AD_Table_ID=?"; PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = DB.prepareStatement(sql, null); pstmt.setInt(1, AD_Table_ID); rs = pstmt.executeQuery(); if (rs.next()) { m_tableName = rs.getString(1); m_tablePK = rs.getString(2); } } catch (SQLException e) { log.log(Level.SEVERE, "ImpFormat.setTable", e); } finally { DB.close(rs, pstmt); rs = null; pstmt = null; } if (m_tableName == null || m_tablePK == null) log.log(Level.SEVERE, "Data not found for AD_Table_ID=" + AD_Table_ID); // Set Additional Table Info m_tableUnique1 = ""; m_tableUnique2 = ""; m_tableUniqueParent = ""; m_tableUniqueChild = ""; if (m_AD_Table_ID == I_I_Product.Table_ID) // I_Product { m_tableUnique1 = "UPC"; // UPC = unique m_tableUnique2 = "Value"; m_tableUniqueChild = "VendorProductNo"; // Vendor No may not be unique ! m_tableUniqueParent = "BPartner_Value"; // Makes it unique } else if (m_AD_Table_ID == I_I_BPartner.Table_ID) // I_BPartner { ; } else if (m_AD_Table_ID == I_I_ElementValue.Table_ID) // I_ElementValue { m_tableUniqueParent = "ElementName"; // the parent key m_tableUniqueChild = "Value"; // the key } else if (m_AD_Table_ID == I_I_ReportLine.Table_ID) // I_ReportLine { m_tableUniqueParent = "ReportLineSetName"; // the parent key m_tableUniqueChild = "Name"; // the key } } // setTable /** * Get Import Table Name * @return AD_Table_ID */ public int getAD_Table_ID() { return m_AD_Table_ID; } // getAD_Table_ID /** * Set Format Type * @param newFormatType - F/C/T/X */ public void setFormatType(String newFormatType) { if (newFormatType.equals(X_AD_ImpFormat.FORMATTYPE_FixedPosition) || newFormatType.equals(X_AD_ImpFormat.FORMATTYPE_CommaSeparated) || newFormatType.equals(X_AD_ImpFormat.FORMATTYPE_TabSeparated) || newFormatType.equals(X_AD_ImpFormat.FORMATTYPE_XML) || newFormatType.equals(X_AD_ImpFormat.FORMATTYPE_CustomSeparatorChar) ) m_formatType = newFormatType; else throw new IllegalArgumentException("FormatType must be F/C/T/X/U"); } // setFormatType /** * Set Format Type * @return format type - F/C/T/X */ public String getFormatType() { return m_formatType; } // getFormatType /** * Set Business Partner * @param newBPartner (value) * @deprecated */ @Deprecated public void setBPartner(String newBPartner) { m_BPartner = newBPartner; } // setBPartner /** * Get Business Partner * @return BPartner (value) * @deprecated */ @Deprecated public String getBPartner() { return m_BPartner; } // getVPartner /** * Add Format Row * @param row row */ public void addRow (ImpFormatRow row) { m_rows.add (row); } // addRow /** * Get Format Row * @param index index * @return Import Format Row or null (if index is not valid) */ public ImpFormatRow getRow (int index) { if (index >=0 && index < m_rows.size()) return (ImpFormatRow)m_rows.get(index); return null; } // getRow /** * Get Format Row Count * @return format row count */ public int getRowCount() { return m_rows.size(); } // getRowCount /** * Load import format * @param Id id * @return Import Format */ public static ImpFormat load (int Id) { if (log.isLoggable(Level.CONFIG))log.config(String.valueOf(Id)); ImpFormat retValue = null; String sql = "SELECT * FROM AD_ImpFormat WHERE AD_Impformat_ID=?"; PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = DB.prepareStatement(sql, null); pstmt.setInt (1, Id); rs = pstmt.executeQuery(); if (rs.next()) { retValue = new ImpFormat (rs.getString("Name"), rs.getInt("AD_Table_ID"), rs.getString("FormatType")); if (X_AD_ImpFormat.FORMATTYPE_CustomSeparatorChar.equals(rs.getString(I_AD_ImpFormat.COLUMNNAME_FormatType))) { retValue.setSeparatorChar(rs.getString(I_AD_ImpFormat.COLUMNNAME_SeparatorChar)); } } } catch (SQLException e) { log.log(Level.SEVERE, sql, e); return null; } finally { DB.close(rs, pstmt); rs = null; pstmt = null; } loadRows (retValue, Id); return retValue; } // getFormat /** * Load Format Rows via import format id * @param format format * @param ID import format id */ private static void loadRows (ImpFormat format, int ID) { String sql = "SELECT f.SeqNo,c.ColumnName,f.StartNo,f.EndNo,f.DataType,c.FieldLength," // 1..6 + "f.DataFormat,f.DecimalPoint,f.DivideBy100,f.ConstantValue,f.Callout," // 7..11 + "f.Name, f.importprefix " // 12..13 + "FROM AD_ImpFormat_Row f,AD_Column c " + "WHERE f.AD_ImpFormat_ID=? AND f.AD_Column_ID=c.AD_Column_ID AND f.IsActive='Y' " + "ORDER BY f.SeqNo"; PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = DB.prepareStatement(sql, null); pstmt.setInt (1, ID); rs = pstmt.executeQuery(); while (rs.next()) { ImpFormatRow row = new ImpFormatRow (rs.getInt(1), rs.getString(2), rs.getInt(3), rs.getInt(4), rs.getString(5), rs.getInt(6), rs.getString(12)); // row.setFormatInfo(rs.getString(7), rs.getString(8), rs.getString(9).equals("Y"), rs.getString(10), rs.getString(11), rs.getString(13)); // format.addRow (row); } } catch (SQLException e) { log.log(Level.SEVERE, sql, e); } finally { DB.close(rs, pstmt); rs = null; pstmt = null; } } // loadLines /** * Parse line and returns list of values * * @param line line * @param withLabel true if with label * @param trace create trace info * @param ignoreEmpty - ignore empty fields * @return Array of values */ public String[] parseLine (String line, boolean withLabel, boolean trace, boolean ignoreEmpty) { if (trace) if (log.isLoggable(Level.CONFIG)) log.config("" + line); ArrayList list = new ArrayList(); // for all columns for (int i = 0; i < m_rows.size(); i++) { ImpFormatRow row = (ImpFormatRow)m_rows.get(i); StringBuilder entry = new StringBuilder (); // Label-Start if (withLabel) { //start concat mechanic boolean concat = false; //only act if we combine String or Constant if (row.isString() || row.isConstant()) //if the list contains an entry for the same column, remove the old one and concatenate the two for (int j = 0; j < list.size(); j++) { if (list.get(j).startsWith(row.getColumnName() + "=")) { concat = true; entry.append(list.get(j)); if (entry.charAt(entry.length()-1) == '\'') entry.deleteCharAt(entry.length()-1); //remove "'" for strings list.remove(j); break; } } //end concat mechanic if (!concat) { entry.append(row.getColumnName()); entry.append("="); if (row.isString()) { entry.append("'"); } else if (row.isDate()) { if (DB.isPostgreSQL()) entry.append("TO_TIMESTAMP('"); else entry.append("TO_DATE('"); } } } // Get Data String info = null; if (row.isConstant()) info = "Constant"; else if (m_formatType.equals(X_AD_ImpFormat.FORMATTYPE_FixedPosition)) { // check length if (row.getStartNo() > 0 && row.getEndNo() <= line.length()) info = line.substring(row.getStartNo()-1, row.getEndNo()); } else { info = parseFlexFormat (line, m_formatType, row.getStartNo()); } if (info == null) info = ""; // Interpret Data entry.append(row.parse(info)); // Label-End if (withLabel) { if (row.isString()) entry.append("'"); else if (row.isDate()) entry.append("','YYYY-MM-DD HH24:MI:SS')"); // JDBC Timestamp format w/o miliseconds } if (!ignoreEmpty || (ignoreEmpty && info.length() != 0)) list.add(entry.toString()); // if (trace) if (log.isLoggable(Level.FINE)) log.fine(info + "=>" + entry.toString() + " (Length=" + info.length() + ")"); } // for all columns String[] retValue = new String[list.size()]; list.toArray(retValue); return retValue; } // parseLine /** * Parse flexible line format.
    * A bit inefficient as it always starts from the start. * * @param line the line to be parsed * @param formatType Comma or Tab * @param fieldNo number of field to be returned * @return field in lime or "" * @throws IllegalArgumentException if format unknowns */ private String parseFlexFormat (String line, String formatType, int fieldNo) { final char QUOTE = '"'; // check input char delimiter = ' '; if (formatType.equals(X_AD_ImpFormat.FORMATTYPE_CommaSeparated)) { delimiter = ','; } else if (formatType.equals(X_AD_ImpFormat.FORMATTYPE_TabSeparated)) { delimiter = '\t'; } else if (formatType.equals(X_AD_ImpFormat.FORMATTYPE_CustomSeparatorChar)) { delimiter = getSeparatorChar().charAt(0); } else { throw new IllegalArgumentException ("ImpFormat.parseFlexFormat - unknown format: " + formatType); } if (line == null || line.length() == 0 || fieldNo < 0) return ""; // We need to read line sequentially as the fields may be delimited // with quotes (") when fields contain the delimiter // Example: "Artikel,bez","Artikel,""nr""",DEM,EUR // needs to result in - Artikel,bez - Artikel,"nr" - DEM - EUR int pos = 0; int length = line.length(); for (int field = 1; field <= fieldNo && pos < length; field++) { StringBuilder content = new StringBuilder(); // two delimiter directly after each other if (line.charAt(pos) == delimiter) { pos++; continue; } // Handle quotes if (line.charAt(pos) == QUOTE) { pos++; // move over beginning quote while (pos < length) { // double quote if (line.charAt(pos) == QUOTE && pos+1 < length && line.charAt(pos+1) == QUOTE) { content.append(line.charAt(pos++)); pos++; } // end quote else if (line.charAt(pos) == QUOTE) { pos++; break; } // normal character else content.append(line.charAt(pos++)); } // we should be at end of line or a delimiter if (pos < length && line.charAt(pos) != delimiter) if (log.isLoggable(Level.INFO)) log.info("Did not find delimiter at pos " + pos + " " + line); pos++; // move over delimiter } else // plain copy { while (pos < length && line.charAt(pos) != delimiter) content.append(line.charAt(pos++)); pos++; // move over delimiter } if (field == fieldNo) return content.toString(); } // nothing found return ""; } // parseFlexFormat /** * Insert/Update Database. * @param ctx context * @param line line * @param trxName transaction * @return true if inserted/updated */ public boolean updateDB (Properties ctx, String line, String trxName) { if (line == null || line.trim().length() == 0) { if (log.isLoggable(Level.FINEST)) log.finest("No Line"); return false; } String[] nodes = parseLine (line, true, false, true); // with label, no trace, ignore empty if (nodes.length == 0) { if (log.isLoggable(Level.FINEST)) log.finest("Nothing parsed from: " + line); return false; } // Standard Fields int AD_Client_ID = Env.getAD_Client_ID(ctx); int AD_Org_ID = Env.getAD_Org_ID(ctx); if (getAD_Table_ID() == X_I_GLJournal.Table_ID) AD_Org_ID = 0; int UpdatedBy = Env.getAD_User_ID(ctx); // Check if the record is already there ------------------------------ StringBuilder sql = new StringBuilder ("SELECT COUNT(*), MAX(") .append(m_tablePK).append(") FROM ").append(m_tableName) .append(" WHERE AD_Client_ID=").append(AD_Client_ID).append(" AND ("); // String where1 = null; String where2 = null; String whereParentChild = null; for (int i = 0; i < nodes.length; i++) { if (nodes[i].endsWith("=''") || nodes[i].endsWith("=0")) ; else if (nodes[i].startsWith(m_tableUnique1 + "=")) where1 = nodes[i]; else if (nodes[i].startsWith(m_tableUnique2 + "=")) where2 = nodes[i]; else if (nodes[i].startsWith(m_tableUniqueParent + "=") || nodes[i].startsWith(m_tableUniqueChild + "=")) { if (whereParentChild == null) whereParentChild = nodes[i]; else whereParentChild += " AND " + nodes[i]; } } StringBuilder find = new StringBuilder(); if (where1 != null) find.append(where1); if (where2 != null) { if (find.length() > 0) find.append(" OR "); find.append(where2); } if (whereParentChild != null && whereParentChild.indexOf(" AND ") != -1) // need to have both criteria { if (find.length() > 0) find.append(" OR (").append(whereParentChild).append(")"); // may have only one else find.append(whereParentChild); } sql.append(find).append(")"); int count = 0; int ID = 0; if (find.length() > 0) { PreparedStatement pstmt = null; ResultSet rs = null; try { pstmt = DB.prepareStatement(sql.toString(), trxName); rs = pstmt.executeQuery(); if (rs.next()) { count = rs.getInt(1); if (count == 1) ID = rs.getInt(2); } } catch (SQLException e) { log.log(Level.SEVERE, sql.toString(), e); return false; } finally { DB.close(rs, pstmt); rs = null; pstmt = null; } } // Insert Basic Record ----------------------------------------------- if (ID == 0) { ID = DB.getNextID(ctx, m_tableName, null); // get ID sql = new StringBuilder("INSERT INTO ") .append(m_tableName).append("(").append(m_tablePK).append(",") .append("AD_Client_ID,AD_Org_ID,Created,CreatedBy,Updated,UpdatedBy,IsActive") // StdFields .append(") VALUES (").append(ID).append(",") .append(AD_Client_ID).append(",").append(AD_Org_ID) .append(",getDate(),").append(UpdatedBy).append(",getDate(),").append(UpdatedBy).append(",'Y'") .append(")"); // int no = DB.executeUpdate(sql.toString(), trxName); if (no != 1) { log.log(Level.SEVERE, "Insert records=" + no + "; SQL=" + sql.toString()); return false; } if (log.isLoggable(Level.FINER)) log.finer("New ID=" + ID + " " + find); } else { if (log.isLoggable(Level.WARNING)) log.warning("Not Inserted, Old ID=" + ID + " " + find); return false; } // Update Info ------------------------------------------------------- sql = new StringBuilder ("UPDATE ") .append(m_tableName).append(" SET "); for (int i = 0; i < nodes.length; i++) sql.append(nodes[i]).append(","); // column=value sql.append("IsActive='Y',Processed='N',I_IsImported='N',Updated=getDate(),UpdatedBy=").append(UpdatedBy); sql.append(" WHERE ").append(m_tablePK).append("=").append(ID); // Update Cmd int no = DB.executeUpdate(sql.toString(), trxName); if (no != 1) { log.log(Level.SEVERE, m_tablePK + "=" + ID + " - rows updated=" + no); return false; } return true; } // updateDB } // ImpFormat