Search code examples
jsfannotationsjsf-2.2custom-componentembedded-tomcat-8

How can I use annotations instead of XML to create a custom component tag in embedded Tomcat


Edit: The only technologies required to reproduce this issue are JSF 2.2 and Spring Boot 1.2.1 + Its embedded Tomcat 8.0.5 server. Everything else listed in this question is just to give context on the tech I'm using.

Update #2: Following along with BalusC's thoughts, I ported my sample custom component into a barebones Servlet 3.1 + JSF 2.2 application. You can find the code for it on Github here.

This simple case does not exhibit the issue I'm describing here. The @FacesComponent annotation works. This heavily implies that the problem is being caused either by Spring 4.1.2 or Spring Boot itself. It's getting late, so I'll be investigating this further tomorrow.

TL;DR: I want to use @FacesComponent and its attributes to replace foundation-components-html.taglib.xml and the <component> entry in faces-config.xml

I currently have custom components working in my project using XML definitions. I recently learned that JSF 2.2 introduced a feature which removes the need for XML entirely. I would love to use this, but when I purely use annotations, they are ignored by JSF. Raw tags show up in my HTML.

(i.e. <custom:paragraph></custom:paragraph>)

I have demonstrated this issue in a sandbox of mine I keep hosted on Github. If you want to take a crack at that, I'll explain how at the bottom of this post.

All you need to do is delete foundation-components-html.taglib.xml, and comment out the faces-config.xml entry for <component> and run the application to encounter the issue. I left it in the 'functioning' state so that anyone who wishes to help has an easy, verifiably correct starting point. Just hit up http://localhost:8080

Technologies Used:

  • Spring Boot 1.2.1

  • JSF 2.2 via Mojarra 2.2.6

  • Embedded Tomcat 8.0.5

NOTE: Remember, this setup currently works, but it's running on the taglib and faces-config entries! My question is how to remove these dependencies using the latest features in JSF 2.2

Full Project

Custom Component

package foundation.components;

import java.io.IOException;

import javax.faces.component.FacesComponent;

import javax.faces.component.UIComponentBase;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

/**
 * The Paragraph Component
 * @author Seth Ellison
 */
@FacesComponent(value=UIParagraph.COMPONENT_TYPE, createTag=true, tagName="paragraph", namespace="http://www.blah.com/components/html")
public class UIParagraph extends UIComponentBase {

public static final String COMPONENT_TYPE = "foundation.components.Paragraph";

private String value;
private String styleClass;

@Override
public void encodeBegin(final FacesContext facesContext) throws IOException {

    // Encode Implementation Omitted for Brevity. 
}

@Override
public String getFamily() {        
    return "blah.components.family";
}

// Getters/Setters...

}

Taglib Definition

<facelet-taglib version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facelettaglibrary_2_2.xsd">

    <namespace>http://www.blah.com/components/html</namespace>

    <tag>
        <tag-name>paragraph</tag-name>
        <component>
            <component-type>foundation.components.Paragraph</component-type>
        </component>
    </tag>
</facelet-taglib>

Faces Config

<faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"             
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
        version="2.2" metadata-complete="false">

    <component>
        <component-type>foundation.components.Paragraph</component-type>
        <component-class>foundation.components.UIParagraph</component-class>
    </component>  
</faces-config>

XHTML Template (Stripped down for clarity)

<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:jsf="http://xmlns.jcp.org/jsf"
  xmlns:custom="http://www.blah.com/components/html">

    <head jsf:id="head"></head>
    <body jsf:id="body">
       <custom:paragraph value="This is a template for a simple marketing or informational website. It includes a large callout called a jumbotron and three supporting pieces of content. Use it as a starting point to create something more unique." />
    </body>
</html>

If you'd like to run this, the easiest way would be to download the Spring Tool Suite, grab the code from Github, right click the project, and run it as a Spring Boot App. You'll get a connection error when the JPA configuration fires up, because you (likely) aren't running a local MySQL server. Don't worry about this. It's not at all required to visit the index page and check out the tag status. I frequently run the app both with, and without the DB fired up to no ill effect. Lastly, to get PrettyFaces to play nice with Spring Boot, you have to create either a Symbolic Link or a Hard Link from target/classes into WEB-INF/ -- PrettyFaces is coded to look in WEB-INF/classes or WEB-INF/lib when scanning for annotations.

Snippets for BalusC

This function exists in a class which is marked with @Configuration and implements ServletContextAware

@Bean
public ServletListenerRegistrationBean<ConfigureListener> jsfConfigureListener() {
    return new ServletListenerRegistrationBean<ConfigureListener>(
            new ConfigureListener());
}

Solution

  • Alright, I figured out what was causing the issue.

    This morning I sat down to think about the differences between my working Servlet 3.1 version of the code, and the broken Spring Boot version. The main difference was how the code was being run. Embedded server vs. Standalone.

    Spring Boot's embedded Tomcat server was the cause.

    When I switched my sandbox around in accordance with this answer, everything turned on normally, and my custom components worked purely off of the @FacesComponent annotation!

    I figure this has something to do with the way classes are organized post-startup on the embedded server vs. a discrete deploy to the Pivotal Tomcat server. JSF's annotation scanner seems to simply ignore annotations in that case.