Posts

ADF Listeners - Part III

ADF Application Event Listeners, QueryString Parameters reinjection between sessions – Part III

In ADF applications as any web application you can make use of QueryString parameters to perform custom actions inside of your application. But what happens to your QueryString parameters when the current session expires? In ADF they are lost between sessions. So, how can we reinject the QueryStrings parameters in the new session? You have two options:

  • You can manually call again the application with the same QueryString parameters. But what if you’re using your application inside of another? Probably this option may not work.
  • You can try to catch the session timeout. But you need to catch the session timeout before it actually expires otherwise you lost the QueryString parameters. How to do it?

The answer to the previous question resides on the ADF application Events Listeners. In my previous posts you can find details about ADF application Events Listeners. Take a look here.

In this post I will show you how to accomplish this task.

 

Solution

In order to gather the current session QueryString parameters and reinject them in the newest one before session timeout occur you need to create the following custom listeners:

  • Custom Phase Listener
  • Custom Filter

Inside of the custom phase listener class we will be listening the lifecycle phases of our application, gather the QueryString parameters and save them in a cookie so we are able to access them in the future when the session timeout happens. The following code explains how to do it.

package view.listeners.session;

import java.util.Map;

import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import javax.faces.event.PhaseId;
import javax.faces.event.PhaseListener;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class CustomPhaseListener implements PhaseListener{
    public CustomPhaseListener() {
        super();
    }

    public void afterPhase(PhaseEvent phaseEvent) {
        System.out.println("CustomPhaseListener");
    }

    public void beforePhase(PhaseEvent phaseEvent) {
        System.out.println("CustomPhaseListener");
        FacesContext vFacesContext = phaseEvent.getFacesContext();
        ExternalContext vExternalContext = vFacesContext.getExternalContext();
        HttpSession session = (HttpSession) vExternalContext.getSession(false);
        boolean newSession = (session == null) || (session.isNew());
        boolean postback = !vExternalContext.getRequestParameterMap().isEmpty();
        boolean timedout = postback && newSession;
        
        if (!timedout)
        {
            //Returns all QueryString paramters but you are only interested in 
            //yours parameters.
            Map<String, Object[]> queryStringMap = ((HttpServletRequest)vExternalContext.getRequest()).getParameterMap();
            
            //This utility class detectes which QueryString parameters are 
            //generated  by the ADF or have been created by you.
            QueryStringUtils qsConstants = new QueryStringUtils();
            //Returns a string with all QueryString parameters created by you.
            String queryString = qsConstants.getStringQueryStringParameters(queryStringMap);
            
            if (queryString != null && !queryString.isEmpty() &&
                !vExternalContext.getRequestCookieMap().containsKey(CookieConstants.COOKIE_QUERY_STRING_NAME))
            {
                //Inserts the QueryString parameters in a COOKIE.
                HttpServletResponse vResponse = (HttpServletResponse)vExternalContext.getResponse();
                Cookie cookie = new Cookie(CookieConstants.COOKIE_QUERY_STRING_NAME, queryString);
                vResponse.addCookie(cookie);
            }
        }
    }

    public PhaseId getPhaseId() {
        return PhaseId.ANY_PHASE;
    }
}

You can find the code for the QueryStringUtils here:

package view.listeners.session;

import java.net.URLDecoder;
import java.net.URLEncoder;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class QueryStringUtils {
    
    //Example of QueryString parameters that the application accepts.
    public static final String QUERY_STRING_1 = "_qs1";
    public static final String QUERY_STRING_2 = "_qs2";
    
    private List listOfQueryStringConstants;

    public QueryStringUtils(){
        listOfQueryStringConstants = new ArrayList();
        listOfQueryStringConstants.add(QUERY_STRING_1);
        listOfQueryStringConstants.add(QUERY_STRING_2);
    }
    
    public List getQueryStringConstantsNames() {
        return listOfQueryStringConstants;
    }
    
    public Hashtable getHashQueryStringParameters(Map<String, Object[]> queryStringParams) {
        Hashtable result = new Hashtable();
        
        for(Entry<String, Object[]> entry : queryStringParams.entrySet()) {
              if (getQueryStringConstantsNames().contains(entry.getKey())) 
              {
                  Object propertyValue = entry.getValue()[0];
                  if (propertyValue != null)
                  {
                      try
                      {
                          String propertyValueAux = URLDecoder.decode(propertyValue.toString(), "UTF-8");
                          result.put(entry.getKey(), propertyValueAux);
                      }
                      catch(Exception ex) {
                          ex.printStackTrace();
                      }
                  }    
              }
        }
        
        return result;
    }

    public String getStringQueryStringParameters(Map<String, Object[]> queryStringParams) {
        String result = "";
        int i = 0;
        
        for(Entry<String, Object[]> entry : queryStringParams.entrySet()) {
              if (getQueryStringConstantsNames().contains(entry.getKey())) 
              {
                  Object propertyValue = entry.getValue()[0];
                  if (propertyValue != null)
                  {
                      try
                      {
                          String propertyValueAux = URLEncoder.encode(propertyValue.toString(), "UTF-8");
                          if (i == 0)
                            result += entry.getKey() + "=" + propertyValueAux;
                          else
                            result += "&" + entry.getKey() + "=" + propertyValueAux;
                          i ++;
                      }
                      catch(Exception ex) {
                          ex.printStackTrace();
                      }
                  }    
              }
        }
        
        return result;
    }
}

In the class CookieConstants you have:

package view.listeners.session;

public class CookieConstants {
    
    public static final String COOKIE_QUERY_STRING_NAME = "QUERY_STRING_COOKIE_EXAMPLE";
}

In the custom filter we will create a custom ServletRequest so we can insert the QueryString parameters for each new request. When the session timeout happens the custom filter is invoked and the QueryString parameters saved in the cookie are read and inserted in the request of the newest session created. Here is the code for the custom filter:

package view.listeners.session;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;


public class CustomFilter implements Filter{
    
    private FilterConfig _filterConfig = null;
    
    public void init(FilterConfig filterConfig) {
        _filterConfig = filterConfig;
    }

    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse,
                         FilterChain filterChain) throws java.io.IOException, 
                                                         javax.servlet.ServletException{
        CustomHttpServletRequestWrapper requestWrapper = new CustomHttpServletRequestWrapper((HttpServletRequest)servletRequest);
        filterChain.doFilter(requestWrapper, servletResponse);
    }

    public void destroy() {
        _filterConfig = null;
    }
}

In the custom ServletRequest we will manipulate the request data and reinject the QueryString parameters. The code for the custom ServletRequest is presented next:

package view.listeners.session;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;

import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper{
    
    private Hashtable<String,String> queryStringParameters;
    
    public CustomHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
        setHashQueryStringParameters();
    }

    private String getCookieQueryStringParameters() {
        String result = null;
        Cookie[] cookie = super.getCookies();
        
        for(int i = 0; i < cookie.length; i++) {
            if (cookie[i].getName().equalsIgnoreCase(CookieConstants.COOKIE_QUERY_STRING_NAME)) {
                result = cookie[i].getValue();
            }
        }
        
        return result;
    }

    private void setHashQueryStringParameters() {
        queryStringParameters = new Hashtable<String, String>();
        String parameters = getCookieQueryStringParameters();
        
        if (parameters != null && !parameters.isEmpty()) 
        {
            String[] splitParameters = parameters.split("&");
            
            for (int j = 0; j < splitParameters.length; j++)
            {
                String[] keyValue = splitParameters[j].split("=");
                queryStringParameters.put(keyValue[0], keyValue[1]);
            }
        }
    }

    public ArrayList getQueryStringParametersNames() {
        return new ArrayList<String>(queryStringParameters.keySet());
    }

    @Override
    public Map getParameterMap() {
        return super.getParameterMap();
    }

    @Override
    public Enumeration getParameterNames() {
        Enumeration enumeration = super.getParameterNames();
        
        if(queryStringParameters.size() != 0){
            //Custom Enumeration to set the default ADF QueryString parameters 
            //and our custom parameters.
            CustomEnumeration myEnumeration = new CustomEnumeration(enumeration, getQueryStringParametersNames());
            enumeration = myEnumeration.getEnumeration();
        }
        return enumeration;
    }

    @Override
    public String[] getParameterValues(String string) {
        String[] result = super.getParameterValues(string);
        
        if (queryStringParameters.size() != 0 && queryStringParameters.containsKey(string)){
            result = new String[1];
            result[0] = queryStringParameters.get(string);
        }
        
        return result;
    }

    @Override
    public String getParameter(String string) {
        String result = super.getParameter(string);
        
        if (queryStringParameters.containsKey(string))
            result = queryStringParameters.get(string);
        
        return result;
    }
}

The code for the CustomEnumeration class can be found here:

package view.listeners.session;

import java.util.ArrayList;
import java.util.Enumeration;

import weblogic.utils.enumerations.IteratorEnumerator;

public class CustomEnumeration {
    private Enumeration enumeration;
    private ArrayList<String> queryStringParametersNames;
    
    public CustomEnumeration(Enumeration enumeration, ArrayList<String> queryStringParametersNames) {
        this.enumeration = enumeration;
        this.queryStringParametersNames = queryStringParametersNames;
    }
    
    public Enumeration getEnumeration() {
        ArrayList<String> arrayList = new ArrayList<String>(); 
        while (enumeration.hasMoreElements()) {
            arrayList.add((String)enumeration.nextElement());
        }
        
        arrayList = addqueryStringParameters(arrayList);
        
        Enumeration result = new IteratorEnumerator(arrayList.iterator()); 
        return result;
    }
    
    private ArrayList<String> addqueryStringParameters(ArrayList<String> arrayList) {
        
        for (int i = 0; i < queryStringParametersNames.size(); i++) {
            String value = queryStringParametersNames.get(i);
            if (!arrayList.contains(value))
                arrayList.add(value);
        }
        return arrayList;
    }
}

Conlusion

By intercepting the application lifecycle phases and the page requests using a custom phase listener and a custom filter we have been able to reinject QueryString parameters between sessions.

 

Cheers,

Pedro Gabriel

 

@PedrohnGabriel

Post original image by Fe Ilya

ADF Listeners - Part II

ADF Application Event Listeners – Part II

Hi all,

In my previous post  ADF Application Event Listeners – Part I I have focused in some of the ADF applications event listeners that we can listen and how to do it. In this post I will show you some examples of data that you can get from those event listeners and in some cases rewrite it.

 

HTTP Session Events

After application session is created the sessionCreated method is triggered. From the HttpSessionEvent input parameter in the same method you are able to access data from these classes:

  • servlet.http.HttpSession
  • servlet.internal.session.MemorySessionData
  • servlet.internal.session.MemorySessionContext
  • servlet.internal.WebAppServletContext

For internal classes is not recommend to make any change on them, however, you are able to access it for your own purpose.

In order to get these previous classes you just need the following:

public void sessionCreated(HttpSessionEvent httpSessionEvent) {
   HttpSession httpSession = httpSessionEvent.getSession();   

   MemorySessionData msd = (MemorySessionData)httpSessionEvent.getSource();
                
   MemorySessionContext msc = (MemorySessionContext)msd.getContext();

   WebAppServletContext appServletContext = msd.getWebAppServletContext();
}

 

In the next table is presented the resume of what you may find.

Class Method Description
HttpSession getId() Returns de the unique identifier assigned to the created session.
getCreationDate() Returns the time when the session was created measured in milliseconds.
getMaxInactiveInterval() Returns the maximum time interval, in seconds that the servlet container will keep the session open between client accesses. If configured the value will be the same as configured in the property “Session Timeout” in the “web.xml” file.
getLastAccessedTime() Returns the last time the client sent a request associated with this session measured in milliseconds.
Invalidate() Invalidated this session then unbinds any objects bound to it.
MemorySessionData getConcurrentRequestCount() Returns the number of concurrent requests for the application.
MemorySessionContext getCurrOpenSessionsCount() Returns the number of concurrent open sessions for the application.
getPersistentStoreType() The scope type where the session data is stored. This value will be the same as configured in the property “Store Type” in the “weblogic-application.xml”.
getTotalOpenSessionsCount() Returns the number open sessions for the application.
getWebAppServletContext() Returns the class that retrieves the context of the current servlet of the application. We will see in further detail this class.

Servlet Context Listener

The Servlet Context is initiated during the first application access after deployment. For future accesses to the application the servlet context is reused interchangeably of the new sessions created.

After the servlet context is initialized the contextInitialized method is triggered. From the ServletContextEvent input parameter in the same method you are able to access data from these classes:

  • servlet.internal.WebAppServletContext
  • application.internal.ApplicationContextImpl
  • management.configuration.AppDeploymentMBean
  • servlet.internal.WebAppConfigManager
  • management.configuration.WebAppComponentMBeanImpl
  • management.runtime.ServletRuntimeMBean

For internal classes is not recommend to make any change on them, however, you are able to access it for your own purpose.

In order to get these previous classes you just need the following:

public void contextInitialized(ServletContextEvent servletContextEvent) {
   WebAppServletContext wasc = (WebAppServletContext)servletContextEvent.getSource();
        
   ApplicationContextImpl acimpl = (ApplicationContextImpl)wasc.getApplicationContext();
   
   AppDeploymentMBean appDeploymentMBean = acimpl.getAppDeploymentMBean();
        
   WebAppComponentMBeanImpl appComponentMBean = WebAppComponentMBeanImpl)wasc.getMBean();
        
   ServletRuntimeMBean[] srmb = wasc.getServletRuntimeMBeans();
}

 

In the next table is presented the resume of what you may find.

Class Method Description
WebAppServletContext getAppDisplayName() Application display name.
getMBean() Returns the WebAppComponentMBeanImpl class.
getServletRuntimeMBeans() Returns the ServletRuntimeMBean class.
getClasspath() Returns the path for all jars included in the application.
getDocroot() Returns the path for the WAR file of the application.
getMajorVersion() Returns the major version of the application.
getMinorVersion() Returns the minor version of the application.
getApplicationSecurityRealmName() Returns the security realm name of the user logged in.
getApplicationParameters() Application Configuration Parameters. This is further detailed next.
getAbsoluteSourcePath() The path of the application’s installation.
getServer() Returns the name of the server where the application is deployed in.
getServerInfo() Returns the server info where the application is deployed in. We will provide an example next.
ApplicationContextImpl getAuthRealmName() Returns the realm name of the user logged in.
getDefaultEncoding() Returns application’s default encoding.
getDispatchPolicy() The class of work executed in queues of the weblogic.
getMimeTypeDefault() Returns application’s mime type.
AppDeploymentMBean getSessionCookieName() Returns the internal name of the session’s cookie.
WebAppConfigManager getSessionJDBCConnectionTimeoutSecs() Returns in seconds the JDBC connection timeout.
getSessionPersistentStoreDir() In my tests I have found “session_db” value.
getSessionPersistentStoreTable() In my tests I have found “wl_servlet_sessions” value.
WebAppComponentMBeanImpl getSessionPersistentStoreType() In my tests I have found “memory” value.
getSessionTimeoutSecs() Returns in seconds the session time out.
ServletRuntimeMBean getName() Returns the name of the servlet.
getType() Returns the type of the servlet.

 

In the getServerInfo() method from the WebAppServletContext class you can find something like this:

“WebLogic Server 10.3.5.0 Fri Apr 1 20:20:06 PDT 2011 1398638 Oracle WebLogic Server Module Dependencies 10.3 Thu Mar 3 14:37:52 PST 2011 Oracle WebLogic Server on JRockit Virtual Edition Module Dependencies 10.3 Thu Feb 3 16:30:47 EST 2011”

 

In the getApplicationParameters() method from the ApplicationContextImpl class you can find for example the following parameters:

Parameter Definition
“weblogic.app.rmiGracePeriod” The amount of time, in seconds, that the work manager accepts and schedules RMI calls until there are no more RMI requests arriving within the RMI grace period during a gracefulshutdown or a retirement.
“weblogic.app.ignoreSessions” Immediately places the application into Administration mode without waiting for current HTTP sessions to complete.
“weblogic.app.adminMode” Indicates that a running application should switch to Administration mode and accept only Administration requests via a configured Administration channel. If this option is not specified, the running application is stopped and cannot accept Administration or client requests until is it restarted.

 

In the ServletRuntimeMBean class you can find for example the following servlets:

Name Type
BIGRAPHSERVLET ServletRuntime
JspServlet ServletRuntime
Faces Servlet ServletRuntime
FileServlet ServletRuntime
resources ServletRuntime
adw ServletRuntime
WebServiceServlet ServletRuntime
MapProxyServlet ServletRuntime
BIGAUGESERVLET ServletRuntime
GatewayServlet ServletRuntime

Servlet Request Listener

The Servlet Request Listener is triggered whenever a new request is made to the server regarding the current application. For those cases the requestInitialized method is triggered with the ServletRequestEvent class as input parameter.

ServletRequestEvent and ServletContextEvent extend from the same java class “java.util.EventObject”. You will be able to find the same data as detailed for the “Servlet Context Listener”.

Phase Listeners

You can listen to all lifecycle phases. From the beforePhase and afterPhase methods you are able to access and manipulate data from these classes:

  • servlet.http.HttpServletRequest
  • servlet.http.HttpServletResponse

You can get these classes as detailed next:

public void beforePhase(PhaseEvent phaseEvent) {
   //You get the current phase for what the event has been triggered.        
   System.out.println("getPhaseId: " + phaseEvent.getPhaseId());
        
   FacesContext vFacesContext = phaseEvent.getFacesContext();    
   ExternalContext vExternalContext = vFacesContext.getExternalContext();
        
   HttpServletRequest vRequest = (HttpServletRequest)vExternalContext.getRequest());
        
   Map<String, Object[]> queryStringMap = vRequest.getParameterMap();
        
   HttpServletResponse vResponse = (HttpServletResponse)vExternalContext.getResponse();
}

 

Note: Don’t forget to set the getPhaseId method to trigger all or only the phases that you really want when you are creating your own custom Phase Listener.

public PhaseId getPhaseId() {
  return PhaseId.ANY_PHASE;
}

 

If you create a custom Filter you are able to manipulate the data from the request and response by setting the data from the servletRequest and servletResponse input parameters’ classes for the doFilter method.

In order to set the data from the servletRequest class do the following steps:

  1. Create a class that extends from “servlet.http.HttpServletRequestWrapper” class.CustomHttpServletRequestWrapper
  2. Override the methods you want to customize.CustomHttpServletRequestWrapper_OverideMethods
  3. Set the new class in the doFilter method.
    public void doFilter(ServletRequest servletRequest,
                         ServletResponse servletResponse,
                         FilterChain filterChain) throws java.io.IOException, javax.servlet.ServletException{
            
       CustomHttpServletRequestWrapper requestWrapper = new CustomHttpServletRequestWrapper((HttpServletRequest)servletRequest);
            
       filterChain.doFilter(requestWrapper, servletResponse);
    }
    
    

 

On the next post we’ll explore how to use these application events to get custom query string parameters and reinject them in new sessions after session’s time out.

Cheers,

Pedro Gabriel

 

@PedrohnGabriel

Feature Image by Melvin Gaal