/** * Copyright 2013 Tommi S.E. Laukkanen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.bubblecloud.ilves.util; import liquibase.Liquibase; import liquibase.database.Database; import liquibase.database.DatabaseFactory; import liquibase.database.jvm.JdbcConnection; import liquibase.diff.DiffResult; import liquibase.diff.compare.CompareControl; import liquibase.diff.output.DiffOutputControl; import liquibase.diff.output.changelog.DiffToChangeLog; import liquibase.resource.ClassLoaderResourceAccessor; import org.bubblecloud.ilves.exception.SiteException; import org.eclipse.persistence.config.PersistenceUnitProperties; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.sql.Connection; import java.util.HashMap; import java.util.Map; /** * Utility for persistence operations. * @author Tommi S.E Laukkanen */ public final class PersistenceUtil { /** * The entity manager factory for test. */ private static Map<String, EntityManagerFactory> entityManagerFactories = new HashMap<String, EntityManagerFactory>(); /** * Private constructor to disable construction of utility class. */ private PersistenceUtil() { } /** * Gets singleton entity manager factory for given persistence unit and properties category. * @param persistenceUnit the persistence unit * @param propertiesCategory the properties category * @return the entity manager factory singleton */ public static EntityManagerFactory getEntityManagerFactory(final String persistenceUnit, final String propertiesCategory) { final String entityManagerFactoryKey = persistenceUnit + "-" + propertiesCategory; synchronized (entityManagerFactories) { if (!entityManagerFactories.containsKey(entityManagerFactoryKey)) { final EntityManagerFactory entityManagerFactory = newEntityManagerFactory(persistenceUnit, propertiesCategory); final String changeLog = PropertiesUtil.getProperty( propertiesCategory, "liquibase-change-log"); try { final EntityManager entityManager = entityManagerFactory.createEntityManager(); entityManager.getTransaction().begin(); final Connection connection = entityManager.unwrap(Connection.class); final Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation( new JdbcConnection(connection)); final Liquibase liquibase = new Liquibase(changeLog, new ClassLoaderResourceAccessor(), database); liquibase.update(""); if (entityManager.getTransaction().isActive()) { entityManager.getTransaction().commit(); } } catch (Exception e) { throw new SiteException("Error updating database.", e); } entityManagerFactories.put(entityManagerFactoryKey, entityManagerFactory); } return entityManagerFactories.get(entityManagerFactoryKey); } } /** * Allows removing entity manager factory in case of database failure. * * @param persistenceUnit the persistence unit * @param propertiesCategory the properties category */ public static void removeEntityManagerFactory(final String persistenceUnit, final String propertiesCategory) { final String entityManagerFactoryKey = persistenceUnit + "-" + propertiesCategory; synchronized (entityManagerFactories) { if (entityManagerFactories.containsKey(entityManagerFactoryKey)) { entityManagerFactories.remove(entityManagerFactoryKey); } } } /** * Diffs database schema against new changes in JPA model and returns Liquibase diff XML file as String. * * @param persistenceUnit the persistence unit * @param propertiesCategory the properties category * @return the Liquibase diff XML file as String. */ public static String diff(final String persistenceUnit, final String propertiesCategory) { try { final String originalDllGeneration = PropertiesUtil.getProperty(propertiesCategory, PersistenceUnitProperties.DDL_GENERATION); final String originalJdbcUrl = PropertiesUtil.getProperty( propertiesCategory, PersistenceUnitProperties.JDBC_URL); final String refJdbcUrl = PropertiesUtil.getProperty( propertiesCategory, PersistenceUnitProperties.JDBC_URL) + "ref"; // Construct schema according to liquibase changelog. PropertiesUtil.setProperty(propertiesCategory, PersistenceUnitProperties.DDL_GENERATION, "none"); final EntityManagerFactory factory = getEntityManagerFactory(persistenceUnit, propertiesCategory); removeEntityManagerFactory(persistenceUnit, propertiesCategory); // Empty ref schema PropertiesUtil.setProperty(propertiesCategory, PersistenceUnitProperties.JDBC_URL, refJdbcUrl); dropDatabaseObjects(persistenceUnit, propertiesCategory); // Construct ref schema according to liquibase changelog. getEntityManagerFactory(persistenceUnit, propertiesCategory).close(); removeEntityManagerFactory(persistenceUnit, propertiesCategory); // Update ref schema according to JPA changes. PropertiesUtil.setProperty(propertiesCategory, PersistenceUnitProperties.DDL_GENERATION, "create-or-extend-tables"); PropertiesUtil.setProperty(propertiesCategory, PersistenceUnitProperties.JDBC_URL, refJdbcUrl); final EntityManagerFactory refFactory = newEntityManagerFactory(persistenceUnit, propertiesCategory); // Reset original settings. PropertiesUtil.setProperty(propertiesCategory, PersistenceUnitProperties.DDL_GENERATION, originalDllGeneration); PropertiesUtil.setProperty(propertiesCategory, PersistenceUnitProperties.JDBC_URL, originalJdbcUrl); final String changeLog = PropertiesUtil.getProperty( propertiesCategory, "liquibase-change-log"); final EntityManager entityManager = factory.createEntityManager(); final EntityManager refEntityManager = refFactory.createEntityManager(); entityManager.getTransaction().begin(); refEntityManager.getTransaction().begin(); final Connection connection = entityManager.unwrap(Connection.class); final Connection refConnection = refEntityManager.unwrap(Connection.class); final Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection)); final Database refDatabase = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(refConnection)); final Liquibase liquibase = new Liquibase(changeLog, new ClassLoaderResourceAccessor(), database); final DiffResult diffResult = liquibase.diff(refDatabase, database, CompareControl.STANDARD); final ByteArrayOutputStream byteArrayOutputStream=new ByteArrayOutputStream(); final PrintStream printStream=new PrintStream(byteArrayOutputStream); final DiffToChangeLog diffToChangeLog = new DiffToChangeLog(diffResult, new DiffOutputControl()); diffToChangeLog.print(printStream); entityManager.getTransaction().rollback(); refEntityManager.getTransaction().rollback(); return byteArrayOutputStream.toString(); } catch(final Exception e) { throw new SiteException("Error diffing liquibase and JPA schemas.", e); } } /** * Drops database objects of given schema. * @param persistenceUnit the persistence unit * @param propertiesCategory the properties category */ private static void dropDatabaseObjects(String persistenceUnit, String propertiesCategory) { try { final EntityManagerFactory tempRefEntityManagerFactory = newEntityManagerFactory(persistenceUnit, propertiesCategory); final EntityManager entityManager = tempRefEntityManagerFactory.createEntityManager(); entityManager.getTransaction().begin(); final Connection connection = entityManager.unwrap(Connection.class); final Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection)); database.dropDatabaseObjects(database.getDefaultSchema()); entityManager.getTransaction().commit(); entityManager.close(); database.close(); tempRefEntityManagerFactory.close(); } catch(final Exception e) { throw new SiteException("Error dropping database objects.", e); } } /** * Constructs new entity manager factory. * * @param persistenceUnit the persistence unit * @param propertiesCategory the properties category * @return the new entity manager factory. */ private static EntityManagerFactory newEntityManagerFactory(String persistenceUnit, String propertiesCategory) { final Map properties = new HashMap(); properties.put(PersistenceUnitProperties.JDBC_DRIVER, PropertiesUtil.getProperty( propertiesCategory, PersistenceUnitProperties.JDBC_DRIVER)); properties.put(PersistenceUnitProperties.JDBC_URL, PropertiesUtil.getProperty( propertiesCategory, PersistenceUnitProperties.JDBC_URL)); properties.put(PersistenceUnitProperties.JDBC_USER, PropertiesUtil.getProperty( propertiesCategory, PersistenceUnitProperties.JDBC_USER)); properties.put(PersistenceUnitProperties.JDBC_PASSWORD, PropertiesUtil.getProperty( propertiesCategory, PersistenceUnitProperties.JDBC_PASSWORD)); properties.put(PersistenceUnitProperties.DDL_GENERATION, PropertiesUtil.getProperty( propertiesCategory, PersistenceUnitProperties.DDL_GENERATION)); return Persistence.createEntityManagerFactory( persistenceUnit, properties); } }