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 * 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 }