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 }