Don't persist UIComponent state, now with attributes!

In this blogpost I showed how to disable the persistence of a UIComponent, but it was needed to write Java code to check for the specific component and change. In this blog entry I will show a more generic way to do this, with the attribute tag.

On the showDetailItem we define an attribute with a name and value:

        
        
     

The Java code for the addComponentChange method in the PortalComposerChangeManager now looks like this:
  public void addComponentChange(FacesContext context, UIComponent component,
                                 ComponentChange change)
  {
    Map attrs = component.getAttributes();
    String value = (String) attrs.get(NON_PERSIST_STRING);

    if (value != null)
    {
      logger.fine("NonPersist attribute found, we don't persist!");
      return;
    }
    super.addComponentChange(context, component, change);
  }

I defined a static final NON_PERSIST_STRING with the value "nonPersist", like the name of the attribute. If the attribute is found, we don't persist the componentChange.
If you want a more specific approach, you can still check for a specific ComponentChange, in the case of this example, we still check if the ComponentChange matches the value in the attribute (disclosed):
  public void addComponentChange(FacesContext context, UIComponent component,
                                  ComponentChange change)
  {
    Map attrs = component.getAttributes();
    String value = (String) attrs.get(NON_PERSIST_STRING);

    if (value != null)
    {
      if (change instanceof AttributeComponentChange &&
          value.equals(((AttributeComponentChange) change).getAttributeName()))
      { //we check if the attribute value equals the change.
        logger.fine("{0} attribute found; {1} event on {2}, we don’t persist.", new Object[]
            { NON_PERSIST_STRING, value, component.getClass() });
        return;
      }
    }
    super.addComponentChange(context, component, change);
  }

This code can be expanded or changed to check for specific changes according to your needs.
I uploaded a new workspace here, so you can download and browse the source if you want to.

Support for multiple session timeouts


Our use case was that we needed to support different timeouts for different application roles. This because our internal users are using the same application as our external users. There is no default support for this behavior in ADF, but you can achieve this by using a PagePhaseListener.

In our production application we used different settings, but for this example I configured the following web.xml parameters:
 
    3
  
  
    oracle.adf.view.rich.sessionHandling.WARNING_BEFORE_TIMEOUT
    60
  
The session-timeout is set to 3 minutes and we want to show the default ADF warning popup 1 minute before the session expires. In case of a internal user login, the session-timeout parameter get overridden.
This is achieved by creating a PagePhaseListener:
public class PortalPhaseListener implements PagePhaseListener
{
  private static final int INTERNAL_TIMEOUT = 360;

  public void beforePhase(PagePhaseEvent pagePhaseEvent)
  {
    if (pagePhaseEvent.getPhaseId() == JSFLifecycle.JSF_RESTORE_VIEW_ID)
    {
      final ExternalContext ectx = FacesContext.getCurrentInstance().getExternalContext();
      final SecurityContext secCtx = ADFContext.getCurrent().getSecurityContext();     

      if (secCtx.isAuthenticated() && isInternalUser())
      {
        final HttpServletRequest httpServletRequest = (HttpServletRequest) ectx.getRequest();        
        httpServletRequest.getSession().setMaxInactiveInterval(INTERNAL_TIMEOUT);
      }
    }
  }
}
I decided to do this in the restore view phase, first we check if the user is logged on and if the user has the internal role. If both are true, we set the MaxInactiveInterval on the HttpSession.
Now the timeout becomes 6 minutes, meaning that after 5 minutes the default ADF popup will show, informing the user that the session will expire if no activity is shown within the next minute.

The result is a different session timeout for different user roles, you can do this for as many roles as you like, making your timeout settings more flexible. 

Don’t persist UIComponent state in the session


Our use case was as followed: In our WebCenter Portal application we have a tableform layout, with a panelAccordion on the form layout. For other functionality we needed MDS persistence to be on, so we couldn’t disable MDS customizations. A side effect was that the panelAccordion remembered the showDetailItem that was disclosed and reopened the form page on the previously disclosed tab. 

We have the Seeded Customizations enabled, you can do this through the project properties:
 This results in a web.xml entry:
  
    org.apache.myfaces.trinidad.CHANGE_PERSISTENCE
    oracle.adf.view.page.editor.change.ComposerChangeManager
  
Now we can configure our adf-config file to exclude Persist Changes:

However, this doesn’t help you for your current session. We can see in the JavaDoc from the FilteredPersistenceChangeManager, the following documentation:
If the DocumentChange fails through the restriction, the corresponding ComponentChange is persisted to the Session.

We also don’t want to persist the changes in the session, so we had to subclass the ComposerChangeManager and override the addComponentChange method:
public class PortalComposerChangeManager extends ComposerChangeManager
{
  /**
   * adf-config.xml still persist in session, @see FilteredPersistenceChangeManager
   * We don't want this for the showDetailItem in the RichPanaelAccordion.
   *
   * @param context the FacesContext.
   * @param component the UIComponent.
   * @param change the ComponentChange.
   */
  @Override
  public void addComponentChange(FacesContext context, UIComponent component,
                                 ComponentChange change)
  {
    if (component instanceof RichShowDetailItem &&
        ((RichShowDetailItem) component).getParent() instanceof RichPanelAccordion &&
        change instanceof AttributeComponentChange &&
        "disclosed".equals(((AttributeComponentChange) change).getAttributeName()))
    {
      logger.fine("ComponentChange; disclosed event on showDetailItem in a RichPanelAccordion, we don’t persist.");
      return;
    }
    super.addComponentChange(context, component, change);
  }
}

This method is called by the framework to persist the component change. We override this method and  check if the component is a RichShowDetailItem in a RichPanelAccordion. If the ComponentChange is also a disclosed event, we don't call the super, so this change doesn't persist.

Since I don’t have my Oracle Cloud Trial anymore, I included a workspace, which reproduces this behavior. 
You can see the difference between the two accordions by changing the web.xml parameter. State is saved in session with the default web.xml param:

    Change Persistence Parameter
    org.apache.myfaces.trinidad.CHANGE_PERSISTENCE
    oracle.adf.view.page.editor.change.ComposerChangeManager
  

You can see the panelAccordion resetting to the first showDetailItem when you change to this parameter: 

    Change Persistence Parameter
    org.apache.myfaces.trinidad.CHANGE_PERSISTENCE
    nl.olrichs.blog.panelmds.portal.view.change.PortalComposerChangeManager
  


All you need to do for reproducing is:
  • Click the button toPanelAccordion.
  • Open a different showDetailItem.
  • Click the back button.
  • Click the button toPanelAccordion again.