最近有个任务,就是要做一个像Igoogle一样可以拖拽的页面,然后开发一些小控件,可以动态维护和布局。之前没有接触过,感觉可能就是些JS,外加两张表存储控件位置,觉得应该还能实现,就试着做了下,因为之前听说过Portal这个名词,于是就本是遵循规范、借鉴开源、不重复造轮子的原则,在网上搜搜Portal相应规范和开源实现,开始了我的实现之路。在浪费了近一个月时间的仍希望渺茫的时候,接触到了OpenSocial,才终于完成了任务。
(一)Portal
Portal一词中文翻译为门户,是基于Web的应用,通常提供个性化,单点登录,整合不同资源的综合信息展示平台。Portal展现在最终用户面前的是类似于Web网页的Portal页面。
构成Portal页面的是能够建立和展现不同内容的一系列Portlet。Portlet处理从Portal传递来的用户请求,动态生成输出内容的一个片段,展现在Portal页面的某个位置上。
下图显示了Portal服务器、Portlet容器以及Portlet之间的关系。
Portlet是一个符合JSR168标准的类,是运行于Portal上的真正代表业务逻辑的部件,负责展现内容、处理请求。在Portal搭建好后,主要的工作就是根据业务要求开发Portlet了。
在部署Portlet到一个Portal时,除需要将逻辑代码拷贝过去外,还需要定义一个portlet.xml的配置文件来注册。Portal要支持Portlet需要是Portlet容器,就像tomcat是servlet容器一样,也就是说,需要扩展servlet容器才可以支持Portal功能。
我们的实际情况是有多个项目,每个项目都有单独的项目组维护,如果将每个Portlet业务代码(java代码、js、jsp等)拷贝到Portal门面项目里,会造成逻辑混乱、维护困难,这肯定是不能接收的。
Portal的开源项目有Pluto、Jetspeed、Liferay。项目的功能依次递增,代码复杂度也依次递增。Pluto实现了JSR168规范,扩展servlet容器为Portlet容器,Jetspeed和Liferay都是在它的基础上构建的;Jetspeed实现了控件的动态增删除和简单的自定义;Liferay则是一个功能齐全的门户解决方案。
我借鉴Liferay的源码来实现自己的Portal,但它的代码混乱、逻辑复杂,显示层struts1、servlet、volocity、FreeMarker 等各种技术滥用,后台代码组织更凌乱。而且代码使用复杂的ant组织方式,让人却步。由于Portlet固有的问题及Liferay实现方式的复杂,我最后放弃了使用这种技术。
(二)OpenSocial
山穷水复疑无路,柳暗花明又一村。在绝望的时候我看到了OpenSocial。
OpenSocial是Google发布的一个项目,定位于为SNS平台定义一套通用API,让应用开发者只需使用OpenSocial API进行一次开发,就可以将应用发布在所有支持OpenSocial的平台中。OpenSocial定义了一系列协议、标准和接口,用于将应用、容器和其他客户端融合在一起。OpenSocial是基于Web的,开发人员可以使用标准的javascript和html创建应用,应用可以使用OpenSocial的接口,访问SNS平台提供的信息。
OpenSocial一般由以下几部分组成:
Gadget
指由第三方开发人员开发的应用,一般由xml配置文件、javascript和html等其他资源组成。通过javascript调用OpenSocial提供的标准通用API来和SNS平台进行交互。应用的开发者可以使用OpenSocial的Javascript接口编写基于HTML和Ajax的Social应用,还可以使用Flash/Flex技术,开发用户界面更友好的富Internet应用。Gadget运行于Gadget容器中。
Gadget容器:
一个SNS平台,如果可以集成OpenSocial应用,我们就称其为Gadget容器。作为一个Gadget容器,需要执行渲染Gadgets、代理请求、响应REST和RPC请求等工作,还需要实现OpenSocial标准中描述的接口,这些接口包括:获取用户信息、存储和读取用户活动、存储和读取数据、发送消息等。Apache Shindig是OpenSocial容器的一个参考实现。
第三方网站
提供js及rest等风格的API,供开发的Gadget调用。这些Gadget一般会部署到第三方的平台,Gadget使用OpenSocial标准的Javascript接口做Ajax调用,通过SNS平台代理调用第三方网站的API。
如图:支持 OpenSocial 标准后的开发方式
控件简介
Gadget 是一个独立的应用。它由一个特定格式的 XML 文件定义,这个 XML 文件中包含定义 Gadget 应用行为的 HTML、CSS、JavaScript 代码和图片、声音、视频等资源。HTML 可以用来展现内容。JavaScript 用于获取服务器数据,执行应用逻辑,动态修改展现内容。CSS 用来指定内容展现的样式。下面的例子(helloworld.xml)展示了 Gadget 基本的元素。
<?xml version="1.0" encoding="UTF-8" ?> <Module> <ModulePrefs title="Hello World!"> <Require feature="dynamic-height"/> </ModulePrefs> <Content type="html"> <![CDATA[ Hello, world! ]]> </Content> </Module>
在这个例子中,开发人员可以看到定义 Gadget 行为的几个重要元素:
<Module> 表示这个 XML 文件定义了一个 Gadget。
<ModulePrefs> 描述 Gadget 的元数据和作者信息。
<Require feature=" dynamic-height " /> 声明了 Gadget 需要加载一个 Feature。Feature 实现了特定功能 JavaScript API。
<Content type="html"> 声明 Gadget 的内容是 HTML。
<![ CDATA[ … ]]> 包含了 Gadget 的内容:JavaScript、CSS、HTML。这个元素类似于 HTML 页面的 body 元素。
javaScript api
OpenSocial 中的 JavaScript API 通过 Feature 来组织。每个 Feature 是一个可以单独加载的 JavaScript 模块。通过以下的语法,Gadget 可以声明加载某个 Feature。
<Require feature="dynamic-height"/>
Feature可以分为以下几类:
展现一个 Gadget 肯定都会用到的特性,诸如 globals、core、rpc。
globals 定义了一些全局变量,如 gadgets、shindig、osapi。
core 特性提供了展现一个 Gadget 所需要的核心功能,包括 io 操作、用户设置等。
rpc 特性提供了整个 rpc 调用的实现。shindig 默认使用 iframe 来展现 Gadget,如果 Gadget 需要访问外部容器定义的某些方法,就需要通过 rpc 调用来完成。
Gadget 容器用到的特性,诸如 shindig-container、container。
OpenSocial 相关的一些特性,诸如 osapi、opensocial-data、opensocial-reference 等。
一些 util 特性,诸如 shindig.uri、shindig.xhrwrapper、xmlutil。
跟 Gadget 的用户界面比较相关的特性,诸如 settitle、tabs、views、minimessages、flash。
settitle 顾名思义就是设置 Gadget 的标题,使用这个特性可以在 Gadget 里面根据需要来动态的设置 Gadget 的标题。
tabs 特性支持在 Gadget 里面方便的创建多个 tab 和对应的内容。
views 特性提供了可以在多个 view 之间切换的功能。Gadget可以定义多个 view,使用 views 特性可以自由的在多个 view 间切换。
minimessages 特性则提供了几种类型的提示消息,比如显示多长时间就自动消失的消息提示。
flash 特性则可以在 Gadget 里面嵌入 flash 文件。
为了 Gadget之间的通信所用到的特性,pubsub 和 pubsub-2。
常用的globals、core、rpc、util已经默认引入,其他的Feature需要显式require引入。
下面是一个复杂些的例子:
gadget.xml
<?xml version="1.0" encoding="UTF-8"?> <Module> <ModulePrefs title="Make Request Example"> <Require feature="dynamic-height"></Require> </ModulePrefs> <Content type="html"> <![CDATA[ <script type="text/javascript" src="makeRequest.js"></script> <h4>Select an NHL team and press "GO!" to see more information</h4> <select id="team"> <option value="BOS">Boston</option> <option value="PHI">Philadelphia</option> <option value="TBL">Tampa Bay</option> <option value="CHI">Chicago</option> <option value="VAN">Vancouver</option> <option value="SJS">San Jose</option> </select> <button type="button" onclick="lookupTeam()">GO!</button> <br /> Team ID: <span id="teamID"></span><br /> Team Name: <span id="name"></span><br /> Conference: <span id="conference"></span><br /> Division: <span id="division"></span> <script type="text/javascript"> gadgets.util.registerOnLoadHandler(init); </script> ]]> </Content> </Module>
makeRequest.js
function init(){ var miniMessage = new gadgets.MiniMessage(); miniMessage.createStaticMessage("Welcome to the OpenSocial Explorer!"); } function lookupTeam() { var index = document.getElementById('team').selectedIndex; var options = document.getElementById('team').options; var teamID = options[index].value; var params = {}; params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.JSON; var url = 'http://www.nicetimeonice.com/api/teams/' + teamID; gadgets.io.makeRequest(url, function(response) { if (response.errors.length == 0) { var data = response.data; document.getElementById('teamID').innerHTML = data.teamID; document.getElementById('name').innerHTML = data.name; document.getElementById('conference').innerHTML = data.conference; document.getElementById('division').innerHTML = data.division; gadgets.window.adjustHeight(); } else { gadgets.error('There was an error making the request.'); } }, params); }
可以看到,OpenSocial并不是为Portal打造的,它主要是为SNS平台的交互定义规范。但由于它的交互都是基于rest和标准的html,系统间的耦合很小,部署也更简单方便,是很好的一个选择。去掉它的社交属性,Gadget容器加上portal的管理方式,这不正是我想要的吗!
(三)OpenSocial版Portal
说真来容易,真做起来,还是不易,好在前人已经铺路,我这个后来者就可以乘凉了。
infoScoop这个项目已经实现了我想要的所有功能!portal+OpenSocial,完美的组合。而且这个开源项目代码结构组织的很好,后台只负责返回json和js格式的数据,前台js负责处理Portlet(或叫Gadget)的展现逻辑、这样后台的代码量就很少,而且技术选型也很好,只用了hibernate和servlet,没有用什么怪异的技术,它使用Apache Shindig这个标准套件来解析Gadget,代码分层很清楚,很容易理解。
前台使用js处理展现逻辑时,使用js面向对象组织,代码可读性很强,组织的也很清楚。
终于在经历波折后实现了我想要的功能,感谢互联网的改变,让分布式系统交互更容易,感谢开源的力量。