Coverage Report - liquibase.integration.spring.SpringLiquibase
 
Classes in this File Line Coverage Branch Coverage Complexity
SpringLiquibase
0%
0/68
0%
0/18
1.741
SpringLiquibase$SpringResourceOpener
0%
0/14
0%
0/4
1.741
 
 1  
 package liquibase.integration.spring;
 2  
 
 3  
 import java.io.FileNotFoundException;
 4  
 import java.io.IOException;
 5  
 import java.io.InputStream;
 6  
 import java.net.URL;
 7  
 import java.sql.Connection;
 8  
 import java.sql.SQLException;
 9  
 import java.util.Enumeration;
 10  
 import java.util.Map;
 11  
 import java.util.Vector;
 12  
 
 13  
 import javax.sql.DataSource;
 14  
 
 15  
 import liquibase.Liquibase;
 16  
 import liquibase.database.Database;
 17  
 import liquibase.database.DatabaseFactory;
 18  
 import liquibase.database.jvm.JdbcConnection;
 19  
 import liquibase.exception.DatabaseException;
 20  
 import liquibase.exception.LiquibaseException;
 21  
 import liquibase.logging.LogFactory;
 22  
 import liquibase.logging.Logger;
 23  
 import liquibase.resource.ResourceAccessor;
 24  
 
 25  
 import org.springframework.beans.factory.BeanNameAware;
 26  
 import org.springframework.beans.factory.InitializingBean;
 27  
 import org.springframework.context.ResourceLoaderAware;
 28  
 import org.springframework.core.io.Resource;
 29  
 import org.springframework.core.io.ResourceLoader;
 30  
 
 31  
 /**
 32  
  * A Spring-ified wrapper for Liquibase.
 33  
  * 
 34  
  * Example Configuration:
 35  
  * <p>
 36  
  * <p>
 37  
  * This Spring configuration example will cause liquibase to run automatically when the Spring context is initialized.
 38  
  * It will load <code>db-changelog.xml</code> from the classpath and apply it against <code>myDataSource</code>.
 39  
  * <p>
 40  
  * 
 41  
  * <pre>
 42  
  * &lt;bean id=&quot;myLiquibase&quot;
 43  
  *          class=&quot;liquibase.spring.SpringLiquibase&quot;
 44  
  *          &gt;
 45  
  * 
 46  
  *      &lt;property name=&quot;dataSource&quot; ref=&quot;myDataSource&quot; /&gt;
 47  
  * 
 48  
  *      &lt;property name=&quot;changeLog&quot; value=&quot;classpath:db-changelog.xml&quot; /&gt;
 49  
  * 
 50  
  *      &lt;!-- The following configuration options are optional --&gt;
 51  
  * 
 52  
  *      &lt;property name=&quot;executeEnabled&quot; value=&quot;true&quot; /&gt;
 53  
  * 
 54  
  *      &lt;!--
 55  
  *      If set to true, writeSqlFileEnabled will write the generated
 56  
  *      SQL to a file before executing it.
 57  
  *      --&gt;
 58  
  *      &lt;property name=&quot;writeSqlFileEnabled&quot; value=&quot;true&quot; /&gt;
 59  
  * 
 60  
  *      &lt;!--
 61  
  *      sqlOutputDir specifies the directory into which the SQL file
 62  
  *      will be written, if so configured.
 63  
  *      --&gt;
 64  
  *      &lt;property name=&quot;sqlOutputDir&quot; value=&quot;c:\sql&quot; /&gt;
 65  
  * 
 66  
  * &lt;/bean&gt;
 67  
  * 
 68  
  * </pre>
 69  
  * 
 70  
  * @author Rob Schoening
 71  
  */
 72  
 public class SpringLiquibase implements InitializingBean, BeanNameAware, ResourceLoaderAware {
 73  
     public class SpringResourceOpener implements ResourceAccessor {
 74  
         private String parentFile;
 75  
 
 76  0
         public SpringResourceOpener(String parentFile) {
 77  0
             this.parentFile = parentFile;
 78  0
         }
 79  
 
 80  
         public InputStream getResourceAsStream(String file) throws IOException {
 81  
             try {
 82  0
                 Resource resource = getResource(file);
 83  0
                 return resource.getInputStream();
 84  0
             } catch (FileNotFoundException ex) {
 85  0
                 return null;
 86  
             }
 87  
         }
 88  
 
 89  
         public Enumeration<URL> getResources(String packageName) throws IOException {
 90  0
             Vector<URL> tmp = new Vector<URL>();
 91  
 
 92  0
             tmp.add(getResource(packageName).getURL());
 93  
 
 94  0
             return tmp.elements();
 95  
         }
 96  
 
 97  
         public Resource getResource(String file) {
 98  0
             return getResourceLoader().getResource(adjustClasspath(file));
 99  
         }
 100  
 
 101  
         private String adjustClasspath(String file) {
 102  0
             return isClasspathPrefixPresent(parentFile) && !isClasspathPrefixPresent(file) ? ResourceLoader.CLASSPATH_URL_PREFIX
 103  
                     + file
 104  
                     : file;
 105  
         }
 106  
 
 107  
         public boolean isClasspathPrefixPresent(String file) {
 108  0
             return file.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX);
 109  
         }
 110  
 
 111  
         public ClassLoader toClassLoader() {
 112  0
             return getResourceLoader().getClassLoader();
 113  
         }
 114  
     }
 115  
 
 116  
     private String beanName;
 117  
 
 118  
     private ResourceLoader resourceLoader;
 119  
 
 120  
     private DataSource dataSource;
 121  
 
 122  0
     private Logger log = LogFactory.getLogger(SpringLiquibase.class.getName());
 123  
 
 124  
     private String changeLog;
 125  
 
 126  
     private String contexts;
 127  
 
 128  
     private Map<String, String> parameters;
 129  
 
 130  
     private String defaultSchema;
 131  
 
 132  
     public SpringLiquibase() {
 133  0
         super();
 134  0
     }
 135  
 
 136  
     public String getDatabaseProductName() throws DatabaseException {
 137  0
         Connection connection = null;
 138  0
         String name = "unknown";
 139  
         try {
 140  0
             connection = getDataSource().getConnection();
 141  0
             Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(
 142  
                     new JdbcConnection(dataSource.getConnection()));
 143  0
             name = database.getDatabaseProductName();
 144  0
         } catch (SQLException e) {
 145  0
             throw new DatabaseException(e);
 146  
         } finally {
 147  0
             if (connection != null) {
 148  
                 try {
 149  0
                     if (!connection.getAutoCommit()) {
 150  0
                         connection.rollback();
 151  
                     }
 152  0
                     connection.close();
 153  0
                 } catch (Exception e) {
 154  0
                     log.warning("problem closing connection", e);
 155  0
                 }
 156  
             }
 157  
         }
 158  0
         return name;
 159  
     }
 160  
 
 161  
     /**
 162  
      * The DataSource that liquibase will use to perform the migration.
 163  
      * 
 164  
      * @return
 165  
      */
 166  
     public DataSource getDataSource() {
 167  0
         return dataSource;
 168  
     }
 169  
 
 170  
     /**
 171  
      * The DataSource that liquibase will use to perform the migration.
 172  
      */
 173  
     public void setDataSource(DataSource dataSource) {
 174  0
         this.dataSource = dataSource;
 175  0
     }
 176  
 
 177  
     /**
 178  
      * Returns a Resource that is able to resolve to a file or classpath resource.
 179  
      * 
 180  
      * @return
 181  
      */
 182  
     public String getChangeLog() {
 183  0
         return changeLog;
 184  
     }
 185  
 
 186  
     /**
 187  
      * Sets a Spring Resource that is able to resolve to a file or classpath resource. An example might be
 188  
      * <code>classpath:db-changelog.xml</code>.
 189  
      */
 190  
     public void setChangeLog(String dataModel) {
 191  
 
 192  0
         this.changeLog = dataModel;
 193  0
     }
 194  
 
 195  
     public String getContexts() {
 196  0
         return contexts;
 197  
     }
 198  
 
 199  
     public void setContexts(String contexts) {
 200  0
         this.contexts = contexts;
 201  0
     }
 202  
 
 203  
     public String getDefaultSchema() {
 204  0
         return defaultSchema;
 205  
     }
 206  
 
 207  
     public void setDefaultSchema(String defaultSchema) {
 208  0
         this.defaultSchema = defaultSchema;
 209  0
     }
 210  
 
 211  
     /**
 212  
      * Executed automatically when the bean is initialized.
 213  
      */
 214  
     public void afterPropertiesSet() throws LiquibaseException {
 215  0
         String shouldRunProperty = System.getProperty(Liquibase.SHOULD_RUN_SYSTEM_PROPERTY);
 216  0
         if (shouldRunProperty != null && !Boolean.valueOf(shouldRunProperty)) {
 217  0
             System.out.println("Liquibase did not run because '" + Liquibase.SHOULD_RUN_SYSTEM_PROPERTY
 218  
                     + "' system property was set to false");
 219  0
             return;
 220  
         }
 221  
 
 222  0
         Connection c = null;
 223  0
         Liquibase liquibase = null;
 224  
         try {
 225  0
             c = getDataSource().getConnection();
 226  0
             liquibase = createLiquibase(c);
 227  0
             liquibase.update(getContexts());
 228  0
         } catch (SQLException e) {
 229  0
             throw new DatabaseException(e);
 230  
         } finally {
 231  0
             if (liquibase != null) {
 232  0
                 liquibase.forceReleaseLocks();
 233  
             }
 234  0
             if (c != null) {
 235  
                 try {
 236  0
                     c.rollback();
 237  0
                     c.close();
 238  0
                 } catch (SQLException e) {
 239  
                     // nothing to do
 240  0
                 }
 241  
             }
 242  
         }
 243  
 
 244  0
     }
 245  
 
 246  
     protected Liquibase createLiquibase(Connection c) throws LiquibaseException {
 247  0
         Liquibase liquibase = new Liquibase(getChangeLog(), createResourceOpener(), createDatabase(c));
 248  0
         if (parameters != null) {
 249  0
             for (Map.Entry<String, String> entry : parameters.entrySet()) {
 250  0
                 liquibase.setChangeLogParameter(entry.getKey(), entry.getValue());
 251  
             }
 252  
         }
 253  
 
 254  0
         return liquibase;
 255  
     }
 256  
 
 257  
     /**
 258  
      * Subclasses may override this method add change some database settings such as default schema before returning the
 259  
      * database object.
 260  
      * 
 261  
      * @param c
 262  
      * @return a Database implementation retrieved from the {@link DatabaseFactory}.
 263  
      * @throws DatabaseException
 264  
      */
 265  
     protected Database createDatabase(Connection c) throws DatabaseException {
 266  0
         Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(c));
 267  0
         if (this.defaultSchema != null) {
 268  0
             database.setDefaultSchemaName(this.defaultSchema);
 269  
         }
 270  0
         return database;
 271  
     }
 272  
 
 273  
     public void setChangeLogParameters(Map<String, String> parameters) {
 274  0
         this.parameters = parameters;
 275  0
     }
 276  
 
 277  
     /**
 278  
      * Create a new resourceOpener.
 279  
      */
 280  
     protected SpringResourceOpener createResourceOpener() {
 281  0
         return new SpringResourceOpener(getChangeLog());
 282  
     }
 283  
 
 284  
     /**
 285  
      * Spring sets this automatically to the instance's configured bean name.
 286  
      */
 287  
     public void setBeanName(String name) {
 288  0
         this.beanName = name;
 289  0
     }
 290  
 
 291  
     /**
 292  
      * Gets the Spring-name of this instance.
 293  
      * 
 294  
      * @return
 295  
      */
 296  
     public String getBeanName() {
 297  0
         return beanName;
 298  
     }
 299  
 
 300  
     public void setResourceLoader(ResourceLoader resourceLoader) {
 301  0
         this.resourceLoader = resourceLoader;
 302  0
     }
 303  
 
 304  
     public ResourceLoader getResourceLoader() {
 305  0
         return resourceLoader;
 306  
     }
 307  
 
 308  
     @Override
 309  
     public String toString() {
 310  0
         return getClass().getName() + "(" + this.getResourceLoader().toString() + ")";
 311  
     }
 312  
 }