after several questions about it i decided to implement a codi-add-on which can be used with jsf1.2 as well as with jsf2+. due to the powerful spi of codi, the result is a simple implementation which offers additional features like grouped beans (you can manually destroy beans of the same group immediately even though you stay on the same page), optional bean events,... .
it's e.g. problematic for some use-cases that the jsf2+ implementation of the view-scope stores the beans in the component tree. so several users ask for a session based view-scope. that means the beans for a page will be stored in the session instead of the component tree. if you really need such a scope instead of the view-access-scope provided by codi, you can implement such a scope with the spi of codi + two simple classes.
approach #1
the first one is a marker annotation and the second one an observer for the PreViewConfigNavigateEvent. that's it! if you only use the type-safe navigation of codi, you are done and you annotate your bean with @View and the @ConversationScoped annotation provided by codi. as soon as a navigation to a new page is detected all conversation scoped beans annotated with @View will be destroyed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@ApplicationScoped | |
public class SimulatedViewScopeObserver | |
{ | |
protected void observeNavigation( | |
@Observes PreViewConfigNavigateEvent preViewConfigNavigateEvent, | |
EditableWindowContext editableWindowContext) | |
{ | |
if (!preViewConfigNavigateEvent.getFromView() | |
.equals(preViewConfigNavigateEvent.getToView())) | |
{ | |
for(Map.Entry<ConversationKey, EditableConversation> conversation : | |
editableWindowContext.getConversations().entrySet()) | |
{ | |
if(ConversationScoped.class | |
.isAssignableFrom(conversation.getKey().getScope())) | |
{ | |
if(conversation.getKey().getConversationGroup() | |
.isAnnotationPresent(View.class)) | |
{ | |
conversation.getValue().close(); | |
} | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Target({TYPE}) | |
@Retention(RUNTIME) | |
@Documented | |
public @interface View | |
{ | |
} |
if you don't use the type-safe navigation of codi, you can implement e.g. a custom jsf navigation handler which does the same for all navigations.
if you prefer a single annotation instead (let's call it @MyViewScoped), you can write a portable cdi extension which adds the @ConversationScoped dynamically to all beans which are annotated with @MyViewScoped.
it took longer to write this blog entry, than implementing this mechanism :)
or
approach #2
the result of this approach is the same like approach #1. the advantage is that you don't have to care about the navigation and the disadvantage is that you have to extend a default implementation.
if you are ok with extending the default implementation (with weld you have to #veto the default implementation instead of using @Specializes), you can override the default implementation of #createConversation (a method which is part of the spi of codi). if you just implement the interface, you would override everything and you would deactivate all codi scopes. ok - so let's add a new scope with the same marker annotation and a ConversationFactory instead of the observer:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ViewConversationExpirationEvaluator | |
implements ConversationExpirationEvaluator | |
{ | |
private static final long serialVersionUID = -1298186767354202960L; | |
private String viewId; | |
private boolean expired; | |
ViewConversationExpirationEvaluator() | |
{ | |
if(FacesContext.getCurrentInstance().getViewRoot() != null) | |
{ | |
this.viewId = FacesContext.getCurrentInstance() | |
.getViewRoot().getViewId(); | |
} | |
} | |
public boolean isExpired() | |
{ | |
return this.viewId == null || | |
!this.viewId.equals(FacesContext.getCurrentInstance() | |
.getViewRoot().getViewId()); | |
} | |
public void touch() | |
{ | |
if(this.viewId == null && !this.expired) | |
{ | |
this.viewId = FacesContext.getCurrentInstance() | |
.getViewRoot().getViewId(); | |
} | |
} | |
public void expire() | |
{ | |
this.viewId = null; | |
this.expired = true; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Target({TYPE}) | |
@Retention(RUNTIME) | |
@Documented | |
public @interface ViewConversationScoped | |
{ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Specializes | |
public class ViewScopeAwareConversationFactory extends JsfAwareConversationFactory | |
{ | |
private static final long serialVersionUID = 8809629790207239230L; | |
@Inject | |
private BeanManager beanManager; | |
@Override | |
public EditableConversation createConversation( | |
ConversationKey conversationKey, ConversationConfig configuration) | |
{ | |
if(conversationKey.getConversationGroup() | |
.isAnnotationPresent(ViewConversationScoped.class)) | |
{ | |
return new DefaultConversation(conversationKey, | |
new ViewConversationExpirationEvaluator(), | |
configuration, | |
this.beanManager); | |
} | |
return super.createConversation(conversationKey, configuration); | |
} | |
} |
this implementation is compatible with all jsf concepts which are supported by codi!
approach #2.1
this approach is the same like #2 but instead of implementing it on our own, we can just use the @ViewConversationScoped provided by the enhanced-conversations add-on for codi:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.os890.codi.addon.conversation.api.ViewConversationScoped; | |
import javax.annotation.PostConstruct; | |
import javax.inject.Named; | |
import java.io.Serializable; | |
import java.util.Date; | |
@Named | |
@ViewConversationScoped | |
public class ViewConversationBean implements Serializable | |
{ | |
private static final long serialVersionUID = 609266293914288287L; | |
private Date createdAt; | |
@PostConstruct | |
protected void init() | |
{ | |
this.createdAt = new Date(); | |
} | |
public Date getCreatedAt() | |
{ | |
return createdAt; | |
} | |
} |