Content Application Developer Manual / Version 2404
Table Of Contents
The pages of a typical CAE based website are composed of several content objects where each page
fragment corresponds to one or more contents. For example, a teaser area on a page may be
modeled from teaser content items that are placed in a link list property. When rendering the page,
then the entire content structure is rendered by recursively applying the content beans to
matching templates, for example a content of type Teaser
is translated to
a content bean Teaser.class
that is rendered by a template
Teaser.ftl
.
There are situations where it may not be adequate to add a new content type for every piece of functionality that should be used on a website. This may be true when there is only one or a very few content instances of this type.
Example: Consider a website function "Current Weather" that displays the weather forecast for the user's current location. Another example would be a "Login" form that enables the user to login to or log out from the website. In order to enable an editor to add, remove or replace such functionality in a page, it is necessary to represent it as a content item. On the other hand it would be a huge overhead to add a content type "Weather" and a content type "Login".
Such functionality can be easily added to an application using the Substitution API. The basic
idea behind the API is, that there is a generic content type that serves as a kind of
placeholder. Content of this type must have a string property containing an identifier (for
example com.mycompany.weather
) that is internally used to render the real
information that is represented by the content item. This identifier is an arbitrary string, linking
the content object to the substitution implementing the intended behavior. To avoid name clashes
of logical identifiers, for instances with future project extensions, it is recommended to adopt
the naming convention known from Java packages as shown here.
Example: Let's say that there is placeholder content type called Action
<DocType Name="Action"> <StringProperty Name="id" Length="128"/> </DocType>
with a corresponding content bean implementing this interface:
public interface Action { String getId(); }
Instead of rendering the Action bean using a template Action.ftl
, a more
special bean Weather
could be rendered using a matching template
Weather.ftl
. This kind of substitution (for example an Action with an id
com.mycompany.weather
is substituted by an instance of bean
Weather
) is supported by the Substitution API. Note that the bean
resulting from the substitution can be of an arbitrary type, and does not need to implement any
particular interface.
In order to define such substitution, simply add an @Substitution
annotated method to any bean in the application context:
package com.mycompany.weather.handlers;
import com.coremedia.objectserver.view.substitution.*;
public class WeatherHandler {
// ...
// Substitution ID "com.mycompany.weather" is arbitrary,
// but uses package naming conventions to avoid name
// clashes.
// It must match the property value in the
// corresponding content object, whose content bean will
// be substituted with this Weather bean during rendering
// by the ${cm:substitute} function.
@Substitution("com.mycompany.weather")
public Weather createWeatherBean(Action original,
HttpServletRequest request) {
return new Weather(original,
getCurrentWeather(request.getSession()));
}
}
Example 5.4. Annotating a Substitution method
The (generic) template Action.ftl
can perform this substitution by calling the
Freemarker function cm.substitute()
and dispatching the substitution
result to its responsible template (for example Weather.ftl
).
<#-- @ftlvariable name="self" type="com.mycompany.Action" --> <@cm.include self=cm.substitute(self.id!"", self) />
Example 5.5. Use of cm.substitute() in CMAction.ftl
For more information on FreeMarker usage, see Section 6.5.1, “CoreMedia (cm)” in Frontend Developer Manual.
Using the @Substitution
annotation isn't the only way to register a
substitution. Consider a login example that requires a handler to perform the login action:
import com.coremedia.objectserver.view.substitution.*; public LoginHandler { // ... @RequestMapping("/{id}/login") public ModelAndView handleLogin( @PathVariable("id")Page page, @RequestParam("user")String user, @RequestParam("password")String password, HttpServletRequest request){ LoginState state=processLogin(user, password, request.getSession()); ModelAndView result=HandlerHelper.createModel(page); SubstitutionRegistry.register("com.mycompany.login", state, result); return result; } }
Example 5.6. Registering a substitution programmatically
This example demonstrates the substitution from within a handler. The advantage in comparison to the
annotation based approach is the fact that form data can be handled conveniently using the
binding of Spring MVC.
In fact, the different approaches can be used in conjunction. An explicitly registered
substitution (using the SubstitutionRegistry
service) has precedence over
the annotation approach. Thus, @Substitution
can be used as a fallback
in case that there hasn't been a registration by a handler.
Spring Forms
When using the Spring Form Freemarker integration, then it is necessary to have the form beans stored under
certain names (other than self
) in the request scope. For this reason, an optional
modelAttribute
can be specified in the @Substitution
annotation. When this is done, then the substituted bean is stored under this name in the
request. Example: An annotation @Substitution(value="com.mycompany.weather",
modelAttribute="weatherBean")
will cause the substituted bean to be stored in the request
as an attribute weatherBean
.