, 【IT168 文档】
前言:随着社交网络的不断发展,用户关系信息已经成为一类重要的网络数据。为了能使开发者在社交网络平台上开发出富体验的应用,充分挖掘和共享平台的用户关系数据,从而极大丰富 SNS 自身的功能,开放平台(OpenAPI)已经成为各主流社交网站的共同趋势。OpenSocial 为构建跨多个网站的社交应用程序提供了一组通用 API。开发人员可以自由使用包括 JavaScript、HTML 在内的标准的 Web 技术创建应用程序,用以访问社交网络的用户关系信息。Shindig 是 OpenSocial 规范的引用实现,旨在帮助 OpenSocial 开发人员快速构建自己的 OpenSocial 应用平台。本文通过实际的例子,指导读者如何在 Eclipse 环境下构建 / 编译 / 调试 Apache 的 Shindig Java 工程,及其如何通过 SPI 实现,把现有的用户关系数据适配到 Shindig 容器。
什么是 OpenSocial
OpenSocial 是基于开放标准的一组通用的 API,用于帮助 WEB 的开发者构建跨多个社交网站的可移植的社交应用程序。OpenSocial 提供开发者一套通用的 API,基于该通用 API 开发的社交应用程序可以运行在任意支持 OpenSocial 规范的社交网站上。
关于更多的有关 OpenSocial 内容,请读者参见 www.opensocial.org.
Apache Shindig
Shindig 是 OpenSocial 规范的引用实现,其主要的组件包括 :
Gadget Container JavaScript,OpenSocial Gadget 容器,客户端的 JavaScript 类库 (gadget.js),提供例如 UI Layout,Security, Communication 等相关的功能。
Gadget Rendering Server,负责解析 Gadget XML, 转化成浏览器使用的 HTML/JavaScript/CSS。
OpenSocial Container JavaScript,位于客户端的 OpenSocial 容器,也是 JavaScript 类库,提供 OpenSocial 相关的功能,例如存取 People, Activity, AppData 等相关的社交数据。
OpenSocial Data Server,提供基于 Restful/RPC 协议的 Services,用于存取 People, Activity, AppData 等相关的社交数据
图 1 是 Shindig 的服务器端架构图:
图 1. Shindig Architecture( 引自 Chris Schalk@GoogleTM)
从图 1 中可以看到,Shindig 基于 Java Servlet Stack 实现。GadgetRenderingServlet 负责 Gadget Rendering, 而 DataServiceServlet 和 JsonRpcServlet 实现 OpenSocial Data Server 中相对应的 Restful 及其 RPC 服务。JsonDbOpensocialService 通过实现 ActivityService, PersonService, AppDataService 三个接口向 Shindig OpenSocial 容器提供基于 Json 格式的 OpenSocial 数据。客户端的 Gadgets 可以使用标准的 OpenSocial API 访问到这些数据。
关于更多的有关 Apache Shindig 内容,请读者参见 http://incubator.apache.org/shindig.
Eclipse 环境下编译 / 调试 Shindig
我们通过以下的步骤来完成:
安装 Maven plugin
Maven 是一个基于 Java 的代码构建和依赖管理工具,Apache Shindig 的源代码是通过 Maven 来管理的,所以我们需要安装 Maven 的 Eclipse 插件,读者可以使用 Eclipse 的 updatesite 机制,连接到"http://m2eclipse.sonatype.org/update/"站点安装。
使用 Subversion 下载 Shindig 代码
在 http://svn.apache.org/repos/asf/incubator/shindig/trunk/,使用 SVN 客户端下载到 Shindig 的源代码。
编译和调试 Shindig
首先,在 Eclipse IDE 里,通过 File/Import/General/Maven Projects 选项导入我们下载的所有 Shindig 的源代码。导入完成后,Shindig 就作为几个 Maven 工程存在于你当前的 WorkSpace 中。
通过 Run/Debug Configurations/Maven Build 配置编译 Shindig 参数,如图 2 所示:
图 2. Package Shindig
如图 2 所示,“E:\svn_repository\opensocial-shindig”是你的 Shindig 源代码的根目录。点击 Debug, 这将使用 Maven 来 Build 整个 Shindig 代码。在 Build 成功后,我们使用 Jetty 来启动 Shindig,默认情况下,Jetty Server 将运行在 8080 端口。如图 3 所示:
图 3. Run Shindig
如图 3 所示,我们设置了 Maven 目标,使用 Jetty 来启动 Shindig, 而 Base directory 设置为 shindig-server Maven 工程的根目录。点击 Debug, Jetty Server 运行,而 Shindig 部署在 Jetty Server 上。 在 Shindig 成功启动后, 你就可以使用 http://localhost:8080/gadgets/files/samplecontainer/samplecontainer.html来访问 Shindig 提供的 Gadget 的例子。
Shindig 服务器端 SPI 扩展
Shindig 作为 OpenSocial 规范的引用实现,提供了 SPI 的扩展能力,允许你把数据适配到 Shindig 容器中去。你的这些数据也许存在于诸如 My SQL/Oracle 的关系数据库,或者是以 JSON 格式存储的静态文件,无论哪种存储,你都可能通过 Shindig SPI 将它们适配到 Shindig, 从而使这些数据公布在 OpenSocial 平台上。
图 4. Shindig SPI 扩展
如图 4 所示,你的应用需要实现 ActivityService, PersonService, AppDataService 三个接口,利用诸如 JDBC/Hibernate 等机制把数据提供给 Shindig。
接下来,本文将通过一个例子,实现 PersonService 接口向 Shindig 提供 People/Friends 相关的 OpenSocial 数据。
清单 1 是 SocialTestJsonPersonService类的实现。
清单 1. SocialTestJsonPersonService Class
private static final String PEOPLE_TABLE = "people";
private static final String FRIEND_LINK_TABLE = "friendLinks";
private JSONObject db;
private BeanConverter converter;
……
public Future<RestfulCollection<Person>> getPeople(Set<UserId>
userIds,GroupId groupId, CollectionOptions options, Set<String> fields,
SecurityToken token) throws ProtocolException {
List<Person> result = Lists.newArrayList();
try {
//Read people data from JSON table.
JSONArray people = db.getJSONArray(PEOPLE_TABLE);
Set<String> idSet = getIdSet(userIds, groupId, token);
for (int i = 0; i < people.length(); i++) {
JSONObject person = people.getJSONObject(i);
if (!idSet.contains(person.get(Person.Field.ID.toString()))) {
continue;
}
// Add group support later
Person personObj = filterFields(person, fields, Person.class);
result.add(personObj);
}
if (GroupId.Type.self == groupId.getType() && result.isEmpty()) {
throw new ProtocolException(HttpServletResponse.SC_BAD_REQUEST,
"Person not found");
}
int totalSize = result.size();
return ImmediateFuture.newInstance(new RestfulCollection<Person>(
result, options.getFirst(), totalSize, options.getMax()));
} catch (JSONException je) {
throw new ProtocolException(
HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je
.getMessage(), je);
}
}
public Future<Person> getPerson(UserId id, Set<String> fields,
SecurityToken token) throws ProtocolException {
try {
//Read people data from JSON table.
JSONArray people = db.getJSONArray(PEOPLE_TABLE);
for (int i = 0; i < people.length(); i++) {
JSONObject person = people.getJSONObject(i);
if (id != null
&& person.get(Person.Field.ID.toString()).equals(
id.getUserId(token))) {
Person personObj = filterFields(person, fields,
Person.class);
return ImmediateFuture.newInstance(personObj);
}
}
throw new ProtocolException(HttpServletResponse.SC_BAD_REQUEST,
"Person not found");
} catch (JSONException je) {
throw new ProtocolException(
HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je
.getMessage(), je);
}
}
……
}
从清单 1 可以看到,SocialTestJsonPersonService 实现了 PersonService 两个接口方法 getPeople 及其 getPerson。getPeople 根据传入参数 userIds,返回相应于该 ID 列表的用户列表,而 getPerson 根据传入参数 id,返回相应于该 ID 的用户。
注意,Shindig 依赖 Guice 做动态的依赖注入 (dependency Injection),我们需要在 org.apache.shindig.social.sample.SampleModule 里指示 Guice 把 PersonService 绑定到 SocialTestJsonPersonService 实现,如清单 2 所示:
清单 2. SampleModule Class
@Override
protected void configure() {
super.configure();
bind(String.class).annotatedWith(Names.named("shindig.canonical.json.db"))
.toInstance("sampledata/canonicaldb.json");
bind(String.class).annotatedWith(Names.named("shindig.socialtest.json.db"))
.toInstance("sampledata/socialtestdb.json");
bind(ActivityService.class).to(JsonDbOpensocialService.class);
bind(AppDataService.class).to(JsonDbOpensocialService.class);
//bind(PersonService.class).to(JsonDbOpensocialService.class);
bind(PersonService.class).to(SocialTestJsonPersonService.class);
bind(MessageService.class).to(JsonDbOpensocialService.class);
bind(OAuthDataStore.class).to(SampleOAuthDataStore.class);
// We do this so that jsecurity realms can get access to the jsondbservice singleton
requestStaticInjection(SampleRealm.class);
}
……
}
从清单 2 中,还可以看到,标记为"shindig.socialtest.json.db"的字符串绑定到了"sampledata/socialtestdb.json", socialtestdb.json 是我们示例中的 JSON 数据文件,用来保存 People 数据。在 SocialTestJsonPersonService 的实现中,db.getJSONArray(PEOPLE_TABLE) 就是从该 JSON 文件获取所有的 People 数据。
在下一节,我们给出客户端实现,来消费 socialtestdb.json 中的 Social 数据。
Gadget/Restful 客户端实现
Gadget 实现
清单 3. SocialAppTest.xml
<Module>
<ModulePrefs
title="Social Application Test"
author_email="lisanh@cn.ibm.com">
<Require feature="osapi" />
<Require feature="dynamic-height" />
</ModulePrefs>
<Content type="html" ><![CDATA[<!-- Fetching People and Friends -->
<div>
<button onclick='fetchPeople();'>Fetch people and friends</button>
<div>
<span id='viewer'></span>
<ul id='friends'></ul>
</div>
</div>
<script type='text/javascript'>
var allPeople;
function render(data) {
var viewer = data.viewer;
allPeople = data.viewerFriends.list;
document.getElementById('viewer').innerHTML = viewer.id;
document.getElementById('friends').innerHTML = '';
for (var i = 0; i < allPeople.length; i++) {
document.getElementById('friends').innerHTML += '<li>' +
allPeople[i].name.formatted + '</li>';
}
gadgets.window.adjustHeight();
}
function fetchPeople() {
var fields = ['id','age','name','gender','profileUrl','thumbnailUrl'];
var batch = osapi.newBatch();
batch.add('viewer', osapi.people.getViewer({sortBy:'name',fields:fields}));
batch.add('viewerFriends',
osapi.people.getViewerFriends({sortBy:'name',fields:fields}));
batch.add('viewerData', osapi.appdata.get({keys:['count']}));
batch.add('viewerFriendData',
osapi.appdata.get({groupId:'@friends',keys:['count']}));
batch.execute(render);
}
</script>]]></Content>
</Module>
如清单 3 所示,fetchPeople 使用了 osapi 获得 OpenSocial 数据,并把它们展示在 HTML 页面上。osapi 是一个轻量级的 JavaScript 类库,用于帮助客户端获得 OpenSocial 数据。显示该 Gadget 的 HTML 页面代码 (socialtest.html),请读者详见文章后面的资源类表,在这里我们就不一一列出。
现在,我们可以在 Eclipse IDE 中启动 Shindig, 在你的浏览器里输入地址:
http://localhost:8080/gadgets/files/samplecontainer/socialtest.html,打开 SocialAppTest Gadget,点击”Fetch people and friends”按钮,SocialAppTest Gadget 向本地 Shindig 请求数据,Shindig 从 socialtestdb.json JSON 文件中获取数据,返回给 Gadget, 并在浏览器中显示,如图 4 所示:
图 4. SocialAppTest Gadget
RESTful 客户端实现
另外,我们还可以选择使用 Java 应用程序,通过 REST 协议获得 OpenSocial 数据,如清单 4 所示。
清单 4. RESTful Client 实现
private static final String BASE_URI = "http://localhost:8080/social/rest/";
private static final String VIEWER_ID = "john.doe";
public static void main(String[] args) {
OpenSocialClient client = new OpenSocialClient("SocialAppTest");
client.setProperty(OpenSocialClient.Property.REST_BASE_URI, BASE_URI);
client.setProperty(OpenSocialClient.Property.VIEWER_ID, VIEWER_ID);
try {
OpenSocialPerson viewer = client.fetchPerson(VIEWER_ID);
System.out.println("Viewer: " + viewer.getId());
Collection<OpenSocialPerson> friends = client.fetchFriends(viewer.getId());
for (OpenSocialPerson friend : friends) {
System.out.println("Friend: " + friend.getId());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
清单 4 的运行结果和 SocialAppTest Gadget 一样,显示当前的 Viewer 及其他的朋友。
结束语
通过本文,读者已经了解了如何使用 Shindig SPI 来将自己的 Social 数据适配到 Shindig 平台,也了解了如何构建客户端的应用来消费这些 Social 数据。
现在,我们不妨回头总结一下整个 OpenSocial 平台的系统结构。一般来说,OpenSocial 系统应用使用 OpenSocial Gadget 作为应用前端,OpenSocial Gadget 类似于 iGoogle Gadget,不过增加了 OpenSocial 数据的访问能力。当然,你也可以使用基于 REST/RPC 协议构建的桌面 RCP 应用作为前端。而对于 OpenSocial 平台服务器端,也有两个选择,一则如本文所讨论的这样,利用成熟开源的,与 OpenSocial 规范相兼容的 OpenSocial 容器实现 ( 例如 Shindig),通过 SPI 扩展实现自己的 OpenSocial 容器,或者你从头开始实现 OpenSocial 规范相兼容的 OpenSocial 容器。