【开源中国 APP 全新上线】“动弹” 回归、集成大模型对话、畅读技术报告”
Shindig 作为 OpenSocial 的开源参考实现,提供了各个方面的扩展能力。本篇会介绍如何为 Shindig 添加新的 feature,供 gadget 使用,再介绍如何添加 JPA 支持,替代 Shindig 自带的 JSON 实现成为新的持久化层。
Shindig 是 OpenSocial 的参考实现,很多第三方网站直接参考 Shindig 的开发实现,添加自己网站自定义的内容,成为 OpenSocial 的兼容容器。向广大的第三方开发者开放自己的服务、数据以及 API,允许开发者开发自己的 gadget,并运行在这个 OpenSocial 兼容容器中。gadget 的种类可以是丰富多彩,提供各种外观和功能的应用,这本身就是 OpenSocial 规范及 Shindig 实现所带来的最广泛的扩展能力。但这并不是本篇文章里面要谈的 Shindig 扩展能力。
Shindig 开发实现自身也为开发者(包括兼容容器实现厂商)提供了其他方面的扩展能力。首先就是对 feature 的扩展能力。在本系列的第二部分中,介绍了有关 feature 的概念。开发者在编写自己的 gadget 时,如果在 xml 中声明 require 某些个 feature,这个 gadget 在渲染后,就会具备这些 feature 的能力。那这些 feature 是什么呢?这就是本章会讲述的内容。
熟悉 OpenSocial 规范的人会了解到,规范中包括重要的三要素是 Person、Activity 和 AppData。在 Shindig 中,分别有 PersonService、ActivityService 和 AppDataService 三个接口对应这三个要素,并提供了实现类实现这三个接口,提供对后台数据的 CRUD 操作。如果看到 Shindig 的源码,就会知道 Shindig 采用了 JSON 实现类来实现这三个接口,并默认是启用状态。这样当服务器启动后,在客户端做的一些 CRUD 的操作,就会以 JSON 对象的形式存放在服务器端内存中,这些数据会随着服务器的重启或停止而丢失。几乎每一个采用 Shindig 作为 OpenSocial 容器的开发者都会愿意把数据存放在自己的数据库中,事实上 Shindig 已经有了针对这三个接口的数据库实现,但一是没有默认启用,二是实现的不完善甚至还有 bug,毕竟 Shindig 是一个参考实现。本章的后半部分也会介绍如何启用 Shindig 中已经实现的数据库支持,并介绍如何来扩展上述的三个接口,提供自己的存取实现。
细心的开发者在查看 Shindig 中自带的 sample 页面源码时会发现几乎所有的页面都会带有这样一行 <script> 标签:
<script type="text/javascript" src="../gadgets/js/shindig-container:rpc.js?c=1&debug=1&nocache=1"> </script> |
检查 web.xml 后会发现,这是由 Shindig 后台一个 JsServlet 来处理这样的请求,这个 script 的 src 中用冒号分隔开的就是 Shindig feature 的名字,分别会对应 feature 自己定义的一个或多个 js 文件。这个 servlet 会把请求的那些 Javascript 文件内容合并起来,再返回给客户端运行。如果你有注意每个 gadget 在通过 ifr 请求渲染时,ifr 请求响应返回的 html 中也会包含这样的 <script> 标签,形式跟 sample 页面中的 script 标签很类似,那些用冒号隔开的同样是 Shindig feature 的名称。所以,可以把 Shindig feature 简单的理解为就是一个或多个 js 文件的集合。
我们随便打开一个 Shindig feature project 下的某个 feature 目录,我们会发现它具体会包含几个 js 文件以及一个特殊的 feature.xml:
清单 2. 示例 feature 的 feature.xml 定义
<feature> <name>featureA</name> <dependency>featureB<dependency> <gadget> <script src="globals.js"/> </gadget> <container> <script src="globals.js"/> </container> </feature> |
这个 feature.xml 就是这个 feature 的定义文件,包括它的名称,它的依赖,以及它在 container 和 gadget 级别分别对应什么 js 文件。名称就不用多说了,gadget 在 require 某个 feature 时,就是用的这个名称。依赖则代表这个 feature 依赖某些其他的 feature,这意味着如果某个 gadget require 了这个 feature,它所依赖的那些 feature 也会“被”require 而被 JsServlet 请求加载。container 和 gadget 级别分别对应不同的 js 文件,这可以让 page 和 gadget 为实现一个 feature 分别加载不同的 js 文件,这在一定程度上避免了 js 内容的重复。
让我们来做一个简单的示例,来看看如何实现一个新的 feature,并在 gadget require 这个 feature 之后,feature 的代码能运行起来。
首先我们在 Shindig feature project 下新加一个目录名字叫 helloworld,具体目录结构可以参见其他的 feature。并创建一个新的 feature.xml,内容是:
清单 3. helloworld feature 的 feature.xml
<feature> <name>helloworld</name> <dependency>globals<dependency> <gadget> <script src="hello_gadget.js"/> </gadget> <container> <script src="hello_container.js"/> </container> </feature> |
代码代表着这个新 feature 名字叫做 helloworld,依赖 globals,在 gadget 级别的 js 文件是 hello_gadget.js,在 container 界别的 js 文件是 hello_container.js。
hello_gadget.js 文件的内容是:
gadgets['helloworld'] = (function() { return { helloGadget : function() { alert(“hello gadget”); } }; })(); |
hello_container.js 文件的内容是:
gadgets['helloworld'] = (function() { return { helloContainer : function() { alert(“hello container”); } }; })(); |
可以从这两个 js 文件内容看到,如果调用响应的方法,就会有 alert 弹出。
接着,我们为 shindig feature 下的 feature.txt 添加一行新的内容:
features/helloworld/feature.xml |
这样,这个新的 feature 就基本构建完成了。下面让我们来使用它。
我们新建一个简单的 gadget,为了验证我们新构建的 feature,我们让这个 gadget require 新 feature helloworld,并在 gadget 渲染后执行方法:
<Require feature="helloworld"/></Require> ... gadgets.helloworld.helloGadget(); ... |
然后我们构建一个简单的 sample page,它会渲染上面的 gadget,但是要包含这样一行 script 标签:
<script type="text/javascript" src="../gadgets/js/shindig-container:helloworld.js?c=1&debug=1&nocache=1"> </script> |
在我们重新构建好 Shindig project 之后,启动 tomcat server,访问我们新创建的 sample page,你就会看到有两个 alert 对话框分别弹出,分别是“hello container”和“hello gadget”。
这代表着这个新的 helloworld feature 已经可以正常工作了。下面需要做的就是逐步去丰富和增强这个 feature 的功能了。这是个最简单的 feature 实现,feature 里面只包含了 Javascript 文件,当然新 feature 是可以添加 Java 代码的,比如为 Shindig 提供一个新的 Guice module,并注入一些类的实现来增加新的 server 端行为,但限于篇幅这里就赘述了,具体请参考 Shindig extras project 的实现。
Shindig 是默认采用 JSON 格式的服务作为 service 数据服务实现的,打开 SampleModule.java,你会发现这段代码:
清单 9. SampleModule 中绑定的 service 实现类
bind(ActivityService.class).to(JsonDbOpensocialService.class); bind(AppDataService.class).to(JsonDbOpensocialService.class); bind(PersonService.class).to(JsonDbOpensocialService.class); |
这段代码是采用 Google Guice 2.0 反射注入框架来绑定 service 接口到具体实现的。你可以看出对于 PersonService、ActivityService 和 AppDataService 是都绑定到 JsonDbOpensocialService 的,也就是默认的 JSON 数据服务实现。这个 JSON 实现会把所有的数据以 JSON 对象的形式保存在服务器内存中以供读取,但随着服务器重启或停止,更新后的数据就会全部丢失。
如果期望启用 Shindig 自带的 service 数据库实现,需要进行以下步骤:
首先打开 Shindig server project 中的 web.xml 文件,需要在 web.xml 中添加 JPASocialModule,并去掉 SampleModule。修改后的代码是这样的:
清单 10. 用 JPASocialModule 替代 SampleModule
<param-value> org.apache.shindig.common.PropertiesModule: org.apache.shindig.gadgets.DefaultGuiceModule: org.apache.shindig.social.core.config.SocialApiGuiceModule: org.apache.shindig.social.opensocial.jpa.spi.JPASocialModule: org.apache.shindig.social.opensocial.as.ActivityStreamsDbGuiceModule: org.apache.shindig.gadgets.oauth.OAuthModule: org.apache.shindig.common.cache.ehcache.EhCacheModule: org.apache.shindig.sample.shiro.ShiroGuiceModule: org.apache.shindig.sample.container.SampleContainerGuiceModule: org.apache.shindig.extras.ShindigExtrasGuiceModule: </param-value> |
如果你打开 JPASocialModule,你就会发现它月 SampleModule 的不同:
清单 11. JPASocialModule 中绑定的 service 实现类
bind(ActivityService.class).to(ActivityServiceDb.class).in(Scopes.SINGLETON); bind(PersonService.class).to(PersonServiceDb.class).in(Scopes.SINGLETON); bind(AppDataService.class).to(AppDataServiceDb.class).in(Scopes.SINGLETON); |
在 JPASocialModule 中,它把 PersonService 等三个 service 的接口都绑定到了 Shindig 另外提供的数据库实现上了。
有一行关于 OAuthDataStore 的绑定也需要从 SampleModule 中转移到 JPASocialModule 中:
bind(OAuthDataStore.class).to(SampleOAuthDataStore.class); |
因为 JPASocialModule 是在 Shindig samples project 中,这个 project 默认是不包含在 Shindig server build 的过程中的。但现在需要添加进来,就需要在 Shindig server project 的 pom.xml 中添加对 Shindig samples 的依赖。并使用 OpenJPA 作为默认数据库驱动,在这里使用的 OpenJPA 的版本是 2.0.1。
修改 socialjpa.properties 文件,添加自己的数据库驱动和访问信息,这里我们使用的是 derby 数据库。
清单 13. socialjpa.properties 文件
#Network Derby db.driver=org.apache.derby.jdbc.ClientDriver db.url=jdbc:derby://localhost:1527/shindig;create=true db.user=sa db.password=sa |
注意在 db.url 中的“create=true”,这表示如果 derby 中如果没有 shindig 名称的数据库,就会自动创建一个,但并不会反复创建。
好了,重新编译整个 Shindig project,先启动本地的 derby 数据库,再启动 tomcat。如果启动过程中发现错误,请重新检查以上步骤。在 Shindig server 启动完成后,可以使用任意一款数据库可视化工具连接打开 shindig 数据库,在 person 表中新插入一条记录,oid=1,person_id=1,display_name=John Doe。
然后打开浏览器,在地址栏中输入 url:http://localhost:8080/social/rest/people/1/@self
你就会看到返回的 person 的数据,格式是 JSON 格式:
{"entry":{"ims":[],"applictions":[],"emails":[],"addresses":[], "filterCapability":{},"networkPresence":{"value":"XA", "displayValue":"Extended Away"},"id":"1", "objectId":1,"accounts":[],"urls":[], "organizations":[],"phoneNumbers":[], "photos":[],"displayName":"Shindig User"}} |
这个请求是 Shindig 提供的 rest 请求,具体的请求细节和参数请查看 Shindig 提供的 rest 请求规范:http://shindig.apache.org/getting-started.html
这样,Shindig 自带的 JPA 实现对 social 数据的支持就启用了。这是一个很简单的例子,当然,如果你期望定制它们的行为符合自己的要求,甚至自己创建更多的实体类进行存取访问,就需要自己提供代码实现,但是机制是一样的。
Shindig 提供了针对 social 数据接口的两种实现,比如 PersonService 既有 JSON 数据实现,也有对数据库的支持,比如上面提到的 PersonServiceDb.java。但是如果希望自己定制自己的 social 数据接口的实现,比如希望自己的 person 数据是来自其他的接口,既不是 JSON,也不是数据库,这样的需求 Shindig 同样可以满足。下面通过编写一个简单的例子,演示如何扩展 Shindig 的 PersonService 接口,提供自定义的实现。
首先创建一个名为 PersonServiceImpl 的类,实现 PersonService 接口,在需要实现的 getPerson 方法中编写如下代码:
清单 15. PersonServiceImpl 实现的 getPerson 方法
public Future<Person> getPerson(UserId id, Set<String> fields, SecurityToken token) throws SocialSpiException { initDB(); if (id != null) { Person currentPerson = db.findPerson(id.getUserId(token)); if (currentPerson != null) { return ImmediateFuture.newInstance(currentPerson); } } throw new SocialSpiException(ResponseError.BAD_REQUEST, "Person not found"); } |
这块代码和 Shindig 提供的 JSON 实现很类似,区别在于这里的 db 参数指向的是一个模拟存储 person 的地方,下面就是它的代码片段:
public class ShindigIntegratorPersistenceAdapter { private Map<String, Person> persons;. private void init() { if (persons == null) { // create personlist persons = new HashMap<String, Person>(); Name name = new NameImpl(); name.setGivenName("James"); name.setFamilyName("Bond"); name.setFormatted("James Bond"); Person john = new PersonImpl("john.doe", "007", name); persons.put(john.getId(), john); } } public Person findPerson(String id) { Person person = persons.get(id); return person; } |
可以看出来,这个模拟存储 person 的类,是在内存中保存了一个 Map 包括所有的 person 对象,并提供一个 findPerson 用来查询 person 对象。
这样,在 SampleModule 中只要把 PersonService 的实现类绑定到 PersonServiceImpl,就可以在运行时动态绑定到新的 PersonServiceImpl 对象,并查询 person 对象了。当然你也可以创建自己的 Guice module,用来绑定自己实现的那三个 service,这样可以替换掉原来的 SampleModule,更方便于测试和使用。
清单 17. 在 SampleModule 中绑定 PersonServiceImpl 类
bind(ActivityService.class).to(JsonDbOpensocialService.class); bind(AppDataService.class).to(JsonDbOpensocialService.class); bind(PersonService.class).to(PersonServiceImpl.class);
这篇文章介绍了如何为 Shindig 添加一个新的 feature 实现,如果启用 Shindig 的 JPA 支持,以及如何实现一个自己的针对 PersonService 接口的实现类。这些都是在某种程度上为了满足自己的实际需求,对 Shindig 提供的扩展接口和能力,提供自己的实现。Shindig 还有一些其他的扩展接口和能力,请读者自己探索。
文章出处:IBM developerWorks