Installers

Examples: creating database schema, installing default or test data…​

An @Installer annotation marks a class as an installer and allows you to specify the important installer attributes.

attribute description

name

Optional (unique) name of the installer. If not specified, the fully qualified class name of the installer class will be used.

description

Descriptive text of what will be installed.

phase

When this installer should run during the context bootstrap. See bootstrap phases.

runCondition

Condition when this installer should run. See run conditions.

version

Version number of the installer. This value should be incremented to enforce a new run of the same installer. Only relevant if the run condition is VersionDifferent.

An @InstallerMethod annotation marks a method within an @Installer marked class as a method that should be executed when the installer is executed. Using the required attribute, it is possible to define the method’s parameters as optional, which means they will not be autowired if they are not found.

Installer detection

By default installers are detected from the classpath by scanning the installers package relative to the package of the AcrossModule. Changing the packages to scan for installers can be done by overriding the getInstallerScanPackages() method on AcrossModule.

If you manually want to specify a list of installers, you can do so by overriding the getInstallers() method. Though still possible for compatibility reasons, installers should be specified with their Class.

Installer tracking and synchronization

The execution of installers is tracked using the AcrossInstallerRepository. This allows for installers to execute only if they have not yet been executed (see run conditions).

In addition, installer execution is synchronized for multiple applications connecting on the same database. This is done through the DistributedLockRepository that is created in the core schema. A lock will be taken on the database as soon as a single installer wishes to execute, and will be released once that installer has finished executing.

The lock owner will contain the hostname of the computer running the application, as well as the displayName of the AcrossContext.

Because of tracking and synchronization, a datasource is required in order to execute installers.

The AcrossInstallerRepository can be wired as a bean in installers. This can be helpful in migration trajectories for example to rename installers.

Bootstrap phases

Installers are executed during a specific phase of the context bootstrap. This phase should be specified for every installer. The phase will determine which beans are available in the installer.

The following bootstrap phases exist:

BeforeContextBootstrap

The installer is executed before any of the modules in the AcrossContext are bootstrapped. Only beans from the parent ApplicationContext or the installer ApplicationContext are available.

BeforeModuleBootstrap

The installer is executed before the module that owns it bootstraps, but after all previous modules have bootstrapped. All beans from previous modules will also be available to the installer.

AfterModuleBootstrap

The installer is executed after the module that owns it has bootstrapped, but before the next module bootstraps. All beans from the current module are available.

AfterContextBootstrap

The installer is executed after all modules have bootstrapped, but before the AcrossContextBootstrappedEvent is published. All beans from all modules are available.

Run conditions

The following run conditions are supported for an installer:

AlwaysRun

The installer will always be executed, everytime the module bootstraps.

VersionDifferent

The installer will only execute if the version attribute is higher than the version previously executed.

In addition to the standard run conditions, you can use @Conditional annotations on installer classes. This includes for example the use of @ConditionalOnAcrossModule to execute installers if certain modules are active.

Using AlwaysRun installers will always cause the bootstrap lock to be taken. This might cause a slowdown when starting multiple applications on the same datasource simultaneously.

Installer group

In addition to the standard @Installer attributes, an @InstallerGroup annotation can be specified. This allows grouping types of installers together (for example schema) and overruling their execution using InstallerSettings.

Installer execution order

If all conditions are fulfilled, installers will be executed in a predetermined order:

  1. in bootstrap phase order

  2. in module bootstrap order

  3. in installer order (according to @Order annotations on the installer class)

Installers without an @Order annotation behave as if they have an order index of 0. In that case the installer registration order will be used as a fallback, with installers specified through getInstallers() having a higher precedence.

Datasources and installers

An AcrossContext requires at least one datasource if modules need to run installers. Certain core features like the DistributedLockRepository also require the core schema to be installed and will require a valid datasource to be configured.

The main datasource is available for all modules as a bean named acrossDataSource. Optionally a separate installer datasource can be configured that will be available as acrossInstallerDataSource. If no separate installer datasource is specified, the default across datasource will be used.

A valid default datasource is always required for installers to run. It is not possible to configure only an installer datasource as the distributed locking mechanism uses the default datasource.

The installer datasource is the default datasource used for all AcrossLiquibaseInstaller instances.

Installer ApplicationContext

If installers need to be run for a module, a specific ApplicationContext is created in which the installers will be wired as beans. This ApplicationContext can exist before the actual module ApplicationContext does. However, all beans from the parent Across context and the module context - when created - are available in installers.

Installer contexts are temporary, once the Across context has bootstrapped they are closed. Configuration and other annotated classes can be added to the installer context by using ApplicationContextConfigurer implementations, either on the AcrossContext or on an AcrossModule.

By default, the package installers.config relative to the module package will be scanned for beans to be added to the installer ApplicationContext.

Example using different datasource inside the modules
@Configuration
class Config implements AcrossContextConfigurer
{
    /**
     * Installer tracking will be done on this datasource.
     */
    @Bean
    public EmbeddedDatabase acrossDataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType( EmbeddedDatabaseType.HSQL )
                .setName( "core" )
                .build();
    }

    @Bean
    public EmbeddedDatabase moduleDataSource() {
        return new EmbeddedDatabaseBuilder()
                .setType( EmbeddedDatabaseType.HSQL )
                .setName( "data" )
                .build();
    }

    @Override
    public void configure( AcrossContext context ) {
        ProvidedBeansMap beans = new ProvidedBeansMap();
        beans.put( AcrossContext.DATASOURCE, new PrimarySingletonBean( moduleDataSource() ) );
        beans.put( AcrossContext.INSTALLER_DATASOURCE, moduleDataSource() );

        context.addApplicationContextConfigurer( new ProvidedBeansConfigurer( beans ),
                                                 ConfigurerScope.MODULES_ONLY );
        context.addInstallerContextConfigurer( new ProvidedBeansConfigurer( beans ) );
    }
}
The installer context has no web support as it is a direct implementation of AcrossApplicationContext but does not implement WebApplicationContext.

Installers are registered as bean definitions in the installer ApplicationContext. You can use any Spring @Conditional annotations to suppress installer execution, even if the run conditions are fulfilled.

When registering bean definitions to the installer context, a good practice is to demarcate beans as @Lazy. In that case they will never get created if the installer conditionals fail.

AcrossLiquibaseInstaller

Across core comes with an AcrossLiquibaseInstaller class. This is an abstract base class for executing liquibase XML resources. Simply extending the base class and annotating as installer will execute an XML resource in the same package as the installer class against the installer datasource.

Example using different datasource inside the modules
package my.package;

@Installer(description = "Liquibase installer", runCondition = InstallerRunCondition.AlwaysRun)
public class LiquibaseInstaller extends AcrossLiquibaseInstaller
{
    // Will execute the resource file 'my/package/LiquibaseInstaller.xml'
    // As liquibase has its own locking mechanism, we can safely always run (even though you should avoid this when possible)

}

SchemaConfiguration

An AcrossLiquibaseInstaller will use a SchemaConfiguration bean to configure its default schema and pass configuration properties. A SchemaConfiguration for the current module will be looked up first, if none is found a default bean will be used if it exists.

The default SchemaConfiguration is a bean without any @Module annotations. The presence of a @Module annotation marks that SchemaConfiguration as applying only for that particular module.

If no default or module SchemaConfiguration is found, the installer will continue without setting a default schema.

Adding SchemaConfiguration beans to a context directly via a ProvidedBeansConfigurer erases the visibility of bean declaration annotations.
Example of a configuration class defining some SchemaConfiguration beans
package my.package;

@EnableAcrossContext
@Configuration
public class SchemaConfig
{
    /
     * AcrossLiquibaseInstaller in the QuotationModule will automatically pick up this SchemaConfiguration.
     * Liquibase scripts will run with 'MY_QUOTATIONS' as their default schema.
     */
     @Bean
     @Module( "QuotationModule" )
     public SchemaConfiguration userSchemaConfiguration() {
        return new SchemaConfiguration( "MY_QUOTATIONS" );
     }

     /
      * This is the default SchemaConfiguration.
      * AcrossLiquibaseInstaller in other module will pick up this SchemaConfiguration.
      */
     @Bean
     public SchemaConfiguration defaultSchemaConfiguration() {
        return new SchemaConfiguration( "MY_SCHEMA" );
     }
}
Fixing a default schema

Alternatively the SchemaConfiguration bean schema can be ignored and a default schema fixed on the AcrossLiquibaseInstaller. This is done in the installer implementation through the setDefaultSchema() method.

Example fixed schema configuration using AcrossLiquibaseInstaller#setDefaultSchema
package my.package;

/**
 * Liquibase script of this installer will run with 'IM_SPECIAL' as default schema.
 * Regardless of any defined SchemaConfiguration.
 */
@Installer(description = "Liquibase installer", runCondition = InstallerRunCondition.AlwaysRun)
public class SpecialSchemaInstaller extends AcrossLiquibaseInstaller
{
    public SpecialSchemaInstaller() {
        setDefaultSchema( "IM_SPECIAL" );
    }
}

InstallerSettings

For advanced configuration, both AcrossContext and AcrossModule allow InstallerSettings to be set. InstallerSettings can be used to set the action (eg. force executed, skip) to be performed for one or more installers or installer groups.

InstallerSettings accepts an InstallerActionResolver for determining install action at runtime. Alternatively an installer can implement InstallerActionResolver that will be used in a second stage only if the original action is EXECUTE.

Please refer to the javadoc for more information.