View Javadoc

1   /**
2    * Copyright 2004-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.torque.mojo;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.io.OutputStream;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import org.apache.commons.io.FileUtils;
25  import org.apache.commons.io.IOUtils;
26  import org.apache.commons.lang.StringUtils;
27  import org.apache.maven.plugin.AbstractMojo;
28  import org.apache.maven.plugin.MojoExecutionException;
29  import org.apache.torque.util.SimpleScanner;
30  
31  /**
32   * Examine SQL files and convert them to always use the delimiter <code>/</code> on it's own line. Convert any lines ending with
33   * <code>;</code> to use a <code>/</code> on it's own line.
34   *
35   * @goal convertsql
36   */
37  public class ConvertSQLMojo extends AbstractMojo {
38  	private static final String FS = File.separator;
39  
40  	/**
41  	 * The encoding to use when reading/writing files
42  	 *
43  	 * @parameter expression="${impex.encoding}" default-value="${project.build.sourceEncoding}"
44  	 */
45  	private String encoding;
46  
47  	/**
48  	 * The old delimiter used to terminate a SQL statement by being present at the end of a line.
49  	 *
50  	 * @parameter expression="${impex.oldDelimiter}" default-value=";"
51  	 * @required
52  	 */
53  	private String oldDelimiter;
54  
55  	/**
56  	 * The new delimiter to put on its own line
57  	 *
58  	 * @parameter expression="${impex.newDelimiter}" default-value="/"
59  	 * @required
60  	 */
61  	private String newDelimiter;
62  
63  	/**
64  	 * Directory to examine for .sql files
65  	 *
66  	 * @parameter expression="${impex.sourceDir}" default-value="${project.build.directory}/sql/source"
67  	 * @required
68  	 */
69  	private File sourceDir;
70  
71  	/**
72  	 * Directory to generate the converted files into
73  	 *
74  	 * @parameter expression="${impex.outputDir}" default-value="${project.build.directory}/sql/output"
75  	 * @required
76  	 */
77  	private File outputDir;
78  
79  	/**
80  	 * CSV list of regex patterns for files to include
81  	 *
82  	 * @parameter expression="${impex.includes}" default-value="\*\*\/*.sql"
83  	 * @required
84  	 */
85  	private String includes;
86  
87  	/**
88  	 * CSV list of regex patterns for files to exclude
89  	 *
90  	 * @parameter expression="${impex.excludes}" default-value="\*\*\/.svn/**,\*\*\/.git/**"
91  	 */
92  	private String excludes;
93  
94  	/**
95  	 * 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
96  	 * the same, but the Liquibase metadata makes it appear as though the resulting SQL file has changed. By omitting the metadata you can
97  	 * run the same change set multiple times and get the exact same SQL file as output. This makes it much easier to perform validation
98  	 * checks on the SQL.
99  	 *
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 }