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.CatalogAndSchema;
032import liquibase.Liquibase;
033import liquibase.resource.CompositeResourceAccessor;
034import liquibase.resource.FileSystemResourceAccessor;
035import liquibase.resource.ResourceAccessor;
036import liquibase.integration.ant.AntResourceAccessor;
037import liquibase.integration.ant.BaseLiquibaseTask;
038import liquibase.database.Database;
039import liquibase.database.DatabaseFactory;
040import liquibase.database.core.H2Database;
041import liquibase.database.jvm.JdbcConnection;
042import liquibase.structure.DatabaseObject;
043import liquibase.snapshot.DatabaseSnapshot;
044import liquibase.snapshot.SnapshotControl;
045import liquibase.snapshot.SnapshotGeneratorFactory;
046import org.apache.tools.ant.BuildException;
047
048import org.h2.tools.Backup;
049import org.h2.tools.DeleteDbFiles;
050
051import liquibase.ext.kualigan.diff.DiffGenerator;
052import liquibase.diff.DiffGeneratorFactory;
053import liquibase.diff.DiffResult;
054import liquibase.diff.compare.CompareControl;
055
056import java.io.File;
057import java.io.PrintStream;
058
059import java.sql.Connection;
060import java.sql.DriverManager;
061import java.sql.Statement;
062
063import java.util.Arrays;
064import java.util.HashSet;
065import java.util.Set;
066
067import static org.apache.tools.ant.Project.MSG_DEBUG;
068
069/**
070 *
071 * @author Leo Przybylski (przybyls@arizona.edu)
072 */
073public class GenerateChangeLog extends BaseLiquibaseTask {
074    private String source;
075    private String target;
076    private boolean stateSaved;
077
078    public GenerateChangeLog() { }
079
080    public boolean isStateSaved() {
081        return stateSaved;
082    }
083
084    public void setStateSaved(boolean ss) {
085        stateSaved = ss;
086    }
087    
088    public void setSource(String refid) {
089        this.source = refid;
090    }
091    
092    public String getSource() {
093        return this.source;
094    }
095
096    public void setTarget(String refid) {
097        this.target = refid;
098    }
099
100    public String getTarget() {
101        return this.target;
102    }
103    
104    @Override
105    protected void executeWithLiquibaseClassloader() throws BuildException {
106        final RdbmsConfig source = (RdbmsConfig) getProject().getReference(getSource());
107        final RdbmsConfig target = (RdbmsConfig) getProject().getReference(getTarget());
108        Database lbSource = null;
109        Database lbTarget = null;
110        final DatabaseFactory factory = DatabaseFactory.getInstance();
111        try {
112            lbSource = factory.findCorrectDatabaseImplementation(new JdbcConnection(openConnection("source")));
113            lbSource.setDefaultSchemaName(source.getSchema());
114            lbTarget = factory.findCorrectDatabaseImplementation(new JdbcConnection(openConnection("target")));
115            lbTarget.setDefaultSchemaName(target.getSchema());
116            
117            exportSchema(lbSource, lbTarget);
118            if (isStateSaved()) {
119                exportData(lbSource, lbTarget);
120            }
121            
122            if (lbTarget instanceof H2Database) {
123                final Statement st = ((JdbcConnection) lbTarget.getConnection()).createStatement();
124                st.execute("SHUTDOWN DEFRAG");
125            }
126            
127        } catch (Exception e) {
128            throw new BuildException(e);
129        } finally {
130            try {
131                if (lbSource != null) {
132                    lbSource.close();
133                }
134                if (lbTarget != null) {
135                    lbTarget.close();
136                }
137            }
138            catch (Exception e) {
139            }
140        }
141
142        if (isStateSaved()) {
143            log("Starting data load from schema " + source.getSchema());
144            MigrateData migrateTask = new MigrateData();
145            migrateTask.bindToOwner(this);
146            migrateTask.init();
147            migrateTask.setSource(getSource());
148            migrateTask.setTarget("h2");
149            migrateTask.execute();
150            try {
151                Backup.execute("work/export/data.zip", "work/export", "", true);
152                
153                // delete the old database files
154                DeleteDbFiles.execute("split:22:work/export", "data", true);
155            }
156            catch (Exception e) {
157                throw new BuildException(e);
158            }
159        }
160    }
161
162    protected void exportSchema(final Database source, final Database target) {
163        try {
164            exportTables(source, target);
165            exportSequences(source, target);
166            exportViews(source, target);
167            exportIndexes(source, target);
168            exportConstraints(source, target);
169        }
170        catch (Exception e) {
171            throw new BuildException(e);
172        }
173    }
174
175    protected void export(final Database source, 
176                          final Database target, 
177                          final String snapshotTypes, 
178                          final String suffix) throws Exception {
179        final CatalogAndSchema catalogAndSchema = source.getDefaultSchema();
180        final SnapshotControl snapshotControl = new SnapshotControl(source, snapshotTypes);
181        final CompareControl compareControl   = new CompareControl(new CompareControl.SchemaComparison[]{new CompareControl.SchemaComparison(catalogAndSchema, catalogAndSchema)}, snapshotTypes);
182        //        compareControl.addStatusListener(new OutDiffStatusListener());
183
184        final DatabaseSnapshot referenceSnapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(compareControl.getSchemas(CompareControl.DatabaseRole.REFERENCE), source, snapshotControl);
185        final DatabaseSnapshot comparisonSnapshot = SnapshotGeneratorFactory.getInstance().createSnapshot(compareControl.getSchemas(CompareControl.DatabaseRole.REFERENCE), target, snapshotControl);
186        // diff.setDiffTypes(snapshotTypes);
187
188        final DiffResult results = DiffGeneratorFactory.getInstance().compare(referenceSnapshot, comparisonSnapshot, compareControl);
189        // results.printChangeLog(getChangeLogFile() + suffix, target);
190    }
191
192    protected void exportConstraints(final Database source, final Database target) throws Exception {
193        export(source, target, "foreignKeys", "-cst.xml");
194    }
195
196    protected void exportIndexes(final Database source, final Database target) throws Exception {
197        export(source, target, "indexes", "-idx.xml");
198    }
199
200    protected void exportViews(final Database source, final Database target) throws Exception {
201        export(source, target, "views", "-vw.xml");
202    }
203
204    protected void exportTables(final Database source, final Database target) throws Exception {
205        export(source, target, "tables, primaryKeys, uniqueConstraints", "-tab.xml");
206    }
207
208    protected void exportSequences(final Database source, final Database target) throws Exception {
209        export(source, target, "sequences", "-seq.xml");
210    }
211
212
213    private void exportData(final Database source, final Database target) {
214        Database h2db = null;
215        RdbmsConfig h2Config = new RdbmsConfig();
216        h2Config.setDriver("org.h2.Driver");
217        h2Config.setUrl("jdbc:h2:split:22:work/export/data");
218        h2Config.setUsername("SA");
219        h2Config.setPassword("");
220        h2Config.setSchema("PUBLIC");
221        getProject().addReference("h2", h2Config);
222        
223        final DatabaseFactory factory = DatabaseFactory.getInstance();
224        try {
225            h2db = factory.findCorrectDatabaseImplementation(new JdbcConnection(openConnection("h2")));
226            h2db.setDefaultSchemaName(h2Config.getSchema());
227            
228            // export(new Diff(source, getDefaultSchemaName()), h2db, "tables", "-dat.xml");
229
230            ResourceAccessor antFO = new AntResourceAccessor(getProject(), classpath);
231            ResourceAccessor fsFO = new FileSystemResourceAccessor();
232            
233            String changeLogFile = getChangeLogFile() + "-dat.xml";
234
235            Liquibase liquibase = new Liquibase(changeLogFile, new CompositeResourceAccessor(antFO, fsFO), h2db);
236
237            log("Loading Schema");
238            liquibase.update(getContexts());
239            log("Finished Loading the Schema");
240
241        } 
242        catch (Exception e) {
243            throw new BuildException(e);
244        } 
245        finally {
246            try {
247                if (h2db != null) {
248                    // hsqldb.getConnection().createStatement().execute("SHUTDOWN");
249                    log("Closing h2 database");
250                    h2db.close();
251                }
252            }
253            catch (Exception e) {
254                if (!(e instanceof java.sql.SQLNonTransientConnectionException)) {
255                    e.printStackTrace();
256                }
257            }
258
259        }
260    }
261
262    private void debug(String msg) {
263        log(msg, MSG_DEBUG);
264    }
265
266    private Connection openSource() {
267        return openConnection(getSource());
268    }
269
270    private Connection openTarget() {
271        return openConnection(getTarget());
272    }
273
274    private Connection openConnection(String reference) {
275        final RdbmsConfig config = (RdbmsConfig) getProject().getReference(reference);
276        return openConnection(config);
277    }
278    
279
280
281    private Connection openConnection(RdbmsConfig config) {
282        Connection retval = null;
283        int retry_count = 0;
284        final int max_retry = 5;
285        while (retry_count < max_retry) {
286            try {
287                debug("Loading schema " + config.getSchema() + " at url " + config.getUrl());
288                Class.forName(config.getDriver());
289                retval = DriverManager.getConnection(config.getUrl(), config.getUsername(), config.getPassword());
290                retval.setAutoCommit(true);
291            }
292            catch (Exception e) {
293                if (!e.getMessage().contains("Database lock acquisition failure") && !(e instanceof NullPointerException)) {
294                    throw new BuildException(e);
295                }
296            }
297            finally {
298                retry_count++;
299            }
300        }
301        return retval;
302    }
303}