ADF provides strong transaction handling mechanisms for database based applications which are backed by entity and view objects. But what if you have a application which does not use database ? How do you then detect changes to your data ?.  Let’s say you have a web service backed programmatic view objects and you have extended your application to work without a database and user saves some data and clicks on update record ? How would you then verify a) whether the data has changed b) handle commits and rollback c) warn user of uncommitted data changes

In this post i will share the solution to above problems.

1) Detecting changes: If you are using data controls and bindings then you can detect changes when the user submits the data by using the following snippet of code. Note that doing this is very relevant as you do not want to call the backend web service or execute some update code where no action is required.

DCBindingContainer dcBindingContainer=(DCBindingContainer)
BindingContext.getCurrent().getCurrentBindingsEntry();
if(dcBindingContainer.getDataControl().isTransactionModified()){
// Handle update calls to your service here
}
else{
//well nothing to do here maybe notify the user of the same
JSFUtils.addFacesInformationMessage("No change detected");
}


2. Handling Commit or Rollback: Now to handle commits or rollback you can use the following code snippet also note that you must rollback or commit the transaction manually.



//To commit the transaction
dcBindingContainer.getDataControl().commitTransaction();
//To rollback the transaction
dcBindingContainer.getDataControl().rollbackTransaction();


3. Implementing Uncommitted Data Changes warning: To prevent navigation between regions you can implement your own logic on a similar lines as shown in the following code snippet. The backing bean snippet is shown below followed by the test page code.



public class NavBacking implements Serializable {
private static final long serialVersionUID = 1L;
//this will hold the value of task flow id being passed in the setter
private String newTaskFlowId;
//this is where the value of task flow id is picked from
private String taskFlowId

public void setTaskFlowId(String taskFlowId) {
//here we will override the setter to set the taskflow value into our own variable for the time being
this.newTaskFlowId=taskFlowId;

}
/**
* This method is used to check whether the transaction is dirty or not
* and then it launches a popup to confirm from user whether he wants to
* discard the changes or not
* @return
*/
public String checkChanges() {
DCBindingContainer dcBindingContainer=(DCBindingContainer)
BindingContext.getCurrent().getCurrentBindingsEntry();
if(dcBindingContainer.getDataControl().isTransactionModified()){
/**
*check placed here to confirm whether the oldtaskflow id and new taskflow id are same
*and if they are it allows the change
*
*/

if(!taskFlowId.equals(newTaskFlowId)){
FacesContext context = FacesContext.getCurrentInstance();
ExtendedRenderKitService erks =
Service.getRenderKitService(context, ExtendedRenderKitService.class);
//show popup
erks.addScript(context,"AdfPage.PAGE.findComponent('"+popupId +"').show();");
}
}
else{
this.taskFlowId=newTaskFlowId;
}

return null;
}
/**
* If the user insists on discarding changes rollback the transaction
* @return
*/
public String okAction() {
DCBindingContainer dcBindingContainer=(DCBindingContainer)
BindingContext.getCurrent().getCurrentBindingsEntry();
dcBindingContainer.getDataControl().rollbackTransaction();
this.taskFlowId=newTaskFlowId;
return null;
}
}

//Unbounded taskflow page which is used to call the bounded taskflow

<af:popup id="pp3" animate="default" childCreation="deferred"  clientComponent="true">
<af:dialog id="dg1" closeIconVisible="false" type="none"
title="Uncommitted Data Warning" >
<f:facet name="buttonBar">
<af:toolbar id="tb1">
<af:commandButton id="cb1"
text="OK" immediate="true" action="#{viewScope.NavBacking.okAction}"/>
<af:commandButton id="cb2"
text="CANCEL" immediate="true" action=" "/>
</af:toolbar>
</f:facet>
<af:inputText id="opt1" readOnly="true" wrap="hard"
value="You have made some changes are you sure you want to continue"/>
<af:spacer id="sp2" height="5"/>
</af:dialog>
</af:popup>
// now you have to ensure that each command link enforces a call to checkChanges

<af:commandLink text="pageName"
visible="true" immediate="true"
action="#{viewScope.NavBacking.checkChanges}"
id="cl2">
<af:setPropertyListener type="action"
from="taskflowIdGoesHere"
to="#{viewScope.NavBacking.taskFlowId}"/>
</af:commandLink>

Note: For this code to work your bounded taskflow must share the datacontrol with the calling unbounded taskflow.

Screenshots:-







Links and references:-



Checking Dirty Data by Jobinesh