- 4 minutes read

Glassfish is the official reference implementation of JavaEE. Nonetheless, there's an annoying incompatibility with JSF. Glassfish doesn't recognize the components of BootsFaces at server startup reliably. Sometimes it does, sometimes it doesn't. Payara is affected, too. The bug happens more often on older versions, but we've got a bug report for a current Glassfish 5.0.1.

We receive this error report every once in a while on our BootsFaces bug tracker. Luckily, thanks to GitHub user jimmc929, there's a solution.

What's the error?

When a BootsFaces application is deployed to Glassfish, the application runs fine in, roughly, 80% of the time. The rest of the time the BootsFaces components are not rendered. Everything else - HTML code, standard JSF components, PrimeFaces components - works fine. Only the BootsFaces widgets are missing.

What's happening is that BootsFaces uses annotations to define the JSF components. Most other libraries, such as Mojarra, MyFaces, and PrimeFaces, use the old mechanism using an XML file. For some reason, Glassfish fails to scan the classpath every once in a while. When this happens, the annotation-based approach of BootsFaces fails, too.

Solution for Glassfish 4.1.1 (and Payara)

So the solution is simple. Scan the classpath yourself and register the BootsFaces components manually. According to GitHub user jimmc929, this is how it's done:

@ManagedBean(eager=true) @ApplicationScoped public class InitBootsfacesBean { /** * Register the Bootsfaces renderers */ private void initializeBootsfacesRenderers() { // Loop through all of the Bootsfaces components for (ComponentsEnum value : ComponentsEnum.values()) { // Get the component class information String className; // switchComponent has wrong classpath in ComponentsEnum if (value.name().equals("switchComponent")) { // Use correct qualified name className = "net.bootsfaces.component.switchComponent.Switch"; } else { // Otherwise, use specified value className = value.classname(); } // See if this is an internal reference if (className.contains("Internal")) { LOGGER.log(Level.INFO, "Init Bootsfaces: Skipping internal component: " + className); } else { LOGGER.log(Level.INFO, "Init Bootsfaces: Processing component: " + className); try { // See if we can instantiate the class Class componentClass = Class.forName(className); Class baseClass = componentClass.asSubclass(UIComponentBase.class); UIComponentBase component = baseClass.newInstance(); String rendererFamily = component.getFamily(); String rendererType = component.getRendererType(); // Determine the renderer class name String simpleName = componentClass.getSimpleName(); String rendererClassName; switch (simpleName) { case "NavCommandLink": // Shares same renderer with NavLink rendererClassName = "net.bootsfaces.component.navLink.NavLinkRenderer"; break; default: // We have to guess at the name of the renderer rendererClassName = className + "Renderer"; } // Look up the renderer Class rendererSuperclass = Class.forName(rendererClassName); Class rendererClass = rendererSuperclass.asSubclass(Renderer.class); Renderer renderer = rendererClass.newInstance(); LOGGER.log(Level.INFO, "Init Bootsfaces: Registering renderer: " + rendererFamily + "/" + rendererType); // ****** THIS IS THE IMPORTANT CALL TO REGISTER THE RENDERER ********* addRenderer(rendererFamily, rendererType, renderer); } catch (Throwable e) { LOGGER.log(Level.WARNING, "Init Bootsfaces: Unable to register renderer for component: " + className, e); } } } } public void addRenderer(String family, String rendererType, Renderer renderer) { getDefaultRenderKit().addRenderer(family, rendererType, renderer); } }

Solution for Glassfish 5.0.1 (and Payara)

According to another GitHub user who calls themselves poetically TheTimeWalker, the API of Glassfish 5 has changed. They solved the problem by modifying

private RenderKit defaultRenderKit = getDefaultRenderKit(); public RenderKit getDefaultRenderKit() { RenderKitFactory renderKitFactory = (RenderKitFactory) FactoryFinder.getFactory(FactoryFinder.RENDER_KIT_FACTORY); FacesContext facesContext = FacesContext.getCurrentInstance(); return renderKitFactory.getRenderKit(facesContext, RenderKitFactory.HTML_BASIC_RENDER_KIT); } public void addRenderer(String family, String rendererType, Renderer renderer) { defaultRenderKit.addRenderer(family, rendererType, renderer); }

Wrapping it up

It's weird that the reference implementation of JavaEE is buggy. In general, my suggestion is to use one of the other application servers. However, not everybody has this liberty. If so, the code snippets above help you to solve the bug yourself. Kudo to the developers who spent a lot of time to solve the bug, in particular to jimmc929, tg47 and TheTimeWalker. Keep up the good work!


Comments