001 /**
002 * Copyright 2004-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.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 * CSV list of regex patterns for files to include
081 *
082 * @parameter expression="${impex.includes}" default-value="\*\*\/*.sql"
083 * @required
084 */
085 private String includes;
086
087 /**
088 * CSV list of regex patterns for files to exclude
089 *
090 * @parameter expression="${impex.excludes}" default-value="\*\*\/.svn/**,\*\*\/.git/**"
091 */
092 private String excludes;
093
094 /**
095 * 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
096 * the same, but the Liquibase metadata makes it appear as though the resulting SQL file has changed. By omitting the metadata you can
097 * run the same change set multiple times and get the exact same SQL file as output. This makes it much easier to perform validation
098 * checks on the SQL.
099 *
100 * @parameter expression="${impex.skipIrrelevantLiquibaseMetadataLines}" default-value="false"
101 */
102 private boolean skipIrrelevantLiquibaseMetadataLines;
103
104 private List<String> liquibaseTokens = getLiquibaseTokens();
105
106 @Override
107 public void execute() throws MojoExecutionException {
108 try {
109 // Make sure the sourceDir exists
110 FileUtils.forceMkdir(sourceDir);
111 getLog().info("Source Dir - " + sourceDir.getCanonicalPath());
112 getLog().info("Output Dir - " + outputDir.getCanonicalPath());
113 getLog().info("Includes - " + includes);
114 getLog().info("Excludes - " + excludes);
115 getLog().info("Old Delimiter - " + oldDelimiter);
116 getLog().info("New Delimiter - " + newDelimiter);
117 getLog().info("Encoding - " + encoding);
118 List<File> files = getFiles();
119 if (files == null || files.size() == 0) {
120 getLog().info("No files found");
121 return;
122 }
123 getLog().info("Found " + files.size() + " SQL files to convert");
124 convert(files);
125 } catch (Exception e) {
126 throw new MojoExecutionException("Unexpected error", e);
127 }
128 }
129
130 protected void convert(List<File> files) throws IOException {
131 for (File file : files) {
132 convert(file);
133 }
134 }
135
136 protected List<String> getLiquibaseTokens() {
137 List<String> tokens = new ArrayList<String>();
138 tokens.add("-- Ran at:");
139 tokens.add("-- Against:");
140 tokens.add("-- Liquibase version:");
141 tokens.add("-- Ran at:");
142 tokens.add("-- Against:");
143 tokens.add("-- Liquibase version:");
144 return tokens;
145 }
146
147 protected boolean isSkipLine(String line) {
148 if (!skipIrrelevantLiquibaseMetadataLines) {
149 return false;
150 }
151 for (String token : liquibaseTokens) {
152 if (line.startsWith(token)) {
153 return true;
154 }
155 }
156 return false;
157 }
158
159 protected String getRelativePath(File dir, File file) throws IOException {
160 String dirPath = dir.getCanonicalPath() + FS;
161 String filePath = file.getCanonicalPath();
162 return StringUtils.remove(filePath, dirPath);
163 }
164
165 protected void convert(File file) throws IOException {
166 String outputFilename = outputDir + FS + getRelativePath(sourceDir, file);
167 File outputFile = new File(outputFilename);
168 @SuppressWarnings("unchecked")
169 List<String> lines = FileUtils.readLines(file, encoding);
170 getLog().info("Writing " + outputFile.getCanonicalPath());
171 OutputStream out = null;
172 try {
173 out = FileUtils.openOutputStream(outputFile);
174 for (String line : lines) {
175 if (isSkipLine(line)) {
176 continue;
177 }
178 String convertedLine = getConvertedLine(line);
179 out.write(convertedLine.getBytes(encoding));
180 }
181 } finally {
182 IOUtils.closeQuietly(out);
183 }
184 }
185
186 protected String getConvertedLine(String line) {
187 String trimmed = StringUtils.trimToNull(line);
188 if (StringUtils.endsWith(trimmed, oldDelimiter)) {
189 int pos = line.lastIndexOf(oldDelimiter);
190 return line.substring(0, pos) + IOUtils.LINE_SEPARATOR_UNIX + newDelimiter + IOUtils.LINE_SEPARATOR_UNIX;
191 } else {
192 return line + IOUtils.LINE_SEPARATOR_UNIX;
193 }
194 }
195
196 protected List<File> getFiles() throws IOException {
197 getLog().info("Examining " + sourceDir.getCanonicalPath());
198 String[] includeTokens = StringUtils.split(includes, ",");
199 String[] excludeTokens = StringUtils.split(excludes, ",");
200 SimpleScanner scanner = new SimpleScanner(sourceDir, includeTokens, excludeTokens);
201 return scanner.getFiles();
202 }
203
204 public File getSourceDir() {
205 return sourceDir;
206 }
207
208 public void setSourceDir(File inputDir) {
209 this.sourceDir = inputDir;
210 }
211
212 public File getOutputDir() {
213 return outputDir;
214 }
215
216 public void setOutputDir(File outputDir) {
217 this.outputDir = outputDir;
218 }
219
220 public String getIncludes() {
221 return includes;
222 }
223
224 public void setIncludes(String includes) {
225 this.includes = includes;
226 }
227
228 public String getExcludes() {
229 return excludes;
230 }
231
232 public void setExcludes(String excludes) {
233 this.excludes = excludes;
234 }
235
236 public String getEncoding() {
237 return encoding;
238 }
239
240 public void setEncoding(String encoding) {
241 this.encoding = encoding;
242 }
243
244 public String getOldDelimiter() {
245 return oldDelimiter;
246 }
247
248 public void setOldDelimiter(String oldDelimiter) {
249 this.oldDelimiter = oldDelimiter;
250 }
251
252 public String getNewDelimiter() {
253 return newDelimiter;
254 }
255
256 public void setNewDelimiter(String newDelimiter) {
257 this.newDelimiter = newDelimiter;
258 }
259
260 public boolean isSkipIrrelevantLiquibaseMetadataLines() {
261 return skipIrrelevantLiquibaseMetadataLines;
262 }
263
264 public void setSkipIrrelevantLiquibaseMetadataLines(boolean skipIrrelevantLiquibaseMetadataLines) {
265 this.skipIrrelevantLiquibaseMetadataLines = skipIrrelevantLiquibaseMetadataLines;
266 }
267
268 }