Target release6.3.3
Epic
Document statusDRAFT
Document owner

Steve Blackmon

DesignerSteve Blackmon
Developers
QA

Goals

  • Support seamless binding of pojo types to freemarker templates for rendering to HTML

Background and strategic fit

Freemarker is a powerful engine for rendering java objects as HTML.  Juneau's HTML5 DTOs and related packages allow Juneau developers to implement java code for specialized HTML rendering of any java pojo, but there are limitations to this approach that this design seeks to address.

Assumptions

  • Freemarker is a logical choice when implementing serialization of an object or object tree into an HtmlElement.
  • Generating an HtmlElement from Freemarker is simpler and more portable that doing the same via java code using HTML5 DTOs.
  • Binding the resulting HtmlElement produced by evaluating Freemarker template(s) to the appropriate HTML5 DTO allows validation of the result vis-a-vis the HTML5 spec. 
  • Choice of how to present (View layer) an object tree should be entirely disconnected from implementation of the object class (Model layer)

Requirements

#TitleUser StoryImportanceNotes
1
2    

User interaction and design

Resource.java
/**
 * When displaying just one Group, render a subset of attributes in a table.
 */
@RestMethod(
    name="GET",
    path="/{path}",
    templates={
		"Group: Group.table.ftl"
	}
)
public Group viewGroup( RestRequest req,
                        @Path String path) {
}
 
/**
 * When displaying a list of Groups, render a different subset of attributes per row.
 */
@RestMethod(
    name="GET",
    path="/",
    templates={
		"Group: Group.div.ftl"
	}
)
public List<GroupHtml> listGroups() throws IOException {


}




Questions

Below is a list of questions to be addressed as a result of this requirements document:

QuestionOutcome

Not Doing

8 Comments

  1. Thought....

    Allow the HtmlRender class to return a Pipe which simply allows you to pipe arbitrary HTML to the writer... 

    public abstract class Pipe {
       public void writeTo(Writer w) throws Exception;
    }

    @Override
    public Pipe getContent(SerializerSession session, final Adr value) {
    return new Pipe() {
    @Override public void writeTo(Writer w) throws Exception {
    cfg.getTemplate("MemberAddressRender.div.ftl").process(value, w);
        }
    };
    }

    That's more efficient than parsing the template result and returning a Div to be serialized again.  The downside is that the formatting won't necessarily match (e.g. whitespace, quote char, etc...).  So I'm not sure it's better.

     

    If we do add templates to the @RestMethod annotation, we may want to place it inside the @HtmlDoc child annotation since it only applies to HTML.  

    It looks like we're introducing a compile-time dependency on Freemarker in core.  We may want to discuss this.  We don't currently have any compile-time dependencies in core.

     
  2. Allow the HtmlRender class to return a Pipe which simply allows you to pipe arbitrary HTML to the writer.

    I was sort of hoping we could do away with the need to make a new .java file for each type entirely.  To change the format within one or more RestMethods, developer would just need to properly annotate their method(s) with the template(s) they wanted applied and ensure the presence of the named template(s) on the classpath. Optionally, developer could use HtmlSerializerBuilder to bind templates to be applied more broadly than just one RestResource / RestMethod.

    If we do add templates to the @RestMethod annotation, we may want to place it inside the @HtmlDoc child annotation since it only applies to HTML.

    Seems Reasonable

    It looks like we're introducing a compile-time dependency on Freemarker in core.  We may want to discuss this.  We don't currently have any compile-time dependencies in core.

    It could be an optional dependency - user would encounter a class not found exception IFF they try to bind a .ftl template.  Or we could pulling org.apache.junuea.html.* into its own modules, and then implement support for specific rendering engines (starting with ftl) in standalone modules.  In the event that other template types are desired (w3c html, velocity, handlebars, etc..) their dependencies would be outside core as well.


    1. It could be an optional dependency - user would encounter a class not found exception IFF they try to bind a .ftl template.  Or we could pulling org.apache.junuea.html.* into its own modules, and then implement support for specific rendering engines (starting with ftl) in standalone modules.  In the event that other template types are desired (w3c htmlvelocityhandlebars, etc..) their dependencies would be outside core as well.

      Ok...I can see something like that.  The core and rest projects could define an API for generalized template mappings, but you'd have to specify an implementation object with it which would be defined in a separate project...

      @RestMethod(
      htmldoc=@HtmlDoc(
      templates={
      "Adr: MemberAddressRender.div.ftl"
      },
      templateEngine=FreemarkerTemplateEngine.class
      )
      )

      Under-the-covers, these would simply be mapped to two configuration properties on HtmlSerializerContext:
      HTML_templates, type = Map<String,String>
      HTML_templateEngine, type=Class<? extends TemplateEngine>

      Then the TemplateEngine API could simply be something like this...

      public abstract class TemplateEngine {
      public HtmlRender getRender(Object o, String templateName) throws Exception;
      }


  3. That's more efficient than parsing the template result and returning a Div to be serialized again.  The downside is that the formatting won't necessarily match (e.g. whitespace, quote char, etc...).  So I'm not sure it's better.


    I think there may be benefit to coercing the output of the template into a tree of typed HTML5 DTO beans, to get test-time and run-time assurance that what the template produced is in fact valid HTML5 (I don't think freemarker does this.  It only validates the template itself not the output). This benefit would be worth the performance penalty, at least for some use cases.  However, there are other use cases: the template produces several MB of output when rendered for example - where a String or OutputStream (Pipe?) would be the preferred handoff.  So we'd probably want to support implementations of HtmlRender that return an Object/String/OutputStream/Pipe (no validation) or can specify a specific DTO class - if a DTO class is specified the result would be parsed and serialized in line, otherwise passed through.

  4. Steve Blackmon - Do you want me to create the HTML_template* properties and @HtmlDoc annotation changes, and you can provide the Freemarker implementation?

    We need to come up with a different name for the @HtmlDoc(templates) annotation because there's already an @HtmlDoc(template).  Maybe renderMap/renderEngine.

     

    1. Sure lets give it a shot.

      Any preference on how/where to support parse to HTML5 DTO for validation and how implementer should signal what DTO to validate against?

      Should those details be left to the RenderEngine implementation?  If so, maybe something like this?

      htmldoc=@HtmlDoc(
          renderTemplates={
            @Render={
              class=Adr.class, // use this when HtmlSerializer encounters any Adr
              engine=FreemarkerTemplateEngine.class, 
      		template="MemberAddressRender.div.ftl", // classpath resource 
      		validate=Div.class // confirm output is a valid <div>, default = Object.class means no validation
            }
          }
          
        )


       


      1. You can do this...

           XmlParser p = XmlParser.DEFAULT.builder().beanDictionary(HtmlBeanDictionary.class).build();
           String s = "<div></div>";
           Object o = p.parse(s, Object.class); // Let the parser figure it out.
           System.err.println(o.getClass().getName()); // Prints org.apache.juneau.dto.html5.Div

        How that works is that the Div class has @Bean(typeName="div") defined on it.  The type names have different representations in each language (e.g. "_type" attribute in JSON), but in XML and HTML they're used as the element name.  The dictionary is simply a collection of beans with type names.  When the XmlParser sees the element name "div", it knows it's parsing into a Div bean.

         

  5. Steve Blackmon - With changes in 6.4.0, this should now be possible to implement using the new @Swap annotation and support for per-media-type swaps.

    The FreeMarkerSwap class can be defined as...

       public class FreeMarkerSwap extends PojoSwap<Object,Reader> {
    
          public MediaType[] forMediaTypes() {
             return MediaType.forStrings("*/html");
          }
    
          public Reader swap(BeanSession session, Object o, String template) throws Exception {
             return getFreeMarkerReader(template, o);  // Some method that creates raw HTML.
          }
       }

    Optionally, you could return a parsed Div object, but I'm not sure that's worth the effort.  It would allow for correct indentation in pretty mode, but I don't think it's worth the performance hit.

    To apply to a POJO, use this annotation....

          @Swap(impl=FreeMarkerSwap.class, template="MyPojo.div.ftl")
          public class MyPojo {}

     

    I recommend placing it in a new artifact juneau-marshall-freemarker as part of the juneau-core module.