What’s new in this version?

2.0.0.RELEASE

Upgrade to Spring Platform Athens, new major dependency versions:

  • Spring framework 4.3.x

  • Spring Boot 1.4.x

  • Thymeleaf 3.x

Across Core

  • @AcrossEventHandler has been deprecated, all beans are now automatically scanned for @Event methods

  • the AcrossEventPublisher interface has some MBassador specific methods removed

    • note that the backing implementation of the event bus might change in the future, applications should avoid using MBassdor specific features

  • development mode will register a default resources directory for dynamic modules

    • this will be an existing src/main/resources folder relative to the working directory of the running application

  • @ConditionalOnDevelopmentMode added to detect if development mode is active

Across Test

  • The spring-boot-test dependency is now part of across-test

  • Updated documentation to use the new @SpringBootTest annotation

AcrossWebModule

  • ServletContextInitializer beans in modules are now supported directly for customizing the ServletContext

    • @ConditionalOnConfigurableServletContext and @ConditionalOnNotConfigurableServletContext added to determine if ServletContext can still be customized

    • favour the use of FilterRegistrationBean or ServletRegistrationBean instead of AcrossWebDynamicServletConfigurer, the latter is now deprecated and will be removed in a future release

  • Spring Boot devtools are now supported - application context restart will be triggered by default if devtools is on the classpath

  • AcrossWebModule now ensures that all HandlerMapping beans from other modules are exposed

  • a path prefix @static and @resource is registered to link to static resources

  • Thymeleaf dialect extensions

    • using prefixes directly in url expressions is now supported

    • addition of across:static and across:resource attributes for generating links to static resources

  • add infrastructure for defining custom request mapping conditions using @CustomRequestMapping and CustomRequestCondition

  • partial rendering of a single ViewElement is now also supported (see BootstrapUiModule documentation).

1.1.3.RELEASE

Maintenance release containing several bugfixes.

1.1.2.RELEASE

Across Core

Across now requires JDK8 and has improved support for Spring Boot. The latter is also a required dependency as of 1.1.2. A lot of improvements have been done to reduce the amount of boilerplate code necessary to create a new AcrossContext.

  • @EnableAcrossContext annotation now supports autoconfiguration of an AcrossContext

  • @ModuleConfiguration annotation provides a no-hassle way to declare annotated classes that need to be added to a specific module

  • message sources are now auto-detected if they follow the conventions

  • both AcrossModule and ModuleBootstrapConfig now have shortcut expose() methods that make it easier to expose additional beans

  • several changes and improvements were made to installers:

    • installers are now created in their own configurable installer ApplicationContext

    • installers are detected automatically through classpath scanning (defaults to installers package of a module)

    • installers are ordered based on the presence of an @Order annotation

    • installers now support @Conditional annotations for building more complex conditions

    • installers should only be specified as class names in getInstallers(), the use of instances is deprecated

    • InstallerSettings has been refactored to use InstallerMetaData instead (breaking change)

    • installers can implement InstallerActionResolver directly to suppress execution at runtime

    • AcrossInstallerRepository now has methods to rename installers

    • AcrossLiquibaseInstaller detects the most appropriate SchemaConfiguration to use and modifies default schema accordingly

  • development mode can be set through the property across.development.active and is active by default if a Spring profile called dev is active

AcrossWebModule

Across Test

Several improvements have been done for easier integration testing of modules in a web context.

  • test context builders have been added for easy configuration of an AcrossContext in test methods

  • MockAcrossServletContext can now be used for testing of dynamic ServletContext configuration

  • addition of a AcrossMockMvcBuilders class for creating a MockMvcBuilder based on an AcrossContext

    • both annotations and builders now provide a singleton MockMvc instance that is initialized with the bootstrapped context and all dynamically registered filters

1.1.1.RELEASE

Initial public release available on Maven central.

Across in a nutshell

Across is a Java framework that aims to facilitate module based development for Java (web) applications. It builds heavily on Spring framework and allows defining a module consisting of a number of classes and configuration files. Every module defines its own Spring application context and can share one or more beans with other modules.

Across modules can contain any set of functionality: low-level add-ons like facilities to activate caching or end user functionality including web controllers and their views. Across provides the basic infrastructure to define modules and make them depend on each other. A module will most often be a separate jar, but this is in no way a requirement.

The general principle is that every Across module can depend on others, but is responsible for its own installation and start-up procedures. To this end, a module can define its own installers.

Getting started

This is the reference documentation,

  • release overview

  • samples and tutorials

  • appendices list events and overview of conventions

I. About Across

1. Before you begin

To understand what Across is and how it works you should have good knowledge of Java web development as well as Spring framework and Spring Web MVC. At the very least you should be familiar with the concepts of dependency injection (auto-wiring), the ApplicationContext and JavaConfig configuration style.

2. Modular development

Across framework aims to facilitate module based development of your Java applications. An application would simply consist of a collection of modules, where each module brings a set of functionality to the table. Modules are somewhat knowledgeable, they know about the application/set-up they are running in and can make decisions based on that knowledge. They know about other modules and the services they provide, and they are able to talk to those other modules to provide automatic integration.

Well defined modules can offload work from the developer. The development team does not need to make the same decisions over and over again for every new application. With a well defined module it needs to be made only once, and in new applications the same module will make the same reliable decision.

For modules to be able to do this they have to speak a common language. In software development terms this means they need to implement a same contract (usually in the form of interfaces or base classes). Providing that common language is what Across framework is about.

Across framework gives developers an easy way to create, configure and reuse smart modules.

The difference between libraries and modules

The terms libraries and modules are sometimes used interchangeably in the software development landscape. The exact meaning depends on the context. When talking about Across we identify the following difference between the two:

A library

Provides a set of low-level building blocks, infrastructure that can be used to build application logic. Libraries are dumb, they only do exactly as they have been told by a developer.

In a car analogy libraries could be wheels and frames. Someone - a developer - would still need to decide how to assemble the different components to achieve an actual car.

In this context Apache Commons is a library, but so is Spring framework as well as the II. Across Core itself.

A module

Provides more high-level building blocks in the form of complete functionality requiring little or even no configuration. Even with just a few modules present you could get a fully functional application. Modules are smart, they can make decisions based on their knowledge of the environment they are running in.

In the car analogy a module could in fact be an entire car. The car knows how to drive, it knows it has four wheels and knows the state of every one of them. Configuration of the module could be the decision of the engine you’d want.

As to deciding whether you want a module or a library, think of it this way: are you required to build a car, or is your mission to travel from A to B and you simply need a car to be able to do so?

In all Across documentation we refer to a module as an implementation of an AcrossModule that provides a set of functionality out of the box.

2.1. Across standard modules

Apart from the Across framework itself, a different project maintains a set of Across standard modules. These modules aim to be productivity enhancers by providing you with a common set of functionality that can be used as the foundation for your applications or even for an entire web cms.

There are standard modules that provide you with the set-up for an administrative web interface, secured with Spring security and built on top of an entire roles and permissions management domain layer. Other modules generate web forms at runtime for persistent entities you have declared in your application.

Across framework provides only a ground layer, the real win is in re-using well-defined modules that have already been built. The standard modules projects aims to do exactly that: bring some best of breed modules to the open source Across landscape.

2.2. Across platform

Across platform provides a single BOM pom that has a curated list of dependencies for both Across core and Across Standard Modules. Using the platform is advised for Across based applications as the platform will ensure different module versions play together nicely.

Across and Spring framework

Spring framework is a de facto industry standard for building Java (web) applications. It was a logical choice to use it as the foundation for Across. Across extends or even customizes some aspects of Spring, but still supports most if not all common Spring functions out of the box. To use Across optimally, a good knowledge of Spring is required.

II. Across Core and III. Across Web build on top of the common Spring framework, while other standard modules integrate different Spring libraries like Spring Data, Spring Batch or Spring security.

Across and OSGi

While Across talks about modules just like OSGi does and some similarity between the basic concepts can be found, there is currently no relation between the two. Across modules are not OSGi and the current development does not involve developing and testing for OSGi containers.

The focus of Across is more on providing a way to group end-user functionality than it is on a dynamic runtime environment. Integrating Across applications in the OSGi landscape is on the long-term roadmap.

3. Across and Spring Boot

TODO

See [spring-boot-autoconfigure] for the list of Spring Boot autoconfiguration classes directly supported by Across.

4. Core concepts

At the heart of Across are a couple of concepts you should understand. The terms related tot these concepts are used throughout all Across documentation, consider them part of the Across DSL.

Across context

An Across context is a configured set of Across modules and usually represents a single application. A single Across context is represented by the AcrossContext class but usually created by using @EnableAcrossContext or @AcrossApplication.

Compared to a single Spring ApplicationContext, an AcrossContext represents an entire hierarchy of ApplicationContext instances. The AcrossContext itself is an ApplicationContext that can have optionally have a parent ApplicationContext. However every single module loaded in the Across context also is a separate ApplicationContext with the Across context as parent.

Across module

An Across module represents a grouped set of beans and services that provide application logic and that often other modules can use. Every module is uniquely identified by its name and is defined by [creating a descriptor class] that extends from the AcrossModule class.

The AcrossModule implementation defines how the module contents should be loaded in the Across context. If a module adheres to a set of [conventions], required configuration can be kept at a minimum. For simple applications, it is even possible to define [package based dynamic modules] that require no separate descriptor.

Module dependencies

Modules can depend on other modules. If module B depends on module A, B states explicitly that it wishes to make use of the functionality that A will provide. The module dependencies will determine the loading order of the modules in the configured Across context.

Modules have two types of dependencies on other modules:

  • explicit dependencies usually configured with <<`@AcrossDepends`>>

  • implicit dependencies defined by the [module role]

Bootstrapping

An Across context is configured by dermining which modules should be loaded. The actual creation of the module beans is only done when the Across context [bootstraps]. Bootstrapping an Across context involves the creation of the backing Spring ApplicationContext instances. Every single module will have an ApplicationContext configured according to the AcrossModule descriptor. During bootstrapping, all modules are bootstrapped sequentially and in the order determined through their dependencies.

An AcrossContext is only successfully bootstrapped if all modules have bootstrapped successfully. A general AcrossContext and AcrossModule lifecycle has a start (bootstrap) and stop (shutdown) phase. In most applications these are managed transparently through the use of @EnableAcrossContext or @AcrossApplication.

Installers

An Across module can define any number of installer beans. Installers are special beans that will only exist:

  • during the bootstrapping of the Across context

  • if all conditions for the installers apply

Installers can be used to setup the necessary infrastructure for the services that a module provides. Common use cases for installers include:

  • installing a database schema

  • inserting (test) data

  • running migration tasks

Across itself provides the mechanism for defining installers and optimizing (conditional) installer execution.

Exposing beans

Module B can only use a service of module A if module A has explicitly exposed that service. A service is usually provided through one or more beans, so that means that module A must expose those beans to its owning Across context. If that has been done, beans from module B can simply wire the beans from module A directly. Exposing is the means through which module collaboration is defined.

Events

An Across context creates a central event bus that every module has access to. Any bean can publish events on the bus, and special listeners can catch and handle the events published.

Using events is a common way for modules to provide extensions points to other modules, without forcing dependencies.

Refreshing beans

Sometimes it is not possible to define explicit dependencies between modules. For example where module B depends on module A but creates a bean that A should use. Because circular dependencies are not allowed the only solution for this would be to use [refreshable beans].

Specially demarcated beans can be refreshed after the entire Across context has finished bootstrapped. These beans will have their dependencies updated based on the now fully populated AcrossContext. The valid use cases for refreshable beans are limited, and if unsure explicit dependencies or the use of events is preferred.

5. Across framework artifacts

Overview

Across framework itself consists of 3 library artifacts (across-core, across-web and across-test) and one bill-of-material pom (standard-module-bom).

across-core

Supports the configuration of an Across context and provides all necessary infrastructure to define and execute modules.

across-web

Provides an AcrossModule that supports configuration of a web application using Across with the Spring DispatcherServlet as the most common scenario.

across-test

Supports integration testing of your Across modules, both web and non-web related. Builds on top of spring-test.

standard-module-bom

BOM pom for creating an Across Standard Module or any module wishing to adhere to the standard module conventions.

There is also a separate platform-bom project that provides a bill-of-material for applications built on Across and the Across Standard Modules.

Common dependencies

Across is directly tied to Spring framework and all artifacts come with a set of curated dependencies. Across itself uses the Spring Platform as a basis for all its dependencies.

Repository

All Across related artifacts - both release and snapshot versions - can be fetched from the Foreach Nexus repository. This also includes the set of Across standard modules.

<repositories>
    <repository>
        <id>foreach-nexus</id>
        <name>Foreach nexus</name>
        <url>http://repository.foreach.be/nexus/repository/public/</url>
    </repository>
</repositories>

Release version are also available in Maven Central.

II. Across Core

Across Core is the main dependency that is required for bootstrapping an AcrossContext.

Artifact

<dependencies>
    <dependency>
        <groupId>com.foreach.across</groupId>
        <artifactId>across-core</artifactId>
        <version>2.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

6. Configuring an AcrossContext

6.1. @EnableAcrossContext

Usually your application will consist of a single AcrossContext that is configured in the main Spring ApplicationContext. You can enable the automatic creation of an AcrossContext by putting the @EnableAcrossContext on any @Configuration class. This will initialize a new AcrossContext with the main ApplicationContext as parent, look for a default datasource bean named acrossDataSource and then configure modules based on the @EnableAcrossContext attribute values.

6.1.1. AcrossContext autoconfiguration

By default autoconfiguration of the AcrossContext will be enabled. This implies that all AcrossModule beans from the parent ApplicationContext will be configured, along with any modules specified by name on @EnableAcrossContext.

When autoconfiguration is enabled, packages will be scanned to find valid module classes. Unless packages are configured manually using the modulePackages or modulePackageClasses attributes, the com.foreach.across.modules package along with the package of the declaring @Configuration class will be scanned.

Warning
If you use @EnableAcrossContext on a top-level class (no package specified), package modules and com.foreach.across.modules will be used for default scanning.

Scanning for modules in itself does not add any modules to the AcrossContext. It simply maps all modules that could be autoconfigured on their unique name. For a module to be autoconfigurable it should adhere to certain conventions, see the section on creating an Across module for more details.

Note
The module scanning packages and settings can be controlled through attributes on @EnableAcrossContext.
Warning
A module name should be unique. If you have more than one module with the same name, the last module scanned will take precedence.
Minimal example of using @EnableAcrossContext
/**
 * Minimal configuration that will attempt to resolve module MyCustomModule:
 * - in the standard modules package (com.foreach.across.modules)
 * - in the package that AppConfiguration belongs to
 *
 * If found, MyCustomModule and its required dependencies will be added to the AcrossContext.
 * If not found, AcrossContext bootstrapping will fail.
 */
@Configuration
@EnableAcrossContext( "MyCustomModule" )
public class AppConfiguration
{
}

If you want to customize the module configuration or a module is not autoconfigurable, you can add it as a bean in the parent ApplicationContext instead.

Example adding a module as a bean
/**
 * AcrossWebModule is added to the AcrossContext by name.
 *
 * MyCustomModule is added as a bean in the ApplicationContext.
 * Because the bean implements AcrossModule, it will also be added to the AcrossContext.
 *
 * All required dependencies of either module will also be added if they can be found through scanning.
 */
@Configuration
@EnableAcrossContext( AcrossWebModule.NAME )
public class AppConfiguration
{
    @Bean
    public MyCustomModule myCustomModule() {
        return new MyCustomModule();
    }
}

As the previous example shows, you can safely combine beans and named module configuration. Modules are configured in a certain order:

  1. Modules defined by name on @EnableAcrossContext

  2. Module beans detected in the ApplicationContext

  3. Modules added through AcrossContextConfigurer instances

  4. Missing module dependencies resolved during bootstrap

If a module with the same name gets added more than once, the last version added will always win.

6.1.2. Customizing the AcrossContext

After initial configuration but before bootstrapping, the configured AcrossContext will be delegated to all AcrossContextConfigurer beans it can find in the ApplicationContext. This allows for changing any settings before the actual bootstrap happens.

Example customizing an AcrossContext
@Configuration
@EnableAcrossContext
public class WebConfiguration implements AcrossContextConfigurer
{
	@Bean
	public DataSource acrossDataSource() {
		BasicDataSource dataSource = new BasicDataSource();
		dataSource.setDriverClassName( "org.hsqldb.jdbc.JDBCDriver" );
		dataSource.setUrl( "jdbc:hsqldb:mem:/hsql/testDataSource" );
		dataSource.setUsername( "sa" );
		dataSource.setPassword( "" );

		return dataSource;
	}

	@Override
	public void configure( AcrossContext context ) {
		context.setDevelopmentMode( true );

		context.addModule( new SomeModule() );
	}
}

Manual configuration

An AcrossContext can be also configured manually and is started using the bootstrap() method, and stopped using shutdown(). These methods take care of the Spring ApplicationContext lifecycle of all modules configured in the context.

AcrossContext context = new AcrossContext();
context.setParentApplicationContext( optionalParentContext );
context.setDataSource( someDataSource );
context.addModule( new SomeModule() );

// Start the context
context.bootstrap();

// Stop the context
context.shutdown();

7. Module configuration

Apart from module settings, an application can also define a number of @ModuleConfiguration classes to be added to the bootstrapping modules. When using the @EnableAcrossContext, the packages to scan for these can be set using attributes, but by default the sub-packages config and extensions of the importing class will be used.

Alternatively the packages to scan can be controlled via the moduleConfigurationScanPackages property on AcrossContext.

8. TODO: Across Bootstrap order

order modules based on dependencies run installers in the different phases publish events

prepareForBootstrap AcrossBootstrapConfig / ModuleBootstrapConfig extendModule AcrossContextInfo and AcrossModuleInfo - modifying bootstrap configuration from within a module

9. Properties

Both AcrossModule and AcrossContext have a collection of Properties. These can be accessed directly from the instance, but will also be registered as PropertySource in the bootstrapping ApplicationContext. Across favours the PropertySource/Environment approach over the concept of multiple PropertyPlaceholderConfigurer. Reasoning behind this is explained here as a comment on a Spring issue.

Registering additional PropertySources

Apart from the Properties directly on AcrossModule and AcrossContext instances, there are methods to register additional PropertySources to a module or context. PropertySources or properties configured on a module, will only be available within that module; whereas PropertySources or properties on the context will be available in all modules. Because PropertySource is most often based on an actual Resource file, the addPropertySources() methods allow direct usage of Resource instances. If the Resource does not exist, it is simply ignored.

PropertySource priority

Within the context or module, the order in which PropertySources have been registered determines the order of looking for the property values. In a context/module hierarchy, the following order is used:

  1. properties set directly on the current AcrossModule

  2. PropertySources attached to the current AcrossModule (last one added first)

  3. properties set directly on the AcrossContext

  4. PropertySources attached to the AcrossContext (last one added first)

  5. PropertySources registered in the parent ApplicationContext

Accessing properties in modules

Accessing properties in modules is no different than in standard Spring. By default all properties can be accessed using the Environment.

Example using Environment
@Configuration
public class Config
{
    @Bean
    public AcrossContext acrossContext( ConfigurableApplicationContext applicationContext ) throws Exception {
        AcrossContext context = new AcrossContext( applicationContext );
        context.setDataSource( acrossDataSource() );
        context.setAllowInstallers( true );

        // Configure some properties
        context.setProperty( "directProperty", 789 );
        context.addPropertySources( new ClassPathResource("my.properties") );

        context.addModule( testModule1() );
        context.addModule( testModule2() );
        return context;
    }
}

@Configuration
public class ConfigInModule{
    @Autowired
    private Environment environment;

    @Bean
    public MyBean myBean() {
        return new MyBean( environment.getProperty( "my.property" ) );
    }
}

If you want to use placeholders you must add a PropertySourcesPlaceholderConfigurer to every module where you want placeholders to be resolved. Because of the way BeanFactoryPostProcessors and PropertyPlaceholderConfigurers work, it is best to configure a new instance in every module, instead of passing a shared postprocessor to all modules. A default ApplicationContextConfigurer is available in the PropertyPlaceholderSupportConfigurer class. In practice however: it should be the responsibility of the module to configure a PropertyPlaceholderConfigurer if it uses placeholders!

Example using PropertySources and simple property placeholder resolving
@Configuration
public class Config
{
    @Bean
    public AcrossContext acrossContext( ConfigurableApplicationContext applicationContext ) throws Exception {
        AcrossContext context = new AcrossContext( applicationContext );
        context.setDataSource( acrossDataSource() );
        context.setAllowInstallers( true );

        // Configure some properties
        context.setProperty( "directProperty", 789 );
        context.addPropertySources( new ClassPathResource("my.properties") );

        // Register the property placeholder resolving in every module
        context.addApplicationContextConfigurer( new PropertyPlaceholderSupportConfigurer(),
                                                 ConfigurerScope.CONTEXT_AND_MODULES );

        context.addModule( testModule1() );
        context.addModule( testModule2() );
        return context;
    }
}

@Configuration
public class ConfigInModule {
    @Bean
    public MyBean myBean( @Value("${my.property}") String myProperty ) {
        return new MyBean( environment.getProperty( myProperty  ) );
    }
}
Example using custom property placeholder resolving
// Import inner class to make resolver available both in parent as in Across context
@Configuration
@Import(CustomPropertyConfig.class)
public class Config
{
    @Configuration
    public static class CustomPropertyConfig
    {
        @Bean
        public static PropertySourcesPlaceholderConfigurer properties() {
            PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();
            configurer.setLocation( new ClassPathResource( "my.properties" ) );
            return configurer;
        }
    }

    @Bean
    public AcrossContext acrossContext( ConfigurableApplicationContext applicationContext ) throws Exception {
        AcrossContext context = new AcrossContext( applicationContext );
        context.setDataSource( acrossDataSource() );
        context.setAllowInstallers( true );

        // Register the property placeholder resolving in every module
        context.addApplicationContextConfigurer( new AnnotatedClassConfigurer( CustomPropertyConfig.class ),
                                                 ConfigurerScope.CONTEXT_AND_MODULES );


        context.addModule( testModule1() );
        context.addModule( testModule2() );
        return context;
    }
}

@Component
public class SomeBeanInModule {
    @Value("${my.property}")
    private String someValueFromProperty;
}

10. TODO: Across datasource

An AcrossContext supports the configuration of a DataSource instance. The datasource will be available for all modules as bean named acrossDataSource. Additionally a second datasource can be configured that will be available as the acrossInstallerDataSource. If no separate installer datasource is specified, the default across datasource will be used.

TODO: if you want to run installers, you need a datasource (distributedlock repository, installer tracking)

11. Events

Every Across context creates an AcrossEventPublisher bean. This bean can be wired in any module to dispatch events. The default implementation of the event publisher uses MBassador.

Publishing events

Publishing an event can be done from anywhere by simply creating an instance of the AcrossEvent interface. This is a marker interface not defining any additional methods.

public static class SomeEvent implements AcrossEvent {
}

@Autowired
private AcrossEventPublisher eventBus;

public void sendEvent() {
  eventBus.publish( new SomeEvent() );
}

Subscribing to events

Event handlers must not implement any special interface, event handler methods must declare themselves as handling an event by specifying the @Event annotation. A single object can have many handler methods. All beans created in the ApplicationContext are automatically scanned for event handler methods.

Example component with an event handling method
@Component
public class MyHandler {
    @Event
    public void handle( SomeEvent event ) {
        // Called whenever an event of type SomeEvent is published
    }
}
Warning
Take special care when combining AOP with event handlers, as the event publisher only has limited support for AOP proxies. Depending on the strategy used (CGlib or JDK proxy) the actual proxy (CGlib) or the proxy target (in case of JDK proxy) will be registered as the event handler. In the latter case the event will still be fired but the actual handle method will not be intercepted.

11.1. Manual subscription

Any object can register itself as listener by subscribing to the AcrossEventPublisher.

Example of a non-managed object manually subscribing to the AcrossEventPublisher
private AcrossEventPublisher eventBus;

public void startListening() {
    // Subscribe ourselves to the event bus
    eventBus.subscribe( this );
}

public void stopListening() {
    // Remove ourselves from the event bus
    eventBus.unsubscribe( this );
}

@Event
public void handle( SomeEvent event ) {
    // Called whenever an event of type SomeEvent is published
}

Filtering events

Depending on the interface an event implements a handler can be specific about which events it wants to receive.

Event implements Filtering

AcrossEvent

Is only done on the class of the event. All instances of the specific event class (or subclasses) will be handled.

NamedAcrossEvent

Is done on the class of the event as well as the name of the event if an @EventName annotation is present on the handler method. WARNING: If the handler method defines an @EventName only events matching one of the names and implementing NamedAcrossEvent will be handled.

ParameterizedAcrossEvent

Is done on the class of the event as well as on the defined generic parameters. All instances matching the specific event class and the specific generic class (or subclasses) will be handled. To support complex generic type filtering, implementations must specify their generic parameters as Spring ResolvableType instances. Multiple generic parameters as well as nested generics are supported.

Filters can be combined: it is possible to filter an event on both event name and generic parameter. An event must pass all filters before it is handled.

Examples of custom events
/**
 * Named events.
 */
class MyNamedEvent implements NamedAcrossEvent {
  @Override
  public String getEventName() {
    return "MyNamedEvent";
  }
}

@Event
public void handle( @EventName({"MyNamedEvent","MyOtherNamedEvent"}) NamedAcrossEvent event ) {
    // do something
}

/**
 * Event with generic parameters.
 */
class MyListEvent<T> implements ParameterizedAcrossEvent {
  private final ResolvableType[] genericTypes;


  public MyListEvent( Class memberClass ) {
    genericTypes = new ResolvableType[] {
      ResolvableType.forClassWithGenerics( List.class, memberClass )
    };
  }


  @Override
  public ResolvableType[] getEventGenericTypes() {
    return genericTypes;
  }
}

@Event
public void handle( MyListEvent<List<Integer>> event ) {
  // would match against: new MyListEvent<List<Integer>>( Integer.class );
}

Bootstrap events

The following bootstrap events are being published by the AcrossContext:

AcrossModuleBeforeBootstrapEvent

Sent for each module, right before the bootstrapping of that module starts. This is the very last point in time in which the ModuleBootstrapConfig can still be modified.

AcrossModuleBootstrappedEvent

Sent for each module, right after the module has bootstrapped.

AcrossContextBootstrappedEvent

Sent once for every AcrossContext, after the entire bootstrap phase has finished.

Exceptions in events

When dispatching an event through its handlers, each handler is executed in isolation from the others. Events do not bubble up to the publisher. This means if an exception occurs within a handler, that will not impact the original publisher in anyway and the next handler will simply execute. However, when an exception occurs that is not handled within the handler method, a new PublicationError message is publishes that can be picked up with a custom IPublicationErrorHandler implementation added to the underlying MBassadorEventPublisher using addErrorHandler(). By default all exceptions will be logged in the AcrossEventPublisher logger. There is no defined order in which an event is sent to the handlers. Your event handlers should not depend on other event handlers having run.

11.2. Event limitations

Ordering event handlers is currently not supported.

12. Refreshing

The AcrossContext is refreshed once, right before it is fully bootstrapped. Refresh happens after all individual modules have bootstrapped and all beans have been exposed, but before the after-bootstrap installers have run.

There are several ways to hook into the refresh phase:

12.1. @Refreshable

Any bean annotated with @Refreshable will be autowired twice. Once when it gets created, and once when the AcrossContext is refreshed. You can use this for optional dependencies where the value might be exposed by a later module.

Warning
Although the functionality is there, you should probably avoid using @Refreshable and prefer @PostRefresh or subscribing to one of the bootstrap related events.

12.2. @PostRefresh

Much like @PostConstruct, a bean can have one or more @PostRefresh methods. These will be executed when the AcrossContext is refreshed.

12.3. @RefreshableCollection

@RefreshableCollection is an annotation to qualify dependencies being autowired. It can only be used on dependencies of type Collection.

When using @RefreshableCollection, instead of a regular collection of beans, a RefreshableRegistry will be wired. The behaviour and type of the RefreshableRegistry can be determined through the annotation attributes.

Example autowiring collection that will contain all MyBean instances from all modules, even if not exposed
@Autowired
public void setMyBeans( @RefreshableCollection(includeModuleInternals=true) Collection<MyBean> myBeans ) {
    this.myBeans = myBeans;
}

12.4. RefreshableRegistry

A RefreshableRegistry is a collection of beans of a specified class that updates itself once when the AcrossContext is refreshed. A RefreshableRegistry can also scan for beans inside the other modules in the running context, without the need for those beans to be exposed. This allows other modules to pick up extensions or configurations from other modules that bootstrap later.

The value of a RefreshableRegistry are also ordered according to the Across module order hierarchy.

IncrementalRefreshableRegistry is a specialization that updates its values every time a module has bootstrapped.

13. Ordering beans

In an Across based application, the primary order of bean interaction should be defined by the module dependencies. When scanning for beans in all modules (eg. when using a RefreshableRegistry or @RefreshableCollection), the beans will be returned according to the module order: beans from earlier bootstrapped modules will be before beans from later modules. The reasoning is simple: if I depend on module A, i can rely on module A having done its things before I will. If you need more sophisticated ordering, there are two extension points to the default behavior:

Order annotation and Ordered interface

Core Spring classes. If you define these on beans, these will take precedence over the order of the modules themselves. The Ordered interface takes precedence over an @Order annotation, if both are present. Using global ordering should be avoided as much as possible, but using for example Ordered.LOWEST_PRECEDENCE you can ensure that a bean comes after all other context created beans in the list.

OrderInModule annotation and OrderedInModule interface

The equivalent of @Order and Ordered, but they only apply within a single module. Use these if you have multiple beans (eg security configurers) of the same type in a single module, and it is important they follow a sequence.

Note
Unless a specific order is given either through the interface or annotation, a default order of Ordered.LOWEST_PRECEDENCE - 1000 is applied. This way you can still force beans to be ordered behind beans without an explicit order.
Warning
The current version of Across does not apply the module order hierarchy to event handlers. If event handlers need an order, it should be defined explicitly and you cannot rely that event handlers from other modules you depend on will have been executed before you. This is an important work in progress for one of the next versions of Across.

14. Development mode

The AcrossContext can have development mode enabled through the developmentMode property. Modules can use development mode to configure different services (or services differently). An example is auto-reloading and no caching of the message sources if development mode is active.

Apart from setting the developmentMode property on AcrossContext, development mode can be activated the following way:

  1. property across.development.active is true

  2. Spring profile dev is active

Resource resolving

By default resources (eg. messages, templates) are resolved from the classpath. It is possible to configure a physical location for the resources of a module if development mode is active (eg on your local filesystem).

Default location
For simple Across based applications using @AcrossApplication it might be enough to simply activate development mode. If the working directory of the running application contains a src/main/resources directory, this directory would be used for all dynamic modules, unless a specific value is set. This would usually be the case for single maven module projects.

Specific module resource locations
The path can be configured by adding the right property to the application properties, or specifying them in the development mode properties. The development mode properties is a special properties file that by default will be looked for in ${user.home}/dev-configs/across-devel.properties.

A resource location for the development mode properties can be specified by setting the across.development.properties property value. Development mode properties are loaded using resource resolving, so classpath resources can be used as well (eg. classpath:/dev.properties).

The development mode properties file should contain properties where the key references the module name, and the value the physical resources directory.

Example development properties
# Absolute directory
acrossModule.MyModule.resources=c:/code/mymodule/src/main/resources
acrossModule.OtherModule.resources=c:/code/othermodule/src/main/resources

# Relative to the working directory
acrossModule.SomeModule.resources=some-module/src/main/resources
Note
Using relative paths can be an effective way to embed development properties in a multi-module maven project. Paths should be relative to the working directory of the running application. Depending on how you run the application, the working directory can differ.
Warning
Properties added directly to the application properties will take precedence over those present in the development mode properties.

15. TODO: Default beans

15.1. AcrossContextInfo

15.2. AcrossContextBeanRegistry

15.3. AcrossInstallerRepository

15.4. ConversionService

An AcrossContext requires a valid ConversionService bean named conversionService to be present. If there is none, a DefaultFormattingConversionService will be created and exposed. If you want to manage the ConversionService yourself, simply create a ConversionService with the right name in the parent ApplicationContext.

The default conversionService will also be attached to the ApplicationContext environment of every module. This means property fetching using Environment.getProperty() methods will perform type conversion using the ConversionService (eg. useful for date property parsing).

Date conversion

Apart from the ConversionService itself, an AcrossContext creates a default StringToDateConverter bean named defaultDateConverter. This converter supports converting a string to a corresponding date in many common date patterns. The StringToDateConverter assumes a fixed locale that can be configured (default: US).

Warning
If you manage the ConversionService outside of the AcrossContext the default StringToDateConverter will not be registered either.

15.5. DistributedLockRepository

tbd

15.6. AcrossDevelopmentMode

Utility bean that can be used to verify if development mode is active, and to get development locations for resources files.

16. Developing modules

16.1. Creating an AcrossModule

Creating a new Across module is done by extending AcrossModule and providing a valid name and description. An AcrossModule is in essence a configuration class. It uniquely identifies the module and holds all configuration parameters required to bootstrap the module. It also allows you to alter settings of how beans should be shared with other modules.

A module name should be unique and as a best practice also be available as a public static final NAME field on the AcrossModule implementation.

Minimal AcrossModule
public class ValidModule extends AcrossModule
{
	public static final String NAME = "ValidModule";

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

	@Override
	public String getDescription() {
		return "ValidModule exposes some valid beans.";
	}
}
Making an AcrossModule autoconfigurable

For an Across module to be autoconfigurable it must adhere to the following conventions:

  • the module name must be available in the public static final NAME field

  • the module must have public constructor without any parameters

Definining beans

Upon bootstrap a module will create its own ApplicationContext with the AcrossContext as parent. The beans created in the module ApplicationContext are configured using ApplicationContextConfigurer instances.

By default a scan for @Configuration classes will happen on the config package below the package of the AcrossModule implementation. If you want more explicit control, you can override the registerDefaultApplicationContextConfigurers() method.

Module resources

Most modules will also define one or more resources in a location determined on the resources key of the module. By default the resources key or a module is the same as the module name. You can change this by overriding the getResourcesKey() method.

16.2. TODO: Module settings

about @ConfigurationProperties Spring configuration metadata property source

16.3. Module dependencies

There are several ways to influence the beans that are being bootstrapped with a module, by passing information from the configuration to the actual module ApplicationContext.

16.3.1. @AcrossDepends

Using @AcrossDepends on a module

Using AcrossDepends annotation on a component class you can avoid beans or Configurations from being created based on the presence of other modules. required modules: the component will only be created if all required modules are present optional modules: the component will only be created if at least one of the optional modules is present

Using @AcrossDepends on component classes

expression will be evaluated against the module context - as usual - beans from earlier modules will be available

16.3.2. TODO: AcrossCondition

AcrossCondition is another implementation of Spring Conditional. AcrossCondition takes one or more Spring Expression Language (SpEL) statements as value, and all statements must evaluate to true before the component will be created. It can be used to check environment or module properties (or any property on already existing beans from the parent context). The current module instance can always be referenced under the key currentModule in the expression.

Examples using AcrossCondition
@Configuration
@AcrossCondition("#{currentModule.transactionEnabled}")
public class EnableTransactionConfiguration {
    // Only executed if the property isTransactionEnabled() on the AcrossModule instance is true
}

@Controller
@AcrossCondition("${mymodule.testcontroller:true}")
public class MyTestController {
    // The controller would only be created based on the property 'mymodule.testcontroller' in the Environment of the bootstrapping module
    // If the Environment does not contain the property, value 'true' will be used
}

16.3.3. TODO: Module roles

infrastructure modules

16.4. Bootstrap interaction

16.4.1. @ModuleConfiguration

Any module can provide a number of classes annotated with @ModuleConfiguration. These classes are just like classes annotated with @Configuration, except they will not be added to the ApplicationContext of the current module, but to the ones of the modules specified in the annotation attributes.

@ModuleConfiguration serves as a replacement for @Configuration. All other bean related annotations like conditionals should also work on @ModuleConfiguration classes. Keep in mind however that the actual creation of the beans (and evaluation of conditionals) will happen in the ApplicationContext to which the configuration is added, and not necessarily the one of the module defining the class.

Example of using @ModuleConfiguration
@ModuleConfiguration(SpringSecurityModule.NAME)
public class SecurityConfiguration
{
	@Autowired
	public void configureGlobal( AuthenticationManagerBuilder auth ) throws Exception {
		auth.inMemoryAuthentication()
		    .withUser( "admin" ).password( "admin" )
		    .authorities( new NamedGrantedAuthority( "access administration" ) );
	}
}
Warning
When not specifying any module on a @ModuleConfiguration, the configuration will be added to all modules, including the module defining the class.
Scanning for @ModuleConfiguration classes

By default the sub-packages config and extensions of an AcrossModule will be scanned to detect classes annotated with @ModuleConfiguration. A module can configure these packages by overriding the getModuleConfigurationScanPackages() method.

16.4.2. TODO: Subscribe to events

16.4.3. TODO: prepareForBootstrap

A third approach for customizing module configuration is implementing prepareForBootstrap() on the AcrossModule. This is the last point in time were any module can modify the bootstrap configuration of any other module (adding configurers, modifying expose filter etc).

todo: ModuleConfigurationSet

Example of a prepareForBootstrap
@Override
public void prepareForBootstrap( ModuleBootstrapConfig currentModule, AcrossBootstrapConfig contextConfig ) {
    if ( ArrayUtils.contains( supportedViews, AcrossWebViewSupport.JSP ) ) {
        currentModule.addApplicationContextConfigurer(
                new AnnotatedClassConfigurer( JstlViewSupportConfiguration.class ) );
    }
    if ( ArrayUtils.contains( supportedViews, AcrossWebViewSupport.THYMELEAF ) ) {
        currentModule.addApplicationContextConfigurer(
                new AnnotatedClassConfigurer( ThymeleafViewSupportConfiguration.class ) );
    }
}

16.5. Exposing beans

A module can share its beans with other modules by exposing them. All exposed beans from a module are pushed upwards into the parent ApplicationContext of the AcrossContext itself. Likewise, at the end of the context bootstrap, all exposed beans are pushed into the ApplicationContext that is - optionally - configured as the parent of the AcrossContext.

Which beans are exposed is determined by the exposeFilter property on an AcrossModule. By default all beans annotated with @Exposed or @Service will be exposed. See the com.foreach.across.core.filters.BeanFilter implementations for standard filters.

When pushing beans to the parent ApplicationContext it is possible to run into name conflicts or duplicate bean types. You can use an ExposedBeanDefinitionTransformer to modify the bean definitions that are pushed to the parent. Common examples for this are changing the bean names or marking the beans as primary. You can set the transformer to use through the exposeTransformer property on an AcrossModule or the AcrossContext.

ExposedBeanDefinitionTransformer on AcrossModule

will be applied when pushing beans from the module into the AcrossContext

ExposedBeanDefinitionTransformer on AcrossModule

will be applied when pushing beans from the AcrossContext into the parent ApplicationContext

public class SomeModule extends AcrossModule
{
	/**
	 * Only expose specific beans from this module and ensure that
	 * they have a prefixed bean name for the other modules.
	 */
	public SomeModule() {
		setExposeFilter( new ClassBeanFilter( PlatformTransactionManager.class, SessionFactory.class ) );
		setExposeTransformer( new BeanPrefixingTransformer( "someModule" ) );
	}
}

Beans created directly in the AcrossContext scope - and not inside one of the modules - will only be exposed if they are annotated explicitly with @Exposed. The AcrossContext does not support a custom exposeFilter but you can supply an additional exposeTransformer on the context level. If you want to avoid an AcrossContext to push the exposed beans further upwards into its parent ApplicationContext, you should configure the ExposedBeanDefinitionTransformer.REMOVE_ALL as exposeTransformer on the AcrossContext itself. For an AcrossModule using the REMOVE_ALL transformer would be the same as putting null as exposeFilter.

Manually exposing beans

In addition to setting the exposeFilter property, AcrossModule has expose(…​) methods that allow you to quickly expose one or more beans based on bean name or type.

16.6. Message sources

16.6.1. Auto-detecting message sources

If a module has a message source packaged in the right location it will be auto-detected and configured. The following default message properties will be configured automatically if they are present on the classpath:

  • /messages/MODULE_RESOURCES/MODULE_NAME.properties (deprecated)

  • /messages/MODULE_RESOURCES/default.properties

  • /messages/MODULE_RESOURCES/default/*.properties

The default message sources are configured using an instance of AcrossModuleMessageSource. AcrossModuleMessageSource is a special case of Spring’s ReloadableResourceBundleMessageSource. If development mode is active and the development locations for the module have been configured, the classpath lookups are replaced by physical file lookups with a cache time of 1 second. This is very useful during development where updates to resource bundles can be instantly visible.

Automatic configuration of message sources only happens if the module does not define a bean named messageSource. The following section details how you can manually define message sources.

16.6.2. Message source hierarchy

Across has its own custom behavior in relation to messageSource beans declared in modules. Each module can declare a default MessageSource bean named messageSource. Upon bootstrap this message source will be added to a hierarchy along with all other messageSource beans. Once bootstrap is complete, a message will be looked up in all sources defined.

Default message source hierarchy

By default Across uses the following approach to build a message source hierarchy:

  • a messageSource bean from a parent ApplicationContext is considered to be application specific and should allow customizing the messages of the modules

  • a messageSource bean in a module is expected to be able to customize messages from other modules that the current module depends on

    • this is also interpreted as: the message source of a module takes precedence over the message source of earlier modules

As a result, once a context is fully bootstrapped and a message is requested, the sources are queried in the following order:

  1. message sources of the parent ApplicationContext (and up its hierarchy if more than one parent)

  2. message sources of the modules in reverse bootstrap order (last bootstrapped first)

Warning
Once an AcrossContext is fully bootstrapped, messages will be looked up in the hierarchy built from all bootstrapped modules. During the bootstrap phase however, only message sources from earlier modules and the parent ApplicationContext are available.
Reverting to default spring behavior

Default Spring behavior with message sources is that a MessageSource in an ApplicationContext will get the MessageSource of the parent ApplicationContext as its parent. In a module setup, this would mean that a message would first be looked up in the source of the module, and only afterwards in the source of the context. You can force the default behavior to apply by annotating a messageSource bean with @Internal. Note that in that case the messageSource will remain internal to the module where it’s declared.

Configuring a custom message source for a module with development mode support
@Configuration
public class ModuleConfig
{
	@Bean
	public MessageSource messageSource() {
        AcrossModuleMessageSource source = new AcrossModuleMessageSource();
        source.setBasenames( "classpath:/my/module/messages" );

        return source;
	}
}

16.7. Installers

A module can have one or more installer classes. The purpose of installers is setup data that modules need, before the actual application takes over. 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.

16.7.1. 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.

Note
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.

16.7.2. 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 datasource as soon as a single installer wishes to execute, and will only be released once bootstrapping is fully done.

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

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

16.7.3. 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.

16.7.4. 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 @AcrossDepends to execute installers if certain modules are active.

16.7.5. 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.

16.7.6. 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)

Warning
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.

16.7.7. 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.

Important
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.

16.7.8. 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 ) );
    }
}
Note
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.

Note
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.

16.7.9. 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 (slower than Across!), we can safely always run
}
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.

Warning
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" );
    }
}

16.7.10. 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.

16.8. Module version information

Across modules have version information attached in the form of an AcrossVersionInfo property. This object provides information like the current version and time when it was built. By default this information is fetched automatically from the META-INF/MANIFEST.MF resource. The following attributes are required for a fully configured AcrossVersionInfo instance:

Table 1. MANIFEST attributes
Attribute Example Description

Implementation-Title

across-web

Name of the project the class (AcrossModule) belongs to. Often also the JAR name.

Implementation-Version

2.0.1-SNAPSHOT

Build version of the AcrossModule.

Build-Time

20150831-1011

Timestamp when the version was built (in yyyyMMdd-HHmm format).

Using Maven a valid MANIFEST file can automatically be created using the following plugin configuration:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-jar-plugin</artifactId>
    <version>2.6</version>
    <configuration>
        <archive>
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
            </manifest>
            <manifestEntries>
                <Build-Time>${maven.build.timestamp}</Build-Time>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

III. Across Web

17. General information

17.1. About

AcrossWebModule activates Spring MVC support for all modules in an AcrossContext. This means modules can configure the Spring MVC support, add controllers, servlets and filters.

On top of that, the out-of-the-box Spring MVC functionality is extended in several ways:

AcrossWebModule also contains @AcrossApplication, a replacement for @SpringBootApplication when working with an AcrossContext.

17.2. Artifact

<dependencies>
    <dependency>
        <groupId>com.foreach.across</groupId>
        <artifactId>across-web</artifactId>
        <version>2.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

17.3. Module settings

All properties start with the acrossWebModule. prefix.

Property Type Description Default

views.thymeleaf.enabled

Boolean

Should Thymeleaf view support be enabled.

true

views.jsp.enabled

Boolean

Should JSP/JSTL view support be enabled.

false

resources.path

String

Relative path for serving all static resources.

/across/resources

resources.folders

String[]

Default subfolders of views that should be served as static resources.

js,css,static

resources.versioning.enabled

Boolean

Auto configure versioning of the default resource resolvers.

true

resources.versioning.version

String

Fixed version if resource versioning is enabled. Default will use build number or module version.

null

resources.caching.enabled

Boolean

Auto configure client-side caching of static resources.

true

resources.caching.period

Integer

Period for client-side resource caching (if enabled). Defaults to 1 year.

60*60*24*365

registerGlobalBuilderContext

Boolean

Should a request-bound ViewElementBuilderContext be created.

false

templates.enabled

Boolean

Should support for web templates be enabled.

true

templates.auto-register

Boolean

If web templates are enabled, should named templates be autodetected.

true

multipart.auto-configure

Boolean

Should a multipart resolver be configured automatically.

true

multipart.settings

MultipartConfigElement

Multipart upload settings, if missing defaults will be used.

null

development-views

Map<String,String>

Map of physical locations for views resources. Only used if development mode is active.

Collections.emptyMap()

18. Spring MVC configuration

AcrossWebModule sets up Spring MVC configuration including a single DispatcherServlet and default HandlerMapping beans. In addition to the standard Spring MVC configuration, AcrossWebModule configures extra:

AcrossWebModule activates support for WebMvcConfigurer beans in other modules. Any WebMvcConfigurer(often an extension of WebMvcConfigurerAdapter) will be detected and used to configure the mvc support. Note that WebMvcConfigurer beans do not need to be exposed.

19. @RequestMapping extensions

AcrossWebModule adds several extensions to the out-of-the-box Spring MVC functionality.

19.1. @CustomRequestMapping

Default @RequestMapping implementations can be further specified by adding custom request conditions using @CustomRequestMapping annotations. The latter allows you to restrict a @RequestMapping on any annotation attribute value on the handler method, or even any attribute value that is available on the HttpServletRequest.

A @CustomRequestMapping allows you to specify a number of CustomRequestCondition classes. When building the request mapping info, AcrossWebModule will create beans of every CustomRequestCondition and will supply them with the AnnotatedElement that represents the handler method or handler type they were configured on. Multiple CustomRequestCondition classes can be specified and they can be combined on both handler type and handler method, just like the regular @RequestMapping.

You can also use @CustomRequestMapping as a meta-annotation, which is often much easier in use.

Note
When adding multiple CustomRequestCondition to a mapping, the mapping will only be applied if all conditions match.
Example @CustomRequestMapping

The following example creates an additional request mapping condition that will only use that mapping if the request has a specific referrer header value.

@Controller
public class TestController {
    /**
     * Example mapping that accepts only GET requests made to the
     * fromGoogle path if the request referrer header contains
     * http://www.google.com.
     */
    @GetMapping("/fromGoogle")
    @ReferrerMapping("http://www.google.com")
    public String google() {
        ...
    }
}

/**
 * Custom mapping that will only match if any of the referrers are set.
 * If both type and method level annotation is present, the method level annotation
 * attributes will replace the type level attributes.  This is taken care of in the
 * combine() method of the ReferrerRequestCondition.
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@CustomRequestMapping(ReferrerRequestCondition.class)
private @interface ReferrerMapping
{
    // possible header values - the mapping will apply if the request
    // referrer has any of these values
    String[] value();
}

/**
 * Condition implementation.  This will be instantiated as a bean,
 * so wiring other components is possible as well as long as they are
 * visible in the AcrossWebModule context.
 */
private static class ReferrerRequestCondition extends AbstractCustomRequestCondition<ReferrerRequestCondition>
{
    private String[] referrers = new String[0];

    // get the configured headers from the annotation
    @Override
    public void setAnnotatedElement( AnnotatedElement element ) {
        referrers = AnnotatedElementUtils.findMergedAnnotation( element, ReferrerMapping.class )
                                         .value();
    }

    @Override
    protected Collection<String> getContent() {
        return Arrays.asList( referrers );
    }

    @Override
    protected String getToStringInfix() {
        return " || ";
    }

    // no relevant combination - other simply replaces this one
    @Override
    public ReferrerRequestCondition combine( ReferrerRequestCondition other ) {
        return other;
    }

    // return self if headers matches, else there is no matching condition
    @Override
    public ReferrerRequestCondition getMatchingCondition( HttpServletRequest request ) {
        if ( StringUtils.containsAny( request.getHeader( HttpHeaders.REFERER ), referrers ) ) {
            return this;
        }
        return null;
    }

    // always consider them equal
    // there is no relevant ordering if headers are different
    @Override
    public int compareTo( ReferrerRequestCondition other, HttpServletRequest request ) {
        return 0;
    }
}

19.2. Prefixed request mappings

AcrossWebModule contains a PrefixingRequestMappingHandlerMapping implementation. The PrefixingRequestMappingHandlerMapping allows request mappings to be prefixed at runtime. Core infrastructure is the PrefixingRequestMappingHandlerMapping and the PrefixingHandlerMappingConfigurer. The latter can be used to add interceptors only to request mappings attached to a specific PrefixingRequestMappingHandlerMapping instance.

PrefixingRequestMappingHandlerMapping also fully supports custom request conditions.

Examples of prefixing request mappings can be found in the AdminWebModule and DebugWebModule.

20. AcrossWebModule and ConversionService

AcrossWebModule requires a FormattingConversionService bean named mvcConversionService to be present. If there is none, one will be created and exposed. By default the parent conversionService bean will be reused if it implements the right interface. If you want to manage the mvcConversionService separately, simply create a FormattingConversionService with the right name in the parent context.

@Configuration
public class ManualConversionServiceConfiguration
{
	@Bean(name = AcrossWebModule.CONVERSION_SERVICE_BEAN)
	public FormattingConversionService mvcConversionService() {
	    return new FormattingConversionService();
	}
}

The mvcConversionService will be passed to all WebMvcConfigurer instances in order to add formatters. It is also the default ConversionService attached to the WebDataBinder.

Note
There will be at least 2 ConversionService beans in the across context when AcrossWebModule is enabled: conversionService and mvcConversionService. If you have duplicate bean exceptions due to unqualified autowiring, you should make one of both primary.

21. TODO: @AcrossApplication

@AcrossApplication annotation can be used as an alternative for @EnableAcrossContext and @SpringBootApplication when defining an application. In addition to default context configuration options, it also adds selected Spring Boot autoconfiguration classes and enables dynamic modules.

Using @AcrossApplication is probably the easiest way to setup a simple (Spring Boot based) application backed by an AcrossContext.

Warning
Using @EnableAutoConfiguration and Spring Boot starters directly with Across is currently not supported. Only a selected number of starters have support directly built into Across. See Across and Spring Boot for more background information.

21.1. Embedded servlet container

Using @AcrossApplication adds the necessary Spring Boot auto configuration classes for embedded servlet containers. If you have for example spring-boot-starter-tomcat on the classpath, you can run an Across based application inside an embedded Tomcat using SpringApplication.

Minimal example of a Spring Boot based Across application
@AcrossApplication(modules = { MyModule.NAME, MyOtherModule.NAME })
public class MyApplication
{
	public static void main( String[] args ) {
		SpringApplication.run( MyApplication.class, args );
	}
}

21.2. Dynamic Across modules

Using @AcrossApplication on a class will automatically enable dynamic Across modules on that class. Before bootstrapping the context, the configurator will attempt to find an application specific infrastructure, application or postprocessor module. The name of the expected module is generated based on the name of the importing class. If no module with that name can be resolved, the configurator will see if a child package is present relative to the package of the importing class. Depending on the child package name, a dynamic module will be generated.

Importing class

MyApplication

MyConfiguration

Infrastructure module
package: infrastructure

MyApplicationInfrastructureModule
resources: myApplicationInfrastructure

MyConfigurationInfrastructureModule
resources: myConfigurationInfrastructure

Application module
package: application

MyApplicationModule
resources: my

MyConfigurationApplicationModule
resources: myConfiguration

PostProcessor module
package: postprocessor

MyApplicationPostProcessorModule
resources: myApplicationPostProcessor

MyConfigurationPostProcessorModule
resources: myConfigurationPostProcessor

Example package structure for application modules
com.mypackage
  MyApplication.class
  application.config
    MyApplicationModuleConfiguration.class
  infrastructure.installers
    SchemaInstallerToRunBeforeAnyOtherModule.class

Disabling dynamic modules on an @AcrossApplication can be done through the enableDynamicModules attribute.

Warning
Dynamic modules perform an automatic component scan of all their child packages with the exception of installers and extensions. If you want the default behavior of scanning only the config child package, you must provide an AcrossModule implementation.
Note
If you want to manually add dynamic modules, please see the javadoc for AcrossDynamicModulesConfigurer and AcrossDynamicModulesConfiguration.

22. Multipart support

By default AcrossWebModule will attempt to automatically configure a MultipartFilter to support multipart form data. When correctly configured, this will enable multipart support for all servlets and web requests.

Servlet container support

By default a StandardServletMultipartFilter is being added, that builds on top of the Servlet 3.0 specification. Servlets will still need a MultipartConfigElement set for multipart data to be handled correctly. The latter is done automatically for the DispatcherServlet.

Customizing the multipart configuration

Common settings (eg. max file size) can be customized by providing a MultipartConfigElement bean. This bean will be automatically picked up for the configuration. If none is present, AcrossWebModule will create a default bean.

Apache Commons support

If Apache Commons FileUpload is available on the classpath, the filter will be configured using a CommonsMultipartResolver instead.

Manually creating a MultipartResolver

It is also possible to manually create a MultipartResolver bean. The bean name will determine how multipart resolving is applied:

filterMultipartResolver

Will still enable the MultipartFilter to be registered but will use the configured bean as its internal resolver.

multipartResolver

Will disable the MultipartFilter registration, but will register the resolver on the DispatcherServlet. Multipart support will only be enabled on requests made through the DispatcherServlet.

Warning
Spring Boot will automatically rename any MultipartResolver bean to multipartResolver so it will be picked up by the DispatcherServlet. If you want to use the MultipartFilter but want to customize the MultipartResolver it uses, you should ensure that the resolver bean is injected in the AcrossWebModule and not exposed.
Disabling multipart configuration

Disabling the automatic multipart resolving configuration can be done by setting acrossWebModule.multipart.auto-configure to false.

23. Static resources

AcrossWebModule automatically enables serving of static resources bundled with modules. With the default settings static resources will be configured with versioned urls and client-side caching.

23.1. Conventions

The default resources are served from the /views folder on the classpath. By default AcrossWebModule will serve resources from /views/static. Additional subfolders can be configured with the acrossWebModule.resources.folders property.

By convention (and for best development mode support) module specific resources should be located in a subfolder with the resourcesKey of the module.

Example resources layout for MyModule
src/main/resources
   views/
     static/
       MyModule/
         js/controller.js
         css/my-module.css
         img/logo.png
     th/
       MyModule/
         controller.thtml
         user-profile.thtml

The relative path under which the static resources are available is determined by the acrossWebModule.resources.path property.

23.2. Client-side caching

By default static resources will be cached on the client-side for a period of 1 year. Disabling client-side caching is done with the acrossWebModule.resources.caching.enabled property. If client-side caching is disabled, no cache headers will be sent to the client.

Configuring the caching period is done separately with the acrossWebModule.resources.caching.period property. Setting the value to 0 with caching enabled will sent no-cache headers. This is also the default if development mode is active.

23.3. Resource URL versioning

By default AcrossWebModule enables Spring versioning of static resources. This will generate versioned URLs, create a ResourceUrlProvider bean and add a ResourceUrlEncodingFilter and ResourceUrlProviderExposingInterceptor to the request handlers.

Using versioned resource URLs is transparent:

  • in Thymeleaf all relative links added with @{/relative/path} will be rewritten if necessary

  • in JSP the equivalent is the <spring:url value="/relative/path" var="url"/> tag

Using these will work both with or without resource versioning.

Warning
If versioning is disabled, the ResourceUrlProvider will not be available on the request. Modules using accessing the ResourceUrlProvider directly should built-in support for this.

23.3.1. Fixed version strategy

AcrossWebModule supports automatic configuration of resource versioning using a single fixed version. When enabled this means that resources of the form /across/resources/static/mymodule/mymodule.css will get rewritten to /across/resources/static/VERSION/mymodule/mymodule.css.

The fixed version used is determined as follows:

  1. acrossWebModule.resources.versioning.version property

  2. build.number property

  3. version of the AcrossWebModule

Using the fixed version strategy works well for relative includes in both CSS and JS files, avoiding the need to rewrite URLs inside those files.

23.3.2. Customizing the version strategy

It is possible to keep configuration of the default resources active but only change the version strategy used. This can be done by injecting your own VersionResourceResolver bean named versionResourceResolver the AcrossWebModule context.

Example providing custom version strategy
@ModuleConfiguration(AcrossWebModule.NAME)
public static class CustomVersionResourceResolver
{
	@Bean
	public VersionResourceResolver versionResourceResolver() {
		return new VersionResourceResolver()
				.addVersionStrategy( new FixedVersionStrategy( "1.0" ), "/**/*.css" )
				.addVersionStrategy( new FixedVersionStrategy( "2.0" ), "/**" );
	}
}
Note
Because of limitations of the CssLinkTransformer in combination with a fixed version strategy, AcrossWebModule does not rewrite links inside css files. If you absolutely need this in your application, you will have to disable the automatic resource versioning and configure it yourself.

23.4. WebResourceRegistry

AcrossWebModule allows for programatically registering web resources through the use of a WebResourceRegistry. A new WebResourceRegistry is attached to every request and can be accessed as a request attribute or a handler method parameter.

Resources can be added in a group (eg: css, javascript) and with a specific location (eg: relative, views, external). The groups can be used for retrieval of related resources, the location determines the WebResourceTranslator that will be used. Web resource translating happens before the actual view is rendered and allows for the data to be converted into a more appropriate format for the view rendering. Using the WebResource.VIEWS location for a web resource will have the relative path prefixed with the configured across web views resources prefix.

Resources can be identified with a unique key, ensuring they are added only once.

Example controller registering specific web resources
@Controller
public class MyController
{
	@ModelAttribute
	public void registerWebResources( WebResourceRegistry webResourceRegistry ) {
	    webResourceRegistry.add( WebResource.CSS, "//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css, WebResource.EXTERNAL );
	    webResourceRegistry.add( WebResource.CSS, "/static/mymodule/css/main.css", WebResource.VIEWS );
	}
}
Note
Views can access the WebResourceRegistry under the webResourceRegistry attribute. It is however the view implementation that will determine how resources are handled.

23.4.1. WebResourcePackage

To avoid adding single items, web resources can be bundled into a WebResourcePackage and registered with the WebResourcePackageManager. Adding an entire package can then be done by calling WebResourceRegistry#addPackage with the right package name.

Example creating a WebResourcePackage
@Component
public class BootstrapUiWebResources extends SimpleWebResourcePackage
{
	public static final String VERSION = "3.3.5";
	public static final String NAME = "bootstrap";

	public BootstrapUiWebResources() {
		setDependencies( JQueryWebResources.NAME );     // Install the jquery package first
		setWebResources(
				new WebResource( WebResource.CSS, NAME,
				                 "//maxcdn.bootstrapcdn.com/bootstrap/" + VERSION + "/css/bootstrap.min.css",
				                 WebResource.EXTERNAL ),
				new WebResource( WebResource.JAVASCRIPT_PAGE_END, NAME,
				                 "//maxcdn.bootstrapcdn.com/bootstrap/" + VERSION + "/js/bootstrap.min.js",
				                 WebResource.EXTERNAL )
		);
	}

	@Autowired
	public void registerPackage( WebResourcePackageManager packageManager ) {
	    packageManager.register( NAME, this );
	}
}

24. Development mode support

AcrossWebModule supports development mode retrieval of physical resources. If the conventions for static resources and Thymeleaf templates were followed, these will be fetched automatically from their physical location. This ensures that an application restart is not required for static resources or Thymeleaf rendering.

If development mode is active AcrossWebModule will also send no-cache headers for static resources and will use a generated fixed version if versioning is enabled.

25. TODO: JSP and Thymeleaf integration

If both JSP and Thymeleaf support are enabled, you can easily use both view types at the same time. The AcrossWebModule also provides a tag that can be used to import Thymeleaf templates or fragments in a JSP rendering pipeline. The same model (request attributes) should be available in the Thymeleaf template as in the calling JSP.

<%@ taglib prefix="across" uri="http://across.foreach.com/tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
	<title>JSP including a Thymeleaf template</title>
</head>
<body>
	<across:thymeleaf template="th/mymodule/thymeleaf-from-jsp-include" />
	<div class="child">
		<across:thymeleaf template="th/mymodule/thymeleaf-from-jsp-include :: fragment" />
	</div>
</body>
</html>

mention serving of thymeleaf files

26. Development mode support

AcrossWebModule supports development mode retrieval of physical resources. If the conventions for static resources and Thymeleaf templates were followed, these will be fetched automatically from their physical location. This ensures that an application restart is not required for static resources or Thymeleaf rendering.

If development mode is active AcrossWebModule will also send no-cache headers for static resources and will use a generated fixed version if versioning is enabled.

27. Web templates

AcrossWebModule provides support for simple template processing. A controller method can be linked to a specific template which will determine some pre- and post-processing that might be done. The base infrastructure is provided by WebTemplateProcessor, WebTemplateInterceptor and WebTemplateRegistry.

27.1. Creating a template

The easiest way to create a new template is to create a bean extending LayoutTemplateProcessorAdapterBean. Your implementation should get a unique name and a path to the template view file. This basic template implementation has adapter methods for registering of web resources and generating one or more menu structures.

Example layout template implementation
@Component
public class MyTemplate extends LayoutTemplateProcessorAdapterBean {

    public MyTemplate() {
        super( "MyTemplate", "th/mysite/layout" );
    }

    @Override
    protected void registerWebResources( WebResourceRegistry registry ) {
        registry.add( WebResource.CSS, "/static/mysite/css/main.css", WebResource.VIEWS );
    }

    @Override
    protected void buildMenus( MenuFactory menuFactory ) {
        menuFactory.buildMenu( "topMenu" );
    }
}

When the template is applied it will take the original view name and put it as a model attribute with the key childPage. The actual template view can then dispatch to the original view (or parts of it) when building the final layout.

Example Thymeleaf template that renders the content fragment of the original view
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>My Site</title>

    <script th:each="javascript : ${webResourceRegistry.getResources('javascript')}" th:src="@{${javascript.data}}"
            th:if="${javascript.location != 'inline'}"></script>

    <link rel="stylesheet" type="text/css" th:each="css : ${webResourceRegistry.getResources('css')}"
          th:href="@{${css.data}}"/>
</head>
<body>
    <div th:replace="${childPage} :: content">
        Insert original view
    </div>

    <script th:each="javascript : ${webResourceRegistry.getResources('javascript-page-end')}" th:src="@{${javascript.data}}"
            th:if="${javascript.location != 'inline'}"></script>
</body>
</html>

27.2. Linking a template to a controller

Any controller method can specify the template to use by setting the @Template annotation with the unique template name. The @Template annotation can be set on the @Controller itself (in which case it will apply to all mapping methods) or on mapping method directly. The latter will always take precedence.

Note
You can always clear a template from being applied by setting a @ClearTemplate annotation on a method. Special Spring MVC return options (like methods annotated with @ResponseBody) will automatically suppress the template.
Warning
Methods in a @Controller do not take any @Template annotations of their @ControllerAdvice beans into account and vice versa.

27.3. Linking a template to an exception handler

Just like @Template can be used on any @RequestMapping method, it can also be used on a @ExceptionHandler method. Since an exception handler uses its own template, a template might be executed twice if an exception occurs. Initially a template might have been executed for the controller method. In case of an exception, that template will be cleared and a new WebResourceRegistry will be created for the @ExceptionHandler along with the optional template.

27.4. Registering the default template

If no explicit template is specified, the default template will be used it there is one. The default template must be set on the WebTemplateRegistry. set on the WebTemplateRegistry.

Example template registering itself as default
@Component
public class MyTemplate extends LayoutTemplateProcessorAdapterBean {
    ...

    @Autowired
    public void registerAsDefaultTemplate( WebTemplateRegistry webTemplateRegistry ) {
        webTemplateRegistry.setDefaultTemplateName( getName() );
    }

    ...
}

27.5. Partial rendering

Across web templates also activate support for partial rendering: rendering only a specific fragment of a (Thymeleaf) template. This can be done by simply specifying the fragment to render as a _partial request parameter.

Examples:

  • /mypath?id=1: would render the full page output

  • /mypath?id=1&_partial=content: would only render the fragment named content from the Thymeleaf template

Warning
If a partial fragment is specified, the entire WebTemplateProcessor will be skipped, only the target view will be rendered. All other controller code will still be executed.
Note
The fragment specification can also be the name of a single ViewElement that needs to be rendered instead. See the BootstrapUiModule documentation for more information on the ViewElement infrastructure.

AcrossWebModule provides some infrastructure to build a tree-like menu. The output class is a Menu instance that is created by the MenuFactory service. When building a menu, a BuildMenuEvent is published with the menu instance, the name of the menu will be the name of the event. The BuildMenuEvent provides access to a PathBasedMenuBuilder that allows asynchronous registering of menu items with a unique hierarchical path. After the event handling the actual menu will be built and path structure will be used for the tree hierarchy.

29. Registering servlets and filters

It is possible to register Servlet and Filter instances from within an Across module by providing ServletContextInitializer beans. During bootstrap, AcrossWebModule will detect these beans and execute them in order.

For the ServletContext to be customized, the AcrossContext must be bootstrapped during the ServletContext initialization. This is the default when you use @AcrossApplication with an embedded container (Spring Boot application). When deploying to a separate container, you can use AbstractAcrossServletInitializer to achieve the same.

Conditionals

@ConditionalOnConfigurableServletContext can be used to verify that the context is being bootstrapped in a web configuration where the ServletContext allows customization. Likewise @ConditionalOnNotConfigurableServletContext can be used to check the opposite is true.

Example registering the resourceUrlEncodingFilter after all other filters
@Bean
@ConditionalOnProperty(prefix = "acrossWebModule.resources.versioning", value = "enabled", matchIfMissing = true)
@ConditionalOnConfigurableServletContext
public FilterRegistrationBean resourceUrlEncodingFilterRegistration() {
    FilterRegistrationBean registration = new FilterRegistrationBean();
    registration.setName( RESOURCE_URL_ENCODING_FILTER );
    registration.setFilter( new ResourceUrlEncodingFilter() );
    registration.setAsyncSupported( true );
    registration.setMatchAfter( true );
    registration.setUrlPatterns( Collections.singletonList( "/*" ) );
    registration.setDispatcherTypes( DispatcherType.REQUEST, DispatcherType.ERROR, DispatcherType.ASYNC );
    registration.setOrder( Ordered.LOWEST_PRECEDENCE );

    return registration;
}
Warning
ServletContextInitializer beans can be ordered but the order between initializers defined inside the Across context and outside the context (on application configuration level) is non deterministic. To enforce overall ordering, it might be easiest to add those initializers to a module using @ModuleConfiguration.

30. Default HTTP encoding

AcrossWebModule registers a default CharacterEncodingFilter that forces the request and response encoding to use UTF-8. This is done through the HttpEncodingAutoConfiguration from Spring Boot and can be controlled with the following properties:

  • spring.http.encoding.enabled

  • spring.http.encoding.charset

  • spring.http.encoding.force

If a CharacterEncodingFilter bean is found in the parent ApplicationContext but default HTTP encoding is enabled, AcrossWebModule will take care of registering that filter bean.

31. Generating URLs

31.1. WebAppPathResolver and path prefixing

AcrossWebModule introduces the PrefixingRequestMappingHandlerMapping. This allows @RequestMapping methods to be prefixed at runtime, based on configuration.

Some standard Across modules like AdminWebModule and DebugWebModule make use of this functionality.

Because the actual relative path of a controller is then not known during development, you can define a relative path with a prefix. If you provide your path to the WebAppPathResolver bean, the prefix will then be replaced at runtime by the final path.

The AcrossWebModule Thymeleaf dialect integrates with the WebAppPathResolver so prefix based paths are supported directly in templates.

Note
If you want to ensure your relative path is never prefixed, start it with an exclamation mark (!).

31.1.1. Default prefixes

AcrossWebModule registers default prefixers for static resources:

Name Target

@resource

Static resources in the root resources folder.

@static

Static resources in the static subfolder. This is a short-hand for @resource:/static/<path>

31.1.2. Path resolving examples

The examples assume that res is the configured acrossWebModule.resources.path:

Input Output

/my/path

/my/path

@resource:/my/path

/res/my/path

@static:/my/path?id=10#home

/res/static/my/path?id=10#home

redirect:@resource/my/path

redirect:/res/my.path

!@resource/my/path

@resource/my/path

31.1.3. Registering your own prefixing handler mapping and resolver

See PrefixingHandlerMappingConfiguration and PrefixingPathContext.

31.2. WebAppLinkBuilder

The WebAppPathResolver will only replace the relative path prefix, but to generate a valid application link you would still need to prepend the possible context path as wel as encode the URL. If you want to generate a complete link from code, you can use the WebAppLinkBuilder bean.

Warning
You should only use the the WebAppLinkBuilder if your links will not be processed additionally. To generate redirect paths, or links for a spring:url or Thymeleaf link builder @{}, use the WebAppPathResolver.

Otherwise you could end up with links having the context path added twice.

32. Thymeleaf dialect

AcrossWebModule also registers a Thymeleaf dialect. The Thymeleaf dialect uses the prefix across and offers a number of utility elements, attributes and expression objects.

32.1. Element processors

The AcrossWebDialect adds the following elements that can be used in HTML templates:

Name Description Attributes

across:view

Takes a ViewElement as element attribute and will be replaced by the rendering of that ViewElement.

element : ViewElement instance

32.2. Attribute processors

The AcrossWebDialect adds the following attributes that can be used in HTML templates:

Name Description Examples

across:resource

Takes a relative path to a static resource. Will generate a valid url path to that static resource and will write the value as a new attribute. The name of the attribute that will be written depends on the element the attribute is used on.

All HTML5 elements supporting src or href attributes are supported, along with image and use elements

<a across:resource="/img"/>
output: <a href="/res/img">

<script across:resource="/js/my.js"/>
output: <script src="/res/js/my.js"/>

<image across:resource="/img"/>
output: <image xlink:href="/res/img"/>

across:static

Takes a relative path to a resource in the static subfolder. Using across:static="/img" would be the same as using across:resource="/static/img". Supports the same elements as across:resource.

<a across:static="/img"/>
output: <a href="/res/static/img">

across:resource-attr-name

Same as across:resource except the resulting attribute name will be attr-name.
Any double dash (--) will be replaced by a colon (:).

<a across:resource-data-url="/img"/>
output: <a data-url="/res/img">

across:static-attr-name

Same as across:static except the resulting attribute name will be attr-name.
Any double dash (--) will be replaced by a colon (:).

<a across:static-data-url="/img"/>
output: <a data-url="/res/static/img">

32.3. Expression objects

The AcrossWebDialect makes the following expression objects available for script execution:

Name Value Description

#webapp

WebAppPathResolver

Used for generating prefixed links based on a relative path. For example to static resources.

These links can still be processed by the Thymeleaf link builder (@{..}). Usually not necessary to use the #webapp however, see the URL support section.

#htmlIdStore

HtmlIdStore

Holds the generated HTML id attribute for a ViewElement.

32.4. URL support

Apart from the availability of #webapp, AcrossWebModule activates transparent support for the WebAppPathResolver on Thymeleaf URL generation.

Example: <img th:src="@{@static:/img}"> would produce <img src="/res/static/img">

IV. Across Test

Across Test is a utility library that provides helpers for integration testing of Across modules in both a standard and web context. Across Test builds on top of the Spring testing library.

Artifact

<dependencies>
    <dependency>
        <groupId>com.foreach.across</groupId>
        <artifactId>across-test</artifactId>
        <version>2.0.1-SNAPSHOT</version>
    </dependency>
</dependencies>

Thymeleaf integration: AcrossWebDialect

33. Base test classes

33.1. Module conventions test

Across Test comes with a base unit test class that can be used to verify an AcrossModule implementation adheres to the conventions. All you have to do is extend from AbstractAcrossModuleConventionsTest and implement the createModule() method.

Basic implementation of a module conventions test
public class TestAcrossWebModuleConventions extends AbstractAcrossModuleConventionsTest
{
	@Override
	protected AcrossModule createModule() {
		return new AcrossWebModule();
	}
}

34. Test context builders

Across Test comes with a set of helpers for integration testing an Across module with all its dependencies. Main access point is the AcrossTestBuilders class that will provide you with either an AcrossTestContextBuilder or AcrossTestWebContextBuilder. The latter should be used if you want to simulate a web container environment.

Both builder implementations provide a fluent api for easily configuring an AcrossContext and setting application properties. See the javadoc for all different configuration options. Upon building the AcrossContext will be bootstrapped and an instance of AcrossTestContext will be returned. AcrossTestContext (and the extending AcrossTestWebContext) wraps around the bootstrapped AcrossContext and provides easy querying of the bootstrapped context. In addition AcrossTestContext implements Closeable that shuts down the context, easy for using with try-with-resources.

Simple text context builder example
@Test
public void verifySomeBeanIsExposed() {
    try (
            AcrossTestContext ctx = AcrossTestBuilders.standard()
                    .modules( SomeModule.NAME )
                    .build()
    ) {
        // SomeBean should be available in the root context
        assertNotNull( ctx.getBeanOfType( SomeBean.class ) );
    }
}

Using the builders will by default load the test datasources and will execute a reset of the databases before bootstrapping. Both options can be configured separately on the builder.

Warning
You should obviously make sure you are not dropping the databases when connecting to an existing database.
Example connecting to existing datasource
@Test
public void doSomethingOnExisting() {
    try (
            AcrossTestContext ignore = AcrossTestBuilders.standard()
                    .useTestDataSource( false )
                    .dropFirst( false )
                    .dataSource( ds )
                    .build()
    ) {
        ...
    }
}

In a web scenario the AcrossTestWebContextBuilder should be used. This will ensure a MockAcrossServletContext is being used and return an AcrossTestWebContext. The AcrossTestWebContext provides access to the MockAcrossServletContext and a MockMvc bean that is instantiated with all filters registered by the modules.

Example web context builder with mock mvc call
@Test
public void filterNotRegisteredButResourceStillWorks() {
    try (
            AcrossTestWebContext ctx = AcrossTestBuilders.web()
                    .property( "acrossWebModule.resources.versioning.enabled", "false" )
                    .modules( AcrossWebModule.NAME )
                    .build()
    ) {
        // URL encoding filter should not be registered
        MockAcrossServletContext servletContext = ctx.getServletContext();
        assertNull( servletContext.getFilterRegistration( ResourceUrlEncodingFilterConfiguration.FILTER_NAME ) );

        // Static resource should still be returned
        MockMvc mvc = ctx.mockMvc();
        mvc.perform( get( "/across/resources/static/testResources/test.txt" ) )
            .andExpect( status().isOk() )
    }
}

35. Annotations

The Across Test library also provides a set of Across-specific annotations that extend the default Spring Testing annotations (eg @ContextConfiguration) to support AcrossContext setups.

35.1. @AcrossTestConfiguration

Class-level annotation to be used as an alternative to @EnableAcrossContext. Like @EnableAcrossContext this annotation will configure and bootstrap an AcrossContext but also add support for:

When using either @EnableAcrossContext or @AcrossTestConfiguration, all beans exposed can be wired directly in the unit test class.

Example using @AcrossTestConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
@ContextConfiguration
public class TestSample
{
	@Autowired
	private MyExposedBean myExposedBean;

	@Test
	public void doSomethingWithMyExposedBean() {
		...
	}

	@AcrossTestConfiguration(modules = { MyModule.NAME })
	protected static class Config
	{
	}
}

35.2. @AcrossWebAppConfiguration

Class-level annotation that sets up a @WebAppConfiguration test that uses a MockAcrossServletContext for initialization. @AcrossWebAppConfiguration contains a @ContextConfiguration annotation, unless additional annotations are specified, it will load static innter @Configuration classes of the annotated test class.

When using this annotation on a test class the MockAcrossServletContext instance can be autowired for introspection. In combination with an inner static @AcrossTestConfiguration class, this will support a MockMvc instance initialized with all dynamically registered filters.

Example combining @AcrossWebAppConfiguration and @AcrossTestConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
@AcrossWebAppConfiguration
@TestPropertySource( properties = "acrossWebModule.resources.versioning.enabled=false" )
public class TestSample
{
    @Autowired
    private MockAcrossServletContext servletContext;

	@Autowired
	private MockMvc mvc;

	@Test
	public void resourceUrlEncodingFilterShouldNotBeRegistered() {
        assertNull( servletContext.getFilterRegistration( ResourceUrlEncodingFilterConfiguration.FILTER_NAME ) );
	}

	@Test
	public void staticResourceShouldBeReturned() {
	    mvc.perform( get( "/across/resources/static/testResources/test.txt" ) )
           .andExpect( status().isOk() )
	}

	@AcrossTestConfiguration(modules = { AcrossWebModule.NAME })
	protected static class Config
	{
	}
}

36. Spring Boot Application testing

It is possible to test your @AcrossApplication with Spring Boot testing support. Using the @SpringBootTest in your tests is necessary for default application.properties loading and YAML support. Refer to the Spring Boot reference documentation for more information on Spring Boot testing facilities.

36.1. @WebIntegrationTest

A full-stack web integration test (eg. with an embedded Tomcat) does not require any special configuration.

Example integration test with an embedded servlet container on a random port
@RunWith(SpringJUnit4ClassRunner.class)
@WebIntegrationTest(randomPort = true)
@SpringBootTest(classes = MyApplication.class)
public class TestSpringBootWebIntegration
{
	private final TestRestTemplate restTemplate = new TestRestTemplate();

	@Value("${local.server.port}")
	private int port;

	@Test
	public void controllersShouldSayHello() {
		assertEquals( "application says hello", get( "/application" ) );
		assertEquals( "infrastructure says hello", get( "/infrastructure" ) );
	}

	private String get( String relativePath ) {
		return restTemplate.getForEntity( url( relativePath ), String.class ).getBody();
	}

	private String url( String relativePath ) {
		return "http://localhost:" + port + relativePath;
	}
}

36.2. Integration test with MockMvc support

It is also possible to start your entire @AcrossApplication without an embedded servlet container. However if you want to have support for MockMvc instantiated with dynamically registered filters, you must manually configure the application loader to use a MockAcrossServletContext. This is done by adding the required configuration to @SpringBootTest and initializer classes to the @ContextConfiguration. See the appendix section for an overview of all configuration classes available.

Example Spring Boot integration test with MockMvc
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@SpringBootTest( classes = { MyApplication.class, MockMvcConfiguration.class } )
@ContextConfiguration( initializers = MockAcrossServletContextInitializer.class )
public class TestSpringBootMockMvc
{
	@Autowired
	private MockMvc mockMvc;

	@Test
	public void controllersShouldSayHello() throws Exception {
		assertContent( "application says hello", get( "/application" ) );
		assertContent( "infrastructure says hello", get( "/infrastructure" ) );
	}

	private void assertContent( String expected, RequestBuilder requestBuilder ) throws Exception {
		mockMvc.perform( requestBuilder )
		       .andExpect( status().isOk() )
		       .andExpect( content().string( is( expected ) ) );
	}
}
Note
Using @AcrossWebAppConfiguration in combination with @SpringBootTest is not possible, as the context loaders they use conflict with each other.

37. Mock MVC integration

By default using either the test builders or @AcrossTestConfiguration will provide you with a singleton MockMvc instance that is configured with the bootstrapped AcrossContext. You can also manually add this singleton configuration by importing the MockMvcConfiguration in your test application context, see the example in the Spring Boot section.

Across Test also provides a AcrossMockMvcBuilders class that has factory methods for creating an ad hoc MockMvcBuilder based on a bootstrapped AcrossContext. Use this implementation if you want to customize the MockMvc configuration, for example for Spring REST documentation generation.

Ad hoc instantiation of a MockMvc instance
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
@AcrossWebAppConfiguration
public class TestSample
{
    @Autowired
    private AcrossContextInfo contextInfo;

    private MockMvc mvc;

    @Before
    public void setUp() {
        this.mvc = AcrossMockMvcBuilders.acrossContextSetup( contextInfo ).build();
    }

    @Test
    public void staticResourceShouldBeReturned() {
        mvc.perform( get( "/across/resources/static/testResources/test.txt" ) )
           .andExpect( status().isOk() )
    }

    @EnableAcrossContext(modules = { AcrossWebModule.NAME })
    protected static class Config
    {
    }
}

38. Testing dynamic modules

Testing dynamic modules outside of a full @AcrossApplication test can be done by adding one or more AcrossDynamicModulesConfigurer instances to the AcrossContext configuration. Every configurer instance is responsible for building a set of dynamic modules for a particular base package. An existing @AcrossApplication class can easily be reused as base for the configurer.

Example adding dynamic modules to a builder
@Test
public void dynamicModulesRegistration() {
    try (
            AcrossTestContext ctx = standard()
                    .configurer( new AcrossDynamicModulesConfigurer( DummyApplication.class ) )
                    .build()
    ) {
        assertTrue( ctx.contextInfo().hasModule( "DummyApplicationModule" ) );
    }
}
Example adding dynamic modules to an annotations based test
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
@ContextConfiguration
public class TestDynamicModulesConfiguration
{
	@Autowired
	private AcrossContextInfo contextInfo;

	@Test
	public void sampleModulesShouldBeAdded() {
	    assertTrue( contextInfo.hasModule( "DummyApplicationModule" ) );
   	}

	@Configuration
	@EnableAcrossContext
	protected static class SampleConfiguration
	{
		@Bean
		public AcrossDynamicModulesConfigurer sampleDynamicModules() {
			return new AcrossDynamicModulesConfigurer( DummyApplication.class );
		}
	}
}

39. MockAcrossServletContext

The MockAcrossServletContext is an extension of the MockServletContext from Spring Test. It adds an implementation that keeps track of all registered servlets, filters and listeners.

Unlike the MockServletContext this implementation does not throw an UnsupportedOperationException on registration operations. For common operations like servlet and filter registration this implementation will behave as an actual ServletContext from a web container. The actual registrations that occurred can be retrieved - in order - from the MockAcrossServletContext.

MockAcrossServletContext can also mimic initialization of the ServletContext. When initialize() is called, the init() methods of the servlet/filter instances will be called with the registration parameters. Note that initialize() can be called only once, and afterwards all registration operations will throw an IllegalStateException.

Using a MockAcrossServletContext can be used for unit testing AcrossWebDynamicServletConfigurer implementations or for integration tests needing web functionality. MockAcrossServletContext is used automatically behind the scenes when using the test builders or annotations. In an integration test scenario, the implementation can either be wired as a bean (when using the annotations) or retrieved from the resulting AcrossTestWebContext.

Please refer to the javadoc for MockAcrossServletContext for more information.

40. Test datasources

When using the Across Test annotations or builders, default support for test datasources is activated. If no specific datasource is configured on the AcrossContext a default test datasource will be detected.

Test datasource detection

Test datasources can be configured in a specific property file, located at ${user.home}/dev-configs/across-test.properties. That file is automatically loaded and can contain multiple datasource sections, where every datasource has a unique NAME and its properties are prefixed with acrossTest.datasource.NAME. Required properties for a valid datasource are:

  • acrossTest.datasource.NAME.driver

  • acrossTest.datasource.NAME.url

  • acrossTest.datasource.NAME.username

  • acrossTest.datasource.NAME.password

Example across-test.properties file
acrossTest.datasource.mysql.driver=com.mysql.jdbc.Driver
acrossTest.datasource.mysql.url=jdbc:mysql://127.0.0.1:3306/testdb
acrossTest.datasource.mysql.username=testdb_user
acrossTest.datasource.mysql.password=testdb_pwd

When configuring the context, the environment property acrossTest.datasource will be checked to determine the NAME of the actual datasource that shoul be used. If that property does not exist or holds the value auto, an embedded HSQLDB is automatically added instead. This ensures that installers can always run against an actual datasource.

If you want to change the fallback default datasource, you can do so by setting the acossTest.datasource.default property value.

Note
A common strategy is to run the integration tests multiple times with a different acrossTest.datasource property. The standard-module-bom automatically configures your maven build to run all unit test starting with IT* in the integration-test phase. This is an efficient way to test for example installer compatibility for different database types.

If you want to manually add support for test datasources, you should add a TestDataSourceConfigurer for configuring the AcrossContext.

Database reset

When using test datasources it is usually wanted behaviour to drop the database content before bootstrapping the AcrossContext. This is done automatically when you are using the Across Test annotations or builders. If you want to add this manually, you should add a ResetDatabaseConfigurer to the context configuration.

V. Appendices

41. Advanced topics

41.1. Module resolving

By default modules are resolved using a ClassPathScanningModuleDependencyResolver. If a module can’t be resolved the reason is usually one of the following:

  • the package containing the module has not been added for module scanning

  • there is no public static final String NAME field containing the unique module name

  • there is no public constructor without parameters

You can configure more verbose logging by setting the logger level for com.foreach.across.core.context.ClassPathScanningCandidateModuleProvider to TRACE. This will output all candidate modules that have been considered during scanning.

42. Conventions

42.1. TODO: AcrossModule

42.2. TODO: @AcrossApplication

43. TODO: Published events

The following is a listing of all events published by Across core or AcrossWebModule.

44. Test configuration classes

Across Test library has a number of @Configuration and AcrossContextConfigurer classes that can come in useful when setting up integration tests. These configurations can be found in the com.foreach.across.test.support.config package.

Making use of these directly is only required when setting up integration tests without the Across test annotations or builders.

Table 2. Test configuration classes

TestDataSourceConfigurer

AcrossContextConfigurer that will configure the test datasource on the AcrossContext if no other datasource has been set.

ResetDatabaseConfigurer

AcrossContextConfigurer that resets the datasources attached to the AcrossContext before it bootstraps. This will drop all tables, stored procedures etc in the database.

MockMvcConfiguration

Configures a MockMvc bean that will be created from a bootstrapped AcrossContext. If a MockAcrossServletContext is available, all registered filters will be configured on the MockMvc instance.

MockAcrossServletContextInitializer

ApplicationContextInitializer for use with @ContextConfiguration that ensures that MockAcrossServletContext is used instead of the default MockServletContext. Note that this initializer also requires that @WebAppConfiguration has been used on the test class.