使用 TweetMe4i 和 JSON 打下您的基础

使用开放源码 Java 和 RPG 搜索和记录 twitter 消息

审计的世界已经成为一种现实,越来越的金融企业需要开始习惯这样的世界。需要审计的内容之一就是发送到 Twitter、LinkedIn 和 Facebook 等社交媒体网站的文本。本教程将指导您学习如何通过 IBM i 上的 RPG 进行 Twitter 搜索,并将结果记录到一个 DB2 表中。

Aaron Bartell, 软件开发人员, Krengel Tech

Aaron Bartell 的照片Aaron Bartell 是 Krengel Technology Inc. 的一名高级软件开发人员。Aaron 非常关注关于使用 RPG 和 Java™ 在 IBM i 和 Android 平台上创建现代应用程序的开发工作。他是 RPG-XML Suite 的主要开发人员,也是 OpenRPGUI.com 的主要 “推动者”。他非常乐意成为强大的 IBM i 社区的一员,并积极参与论坛活动和文章撰写,还在自己的网站 MowYourLawn.com 提供了开放源码软件。他最近正在思考如何利用自己的能力为其他有需要的人提供帮助。他开办了 SofwareSavesLives.com 网站,为非洲的淡水井建设和还在挨饿的孩子们募集捐款。Aaron 与他的妻子和五个孩子居住在明尼苏达州南部,他喜欢在自己的乐队 Scattered Showers 中演奏电吉他和贝斯。



2012 年 4 月 09 日

这种做法在何时有意义

在 IBM i 平台上编程时,我喜欢在 RPG 中完成一切工作。需要应对的语言和运行时越少,就有越多的精力投入满足业务需求的工作之中,而不需要花大量的精力来修复技术。不要误解我,我热爱学习 RPG 领域以外的一切新事物,并且最近正在钻研 PhoneGap 和 Apache Callback,但是我知道,技术的采用应该建立在充分理解长期后果的基础之上(也就是说,这使 IT 部门需要跟踪的信息的半衰期加快了一倍)。我之所以选择用这段话作为这篇文章的开头,是因为我想展示如何使用 Java 作为中介,通过 RPG 查询 Twitter。选择这种路线(而不是 RPG 直接到 Twitter)的原因在于 RPG 中缺少使我们的目标切实可行的很多重要特性,尤其是缺少通过 Twitter 验证的 OAuth 机制的 RPG 版实现。OAuth 是一种(相对较)新的机制,用于从一个站点或服务执行针对另一个站点的身份验证。请查看 Twitter 的观点,进一步了解 OAuth 身份验证协议。


Twitter4j 实现目标

使用 Java 在现今这个软件生态系统中仍然极其吸引人,原因之一就在于它的普遍存在性,有许许多多的开放源码 Java 项目,很多都已非常成熟,而且可以免费供您使用。出于本文的目的,我们使用的是免费、开放源码的 Twitter4j 项目。应该说明的是,还有其他一些用于 Twitter 的 Java 库。我选择了 Twitter4j,因为我认为这是最有前途的库(非常活跃,拥有丰富的 API,而且经过深思熟虑,包含调试需求等特性,也有非常适合商业环境的许可,比如 Apache License 2.0)。如果您还没有想清楚这样一个问题:为什么要关心在开放源代码项目中使用什么样的许可?那么就应该花几分钟的时间,阅读有关 许可 的常见问题解答。我已经了解到,并非所有开放源代码项目都是采用相同的方式创建的,而且在选取 “安全”、良好的项目时绝对需要技巧。大多数程序员都需要了解这方面的知识,某些诉讼案件(即 GPLv3)增加了大众对于许可的忧虑。

Twitter4j 拥有多种特性,允许您通过多种前端连接到 Twitter,但本文仅关注如何进行 Twitter 搜索。具体来说,我们将深入研究这个 Twitter 页面 中找到的高级搜索功能,以便了解 “aaronbartell” 配置文件在特定时间段内的微博内容。

就这方面而言,我们最好回顾一下几个月之前我发布的一个名为 TweetMe4i 的新开放源码项目。TweetMe4i 的初始代码基础主要关注的是发送一个 Twitter 账户的状态更新。在 TweetMe4i 网页 中,您可以了解有关如何注册 Twitter 各个方面的文档,以及如何配置您的 IBM i 的文档。这篇文章以其初始代码基础(具体来说,就是 TweetMe4i.java)为依据,添加了一些功能,通过 search() Java 方法提供如清单 1 所示的搜索功能。您可以在本文附带的文件中找到 TweetMe4i.java 的完整源代码。

清单 1. 搜索 Java 代码
public static String search(String searchStr) {
  String result = "";
  StringBuilder jsonTweet = new StringBuilder();
  try {
    System.setOut(new PrintStream(new FileOutputStream("TweetMe4iLog.txt")));

    Twitter twitter = new TwitterFactory().getInstance();
    QueryResult qryResult = twitter.search(new Query(searchStr));

    boolean firstTime = true;
    jsonTweet.append("{ \"tweets\": [");
    for (Tweet tweet : qryResult.getTweets()) {
      if(!firstTime)
        jsonTweet.append(",");
      else;
        firstTime = false;
    jsonTweet.append(DataObjectFactory.getRawJSON(tweet));
    }
    jsonTweet.append("] }");
    result = resultDS("SUCCESS", jsonTweet.toString());
  } catch (Exception te) {
    result = resultDS("FAIL", "Java Exception:" + stackTraceToString(te));
  }
  return result;
}

如您所见,search() Java 方法将接收并返回一个字符串对象。下面是一个搜索字符串的示例:

 "from:aaronbartell since:2011-11-01 until:2001-11-30"

search() Java 方法的基本概念是使用搜索字符串调用 Twitter,随后将搜索结果作为 JSON 字符串返回给调用方程序。在本例中,调用方程序是使用 RPG 编写的,下一节我们将介绍 RPG。JSON 被用作结果集返回机制,因为它使您能更轻松地处理 RPG 内容,特别是在可以访问 RPG 中的免费、开放源代码 JSON 解析器和串行器时,稍后将介绍更多相关内容。

search() 方法中,您会注意到该代码调用了 System.setOut()。执行这个调用的目的是使程序能够将 System.out 消息重定向到一个流文件,从而显著简化调试工作。请注意,代码并未指定 TweetMe4iLog.txt 的完全限定路径。这也就是说,流文件将写入当前目录。您可以直接在 IBM i 命令行中输入不带任何选项的 WRKLNK,用这种方式来查看当前目录。

接下来,在清单 1 中,代码使用了 TwitterFactory.getInstance() 调用获取了 Twitter 对象的一个新实例。该代码调用了 twitter.search(),并将调用中提供的搜索字符串传递到这个方法之中。在底层,对 Twitter.com 发出了一条 HTTP 请求,以提交搜索请求,并将返回的结果放入 qryResult 对象。程序随后遍历了 qryResult 对象,并开始组合将返回给 RPG 程序的 JSON 字符串。组合逻辑应该编码一些 JSON 字符串,原因在于 JSON 值必需的封装结构是通过调用 DataObjectFactory.getRawJSON(tweet) 来提供的,可以用它来获取 twitter 结果集的 JSON 表示。清单 2 给出了 JSON 输出结果的示例。

为了使 DataObjectFactory.getRawJSON(tweet) 成功完成工作,您需要为现有 twitter4j.properties 文件添加一个条目,如果您从 http://mowyourlawn.com/tweetme4i.html 安装了 TweetMe4i,那么这个文件的位置应该是 /java/tweetme4i/twitter4j.propertiestwitter4j.properties 文件中添加的是 jsonStoreEnabled=true

清单 2. search () 方法提供的 JSON 结果
{
   "tweets":[
      {
         "text":"Opps, wrong url on that last one ( though a great Christmas
            song :-)  Try this one: http://t.co/p1w3x4jt",
         "from_user_id":44956334,
         "id":144505404985057280,
         "from_user":"aaronbartell",
         "created_at":"Wed, 07 Dec 2011 19:55:47 +0000",
         "metadata":{
            "result_type":"recent"
         }
      },
      {
         "text":"Give feedback for next #RPGNextGen 2.0 features
            http://t.co/rwZL1KH2  #opensource #free #RPG #IBMi editor
            #eclipse based",
         "from_user_id":44956334,
         "id":144504285328195584,
         "from_user":"aaronbartell",
         "created_at":"Wed, 07 Dec 2011 19:51:21 +0000",
         "metadata":{
            "result_type":"recent"
         }
      },
      {
         "text":"RT @SystemiNetwork: Maxed Out: New IBM Tools Uses POWER
            to Crush Commodity Server Architectures: .http://bit.ly/u36umU
           #IBMi",
         "from_user_id":44956334,
         "from_user_name":"Aaron Bartell",
         "id":143789937253294080,
         "from_user_id_str":"44956334",
         "from_user":"aaronbartell",
         "created_at":"Mon, 05 Dec 2011 20:32:47 +0000",
         "metadata":{
            "result_type":"recent"
         }
      }
   ]
}

如果这是您第一次接触 JSON,那么您可以在 此处 了解有关的更多信息。JSON 表示 Javascript Object Notation,在 Web 浏览器编程领域中是一种轻松序列化、反序列化 JavaScript 对象的流行方法,可以使对象能够与服务器进行通信。例如,在过去,我常常将它与 OpenRPGUIExtJS 一起使用。最近,我将 JSON 扩展到了 Javascript 领域之外。举例来说,在我的 DynaDroid4i 项目中,JSON 利用它与 Android 应用程序进行通信,这个项目中完全没有使用 Javascript。然而,JSON 仍然用于从 IBM i 向 Android 移动设备发送消息,或者从 Android 移动设备向 IBM i 发送消息。


RPG 端

现在,应该转到解决方案的 RPG 一端,看看如何调用 Java search() 方法。清单 3 展示了 RPG 程序 TM4ISEARCH 的主要部分。请注意,可以在本文附带压缩文件中找到 TM4ISEARCH 的完整源代码。

清单 3. TM4ISEARCH 的主要部分
       monitor;
         cmd = 'ADDENVVAR ENVVAR(CLASSPATH) REPLACE(*YES) VALUE(' +
            qte +
            '/java/tweetme4i' +
            ':/java/tweetme4i/TweetMe4i.jar' +
            ':/java/tweetme4i/twitter4j-core-2.2.5.jar' +
            qte + ')';

         QCMDEXC(%trimr(cmd): %len(%trimr(cmd)) );

         cmd = 'ADDENVVAR ENVVAR(QIBM_RPG_JAVA_PROPERTIES) ' +
           'REPLACE(*YES) VALUE(' + qte + '-Djava.version=1.5;' + qte + ')';

         QCMDEXC(%trimr(cmd): %len(%trimr(cmd)) );
       on-error;
       endmon;
       
       gSrchStr = 'from:aaronbartell since:2011-10-01';
       jStr = TweetMe4i_search( newStr(gSrchStr) );
       gResult = getBytes(jStr);
       
       if gResult.code = 'SUCCESS';
         jsonToDB(gResult.text);
       endif;

清单 3 中的粗体是我们要重点讨论的部分。首先,可以看到添加了 CLASSPATH 环境变量,以便包含 TweetMe4i.jartwitter4j-core-2.2.5.jarTweetMe4i.jar 包含之前讨论的 search() 函数,在撰写本文之时,twitter4j-core-2.2.5.jar 是 Twitter4j 的最新版本。接下来,观察将搜索字符串应用于 gSrchStr 变量的等式,随后,在 TweetMe4i_search() 调用中使用到了这个变量。清单 4 给出了 TweetMe4i_search API 的 RPG 原型。

清单 4. TweetMe4i_search 原型
 D TweetMe4i_search...
D                 pr              o   class(*java: jStrConst) static
D                                     extproc(
D                                     *java:
D                                     'com.mowyourlawn.twitter.TweetMe4i':
D                                     'search')
D  pTxt                           o   class(*java: jStrConst) const

在 RPG 程序接收到返回的 JSON 响应之后,清单 3 中最后一部分重要代码就是 jsonToDB() 调用。清单 5 给出了jsonToDB() 子过程,它的目的是获取输入的 JSON 字符串,对其进行解析,并将内容写入一个 DB2 表 TWTHST(即 Tweet History 的缩写)。

清单 5. jsonToDB 子过程
 P jsonToDB        b
 D jsontoDB        pi
 D  pStr                      65535a

 D jRoot           S               *
 D jTweets         S               *
 D jObj            S               *
 D tweetCount      S             10i 0
 D x               S             10i 0
  /free

   jRoot = json_parse(%addr(pStr));

   jTweets = json_getArray(jRoot: 'tweets');
   tweetCount = jsona_size(jTweets);

   for x = 0 to (tweetCount - 1);
     jObj = jsona_getObject(jTweets: x);

     if jObj = *null;
       leave;
     endif;

     clear TWTHSTR;
     ID = json_getLong(jObj: 'id');
     chain ID TWTHST;
     if not %found(TWTHST);
       TXT = %str( json_get(jObj: 'text'));
       FRMUSRID = json_getInt(jObj: 'from_user_id');
       FRMUSR = %str( json_get(jObj: 'from_user'));
       CRTDAT = %str( json_get(jObj: 'created_at'));
       write TWTHSTR;
     endif;
   endfor;

   json_dispose(jRoot);

  /end-free
 P                 e

这个子过程引入了来自 RPGNextGen.com 的 Mihael Schmidt 的 JSON *SRVPGM 对象。所有从 json_jsona_ 开始的子过程调用都是从 Mihael 的 JSON *SRVPGM 类发出的,以小写字母 j 开头的变量表示 JSON *SRVPGM's 对象(实际上是指针)。如果您还没有访问过 RPGNextGen.com,应该抽时间去访问这个网站。Mihael 是个大忙人,但对于 RPG 社区非常慷慨,开发了众多开放源代码工具。

jsonToDB 子过程中的第一步是调用 json_parse() 例程,该例程将获取 JSON 字符串,并将其存储在底层的专用指针数组中。这种操作允许 RPG 程序后续按照名称访问各个 JSON 部分。该代码使用 json_getArray() 获取数组的 tweets 部分,并在 jTweets 指针中存储了对它的引用,如有必要,请回顾一下清单 2,了解相关的结构。通过 jsona_size(), 获得 tweets 总数之后,代码逻辑将从索引零开始遍历结果。开放源代码贡献者 Mihael 在创建他那部分工具时必然已经考虑到了 Java,我们都知道,RPG 程序员在编写索引时通常是从 1 开始编写的!对 jsona_getObject() 的调用将检索 tweets 迭代,并将其存储在 jObj 之中。确定 jObj 是否为空之后(也就是说,检索是否成功,条目是否存在),代码将开始使用json_* API 获取 JSON tweets 结果的不同部分,并将其填充到 DB2 记录之中。如果深入研究 json_* 原型,就会发现其中包括通用的 “getter”,例如 json_get(),此外还有许多具体的获取函数,例如 json_getString() json_getInt()。无论哪种方法都有很好的表现,而较为一般的函数需要进行强制类型转换(即 %str())。

清单 6 给出了 TWTHST 表的定义。TWTHST 中的各列均填充了来自 Twitter 的数据,包括表的惟一键在内。column label 值对应于 Twitter 和 Twitter4j 提供的 JSON 实体的实际名称。

清单 6. TTHST 表定义
CREATE TABLE TWEETME4I.TWTHST (
  ID NUMERIC(30,0) NOT NULL DEFAULT 0 ,
  TXT CHAR(140) CCSID 37 NOT NULL DEFAULT '' ,
  FRMUSRID NUMERIC(9, 0) NOT NULL DEFAULT 0 ,
  FRMUSR CHAR(20) CCSID 37 NOT NULL DEFAULT '' ,
  CRTDAT CHAR(31) CCSID 37 NOT NULL DEFAULT '' ,
  PRIMARY KEY( ID ) );

LABEL ON COLUMN TWEET4MEI.TWTHST
(    ID TEXT IS 'ID' ,
	TXT TEXT IS 'TEXT' ,
	FRMUSRID TEXT IS 'FROM_USER_ID' ,
	FRMUSR TEXT IS 'FROM_USER' ,
	CRTDAT TEXT IS 'CREATED_AT' ) ;

jsonToDB() 的最后一个必要步骤就是调用 json_dispose(),清除所有已分配的内存。此时,代码已经完成,TWTHST DB2 表中已经有了如图 1 所示的结果,这个屏幕快照来自 Squirrel SQL(如果您还没有使用过这款出色的工具,那么值得花时间去了解一下)。

图 1. 通过 Squirrel SQL 提供的 TWTHST 内容
图 1. 通过 Squirrel SQL 提供的 TWTHST 内容

结束语

就是这样!您已经正式地从您的 IBM i 完成了一次 Twitter 搜索,而且将结果存储到了我们喜爱的 DB2 中。请注意,还有用于其他一些社交网站的 API,例如 LinkedIn 和 Facebook,那这些产品也需要更高的权限才能看到来回传送的内容(也就是说,在 Facebook 中,您必须是某人的好友才能看到他们的内容)。如果您看到了有价值的 Facebook API 或者 LinkedIn API,请 通过电子邮件与我联系,我会试试能否整合一些内容,然后写一些相关的文章。


参考资料

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=IBM i
ArticleID=807650
ArticleTitle=使用 TweetMe4i 和 JSON 打下您的基础
publish-date=04092012