In technical terms, view technologies are provided by implementing the interface
javax.mvc.engine.ViewEngine
- with Jakarta EE 9 the package name changes
to jakarta.mvc.engine.ViewEngine
. That means, that the integration of
FreeMarker template engine requires the implementation of the ViewEngine
interface:
@ApplicationScoped
@Priority(ViewEngine.PRIORITY_APPLICATION)
public class FreeMarkerViewEngine implements ViewEngine {
// FreeMarker configuration is initialized lazily
private volatile Configuration cfg;
@Override
public boolean supports(String view) {
return view.endsWith(".ftl");
}
@Override
public void processView(ViewEngineContext context) throws ViewEngineException {
ensureInit(context);
Models models = context.getModels();
try {
Template temp = cfg.getTemplate(context.getView());
/* Merge data-model with template */
Writer out = new OutputStreamWriter(context.getOutputStream());
temp.process(models, out);
} catch (IOException | TemplateException e) {
throw new ViewEngineException(e);
}
}
private void ensureInit(ViewEngineContext context) {
if (cfg == null) {
synchronized (FreeMarkerViewEngine.class) {
if (cfg == null) {
HttpServletRequest httpRequest = context.getRequest(HttpServletRequest.class);
Map<String, Object> appConfig = context.getConfiguration().getProperties();
Object viewFolderConfig = appConfig.get(ViewEngine.VIEW_FOLDER);
String viewFolder = viewFolderConfig != null ? viewFolderConfig.toString() : DEFAULT_VIEW_FOLDER;
// Create your Configuration instance, and specify if up to what FreeMarker
// version (here 2.3.29) do you want to apply the fixes that are not 100%
// backward-compatible. See the Configuration JavaDoc for details.
cfg = new Configuration(Configuration.VERSION_2_3_29);
// Specify the source where the template files come from. Here I set a
// plain directory for it, but non-file-system sources are possible too:
cfg.setServletContextForTemplateLoading(httpRequest.getServletContext(), viewFolder);
// From here we will set the settings recommended for new projects. These
// aren't the defaults for backward compatibility.
// Set the preferred charset template files are stored in. UTF-8 is
// a good choice in most applications:
cfg.setDefaultEncoding("UTF-8");
// Sets how errors will appear.
// During web page *development* TemplateExceptionHandler.HTML_DEBUG_HANDLER is
// better.
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
// Don't log exceptions inside FreeMarker that it will thrown at you anyway:
cfg.setLogTemplateExceptions(false);
// Wrap unchecked exceptions thrown during template processing into
// TemplateException-s:
cfg.setWrapUncheckedExceptions(true);
// Do not fall back to higher scopes when reading a null loop variable:
cfg.setFallbackOnNullLoopVariable(false);
}
}
}
}
}
Because view engines are CDI beans, FreeMarkerViewEngine
is automatically
picked up by Jakarta MVC as candidate for rendering views. Whether this engine
is requested to render views at runtime is a matter of the priority (annotation)
and the result of the supports(String view)
method call. The standard explains
the the selection mechanism in detail.
The configuration of the FreeMarker template engine defines the location to
load templates, which requires the Servlet context. Because the Servlet context
becomes available with an HTTP request, the lazy initialization of FreeMarker’s
Configuration
is delayed till the first rendering request arrives. The lazy
initialization also makes synchronization and the application of the
double-checked locking pattern necessary.
|
FreeMarker’s Configuration is thread-safe after the last configuration setting
is done.
|
When stripping off the setup and configuration of the template engine, the
actual integration into Jakarta MVC boils down to the lines:
Writer out = new OutputStreamWriter(context.getOutputStream());
temp.process(models, out);
Beside the stream output writer, which eventually wraps the output stream
of the HTTP request, the MVC model mappings need to be adapted to the
template engine. Because MVC Models
implements Java’s Map
interface, this
object can be passed directly as model object into the FreeMarker template
engine.
The demo project shows the ease of use of the Jakarta MVC view technology and
the simplicity of integrating different template engines.