31 Jul 2013

URL Task Flow Call with HTTP POST Method

As we know a bounded task flow can be invoked by some URL either directly from a browser or from some external application. This feature is enabled if task flow's property "URL invoke" is set to "url-invoke-allowed" and it is commonly used in integration projects. Usually clients (or invokers) use HTTP GET method and pass their parameters in the URL. Let's consider some simple task flow with one required input parameter:
  <task-flow-definition id="task-flow-definition">    
    <input-parameter-definition id="__23">
      <name id="__24">userName</name>
      <value id="__67">#{requestScope.userName}</value>
      <class id="__63">java.lang.String</class>
      <required/>
    </input-parameter-definition>    
    ...


 The task flow can be invoked by the URL like this

http://127.0.0.1:7101/TestApp/faces/adf.task-flow?adf.tfId=task-flow-definition&adf.tfDoc=/WEB-INF/task-flow-definition.xml&userName=xammer


The client uses a simple html form to construct this GET request:

<html>
  <head>    
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  </head>
  <body>
   <form action="http://127.0.0.1:7101/TestApp/faces/adf.task-flow">
   <input type="hidden" name="adf.tfId" value="task-flow-definition"/>  
   <input type="hidden" name="adf.tfDoc" value="/WEB-INF/task-flow-definition.xml"/>  
   <label>     
        User Name 
      <input type="text" name="userName" value="xammer"/>  
   </label>
      <input type="submit" value="Submit"/>
    </form>
    </body>
</html> 


And it looks like this:

Everything is ok. So far. It works fine for R1 and for R2 as well.
Some clients prefer to use HTTP POST method, and moreover this is their requirement:

<html>
  <head>    
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  </head>
  <body>
   <form action="http://127.0.0.1:7101/TestApp/faces/adf.task-flow" method="POST">
   <input type="hidden" name="adf.tfId" value="task-flow-definition"/>  
   <input type="hidden" name="adf.tfDoc" value="/WEB-INF/task-flow-definition.xml"/>  
   <label>     
        User Name 
      <input type="text" name="userName" value="xammer"/>  
   </label>
      <input type="submit" value="Submit"/>
   </form>
   </body>
</html> 

  

And it works fine as well. The URL in this case is going to look like this:

http://127.0.0.1:7101/TestApp/faces/adf.task-flow


All other necessary information like task flow id and parameter value is inside POST request. But the problem is that it works fine for R1 only. If we try it out on R2 we will get the following:

ADF_FACES-30179:For more information, please see the server's error log for an entry beginning with: The UIViewRoot is null. Fatal exception during PhaseId: RESTORE_VIEW 1.

Why? Because of that:

oracle.adfinternal.controller.application.InvokeTaskFlowException: ADFC-02006: A task flow ID is not found in the URL.
    at oracle.adfinternal.controller.util.UrlParams.getTaskFlowInfo(UrlParams.java:144)
    at oracle.adfinternal.controller.application.RemoteTaskFlowCallRequestHandler.invokeTaskFlowByUrl(RemoteTaskFlowCallRequestHandler.java:84)
    at oracle.adfinternal.controller.application.RemoteTaskFlowCallRequestHandler.doCreateView(RemoteTaskFlowCallRequestHandler.java:63)

All necessary data included task flow id which is supposed to be passed inside POST request is lost. Why?  Because of "loopback". If we discover requests sent from the browser to the server on clicking the Submit button we'll see the following:

Instead of one we have two requests. And the first one (POST) is ours. Exploring the response for this request we can see the following:


 So, instead of sending the "honest" response, the server sends some "loopback" script which generates "window id" and sends the following GET request with generated window id. Cool! But all post data is gone. The GET request is absolutely empty.
Fortunately, the framework doesn't generate any "loopbacks" if the initial POST request has already some "window id". So, the workaround for our case is to develop a servlet filter, setting the "window id" attribute for our request:
public void doFilter(ServletRequest servletRequest,
                     ServletResponse servletResponse,
                     FilterChain filterChain)
  throws IOException, ServletException
{
  HttpServletRequest r = (HttpServletRequest) servletRequest;
  HttpSession s = r.getSession();
  
  //May be this is not an initial request and window id has been generated earlier
  //We want all the following requests to work with the same window id 
  //For our use-case this is ok    
  String windowID = (String) s.getAttribute(_WINDOW_ID_KEY);
  if (windowID == null)
  {
    String pathInfo = r.getPathInfo();
    //This is an initial POST request to get access to the task flow
    if (("/adf.task-flow").equals(pathInfo) &&
        "POST".equals(r.getMethod()))
    {
      windowID = WINDOW_ID;
      //Save window id in the session 
      s.setAttribute(_WINDOW_ID_KEY, windowID);
    }

  }

  //Setup attribute for the request
  //This will prevent generating of the loopback
  if (windowID != null)
    r.setAttribute(_WINDOW_ID_KEY, windowID);

  filterChain.doFilter(servletRequest, servletResponse);
}

private static final String __WINDOW_MANAGER_KEY = RichWindowManager.class.getName();
private static final String _WINDOW_ID_KEY = __WINDOW_MANAGER_KEY + "#WINDOW_ID";  
private static final String WINDOW_ID = "wextflow";

Notice, that this filter should stand before "trinidad" filter in the filter chain:
  <filter>
    <filter-name>ExtPostFilter</filter-name>
    <filter-class>com.cs.fusion.core.view.filter.ExtPostFilter</filter-class>
  </filter> 
  <filter>
    <filter-name>trinidad</filter-name>
    <filter-class>org.apache.myfaces.trinidad.webapp.TrinidadFilter</filter-class>
  </filter>
  <filter>
    <filter-name>ServletADFFilter</filter-name>
    <filter-class>oracle.adf.share.http.ServletADFFilter</filter-class>
  </filter>

That's it!

No comments:

Post a Comment

Post Comment