001 /**
002 * Copyright 2004-2012 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.apache.torque.mojo;
017
018 import java.io.File;
019 import java.io.IOException;
020 import java.io.OutputStream;
021 import java.util.ArrayList;
022 import java.util.List;
023
024 import org.apache.commons.io.FileUtils;
025 import org.apache.commons.io.IOUtils;
026 import org.apache.commons.lang.StringUtils;
027 import org.apache.maven.plugin.AbstractMojo;
028 import org.apache.maven.plugin.MojoExecutionException;
029 import org.apache.torque.util.SimpleScanner;
030
031 /**
032 * Examine SQL files and convert them to always use the delimiter <code>/</code> on it's own line. Convert any lines ending with
033 * <code>;</code> to use a <code>/</code> on it's own line.
034 *
035 * @goal convertsql
036 */
037 public class ConvertSQLMojo extends AbstractMojo {
038 private static final String FS = File.separator;
039
040 /**
041 * The encoding to use when reading/writing files
042 *
043 * @parameter expression="${impex.encoding}" default-value="${project.build.sourceEncoding}"
044 */
045 private String encoding;
046
047 /**
048 * The old delimiter used to terminate a SQL statement by being present at the end of a line.
049 *
050 * @parameter expression="${impex.oldDelimiter}" default-value=";"
051 * @required
052 */
053 private String oldDelimiter;
054
055 /**
056 * The new delimiter to put on its own line
057 *
058 * @parameter expression="${impex.newDelimiter}" default-value="/"
059 * @required
060 */
061 private String newDelimiter;
062
063 /**
064 * Directory to examine for .sql files
065 *
066 * @parameter expression="${impex.sourceDir}" default-value="${project.build.directory}/sql/source"
067 * @required
068 */
069 private File sourceDir;
070
071 /**
072 * Directory to generate the converted files into
073 *
074 * @parameter expression="${impex.outputDir}" default-value="${project.build.directory}/sql/output"
075 * @required
076 */
077 private File outputDir;
078
079 /**
080 * Files to include
081 *
082 * @parameter expression="${impex.includes}" default-value="*.sql"
083 */
084 private String includes;
085
086 /**
087 * Files to exclude
088 *
089 * @parameter expression="${impex.excludes}"
090 */
091 private String excludes;
092
093 /**
094 * Liquibase injects a handful of metadata at the top of each SQL file that causes "noise". All of the checksums and actual SQL may stay
095 * the same, but the Liquibase metadata makes it appear as though the resulting SQL file has changed. By omitting the metadata you can
096 * run the same change set multiple times and get the exact same SQL file as output. This makes it much easier to perform validation
097 * checks on the SQL.
098 *
099 * @parameter expression="${impex.skipIrrelevantLiquibaseMetadataLines}" default-value="false"
100 */
101 private boolean skipIrrelevantLiquibaseMetadataLines;
102
103 @Override
104 public void execute() throws MojoExecutionException {
105 try {
106 List<File> files = getFiles();
107 if (files == null || files.size() == 0) {
108 getLog().info("No files found");
109 return;
110 }
111 getLog().info("Found " + files.size() + " SQL files to convert");
112 convert(files);
113 } catch (Exception e) {
114 throw new MojoExecutionException("Unexpected error", e);
115 }
116 }
117
118 protected void convert(List<File> files) throws IOException {
119 for (File file : files) {
120 convert(file);
121 }
122 }
123
124 protected List<String> getLiquibaseTokens() {
125 List<String> tokens = new ArrayList<String>();
126 tokens.add("-- Ran at:");
127 tokens.add("-- Against:");
128 tokens.add("-- Liquibase version:");
129 tokens.add("-- Ran at:");
130 tokens.add("-- Against:");
131 tokens.add("-- Liquibase version:");
132 return tokens;
133 }
134
135 protected boolean isSkipLine(String line) {
136 if (!skipIrrelevantLiquibaseMetadataLines) {
137 return false;
138 }
139 List<String> tokens = getLiquibaseTokens();
140 for (String token : tokens) {
141 if (line.startsWith(token)) {
142 return true;
143 }
144 }
145 return false;
146 }
147
148 protected void convert(File file) throws IOException {
149 String outputFilename = outputDir + FS + file.getName();
150 File outputFile = new File(outputFilename);
151 @SuppressWarnings("unchecked")
152 List<String> lines = FileUtils.readLines(file, encoding);
153 getLog().info("Creating " + outputFile.getCanonicalPath());
154 OutputStream out = null;
155 try {
156 out = FileUtils.openOutputStream(outputFile);
157 for (String line : lines) {
158 if (isSkipLine(line)) {
159 continue;
160 }
161 String convertedLine = getConvertedLine(line);
162 out.write(convertedLine.getBytes(encoding));
163 }
164 } finally {
165 IOUtils.closeQuietly(out);
166 }
167 }
168
169 protected String getConvertedLine(String line) {
170 String trimmed = StringUtils.trim(line);
171 if (trimmed.endsWith(oldDelimiter)) {
172 int pos = line.lastIndexOf(oldDelimiter);
173 return line.substring(0, pos) + IOUtils.LINE_SEPARATOR_UNIX + newDelimiter + IOUtils.LINE_SEPARATOR_UNIX;
174 } else {
175 return line + IOUtils.LINE_SEPARATOR_UNIX;
176 }
177 }
178
179 protected List<File> getFiles() throws IOException {
180 FileUtils.forceMkdir(sourceDir);
181 getLog().info("Examining " + sourceDir.getCanonicalPath());
182 SimpleScanner scanner = new SimpleScanner(sourceDir, includes, excludes);
183 return scanner.getFiles();
184 }
185
186 public File getSourceDir() {
187 return sourceDir;
188 }
189
190 public void setSourceDir(File inputDir) {
191 this.sourceDir = inputDir;
192 }
193
194 public File getOutputDir() {
195 return outputDir;
196 }
197
198 public void setOutputDir(File outputDir) {
199 this.outputDir = outputDir;
200 }
201
202 public String getIncludes() {
203 return includes;
204 }
205
206 public void setIncludes(String includes) {
207 this.includes = includes;
208 }
209
210 public String getExcludes() {
211 return excludes;
212 }
213
214 public void setExcludes(String excludes) {
215 this.excludes = excludes;
216 }
217
218 public String getEncoding() {
219 return encoding;
220 }
221
222 public void setEncoding(String encoding) {
223 this.encoding = encoding;
224 }
225
226 public String getOldDelimiter() {
227 return oldDelimiter;
228 }
229
230 public void setOldDelimiter(String oldDelimiter) {
231 this.oldDelimiter = oldDelimiter;
232 }
233
234 public String getNewDelimiter() {
235 return newDelimiter;
236 }
237
238 public void setNewDelimiter(String newDelimiter) {
239 this.newDelimiter = newDelimiter;
240 }
241
242 public boolean isSkipIrrelevantLiquibaseMetadataLines() {
243 return skipIrrelevantLiquibaseMetadataLines;
244 }
245
246 public void setSkipIrrelevantLiquibaseMetadataLines(boolean skipIrrelevantLiquibaseMetadataLines) {
247 this.skipIrrelevantLiquibaseMetadataLines = skipIrrelevantLiquibaseMetadataLines;
248 }
249
250 }