CCM NG: JavaDoc for the CcmIntegrator
git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@3614 8810af33-2d31-482b-a856-94f89814c4dfpull/2/head
parent
f448802139
commit
b754e4b04c
|
|
@ -40,20 +40,42 @@ 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>
|
||||
*/
|
||||
public class CcmIntegrator implements Integrator {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(
|
||||
CcmIntegrator.class);
|
||||
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,25 +87,29 @@ public class CcmIntegrator implements Integrator {
|
|||
|
||||
Connection connection = null;
|
||||
try {
|
||||
//Create dependency tree for the modules
|
||||
final DependencyTreeManager treeManager
|
||||
= new DependencyTreeManager();
|
||||
= 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");
|
||||
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(
|
||||
"db/migrations/org/libreccm/base");
|
||||
"db/migrations/org/libreccm/base");
|
||||
appendDbLocation(buffer, connection);
|
||||
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,75 +130,118 @@ 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 {
|
||||
throws SQLException {
|
||||
|
||||
switch (connection.getMetaData().getDatabaseProductName()) {
|
||||
case "H2":
|
||||
buffer.append("/h2");
|
||||
break;
|
||||
// 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()));
|
||||
"Integration failed. Database \"%s\" is not supported yet.",
|
||||
connection.getMetaData().
|
||||
getDatabaseProductName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
throws SQLException {
|
||||
|
||||
final StringBuffer buffer = new StringBuffer(
|
||||
"classpath:/db/migrations/");
|
||||
//buffer.append(ModuleUtil.getModulePackageName(module));
|
||||
"classpath:/db/migrations/");
|
||||
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,20 +254,24 @@ 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(
|
||||
"INSERT INTO ccm_core.installed_modules "
|
||||
+ "(module_id, module_class_name, status) "
|
||||
+ "VALUES (%d, '%s', 'NEW')",
|
||||
module.getName().hashCode(),
|
||||
module.getName()));
|
||||
"INSERT INTO ccm_core.installed_modules "
|
||||
+ "(module_id, module_class_name, status) "
|
||||
+ "VALUES (%d, '%s', 'NEW')",
|
||||
module.getName().hashCode(),
|
||||
module.getName()));
|
||||
} catch (SQLException ex) {
|
||||
throw new IntegrationException("Failed to integrate.", ex);
|
||||
}
|
||||
|
|
@ -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,8 +301,9 @@ 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");
|
||||
.getProperties().get("javax.persistence.jtaDataSource");
|
||||
connection = dataSource.getConnection();
|
||||
System.out.println("checking modules...");
|
||||
LOGGER.info("Checking modules...");
|
||||
|
|
@ -231,18 +313,20 @@ 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 "
|
||||
String.format("SELECT module_class_name, status "
|
||||
+ "FROM ccm_core.installed_modules "
|
||||
+ "WHERE module_class_name = '%s'",
|
||||
module.getClass().getName()))) {
|
||||
module.getClass().getName()))) {
|
||||
|
||||
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,17 +338,18 @@ public class CcmIntegrator implements Integrator {
|
|||
moduleInfo.getModuleName());
|
||||
flyway.clean();
|
||||
|
||||
//Delete the module from the installed modules table.
|
||||
try (final Statement statement = connection
|
||||
.createStatement()) {
|
||||
.createStatement()) {
|
||||
statement.addBatch(String.format(
|
||||
"DELETE FROM ccm_core.installed_modules "
|
||||
+ "WHERE module_class_name = '%s'",
|
||||
module.getClass().getName()));
|
||||
"DELETE FROM ccm_core.installed_modules "
|
||||
+ "WHERE module_class_name = '%s'",
|
||||
module.getClass().getName()));
|
||||
statement.executeBatch();
|
||||
LOGGER.info("Done.");
|
||||
} catch (SQLException ex) {
|
||||
throw new IntegrationException(
|
||||
"Failed to desintegrate", ex);
|
||||
"Failed to desintegrate", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue