Migration guide: platform 2.0.x to 2.1.x

Platform 2.1.0 is bound to Across 3.0.0, which is the first release with the across-autoconfigure artifact and builds more on top of Spring Boot. Upgrading to Platform 2.1.0 usually requires you to make some configuration and code changes to your applications.

This guide is not a full overview of all new features and changes in Across or any of its modules. It mainly focuses on upgrade related changes: usually breaking changes or new features that impact application/module configuration.

Across Core

Auto-configuration support

Add autoconfigure artifact

When upgrading your applications (built with @AcrossApplication), the first step is to add the new across-autoconfigure artifact to your project.

<dependencies>
    <dependency>
        <groupId>com.foreach.across</groupId>
        <artifactId>across-autoconfigure</artifactId>
    </dependency>
</dependencies>

The new autoconfigure artifact contains compatibility configuration for many Spring Boot starters. Adding it to your project allows you to rely more on the starters and to remove some previously required configuration imports.

Please see the Spring Boot compatibility documentation for a detailed list of starters that are now supported.

Some useful additions now available without additional configuration:

  • Websocket support (built-in with AcrossWebModule)

  • Spring Data web support

  • Actuator support

  • Spring Boot Admin (& client) support

  • Spring Session support

  • Swagger2 support

When adding the across autoconfigurate artifact to your application, Spring Data Web support is enabled, which causes all repositories to be exposed as well.

Cleanup @AcrossApplication

Review the release notes and Spring Boot compatibility documentation and remove imports and annotations from your @AcrossApplication class.

Example @AcrossApplication configuration cleanup
// Across Platform 2.0.x
@AcrossApplication(
    modules = { ... },
    modulePackages = { "my.custom.modules" },
    modulePackageClasses = { AcrossPlatform.class } (1)
)
@EnableSpringDataWebSupport (2)
@Import( DataSourceAutoConfiguration.class )
public class MyApplication
{
}

// Across Platform 2.1.x - with across-autoconfigure
@AcrossApplication( modules = { ... } )
public class MyApplication
{
}
1 It is no longer necessary to include the default Across standard modules packages when you want to add your own packages for module scanning. The default standard modules package will always be included.
2 Several annotations and configuration imports can simply be dropped as across-autoconfigure takes care of the configuration now.

If you have any of the following annotations in any of your modules or on your application class, they can just be removed when you have the right starters on the classpath:

  • @EnableSpringDataWebSupport

  • @EnableWebSocket

  • @EnableWebSocketMessageBroker

META-INF/across.configuration

Across 3.0.0 externalizes some of its configuration in a META-INF/across.configuration properties file. This is especially used for auto-configuration and configuration validation settings.

Please see the Across reference documentation section for a full description of the configuration file and what you can do with it.

Configuration validation

Across now attempts to validate your configuration when starting. It will check for the presence of configuration classes in places where they should not be. This could be using a configuration class on either the application level or in a module, where a different module already handles that particular bit of configuration.

Bootstrapping your application with a bad configuration will fail.

Better error messages

One of the additions in Across 3.0.0 is the new failure analyzers. When you are using @AcrossApplication and executing as a Spring Boot application, you should receive a more readable error messages with specific information on the problem.

@ComponentScan on your application configuration

An example is that bootstrapping will fail if a @ComponentScan is detected on your @AcrossApplication class, where that component scan would conflict with the application module package. It is possible that in certain applications this would not cause an actual problem, it is however still considered a bad practice and an invalid configuration.

Disabling configuration validation

You can disable configuration validation - for example in production - by setting the property across.configuration.validate to false. We strongly advise you to leave it on during development mode as it can help you avoid common configuration mistakes.

Adding configuration validation

It’s possible to configure your own illegal configurations in the META-INF/across.configuration file. This is especially useful for shared modules that activate application-level features.

Multiple datasources

If your application has multiple datasources, it is now required that one of them is marked as @Primary. It is best to have a specific datasoure named acrossDataSource as well to mark the datasource that should be used for the core Across schema and the standard modules.

Though not strictly required, you usually want to mark the acrossDataSource as the @Primary datasource.

Example multiple datasource configuration with Spring Boot
@Bean
@Primary
@ConfigurationProperties("app.datasource.across")
public DataSourceProperties acrossDataSourceProperties() {
    return new DataSourceProperties();
}

@Bean
@Primary
@ConfigurationProperties("app.datasource.across")
public DataSource acrossDataSource() {
    return acrossDataSourceProperties().initializeDataSourceBuilder().build();
}

@Bean
@ConfigurationProperties("app.datasource.bar")
public DataSourceProperties barDataSourceProperties() {
    return new DataSourceProperties();
}

@Bean
@ConfigurationProperties("app.datasource.bar")
public DataSource barDataSource() {
    return barDataSourceProperties().initializeDataSourceBuilder().build();
}

New event handling

The internal event handling system has been entirely rewritten and now builds on top of the default Spring Framework event handling infrastructure. Instead of the Across-specific @Event, the Spring Framework @EventListener should now be used.

Apart from how events are treated internally, there is no difference anymore in handling Across specific or regular Spring Framework events.

Most old event related classes have simply been deprecated, and breaking changes have been kept as limited as possible. In simple applications no changes would be required for this release.

Example @Event to @EventListener
// Across Platform 2.0.x
@Event
public void handle( @EventName("eventName") MyEvent myEvent ) {
}

// Across Platform 2.1.x
@EventListener(condition = "#myEvent.eventName == 'myEvent'")
public void handle( MyEvent myEvent ) {
    // Called if the event is of type MyEvent
    // and property 'eventName' has the value 'myEvent'
}

See the deprecations section for a list of the event-related deprecations and how to replace them.

Generic event types are handled differently with the new system, where in the past SomeEvent<Object> would have worked, the only working (and more correct) signature is now SomeEvent<? extends Object>.
New available features

Due to this refactoring some new features are now also available:

  • using @TransactionalEventListener

  • explicitly order individual event handler methods using @Order or @OrderInModule

  • event listener methods can have a return value which will be published as a new event

Please read the Across reference documentation section for a full overview of all features.

Removal of MBassador dependency

The MBassador dependency - responsible for the previous event bus implementation - has been removed entirely. Any listener methods that were directly using MBassador annotations will have to be rewritten as an @EventListener.

Across bootstrap events

One of the breaking changes with the new event handling infrastructure is that it is no longer possible to use regular @Event (or @EventListener) to intercept Across bootstrap related events from the parent application class. If you want to handle bootstrap related events from a parent ApplicationContext, you should do so in a component implementing the AcrossLifecycleListener interface.

Using AcrossLifecycleListener to modify the Across context configuration from a parent ApplicationContext (eg the @AcrossApplication class) is not required. Instead you can use the AcrossBootstrapConfigurer interface on one ore more of your components, which was introduced especially for this purpose.

@ModuleConfiguration

The internal processing of @ModuleConfiguration classes - module extensions provided by other modules - has been changed. This was necessary to fix issues with incorrect handling of conditionals on these @ModuleConfiguration classes.

This introduces some possibly breaking changes:

  • @ModuleConfiguration classes are now always @Configuration classes as well, this means they would also be processed by regular component scans, where in the past they might not have been

  • @ModuleConfiguration classes are only scanned for in the extensions package, any @ModuleConfiguration class in a config package will now be loaded in the original module, instead of correctly treated as a module configuration extension

  • import order has changed: module extensions will now always be imported before any other annotated classes (and before any injected Spring Boot auto-configuration classes)

New conditionals

Across 3.0.0 introduces some new conditionals:

@ConditionalOnAcrossModule

Use this annotation when you want to check for the presence or absence of a specific Across module. This replaces the use of @AcrossDepends on regular components. @AcrossDepends should now only be used on module descriptors, and you will see a warning printed when it is used on components.

@ConditionalOnAutoConfiguration

Use this conditional if you want to check if an auto-configuration class has been loaded anywhere in the Across application.

AcrossModule component scan

The default AcrossModule still only scans the config package by default. However, a very common case is the behaviour of the dynamic modules: all child packages are scanned for components, with the exception of extensions and installers. The former contains components for module extensions, the latter installers and related components.

A new helper method has been added to quickly configure an Across module with this behaviour.

Example AcrossModule scanning all child packages
MyModule extends AcrossModule {
    public static final String NAME = "MyModule";

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    protected void registerDefaultApplicationContextConfigurers( Set<ApplicationContextConfigurer> contextConfigurers ) {
        contextConfigurers.add( ComponentScanConfigurer.forAcrossModule( MyModule.class ) );
    }
}

Deprecations

The following is a list of classes that have been deprecated in Across 3.0.0:

StringToDateConverter

A replacement implementation StringToDateTimeConverter is available that also supports the Java 8 time implementations.

AcrossEvent

It is not required anymore for an event type to implement this marker interface.

@Event

Use the Spring Framework @EventListener instead.

@EventName

Use a conditional attribute on your @EventListener instead.

ParameterizedAcrossEvent

Implement the Spring Framework ResolvableTypeProvider interface instead.

AcrossEventPublisher

Use the Spring Framework ApplicationEventPublisher instead.

Across Web

This chapter lists some important - possibly breaking - changes in Across Web support (provided by AcrossWebModule).

Interceptor configuration

PrefixingHandlerMappingConfigurer can no longer be applied to the default AcrossWebModule, use a regular WebMvcConfigurer to add interceptors instead.

  • different prefixed handler mappings (eg. AdminWebModule) still require the use of PrefixingHandlerMappingConfigurer for adding interceptors

  • if you want to add an interceptor to all controllers, you should implement both WebMvcConfigurer and PrefixingHandlerMappingConfigurer

Menu building

The menu infrastructure support has been optimized to fix some technical and functional issues with the moving of path based menu items. Some code has been deprecated and still works as before, alongside the addition of new improved features.

Most important (breaking) changes:

  • MenuSelector factory methods have been moved from the Menu to MenuSelector class

  • some rarely used methods on PathBasedMenuBuilder, PathBasedMenuItemBuilder and BuildMenuEvent have been removed

  • BuildMenuFinishedEvent has been removed, modules requiring this functionality should now register a Menu post-processor on the BuildMenuEvent instead

  • renamed methods on BuildMenuEvent:

    • setSelector()setMenuSelector(), getSelector()getMenuSelector()

    • forMenu()isForMenuOfType()

    • the unreliable move() method for menu items has been deprecated, use the new changeItemPath() or item().changePathTo() instead

The reference documentation regarding menu building has been rewritten completely, we strongly advise you to read the documentation and update your code accordingly.

Multipart uploads

The MultipartProperties from Spring Boot are now being used for multipart configuration. This means different property names should be used for custom configuration.

  • replace acrossWebModule.multipart.auto-configure with spring.http.multipart.enabled

  • replace acrossWebModule.multipart.settings.* with the equivalent spring.http.multipart.* properties

Some default settings - for example maximum file size - might have changed as well.

Example changing the maximum multipart file size
spring:
  http:
    multipart:
      max-file-size: 10MB

Across Standard Modules

The following section applies to the breaking changes in the standard modules. These instructions are only relevant if you use these modules in your applications.

AcrossHibernateJpaModule

The module has undergone significant changes to increase compatibility with the Spring Data JPA starter. When plugging a single AcrossHibernateJpaModule in an application it will now transparently take over the starter JPA support on the application module.

Most important changes include:

  • the default physical naming strategy being used by Hibernate has changed from a Hibernate default to a Spring Boot default

    • this can cause generated queries to be wrong (for example when having a reserved keyword as a table name)

    • in case of problems, you can revert to the old strategy by setting acrossHibernate.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

  • a PlatformTransactionManager is now always created

    • the configuration property to disable the PlatformTransactionManager has been removed

  • in addition a TransactionTemplate bean is now also created and exposed

  • it’s now also possible to specify packages to scan for entities by injecting an @EntityScan annotated class into the AcrossHibernateJpaModule

  • the default AcrossHibernateJpaModule will expose its relevant beans as primary unless specified otherwise

    • any module can take over the primary role by setting acrossHibernate.primary=false and myModule.primary=true

    • the primary module will also create a transactionManager and transationTemplate alias for the corresponding beans it exposes

See the AcrossHibernateJpaModule documentation for a full list of release notes.

SpringSecurityModule

SpringSecurityModule now re-uses Spring Boot security configuration and inspects SecurityProperties for default settings. As a result, additional security might be activated in your application, where previously it was not (see below).

There are now more security filters applied, possibly with a different ordering than before. It is advised to review and test the security configuration of your applications, to ensure it still works as expected.

Basic authentication

It is now possible to activate basic authentication for an entire application by setting security.basic.enabled to true. Unlike a standard Spring Boot application, this is not enabled by default in an Across application. The main reason for the latter is to ensure backwards compatibility with previous releases.

The different SecurityProperties allow you to configure a default user, password and role.

Global AuthenticationManager

Any class wanting to change the AuthenticationManagerBuilder must now be annotated with @EnableGlobalAuthentication. Configuration registered as module extension in SpringSecurityModule and simply auto-wiring the AuthenticationManagerBuilder will no longer work.

Example global authentication manager customization
@ModuleConfiguration(SpringSecurityModule.NAME)
@EnableGlobalAuthentication                     // required
public class AuthenticationConfiguration
{
	@Autowired
	public void configureGlobal( AuthenticationManagerBuilder auth ) throws Exception {
		auth.inMemoryAuthentication()
		    .withUser( "admin" ).password( "admin" )
		    .authorities( new SimpleGrantedAuthority( "access administration" ) );
	}
}
Integration with Actuator

Actuator endpoints are now always secured by default, unless you explicitly set management.security.enabled to false. Previously the security configuration for Actuator was simply ignored, whereas now it should be applied as expected.

Integration with H2 Console

The H2 Web Console (usually /h2-console) will now be secured by default, unless you explicitly set security.basic.enabled to false.

See the SpringSecurityModule documentation for a full list of release notes.

BootstrapUiModule

BootstrapUiModule has mainly been updated with new features. The following are the most important updates from a migration point of view:

BootstrapUiFactory

Direct use of BootstrapUiFactory or BootstrapUiComponentFactory is now discouraged, these interfaces have been deprecated. Use the stateless BootstrapUiBuilders facade instead.

FormGroupElement

FormGroupElement has been refactored to support more descriptions:

  • tooltip can be set which will be added to the label - after the label text and required indicator

  • a descriptionBlock can be set which will be added to the group before the control

  • a helpBlock can be set which will be added to the group after the control

The property renderHelpBlockBeforeControl has been removed as the descriptionBlock is always rendered before the control, and the helpBlock always after the control.

See the BootstrapUiModule documentation for a full list of release notes.

AdminWebModule

EntityAdminMenu and EntityAdminMenuEvent have been deprecated. These classes were never supposed to be in the AdminWebModule package space, as they belong to EntityModule functionality. To migrate, simply rename the import package from com.foreach.across.modules.adminweb.menu to com.foreach.across.modules.entity.views.menu.

New implementations have been added to EntityModule. Migration is strongly advised, though no code changes are required in this release.

See the AdminWebModule documentation for a full list of release notes.

EntityModule

EntityModule has mainly been updated with new features. See the EntityModule documentation for a full list of release notes.

The following are the most important updates from a migration point of view:

  • the module classpath dependencies have been reworked, allowing EntityModule to be used without either AdminWebModule or BootstrapUiModule (with limited feature availability)

    • as a result those dependencies are no longer pulled in transitively, this might require you to add them manually to existing applications

  • EntityModule no longer creates its own Validator instance, the registerForMvc related settings have been removed. The validator used by EntityModule is always the default MVC validator.

  • New implementations of EntityAdminMenu and EntityAdminMenuEvent to replace the equivalent classes from AdminWebModule. You may consider this mostly a package rename for these classes.

  • The descriptionBlock of a FormGroupElement for a property is now always rendered before the control, in accordance with the changes in BootstrapUiModule. New message codes have been added to support both tooltip and helpBlock for a property. The same message codes are also available for a FieldsetFormElement.

  • By default descriptionBlock, helpBlock and tooltip are only added to a FormGroupElement or FieldsetFormElement in ViewElementMode.FORM_WRITE.

  • The behaviour for control name prefixing when using an EntityViewCommand has changed. Controls are prefixed with entity. only if there is an additional builder context attribute set to true (attribute name EntityPropertyControlNamePostProcessor.PREFIX_CONTROL_NAMES). This only done by default in the PropertyRenderingViewProcessor, used by the default entity views. This change can cause side effects in existing custom forms, developers are encouraged to review those.

Linking to entity views

The new version introduces a new approach for linking to entity views, which might result in breaking changes in some application. The EntityLinkBuilder implementation has been deprecated. A new EntityViewLinks component has been added as an entry point for building links to entity views.

An EntityConfiguration contains an EntityViewLinkBuilder attribute that returns a link builder for that particular entity type. The older EntityLinkBuilder attribute is also kept for backwards compatibility, but returns the same instance of the newer implementation.

It is best to review your code and make changes accordingly, even though the new implementation has been kept as backwards compatible as possible.

Within the context of rendering an entity view, you can make the following changes:

Comparing old and new link building
String url;
EntityViewContext entityViewContext;
MyEntity entity;

// -- Retrieving the link builder for the entity type
EntityLinkBuilder oldBuilder = entityViewContext.getLinkBuilder();      // OLD
EntityViewLinkBuilder newBuilder = entityViewContext.getLinkBuilder();  // NEW

// -- Linking to the list view
// url: /entities/myEntity
url = oldBuilder.overview();                                            // OLD
url = newBuilder.listView().toUriString();                              // NEW

// -- Linking to the create view
// url: /entities/myEntity/create
url = oldBuilder.create();                                              // OLD
url = newBuilder.createView().toUriString();                            // NEW

// -- Linking to the update view
// url: /entities/myEntity/1/update
url = oldBuilder.update( entity );                                      // OLD
url = newBuilder.forInstance( entity ).updateView().toUriString();      // NEW

// -- Linking to a custom view
// url: /entities/myEntity/1?view=customViewName
url = UriComponentsBuilder.fromUriString( oldBuilder.view( entity ) )
                          .queryParam( "view", "customViewName" )
                          .toUriString();                               // OLD

url = newBuilder.forInstance( entity )
                .withViewName( "customViewName" )
                .toUriString();                                         // NEW

// -- Linking to an association list view
// url: /entities/myEntity/1/associations/associatedItems/
url = association.getTargetEntityConfiguration()
                 .getAttribute( EntityLinkBuilder.class )
                 .asAssociationFor( oldBuilder, entity )
                 .overview();                                           // OLD

url = newBuilder.forInstance( entity )
                .association( AssociatedItem.class )
                .toUriString();                                         // NEW

// -- Linking to a single associated item update view
// url: /entities/myEntity/1/associations/associatedItems/2/update
url = association.getTargetEntityConfiguration()
                 .getAttribute( EntityLinkBuilder.class )
                 .asAssociationFor( oldBuilder, entity )
                 .update( associatedItem );                             // OLD

url = newBuilder.forInstance( entity )
                .association( associatedItem )
                .updateView()
                .toUriString();                                         // NEW

The new EntityViewLinkBuilder has a more fluent API that allows customizing the URL before converting it to a String. It also has some short-hand methods for commonly used entity view related parameters. Every method call results in a new instance being created, so you will not make inadvertent changes to an existing link builder.

Useful methods of the new EntityViewLinkBuilder
// append a custom path segment
.slash( String path )
// append query parameter
.withQueryParam( String param, Object... values )
// set a from URL ('from' query parameter)
.withFromUrl( String url )
// set a partial fragment ('_partial' query parameter)
.withPartial( String fragment )
// set a custom view name ('view' query parameter)
.withViewName( String viewName )

// return the unprocessed URI (eg. '@adminWeb:/entities/myEntity')
.toString()
// returning the processed URI (eg. '/admin/entities/myEntity')
.toUriString()
// create a new UriComponentsBuilder with the settings
.toUriComponentsBuilder()
// return as URI
.toUri()
// return as UriComponents
.toUriComponents()

// return the original EntityViewLinks
.root()

A new EntityViewLinks component is also available to generate links to entity views from anywhere in your code.

Using EntityViewLinks central component
@Autowired
EntityViewLinks links;

// link to a list view
links.linkTo( MyEntity.class ).toUriString();

// link to single entity view
links.linkTo( MyEntity.class ).forInstance( myEntity ).toUriString();
links.linkTo( MyEntity.class ).withId( 1 ).toUriString();
// or shorter...
links.linkTo( myEntity ).toUriString();
links.linkTo( myEntity ).updateView().toUriString();

// link to associations
links.linkTo( myEntity ).association( AssociatedEntity.class ).toUriString();
links.linkTo( myEntity ).association( associatedItem ).toUriString();

Contributed Modules

Adding additional spring and auto-configuration compatibility potentially incurs issues with contributed/older modules. In the next sections, known issues will be listed.

ImageServer

The following section lists migration issues with the current version of ImageServer.

Handler mapping

ImageServer specific paths are registered but can no longer be resolved. This is because an additional path resolver (/**) has been added through spring compatibility, which accepts any path and has a higher precedence than the ImageServer handler mappings. This will be fixed in a later release.

To work around the issue, the wildcard static resources path must be removed by setting the property:

# Manually specify a static resource path (remove the default /**)
spring.mvc.static-resources-path=

Missing database columns

ImageServer requires that the Hibernate default naming strategy is used. You must manually force your application to use the compatible physical naming strategy. Please see the changes to AcrossHibernateModule for all details.