CCM NG: JavaDoc for the CcmIntegrator

git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@3614 8810af33-2d31-482b-a856-94f89814c4df
pull/2/head
jensp 2015-09-14 09:37:02 +00:00
parent f448802139
commit b754e4b04c
1 changed files with 130 additions and 45 deletions

View File

@ -40,6 +40,14 @@ import java.util.ServiceLoader;
import javax.sql.DataSource;
/**
* Manages the database schema for new and updated modules.
*
* This implementation of Hibernate's {@code Integrator} interface which manages
* the database schema of LibreCCM. It uses the
* <a href="http://www.flywaydb.org">Flyway</a> framework to execute migrations
* on the database if necessary. To find the modules the Java service loader is
* used. Because the integrator is called in a very early phase of the
* application lifecycle we can't use CDI here.
*
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
*/
@ -48,12 +56,26 @@ public class CcmIntegrator implements Integrator {
private static final Logger LOGGER = LogManager.getLogger(
CcmIntegrator.class);
/**
* Service loader containing all modules. Initialised by the
* {@link #integrate(Configuration, SessionFactoryImplementor, SessionFactoryServiceRegistry)}
* method.
*/
private ServiceLoader<CcmModule> modules;
/**
* Checks for new and updated modules when the persistence unit is started.
* If there are updates the necessary database migrations are executed.
*
* @param configuration
* @param sessionFactory
* @param registry
*/
@Override
public void integrate(final Configuration configuration,
final SessionFactoryImplementor sessionFactory,
final SessionFactoryServiceRegistry registry) {
//Find all modules in the classpath
LOGGER.info("Retrieving modules...");
modules = ServiceLoader.load(CcmModule.class);
for (final CcmModule module : modules) {
@ -65,17 +87,20 @@ public class CcmIntegrator implements Integrator {
Connection connection = null;
try {
//Create dependency tree for the modules
final DependencyTreeManager treeManager
= new DependencyTreeManager();
final List<TreeNode> tree = treeManager.generateTree(modules);
final List<TreeNode> orderedNodes = treeManager.orderModules(tree);
//Get DataSource and Connection from the sessionFactory of
//Hibernate.
final DataSource dataSource = (DataSource) sessionFactory.
getProperties().get("javax.persistence.jtaDataSource");
connection = dataSource.getConnection();
//Migrate tables and sequences which don't belong to a module
//for instance hibernate_sequence
//for instance the hibernate_sequence
final Flyway flyway = new Flyway();
flyway.setDataSource(dataSource);
final StringBuffer buffer = new StringBuffer(
@ -84,6 +109,7 @@ public class CcmIntegrator implements Integrator {
flyway.setLocations(buffer.toString());
flyway.migrate();
//Migrate the modules
for (final TreeNode node : orderedNodes) {
migrateModule(node.getModule().getClass(), dataSource);
@ -92,6 +118,7 @@ public class CcmIntegrator implements Integrator {
}
}
//Build Hibernate mappings for the entities.
configuration.buildMappings();
} catch (DependencyException | SQLException ex) {
@ -103,10 +130,48 @@ public class CcmIntegrator implements Integrator {
LOGGER.info("All modules integrated successfully.");
}
/**
* Private helper method to get the database schema name of a module. The
* name is then name of the module in lower case with all hyphens replaced
* with underscores.
*
* @param moduleInfo The module info object for the module
* @return The database schema name of the module.
*/
private String getSchemaName(final ModuleInfo moduleInfo) {
return moduleInfo.getModuleName().toLowerCase().replace("-", "_");
}
/**
* Private helper method to append the name of the database in use to the
* location of the migrations. The value is determined using the return
* value of {@link Connection#getMetaData().getDatabaseProductName()} in
* lower case. The current supported values are:
*
* <table>
* <tr>
* <th>Database Product Name</th>
* <th>Location</th>
* </tr>
* <tr>
* <td><code>H2</code></td>
* <td><code>/h2</code></td>
* </tr>
* <tr>
* <td><code>PostgreSQL</code></td>
* <td><code>/pgsql</code></td>
* </tr>
* </table>
*
*
* If the database is not supported an {@link IntegrationException} will be
* thrown.
*
* @param buffer Buffer for the location string.
* @param connection The JDBC connection object.
* @throws SQLException If an error occurs while accessing the database.
* @throws IntegrationException If the database is not supported yet.
*/
private void appendDbLocation(final StringBuffer buffer,
final Connection connection)
throws SQLException {
@ -115,9 +180,6 @@ public class CcmIntegrator implements Integrator {
case "H2":
buffer.append("/h2");
break;
// case "MySQL":
// buffer.append("/mysql");
// break;
case "PostgreSQL":
buffer.append("/pgsql");
break;
@ -129,49 +191,57 @@ public class CcmIntegrator implements Integrator {
}
}
/**
* Helper method to determine the location of the migrations for a module.
*
* @param moduleInfo The module info object of the module.
* @param connection The database connection.
* @return The location of the database migrations for a specific module.
* @throws SQLException If an error on the JDBC site occurs.
*/
private String getLocation(final ModuleInfo moduleInfo,
final Connection connection)
throws SQLException {
final StringBuffer buffer = new StringBuffer(
"classpath:/db/migrations/");
//buffer.append(ModuleUtil.getModulePackageName(module));
buffer.append(moduleInfo.getModuleDataPackage());
// switch (connection.getMetaData().getDatabaseProductName()) {
// case "MySQL":
// buffer.append("/mysql");
// break;
// case "PostgreSQL":
// buffer.append("/pgsql");
// break;
// default:
// throw new IntegrationException(String.format(
// "Integration failed. Database \"%s\" is not supported yet.",
// connection.getMetaData().
// getDatabaseProductName()));
// }
appendDbLocation(buffer, connection);
return buffer.toString();
}
/**
* Helper method for executing the migrations for a module.
*
* @param module The module for which the migrations are executed.
* @param dataSource The JDBC data source for connecting to the database.
* @throws SQLException If an error occurs while applying the migrations.
*/
private void migrateModule(final Class<? extends CcmModule> module,
final DataSource dataSource) throws SQLException {
//Get the JDBC connection from the DataSource
final Connection connection = dataSource.getConnection();
//Load the module info for the module
final ModuleInfo moduleInfo = new ModuleInfo();
moduleInfo.load(module);
//Create a Flyway instance for the the module.
final Flyway flyway = new Flyway();
flyway.setDataSource(dataSource);
//Set schema correctly for the different databases. Necessary because
//different RDBMS handle case different.
if ("H2".equals(connection.getMetaData().getDatabaseProductName())) {
flyway
.setSchemas(getSchemaName(moduleInfo).toUpperCase(Locale.ROOT));
.setSchemas(getSchemaName(moduleInfo).toUpperCase(
Locale.ROOT));
} else {
flyway.setSchemas(getSchemaName(moduleInfo));
}
flyway.setLocations(getLocation(moduleInfo, connection));
//Get current migrations info
final MigrationInfo current = flyway.info().current();
boolean newModule;
if (current == null) {
@ -184,12 +254,16 @@ public class CcmIntegrator implements Integrator {
newModule = false;
}
//Execute migrations. Flyway will check if there any migrations to apply.
flyway.migrate();
LOGGER.info("Migrated schema {} in database to version {}",
getSchemaName(moduleInfo),
flyway.info().current().getVersion());
//If a new module was installed register the module in the
//installed_modules table with the new status. The ModuleManager will
//call the install method of them module.
if (newModule) {
try (Statement statement = connection.createStatement()) {
statement.execute(String.format(
@ -211,6 +285,13 @@ public class CcmIntegrator implements Integrator {
//Nothing
}
/**
* Called when the application is shutdown. Used to remove the database
* schema of uninstalled modules.
*
* @param sessionFactory
* @param registry
*/
@Override
public void disintegrate(final SessionFactoryImplementor sessionFactory,
final SessionFactoryServiceRegistry registry) {
@ -220,6 +301,7 @@ public class CcmIntegrator implements Integrator {
LOGGER.info("Removing schemas for modules scheduled for uninstall...");
try {
//Get JDBC connection
final DataSource dataSource = (DataSource) sessionFactory
.getProperties().get("javax.persistence.jtaDataSource");
connection = dataSource.getConnection();
@ -231,6 +313,7 @@ public class CcmIntegrator implements Integrator {
moduleInfo.load(module);
try (Statement query = connection.createStatement();
//Check status of each module
ResultSet result = query.executeQuery(
String.format("SELECT module_class_name, status "
+ "FROM ccm_core.installed_modules "
@ -240,9 +323,10 @@ public class CcmIntegrator implements Integrator {
System.out.printf("Checking status of module %s...%n",
module.getClass().getName());
//If there modules marked for uninstall remove the schema
//of the module from the database.
if (result.next() && ModuleStatus.UNINSTALL.toString()
.equals(
result.getString("status"))) {
.equals(result.getString("status"))) {
LOGGER.info("Removing schema for module %s...",
module.getClass().getName());
@ -254,6 +338,7 @@ public class CcmIntegrator implements Integrator {
moduleInfo.getModuleName());
flyway.clean();
//Delete the module from the installed modules table.
try (final Statement statement = connection
.createStatement()) {
statement.addBatch(String.format(