Monday, August 22, 2011

Our Seam 2 to JEE 6 replacement list

JBoss Seam 2 and JEE 5

In the last years we had great success with JBoss Seam 2.x. This very innovative framework makes JEE 5 usable. The classic multi-tiered JEE-based Web application primarily consists of JavaServer Faces (JSF) in the presentation layer, Enterprise JavaBeans (EJB) in the business layer and Java Persistence API (JPA) as O/R mapper for the database access.
The biggest problem was the missing link between JSF and EJB / JPA, some kind of binding framework. How can we access business methods in EJBs via Expression Language (EL)? How can we inject Entity data into the presentation layer and automatically validate input? Yes it's possible, but the classic JSF Managed Beans where really painful to use. JBoss Seam added this binding layer in form of an annotation based Dependency Injection (DI) framework. The addition of Web-relevant Contexts like Event (Request), Conversation, Session etc., the navigation and EL extensions, the Bean validation and much more resulted in a very powerful framework that just felt right.

JEE 6!

Many of this Seam-Core elements where standardized and integrated into JEE 6. The Context and Dependency Injection (CDI) is heavily influenced through Seam (and learned from past mistakes). Numerous JSF and JPA extensions have a counterpart in JBoss Seam 2.
Recently we switched for an ongoing project from JEE 5 / Seam 2 to JEE 6. The main reasons where security issues with older Application servers, performance issues with Seam and also functional aspects like validation groups in JSF. Even though I did know about the common roots of Seam 2 ideas and many JEE 6 innovations I was amazed how smooth the transition was.
Many things only needed minor adjustments. E.g. the JPA Enties should use the new Bean validation annotations (mostly only change the package name). We exchanged some Seam- against new JSF-components. Main work: The navigation rules must be reconfigured.

A (not exhaustive) replacement list...


Often there is really more to it than just a syntax replacement, e.g. @In and @Inject differ heavily. But on the surface you can start this way. Sry for the terrible Copy & Paste HTML:

JEE 5 / Seam 2 JEE 6
xmlns:c="http://java.sun.com/jstl/core" xmlns:c="http://java.sun.com/jsp/jstl/core"

(avoid JSTL in JSF, but sometimes interesting out of performance reasons if you know what you are doing)
xmlns:s="http://jboss.com/products/seam/taglib" (delete namespace 's', nearly for all of Seams JSF-Components now standard alternatives in JSF 2, see below)
xmlns:a="http://richfaces.org/a4j"

<a:support event="onchange" reRender="filter" />
(delete namespace 'a')


 xmlns:f="http://java.sun.com/jsf/core"

<f:ajax render=":filter"/>

(event-attribute isn't necessary in this case, the components default change-event is used; use <h:head> to render the proper jsf.js link)
<h:selectOneMenu value="#{...}">
  <s:selectItems label="#{_i.name}"
    value="#{userFilter.allOrgs}" var="_i"  noSelectionLabel="choose..." hideNoSelectionLabel="true"/>
</h:selectOneMenu>
<h:selectOneMenu value="#{...}" hideNoSelectionOption="true">
  <f:selectItem noSelectionOption="true" itemLabel="choose..." />
  <f:selectItems itemLabel="#{i_.name}"
    value="#{userFilter.allOrgs}" var="_i" />
</h:selectOneMenu>
<s:link includePageParams="false" 
    view="edit.xhtml" value="Edit"> 
    <f:param name="id" value="#{_user.id}" />
</s:link>
<h:link includeViewParams="false" 
    outcome="edit" value="Edit"> 
    <f:param name="id" value="#{_user.id}" />
</h:link>
<s:selectItems
    label
="#{messages[_action.name()]}" ... 
<s:convertEnum />
<f:selectItems
    itemLabel="#{messages[_action]}" ...

(delete s:convertEnum, global default converter for single enum values)

<template>.page.xml or pages.xml:

<param name="id" value="#{userHome.id}" />
<action execute="#{userHome.wire}" />

insert into <template>.xhtml:

<f:metadata>
    <f:viewParam name="id" value="#{userHome.id}" />
    <f:event type="preRenderView" listener="#{userHome.init()}" />
    <f:viewAction action="userHome.init()" /> (just since JSF 2.2)
</f:metadata>



import org.hibernate.annotations.Cache;
import org.hibernate.annotations
    .CacheConcurrencyStrategy;
@Entity
@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL)

public class User extends BaseEntity {

import javax.persistence.Cacheable;
@Entity
@Cacheable
public class User extends BaseEntity {

(additionally add in persistence.xml: <shared-cache-mode>ENABLE_SELECTIVE </shared-cache-mode>)



import org.hibernate.validator.Length;
import org.hibernate.validator.NotEmpty;
@Length(max = 50)
@NotEmpty
private String name;

import javax.validation.constraints.NotNull;

import javax.validation.constraints.Size;
@NotNull
@Size(max = 50, min = 1)
private String name;

import org.jboss.seam.framework.EntityQuery;
@Name(userList")
public class UserList extends EntityQuery<User> {
(Seam 2 style list controller from seam-gen: don't use them, even with Seam 2)

import org.jboss.seam.annotations.In;
@In
private UserHome userHome;

@Out(required = false)
private User user;

@Factory(autoCreate = true, scope = ScopeType.APPLICATION)
public Map<String, String> getProps() {
    return PROPERTY_MAP;
}


import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces; 
import javax.inject.Inject;

@Inject
private UserHome userHome;

@Produces
@Named
@ApplicationScoped
public Map<String, String> getProps() {
    return PROPERTY_MAP;
}

@DataModel
private List<Veranstaltungstyp> userList;
... 
@DataModelSelection
private User selectedUser;

e.g. via @Produces, no "Outject" available:

private ListDataModel<User> userList;
...
User selectedUser = this.userList.getRowData();

@Name("userService")
@AutoCreate
@Stateless
@Local(UserService.class)
public class UserServiceBean implements UserService {
@Stateless
public class UserService {

(@Named only necessary if we use this in EL, all Stateful / Stateless / POJO Beans are automatically CDI Beans, default @Inject access via Type & optional Annotations, not via Named)
final UserService userService = (UserService) Component.getInstance("userService");
final UserService userService = CdiUtil.getSingleton(UserService.class);

(was often necessary in Seam 2 out of performance reasons with @BypassInterceptors and in many none-Seam-Components, now only in Filter/Entity if really necessary, helper class for static BeanManager e.g.: https://issues.jboss.org/secure/attachmentzip/unzip/12429820/12341024%5B26%5D/swe2Web/src/de/swe2/util/CdiUtil.java)

@Logger 
private static Log log;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger LOG =
    LoggerFactory.getLogger(
        UserService.
class);

(in Seam 3 also possible "@Inject jboss.Logger log" but we really favor static access here, don't overdo injections)

@In
private FacesMessages facesMessages;

facesMessages.add(
    Severity.INFO,
    "Info info #{el.info} info.");

FacesContext.getCurrentInstance()
    .addMessage(

        "
<componentId>" (or null),
    new FacesMessage(
        FacesMessage.
SEVERITY_INFO,
        "
Info info info."));

(in Seam 3 also possible "@Inject Msg msg" but this cannot evaluate embedded EL yet and we favor own static wrapper here, like:)

FacesMessages.addInfo(
    "<componentId>", 
    "Info info info.");

#{identity.login}

@In
private Identity identity;

@Name("authenticator")
public class Authenticator {
    @In
    private Credentials credentials;
    @Override
    public boolean authenticate() {

with Seam 3 Security:

#{identity.login}

@Inject
private Identity identity;

@Named
@Stateless
public class Authenticator {
    @Inject 
    private Credentials credentials;
    @Override
    public void authenticate() {
<exception class="java.lang.IllegalStateException" log="false">
    <end-conversation />
    <redirect view-id="/error.xhtml" />
</exception>
see follow up post JSF 2 - Global Exception Handling


 

Seam 3?

I started with checking out the Seam 3 Booking example project (beware of PrettyFaces 3.2.0 in this example, it adds 500 ms CPU time to each request in development mode). I stripped the project down and build a new empty project template for us. During the conversion from Seam 2 to the new technology stack I noticed that we don't really need Seam anymore. There are some useful modules and ideas in Seam 3 but they are really optional and not a "must" like in JEE 5 days. Another interesting observation was that these extensions very often consists only of a few lines of code - CDI is _very_ powerful! The Seam Security Annotation tricks are amazing!

If Seam is a selling point for JBoss then I would say from a business perspective the standardization of Seam core was like shooting themselves in the foot. If not - congratulations for the great work!

Update 29.09.11

I kind of expected something like that: http://planet.jboss.org/post/so_what_s_happening_with_seam

Spring as CDI alternative?

Some people might say I should use Spring as binding layer between JSF and JPA but I really don't like Spring in the JEE environment. It's like deploying Glassfish into JBoss AS because I like Grizzly. IMHO this are two heavy overlapping technology worlds. Decide for one or the other but only mix if it's really really necessary. It's OK to use Spring inside JEE for something like a great embedded Spring-based Process Engine but really not for something like 50 code lines less for a JNDI wrapper or some "I didn't want to learn CDI" developer.

17 comments:

  1. You are right. Seam3 isn't needed because it's very very buggy and lacks in view of innovation and performance or they are just half baked. Things look nice on slides but badly fail in reality.

    Looks like you have a pretty simple application. We tried to stick with pure Java EE6 but it's just not possible with a lot of use-cases. There are several reasons. The first you see after few days is the broken conversation scope which is just an unusable subset of Seam2 conversations. Finally there is currently no way around MyFaces CODI. As I found out recently others saw the same or similar issues. One of many links: http://pragmatic-jee.blogspot.com/2011/08/java-ee-all-you-need.html You can find others in different forums. After our first project, I made a research, if it's possible to find small workarounds to get rid of as many libs as possible. We could drop a lot of them, but there was just no way to get rid of MyFaces CODI. We haven't had problems with it and it's very stable and fast, so we made an exception and we continued to use it. The productivity also increased a lot and now it's just unquestionable for new Java EE projects.

    ReplyDelete
  2. Thank you for your comment.
    Our app is nothing like simple...and this list is far from complete and should only give an impression. I know CODI and e.g. the more complete Conversations but the CDI rules apply there too - many extensions are really only a few lines of code. We get ideas from CODI and Seam 3 and just implement them in the project with our adjustments. In Seam 3 is especially annoying (in 3.1.Beta even more) that you need 5 libs if you only need one feature. E.g. Seam Security with SimpleUser -> all of PicketLink -> Seam Persistence/Transaction/Faces etc...woha.
    One thing we learned from past projects is, we will use Conversations _less_, especially less then suggested in Seam 2. But this is another article ;)
    May be we will consider CODI again, after all it's much leaner. In each case - we owe the Seam guys a lot!

    ReplyDelete
  3. Thanks for sharing this posting.Very good review.web hosting reviews

    ReplyDelete
  4. Does that mean you are using the session scope or do you pass around IDs and all changes via URL params or hidden fields and reload everything again and again?

    ReplyDelete
  5. reloading sounds terrible, right? :) IMHO not so much with properly configured 2nd level memory grid cache. beans in conversations eat (session-)memory too...and often not intended by developer joe-xy.

    dragging unsaved entities (classic wizard example) through multiple editing pages in a conversation is also very questionable. this merge/transaction would never work in most systems ;) customers don't like this too: hey, I was at lunch and my data...my data!

    we avoid embedded conversations and such stuff. we have enough conceptual (not technical) problems to trigger where conversations start and end as is and don't need this feature. it's really difficult to trigger an conversation-end with the many possible click paths in modern web apps.

    we use simple CDI conversation to carry data to sub views, not hidden fields (brrr) or view params (only if bookmarkable). grouped conversations (CODI) might be interesting but our views and controllers beans are often 1:1.

    we avoid entities and extended persistence contexts in combination with conversations - our lessons learnd.
    sessions are ok if you don't use wizards parallel (often even a requirement). timeouts are longer then conversation but on the other side there is only one ;)

    goes to far here - might really be a seperate post topic.

    best regards,
    André

    ReplyDelete
  6. I agree with you. The session is no option, because it causes major problems with multiple tabs. Hidden fields and URL params are in most cases also no nice option. It's as ugly as the structure of PHP applications. So it's nice to see that you aren't using those workarounds.

    Starting and ending std. CDI conversations is as broken as the whole (way too simple) conversation concept of CDI.

    It looks like we have similar requirements. We also have one controller per view. Therefore, the parallel conversations are a perfect fit! We are using conversation scoped beans and if you need to transfer information between controllers (and it isn't a wizard which would have a @ViewAccessScoped wizard controller -> no timeout at lunch-time ;-) ) we just inject one controller into an other controller. As soon as the information is transferred, you can even close the conversation of the "old" controller which immediately frees all memory and doesn't hang around until the end of the request (also a major drawback of std. conversations in a lot of use-cases). -> Parallel conversations of CODI are just awesome and way more flexible! E.g. you can get the same result as with other scopes: If you need a bean just for rendering, you can use @PostRenderView for closing it. That's similar to the @RenderScoped of Seam3, but you don't need it very often (again you don't need Seam3 for it) because you need your beans at the next request(s) to this page.

    Timeouts/Lunch-time ;-)
    We have just long timeout settings, because editing data by different users don't collide very often due to the nature of our applications and workflows. Otherwise we would have shorter timeouts, but they would be still way longer than your allowed time for lunch ;-)
    We have 6 hours for the session timeout 4 hours for the window-context timeout and 3 hours for the conversation timeout and with thousands of users per hour we don't have any memory issues even with a medium sized server, because we have most of the state in CODI conversations which allow a fine grained memory management and some state like the current user in the window scope and almost no state in the plain session scope.

    As I saw yesterday, CODI even got sub-groups for closing just a well-defined part of a group. We don't need it, because the parallel conversations are just enough for us, but maybe it's a missing link for someone's use-cases.

    Looks like we have similar requirements, so I'm wondering if there is something in your application which can't be solved with the nice scopes provided by CODI? At least we didn't face a problematic use-case we would need.

    As I see the community also reacts fast in case of new features. So I would give it a try as soon as possible because it really changes the game and I like such innovative and stable projects a lot, because we get paid to implement an application and not for finding workarounds.

    ReplyDelete
  7. Thxs again for sharing your experiences. I rechecked the CODI parallel conversation features and I will consider this. After all it's easy to switch to the more powerfull solution and we have some cases where it might be helpful.

    After all the objective was not to use no libraries at all but we need much less with JEE 6. I'm happy that I don't need a 15 MB EAR anymore to write a (may be not so simple) CRUD webapp.

    ReplyDelete
  8. Yes! Java EE6 is much better than Java EE5 (and with CODI it's awesome).

    ReplyDelete
  9. Hi André
    Great article. I think you'd do really well on DZone's MVB program. Check out dzone.com/aboutmvb for more details, and if you're interested, send me a mail to james at dzone dot com

    Thanks
    James

    ReplyDelete
  10. thats superb article for JEE enthusiasts like us

    ReplyDelete
  11. I kind of expected something like that: http://planet.jboss.org/post/so_what_s_happening_with_seam

    ReplyDelete
  12. Great article! I'm starting to work on migrating a Seam 2 app to Java EE 6, and this is a great starting point.

    Another item for the replacement list: the s:decorate tag. It's technically gone, but you can use Seam Faces's UIInputContainer to do the same thing. More info here: http://jerryorr.blogspot.com/2011/10/replacement-for-sdecorate-in-seam-3.html

    ReplyDelete
  13. thank you...thats a nice hint. i planned to write a follow up post but was @holiday and lots of work now. we use more special custom components like inputTextEdit. your more generic solution is something peope should consider. nevertheless...may be soon i can put an article together about our solution :)

    ReplyDelete
  14. I thought it was going to be some boring old post, but it really compensated for my time. I will post a link to this page on my blog. I am sure my visitors will find that very useful.
    Cbse Entrancei

    ReplyDelete
  15. Nice article. I was trying to migrate the Seam 2 to JEE6. I am not able to find the equalent to and many of the other attributes of pages.xml like ..Any idea on those let me know

    ReplyDelete
    Replies
    1. Attributes of pages.xml like...? And then it ends ;)
      Do you have concrete examples? Not all Seam 2 features are build into JEE 6, just the most importent DI core and concepts.
      We have for all things solutions, e.g. see the other article for even better exception handling then Seam 2 - often just a few lines in JEE 6, but you have to give concrete examples what you are missing.
      This list here is just whats possible quick and dirty 1:1.

      I'd love to describe more of our solutions - but my motivation / time for writing this stuff is divided with many other things *g*

      Delete
  16. whate about seam remoting how migrate it ?

    ReplyDelete