【开源中国 APP 全新上线】“动弹” 回归、集成大模型对话、畅读技术报告”
作者:
, 高级软件工程师, IBM简介: 随着社交网络的不断发展,用户关系信息已经成为一类重要的网络数据。为了能使开发者在社交网络平台上开发出富体验的应用,充分挖掘 和共享平台的用户关系数据,从而极大丰富 SNS 自身的功能,开放平台(OpenAPI)已经成为各主流社交网站的共同趋势。OpenSocial 为构建跨多个网站的社交应用程序提供了一组通用 API。开发人员可以自由使用包括 JavaScript、HTML 在内的标准的 Web 技术创建应用程序,用以访问社交网络的用户关系信息。Shindig 是 OpenSocial 规范的引用实现,旨在帮助 OpenSocial 开发人员快速构建自己的 OpenSocial 应用平台。本文通过实际的例子,指导读者如何在 Eclipse 环境下构建 / 编译 / 调试 Apache 的 Shindig Java 工程,及其如何通过 SPI 实现,把现有的用户关系数据适配到 Shindig 容器。
OpenSocial 是基于开放标准的一组通用的 API,用于帮助 WEB 的开发者构建跨多个社交网站的可移植的社交应用程序。OpenSocial 提供开发者一套通用的 API,基于该通用 API 开发的社交应用程序可以运行在任意支持 OpenSocial 规范的社交网站上。
关于更多的有关 OpenSocial 内容,请读者参见 www.opensocial.org.
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 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 访问到这些数据。
我们通过以下的步骤来完成:
Maven 是一个基于 Java 的代码构建和依赖管理工具,Apache Shindig 的源代码是通过 Maven 来管理的,所以我们需要安装 Maven 的 Eclipse 插件,读者可以使用 Eclipse 的 updatesite 机制,连接到"http://m2eclipse.sonatype.org/update/" 站点安装。
在 http://svn.apache.org/repos/asf/incubator/shindig/trunk/, 使用 SVN 客户端下载到 Shindig 的源代码。
首先,在 Eclipse IDE 里,通过 File/Import/General/Maven Projects 选项导入我们下载的所有 Shindig 的源代码。导入完成后,Shindig 就作为几个 Maven 工程存在于你当前的 WorkSpace 中。
通过 Run/Debug Configurations/Maven Build 配置编译 Shindig 参数,如图 2 所示:
如图 2 所示,“E:\svn_repository\opensocial-shindig”是你的 Shindig 源代码的根目录。点击 Debug, 这将使用 Maven 来 Build 整个 Shindig 代码。在 Build 成功后,我们使用 Jetty 来启动 Shindig,默认情况下,Jetty Server 将运行在 8080 端口。如图 3 所示:
如图 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 作为 OpenSocial 规范的引用实现,提供了 SPI 的扩展能力,允许你把数据适配到 Shindig 容器中去。你的这些数据也许存在于诸如 My SQL/Oracle 的关系数据库,或者是以 JSON 格式存储的静态文件,无论哪种存储,你都可能通过 Shindig SPI 将它们适配到 Shindig, 从而使这些数据公布在 OpenSocial 平台上。
如图 4 所示,你的应用需要实现 ActivityService, PersonService, AppDataService 三个接口,利用诸如 JDBC/Hibernate 等机制把数据提供给 Shindig。
接下来,本文将通过一个例子,实现 PersonService 接口向 Shindig 提供 People/Friends 相关的 OpenSocial 数据。
清单 1 是 SocialTestJsonPersonService类的实现。
清单 1. SocialTestJsonPersonService Class
public class SocialTestJsonPersonService implements PersonService { 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 所示:
public class SampleModule extends SocialApiGuiceModule { @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 数据。
<?xml version="1.0" encoding="UTF-8"?> <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 所示:
另外,我们还可以选择使用 Java 应用程序,通过 REST 协议获得 OpenSocial 数据,如清单 4 所示。
public class SocialAppTest { 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 容器。