Annoying JSF! (Part 1: Resource Handling)

21 11 2011

The last weeks, I dealt with JSF in a more intensive way. Whenever I read posts about JSF2, I found tutorials about the new features or listings about the advantages of JSF. But when looking for a deeper level of knowledge, there are some stumbling blocks that I’ll write about within a series of posts. The first one deals with the Resource Handling mechanism that was introduced with JSF2.

Resource Handling with JSF

The principle of resource handling with JSF is that you can place your web resources (CSS, JS or whatever) within special folders and send a request to the FacesServlet to get the resources to the browser. There are some JSF Facelet Tags to refer to these resources in a transparent way.
You do not have to use this, you can also refer to static resources of the web application (bypassing the Faces Servlet). But JSF resource handling has some advantages:

  • Caching: The resources are loaded once and cached by the FacesServlet to provide faster request handling for the next invocations.
  • Pluggability: You can address resources within a JAR file using the classloader, so you can provide corporate designs as external reusable libraries.
  • Component Architecture: Write custom UI components that introduce some CSS or JS. Those resources are loaded by JSF’s resource handling.

And you can implement your own ResourceHandler to customize it.

The pitfall

CSS is created and tested by web designers in a static way. You can use it within your web pages in a static way. Unfortunately, you cannot use it with JSF resource handling, because links in CSS files (e.g. background images, CSS imports) do not work. See the example below:

body {
  background-image: url("logo.gif");
}

If this is loaded by JSF with a URL of /javax.faces.resources/theme/layout.css.jsf?ln=css, the image is searched under /javax.faces.resources/theme/logo.gif cannot be found, because the Faces Servlet is not mapped to this url (only *.jsf) and the library name (css) is passed as a request parameter that is lost.

To the JSF team:
Suggestion #1: Encode the library name within the request path.

So how to solve the problem? There are some possibilities:

  • Use #{resource['css:theme/logo.gif']} in the CSS file.
    This is recommended by the JSF community to get a valid link rendered within the CSS file. IMHO, this is not a good strategy because you cannot develop and test the CSS in the static way anymore. How should the web designer know about JSF expressions? What about CSS that is used in both JSF and non-JSF applications?
  • Write a custom ResourceHandler that replaces URLs in CSS to match the FacesServlet in a valid way.
    I have done so, but such string replacing algorithms can contain bugs. (Download the archive and put it into your WEB-INF/lib folder of the web application. It contains the source code.)
  • Write a custom ResourceHandler that defines an URL where the library name is encoded within the request path.Ideally, the javax.faces.resources should be removed from the URL. The FacesServlet must then be mapped to the new URL using a prefix (e.g. /resources/*) instead of a suffix.

To the JSF team:
Suggestion #2: Think about URL mappings and folders defined by the specs.
Resources have to be placed under the /resources folder of the web application. There they can be addressed directly via URL bypassing the FacesServlet. IMHO this is not the best solution. So why not place them under /META-INF/resources like it is declared for external JARs?

Further suggestion

Those suggestions came up during implementation of my customized ResourceHandler.

Suggestion #3: Refactor the URL handling for resources
The URL to a resource is given by an object of javax.faces.application.Resource, while the decision of a request to address a resource is made by the ResourceHandler. This should be encapsulated and made configurable (e.g. by an init param of the web app).

Suggestion #4: Limit the responsibilities of the ResourceHandler
“ResourceHandler” means the instance for loading resources. But I found out that it also loads Composite Components that maybe can be declared as resources too – but if the custom ResourceHandler contains bugs, you affect the whole page rendering, not only CSS, JS or images.

Suggestion #5: Generalize the exception for the ResourceHandler that is made within the FacesServlet
Within the FacesServlet, the request is analyzed to be a resource request or not. If so, the request is handled by the ResourceHandler, otherwise the phases of the request lifecycle are passed through. Might there be further exceptions in the future or done by external frameworks? Would it be better to provide a more generic mechanism to plug such exceptions to the FacesServlet? The ResourceHandler would then be one of these exceptions.





Using ServletContainerInitializer to configure RWT applications

11 10 2011

Today is my birthday! Happy Birthday to me… 30 years of age makes me kind of proud and thoughtful. Benjamin Franklin said:

At twenty years of age, the will reigns; at thirty, the wit; and at forty, the judgement.

And my last will – yesterday – was to think about another way of configuring RWT applications as described by Frank Appel. Of course, registering an ApplicationConfigurator as an OSGi Service is pretty easy (on IBM WebSphere Application Server, you can use BluePrint Declarative Services), but as far as the Servlet 3.0 API is available, we can maybe use the Pluggability API?

The result of my thoughts is a prototype that uses the ServletContainerInitializer mechanism that allows to install web frameworks at the web container (or within a web application) and to use the interfaces or annotations of the framework to automatically configure servlets, listeners, filters, …

The Concept

For RWT, such a ServletContainerInitializer could register the HttpServiceServlet and the ApplicationConfigurator service instances. For this, we need an interface or an annotation for the ServletContainerInitializer to recognize an RWT application to configure. We could already use the IEntryPoint interface, but this might conflict with applications implementing this interface that were already configured manually. So the best way would be to define a custom annotation that is only handled by the ServletContainerInitializer.

The Implementation

So I first created the interface (with the most important information for an application):

package org.eclipse.rap.rwt.osgi.servlet;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RWTApplication {

  /**
   * The servlet name and the name of the entry point.
   *
   * @return the servlet name and the name of the entry point
   */
  String value();

  /**
   * The title of the application shown in the browser window.
   *
   * @return the title of the application shown in the browser window
   */
  String title() default "";

}

In the web application (Web Archive Bundle, WAB), I wrote an application like this:

package org.eclipse.rap.internal.rwt.osgi.servlet;

import org.eclipse.rap.rwt.osgi.servlet.RWTApplication;
import org.eclipse.rwt.lifecycle.IEntryPoint;

@RWTApplication(value="demo", title="RWT Demo using Servlet Container Initializer")
public class DemoApplication implements IEntryPoint {

  /* (non-Javadoc)
   * @see org.eclipse.rwt.lifecycle.IEntryPoint#createUI()
   */
  public int createUI() {
    [...]
  }

}

Then, I wrote the ServletContainerInitializer. The problem is that the ServletContainerInitializer is invoked before the OSGi BundleContext is added to the ServletContext, so a ServletContextAttributeListener must be used to register the ApplicationConfigurator service instance to the OSGi platform. The HttpServiceServlet must be registered immediately, this must not be done after the web application’s startup phase (even not in a ServletContextListener registered by a ServletContainerInitializer.

package org.eclipse.rap.internal.rwt.osgi.servlet;

import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import javax.servlet.annotation.HandlesTypes;

import org.eclipse.rap.rwt.osgi.servlet.RWTApplication;
import org.eclipse.rwt.application.ApplicationConfiguration;
import org.eclipse.rwt.application.ApplicationConfigurator;
import org.eclipse.rwt.branding.AbstractBranding;
import org.eclipse.rwt.lifecycle.IEntryPoint;
import org.osgi.framework.BundleContext;

@HandlesTypes(RWTApplication.class)
public class OSGiServiceInitializer implements ServletContainerInitializer {

  private static final Logger logger = Logger.getLogger(OSGiServiceInitializer.class.getName());

  private static final String BC = "osgi-bundlecontext";
  private static final String SERVLET_CLASS_NAME = "org.eclipse.equinox.http.servlet.HttpServiceServlet";
  private static final String SERVLET_NAME = "OSGiHttpServiceServlet";
  private static final String SERVLET_MAPPING = "/*";

  private static final String DYNAMICALLY_ADDED_SERVLET_ATTRIBUTE = "org.eclipse.rap.rwt.osgi.DYNAMICALLY_SERVLET";
  private static final Object DYNAMICALLY_ADDED_SERVLET_VALUE = Boolean.TRUE;

  public OSGiServiceInitializer() {
    super();
    logger.log(Level.INFO, "OSGiServiceInitializer registered to Web Container");
  }

  private static void addHttpServiceServlet(final ServletContext ctx) {
    // check if already registered
    final Map<String, ? extends ServletRegistration> servletRegistrations = ctx.getServletRegistrations();
    for (final Map.Entry<String, ? extends ServletRegistration> entry : servletRegistrations.entrySet()) {
      final ServletRegistration reg = entry.getValue();
      if (SERVLET_CLASS_NAME.equals(reg.getClassName())) {
        return;
      }
    }
    // register servlet
    ctx.addServlet(OSGiServiceInitializer.SERVLET_NAME, OSGiServiceInitializer.SERVLET_CLASS_NAME).addMapping(OSGiServiceInitializer.SERVLET_MAPPING);
    // set context attribute for dynamic registration
    ctx.setAttribute(OSGiServiceInitializer.DYNAMICALLY_ADDED_SERVLET_ATTRIBUTE, OSGiServiceInitializer.DYNAMICALLY_ADDED_SERVLET_VALUE);
  }

  private static void configure(final ServletContext ctx, final BundleContext bc, final Set<Class<? extends IEntryPoint>> applications) {
    for (final Class<? extends IEntryPoint> app : applications) {
      final RWTApplication appConfig = app.getAnnotation(RWTApplication.class);
      if (null != appConfig) {
        logger.log(Level.INFO, "RWT Application configured by OSGiServiceInitializer: " + app.getName());
        bc.registerService(ApplicationConfigurator.class.getName(),
          new ApplicationConfigurator() {

            @Override
            public void configure(ApplicationConfiguration configuration) {
              configuration.addEntryPoint(appConfig.value(), app);
              configuration.addBranding(new AbstractBranding() {
                @Override
                public String getServletName() {
                  return appConfig.value();
                }

                @Override
                public String getTitle() {
                  return "".equals(appConfig.title()) ? null : appConfig.title();
                }

                @Override
                public String getDefaultEntryPoint() {
                  return appConfig.value();
                }
              });
            }
          }, null);
        }
      }
  }

  /*
   * (non-Javadoc)
   *
   * @see javax.servlet.ServletContainerInitializer#onStartup(java.util.Set,
   * javax.servlet.ServletContext)
   */
  @SuppressWarnings({ "unchecked", "rawtypes" })
  @Override
  public void onStartup(final Set<Class<?>> classes, final ServletContext ctx) throws ServletException {
    if (null != classes) {
      classes.remove(RWTApplication.class);
    }
    if (null != classes && !classes.isEmpty()) {
      logger.log(Level.INFO, "ServletContext configured by OSGiServiceInitializer because of found RWT application(s)");
      OSGiServiceInitializer.addHttpServiceServlet(ctx);
      // RWT Application(s) detected
      final BundleContext bc = (BundleContext) ctx.getAttribute(OSGiServiceInitializer.BC);
      if (null != bc) {
        OSGiServiceInitializer.configure(ctx, bc, (Set) classes);
      } else {
        ctx.addListener(new ServletContextAttributeListener() {

          @Override
          public void attributeReplaced(ServletContextAttributeEvent evt) {
            if (OSGiServiceInitializer.BC.equals(evt.getName())) {
              final BundleContext bc = (BundleContext) evt.getValue();
              if (null != bc) {
                OSGiServiceInitializer.configure(ctx, bc, (Set) classes);
              }
            }
          }

          @Override
          public void attributeRemoved(ServletContextAttributeEvent evt) {}

          @Override
          public void attributeAdded(ServletContextAttributeEvent evt) {
            attributeReplaced(evt);
          }

        });
      }

    }
  }

}

The RWTApplication annotation and the ServletContainerInitializer must be packaged within a JAR file (does not have to be an OSGi bundle). This JAR file must be available within the web application’s classpath during application startup.

Conclusion

Of course, this is just an experiment for today, but could be a suggestion to provide RWT for web applications by installing the framework at the web container instead of the web application. At least, I have to say: The will pushed me, the wit will advance. With this in mind: Cheers!





Deploying RWT applications to IBM WebSphere Application Server

10 10 2011

The last days, I tried to get a demo RWT application run on IBM WebSphere Application Server by using the OSGi feature. And the result is: It works! I have written a short manual within the RAP Wiki. But there are some restrictions and notes:

  • Ensure that you have the RWT 1.5 Nightly Build later than October 6th, because there was a bug on registering RWT resources.
  • There is a strange bug of IBM WebSphere Application Server 8 that provides the Servlet 3.0 API with the javax.servlet.* packages exported as version 2.6.0! Fortunately, the RAP team has minded this bug. Thanks guys!
  • I did not already test the whole RAP platform, only RWT and JFace.
  • I only got it work using the isolated way of running the RWT platform. WebSphere provides a Shared Bundle Framework to all applications. Each application is run in an isolated framework, but can access services running in the Shared Bundle Framework. It is possible to install the OSGi Http Service into the Shared Bundle Framework and register servlets of applications to this service. This way, you have a single context root that can be extended by multiple applications. Installing the org.eclipse.rap.rwt bundle into the Shared Bundle Framework did not work because of some strange errors (without any details). I have tested to remove some entries from the manifest file, but this is like finding the needle in a haystack. And I guess, this would not be the default scenario because isolation is one of the requirements to multiple applications running on a single server.

You can find my demo EBA here. The RWT and JFace bundles are not packaged into the EBA. You can add those that are named within the META-INF/APPLICATION.MF file to the EBA or provide the bundles using WAS’ bundle repository configuration. Take care of the versions, because I used RWT 1.5 with Eclipse 3.6.0 bundles, because WebSphere Application Server provides the org.eclipse.osgi bundle version 3.6.0.

After installing this EBA on WebSphere Application Server, you can open the demo with the URL http://<host&gt;:<port>/http/demo. You should then get this:

RWT Demo on WAS8





Some thoughts about OSGi and Java EE 6

28 06 2011

Last week, I played around with the new features of Java EE 6, especially with the Servlet 3.0 API. Because developing Java Enterprise applications with the OSGi application programming model is pretty attractive for me, I had a focus on the comparison of concepts that both “worlds” introduced and how to use them in a conflated environment.
Read the rest of this entry »





Using the OSGi Http Service on IBM WebSphere Application Server

14 05 2011

Since version 7 (Feature Pack) and version 8 (integrated), IBM WebSphere Application Server (WAS) provides the OSGi application model to enterprise application developers. It allows to develop OSGi bundles and to deploy applications as enterprise bundle archives (EBA). As WebSphere uses Equinox as the OSGi implementation, it should be possible to install and run the Rich Ajax Platform on the server. The first step is to evaluate how to install the OSGi Http Service into WebSphere’s web container and how to use it.

Read the rest of this entry »





May I introduce…

13 05 2011

… myself – taddaa! :-)

I’m Ralf. 29 years old. I work for ARS in Munich/Germany as a consultant, trainer and developer dealing with Java EE and OSGi topics. I’m a contributor to the Rich Ajax Platform.

Why do I tell you? I’m going to share my experiences with JavaEE and OSGi (both standalone and combined) and I think it would be nice for you to know a little bit about the person behind the scenes.

Currently, I’m experiencing the OSGi feature of WebSphere Application Server as a runtime of Equinox based applications (like RAP). Once I tested it out, I’ll post my results here.

So stay tuned and enjoy! ;-) See you soon.








Follow

Get every new post delivered to your Inbox.