Thursday, December 31, 2009

Bookmark and Share

Jetty is widely appreciated as low-footprint and lightning-fast web container. That makes it also a perfect fit to be used as development runtime, where short turnaround times are an absolut must.

I used to work with the Jetty Maven plugin, which supports hot-deploying applications after any code changes while Jetty is running. This works pretty well, but comes at the cost that your session data is lost during re-deployment and depending on your application's complexity it also can take some seconds to get it up running again.

So I tried another option: running Jetty in embedded mode. For that purpose Jetty provides a comprehensive API, which we can use to write a simple main class that starts up a Jetty instance serving the application we are currently developing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class JettyRunner {

    public static void main(String[] args) throws Exception {

        Server server = new Server();

        Connector connector = new SelectChannelConnector();
        connector.setPort(8080);
        connector.setHost("127.0.0.1");
        server.addConnector(connector);

        WebAppContext wac = new WebAppContext();
        wac.setContextPath("/myapp");
        wac.setBaseResource(
            new ResourceCollection(
                new String[] {"./src/main/webapp"}));

        server.setHandler(wac);
        server.setStopAtShutdown(true);
        server.start();
        server.join();
    }
}

The code is pretty much straight forward: first a connector with port and hostname is configured, then a context for the application is created and registered with the server, which finally is started up.

When we launch this class in debugging mode from within our IDE, code changes are immediately applied to the running application without the need to re-deploy or restart it due to Java's hot code replacement mechanism.

Furthermore all changes to the files under "src/main/webapp" are instantly visible, as the files are served directly from there.

The reduced number of required restarts/redeployments greatly improves development productivity. Basically restarting is only required when modifying deployment descriptors (such as web.xml) or when class signatures are modified (e.g. by adding new methods).

Running JSF applications

This works like a charm for basic servlets, but things are getting a bit more complicated, when JSF comes into play.

The reason is that JSF searches for all classes carrying any of the JSF annotations (such as @ManagedBean) when starting up. This scan takes place in the directory "META-INF/classes". As this folder doesn't exist in our case, no single managed bean will be detected.

How can this problem be solved? First of all the output folder of the application must be added to the WebAppContext's resource collection. In case of a Maven-based project we have to add the "target" directory:

1
2
3
4
5
...
wac.setBaseResource(
    new ResourceCollection(
        new String[] { "./src/main/webapp", "./target" }));
...

But the problem remains that JSF searches class files in "WEB-INF/classes", although in our case they are located under "classes".

We could modify the destination of class files (by setting Maven's property ${project.build.outputDirectory}, but I wouldn't really recommend this. Diverging from Maven's conventions makes your project harder to understand for other developers and as rumors go there still are Maven plugins around that directly access "target/classes" instead of referencing said property.

Resource aliases

I studied Jetty's API for a while and came across the concept of resource aliases which provides a neat solution to our problem. To define an alias for "WEB-INF/classes" just do the following:

1
2
3
4
...
WebAppContext wac = new WebAppContext();
wac.setResourceAlias("/WEB-INF/classes/", "/classes/");
...

Unfortunately this only works for single resources. While everything is fine when the folder "WEB-INF/classes" itself is accessed, the alias won't be applied when accessing resources contained within this folder, e.g. "WEB-INF/classes/MyManagedBean.class".

To tackle this problem I extended Jetty's WebAppContext to provide a means of pattern based alias resolution. The only overridden method is getResourceAlias():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class AliasEnhancedWebAppContext extends WebAppContext {

    @Override
    public String getResourceAlias(String alias) {

        @SuppressWarnings("unchecked")
        Map<String, String> resourceAliases = 
            (Map<String, String>) getResourceAliases();

        if (resourceAliases == null) {
            return null;
        }

        for (Entry<String, String> oneAlias : 
            resourceAliases.entrySet()) {

            if (alias.startsWith(oneAlias.getKey())) {
                return alias.replace(
                    oneAlias.getKey(), oneAlias.getValue());
            }
        }

        return null;
    }
}

Whenever an alias for a given resource is looked up, it is checked whether the resource name starts with one of the registered aliases. If that's the case, the aliased part will be replaced. Having registered the alias from the previous listing, e.g. "WEB-INF/classes/MyManagedBean.class" will be aliased with "classes/MyManagedBean.class".

It's certainly not perfect (e.g. nested replacements are not considered), but I would regard it good enough for being used in the scenario at hand. So let's look how all this fits together:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class JettyRunner {

    public static void main(String[] args) throws Exception {

        Server server = new Server();

        Connector connector = new SelectChannelConnector();
        connector.setPort(8080);
        connector.setHost("127.0.0.1");
        server.addConnector(connector);

        WebAppContext wac = new AliasEnhancedWebAppContext();
        wac.setContextPath("/myapp");
        wac.setBaseResource(
            new ResourceCollection(
                new String[] {"./src/main/webapp", "./target"}));
        wac.setResourceAlias("/WEB-INF/classes/", "/classes/");

        server.setHandler(wac);
        server.setStopAtShutdown(true);
        server.start();
        server.join();
    }
}

That way we are finally able to run a JSF application on an embedded Jetty instance, providing us with a very efficient way of development.

EL libraries

Unfortunately my happiness was short lasting as I ran into another problem: Using method parameters within EL expressions gave me parsing errors wrapped into a javax.faces.view.facelets.TagAttributeException.

The reason is that method invocations with parameters are a feature of Unified EL version 2.2 which is leveraged by JSF 2. As others already pointed out, you need to replace the EL JARs (API and impl) of you web container with the new ones.

Using Maven this can be done by adding the following dependencies to your project's pom.xml (if required, add the java.net Maven repository to your settings.xml first):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
<dependency>
    <groupId>javax.el</groupId>
    <artifactId>el-api</artifactId>
    <version>2.2</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.glassfish.web</groupId>
    <artifactId>el-impl</artifactId>
    <version>2.2</version>
    <scope>runtime</scope>
</dependency>
...

When working with the JSF 2 reference implementation Mojarra furthermore the expression factory to be used must be set to the one from the new EL implementation by specifying the following context parameter within your web.xml:

1
2
3
4
5
6
...
<context-param>
  <param-name>com.sun.faces.expressionFactory</param-name>
  <param-value>com.sun.el.ExpressionFactoryImpl</param-value>
</context-param>
...

Having done that, you are now able to use parametrized method invocations within EL expressions, which is especially useful when performing actions on the rows of a data table.

14 comments:

Anonymous said...

Thank for the example. I use Mojarra 2.0.2 and Jetty 7.0.2 in an embedded mode. When i start the JettyRunner i get the following exception:
Application was not properly initialized at startup, could not find Factory: javax.faces.context.FacesContextFactory
Have you any idea?

Unknown said...

That's a bit hard to say without having the actual code at hand. Can you find any more information in your server log? Have you registered the Faces servlet within web.xml? Note, that I used Jetty 6.x for the example, maybe something changed with Jetty 7, haven't tried that out so far.

Anonymous said...

Thank you very much for the tips!

marciowb said...

Thank you for the valorous tips.

Unknown said...

Thanks for your comment. Great to hear you like the post :-)

Henrik said...

Great article. I stumbled upon it, because I have seen this in a Wicket project and was looking for a similar solution for JSF.

Chris Caspanello said...

This is a great tutorial. Can you post your full POM file? I feel like I'm having some dependency version issues.

Célio Vasconcelos said...

I love you so much!

Unknown said...

شركة تسليك مجاري المطبخ بالرياض
شركة تسليك مجاري بالرياض

شركة تسليك مجارى الحمام بالرياض
level تسليك المجاري بالرياض
افضل شركة تنظيف بالرياض
تنظيف شقق بالرياض
شركة تنظيف منازل بالرياض
شركة غسيل خزنات بالرياض
افضل شركة مكافحة حشرات بالرياض
رش مبيدات بالرياض
شركة تخزين عفش بالرياض
شركة تنظيف مجالس بالرياض
تنظيف فلل بالرياض
ابى شركة تنظيف بالرياض

afaf elgamal said...


شركة رش مبيد بالدمام
شركة مكافحة حشرات بالدمام


شركة عزل اسطح بالدمام

شركة تنظيف فلل بالدمام

شركة تنظيف منازل بالدمام
شركة تنظيف موكيت بالدمام
شركة تنظيف مجالس بالدمام
شركة تنظيف بالدمام

شركة نقل اثاث بالدمام

شركة رواد الحرمين said...

بموقع مؤسسة الحرمــين فخدماتنا ليس لها بديل واسعارنا ليس لها مثيل ،ولدينا فريق عمل يتصل مع العملاء على جسور الثقه
شركه تنظيف منازل بالجبيل
والصدق والامانه فى العمل ، وهدفنا هو ارضاؤك وراحتك ، لا تقلق ونحن معك

شركه عزل فوم بالجبيل
لا تجهد نفسك ونحن تحت امرك ورهن اشارتك .
أبرز خدمات مؤسسة الحرمــين للمقاولات العامة بالدمام والرياض

شركه عزل فوم بالدمام


شركه عزل اسطح بالاحساء

شركه كشف تسربات المياه بالاحساء


شركة رواد الحرمين said...

بموقع مؤسسة الحرمــين فخدماتنا ليس لها بديل واسعارنا ليس لها مثيل ،ولدينا فريق عمل يتصل مع العملاء على جسور الثقه
شركه تنظيف منازل بالجبيل
والصدق والامانه فى العمل ، وهدفنا هو ارضاؤك وراحتك ، لا تقلق ونحن معك

شركه عزل فوم بالجبيل
لا تجهد نفسك ونحن تحت امرك ورهن اشارتك .
أبرز خدمات مؤسسة الحرمــين للمقاولات العامة بالدمام والرياض

شركه عزل فوم بالدمام


شركه عزل اسطح بالاحساء

شركه كشف تسربات المياه بالاحساء


cleaning said...

شركة تنظيف بالرياض الفيلا الجديده ؛ من خلال تنظيف و غسيل الموكيت و السجاد و الكليم و إزالة بقع الطعام منها أو التخلص من الاتربه و الغبار في وقت قياسي جدا ، كما خدمة الغسيل شقق فلل مجالس موكيت كنب مساجد خزانات منازل غسيل شقق
غسيل فرشات غسيل فلل

شركة تنظيف مجالس
شركة تنظيف بالرياض توفير كافه الادوات والمعدات اللازمه في عمليه النظافة لحصول العميل علي منزل نظيف خالي من اي اوساخ بعمالة مدربة باسعار مميزة نظافة شقق موكيت سجاد كنب ستائر فلل منازل مجالس واجهات شقق مجالس كنب سجاد خزانات
تنظيف فلل
شركة تنظيف خزانات
شركة تنظيف فلل ومنازل وشقق
تنظيف مجالس فرشات ستائر كنب بالرياض

تقدم خدمات
تعقيم منازل
نظافه عامه وخدمات المنزلى
مكافحة حشرات

حلى بلاط تلميع زوجتك وتلميع اثاث
مكيفات فك وتركيب مكيفات
شركة غسيل خزانات
تعقيم شامل للمنزل
شركة تنظيف بحائل
شركة تنظيف بالرياض

تهتم بتنظيف شقق فلل خيام استراحات

تنظيف مكيفات كنب سجاد

مجالس فرشات مكافحة حشرات

تنظيف عزل خزانات

تعقيم فلل مكاتب شركات جلي بلاط سيراميك

بارخص الاسعار

Anonymous said...

كيف اشتري اسهم في سوق الأسهم اليوم؟