View Javadoc

1   /**
2    * Copyright 2006-2012 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  	 * Files to include
81  	 *
82  	 * @parameter expression="${impex.includes}" default-value="*.sql"
83  	 */
84  	private String includes;
85  
86  	/**
87  	 * Files to exclude
88  	 *
89  	 * @parameter expression="${impex.excludes}"
90  	 */
91  	private String excludes;
92  
93  	/**
94  	 * 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
95  	 * the same, but the Liquibase metadata makes it appear as though the resulting SQL file has changed. By omitting the metadata you can
96  	 * run the same change set multiple times and get the exact same SQL file as output. This makes it much easier to perform validation
97  	 * checks on the SQL.
98  	 *
99  	 * @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 }