001/*
002 * Copyright 2011 Atteo.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 */
014package org.atteo.moonshine.liquibase;
015
016import java.sql.SQLException;
017import java.util.Map;
018import java.util.Map.Entry;
019
020import javax.inject.Inject;
021import javax.sql.DataSource;
022
023import liquibase.Liquibase;
024import liquibase.database.DatabaseConnection;
025import liquibase.database.jvm.JdbcConnection;
026import liquibase.exception.DatabaseException;
027import liquibase.exception.LiquibaseException;
028import liquibase.resource.ClassLoaderResourceAccessor;
029import liquibase.resource.ResourceAccessor;
030
031/**
032 * Liquibase facade for migrations execution.
033 * <p>
034 * Usage:
035 * <pre>
036 * &#64;ImportService
037 * private DatabaseService database;
038 *
039 * ...
040 *
041 * database.registerMigration(new DatabaseMigration() {
042 *     &#64;Override
043 *     public void execute(DataSource dataSource) {
044 *         new LiquibaseFacade(datasource).migrate("/migrations/migration01.xml");
045 *     }
046 * }
047 * </pre>
048 * </p>
049 */
050public class LiquibaseFacade {
051    private static final String BEFORE_LAST_UPDATE = "BEFORE_LAST_UPDATE";
052
053    private final DataSource dataSource;
054
055    @Inject
056    public LiquibaseFacade(DataSource dataSource) {
057        this.dataSource = dataSource;
058    }
059
060    /**
061     * Migrate using given migration.
062     * @param changelog resource with the migration
063     */
064    public void migrate(String changelog) {
065        migrate(changelog, null);
066    }
067
068    /**
069     * Migrate using given migration.
070     * @param changelog resource with the migration
071     * @param contexts contexts, see {@link Liquibase#update(String)}.
072     */
073    public void migrate(String changelog, String contexts) {
074        migrate(changelog, contexts, null);
075    }
076
077    /**
078     * Migrate using given migration.
079     * @param changelog resource with the migration
080     * @param contexts contexts, see {@link Liquibase#update(String)}.
081     * @param changelogParameters changelog parameters, can be null
082     */
083    public void migrate(String changelog, String contexts, Map<String, Object> changelogParameters) {
084        ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor();
085
086        DatabaseConnection databaseConnection = null;
087        changelog = normalizeName(changelog);
088
089        try {
090            databaseConnection = new JdbcConnection(dataSource.getConnection());
091            Liquibase liquibase = new Liquibase(changelog, resourceAccessor, databaseConnection);
092            liquibase.tag(BEFORE_LAST_UPDATE);
093
094            if (changelogParameters != null) {
095                for (Entry<String, Object> entry : changelogParameters.entrySet()) {
096                    liquibase.setChangeLogParameter(entry.getKey(), entry.getValue());
097                }
098            }
099
100            liquibase.update(contexts);
101        } catch (LiquibaseException | SQLException e) {
102            throw new RuntimeException(e);
103        } finally {
104            try {
105                if (databaseConnection != null && !databaseConnection.isClosed())
106                    databaseConnection.close();
107            } catch (DatabaseException e) {
108                throw new RuntimeException(e);
109            }
110        }
111    }
112
113    /**
114     * Rollbacks last update.
115     * @param changelog resource with the migration
116     */
117    public void rollbackLastUpdate(String changelog) {
118        rollbackLastUpdate(changelog, null);
119    }
120
121    /**
122     * Rollbacks last update.
123     * @param changelog resource with the migration
124     * @param contexts contexts, see {@link Liquibase#update(String)}.
125     */
126    public void rollbackLastUpdate(String changelog, String contexts) {
127        rollbackLastUpdate(changelog, contexts, null) ;
128    }
129
130    /**
131     * Rollbacks given changelog to given tag.
132     * @param changelog resource with the migration
133     * @param contexts contexts, see {@link Liquibase#update(String)}.
134     * @param changelogParameters changelogParameters
135     */
136    public void rollbackLastUpdate(String changelog, String contexts, Map<String, Object> changelogParameters) {
137        rollback(changelog, contexts, changelogParameters, BEFORE_LAST_UPDATE);
138    }
139
140    /**
141     * Rollbacks given changelog to given tag.
142     * @param changelog resource with the migration
143     * @param contexts contexts, see {@link Liquibase#update(String)}.
144     * @param changelogParameters changelogParameters
145     * @param tag tag to rollback to
146     */
147    private void rollback(String changelog, String contexts, Map<String, Object> changelogParameters, String tag) {
148        changelog = normalizeName(changelog);
149
150        ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor();
151
152        DatabaseConnection databaseConnection = null;
153
154        try {
155            databaseConnection = new JdbcConnection(dataSource.getConnection());
156            Liquibase liquibase = new Liquibase(changelog, resourceAccessor, databaseConnection);
157
158            if (changelogParameters != null) {
159                for (Entry<String, Object> entry : changelogParameters.entrySet()) {
160                    liquibase.setChangeLogParameter(entry.getKey(), entry.getValue());
161                }
162            }
163
164            liquibase.rollback(tag, contexts);
165        } catch (LiquibaseException | SQLException e) {
166            throw new RuntimeException(e);
167        } finally {
168            try {
169                if (databaseConnection != null && !databaseConnection.isClosed()) {
170                    databaseConnection.close();
171                }
172            } catch (DatabaseException e) {
173                throw new RuntimeException(e);
174            }
175        }
176    }
177
178    public void dropAll() {
179        ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor();
180
181        DatabaseConnection databaseConnection = null;
182
183        try {
184            databaseConnection = new JdbcConnection(dataSource.getConnection());
185            Liquibase liquibase = new Liquibase(null, resourceAccessor, databaseConnection);
186            liquibase.dropAll();
187        } catch (LiquibaseException | SQLException e) {
188            throw new RuntimeException(e);
189        } finally {
190            try {
191                if (databaseConnection != null && !databaseConnection.isClosed())
192                    databaseConnection.close();
193            } catch (DatabaseException e) {
194                throw new RuntimeException(e);
195            }
196        }
197    }
198
199    private String normalizeName(String changelog) {
200        if (changelog.startsWith("/") ){
201            changelog = changelog.substring(1);
202        }
203        return changelog;
204    }
205}