001 /** 002 * Copyright 2006-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 the delimiter style from using a <code>;</code> at the end of the line to a <code>/</code> on it's own line 033 * 034 * @goal convertsql 035 */ 036 public class ConvertSQLMojo extends AbstractMojo { 037 private static final String FS = File.separator; 038 039 /** 040 * The encoding to use when reading/writing files 041 * 042 * @parameter expression="${impex.encoding}" default-value="${project.build.sourceEncoding}" 043 */ 044 private String encoding; 045 046 /** 047 * The old delimiter used to terminate a SQL statement by being present at the end of a line. 048 * 049 * @parameter expression="${impex.oldDelimiter}" default-value=";" 050 * @required 051 */ 052 private String oldDelimiter; 053 054 /** 055 * The new delimiter to put on its own line 056 * 057 * @parameter expression="${impex.newDelimiter}" default-value="/" 058 * @required 059 */ 060 private String newDelimiter; 061 062 /** 063 * Director to examine for .sql files 064 * 065 * @parameter expression="${impex.sourceDir}" default-value="${project.build.directory}/sql/source" 066 * @required 067 */ 068 private File sourceDir; 069 070 /** 071 * Directory to generate the converted files into 072 * 073 * @parameter expression="${impex.outputDir}" default-value="${project.build.directory}/sql/output" 074 * @required 075 */ 076 private File outputDir; 077 078 /** 079 * Files to include 080 * 081 * @parameter expression="${impex.includes}" default-value="*.sql" 082 */ 083 private String includes; 084 085 /** 086 * Files to exclude 087 * 088 * @parameter expression="${impex.excludes}" 089 */ 090 private String excludes; 091 092 /** 093 * 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 094 * the same, but the Liquibase metadata makes it appear as though the resulting SQL file has changed. By omitting the metadata you can 095 * run the same change set multiple times and get the exact same SQL file as output. This makes it much easier to perform validation 096 * checks on the SQL. 097 * 098 * @parameter expression="${impex.skipIrrelevantLiquibaseMetadataLines}" default-value="false" 099 */ 100 private boolean skipIrrelevantLiquibaseMetadataLines; 101 102 @Override 103 public void execute() throws MojoExecutionException { 104 try { 105 List<File> files = getFiles(); 106 if (files == null || files.size() == 0) { 107 getLog().info("No files found"); 108 return; 109 } 110 getLog().info("Found " + files.size() + " SQL files to convert"); 111 convert(files); 112 } catch (Exception e) { 113 throw new MojoExecutionException("Unexpected error", e); 114 } 115 } 116 117 protected void convert(List<File> files) throws IOException { 118 for (File file : files) { 119 convert(file); 120 } 121 } 122 123 protected List<String> getLiquibaseTokens() { 124 List<String> tokens = new ArrayList<String>(); 125 tokens.add("-- Ran at:"); 126 tokens.add("-- Against:"); 127 tokens.add("-- Liquibase version:"); 128 return tokens; 129 } 130 131 protected boolean isSkipLine(String line) { 132 if (!skipIrrelevantLiquibaseMetadataLines) { 133 return false; 134 } 135 List<String> tokens = getLiquibaseTokens(); 136 for (String token : tokens) { 137 if (line.startsWith(token)) { 138 return true; 139 } 140 } 141 return false; 142 } 143 144 protected void convert(File file) throws IOException { 145 String outputFilename = outputDir + FS + file.getName(); 146 File outputFile = new File(outputFilename); 147 @SuppressWarnings("unchecked") 148 List<String> lines = FileUtils.readLines(file, encoding); 149 getLog().info("Creating " + outputFile.getCanonicalPath()); 150 OutputStream out = null; 151 try { 152 out = FileUtils.openOutputStream(outputFile); 153 for (String line : lines) { 154 if (isSkipLine(line)) { 155 continue; 156 } 157 String convertedLine = getConvertedLine(line); 158 out.write(convertedLine.getBytes(encoding)); 159 } 160 } finally { 161 IOUtils.closeQuietly(out); 162 } 163 } 164 165 protected String getConvertedLine(String line) { 166 String trimmed = StringUtils.trim(line); 167 if (trimmed.endsWith(oldDelimiter)) { 168 int pos = line.lastIndexOf(oldDelimiter); 169 return line.substring(0, pos) + IOUtils.LINE_SEPARATOR_UNIX + newDelimiter + IOUtils.LINE_SEPARATOR_UNIX; 170 } else { 171 return line + IOUtils.LINE_SEPARATOR_UNIX; 172 } 173 } 174 175 protected List<File> getFiles() throws IOException { 176 FileUtils.forceMkdir(sourceDir); 177 getLog().info("Examining " + sourceDir.getCanonicalPath()); 178 SimpleScanner scanner = new SimpleScanner(sourceDir, includes, excludes); 179 return scanner.getFiles(); 180 } 181 182 public File getSourceDir() { 183 return sourceDir; 184 } 185 186 public void setSourceDir(File inputDir) { 187 this.sourceDir = inputDir; 188 } 189 190 public File getOutputDir() { 191 return outputDir; 192 } 193 194 public void setOutputDir(File outputDir) { 195 this.outputDir = outputDir; 196 } 197 198 public String getIncludes() { 199 return includes; 200 } 201 202 public void setIncludes(String includes) { 203 this.includes = includes; 204 } 205 206 public String getExcludes() { 207 return excludes; 208 } 209 210 public void setExcludes(String excludes) { 211 this.excludes = excludes; 212 } 213 214 public String getEncoding() { 215 return encoding; 216 } 217 218 public void setEncoding(String encoding) { 219 this.encoding = encoding; 220 } 221 222 public String getOldDelimiter() { 223 return oldDelimiter; 224 } 225 226 public void setOldDelimiter(String oldDelimiter) { 227 this.oldDelimiter = oldDelimiter; 228 } 229 230 public String getNewDelimiter() { 231 return newDelimiter; 232 } 233 234 public void setNewDelimiter(String newDelimiter) { 235 this.newDelimiter = newDelimiter; 236 } 237 238 public boolean isSkipIrrelevantLiquibaseMetadataLines() { 239 return skipIrrelevantLiquibaseMetadataLines; 240 } 241 242 public void setSkipIrrelevantLiquibaseMetadataLines(boolean skipIrrelevantLiquibaseMetadataLines) { 243 this.skipIrrelevantLiquibaseMetadataLines = skipIrrelevantLiquibaseMetadataLines; 244 } 245 246 }