From b754e4b04c9213a2a6b4714200454da5bec653dc Mon Sep 17 00:00:00 2001 From: jensp Date: Mon, 14 Sep 2015 09:37:02 +0000 Subject: [PATCH] CCM NG: JavaDoc for the CcmIntegrator git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@3614 8810af33-2d31-482b-a856-94f89814c4df --- .../org/libreccm/modules/CcmIntegrator.java | 175 +++++++++++++----- 1 file changed, 130 insertions(+), 45 deletions(-) diff --git a/ccm-core/src/main/java/org/libreccm/modules/CcmIntegrator.java b/ccm-core/src/main/java/org/libreccm/modules/CcmIntegrator.java index 45946018b..6cd5490c8 100644 --- a/ccm-core/src/main/java/org/libreccm/modules/CcmIntegrator.java +++ b/ccm-core/src/main/java/org/libreccm/modules/CcmIntegrator.java @@ -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 + * Flyway 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 Jens Pelzetter */ 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 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 tree = treeManager.generateTree(modules); final List 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: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Database Product NameLocation
H2/h2
PostgreSQL/pgsql
+ * + * + * 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 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); } }