Documentation Home

Framework Controllers

Overview

We have introduced the concept of framework controllers that are default implementations of endpoints. This is a replacement for our current pattern where "default controllers" are included in the framework but aren't annotated as such, requiring that the client application extent them and add the @RequestMapping annotations.

The idea is that you can have our default endpoints out of the box without any code requirements in the client application.

The thing that makes framework controllers unique is that if you specify your own custom endpoint that has the same URL mapping as a framework endpoint, you will not get an ambiguous mapping exception.

Enabling Framework Controllers

By default, framework controllers are not enabled and therefore will not be added to the handler mappings. In order to enable the framework controllers you must use one of the following annotations on an @Configuration class. If you are running separate root and servlet application contexts, then you need to add this to the servlet configuration. If you are using the Broadleaf auto configuration annotations, place this alongside @EnableBroadleafAdminServletAutoConfiguration and/or @EnableBroadleafSiteServletAutoConfiguration:

  • @EnableFrameworkControllers

This will enable any class annotated with @FrameworkController, which are all of the default MVC controllers. This does not include RESTful controllers.

  • @EnableFrameworkRestControllers

This will enable any class annotated with @FrameworkRestControllers, which are all of the default RESTful controllers. This does not include MVC controllers.

  • @EnableAllFrameworkControllers

This is the equivalent of adding both @EnableFrameworkControllers and @EnableFrameworkRestControllers and is just a convenience annotation to enable all of Broadleaf's default endpoints. Note that you cannot pass excludeFilters to this annotation, if you want to disable specific framework controllers then you need to specify @EnableFrameworkControllers and @EnableFrameworkRestControllers independently and use the excludeFilters there. This annotation simply means "I want all of the default framework controllers" and if that is not the case, then this particular annotation is not for you.

Excluding Controllers

In the event you want to use an enable framework controllers annotation, but want to exclude particular framework controllers, you can leverage the excludeFilters property of the @EnableFrameworkControllers and @EnableFrameworkRestControllers annotations. For example:

@EnableFrameworkControllers(excludeFilters = {
        @Filter(value = DefaultCustomerController.class, type = FilterType.ASSIGNABLE_TYPE),
        @Filter(value = DefaultOrderController.class, type = FilterType.ASSIGNABLE_TYPE)
})

Including a Single Controller

If you only want a small number of framework controllers enabled, it would be easier to declare the ones you want as beans instead of listing a large number of controllers using excludeFilters.

For example, you can activate a single framework controller in an @Configuration class like so:

@Bean
public DefaultCartController defaultCartController() {
    return new DefaultCartController();
}

Important Caveat

These annotations all leverage annotation composition of @ComponentScan and as such cannot be specified alongside another @ComponentScan or annotation that composes @ComponentScan such as @SpringBootApplication or the other annotations that enable framework controllers.

You can avoid having to create a whole new @Configuration class for each annotation by created a nested static class as follows:

@EnableFrameworkControllers
public static class EnableBroadleafControllers {}
@EnableFrameworkRestControllers
public static class EnableBroadleafRestControllers {}

Framework Controllers

Default framework controllers are defined by their @FrameworkController or @FrameworkRestController annotation. There are used exactly like their counterparts @Controller and @RestController. It is important to use the proper annotation for the situation, @FrameworkController for MVC controllers and @FrameworkRestController for RESTful controllers, since the above annotations that enable them leverage this distinction. For example, you would not want to create a RESTful controller by annotating it with @FrameworkController and @ResponseBody. @FrameworkRestController already has a composition with @ResponseBody so all mappings defined within will infer @ResponseBody.

Framework Mappings

In order to avoid conflict with the way Spring reads @RequestMapping annotations, we have also created an @FrameworkMapping annotation. Without it, framework controllers with a parent mapping would get included as regular controllers and classes that extend default framework controllers but don't want their mappings would get them anyway.

Otherwise, @FrameworkMapping behaves exactly like @RequestMapping, including all of the same properties. You can put @FrameworkMapping at the class level to declare the parent mapping and at the method level to declare the specific mapping.

Use Cases

Using Enable Framework Controller Annotations

When you add an enable framework controller annotation, you are declaring that for the most part, you want and are satisfied with the default mappings/API. That being said, there is still room for some customization on top of the defaults.

Overriding Default Functionality

To override the functionality for a particular mapping, you can simply create your own regular controller class with the same mapping as what is in the framework controller and define your functionality there. This class could be stand-alone or could extend the default framework controller if you would like to call super at some point.

Since regular controllers take precedence over framework controller mappings, your mapping will get used when a request comes in.

For example, given the framework controller:

@FrameworkRestController
@FrameworkMapping("/cart")
public class DefaultCartController {
    @FrameworkMapping(path = "/get", method = RequestMethod.GET)
    public Cart getActiveCart() {
        return cartService.getActiveCart();
    }
}

you can hijack the /cart/get mapping with:

@RestController
@RequestMapping("/cart")
public class MyCartController {
    @RequestMapping(path = "/get", method = RequestMethod.GET)
    public MyCart getActiveCart() {
        return myCartService.getActiveCart();
    }
}

or if you want to call super, you could extend the default framework controller as well like so:

@RestController
@RequestMapping("/cart")
public class MyCartController extends DefaultCartController {
    @RequestMapping(path = "/get", method = RequestMethod.GET)
    public MyCart getActiveCart() {
        Cart cart = super.getActiveCart();
        return doCustomThingsToCart(cart);
    }
}

Changing a Mapping

If you want to alter the URL for some mapping, you can do so by defining your own mapping and calling super.

Note that the original framework mapping will still be register and able to be accessed. In order to remove the default framework mapping, see the section Removing a Mapping.

For example, given the framework controller:

@FrameworkRestController
@FrameworkMapping("/cart")
public class DefaultCartController {
    @FrameworkMapping(path = "/get", method = RequestMethod.GET)
    public Cart getActiveCart() {
        return cartService.getActiveCart();
    }
}

you can change the mapping by extending the framework controller and calling super with a new mapping:

@RestController
@RequestMapping("/cart")
public class MyCartController extends DefaultCartController {
    @RequestMapping(path = "/retrieve", method = RequestMethod.GET)
    public Cart getActiveCart() {
        return super.getActiveCart();
    }
}

now we've created a new mapping /cart/retrieve, but note that /cart/get will still be registered.

Changing a Mapping and Functionality

This is achieved by simply applying both patterns above.

Removing a Mapping

If you want to remove all of the mappings for a given framework controller, see the section Excluding Controllers.

If you want to remove (disable) particular a @FrameworkMapping then you'll need to create a @RequestMapping method with the same URL that returns a 404 error.

For example, to disable /cart/get:

@RestController
@RequestMapping("/cart")
public class MyCartController extends DefaultCartController {
    @RequestMapping(path = "/get", method = RequestMethod.GET)
    public ResponseEntity getActiveCart() {
        return new ResponseEntity(HttpStatus.NOT_FOUND);
    }
}

Please note that if you find yourself using this pattern a lot, then maybe you should consider excluding that framework controller (see Excluding Controllers) or not using enable framework controllers at all (see Not Using Enable Framework Controller Annotations). The primary purpose of these default framework controllers is to provide an out of the box experience that can be tweaked, if your application has heavily customized mappings/API, then it would probably be best to simply write your own mappings/API, extending the default framework controllers where desired.

Not Using Enable Framework Controller Annotations

This is the same pattern as before framework controllers were introduced. You basically do not specify any enable framework controller annotations and provide your own @RequestMappings for the application. When no enable framework controller annotations are specified, the @FrameworkController, @FrameworkRestController, and @FrameworkMapping annotations are completely ignored and those classes aren't even registered as beans.

Use Existing Functionality

You can still leverage the functionality of the default framework controllers by extending them and calling super.

For example:

@RestController
@RequestMapping("/cart")
public class MyCartController extends DefaultCartController {
    @RequestMapping(path = "/get", method = RequestMethod.GET)
    public Cart getActiveCart() {
        return super.getActiveCart();
    }
}

Of course you can add some additional code to customize this further, or not even call super at all.

Migration from 5.1 to 5.2

Aside from migrations required due to structure or functional changes to the default Broadleaf controllers, migration is very simple. If you have customized the default controllers that came from DemoSite that extend the default Broadleaf controllers, then you can simply do nothing (don't add enable framework controller annotations) and functionality will continue as normal. If you have not touched the default controllers from DemoSite and want to remove them, you can safely do so as long as you add @EnableAllFrameworkControllers to your configuration.

Handler Mapping Classes

These are the relevant handler mapping implementations listed in order of precedence for both admin and site applications.

Admin

AdminRequestMappingHandlerMapping

This is an admin customized extension of Spring's default RequestMappingHandlerMapping. It serves the same purpose of being the default place @Controller/@RequestMapping annotations are registered except checks that the class doesn't have AdminBasicEntityController as an ancestor.

FrameworkControllerHandlerMapping

This is the handler mapping that registers controllers annotated with @FrameworkController and @FrameworkRestController

AdminControllerHandlerMapping

This is the handler mapping where all @Controller/@RequestMapping annotations are registered where the class has AdminBasicEntityController as an ancestor. This is separated from the default handler mapping because admin controllers have some "catch-all" request mappings that would prevent any mappings registered in FrameworkControllerHandlerMapping from getting matched if they were located in the default handler mapping. Also, all admin controllers must be in the same handler mapping because they rely on "best-match" logic and partial matching.

Site

RequestMappingHandlerMapping

This is the default handler mapping implementation that comes from spring. Controllers are registered in this handler mapping when the class includes a @Controller or @RequestMapping annotation.

FrameworkControllerHandlerMapping

This is the handler mapping that registers controllers annotated with @FrameworkController and @FrameworkRestController