Простой эксперимент позволит нам увидеть разницу между корневым и некорневыми 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 не перекрывают ранее заданные значения.Для устранения ошибки достаточно принудительно очистить ограничение (раскомментировать строки во втором выполнении)
08.06.2014
Отчет из данных ViewObject
Мы можем выдать пользователю отчет сразу по нажатию кнопки, без создания конкаррента. Такое удобно, например, при выгрузке в Excel результатов с поисковой формы.
Ключевые моменты.
1) Формирование XMLNode c данными из VO. Первый параметр - глубина структуры XML.
Шаблон для нашего отчета будет иметь особенности. Имя группы будет составлено как Имя_VO || 'Row'. Например, если VO отчета назывался ReportVO, то группа будет названа ReportVORow. Имена элементов будут совпадать с именами атрибутов VO. Отдельная группа - вручную добавленные параметры шапки:
Ключевые моменты.
1) Формирование XMLNode c данными из VO. Первый параметр - глубина структуры XML.
XMLNode xmlNode = (XMLNode)reportVO.writeXML(4, XMLInterface.XML_OPT_ALL_ROWS);2) Добавление доп.параметров в XML (параметры шапки)
XMLNode xmlNode = (XMLNode)am.invokeMethod("fillWithVODataset");
XMLDocument doc = xmlNode.getDocument();
Element elem = doc.createElement("ReportInfo");
Element elemPeriod = doc.createElement("SomeParameter");
Text startPeriodVal = doc.createTextNode((String)someBean.getValue(pageContext));
elemPeriod.appendChild(startPeriodVal);
elem.appendChild(elemPeriod);
XMLNode firstChild = (XMLNode)xmlNode.getFirstChild();
// xmlNode пуст, если VO был пуст
if (firstChild != null){
XMLNode parent = (XMLNode)firstChild.getParentNode();
parent.appendChild(elem);
}else{
// добавляем элемент в корень
xmlNode.appendChild(elem);
}
3) Натягиваем данные на существующий шаблон XDOselect lb.file_data from xdo_templates_b tmp, xdo_lobs lb where tmp.template_code = :1 and tmp.application_short_name = :2 and lb.application_short_name = tmp.application_short_name and lb.lob_code = tmp.data_source_code and lb.lob_type = 'TEMPLATE' and sysdate between tmp.start_date and nvl(tmp.end_date,sysdate)4) Формируем выходной поток с помощью класса oracle.apps.xdo.template.FOProcessor
Шаблон для нашего отчета будет иметь особенности. Имя группы будет составлено как Имя_VO || 'Row'. Например, если VO отчета назывался ReportVO, то группа будет названа ReportVORow. Имена элементов будут совпадать с именами атрибутов VO. Отдельная группа - вручную добавленные параметры шапки:
<dataStructure> <group name="reportVORow" source="reportVORow"> <element name="ATTR1" value="ATTR1"/> <element name="ATTR2" value="ATTR2"/> ... <group name="ReportInfo" source="ReportInfo"> <element name="SomeParameter" value="SomeParameter"/> ...
29.05.2014
Разработка кастомной формы в Oracle Forms
Цель: создать кастомную форму "Расширенная история записи" и включить в меню Tools (Сервис). Будет показывать расширенную информацию из HR по текущей записи - Ф.И.О., департамент, должность и т.д.
Для работы нам понадобится ПО Oracle Developer Suite 10. Сразу оговорюсь, что эта IDE в теории позволяет запускать формы на выполнение через Java-апплет в браузере. На практике это сделать так и не удалось (были перепробованы JInitiator 1.3.22, 18, 26) с изменением содержимого formsweb.cfg. Так что этот вопрос остается открытым.
Этап 1: модификация формсового меню. Скачиваем $AU_TOP/resource/RU/FNDMENU.mmb и открываем в Oracle Developer Suite 10:
FRM-30085: Unable to adjust form for output. Форма слишком велика, либо элементы выходят за границы формы.
FRM-47023:No such parameter named G_QUERY_FIND exists in form. Хитрая ошибка, которая поначалу вводит в тупик, потому как G_QUERY_FIND - стандартный параметр, который есть всегда. Имеет смысл проверить тип данных параметров. Даже если передается дата, то тип данных должен быть Char. После этой манипуляции проблема ушла.
ORA-06502: PL/SQL: numeric or value error: character string buffer too small. По умолчанию размер поля вывода ограничен 30 символами. Эта ошибка появится, если переменная будет содержать строковое значение большей длины. Имеет смысл поставить 32 000.
Для работы нам понадобится ПО Oracle Developer Suite 10. Сразу оговорюсь, что эта IDE в теории позволяет запускать формы на выполнение через Java-апплет в браузере. На практике это сделать так и не удалось (были перепробованы JInitiator 1.3.22, 18, 26) с изменением содержимого formsweb.cfg. Так что этот вопрос остается открытым.
Этап 1: модификация формсового меню. Скачиваем $AU_TOP/resource/RU/FNDMENU.mmb и открываем в Oracle Developer Suite 10:
Добавляем новый пункт в самый конец списка.
Прописываем блок с вызываемым триггером в окне, открывшемся из контекстного меню PL/SQL Editor. Позже мы создадим этот триггер.
:global.menu_to_appcore := 'ADVANCED_RECORD_HISTORY';
execute_trigger('menu_to_appcore');
Сохраняем файл, заливаем на сервер: frmcmp_batch module=FNDMENU.mmb userid=apps/apps output_file=$AU_TOP/resource/RU/FNDMENU.mmx compile_all=special module_type=menu ... - Inserting menu FNDMENU. - Inserting menu FILE. - Inserting menu EDIT. - Inserting menu VIEW. - Inserting menu FOLDER. - Inserting menu SPECIAL. - Inserting menu SPECIAL_B. - Inserting menu SPECIAL_C. - Inserting menu HELP. - Inserting menu QUERY. - Inserting menu DIAGNOSTICS. - Inserting menu TRACE_MENU. - Inserting menu LOGGING_MENU. - Inserting menu PROPERTIES_MENU. - Inserting menu CUSTOM_CODE_MENU. - Inserting menu CLEAR. - Inserting menu DUPLICATE. - Inserting menu PREFERENCES. - Inserting menu RECORD. - Ready.После перезахода в Forms увидим наш пункт меню:
Если политика партии запрещает правку FNDMENU.mmb (как в нашем случае), то придется добавлять пункт меню динамически. Для этого:
- добавляем код в обработчик EVENT библиотеки CUSTOM.pll. Эта библиотека как раз и была создана для кастомизаций. Приведенный ниже код будет вызывать обработчик нашей кастомной библиотеки при любом событии Forms. Там мы будем накладывать ограничения.
XXFND999.event(event_name);- создаем кастомную библиотеку XXFND999.pll c обработчиком EVENT. Обработчик выполняет двойную функцию: создает пункт меню один раз и вызывает форму при активации одноименного триггера.
procedure event(p_event_name in varchar2) is l_item_label CONSTANT varchar2(100) := 'Расширенная история записи'; l_event_name varchar2(100); l_tgt_fn_app CONSTANT varchar2(200) := 'XXMY'; l_tgt_fn_name CONSTANT varchar2(300) := 'XXFND999_HISTORY_RECORD_ADV'; l_form_name CONSTANT varchar2(300) := l_tgt_fn_name; l_frm_path varchar2(5000); param_id paramlist; l_item_feature varchar2(150); l_created_by item; l_tmp varchar2(150); l_block varchar2(150); begin -- отменяем отработку функционала внутри формы if name_in('SYSTEM.CURRENT_FORM') = l_form_name then return; end if; l_block := name_in('SYSTEM.CURRENT_BLOCK'); -- проверка на существование блока if length(l_block) = 0 then return; end if; -- событие при создании экземпляра формы if p_event_name = 'WHEN-NEW-BLOCK-INSTANCE' then l_event_name := xx_form_custom.create_special(l_item_label,'LINE'); end if; /* кастомный метод для получения первой незанятой ячейки SPECIAL */ l_event_name := xx_form_custom.get_special(l_item_label); -- включение видимости и активация нового пункта xx_form_custom.activate_special(l_item_label); -- сохранение пункта меню xx_form_custom.save_menu(); -- если вызвали нашу форму if (p_event_name = l_event_name)then l_frm_path := fnd_navigate.formpath(l_tgt_fn_app, l_tgt_fn_name); -- проверяем наличие поля CREATED_BY в таблице с текущей записью l_created_by := find_item(name_in('SYSTEM.CURRENT_BLOCK')||'.CREATED_BY'); if not id_null(l_created_by) then -- проверяем наличие созданного списка параметров param_id := get_parameter_list('FND_FORM_ARGS'); if (not id_null(param_id)) then destroy_parameter_list(param_id); end if; -- создаем список параметров и добавляем информацию из служебных полей param_id := create_parameter_list('FND_FORM_ARGS'); add_parameter( param_id, 'CREATED_BY', TEXT_PARAMETER, name_in(name_in('SYSTEM.CURSOR_BLOCK')||'.CREATED_BY')); add_parameter( param_id, 'LAST_UPDATED_BY', TEXT_PARAMETER, name_in(name_in('SYSTEM.CURSOR_BLOCK')||'.LAST_UPDATED_BY')); add_parameter( param_id, 'CREATION_DATE', TEXT_PARAMETER,name_in(name_in('SYSTEM.CURSOR_BLOCK')||'.CREATION_DATE')); add_parameter( param_id, 'LAST_UPDATE_DATE', TEXT_PARAMETER, name_in(name_in('SYSTEM.CURSOR_BLOCK')||'.LAST_UPDATE_DATE')); -- открываем форму open_form ( formmodule_name => l_frm_path , activate_mode => ACTIVATE , session_mode => SESSION , data_mode => NO_SHARE_LIBRARY_DATA , paramlist_id => param_id ); end if; end if; end event;Этап 2: создание формы. Создаем Data Block, создаем триггер WHEN-NEW-BLOCK-INSTANCE и item USER_INFO, которому будем присваивать строку с данными о пользователе, разделенными через перевод строки. Создаем канву, бросаем туда item USER_INFO, ставим в свойствах Multiline=yes, Length=32000. Создаем несколько параметров, идентичных прописанным в кастомной библиотеке: CREATED_BY, LAST_UPDATED_BY, CREATION_DATE, LAST_UPDATE_DATE. Начинаем править код PL/SQL созданного блока данных и прописываем туда такое:
declare
l_create_id varchar2(50) := :PARAMETER.CREATED_BY;
l_update_id varchar2(50) := :PARAMETER.LAST_UPDATED_BY;
l_create_date varchar2(50) := :PARAMETER.CREATION_DATE;
l_update_date varchar2(50) := :PARAMETER.LAST_UPDATE_DATE;
begin
:XXFND361_TEST_V.CREATED_BY_FULLNAME := XXFND361_PKG.get_record_history(p_create_id => l_create_id,
p_update_id => l_update_id,
p_create_date => l_create_date,
p_update_date => l_update_date
);
XXFND999_PKG - обычный PL/SQL пакет:function get_record_history(p_create_id in varchar2, p_update_id in varchar2, p_create_date in varchar2, p_update_date in varchar2 ) return varchar2 is l_result_line varchar2(32000); l_create_person varchar2(150); l_create_org_id varchar2(150); l_create_job varchar2(150); l_create_contact varchar2(150); l_update_person varchar2(150); l_update_org_id varchar2(150); l_update_job varchar2(150); l_update_contact varchar2(150); l_is_person varchar2(1); l_create_organization_id number; l_update_organization_id number; l_create_id varchar2(150) := p_create_id; l_update_id varchar2(150) := p_update_id; l_create_date varchar2(150) := p_create_date; l_update_date varchar2(150) := p_update_date; begin if l_create_id is null then return ''; end if; select case when user_det.full_name is null then fnu.user_name else decode(regexp_substr(upper(user_det.full_name),'I'), null, user_det.full_name, fnu.user_name) || ' ' || user_det.employee_number end, decode(user_det.full_name, null, 'N', 'Y') into l_create_person, l_is_person from FND_USER fnu, per_all_people_f user_det where fnu.employee_id = user_det.person_id(+) and trunc(sysdate) between user_det.effective_start_date(+) and user_det.effective_end_date(+) and fnu.user_id = to_number(l_create_id); -- if l_is_person = 'Y' then begin select asg.organization_id into l_create_organization_id from per_all_people_f user_det, per_all_assignments_f asg, FND_USER fnu where user_det.person_id = asg.person_id and fnu.employee_id = user_det.person_id and trunc(sysdate) between user_det.effective_start_date and user_det.effective_end_date and trunc(sysdate) between asg.effective_start_date and asg.effective_end_date and fnu.user_id = to_number(l_create_id); exception when NO_DATA_FOUND then null; end; -- begin select j.name into l_create_job from per_all_people_f user_det, per_all_assignments_f asg, FND_USER fnu, per_jobs j where user_det.person_id = asg.person_id and fnu.employee_id = user_det.person_id and asg.job_id = j.job_id and trunc(sysdate) between user_det.effective_start_date and user_det.effective_end_date and trunc(sysdate) between asg.effective_start_date and asg.effective_end_date and trunc(sysdate) >= j.date_from and j.date_to is null and fnu.user_id = to_number(l_create_id) and rownum = 1; exception when NO_DATA_FOUND then null; end; -- begin select coalesce(user_det.email_address, fnu.Email_Address, ' ') || ' ' || user_det.work_telephone into l_create_contact from per_all_people_f user_det, per_all_assignments_f asg, FND_USER fnu where user_det.person_id = asg.person_id and fnu.employee_id = user_det.person_id and trunc(sysdate) between user_det.effective_start_date and user_det.effective_end_date and trunc(sysdate) between asg.effective_start_date and asg.effective_end_date and fnu.user_id = to_number(l_create_id); exception when NO_DATA_FOUND then null; end; end if; -------------------------- l_is_person := ''; -------------------------- select case when user_det.full_name is null then fnu.user_name else decode(regexp_substr(upper(user_det.full_name),'I'), null, user_det.full_name, fnu.user_name) || ' ' || user_det.employee_number end, decode(user_det.full_name, null, 'N', 'Y') into l_update_person, l_is_person from FND_USER fnu, per_all_people_f user_det where fnu.employee_id = user_det.person_id(+) and trunc(sysdate) between user_det.effective_start_date(+) and user_det.effective_end_date(+) and fnu.user_id = to_number(l_update_id); if l_is_person = 'Y' then begin select asg.organization_id into l_update_organization_id from per_all_people_f user_det, per_all_assignments_f asg, FND_USER fnu where user_det.person_id = asg.person_id and fnu.employee_id = user_det.person_id and trunc(sysdate) between user_det.effective_start_date and user_det.effective_end_date and trunc(sysdate) between asg.effective_start_date and asg.effective_end_date and fnu.user_id = to_number(l_update_id); exception when NO_DATA_FOUND then null; end; -- begin select j.name into l_update_job from per_all_people_f user_det, per_all_assignments_f asg, FND_USER fnu, per_jobs j where user_det.person_id = asg.person_id and fnu.employee_id = user_det.person_id and asg.job_id = j.job_id and trunc(sysdate) between user_det.effective_start_date and user_det.effective_end_date and trunc(sysdate) between asg.effective_start_date and asg.effective_end_date and trunc(sysdate) >= j.date_from and j.date_to is null and fnu.user_id = to_number(l_update_id); exception when NO_DATA_FOUND then null; end; -- begin select coalesce(user_det.email_address, fnu.Email_Address, ' ') || ' ' || user_det.work_telephone into l_update_contact from per_all_people_f user_det, per_all_assignments_f asg, FND_USER fnu where user_det.person_id = asg.person_id and fnu.employee_id = user_det.person_id and trunc(sysdate) between user_det.effective_start_date and user_det.effective_end_date and trunc(sysdate) between asg.effective_start_date and asg.effective_end_date and fnu.user_id = to_number(l_update_id); exception when NO_DATA_FOUND then null; end; end if; --------------- select decode(l_create_person, null, '', 'Создал запись ' || l_create_person || chr(10)) into l_create_person from dual; select decode(l_create_org_id, null, '', 'Структурное подразделение ' || l_create_org_id || chr(10)) into l_create_org_id from dual; select decode(l_create_job, null, '', 'Должность ' || l_create_job || chr(10)) into l_create_job from dual; if length(trim(l_create_contact)) > 0 then l_create_contact := 'Контакты ' || l_create_contact || chr(10); else l_create_contact := ''; end if; select decode(l_create_date, null, '', 'Дата создания записи ' || l_create_date || chr(10)) into l_create_date from dual; select decode(l_update_person, null, '', 'Изменил запись ' || l_update_person || chr(10)) into l_update_person from dual; select decode(l_update_org_id, null, '', 'Структурное подразделение ' || l_update_org_id || chr(10)) into l_update_org_id from dual; select decode(l_update_job, null, '', 'Должность ' || l_update_job || chr(10)) into l_update_job from dual; if length(trim(l_update_contact)) > 0 then l_update_contact := 'Контакты ' || l_update_contact || chr(10); else l_update_contact := ''; end if; select decode(l_update_date, null, '', 'Дата изменения записи ' || l_update_date) into l_update_date from dual; l_result_line := l_create_person || l_create_org_id || l_create_job || l_create_contact || l_create_date || chr(10) || l_update_person || l_update_org_id || l_update_job || l_update_contact || l_update_date; return l_result_line; exception when OTHERS then return 'Ошибка! p_create_id=' || l_create_id || ';' || 'l_update_id=' || l_update_id || ';' || 'l_create_date=' || l_create_date || ';' || 'l_update_date=' || l_update_date || 'error info: ' || sqlerrm; end get_record_history;Этап 3: создание и связывание функции, формы в Forms. После того, как всё это будет создано, установка производится следующим скриптом:
cp ./forms/XXFND999_HISTORY_RECORD_ADV.fmb $AU_TOP/forms/RU cp ./forms/XXFND999_HISTORY_RECORD_ADV.fmb $AU_TOP/forms/US cp ./resource/CUSTOM.pll $AU_TOP/resource/CUSTOM.pll cp ./resource/XXFND999_CUSTOM.pll $AU_TOP/resource/XXFND999_CUSTOM.pll cd $AU_TOP/forms/RU frmcmp_batch.sh module=$AU_TOP/forms/RU/XXFND999_HISTORY_RECORD_ADV.fmb userid=$APPSID module_type=form compile_all=yes output_file=$XX_TOP/forms/RU/XXFND999_HISTORY_RECORD_ADV.fmx cd $AU_TOP/forms/US frmcmp_batch.sh module=$AU_TOP/forms/US/XXFND999_HISTORY_RECORD_ADV.fmb userid=$APPSID module_type=form compile_all=yes output_file=$XX_TOP/forms/US/XXFND999_HISTORY_RECORD_ADV.fmx cd $AU_TOP/resource frmcmp_batch.sh module=$AU_TOP/resource/XXFND999_CUSTOM.pll userid=$APPSID module_type=library compile_all=yes output_file=$AU_TOP/resource/XXFND999_CUSTOM.plx frmcmp_batch.sh module=$AU_TOP/resource/CUSTOM.pll userid=$APPSID module_type=library compile_all=yes output_file=$AU_TOP/resource/CUSTOM.plxРезультат:
Грабли.
FRM-41106 Cannot create record without parent. Не соблюдено обращение к данным по модели BLOCK.ITEM. Если блок можем быть любым, то корректное получение значения из item такое:name_in(name_in('SYSTEM.CURSOR_BLOCK')||'.CREATED_BY'));
FRM-10056: Incorrect module type stored in the file. Неправильный параметр в sh-скрипте установки расширения. Внимание:frmcmp_batch.sh module=$AU_TOP/forms/US/XXFND999_HISTORY_RECORD_ADV.fmb userid=$APPSID module_type=form compile_all=yes output_file=$XX_TOP/forms/US/XXFND999_HISTORY_RECORD_ADV.fmx
FRM-91507: Internal Error: Unable to generate library. Ошибка в кастомной библиотекеFRM-30085: Unable to adjust form for output. Форма слишком велика, либо элементы выходят за границы формы.
FRM-47023:No such parameter named G_QUERY_FIND exists in form. Хитрая ошибка, которая поначалу вводит в тупик, потому как G_QUERY_FIND - стандартный параметр, который есть всегда. Имеет смысл проверить тип данных параметров. Даже если передается дата, то тип данных должен быть Char. После этой манипуляции проблема ушла.
ORA-06502: PL/SQL: numeric or value error: character string buffer too small. По умолчанию размер поля вывода ограничен 30 символами. Эта ошибка появится, если переменная будет содержать строковое значение большей длины. Имеет смысл поставить 32 000.
23.04.2014
Загрузка и распарсивание файла xls с помощью Java-based concurrent
Цель: прочитать файл электронной таблицы Excel 95-2003 и загрузить результат в таблицу БД Oracle 11g. В нашем примере мы хотим прочитать данные из одной ячейки и положить их в таблицу create table HF_TMP (val VARCHAR2(50))
Алгоритм решения:
1. Загрузка файла через пользовательский интерфейс. Применяется messageFileUploadBean с типом данных BLOB.
При нажатии кнопки Загрузить происходит вставка записи в таблицу загрузок вместе с бинарным файлов Excel в blob-поле, присваивание upload_id и запуск Java-based канкарента, в который этот upload_id передается.
CO:2. Работа канкарента
Serializable[] aserializable = { uploadId }; Class[] aclass = { uploadId.getClass() }; String requestId = (String)am.invokeMethod("sendConcurrentRequest", aserializable, aclass); am.getTransaction().commit();AM:
public void sendConcurrentRequest(Number uploadId) throws RequestSubmissionException { ConcurrentRequest cpRequest = new ConcurrentRequest(this.getOADBTransaction().getJdbcConnection()); Vector requestParameters = new Vector(1); requestParameters.add(uploadId.stringValue()); int requestId = cpRequest.submitRequest(UploadHistFactBean.APPLICATION_SHORT_NAME, UploadHistFactBean.CONCURRENT_PROGRAM_NAME, null, null, false, requestParameters); }
// считываем BLOB
public InputStream getFileForUploadId(){
StringBuffer strBuf = new StringBuffer();
InputStream dataStream = null;
String lQuery = "select file_data from DATA_UPLOADS where request_id = :1";
String res = "";
try
{
OracleCallableStatement statement = (OracleCallableStatement)connection.prepareCall(lQuery);
statement.setInt(1, requestId);
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
BLOB blob = ((OracleResultSet)resultSet).getBLOB(1);
dataStream = blob.getBinaryStream();
}
} catch (SQLException e)
{
throw OAException.wrapperException(e);
}
return dataStream;
} public void initialize(CpContext cpContext) throws SQLException,
XMLParseException,
SAXException,
IOException,
InstantiationException,
IllegalAccessException,
ClassNotFoundException,
NoSuchMethodException,
InvocationTargetException {
connection = cpContext.getJDBCConnection();
requestId = cpContext.getReqDetails().getRequestId();
logFile = cpContext.getLogFile();
reqCompletion = cpContext.getReqCompletion();
NameValueType parameter;
while (cpParameters.hasMoreElements()) {
parameter = cpParameters.nextParameter();
if ("p_upload_id".equalsIgnoreCase(parametr.getName())) {
this.uploadId = Integer.valueOf(parametr.getValue()).intValue();
}
}
public void runProgram(CpContext cpContext) {
try {
initialize(cpContext);
HashMap<String, String> inputParameters = new HashMap<String, String>();
inputParameters.put(DataInput.PARAMETER_PREFIX + "1", "oracle.apps.xxmy.test.bean.TemplateXML");
InputStream file = getFileForUploadId();
{ логика по распарсиванию файла Excel }
connection.commit();
// Успешно завершим конкаррент
Message normComplMessage = new Message("FND", "CONC-CP SUCCESSFUL TERMINATION");
reqCompletion.setCompletion(ReqCompletion.NORMAL, normComplMessage.getMessageText(cpContext.getResourceStore()));
} catch (Exception e) {
// Если произошла ошибка, нужно выставить конкаренту ошибочный статус и выдать в лог стек ошибки
reqCompletion.setCompletion(ReqCompletion.ERROR, e.getMessage());
StringWriter errStringWriter = new StringWriter();
PrintWriter errPrintWriter = new PrintWriter(errStringWriter, true);
e.printStackTrace(errPrintWriter);
logFile.writeln(errStringWriter.toString(), LogFile.ERROR);
} finally {
cpContext.releaseJDBCConnection();
}
}
3. Распарсивание XLS с помощью библиотеки POI. Шаблон oracle.apps.xxmy.test.bean.TemplateXML из шага 2 содержит набор параметров для вызова API вставки в таблицу с указанием данных о том, в каких ячейках брать значения. В нашем случае:<?xml version="1.0" encoding='windows-1251'?> <upload method="XXMY_TEST_PKG.upload_excel"> <element name="test" type="single"> <location src="sheet" sheet="Sheet1" row="0" col="0"/> </element> </upload>Пробегаясь по структуре данных и файлу данных, мы формируем вызов API:
begin XXMY_TEST_PKG.upload_excel(P_TEST => :1, P_REQUEST_ID => :2, P_UPLOAD_ID => :3, P_UPLOAD_METHOD => :4); end;Имея коллекции параметров и их значений, выполняем вызов:
public void doDbUpload() throws SQLException {
OracleCallableStatement statement = (OracleCallableStatement)connection.prepareCall(this.query);
ArrayList<Integer> keyList = new ArrayList<Integer>();
keyList.addAll(uploadParameters.keySet());
Collections.sort(keyList);
Iterator<Integer> parameterIterator = keyList.iterator();
while (parameterIterator.hasNext()) {
Integer parameterIndex = parameterIterator.next();
UploadParameter parameter = this.uploadParameters.get(parameterIndex);
statement.setString(parameterIndex.intValue(), parameter.getValue());
}
statement.execute();
statement.close();
}
Всё. Данные из файла отправляются в таблицу.18.02.2014
WebADI: правка LDT вручную
Функционал интегратора OeBS, прямо скажем, не совершенен.
Глюки, с которыми сталкивался я:
1. Добавление атрибутов в интерфейс
4. Добавление атрибутов в мэппинг BNE_MAPPING_LINES (внимательно!)
Порядок столбцов в формируемом документе excel можно узнать запросом:
Глюки, с которыми сталкивался я:
- быстрее и проще создать интегратор с нуля, чем править уже имеющийся. При удалении объектов интегратора не удаляются внутренние связи, и если удалить, например, один контент и создать новый под другим именем, мэппинг будет упорно ссылаться на старый;
- если в интеграторе нет ошибок, это ещё не значит, что он будет корректно выполнять свою задачу. Это касается, например, добавления новых колонок в шаблон и интерфейсную таблицу. Колонки созданы, изменения в интерфейсе, мэппинге и контенте проведены, шаблон выгружается и загружается нормально, но данные просто не попадают в таблицу. Никаких ошибок, никакого результата;
- при создании разметки полей документа двигать их по одному, пока не добьешься нужного порядка - занятие неэффективное, но другого инструмента BNE не предоставляет. Почему бы не выводить окошко с номером сразу для всех колонок? Здесь глюк был в том, что если спускать колонку сверху вниз, то на середине пути она замирает и не реагирует на нажатия кнопок "вверх", "вниз".
1. Добавление атрибутов в интерфейс
BEGIN BNE_INTERFACE_COLS "2" //увеличивать это число OWNER = "ANONYMOUS" OBJECT_VERSION_NUMBER = "2" INTERFACE_COL_TYPE = "1" INTERFACE_COL_NAME = "LOAD_CODE" ENABLED_FLAG = "Y" REQUIRED_FLAG = "N" DISPLAY_FLAG = "Y" READ_ONLY_FLAG = "N" NOT_NULL_FLAG = "N" SUMMARY_FLAG = "N" MAPPING_ENABLED_FLAG = "Y" DATA_TYPE = "1" FIELD_SIZE = "22" DEFAULT_TYPE = "PARAMETER" DEFAULT_VALUE = "XXIP361_CODE" VAL_COMPONENT = "" "" DISPLAY_ORDER = "2" //увеличивать это число PROMPT_LEFT = "Код загрузки" PROMPT_ABOVE = "LOAD_CODE" LAST_UPDATE_DATE = "2013/05/24" VAL_QUERY = "" "" EXPANDED_SQL = "" "" END BNE_INTERFACE_COLS2. Добавление атрибутов в контент
BEGIN BNE_CONTENT_COLS "3" //увеличивать это число OBJECT_VERSION_NUMBER = "1" OWNER = "ANONYMOUS" COL_NAME = "PROJECT_CODE" USER_NAME = "PROJECT_CODE" LAST_UPDATE_DATE = "2013/05/24" READ_ONLY_FLAG = "N" END BNE_CONTENT_COLS3. Правка таблицы BNE_STORED_SQL. Запрос апдейтим напрямую, добавив новые поля интерфейсной таблицы.
4. Добавление атрибутов в мэппинг BNE_MAPPING_LINES (внимательно!)
BEGIN BNE_MAPPING_LINES "2" //увеличиваем это число INTERFACE_ASN = "XXMY" INTERFACE_CODE = "XXMY_LOAD_X_INTF1" INTERFACE_SEQ_NUM = "12" //ID атрибута интерфейса OWNER = "ANONYMOUS" DECODE_FLAG = "N" OBJECT_VERSION_NUMBER = "1" CONTENT_ASN = "XXRT" CONTENT_CODE = "XXMY_LOAD_CNT2" CONTENT_SEQ_NUM = "2" //ID атрибута контента LAST_UPDATE_DATE = "2013/05/24" END BNE_MAPPING_LINES5. Добавление атрибутов в LAYOUT (внимательно!)
BEGIN BNE_LAYOUT_COLS "520" //увеличиваем число с шагом в 10 OWNER = "APANIN" OBJECT_VERSION_NUMBER = "1" INTERFACE_ASN = "XXMY" INTERFACE_CODE = "XXMY_LOAD_X_INTF1" INTERFACE_SEQ_NUM = "45" //ID атрибута интерфейса DISPLAY_WIDTH = "15" LAST_UPDATE_DATE = "2014/02/13" READ_ONLY_FLAG = "N" END BNE_LAYOUT_COLSПосле этих манипуляций интегратор должен работать с новыми колонками.
Порядок столбцов в формируемом документе excel можно узнать запросом:
with col_order as (select intr.interface_col_name, col.sequence_num from BNE_LAYOUTS_B lay, BNE_LAYOUT_BLOCKS_B blo, BNE_LAYOUT_COLS col, BNE_INTERFACE_COLS_B intr where lay.layout_code = blo.layout_code and blo.layout_code = col.layout_code and blo.block_id = col.block_id and intr.sequence_num = col.interface_seq_num and intr.interface_code = 'XXMY_LOAD_X_INTF1' and lay.layout_code = 'XXMY_CORRECT_LOAD' and blo.block_id = 2 //номер блока (1 - шапка документа) order by col.sequence_num) select co.interface_col_name, co.sequence_num from col_order co;Для изменения порядка делаем апдейт таблицы BNE_LAYOUT_COLS.
25.01.2014
Особенность функции PIVOT
Для облегчения запросов предварительно выполним
alter session set NLS_DATE_LANGUAGE = RUSSIANПоложим, в текущем месяце мы сделали 5 и 8 скворечников, а в следующем сделаем -5 и -8.
select pk, initcap(to_char(pivot_key, 'MONTH')) pivot_key, sum(value) value from (select 1 pk, sysdate pivot_key, 5 value from dual union select 1, sysdate, 8 from dual union select 1, sysdate + 31, -5 from dual union select 1, sysdate + 31, -8 from dual) group by pk, initcap(to_char(pivot_key, 'MONTH'))Развернем выборку, задав месяцы как колонки:
with a as
(select pk,
initcap(to_char(pivot_key, 'MONTH')) pivot_key,
sum(value) value
from (select 1 pk, sysdate pivot_key, 5 value from dual
union
select 1, sysdate, 8 from dual
union
select 1, sysdate + 31, -5 from dual
union
select 1, sysdate + 31, -8 from dual)
group by pk, initcap(to_char(pivot_key, 'MONTH'))
)
select pk, "'Январь'_MON" Jan,
"'Февраль'_MON" Feb
from (select *
from a pivot(sum(value) mon
FOR pivot_key IN('Январь',
'Февраль')
)
);PK JAN FEB
---------- ---------- ----------
1 Как видим, результат неверный. Обозначим месяц по-другому.with a as
(select pk,
to_char(pivot_key, 'MM') pivot_key,
sum(value) value
from (select 1 pk, sysdate pivot_key, 5 value from dual
union
select 1, sysdate, 8 from dual
union
select 1, sysdate + 31, -5 from dual
union
select 1, sysdate + 31, -8 from dual)
group by pk, to_char(pivot_key, 'MM')
)
select pk, "'01'_MON" Jan,
"'02'_MON" Feb
from (select *
from a pivot(sum(value) mon
FOR pivot_key IN('01',
'02')
)
);PK JAN FEB
---------- ---------- ----------
1 13 -13 Непонятно, в чем причина ошибки, но точно не в кириллических названиях.Вывод: для опорного поля в функции PIVOT использовать максимально простые, желательно циферные, псевдонимы.
14.01.2014
Описательные гибкие поля (DF) и OAF
Descriptive Flex Fields (описательные ГП) - инструмент в среде OeBS для универсализации использования одних и тех же полей таблиц для хранения свойств разных сущностей. Обычно это набор полей ATTRIBUTE1..ATTRIBUTE15 varchar2(150) для хранения свойств и поле ATTRIBUTE_CATEGORY для хранения контекста. В зависимости от контекста одно и то же поле может иметь разный бизнес-смысл.
15.11.2013
Пример реализации Virtual Private Database (VPD)
Cуть технологии в том, что к запросу данных из защищенной таблицы или представления динамически добавляется предикат. Предикат формируется в заранее определенном методе.
Создадим тестовую таблицу с данными:
Создадим пакет с функциями, реализующими предикат:
Создадим тестовую таблицу с данными:
create table apps.test_users_all ( id number not null, name varchar2(255) ) / create or replace synonym apps.test_users for apps.test_users_all / insert into apps.test_users_all (id, name) values (1, user) / insert into apps.test_users_all (id, name) values (2, 'SCOTT') / insert into apps.test_users_all (id, name) values (3, 'TIGER') /Обратимся за данными к синониму:
SQL> select * from test_users 2 / ID NAME ---------- ----------- 1 APPS 2 SCOTT 3 TIGERНастроим ограничение так, чтобы отображались только строки, в которых name = user. В данном случае этому ограничению соответствует одна строка с ID = 1.
Создадим пакет с функциями, реализующими предикат:
create or replace package XXTEST_VPD is function users_security (obj_schema varchar2, obj_name varchar2) return varchar2; end XXTEST_VPD / create or replace package body XXTEST_VPD is function users_security (obj_schema varchar2, obj_name varchar2) return varchar2 is begin return 'name = user'; end users_security; end XXTEST_VPD /Применим политику безопасности:
declare l_tmp number; begin begin select 1 into l_tmp from dual where not exists (select null from dba_policies p where p.object_name = 'TEST_USERS'); exception when NO_DATA_FOUND then dbms_rls.drop_policy(object_schema => 'APPS', object_name => 'TEST_USERS', policy_name => 'USERS_SECURITY'); end; dbms_rls.add_policy(object_schema => 'APPS', object_name => 'TEST_USERS', policy_name => 'USERS_SECURITY', policy_function => 'XXTEST_VPD.USERS_SECURITY', function_schema => 'APPS'); end;Здесь мы применяем ограничение на синоним TEST_USERS. Пробуем выполнить запрос к защищенной таблице:
SQL> select * from test_users
2 /
ID NAME
---------- ----------
1 APPS
Мы видим, что синоним ограничен по определенному нами предикату.09.09.2013
Канкарент на основе Java-класса
По шагам:
1. Создаем наш класс
1. Создаем наш класс
package oracle.apps.test;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import oracle.apps.fnd.common.Message;
import oracle.apps.fnd.cp.request.CpContext;
import oracle.apps.fnd.cp.request.JavaConcurrentProgram;
import oracle.apps.fnd.cp.request.LogFile;
import oracle.apps.fnd.cp.request.ReqCompletion;
public class MyJavaTest implements JavaConcurrentProgram {
public static final String APPLICATION_SHORT_NAME = "XXXX";
public static final String CONCURRENT_PROGRAM_NAME = "MY_CONC_PROGRAM";
private Connection connection;
private int requestId;
private LogFile logFile;
private ReqCompletion reqCompletion;
public void initialize(CpContext cpContext) throws SQLException,
XMLParseException,
SAXException,
IOException,
InstantiationException,
IllegalAccessException,
ClassNotFoundException,
NoSuchMethodException,
InvocationTargetException {
connection = cpContext.getJDBCConnection();
requestId = cpContext.getReqDetails().getRequestId();
logFile = cpContext.getLogFile();
reqCompletion = cpContext.getReqCompletion();
Enumeration params = cpContext.getReqDetails().getParaInfo().elements();
String Value = (String)params.nextElement();
int fileId = Integer.parseInt(Value);
// some code
}
public void runProgram(CpContext cpContext) {
try {
initialize(cpContext);
// some code
connection.commit();
// успешное заверщение канкарента
Message normComplMessage = new Message("FND", "CONC-CP SUCCESSFUL TERMINATION");
reqCompletion.setCompletion(ReqCompletion.NORMAL, normComplMessage.getMessageText(cpContext.getResourceStore()));
} catch (Exception e) {
reqCompletion.setCompletion(ReqCompletion.ERROR, e.getMessage());
StringWriter errStringWriter = new StringWriter();
PrintWriter errPrintWriter = new PrintWriter(errStringWriter, true);
e.printStackTrace(errPrintWriter);
logFile.writeln(errStringWriter.toString(), LogFile.ERROR);
} finally {
cpContext.releaseJDBCConnection();
}
}
}
2. Создаем канкарент05.09.2013
Пример типа с коллекциями
Например, нужно реализовать настройку в виде нескольких списков (списки товаров и торговых точек, где применяется акция). ID товаров и точек будем хранить в таблице со структурой (Тип сущности {Товар, Точка}, ID). С помощью приведенной ниже методики мы можем хранить оба списка в одном объекте и легко к ним обращаться как к коллекциям. Алгоритм сбора данных инкапсулируется в самом объекте, а выполняется в конструкторе, что позволяет выполнить сбор данных одной строкой кода.
Итак:
Тип строки
create or replace type MY_COLLECTION_ROW is object( value varchar2(150) );Табличный тип
create or replace type MY_COLLECTION is table of MY_COLLECTION_ROWНовый тип, содержащий объекты типа MY_COLLECTION
create or replace type MY_COLLECTION_TYPE as object (
GOODS_LIST MY_COLLECTION,
SHOPS_LIST MY_COLLECTION,
constructor function MY_COLLECTION_TYPE return self as result,
member procedure init_SHOPS_LIST_collection(p_list in out MY_COLLECTION),
member procedure init_GOODS_LIST_collection(p_list in out MY_COLLECTION)
)
create or replace type body MY_COLLECTION_TYPE is constructor function MY_COLLECTION_TYPE return self as result is begin GOODS_LIST := new MY_COLLECTION(null); SHOPS_LIST := new MY_COLLECTION(null); init_GOODS_LIST_collection(p_list => GOODS_LIST); init_SHOPS_LIST_collection(p_list => SHOPS_LIST); return; end MY_COLLECTION_TYPE; -- member procedure init_GOODS_LIST_collection(p_list in out MY_COLLECTION) is l_line varchar2(150); cursor cur is select rownum from dual connect by rownum < 10; begin open cur; loop fetch cur into l_line; exit when cur%NOTFOUND; p_list.extend; p_list(p_list.last) := MY_COLLECTION_ROW(l_line); end loop; close cur; end init_GOODS_LIST_collection; -- member procedure init_SHOPS_LIST_collection(p_list in out MY_COLLECTION) is l_line varchar2(150); cursor cur is select rownum from dual connect by rownum < 10; begin open cur; loop fetch cur into l_line; exit when cur%NOTFOUND; p_list.extend; p_list(p_list.last) := MY_COLLECTION_ROW(l_line); end loop; close cur; end init_SHOPS_LIST_collection; end;Обращаемся к данным
declare l_options MY_COLLECTION_TYPE; cursor cur(obj MY_COLLECTION) is select * from table(obj); l_tmp_coll varchar2(150); begin l_options := new MY_COLLECTION_TYPE(); open cur(obj => l_options.GOODS_LIST); loop fetch cur into l_tmp_coll; exit when cur%NOTFOUND; dbms_output.put_line(l_tmp_coll); end loop; close cur; end;
24.07.2013
08.07.2013
Ошибка ORA-08103 и партиционирование
Причины появления ошибки ORA-08103: object no longer exists при работе с партиционированными таблицами.
22.06.2013
ООП на PL/SQL: введение
В который раз не знаю, что писать в заголовок поста. Ну, давай начнем так: "реализация ООП впервые появилась в РСУБД Oracle 9g".
20.05.2013
Колонки с однотипными флагами в hGrid
На пути отображения структуры вида <Attribute, AttributeValue> в транспонированном виде с помощью hGrid возникла определенная сложность. Ниже описана методика её преодоления.