Статьи

ADF: вызов потока задач URL с методом HTTP POST

Как мы знаем, ограниченный поток задач может быть вызван по некоторому URL либо напрямую из браузера, либо из какого-либо внешнего приложения. Эта функция включена, если для свойства потока задач «URL invoke» установлено значение «url-invoke-разрешено», и оно обычно используется в интеграционных проектах. Обычно клиенты (или инициаторы) используют метод HTTP GET и передают свои параметры в URL. Давайте рассмотрим простой поток задач с одним обязательным входным параметром:

1
2
3
4
5
6
7
8
  <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>   
    ...

Поток задач может быть вызван следующим URL

1
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

Клиент использует простую HTML-форму для создания этого запроса GET:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<html>
  <head>   
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  </head>
  <body>
   <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>

И это выглядит так:

Снимок экрана 2013-08-02 в 5.55.16 вечера

Некоторые клиенты предпочитают использовать метод HTTP POST, и, кроме того, это их требование:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
<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>

И это прекрасно работает. URL в этом случае будет выглядеть так:

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

Вся остальная необходимая информация, такая как идентификатор потока задач и значение параметра, находится внутри запроса POST. Но проблема в том, что он работает нормально только для R1. Если мы попробуем это на R2, мы получим следующее:

ADF_FACES-30179: Дополнительные сведения см. В журнале ошибок сервера для записи, начинающейся с: UIViewRoot имеет значение null. Неустранимое исключение во время PhaseId: RESTORE_VIEW 1.

Почему? Из-за этого:

1
2
3
4
5
6
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)

Все необходимые данные, включая идентификатор потока задач, который должен быть передан внутри запроса POST, теряются. Почему? Из-за «петли». Если мы обнаружим запросы, отправленные из браузера на сервер при нажатии кнопки «Отправить», мы увидим следующее:

Снимок экрана 2013-08-02 в 6.41.27 вечера

Снимок экрана 2013-08-02 в 6.45.56 вечера

Таким образом, вместо отправки «честного» ответа сервер отправляет некоторый «петлевой» скрипт, который генерирует «идентификатор окна» и отправляет следующий запрос GET с созданным идентификатором окна. Здорово! Но все почтовые данные исчезли. Запрос GET абсолютно пустой.

К счастью, фреймворк не генерирует никаких «петель», если исходный запрос POST уже имеет некоторый «идентификатор окна». Итак, обходной путь для нашего случая — разработать фильтр сервлета, установив атрибут «id окна» для нашего запроса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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";

Обратите внимание, что этот фильтр должен стоять перед фильтром «Тринидад» в цепочке фильтров:

01
02
03
04
05
06
07
08
09
10
11
12
  <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>

Это оно!

Ссылка: URL Task Flow Call с методом HTTP POST от нашего партнера JCG Евгения Федоренко из блога ADF Practice .