Objective: 

Bind a specific POJO to a freemarker template and render the object as an html element.

Steps:

First, your project should include freemarker:

<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.26-incubating</version>
</dependency>

Next, you need a proper pojo containing multiple fields. Here's the pojo I'm trying to customize: 

Adr.java
import java.io.Serializable;
import javax.annotation.Generated;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.juneau.annotation.BeanProperty;

@JsonInclude(JsonInclude.Include.NON_NULL)
@Generated("org.jsonschema2pojo")
@JsonPropertyOrder({
    "addr1",
    "addr2",
    "country",
    "city",
    "state",
    "zip"
})
public class Adr implements Serializable
{

    @JsonProperty("addr1")
    @BeanProperty("addr1")
    private String addr1;
    @JsonProperty("addr2")
    @BeanProperty("addr2")
    private String addr2;
    @JsonProperty("country")
    @BeanProperty("country")
    private String country;
    @JsonProperty("city")
    @BeanProperty("city")
    private String city;
    @JsonProperty("state")
    @BeanProperty("state")
    private String state;
    @JsonProperty("zip")
    @BeanProperty("zip")
    private String zip;

    /**
     * 
     * @return
     *     The addr1
     */
    @JsonProperty("addr1")
    @BeanProperty("addr1")
    public String getAddr1() {
        return addr1;
    }

    /**
     * 
     * @param addr1
     *     The addr1
     */
    @JsonProperty("addr1")
    @BeanProperty("addr1")
    public void setAddr1(String addr1) {
        this.addr1 = addr1;
    }

    public Adr withAddr1(String addr1) {
        this.addr1 = addr1;
        return this;
    }

    /**
     * 
     * @return
     *     The addr2
     */
    @JsonProperty("addr2")
    @BeanProperty("addr2")
    public String getAddr2() {
        return addr2;
    }

    /**
     * 
     * @param addr2
     *     The addr2
     */
    @JsonProperty("addr2")
    @BeanProperty("addr2")
    public void setAddr2(String addr2) {
        this.addr2 = addr2;
    }

    public Adr withAddr2(String addr2) {
        this.addr2 = addr2;
        return this;
    }

    /**
     * 
     * @return
     *     The country
     */
    @JsonProperty("country")
    @BeanProperty("country")
    public String getCountry() {
        return country;
    }

    /**
     * 
     * @param country
     *     The country
     */
    @JsonProperty("country")
    @BeanProperty("country")
    public void setCountry(String country) {
        this.country = country;
    }

    public Adr withCountry(String country) {
        this.country = country;
        return this;
    }

    /**
     * 
     * @return
     *     The city
     */
    @JsonProperty("city")
    @BeanProperty("city")
    public String getCity() {
        return city;
    }

    /**
     * 
     * @param city
     *     The city
     */
    @JsonProperty("city")
    @BeanProperty("city")
    public void setCity(String city) {
        this.city = city;
    }

    public Adr withCity(String city) {
        this.city = city;
        return this;
    }

    /**
     * 
     * @return
     *     The state
     */
    @JsonProperty("state")
    @BeanProperty("state")
    public String getState() {
        return state;
    }

    /**
     * 
     * @param state
     *     The state
     */
    @JsonProperty("state")
    @BeanProperty("state")
    public void setState(String state) {
        this.state = state;
    }

    public Adr withState(String state) {
        this.state = state;
        return this;
    }

    /**
     * 
     * @return
     *     The zip
     */
    @JsonProperty("zip")
    @BeanProperty("zip")
    public String getZip() {
        return zip;
    }

    /**
     * 
     * @param zip
     *     The zip
     */
    @JsonProperty("zip")
    @BeanProperty("zip")
    public void setZip(String zip) {
        this.zip = zip;
    }

    public Adr withZip(String zip) {
        this.zip = zip;
        return this;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(addr1).append(addr2).append(country).append(city).append(state).append(zip).toHashCode();
    }

    @Override
    public boolean equals(Object other) {
        if (other == this) {
            return true;
        }
        if ((other instanceof Adr) == false) {
            return false;
        }
        Adr rhs = ((Adr) other);
        return new EqualsBuilder().append(addr1, rhs.addr1).append(addr2, rhs.addr2).append(country, rhs.country).append(city, rhs.city).append(state, rhs.state).append(zip, rhs.zip).isEquals();
    }

}

 

Next, create a freemarker template to translate an instance of this bean type into a specific HTML element.

Here's an example that renders the address class as a DIV, using a conditional on the addr2 field, and combining city state and zip into a single row.

MemberAddressRender.div.html
<div>
    <table>
        <tr>
            <td>
            ${addr1}
            </td>
        </tr>
        <#if addr2?has_content>
        <tr>
            <td>
            ${addr2}
            </td>
        </tr>
        </#if>
        <tr>
            <td>
            ${city}, ${state}  ${zip}
            </td>
        </tr>
    </table>
</div>

 

Next, you need to create an HtmlRender class for this type of bean.  For example:

MemberAddressRender.java
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import org.apache.juneau.dto.html5.Div;
import org.apache.juneau.dto.html5.Form;
import org.apache.juneau.html.HtmlParser;
import org.apache.juneau.html.HtmlRender;
import org.apache.juneau.serializer.SerializerSession;
import Adr;

import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.Locale;

import static org.apache.juneau.dto.html5.HtmlBuilder.div;
import static org.apache.juneau.dto.html5.HtmlBuilder.h2;

/**
 * Created by sblackmon on 7/17/17.
 */
public class MemberAddressRender extends HtmlRender<Adr> {

  static Configuration cfg = new Configuration(Configuration.VERSION_2_3_26);

  static {
    cfg.setClassLoaderForTemplateLoading(MemberAddressRender.class.getClassLoader());
    cfg.setEncoding(Locale.US, Charset.defaultCharset().name());
    cfg.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
  }

  @Override
  public Div getContent(SerializerSession session, Adr value) {
    StringWriter stringWriter = new StringWriter();
    try {
      Template myTemplate = cfg.getTemplate("MemberAddressRender.div.ftl");
      myTemplate.process(value, stringWriter);
    } catch( Exception ex ) {
      ex.printStackTrace();
    }
    String divString = stringWriter.toString();
    Div div = div();
    try {
      div = HtmlParser.DEFAULT.parse(divString, Div.class);
    } catch( Exception ex ) {
      ex.printStackTrace();
    }
    return div;
  }
}

 

Finally, you need to bind the custom Render class onto the class(es) which have the class you are customizing as a child.

MemberHtml.java
import org.apache.juneau.html.annotation.Html;
import Adr;
import Member;

/**
 * Created by sblackmon on 7/17/17.
 */

public class MemberHtml extends Member {

  @Override
  @Html(link = "servlet:/{username}")
  public String getUsername() {
    return super.getUsername();
  }

  @Override
  @Html(render=MemberAddressRender.class)
  public Adr getAdr() {
    return super.getAdr();
  }

  @Override
  @Html(render=MemberImageRender.class)
  public String getPicture() {
    return super.getPicture();
  }
}

In case you are interested, here's the auto-generated Member class whose presentation is being customized.

Member.java
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import javax.validation.Valid;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.juneau.annotation.BeanProperty;

@JsonInclude(JsonInclude.Include.NON_NULL)
@Generated("org.jsonschema2pojo")
@JsonPropertyOrder({
    "username",
    "email",
    "picture",
    "adr",
    "bday",
    "tel",
    "title",
    "url",
    "skills",
    "profiles"
})
public class Member implements Serializable
{

    @JsonProperty("username")
    @BeanProperty("username")
    private String username;
    @JsonProperty("email")
    @BeanProperty("email")
    private String email;
    @JsonProperty("picture")
    @BeanProperty("picture")
    private String picture;
    @JsonProperty("adr")
    @BeanProperty("adr")
    @Valid
    private Adr adr;
    @JsonProperty("bday")
    @BeanProperty("bday")
    private String bday;
    @JsonProperty("tel")
    @BeanProperty("tel")
    private String tel;
    @JsonProperty("title")
    @BeanProperty("title")
    private String title;
    @JsonProperty("url")
    @BeanProperty("url")
    private String url;
    @JsonProperty("skills")
    @BeanProperty("skills")
    @Valid
    private List<Skill> skills = new ArrayList<Skill>();
    @JsonProperty("profiles")
    @BeanProperty("profiles")
    @Valid
    private Profiles profiles;

    /**
     * 
     * @return
     *     The username
     */
    @JsonProperty("username")
    @BeanProperty("username")
    public String getUsername() {
        return username;
    }

    /**
     * 
     * @param username
     *     The username
     */
    @JsonProperty("username")
    @BeanProperty("username")
    public void setUsername(String username) {
        this.username = username;
    }

    public Member withUsername(String username) {
        this.username = username;
        return this;
    }

    /**
     * 
     * @return
     *     The email
     */
    @JsonProperty("email")
    @BeanProperty("email")
    public String getEmail() {
        return email;
    }

    /**
     * 
     * @param email
     *     The email
     */
    @JsonProperty("email")
    @BeanProperty("email")
    public void setEmail(String email) {
        this.email = email;
    }

    public Member withEmail(String email) {
        this.email = email;
        return this;
    }

    /**
     * 
     * @return
     *     The picture
     */
    @JsonProperty("picture")
    @BeanProperty("picture")
    public String getPicture() {
        return picture;
    }

    /**
     * 
     * @param picture
     *     The picture
     */
    @JsonProperty("picture")
    @BeanProperty("picture")
    public void setPicture(String picture) {
        this.picture = picture;
    }

    public Member withPicture(String picture) {
        this.picture = picture;
        return this;
    }

    /**
     * 
     * @return
     *     The adr
     */
    @JsonProperty("adr")
    @BeanProperty("adr")
    public Adr getAdr() {
        return adr;
    }

    /**
     * 
     * @param adr
     *     The adr
     */
    @JsonProperty("adr")
    @BeanProperty("adr")
    public void setAdr(Adr adr) {
        this.adr = adr;
    }

    public Member withAdr(Adr adr) {
        this.adr = adr;
        return this;
    }

    /**
     * 
     * @return
     *     The bday
     */
    @JsonProperty("bday")
    @BeanProperty("bday")
    public String getBday() {
        return bday;
    }

    /**
     * 
     * @param bday
     *     The bday
     */
    @JsonProperty("bday")
    @BeanProperty("bday")
    public void setBday(String bday) {
        this.bday = bday;
    }

    public Member withBday(String bday) {
        this.bday = bday;
        return this;
    }

    /**
     * 
     * @return
     *     The tel
     */
    @JsonProperty("tel")
    @BeanProperty("tel")
    public String getTel() {
        return tel;
    }

    /**
     * 
     * @param tel
     *     The tel
     */
    @JsonProperty("tel")
    @BeanProperty("tel")
    public void setTel(String tel) {
        this.tel = tel;
    }

    public Member withTel(String tel) {
        this.tel = tel;
        return this;
    }

    /**
     * 
     * @return
     *     The title
     */
    @JsonProperty("title")
    @BeanProperty("title")
    public String getTitle() {
        return title;
    }

    /**
     * 
     * @param title
     *     The title
     */
    @JsonProperty("title")
    @BeanProperty("title")
    public void setTitle(String title) {
        this.title = title;
    }

    public Member withTitle(String title) {
        this.title = title;
        return this;
    }

    /**
     * 
     * @return
     *     The url
     */
    @JsonProperty("url")
    @BeanProperty("url")
    public String getUrl() {
        return url;
    }

    /**
     * 
     * @param url
     *     The url
     */
    @JsonProperty("url")
    @BeanProperty("url")
    public void setUrl(String url) {
        this.url = url;
    }

    public Member withUrl(String url) {
        this.url = url;
        return this;
    }

    /**
     * 
     * @return
     *     The skills
     */
    @JsonProperty("skills")
    @BeanProperty("skills")
    public List<Skill> getSkills() {
        return skills;
    }

    /**
     * 
     * @param skills
     *     The skills
     */
    @JsonProperty("skills")
    @BeanProperty("skills")
    public void setSkills(List<Skill> skills) {
        this.skills = skills;
    }

    public Member withSkills(List<Skill> skills) {
        this.skills = skills;
        return this;
    }

    /**
     * 
     * @return
     *     The profiles
     */
    @JsonProperty("profiles")
    @BeanProperty("profiles")
    public Profiles getProfiles() {
        return profiles;
    }

    /**
     * 
     * @param profiles
     *     The profiles
     */
    @JsonProperty("profiles")
    @BeanProperty("profiles")
    public void setProfiles(Profiles profiles) {
        this.profiles = profiles;
    }

    public Member withProfiles(Profiles profiles) {
        this.profiles = profiles;
        return this;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(username).append(email).append(picture).append(adr).append(bday).append(tel).append(title).append(url).append(skills).append(profiles).toHashCode();
    }

    @Override
    public boolean equals(Object other) {
        if (other == this) {
            return true;
        }
        if ((other instanceof Member) == false) {
            return false;
        }
        Member rhs = ((Member) other);
        return new EqualsBuilder().append(username, rhs.username).append(email, rhs.email).append(picture, rhs.picture).append(adr, rhs.adr).append(bday, rhs.bday).append(tel, rhs.tel).append(title, rhs.title).append(url, rhs.url).append(skills, rhs.skills).append(profiles, rhs.profiles).isEquals();
    }

}

 

Great.  Now any resource which returns a MemberHtml or a List<MemberHtml> to the Juneau HTML Serializer will see the freemarker template applied to any Adr instances within the appropriate cell.

BEFORE:

AFTER:

  • No labels