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    }