Modularity
Pippo was designed since the first version with the modularity in mind. Many aspects (extension points) of this framework can be changed:
WebServer
(using Pippo#setServer() or auto discovery mechanism)TemplateEngine
(using Application#setTemplateEngine() or auto discovery mechanism)Router
(using Application.setRouter())ErrorHandler
(using Application#setErrorHandler())ContentTypeEngine
(using Application#registerContentTypeEngine)
Also you can set some parameters related to file upload process (Application#setUploadLocation() and Application#setMaximumUploadSize()). You can modify some settings for an embedded WebServer using WebServerSettings.
We chose the ServiceLoader mechanism from Java as built in modules system in Pippo because is a standard and easy to use. You can create a modular application using ServiceLocator class (trivial wrapper over Service Loader concept).
To improve the modularity mechanism, we added the concept of Initializer.
When Pippo starts up an application, it scans the classpath roots, looking for Initializer implementations.
It instantiates and execute the initializers defined via Service Loader.
Of course you can create META-INF/services
manually but the easy mode is to use the @MetaInfServices
annotation.
The @MetaInfServices
annotation generates META-INF/services/* file from annotations that you placed on your source code, thereby eliminating the need for you to do it by yourself.
To demonstrate the initializer concept I added a dump FreemarkerInitializer in pippo-freemarker module. The initializer can be implemented like this:
@MetaInfServices
public class FreemarkerInitializer implements Initializer {
@Override
public void init(Application application) {
application.registerTemplateEngine(FreemarkerTemplateEngine.class);
}
@Override
public void destroy(Application application) {
// do nothing
}
}
One scenario when I can use the Initializer concept is when I split my application in several modules and each module
wants to add some routes to the application.
For example my application comes with two modules (two jars): contacts and users.
I can have ContactInitializer.java with this content:
@MetaInfServices
public class ContactInitializer implements Initializer {
@Override
public void init(Application application) {
// show contacts page
application.GET("/contacts", routeContext -> routeContext.render("contacts"));
// show contact page for the contact with id specified as path parameter
application.GET("/contact/{id}", routeContext -> {
int id = routeContext.getParameter("id").toInt(0);
Map<String, Object> model = new HashMap<>();
model.put("id", id);
routeContext.render("contact", model);
});
}
@Override
public void destroy(Application application) {
// do nothing
}
}
I can have UserInitializer.java with this content:
@MetaInfServices
public class UserInitializer implements Initializer {
@Override
public void init(Application application) {
// show users page
application.GET("/users", routeContext -> routeContext.render("users"));
// show user page for the user with id specified as path parameter
application.GET("/user/{id}", routeContext -> {
int id = routeContext.getParameter("id").toInt(0);
Map<String, Object> model = new HashMap<>();
model.put("id", id);
routeContext.render("user", model);
});
}
@Override
public void destroy(Application application) {
// do nothing
}
}
The above example can be improved as readability if we use the RouteGroup
concept described in Routes section
public class ContactRoutes extends RouteGroup {
public UserRoutes() {
super("/contact");
GET("/", routeContext -> routeContext.render("contacts"));
GET("/{id: [0-9]+}", routeContext -> {
int id = routeContext.getParameter("id").toInt(0);
Map<String, Object> model = new HashMap<>();
model.put("id", id);
routeContext.render("contact", model);
});
}
}
@MetaInfServices
public class ContactInitializer implements Initializer {
@Override
public void init(Application application) {
application.addRouteGroup(new ContactRoutes());
}
}
public class UserRoutes extends RouteGroup {
public UserRoutes() {
super("/user");
GET("/", routeContext -> routeContext.render("users"));
GET("/{id: [0-9]+}", routeContext -> {
int id = routeContext.getParameter("id").toInt(0);
Map<String, Object> model = new HashMap<>();
model.put("id", id);
routeContext.render("user", model);
});
}
}
@MetaInfServices
public class UserInitializer implements Initializer {
@Override
public void init(Application application) {
application.addRouteGroup(new UserRoutes());
}
}
NOTE The order of the initializers depends on the order of the jars in the classpath.