Migration to RF 4, CDI and Java 6
Posted by michpetrov in Michal Petrov's Blog on Mar 27, 2013 10:59:13 AMI 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
- Original photoalbum repository (JBoss's SVN)
- Current photoalbum repository (RichFaces' GitHub)
- Online photoalbum demo
- RF3-RF4 Migration guide
- Migration guide - unleashed (contains some things that aren't covered in the original guide)
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:
File | Note |
components.xml | These 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.xml | Remove 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 Annotation | New Annotation | Notes |
org.jboss.seam.annotations.@AutoCreate | - (removed) | Beans are now being auto-created by default |
org.jboss.seam.annotations.@Name | javax.inject.@Named | |
org.jboss.seam.annotations.@In | javax.inject.@Inject | |
org.jboss.seam.annotations.@Out | javax.enterprise.inject.@Produces | SEAM 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.@Create | javax.annotation.@PostConstruct | |
org.jboss.seam.annotations.@Destroy | javax.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 annotation | New annotation | Note |
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 Annotation | New Annotation |
org.hibernate.validator.NotNull | javax.validation.constraints.NotNull |
org.hibernate.validator.Length | org.hibernate.validator.constraints.Length |
org.hibernate.validator.NotEmpty | org.hibernate.validator.constraints.NotEmpty |
org.hibernate.validator.Email | org.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.
Comments