12.02.2016

ADF: кастомный selection-listener и обновление данных в popup

Столкнулся с проблемой: не обновляется отображение переменной биндинга в popup-окне. Решается оборачиванием стандартного листенера в кастомный метод и программным добавлением partial trigger на элемент, инициирующий обновление переменной.
    public void customListener(SelectionEvent selectionEvent) {
        UIComponent component = selectionEvent.getComponent();
        component.processUpdates(FacesContext.getCurrentInstance());
        JSFUtils.resolveMethodExpression("#{bindings.AllUsersVO1.collectionModel.makeCurrent}", null,
                                          new Class[]{SelectionEvent.class}, new Object[]{selectionEvent});
        setExpressionValueCustom();
    }
   
    private void setExpressionValueCustom(){
        ViewRowImpl row = (ViewRowImpl)JSFUtils.resolveExpression("#{bindings.AllUsersVO1.currentRow}");
        JSFUtils.setExpressionValue("#{bindings.userLogin.inputValue}", row.getAttribute("Username"));
        AdfFacesContext.getCurrentInstance().addPartialTarget(getP_delete());
    }
getP_delete() - геттер объекта RichPopup.

21.01.2016

ADF: Уход со страницы с незафиксированными данными

У концепции ADF есть интересное свойство: обязанность по отслеживанию незакоммиченных данных полностью ложится на программиста. Сложно сказать, как с этим планировалось справляться силами недоучившихся студентов, на которых рассчитано декларативное программирование в ADF. Да и не об этом речь. Речь пойдет о том, как уйти со страницы, которая создала новую запись в VO, связанном c EO, не зафиксировать эту запись, и потом благополучно вернуться обратно.

Что сразу приходит в голову:
    @PostConstruct
    public void initBean() {
        AppModuleImpl mod = (AppModuleImpl)ADFUtils.getBindingApplicationModule();
        DBTransaction conn = mod.getDBTransaction();
        if (conn.isDirty()) conn.rollback();
        ...
Так нехорошо, потому что initBean() будет вызываться при каждой активности на странице и транзакция будет теряться тогда, когда это не нужно. К тому же  транзакция будет откатываться на фазе prepareRender, что тоже идеологически неверно. То есть контекст уже проинициализирован, загружена модель данных, проверены валидации, всё готово к прорисовке страницы - и тут мы сообщаем системе, что данные нужно изменить. Не знаю, что именно будет происходить глубоко внутри приложения, но думаю, что ничего хорошего.

Рабочее решение заключается в создании бина уровня приложения, унаследованного от PagePhaseListener, где мы будем анализировать страницу на ранней фазе жизненного цикла и отслеживать грязные данные. Создаем бин и прописываем его в adf-settings.xml:


Сам класс:
    import java.util.ArrayList;
    import java.util.Map;
    import javax.faces.context.FacesContext;

    import javax.servlet.http.HttpServletRequest;
   
    import oracle.adf.controller.v2.lifecycle.PagePhaseEvent;
    import oracle.adf.controller.v2.lifecycle.PagePhaseListener;

    import oracle.adf.share.ADFContext;
   
    import oracle.jbo.server.DBTransaction;
   
    import *.ADFUtils;
    import *.AppModuleImpl;

public class PageListenerBean implements PagePhaseListener{
   
    // если conn.isDirty() и переходим на из url из списка, то rollback
    private static String[] deniedUrls = {"/myapp/faces/task-flow-definition/tree"};
   
    @Override
    public void beforePhase(PagePhaseEvent pagePhaseEvent) {
            try{
                    if (pagePhaseEvent.getPhaseId() == 9){
                        AppModuleImpl mod = (AppModuleImpl)ADFUtils.getBindingApplicationModule();
                        DBTransaction conn = mod.getDBTransaction();
                        if (isPageChanged()){
                            if (changedToDisallowed()){
                                if (conn.isDirty()){
                                    conn.rollback();
                                    System.out.println("= rolled back!");
                                }
                            }
                        }
                     }
            }catch(Exception e){
                ;          
            }
    }
   
    @Override
    public void afterPhase(PagePhaseEvent pagePhaseEvent) {
        ;       
    }
   
    private void setApplicationScopeParameter(String val){
        ADFContext adfCtx =  ADFContext.getCurrent();
        Map applicationScope = adfCtx.getApplicationScope();
        applicationScope.put("currentURL", val);
    }
   
    private String getApplicationScopeParameter(){
        ADFContext adfCtx =  ADFContext.getCurrent();
        Map applicationScope   = adfCtx.getApplicationScope();
        return (String)applicationScope.get("currentURL");
    }
   
    private boolean isPageChanged(){
        try{
            if (!getCurrentUrl().equals(getApplicationScopeParameter())){
                setApplicationScopeParameter(getCurrentUrl());
                return true;
            }
        }catch(NullPointerException e){
            System.out.println("--! PageListenerBean: can't get current url");
        }
        return false;
    }
   
    private boolean changedToDisallowed(){
        for(String url : this.deniedUrls)
            if (url.equals(getCurrentUrl()))
                return true;
        return false;     
    }
   
    private String getCurrentUrl(){
        FacesContext ctx = FacesContext.getCurrentInstance();
        HttpServletRequest servletRequest = (HttpServletRequest) ctx.getExternalContext().getRequest();
        return servletRequest.getRequestURI();
    }
}
При каждом срабатывании проверяется соответствие текущего URL переменной #{applicationScope.currentURL}. Если страница отличается, находится в "стоп-листе" и данные грязные, тогда откатываем транзакцию. Стоп-лист введен во избежание непредвиденных потерь транзакции при единой фиксации нескольких страниц (такое может быть в приложении).

Код 9 - id фазы инициализаци Restore View. Интересно проследить жизненный цикл страницы-источника (откуда переходим) после изменения данных:

В целевой странице (куда переходим)  грязные данные присутствуют во всем цикле:


14.01.2016

Переменные PL/SQL пакета: неочевидность

2 часа просидел над отладкой простейшего кода, не замечая подводного камня.
Допустим:
create or replace package VARTEST is
  BANNER varchar2(255);
  procedure test (p_cur out sys_refcursor);
end VARTEST;

create or replace package body VARTEST is
  procedure test (p_cur out sys_refcursor)
  is
  begin
    open p_cur for
       select count(*) from v$version v where v.BANNER = BANNER;
  end test;
end VARTEST;
Вызовем-ка функцию
begin
  VARTEST.BANNER := 'some awsome text';
  vartest.test(p_cur => :p_cur);
end;

--------------
count(*) 
5
Результат казался неожиданным, пока (случайно!) не выполнил запрос
select * from v$version v where v.BANNER = BANNER;
Запрос, на удивление, выполнился и честно вернул всё содержимое вьюхи. Т.е. BANNER - это в данном случае имя поля, а не переменная пакета.
Итого: имена переменных пакета лучше начинать с префикса, либо использовать явное указание принадлежности переменной пакету.

27.10.2015

PL/SQL: MODEL (пример)

У движка отчетов JasperReports есть особенность: область детализации строго горизонтальна и занимает всю ширину страницы. Если нужно напечатать несколько маленьких бланков, которые умещаются по 2 штуки в ряд, то придется печатать всё-таки по одному.

Выход - подать на вход выборку, уже разбитую на 2 колонки.

Для простейших случаев наподобие представленного можно использовать MODEL - мощную инструкцию PL/SQL, позволяющую с выборкой производить операции с нетривиальной логикой. Применяется крайне редко в особо тяжелых случаях.
select * from (select id1, name1, lead(id2, 1) over(order by null) id2, lead(name2, 1) over(order by null) name2 from (select id1, name1, id2, name2 from (select 1 r, 10 id, 'a' name from dual union select 2 r, 15 id, 'b' name from dual union select 3 r, 20 id, 'c' name from dual union select 4 r, 25 id, 'd' name from dual union select 5 r, 30 id, 'e' name from dual) model dimension by(r, id, name) measures(0 id1, cast(null as varchar2(255)) name1, 0 id2, cast(null as varchar2(255)) name2) rules(id1 [ mod(r, 2) != 0, id, name ] = cv(id), id2 [ mod(r, 2) = 0, id, name ] = cv(id), name1 [ mod(r, 2) != 0, id, name ] = cv(name), name2 [ mod(r, 2) = 0, id, name ] = cv(name)))) where id1 != 0
В приведенном примере сначала мы разбиваем выборку по колонкам в зависимости от четности/нечетности порядкового номера, затем на уровне выше с помощью функции LEAD склеиваем строки, чтобы убрать шахматный порядок в данных. Наконец, ограничением where id1 != 0 отсекаем пустые строки, образовавшиеся после склейки.





23.10.2015

PL/SQL: Месяцы в родительном падеже

Не хотелось плодить кучу условий и использовать какие-либо кириллические константы, поэтому вот:
  function month_rodp(p_date in date) return varchar2
  is
           mon varchar2(20);
  begin
     mon := trim(lower(to_char(p_date, 'MONTH')));
     if (to_char(p_date, 'MM')) in (3,8) then
        mon := mon || chr(1072 using NCHAR_CS);
     else
        mon := substr(mon, 1,  length(mon)-1) || chr(1103 using NCHAR_CS);
     end if;
     return mon;
  end month_rodp;

28.08.2015

Weblogic: weblogic.jdbc.wrapper.Blob_oracle_sql_BLOB cannot be cast to oracle.sql.BLOB

Ошибка возникает потому, что типы oracle.sql сервер Weblogic обвязывает в свои из weblogic.jdbc.wrapper.
Решение 1 - отключить обвязку на уровне источника данных через консоль, галочку нужно снять:


Решение 2 (и предпочтительное) - выполнить приведение типов в коде.
Код до:
public Connection getServerConnection() {
      System.out.println("DataSource = " + this._ds);
      Connection conn = null;
      try {
  if (this.conn == null){
              conn = this._ds.getConnection();  //меняем эту строку
         }
      } catch (SQLException var4) {
         String msg = "ERROR. Cannot get connection from datasource: " + var4.getMessage();
         System.out.println(msg);
         var4.printStackTrace();
      }
      return conn;
}
Код после:
import oracle.jdbc.OracleConnection;
import weblogic.jdbc.extensions.WLConnection;

public Connection getServerConnection() {
      System.out.println("DataSource = " + this._ds);
      Connection conn = null;
      try {
  if (this.conn == null){
              conn = (((WLConnection) this._ds.getConnection()).getVendorConnection());
         }
      } catch (SQLException var4) {
         String msg = "ERROR. Cannot get connection from datasource: " + var4.getMessage();
         System.out.println(msg);
         var4.printStackTrace();
      }
      return conn;
}
Класс WLConnection можно подключить через com.bea.core.datasource_1.9.0.0.jar

27.08.2015

Парсер SAX

SAX - альтернатива стандартному парсеру DOM, который в силу особенностей реализации (создание в памяти объекта XMLDocument, содержащего целиком весь файл), подходит только для распарсивания маленьких xml-документов.

Фишка парсера в том, что поток InputStream обрабатывается последовательно и к тем или иным событиям применяется пользовательская логика:
             SAXParserFactory factory = SAXParserFactory.newInstance();
             SAXParser parser;
             parser = factory.newSAXParser();
             MySAXParser prsHandler = new MySAXParser();
             parser.parse(is, prsHandler);
, где is - объект класса InputStream, MySAXParser - класс с перекрытыми методами обработки событий
public class MySAXParser extends DefaultHandler{
    private String currentParameterTypes;
    private String currentQName;
 
    @Override
    public void startDocument(){  //событие начала парсинга
        System.out.println("start parse document;");
    }
   
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes){
        // встретили элемент
        this.currentParameterTypes = qName;
    }
   
    @Override
    public void endElement(String uri, String localName, String qName){
        // вышли из элемента
    }
   
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        // получает значение тега/атрибута this.currentParameterTypes
        String value = new String(ch, start, length);
    }
}
Ниже - график заполнения heap'a во время распарсивания файда в 150МБ.




11.08.2015

Weblogic: Cannot read from policy store

Cannot read from policy store. Reason is PolicyStore Error, javax.xml.stream.XMLStreamException

Данная ошибка означает, что процесс не может прочитать настройки политик для домена.

При детальном осмотре файловой системы оказалось, что файл %domain%\config\fmwconfig\system-jazn-data.xml в какой-то момент стал пустым (подробнее о структуре файла). Проблема решилась заменой данного файла работоспособным, взятым у коллеги.

17.12.2014

--------------------OeBS is over

Очень интересный проект по внедрению Oracle E-Business Suite R12, на котором я трудился 938 дней, подошел к концу. Ухожу поддерживать отечественного производителя.
Для истории:

08.11.2014

Создание оконного Java-приложения

Цель: создать простейшее оконное приложение на Java, выводить текст, переданный в качестве параметра.

06.11.2014

Реализация очереди Oracle AQ

Oracle Advanced Queuing (AQ) - механизм организации очередей. В простейшем случае создается тип, на основе типа специальным API создается таблица очереди. Очередь действует по принципу FIFO. Приведенный ниже листинг создает демонстрирует вышесказанное.

13.10.2014

OeBS: Обновление параметра FND_INIT_SQL на уровне полномочий

Параметр содержит анонимный блок, выполняющийся при инициализации полномочий. Ниже - пример кода изменения значения параметра для группы полномочий.

08.08.2014

Subversion: синхронизация каталога между двумя репозитариями

Например, мы ведем java код в одном репозитарии, а объекты sql и ldt складируем в другой. Заказчик захотел в каталогах функциональных расширений видеть ещё и папку java c исходниками. Пишем batник синхроинзации.
@echo off
@echo ---------------------------------------
@echo Sync Java sources from java JSVN to source SSVN
@echo ---------------------------------------
# защита от случайного запуска. Обязательно должны запускать с -refresh
if "%1" == "refresh" (
# временный каталог для импорта
if exist c:\svn_tmp (
  <nul set /p strTemp=Cleaning a temp folder...
  rmdir c:\svn_tmp /s /q
  <nul set /p strTemp=done.
  echo.
)
# импорт содержимого репозитария
svn export https://JSVN-url/java_folder c:\svn_tmp
# если что-то появилось в каталоге, начинаем экспорт
if exist c:\svn_tmp\xxmy000(
 <nul set /p strTemp=Cleaning old revisions in SSVN...
 svn delete svn://SSVN-url/appscode/trunk/XXMY/XXMY000/java/src/oracle/apps/xxmy/xxmy000 -m "clean old rev"
 <nul set /p strTemp=done.
 echo.
 <nul set /p strTemp=Importing new revisions to SSVN...
 svn import c:\svn_tmp\xxmy000 svn://SSVN-url/appscode/trunk/XXMY/XXMY000/java/src/oracle/apps/xxmy/xxmy000 -m "actualize"
 <nul set /p strTemp=done.
 echo.
 <nul set /p strTemp=Cleaning a temp folder...
 rmdir c:\svn_tmp /s /q
 <nul set /p strTemp=done.
) else (
 @echo --------!! Error during import
)
) else (
 @echo !Error: Parameter 1 not found. Nothing done.
)
pause

07.07.2014

Особенность retainAM при использовании нескольких AM

Простой эксперимент позволит нам увидеть разницу между корневым и некорневыми Application Modules (AM).

15.06.2014

WebADI: атрибуты интерфейса

Цель: ограничивать элементы в атрибуте интерфейса (выпадающий список) в зависимости от входных параметров. В нашем примере - три атрибута CAUSE_1, CAUSE_2, CAUSE_3, содержащих один и тот же комбо-бокс. Нужно выводить только те элементы, которые актуальны на текущий момент (в параметрах интегратора вводится месяц и год)

10.06.2014

JBO-27122: SQL error during statement preparation

Ошибка появляется, если VO выполняется сначала с ограчением в n1 предикатов, затем в n2 предикатов, причем n2 < n1. Пример:
CausesVOImpl causeVO = getAm().getCausesVO1();
// первое выполнение
if (!causeVO.isPreparedForExecution()){
    causeVO.setMaxFetchSize(999);
}
causeVO.executeEmptyRowSet();
causeVO.setWhereClause("CAUSE_NUMBER > :1 and GROUP_ID = :2");
causeVO.setWhereClauseParam(0, causeNumber);
causeVO.setWhereClauseParam(1, groupId);
causeVO.executeQuery();

// второе выполнение
CausesVOImpl causeVO = getAm().getCausesVO1();
if (!causeVO.isPreparedForExecution()){
    causeVO.setMaxFetchSize(999);
}
causeVO.executeEmptyRowSet();
// causeVO.setWhereClause(null);
// causeVO.setWhereClauseParams(null);
causeVO.setWhereClause("GROUP_ID = :1");
causeVO.setWhereClauseParam(0, getEntityId());
try{
    causeVO.executeQuery();
}catch (Exception e){
    System.out.println(e.getMessage());
}
В приведенном примере ошибка возникнет при втором вызове executeQuery(). Как видим, setWhereClause и setWhereClauseParam не перекрывают ранее заданные значения.
Для устранения ошибки достаточно принудительно очистить ограничение (раскомментировать строки во втором выполнении)