Archives for Fans Or Not

WebADI开发指南

最新的EBS文档12.1.3中增加了《Oracle E-Business Suite Desktop Integration Framework Developer’s Guide》,终于结束了WebADI没有官方开发指南的局面。

Oracle EBS中数据的上传下载一直是一个比较头疼的问题,尤其是在处理用户自行批量上传数据方面,虽然可以通过二次开发实现,但从开发工作量和便捷性方面看都是一种相对别扭的方案。11i中就表现出和Office系列集成态度的WebADI是值得推荐的方案,尽管当年功能的薄弱和开发工具(?)的低能,但依旧被广泛应用。R12中这一块得到了非常大的改进,比如很多曾经用WebADI集成器来进行开发配置的功能都有了Web版本。这也算成形了吧。

虽然改进的方面有很多,个人来看下面这几块改进是非常有帮助的:

  • 集成器的定义,可以直接Web方式定义,并且各个模块(接口、布局、Uploader等)区分清晰。
  • 接口类型分为三种:Table/API-procedure/API-function,尤其对于错误返回机制作了改进。
  • 接口各字段属性验证,这个虽然可以在代码中实现,关键是此处可以直接在表单层面进行控制了。
  • LOV定义,11i中需要通过修改底表数据来实现,而今只需要定义Component即可。
  • Importer功能,可以使用同步/异步方式提交并发请求,也可以直接使用PL/SQL API。
  • 参数定义功能增强,11i有个很无语的bug,修改参数定义会出现莫名的问题。

当然,最大的改进还是WebADI的模块化设计,这使得开发和维护非常简单、有效率。

文档还分出一个小章节来描述使用FNDLOAD移植WebADI,虽然早就被大家摸索出来了(回忆N年前那段黑暗中探索的快乐时光~),但是对于这种清晰的官方描述还是值得肯定的。

关于Table API

在Oracle ERP二次开发领域,有一个非常重要的开发规范就是关于Table表数据操作的。一般而言,技术团队都会规定避免将数据操作sql直接写到逻辑层代码中,至少将具体的底表操作语句独立放到一个package里。EBS的开发规范中称之为Table Handler,就是在需要直接操作数据表的地方调用该Table Hander来执行DML动作。不管项目有多大,都应该遵循这个规范。

此次SQL Developer升级到3.0后,也增加了一个名为Table API的自动生成功能。

自动生成RECORD和Insert/Update/Delete Procedure:

PACKAGE test_tapi IS
  TYPE test_tapi_rec IS RECORD(
    content     test.content%TYPE,
    id          test.id%TYPE,
    upload_time test.upload_time%TYPE);
  TYPE test_tapi_tab IS TABLE OF test_tapi_rec;
  -- insert
  PROCEDURE ins(p_content     IN test.content%TYPE DEFAULT NULL,
                p_id          IN test.id%TYPE,
                p_upload_time IN test.upload_time%TYPE DEFAULT NULL);
  -- update
  PROCEDURE upd(p_content     IN test.content%TYPE DEFAULT NULL,
                p_id          IN test.id%TYPE,
                p_upload_time IN test.upload_time%TYPE DEFAULT NULL);
  -- delete
  PROCEDURE del(p_id IN test.id%TYPE);
END test_tapi;

这个自动生成的代码在二次开发时无法直接拿来用,缺少了异常处理(比如Update时没有对应的行时通常需要抛出NO_DATA_FOUND异常以供后续处理)和LOCK机制(这是由EBS应用程序开发框架所决定的)。

对于SQL Developer这种非专属IDE而言,增加这个功能或许会对很多新入行的人起到不小的指示和帮助作用。

APEX 上传xls文件

上一篇文章介绍了APEX中上传文件的方式,以纯文本文件为例说明了如何对上传的文件进行后续处理。PL/SQL可以直接对纯文本内容进行处理,那么如果直接上传xls二进制文件呢?通常,可以用Java Procedure做这件事,但是自APEX Listener 1.x 发布后,有更简单的方式实现对xls内容的处理。

1.10 版本之后,apex-config.xml 中多了下面这个参数:


false

将该参数设置为true则开启excel到collection的转换功能,简单设置步骤如下:
1. 添加File Browse…的Page Item,名字为P1_FILENAME,Storage Type 为 WWV_FLOW_FILES
2. 添加名为UPLOAD的Button Item,Button Request 设置为 xls2collection

运行该页面,选择一个xls后缀的Excel文件,点击UPLOAD按钮即可上传该Excel文件。

上传的文件和数据分别存放在wwv_flow_files和apex_collections中。其中COLLECTION_NAME的默认名称为Page Item的名称,此例中为P1_FILENAME,C001为Excel的TAB页名称,接下来的各字段为表格内容。

SELECT * FROM apex_collections a WHERE a.collection_name = 'P1_FILENAME'

apex_collections是视图,限制了security_group_id,你需要在应用程序中,并且是当前会话中使用。

删除Collection方式为:

apex_collection.delete_collection(p_collection_name => 'P1_FILENAME');

详细说明可参考APEX 4.0的API Reference文档中的APEX_COLLECTION章节。

有了APEX Listener,很多事情变得更简单了,当然,除了文档,APEX的文档令人吃惊的简单,很多内容都没有提及。

APEX 4 中的文件上传功能

有一个APEX小应用需要提供Excel数据上传功能,并且要对上传的数据进行后续加工处理,于是就有了下面这段有点通用的代码。

APEX 4.0中上传的存储位置有两个选择,一个是集中式的WWV_FLOW_FILES,一个是自定义的表。对于前者我不喜欢,所有应用上传的文件都存储在一个地方,有些时候管理起来不够方便;如果是自定义的表,每个应用单独存放,那么不论在迁移还是某些维护都相对简单,当然,对于上传功能的设计而言就需要多花几分钟时间了。下面是自定义表的一个例子。

1. 先创建存储表

attach_data blob 二进制数据
attach_mimetype varchar2(255) 文件类型
attach_filename varchar2(255) 文件名
attach_last_update date 上传时间
attach_charset varchar2(128) 字符集
attach_id number 文件ID

一般而言,最好将该表最小化,包含必须的几个字段即可,如果需要和其他表关联,则可以通过ATTACH_ID关联。

2. APEX页面中增加File…上传的item,属性设置如下:

3. 添加提交表单的按钮,提交时触发一次Automatic Row Processing (DML)的Process,设置表名和主键,系统会自动将相关数据(包括上传的文件)插入对应的表中。关于主键ID的自动生成,可以在After header位置增加一个自动fetch row的设置即可。

4. 文件上传功能本身是非常简单的,接下来是针对该上传文件的后续处理。在顺序上,要先有处理上传的Process(就是第3步),然后再有后续处理的Process。手工在Page Process处再添加一个名为Parsing Attachment(名字随意)的Process,在Source处理加入匿名PL/SQL代码。例如我喜欢直接调用写好的包,那就写上:

process_upload.process_file_upload;

这里需要用户首先对Excel做一个另存为文本文件的操作,目前为止,我还没发现可以用PL/SQL来直接操作二进制Excel文件。如果有必要,可以考虑使用Java Procedure对Excel文件进行处理。

附上几段代码:

PROCEDURE process_file_upload IS
    l_blob_data       BLOB;
    l_blob_len        NUMBER;
    l_position        NUMBER;
    l_raw_chunk       RAW(2);
    l_raw_line        RAW(32767);
    l_line            VARCHAR2(32767) DEFAULT NULL;
    l_array           wwv_flow_global.vc_arr2;
    l_first_line_flag BOOLEAN;
  BEGIN
    SELECT s.attach_data
      INTO l_blob_data
      FROM sw_detail_attachment s
     WHERE s.attach_id = v('P7_ATTACH_ID');

    DELETE FROM sw_detail_attachment s
     WHERE s.attach_id = v('P7_ATTACH_ID');

    l_blob_len        := dbms_lob.getlength(l_blob_data);
    l_position        := 1;
    l_first_line_flag := TRUE;
    WHILE (l_position <= l_blob_len) LOOP
      l_raw_chunk := dbms_lob.substr(l_blob_data, 1, l_position);

      -- chr(10)
      IF l_raw_chunk = utl_raw.cast_to_raw(chr(10)) THEN
        l_line := utl_raw.cast_to_varchar2(l_raw_line);
        -- remove carriage return
        l_line  := REPLACE(l_line, chr(13), '');
        l_array := string_to_table(l_line);

        IF NOT l_first_line_flag THEN
          insert_data(l_array);
        END IF;
        l_raw_line        := NULL;
        l_first_line_flag := FALSE;
      ELSE
        l_raw_line := l_raw_line || l_raw_chunk;
      END IF;
      l_position := l_position + 1;

    END LOOP;
  END process_file_upload;

上传的文件是以TAB分割的Excel表格数据,我这里先取出文件数据放到l_blob_data中,然后针对换行符进行分割(Windows文件还需要清理掉回车符号),再对每一行使用标准函数wwv_flow_utilities.string_to_table进行解析。该函数可能存在一个问题,就是如果Excel最后一列数据为空,将导致解析后的列数缺少一列,我用下面这种方式修复该问题:

FUNCTION string_to_table(x_str IN VARCHAR2) RETURN wwv_flow_global.vc_arr2 IS
    l_col_num NUMBER := 20;
    l_array   wwv_flow_global.vc_arr2;
  BEGIN
    -- CHR(9) is tab key
    l_array := wwv_flow_utilities.string_to_table(x_str, chr(9));

    IF l_array.count = l_col_num - 1 THEN
      l_array(l_col_num) := NULL;
    END IF;

    RETURN l_array;
  END string_to_table;

Excel数据还可以采用复制粘贴的方式直接通过表单提交上去,不过,如果Excel文件内容比较复杂,存在多个标签页,则直接上传导入Oracle数据库应该还是有相当大的需求的,如果有时间做一个配置性强的上传插件的话估计下载量会不少。

Update
APEX 4.0.1 的一个bug:如果采用非WWV_FLOW_FILES表存储上传的文件,如果用IE内核的浏览器,无法上传任何数据。

迎接变化

最近工作和生活都发生了些变化,参加新的团队,实施新的项目,也可能要投入新的ERP阵营。在企业信息化的工作经历中,我乐意了解并接触不同的产品。就像博客,保持独立性是重要的,不要把人生绑在一张船票上,也不要把企业绑在一个软件厂商上。

由于目前还处于ERP系统选型阶段,所以一切未知,一切也不便多说,但是一些通用的,依旧存在于那里。在工作变换的简短假期中有所憧憬,也有所忧虑,对之前走过的路和将来要走的路都有一些不算深入的思考。一些经验和体会零零碎碎的,整理出了下面几点:

  • 对待变化不忧虑,迎接它。
  • 面对现实,不逃避。
  • 预估风险,面对风险,承担风险。
  • 质量往往和时间成正比。
  • 关键部分坦然面对。
  • 不捣浆糊,有帐迟早要还的。
  • 学好一门语言,其他的,可以在用到时再学——你不是程序员。
  • 了解一些技术,有助于深入吃透功能本身。
  • 愿意花一部分工作时间在其他需要你的地方,尽管它不属于本职工作。
  • 多读官方文档。
  • 沉得下去,浮得上来。

纯属个人体会和感触,不具有任何其他意义。一旦选型结束,将重新投入到实施和技术分享中来,自然会忙,会觉得辛苦,但值得。

面对变化,迎接变化。