001// Copyright 2011 Leo Przybylski. All rights reserved.
002//
003// Redistribution and use in source and binary forms, with or without modification, are
004// permitted provided that the following conditions are met:
005//
006//    1. Redistributions of source code must retain the above copyright notice, this list of
007//       conditions and the following disclaimer.
008//
009//    2. Redistributions in binary form must reproduce the above copyright notice, this list
010//       of conditions and the following disclaimer in the documentation and/or other materials
011//       provided with the distribution.
012//
013// THIS SOFTWARE IS PROVIDED BY Leo Przybylski ''AS IS'' AND ANY EXPRESS OR IMPLIED
014// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
015// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
016// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
017// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
018// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
019// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
020// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
021// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
022//
023// The views and conclusions contained in the software and documentation are those of the
024// authors and should not be interpreted as representing official policies, either expressed
025// or implied, of Leo Przybylski.
026package org.kualigan.tools.ant.tasks;
027
028import org.apache.tools.ant.taskdefs.Jar;
029import org.apache.tools.ant.types.FileSet;
030
031import liquibase.resource.CompositeResourceAccessor;
032import liquibase.resource.FileSystemResourceAccessor;
033import liquibase.resource.ResourceAccessor;
034import liquibase.Liquibase;
035import liquibase.integration.ant.AntResourceAccessor;
036import liquibase.integration.ant.BaseLiquibaseTask;
037import liquibase.database.Database;
038import liquibase.database.DatabaseFactory;
039import liquibase.database.core.H2Database;
040import liquibase.database.jvm.JdbcConnection;
041import org.apache.tools.ant.BuildException;
042
043import org.h2.tools.Backup;
044import org.h2.tools.DeleteDbFiles;
045
046import org.kualigan.tools.liquibase.Diff;
047import org.kualigan.tools.liquibase.DiffResult;
048
049import java.io.File;
050import java.io.PrintStream;
051
052import java.sql.Connection;
053import java.sql.DriverManager;
054import java.sql.Statement;
055
056import static org.apache.tools.ant.Project.MSG_DEBUG;
057
058/**
059 *
060 * @author Leo Przybylski (przybyls@arizona.edu)
061 */
062public class GenerateChangeLog extends BaseLiquibaseTask {
063    private String source;
064    private String target;
065    private boolean stateSaved;
066
067    public GenerateChangeLog() { }
068
069    public boolean isStateSaved() {
070        return stateSaved;
071    }
072
073    public void setStateSaved(boolean ss) {
074        stateSaved = ss;
075    }
076    
077    public void setSource(String refid) {
078        this.source = refid;
079    }
080    
081    public String getSource() {
082        return this.source;
083    }
084
085    public void setTarget(String refid) {
086        this.target = refid;
087    }
088
089    public String getTarget() {
090        return this.target;
091    }
092    
093    public void execute() {
094        final RdbmsConfig source = (RdbmsConfig) getProject().getReference(getSource());
095        final RdbmsConfig target = (RdbmsConfig) getProject().getReference(getTarget());
096        Database lbSource = null;
097        Database lbTarget = null;
098        final DatabaseFactory factory = DatabaseFactory.getInstance();
099        try {
100            lbSource = factory.findCorrectDatabaseImplementation(new JdbcConnection(openConnection("source")));
101            lbSource.setDefaultSchemaName(source.getSchema());
102            lbTarget = factory.findCorrectDatabaseImplementation(new JdbcConnection(openConnection("target")));
103            lbTarget.setDefaultSchemaName(target.getSchema());
104            
105            exportSchema(lbSource, lbTarget);
106            if (isStateSaved()) {
107                exportData(lbSource, lbTarget);
108            }
109            
110            if (lbTarget instanceof H2Database) {
111                final Statement st = ((JdbcConnection) lbTarget.getConnection()).createStatement();
112                st.execute("SHUTDOWN DEFRAG");
113            }
114            
115        } catch (Exception e) {
116            throw new BuildException(e);
117        } finally {
118            try {
119                if (lbSource != null) {
120                    lbSource.close();
121                }
122                if (lbTarget != null) {
123                    lbTarget.close();
124                }
125            }
126            catch (Exception e) {
127            }
128        }
129
130        if (isStateSaved()) {
131            log("Starting data load from schema " + source.getSchema());
132            MigrateData migrateTask = new MigrateData();
133            migrateTask.bindToOwner(this);
134            migrateTask.init();
135            migrateTask.setSource(getSource());
136            migrateTask.setTarget("h2");
137            migrateTask.execute();
138            try {
139                Backup.execute("work/export/data.zip", "work/export", "", true);
140                
141                // delete the old database files
142                DeleteDbFiles.execute("split:22:work/export", "data", true);
143            }
144            catch (Exception e) {
145                throw new BuildException(e);
146            }
147        }
148    }
149
150    protected void exportSchema(Database source, Database target) {
151        try {
152            Diff diff = new Diff(source, source.getDefaultSchemaName());
153            exportTables(diff, target);
154            exportSequences(diff, target);
155            exportViews(diff, target);
156            exportIndexes(diff, target);
157            exportConstraints(diff, target);
158        }
159        catch (Exception e) {
160            throw new BuildException(e);
161        }
162    }
163
164    protected void export(Diff diff, Database target, String diffTypes, String suffix) {
165        diff.setDiffTypes(diffTypes);
166        
167        try {
168            DiffResult results = diff.compare();
169            results.printChangeLog(getChangeLogFile() + suffix, target);
170        } 
171        catch (Exception e) {
172            throw new BuildException(e);
173        }
174    }
175
176    protected void exportConstraints(Diff diff, Database target) {
177        export(diff, target, "foreignKeys", "-cst.xml");
178    }
179
180    protected void exportIndexes(Diff diff, Database target) {
181        export(diff, target, "indexes", "-idx.xml");
182    }
183
184    protected void exportViews(Diff diff, Database target) {
185        export(diff, target, "views", "-vw.xml");
186    }
187
188    protected void exportTables(Diff diff, Database target) {
189        export(diff, target, "tables, primaryKeys, uniqueConstraints", "-tab.xml");
190    }
191
192    protected void exportSequences(Diff diff, Database target) {
193        export(diff, target, "sequences", "-seq.xml");
194    }
195
196
197    private void exportData(Database source, Database target) {
198        Database h2db = null;
199        RdbmsConfig h2Config = new RdbmsConfig();
200        h2Config.setDriver("org.h2.Driver");
201        h2Config.setUrl("jdbc:h2:split:22:work/export/data");
202        h2Config.setUsername("SA");
203        h2Config.setPassword("");
204        h2Config.setSchema("PUBLIC");
205        getProject().addReference("h2", h2Config);
206        
207        final DatabaseFactory factory = DatabaseFactory.getInstance();
208        try {
209            h2db = factory.findCorrectDatabaseImplementation(new JdbcConnection(openConnection("h2")));
210            h2db.setDefaultSchemaName(h2Config.getSchema());
211            
212            export(new Diff(source, getDefaultSchemaName()), h2db, "tables", "-dat.xml");
213
214            ResourceAccessor antFO = new AntResourceAccessor(getProject(), classpath);
215            ResourceAccessor fsFO = new FileSystemResourceAccessor();
216            
217            String changeLogFile = getChangeLogFile() + "-dat.xml";
218
219            Liquibase liquibase = new Liquibase(changeLogFile, new CompositeResourceAccessor(antFO, fsFO), h2db);
220
221            log("Loading Schema");
222            liquibase.update(getContexts());
223            log("Finished Loading the Schema");
224
225        } 
226        catch (Exception e) {
227            throw new BuildException(e);
228        } 
229        finally {
230            try {
231                if (h2db != null) {
232                    // hsqldb.getConnection().createStatement().execute("SHUTDOWN");
233                    log("Closing h2 database");
234                    h2db.close();
235                }
236            }
237            catch (Exception e) {
238                if (!(e instanceof java.sql.SQLNonTransientConnectionException)) {
239                    e.printStackTrace();
240                }
241            }
242
243        }
244    }
245
246    private void debug(String msg) {
247        log(msg, MSG_DEBUG);
248    }
249
250    private Connection openSource() {
251        return openConnection(getSource());
252    }
253
254    private Connection openTarget() {
255        return openConnection(getTarget());
256    }
257
258    private Connection openConnection(String reference) {
259        final RdbmsConfig config = (RdbmsConfig) getProject().getReference(reference);
260        return openConnection(config);
261    }
262    
263
264
265    private Connection openConnection(RdbmsConfig config) {
266        Connection retval = null;
267        int retry_count = 0;
268        final int max_retry = 5;
269        while (retry_count < max_retry) {
270            try {
271                debug("Loading schema " + config.getSchema() + " at url " + config.getUrl());
272                Class.forName(config.getDriver());
273                retval = DriverManager.getConnection(config.getUrl(), config.getUsername(), config.getPassword());
274                retval.setAutoCommit(true);
275            }
276            catch (Exception e) {
277                if (!e.getMessage().contains("Database lock acquisition failure") && !(e instanceof NullPointerException)) {
278                    throw new BuildException(e);
279                }
280            }
281            finally {
282                retry_count++;
283            }
284        }
285        return retval;
286    }
287}