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