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 * Files to include 081 * 082 * @parameter expression="${impex.includes}" default-value="*.sql" 083 */ 084 private String includes; 085 086 /** 087 * Files to exclude 088 * 089 * @parameter expression="${impex.excludes}" 090 */ 091 private String excludes; 092 093 /** 094 * 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 095 * the same, but the Liquibase metadata makes it appear as though the resulting SQL file has changed. By omitting the metadata you can 096 * run the same change set multiple times and get the exact same SQL file as output. This makes it much easier to perform validation 097 * checks on the SQL. 098 * 099 * @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 }