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    }