001    /**
002     * Copyright 2010-2013 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     */
016    package org.kuali.common.jdbc.convert;
017    
018    import java.io.BufferedReader;
019    import java.io.File;
020    import java.io.IOException;
021    import java.io.OutputStream;
022    
023    import org.apache.commons.io.FileUtils;
024    import org.apache.commons.io.IOUtils;
025    import org.apache.commons.lang3.StringUtils;
026    import org.kuali.common.jdbc.reader.DefaultSqlReader;
027    import org.kuali.common.jdbc.reader.SqlReader;
028    import org.kuali.common.jdbc.sql.model.SqlMetaData;
029    import org.kuali.common.util.LocationUtils;
030    import org.slf4j.Logger;
031    import org.slf4j.LoggerFactory;
032    
033    /**
034     * This class provides a common framework for SqlConverter classes which merge insert statements into batch inserts
035     * 
036     * @author andrewlubbers
037     */
038    public abstract class AbstractInsertMergeSqlConverter implements SqlConverter {
039    
040            private static final Logger logger = LoggerFactory.getLogger(AbstractInsertMergeSqlConverter.class);
041    
042            private static final String LF = "\n";
043    
044            @Override
045            public ConversionResult convert(ConversionContext context) {
046                    logger.debug("Converting {}", LocationUtils.getCanonicalPath(context.getOldFile()));
047                    File newFile = context.getNewFile();
048                    File oldFile = context.getOldFile();
049                    DefaultSqlReader reader = new DefaultSqlReader(context.getDelimiter());
050    
051                    BufferedReader in = null;
052                    OutputStream out = null;
053                    try {
054                            in = LocationUtils.getBufferedReader(oldFile, context.getEncoding());
055                            out = FileUtils.openOutputStream(newFile);
056                            return convert(context, reader, in, out);
057                    } catch (IOException e) {
058                            throw new IllegalStateException("Unexpected IO error");
059                    } finally {
060                            IOUtils.closeQuietly(in);
061                            IOUtils.closeQuietly(out);
062                    }
063            }
064    
065            protected ConversionResult convert(ConversionContext context, SqlReader reader, BufferedReader in, OutputStream out) throws IOException {
066                    String sql = reader.getSql(in);
067                    while (sql != null) {
068                            String outputSql = getOutputSql(context, in, sql, reader);
069                            out.write(outputSql.getBytes(context.getEncoding()));
070                            sql = reader.getSql(in);
071                    }
072                    SqlMetaData before = getMetaData(context.getOldFile(), reader, context.getEncoding());
073                    SqlMetaData after = getMetaData(context.getNewFile(), reader, context.getEncoding());
074                    return new ConversionResult(context.getOldFile(), context.getNewFile(), before, after);
075            }
076    
077            protected String getOutputSql(ConversionContext context, BufferedReader in, String sql, SqlReader reader) throws IOException {
078                    boolean insertStatement = isInsert(sql);
079                    if (insertStatement) {
080                            SqlInsertContext mc = new SqlInsertContext();
081                            mc.setSql(sql);
082                            mc.setReader(reader);
083                            mc.setInput(in);
084                            return combineInserts(context, mc);
085                    } else {
086                            // Add the sql followed by linefeed->delimiter->linefeed
087                            StringBuilder sqlBuilder = new StringBuilder(sql);
088                            sqlBuilder.append(getLineFeed()).append(context.getDelimiter()).append(getLineFeed());
089                            return sqlBuilder.toString();
090                    }
091            }
092    
093            /**
094             * Performs the real work of combining insert statements into batch inserts.
095             * 
096             * @param conversionContext
097             *            reference to meta data for this conversion process
098             * @param sqlInsertContext
099             *            contains the sql to convert and IO objects to write conversion results
100             * @return converted sql
101             */
102            protected abstract String combineInserts(ConversionContext conversionContext, SqlInsertContext sqlInsertContext) throws IOException;
103    
104            protected boolean isInsert(String sql) {
105                    String trimmed = StringUtils.trim(sql);
106                    return StringUtils.startsWith(trimmed, getInsertPrefix());
107            }
108    
109            /**
110             * Returns the platform-specific prefix for an INSERT statement
111             * 
112             * @return the prefix
113             */
114            public abstract String getInsertPrefix();
115    
116            protected SqlMetaData getMetaData(File file, SqlReader reader, String encoding) {
117                    BufferedReader in = null;
118                    try {
119                            in = LocationUtils.getBufferedReader(file, encoding);
120                            return reader.getMetaData(in);
121                    } catch (IOException e) {
122                            throw new IllegalStateException("Unexpected IO error");
123                    } finally {
124                            IOUtils.closeQuietly(in);
125                    }
126            }
127    
128            public String getLineFeed() {
129                    return LF;
130            }
131    
132            /**
133             * A common test to determine if the current insert batch should continue adding data
134             * 
135             * @param sql
136             *            the remaining sql to process
137             * @param count
138             *            the number of inserts already processed
139             * @param length
140             *            the character length of the inserts already processed
141             * @param context
142             *            the metadata for this conversion
143             * @return True if the current batch should be appended to, false otherwise
144             */
145            protected boolean continueBatch(String sql, int count, int length, ConversionContext context) {
146                    if (sql == null) {
147                            return false;
148                    }
149                    if (!isInsert(sql)) {
150                            return false;
151                    }
152                    if (count >= context.getMaxCount()) {
153                            return false;
154                    }
155                    if (length >= context.getMaxLength()) {
156                            return false;
157                    }
158                    return true;
159            }
160    }