Skip navigation
2013

I have recently finished migrating a photoalbum application from RF 3.3.X to RF 4.X. I will describe the things I needed to update so that you can migrate your applications as well and maybe avoid some hassle I had to deal with. I will cover things that are for the most part not strictly related to RichFaces - as far as updating the RichFaces components goes the two migration guides should be sufficient.

Links

 

Packaging

The original application was split into three packages - EJB, WAR and EAR. This was causing some deployment issues (that might have been entirely on my part) but later I merged all the files into the WAR package and that simplified things a bit and got rid of the issues.

XML configuration files

RichFaces 4 (or rather JSF 2) have deprecated some previous technologies and as result you can get rid of some of the configuration files. You will most likely find these in your project:

FileNote
components.xmlThese are safe to delete (but you might want to check what they were used for).
pages.xml
faces-config.xml

Change of schema version (1.2 -> 2.0):

<faces-config xmlns="http://java.sun.com/xml/ns/javaee" version="2.0" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd">
web.xmlRemove all Seam related components

Beans.xml

This is a new file you might need to create (put it in src/main/webapp/WEB-INF/). It is necessary for CDI to work, however it can be completely empty. Or almost empty with schema definitions:

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:weld="http://jboss.org/schema/weld/beans"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://docs.jboss.org/cdi/beans_1_0.xsd http://jboss.org/schema/weld/beanshttp://jboss.org/schema/weld/beans_1_1.xsd">
</beans>

SEAM and Hibernate annotations

All SEAM annotations have been removed as the current application uses CDI for injection. Hibernate annotations have been updated (the package structure has changed).

SEAM annotations

Old AnnotationNew AnnotationNotes
org.jboss.seam.annotations.@AutoCreate- (removed)Beans are now being auto-created by default
org.jboss.seam.annotations.@Namejavax.inject.@Named
org.jboss.seam.annotations.@Injavax.inject.@Inject
org.jboss.seam.annotations.@Outjavax.enterprise.inject.@ProducesSEAM injection allowed @In and @Out to be paired, however @Inject and @Produces cannot be paired. In such cases @In @Out was changed to @Inject, the application should work even without the @Produces annotation.
org.jboss.seam.annotations.@Createjavax.annotation.@PostConstruct
org.jboss.seam.annotations.@Destroyjavax.annotation.@PreDestroy
org.jboss.seam.annotations.security.@Restrict- (removed)It was removed because it wasn't needed, however restrictions are being done differently now.

Bean scope

Old annotationNew annotationNote
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Scope;

@Scope(ScopeType.CONVERSATION)
class A …
import javax.enterprise.context.ConversationScoped;

@ConversationScoped
class A …
Every ScopeType.SOMETHING has a @SomethingScoped equivalent except for ScopeType.EVENT which I changed to @RequestScoped.

Issue with Bean scope

For the application to work properly it was not possible to keep the original scope for all beans. The beans that dealt with rendering the pages (or parts thereof) did not work if they were only @RequestScoped. For example this concerned beans that take care of dynamically rendered page fragments:

<ui:include src="#{model.mainArea.template}" />

The issue I encountered was that Ajax was not working in these fragments despite working outside of them. The solution was to change the affected beans to @ApplicationScoped (any lesser scope won't work).

 

Validation Constraints

Old AnnotationNew Annotation
org.hibernate.validator.NotNulljavax.validation.constraints.NotNull
org.hibernate.validator.Lengthorg.hibernate.validator.constraints.Length
org.hibernate.validator.NotEmptyorg.hibernate.validator.constraints.NotEmpty
org.hibernate.validator.Emailorg.hibernate.validator.constraints.Email

Annotations from the javax.persistence package (Id, Column, OneToMany, …) remain the same.

Seam 3 Security (access restriction)

NOTE: This isn't implemented in the application because it wasn't needed and because I stopped using Seam (except for Solder), the documentation of Seam Security can be found here. An alternative for this is PicketLink 3.

Seam security uses the class org.jboss.seam.security.Identity for identification. In terms of access restriction the important change between Seam 2 and 3 is in the methods addRole and hasRole:

  • Seam 2 - identity.addRole(String role)
  • Seam 3 - identity.addRole(String role, String group, String groupType)

 

 

When user is authenticated a role can be added to him ("admin") and based on that role we can restrict access:

Old way

import org.jboss.seam.annotations.security.Restrict;

@Restrict("#{s:hasRole('admin')}")
public void addAlbum(Album album) {
   // do something
}

New way

Admin @interface

import org.jboss.seam.security.annotations.SecurityBindingType;

@SecurityBindingType
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface Admin { }

Authorizer method

import org.jboss.seam.security.annotations.Secures;

public class Restrictions {   
    public @Secures @Admin boolean isAdmin(Identity identity) {
        return identity.hasRole("admin", "USERS", "GROUP");
    }
}

the important part is the @Secures annotation, the name of the method and of the class is up to you. The methods will become injection points.

Restrict access

public @Admin void addAlbum(Album album) {
   // do something
}

Programmatic Bean validation

There have been some changes in the way beans are validated programmatically:

Old way

import org.hibernate.validator.ClassValidator;
import org.hibernate.validator.InvalidValue;

ClassValidator<Album> shelfValidator = new ClassValidator<Album>(Album.class);
InvalidValue[] validationMessages = shelfValidator.getInvalidValues(album, "name");
if (validationMessages.length > 0) {
    for(InvalidValue i : validationMessages) {
        // do something
    }
}

New way

import javax.validation.ConstraintViolation
import javax.validation.Validation;
import javax.validation.Validator;

Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Album>> constraintViolations = validator.validate(album);
if (constraintViolations.size() > 0) {
    for (ConstraintViolation<Album> cv : constraintViolations) {
        // do something
    }
}

 

Both InvalidValue and ContraintViolation provide getMessage() method which returns a String so the rest of the functionality is unchanged.

Event handling

In SEAM events were based on Strings. In CDI events are simple classes that can carry values. Detailed explanation of how events work in CDI can be found in the documentation. I will describe the steps I took in short.

1. Strings -> Enum

The Strings that were used to indicate events were changed into enum values:

Old way

New way

public static final String CHECK_USER_EXPIRED_EVENT = "checkUserExpiredEvent";
public static final String ADD_ERROR_EVENT = "addErrorEvent";
public static final String ALBUM_DELETED_EVENT = "albumDeleted";
public static final String ALBUM_EDITED_EVENT = "albumEdited";
…
public enum Events {
CHECK_USER_EXPIRED_EVENT,
ADD_ERROR_EVENT,
ALBUM_DELETED_EVENT,
ALBUM_EDITED_EVENT,
…
}

2. EventType and EventTypeQualifier

These two classes tell the beans what events you want to use. They work with the Events enum from previous step.

EventType

only needs the value() method with return type of the enum

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

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

import javax.inject.Qualifier;

@Qualifier
@Retention(RUNTIME)
@Target({ FIELD,PARAMETER })
public @interface EventType {
    Events value();
}

EventTypeQualifier

Implements the EventType interface

import javax.enterprise.util.AnnotationLiteral;

@SuppressWarnings("all")
public class EventTypeQualifier extends AnnotationLiteral<EventType> implements EventType {
    private Events value;

    public EventTypeQualifier(Events value) {
        this.value = value;
    }

    public Events value() {
        return value;
    }
}

3. Events

I have created several events for special purposes in the photoalbum (all album related events can carry the album etc.), you will need at least one.

public class AlbumEvent {
    private Album album;
    private String path;

    public AlbumEvent(Album album) {
        this.album = album;
    }

    public AlbumEvent(Album album,String path) {
        this.album = album;
        this.path = path;
    }

    public Album getAlbum() {
        return album;
    }

    public String getPath() {
        return path;
    }
}

4. Firing events

Old way

New way

import org.jboss.seam.core.Events;

Events.instance().raiseEvent(Constants.ALBUM_ADDED_EVENT, album);
import org.richfaces.photoalbum.event.AlbumEvent;
import org.richfaces.photoalbum.event.EventType;
import org.richfaces.photoalbum.event.EventTypeQualifier;
import org.richfaces.photoalbum.event.Events;

@Inject
@Any // event of any type
Event<AlbumEvent> albumEvent;

albumEvent.select(new EventTypeQualifier(Events.ALBUM_ADDED_EVENT))
    .fire(new AlbumEvent(album));  
    // select the type of the event before firing

@Inject
@EventType(Events.ALBUM_ADDED_EVENT) // specific type of event
Event<AlbumEvent> albumEvent;

albumEvent.fire(new AlbumEvent(album)); // no need to select

5. Observing events

Old way

New way

import org.jboss.seam.annotations.Observer;

@Observer(Constants.ALBUM_ADDED_EVENT)
public void onAlbumAdded(Album album) {
    // process the event
}
import org.richfaces.photoalbum.event.AlbumEvent;
import org.richfaces.photoalbum.event.EventType;
import org.richfaces.photoalbum.event.Events;

import javax.enterprise.event.Observes;

public void onAlbumAdded(@Observes @EventType(Events.ALBUM_ADDED_EVENT) AlbumEvent ae) {
    // process the event
}

 


 

File upload

The way rich:fileUpload handles the uploading has been slightly changed.

Old class/method

New class/method

Note

org.richfaces.event.UploadEvent

*.FileUploadEvent

Just a name change

uploadEvent.getUploadItem()

fileUploadEvent.getUploadedFile()


org.richfaces.model.UploadItem

*.UploadedFile


uploadItem.getFileName():String

uploadedFile.getInputStream()

In case you used the filename to create an InputStream, otherwise there is a getName():String method.

Faces messages

Another thing that changed is the way of creating messages (which are later handled by rich:message components)

Old way

import org.jboss.seam.faces.FacesMessages;

@In FacesMessages facesMessages;

facesMessages.addToControl(Constants.SHELF_ID, Constants.SHELF_MUST_BE_NOT_NULL_ERROR, new Object[0]);

New way

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

FacesContext.getCurrentInstance().addMessage(Constants.SHELF_ID,
    new FacesMessage(Constants.SHELF_MUST_BE_NOT_NULL_ERROR));

(FacesContext can also be injected)

Changes in view layer

JSTL core namespace

xmlns:c="http://java.sun.com/jstl/core" has changed to "http://java.sun.com/jsp/jstl/core"

(in case you're switching to JSTL 1.2 or higher which you probably are)

Collection size in EL

It used to be possible to call bean.list.size() in EL but this no longer works - the EL resolver looks for a getSize() method which does not exist:

Old way

New way

<html …>
<h:outputText value="#{bean.list.size()}" />
</html>
<html …
xmlns:fn="http://java.sun.com/jsp/jstl/functions">

<h:outputText value="#{fn:length(bean.size)}" />
</html>

Custom skins

In case you were using custom skins you will find they are not working in RF 4. To solve the issue consult my previous blog post about resource optimization.

Unresolved issues

PARTIAL_STATE_SAVING

class java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to com.sun.faces.application.view.StateHolderSaver

I have encountered a - so far not reliably reproducible - issue with PARTIAL_STATE_SAVING being enabled (which is by default). The issue causes the application to stop responding to anything (and only showing the above error in the log, with no further explanation) but can be resolved by reloading the page. The problem seems to be in JSF itself (JSF_1427).

 

PSS can be disabled but a workaround has to be implemented because some RichFaces components will stop working (RF-12222).

 

However, when PSS is disabled drag-and-drop does not work (RF-10967). So in the end I had to enable it again.