ADF: Leaving the page with unsaved data

The concept of ADF has an interesting feature: the responsibility for tracking uncommitted data lies entirely on the developer. It’s hard to say how this was supposed to be handled by undertrained students, who are the target audience for the declarative programming approach in ADF. But that’s not the point here. The discussion will focus on how to leave a page that has created a new record in a VO associated with an EO, without committing that record, and then successfully return to it.

What immediately comes to mind:
@PostConstruct
    public void initBean() {
        AppModuleImpl mod = (AppModuleImpl)ADFUtils.getBindingApplicationModule();
        DBTransaction conn = mod.getDBTransaction();
        if (conn.isDirty()) conn.rollback();
        ...
This is problematic because initBean() will be called on every page activity, and the transaction will be lost when it’s not supposed to be. Moreover, the transaction will be rolled back during the prepareRender phase, which is also ideologically incorrect. That is, the context has already been initialized, the data model has been loaded, validations have been checked, everything is ready for rendering the page — and at that point, we tell the system that the data needs to be changed. I don’t know exactly what will happen deep inside the application, but I assume nothing good.

The working solution involves creating an application-level bean inherited from PagePhaseListener, where we analyze the page during an early phase of the life cycle and track dirty (unsaved) data. We create the bean and register it in adf-settings.xml:
Java class:
import java.util.ArrayList;
    import java.util.Map;
    import javax.faces.context.FacesContext;

    import javax.servlet.http.HttpServletRequest;
   
    import oracle.adf.controller.v2.lifecycle.PagePhaseEvent;
    import oracle.adf.controller.v2.lifecycle.PagePhaseListener;

    import oracle.adf.share.ADFContext;
   
    import oracle.jbo.server.DBTransaction;
   
    import *.ADFUtils;
    import *.AppModuleImpl;

public class PageListenerBean implements PagePhaseListener{
   
    // if conn.isDirty() and go to url from the list, then rollback
    private static String[] deniedUrls = {"/myapp/faces/task-flow-definition/tree"};
   
    @Override
    public void beforePhase(PagePhaseEvent pagePhaseEvent) {
            try{
                    if (pagePhaseEvent.getPhaseId() == 9){
                        AppModuleImpl mod = (AppModuleImpl)ADFUtils.getBindingApplicationModule();
                        DBTransaction conn = mod.getDBTransaction();
                        if (isPageChanged()){
                            if (changedToDisallowed()){
                                if (conn.isDirty()){
                                    conn.rollback();
                                    System.out.println("= rolled back!");
                                }
                            }
                        }
                     }
            }catch(Exception e){
                ;          
            }
    }
   
    @Override
    public void afterPhase(PagePhaseEvent pagePhaseEvent) {
        ;       
    }
   
    private void setApplicationScopeParameter(String val){
        ADFContext adfCtx =  ADFContext.getCurrent();
        Map applicationScope = adfCtx.getApplicationScope();
        applicationScope.put("currentURL", val);
    }
   
    private String getApplicationScopeParameter(){
        ADFContext adfCtx =  ADFContext.getCurrent();
        Map applicationScope   = adfCtx.getApplicationScope();
        return (String)applicationScope.get("currentURL");
    }
   
    private boolean isPageChanged(){
        try{
            if (!getCurrentUrl().equals(getApplicationScopeParameter())){
                setApplicationScopeParameter(getCurrentUrl());
                return true;
            }
        }catch(NullPointerException e){
            System.out.println("--! PageListenerBean: can't get current url");
        }
        return false;
    }
   
    private boolean changedToDisallowed(){
        for(String url : this.deniedUrls)
            if (url.equals(getCurrentUrl()))
                return true;
        return false;     
    }
   
    private String getCurrentUrl(){
        FacesContext ctx = FacesContext.getCurrentInstance();
        HttpServletRequest servletRequest = (HttpServletRequest) ctx.getExternalContext().getRequest();
        return servletRequest.getRequestURI();
    }
}
At each trigger, the current URL is checked against the variable #{applicationScope.currentURL}. If the page is different, is in the "stop-list," and the data is dirty (unsaved), then the transaction is rolled back. The stop-list is introduced to avoid unexpected transaction loss during unified commits across multiple pages (which can occur in the application).

Code 9 refers to the Restore View initialization phase ID. It’s interesting to trace the lifecycle of the source page (the page we are navigating from) after data modification:

---

Note: The Restore View phase is the first phase in the JSF lifecycle, where the component tree is restored for the current request. This ensures that any ongoing transactions or changes are properly handled before further processing occurs.
On the target page (the one we are navigating to), dirty (unsaved) data persists throughout the entire lifecycle:
ceo@thomsonslegacy.com

We customize Oracle e-Business Suite R12 modules with Oracle Applications Framework and providing Oracle ADF based solutions


© Thomsonslegacy.com, 2025.
If you publish the materials of the site, the reference to the source is obligatory. The site uses cookies.