001    package liquibase.change.core;
002    
003    import java.io.IOException;
004    import java.io.InputStream;
005    import java.io.InputStreamReader;
006    import java.util.ArrayList;
007    import java.util.List;
008    
009    import liquibase.change.AbstractChange;
010    import liquibase.change.ChangeMetaData;
011    import liquibase.change.ChangeWithColumns;
012    import liquibase.change.CheckSum;
013    import liquibase.change.ColumnConfig;
014    import liquibase.database.Database;
015    import liquibase.exception.UnexpectedLiquibaseException;
016    import liquibase.resource.ResourceAccessor;
017    import liquibase.statement.SqlStatement;
018    import liquibase.statement.core.InsertStatement;
019    import liquibase.util.StringUtils;
020    import liquibase.util.csv.CSVReader;
021    
022    public class LoadDataChange extends AbstractChange implements ChangeWithColumns {
023    
024        private String schemaName;
025        private String tableName;
026        private String file;
027        private String encoding = null;
028        private String separator = liquibase.util.csv.opencsv.CSVReader.DEFAULT_SEPARATOR + "";
029        private String quotchar = liquibase.util.csv.opencsv.CSVReader.DEFAULT_QUOTE_CHARACTER + "";
030    
031        private List<LoadDataColumnConfig> columns = new ArrayList<LoadDataColumnConfig>();
032    
033        public LoadDataChange() {
034            super("loadData", "Load Data", ChangeMetaData.PRIORITY_DEFAULT);
035        }
036    
037        protected LoadDataChange(String changeName, String changeDescription) {
038            super(changeName, changeDescription, ChangeMetaData.PRIORITY_DEFAULT);
039        }
040    
041        public String getSchemaName() {
042            return schemaName;
043        }
044    
045        public void setSchemaName(String schemaName) {
046            this.schemaName = StringUtils.trimToNull(schemaName);
047        }
048    
049        public String getTableName() {
050            return tableName;
051        }
052    
053        public void setTableName(String tableName) {
054            this.tableName = tableName;
055        }
056    
057        public String getFile() {
058            return file;
059        }
060    
061        public void setFile(String file) {
062            this.file = file;
063        }
064    
065        public String getEncoding() {
066            return encoding;
067        }
068    
069        public void setEncoding(String encoding) {
070            this.encoding = encoding;
071        }
072    
073        public String getSeparator() {
074            return separator;
075        }
076    
077        public void setSeparator(String separator) {
078            this.separator = separator;
079        }
080    
081        public String getQuotchar() {
082            return quotchar;
083        }
084    
085        public void setQuotchar(String quotchar) {
086            this.quotchar = quotchar;
087        }
088    
089        @Override
090        public void addColumn(ColumnConfig column) {
091            columns.add((LoadDataColumnConfig) column);
092        }
093    
094        @Override
095        public List<ColumnConfig> getColumns() {
096            return (List<ColumnConfig>) (List) columns;
097        }
098    
099        @Override
100        public SqlStatement[] generateStatements(Database database) {
101            CSVReader reader = null;
102            try {
103                reader = getCSVReader();
104    
105                String[] headers = reader.readNext();
106                if (headers == null) {
107                    throw new UnexpectedLiquibaseException("Data file " + getFile() + " was empty");
108                }
109    
110                List<SqlStatement> statements = new ArrayList<SqlStatement>();
111                String[] line = null;
112                int lineNumber = 0;
113    
114                while ((line = reader.readNext()) != null) {
115                    lineNumber++;
116    
117                    if (line.length == 0 || (line.length == 1 && StringUtils.trimToNull(line[0]) == null)) {
118                        continue; // nothing on this line
119                    }
120                    InsertStatement insertStatement = this.createStatement(getSchemaName(), getTableName());
121                    for (int i = 0; i < headers.length; i++) {
122                        String columnName = null;
123                        if (i >= line.length) {
124                            throw new UnexpectedLiquibaseException("CSV Line " + lineNumber + " has only " + (i - 1)
125                                    + " columns, the header has " + headers.length);
126                        }
127    
128                        Object value = line[i];
129    
130                        ColumnConfig columnConfig = getColumnConfig(i, headers[i]);
131                        if (columnConfig != null) {
132                            columnName = columnConfig.getName();
133    
134                            if (value.toString().equalsIgnoreCase("NULL")) {
135                                value = "NULL";
136                            } else if (columnConfig.getType() != null) {
137                                ColumnConfig valueConfig = new ColumnConfig();
138                                if (columnConfig.getType().equalsIgnoreCase("BOOLEAN")) {
139                                    valueConfig.setValueBoolean(Boolean.parseBoolean(value.toString().toLowerCase()));
140                                } else if (columnConfig.getType().equalsIgnoreCase("NUMERIC")) {
141                                    valueConfig.setValueNumeric(value.toString());
142                                } else if (columnConfig.getType().toLowerCase().contains("date")
143                                        || columnConfig.getType().toLowerCase().contains("time")) {
144                                    valueConfig.setValueDate(value.toString());
145                                } else if (columnConfig.getType().equalsIgnoreCase("STRING")) {
146                                    valueConfig.setValue(value.toString());
147                                } else if (columnConfig.getType().equalsIgnoreCase("COMPUTED")) {
148                                    valueConfig.setValue(value.toString());
149                                } else {
150                                    throw new UnexpectedLiquibaseException("loadData type of " + columnConfig.getType()
151                                            + " is not supported.  Please use BOOLEAN, NUMERIC, DATE, STRING, or COMPUTED");
152                                }
153                                value = valueConfig.getValueObject();
154                            }
155                        }
156    
157                        if (columnName == null) {
158                            columnName = headers[i];
159                        }
160    
161                        insertStatement.addColumnValue(columnName, value);
162                    }
163                    statements.add(insertStatement);
164                }
165    
166                return statements.toArray(new SqlStatement[statements.size()]);
167            } catch (IOException e) {
168                throw new RuntimeException(e);
169            } finally {
170                if (null != reader) {
171                    try {
172                        reader.close();
173                    } catch (IOException e) {
174                        ;
175                    }
176                }
177            }
178        }
179    
180        protected CSVReader getCSVReader() throws IOException {
181            ResourceAccessor opener = getResourceAccessor();
182            if (opener == null) {
183                throw new UnexpectedLiquibaseException("No file opener specified for " + getFile());
184            }
185            InputStream stream = opener.getResourceAsStream(getFile());
186            if (stream == null) {
187                throw new UnexpectedLiquibaseException("Data file " + getFile() + " was not found");
188            }
189    
190            InputStreamReader streamReader;
191            if (getEncoding() == null) {
192                streamReader = new InputStreamReader(stream);
193            } else {
194                streamReader = new InputStreamReader(stream, getEncoding());
195            }
196    
197            char quotchar;
198            if (0 == this.quotchar.length()) {
199                // hope this is impossible to have a field surrounded with non ascii char 0x01
200                quotchar = '\1';
201            } else {
202                quotchar = this.quotchar.charAt(0);
203            }
204    
205            CSVReader reader = new CSVReader(streamReader, separator.charAt(0), quotchar);
206    
207            return reader;
208        }
209    
210        protected InsertStatement createStatement(String schemaName, String tableName) {
211            return new InsertStatement(schemaName, tableName);
212        }
213    
214        protected ColumnConfig getColumnConfig(int index, String header) {
215            for (LoadDataColumnConfig config : columns) {
216                if (config.getIndex() != null && config.getIndex().equals(index)) {
217                    return config;
218                }
219                if (config.getHeader() != null && config.getHeader().equalsIgnoreCase(header)) {
220                    return config;
221                }
222    
223                if (config.getName() != null && config.getName().equalsIgnoreCase(header)) {
224                    return config;
225                }
226            }
227            return null;
228        }
229    
230        @Override
231        public String getConfirmationMessage() {
232            return "Data loaded from " + getFile() + " into " + getTableName();
233        }
234    
235        @Override
236        public CheckSum generateCheckSum() {
237            InputStream stream = null;
238            try {
239                stream = getResourceAccessor().getResourceAsStream(getFile());
240                if (stream == null) {
241                    throw new RuntimeException(getFile() + " could not be found");
242                }
243                return CheckSum.compute(stream);
244            } catch (IOException e) {
245                throw new RuntimeException(e);
246            } finally {
247                if (stream != null) {
248                    try {
249                        stream.close();
250                    } catch (IOException e) {
251                        ;
252                    }
253                }
254            }
255        }
256    }