Liferay 6.1开发学习(二十二):在插件工程中使用kaleo工作流

工作流的集成步骤

第一步:请确保ServiceBuilderxml文件中的相应实体包含以下内容,并执行ServiceBuilder

1.     <!-- workflow fields -->  

2.     <column name="companyId" type="long" />  

3.     <column name="groupId" type="long"/>  

4.     <column name="title" type="String"></column>  

5.     <column name="content" type="String"></column>  

6.       

7.     <column name="status" type="int"></column>  

8.     <column name="statusByUserId" type="long"></column>  

9.     <column name="statusByUserName" type="String"></column>  

10.  <column name="statusDate" type="Date"></column>  

11.  <reference package-path="com.liferay.portal" entity="WorkflowInstanceLink"></reference>  

第二步:在liferay-portlet.xml中添加如下内容(asset-renderer-factory可以先暂时不添加)。

1.     <asset-renderer-factory>com.huqiwen.asset.NewsAssetRenderFactory</asset-renderer-factory>  

2.     <workflow-handler>com.huqiwen.workflow.NewsWorkflowHandler</workflow-handler>  

第三步:编写NewsWorkflowHandler,这类名由自己定义,这两个类的具体定义如下:

NewsWorkflowHandler是工作流处理的核心类,比如状态的改变等。继承BaseWorkflowHandler类,此类的示例代码如下:

1.     public class NewsWorkflowHandler extends BaseWorkflowHandler {   

2.       

3.         public static final String CLASS_NAME = News.class.getName();   

4.         @Override  

5.         public String getClassName() {   

6.             return CLASS_NAME;   

7.         }   

8.       

9.         @Override  

10.      public String getType(Locale locale) {   

11.          return ResourceActionsUtil.getModelResource(locale, CLASS_NAME);   

12.      }   

13.    

14.      @Override  

15.      public Object updateStatus(int status, Map<String, Serializable> workflowContext)   

16.              throws PortalException, SystemException {   

17.          long userId = GetterUtil.getLong(   

18.                  (String)workflowContext.get(WorkflowConstants.CONTEXT_USER_ID));   

19.              long resourcePrimKey = GetterUtil.getLong(   

20.                  (String)workflowContext.get(   

21.                      WorkflowConstants.CONTEXT_ENTRY_CLASS_PK));   

22.              ServiceContext serviceContext = (ServiceContext)workflowContext.get(   

23.                      "serviceContext");   

24.          return NewsLocalServiceUtil.updateStatus(userId, resourcePrimKey, status, serviceContext);   

25.      }   

26.    

27.  }  

说明

CLASS_NAME:为我们要添加工作流的实体,比如我这里是新建的一个News的实体。

getType():此方法实际是用来处理国际化的,有些地方可以看到是写的mode.resource+CLASS_NAME,其实这里不重要,这个值的使用就是提供一个key值,用来显示国际化的Key值,对应于国际化资源文件中的key,这里在插件工程的content/Language_zh_CN.properties里面添加此处的说明,比如此示例中我添加的为:model.resource.com.huqiwen.portlet.article.model.News=\u65B0\u95FB\u5BA1\u6838

updateStatus():此方法是用来处理工作流流转的方法,具体的可以照抄上面的内容,return的方法为实体的xxServiceLocalUtil方法,因为我这里的实体名为news,所以如上。

第四步:在我们的实体的处理方法里面添加自定义方法updateStatus,在xxLocalServiceImpl(此处为NewsLocalServiceImpl)里面添加updateStatus方法,方法的定义如下,完成之后再次执行ServiceBuilder

1.     public News updateStatus(long userId,long resourcePrimKey,int status,ServiceContext serviceContext) throws NoSuchUserException, SystemException{   

2.         User user = userPersistence.findByPrimaryKey(userId);   

3.         Date now = new Date();   

4.         News news = newsPersistence.fetchByPrimaryKey(resourcePrimKey);   

5.            

6.         news.setModifiedDate(serviceContext.getModifiedDate(now));   

7.         news.setStatus(status);   

8.         news.setStatusByUserId(user.getUserId());   

9.         news.setStatusByUserName(user.getFullName());   

10.      news.setStatusDate(serviceContext.getModifiedDate(now));   

11.      newsPersistence.update(news, false);   

12.      return news;   

13.  }  

基本上照抄就行,需要注意的是返回的内容为相对应的实体。

到这里时,如果是第二步中没有添加asset-renderer-factory,则我们发布工程,就可以在后台的工作流配置的地方,看到我们添加的工作流了,也可以进行基本的流转的,但是我们仔细测试的话会发则,在我的任务里面,当点击工作流名称的时候不能看到工作流的详情,那要怎么处理呢?接下来我们完善此工作流的处理。

第六步:(第二步里面的asset-renderer-factory确保已经添加),建立NewsAssetRenderFactory类,继承BaseAssetRendererFactory,此类的示例代码如下:

1.     public class NewsAssetRenderFactory extends BaseAssetRendererFactory {   

2.            

3.         public static final String CLASS_NAME = News.class.getName();   

4.         public static final String TYPE = "news";   

5.            

6.         @Override  

7.         public AssetRenderer getAssetRenderer(long classPk, int type)   

8.                 throws PortalException, SystemException {   

9.             int status = WorkflowConstants.STATUS_ANY;   

10.          /**

11.           * 如果需要根据不同的状态获取不同的内容,可以在此进行设置  

12.           */  

13.          if (type == TYPE_LATEST_APPROVED) {   

14.              status = WorkflowConstants.STATUS_APPROVED;   

15.          }   

16.             

17.          News news = NewsLocalServiceUtil.getNews(classPk);   

18.          return new NewsAssetRender(news);   

19.      }   

20.    

21.      @Override  

22.      public String getClassName() {   

23.          return CLASS_NAME;   

24.      }   

25.    

26.      @Override  

27.      public String getType() {   

28.          return TYPE;   

29.      }  

此类也比较简单,基本上是复制下来使用即可,在自己的类里面将news修改为自己对应的实体,这里又有一个新的类NewsAssetRender,同样需要新建,建立NewsAssetRender,继承BaseAssetRenderer,示例代码如下:

1.     public class NewsAssetRender extends BaseAssetRenderer {   

2.         public static final String CLASS_NAME = News.class.getName();   

3.            

4.         public NewsAssetRender(News news){   

5.             _news = news;   

6.         }   

7.         @Override  

8.         public long getClassPK() {   

9.             return _news.getNewsId();   

10.      }   

11.    

12.      @Override  

13.      public long getGroupId() {   

14.          return _news.getGroupId();   

15.      }   

16.    

17.      @Override  

18.      public String getSummary(Locale arg0) {   

19.          return HtmlUtil.stripHtml(_news.getContent());   

20.      }   

21.    

22.      @Override  

23.      public String getTitle(Locale arg0) {   

24.          return _news.getTitle();   

25.      }   

26.    

27.      @Override  

28.      public long getUserId() {   

29.          return _news.getUserId();   

30.      }   

31.    

32.      @Override  

33.      public String getUserName() {   

34.          return _news.getUserName();   

35.      }   

36.    

37.      @Override  

38.      public String getUuid() {   

39.          return _news.getUuid();   

40.      }   

41.    

42.      @Override  

43.      public String render(RenderRequest renderRequest, RenderResponse renderResponse, String template)   

44.              throws Exception {   

45.          if (template.equals(TEMPLATE_FULL_CONTENT)) {   

46.              renderRequest.setAttribute(   

47.                  "news", _news);   

48.    

49.              return "/html/asset/" + template + ".jsp";   

50.          }   

51.          else {   

52.              return null;   

53.          }   

54.      }   

55.    

56.      private News _news;   

57.  }  

此类需要说明的方法为render方法,此方法是我们点击查看详情的时候显示的,可以看到他是跳转到了/html/asset/xxx.jsp的页面,其实这个路径为/html/asset/full_content.jsp的页面。

现在我们的portlet插件工程的docroot目录下面建立上面的路径的jsp页面,JSP里面的内容如下:

1.     <%@page import="com.huqiwen.portlet.article.model.News"%>   

2.     <%@ include file="/html/init.jsp" %>   

3.       

4.     <%   

5.     News news = (News)request.getAttribute("news");   

6.     %>   

7.       

8.     <%= news.getContent() %>  

此处只是示例,所以只是显示了正文,此页面可以根据实际的情况自由编写。

至此,整个工作流的使用介绍完毕,现在deploy插件工程,工作流即集成到了我们的portlet插件工程里面。

工作流的初始化

上面介绍的部分为配置部分,但是工作流什么时候启动呢?一般是我们在添加一个新的内容时启动工作流,启动的代码如下:

WorkflowHandlerRegistryUtil.startWorkflowInstance(companyId,userId, News.class.getName(), news.getNewsId(), news,serviceContext);

此代码在我们添加保存实体后进行。

为了方便对流程的详情进行流利,我们要添加asset的支持,在上面的代码后台,添加如下代码,后面的两个null为分类和标签,如果有使用的话就添加,没有使用的话使用null即可。

AssetEntryLocalServiceUtil.updateEntry(userId, groupId,News.class.getName(), news.getNewsId(), null, null);

上面是当我们添加内容的时候启动了工作流,当我们删除信息时也需要删除工作流,删除的代码如下:

WorkflowInstanceLinkLocalServiceUtil.deleteWorkflowInstanceLinks(news.getCompanyId(), ews.getGroupId(), News.class.getName(),news.getNewsId());

回顾

看到第一步的时候,可能会有疑问,我的实体必须要有titlecontent字段么?我们仔细分析一下代码其实也不是必须的,title的目标是我们可以看到要审批的内容的标题,content的内容是方便我们对内容进行预览,我们可以标题的地方取name值,正文的地方取summary,或者是整个实体的toString,或者是其他内容的拼接。

收集的一些云计算的资料

最近在做关于企业私有云的项目,从网上收集了一些关于云计算(主要为企业私有云)的资料,记录于此。(持续更新……)

概念性介绍

VMWARE的私有云介绍:

http://www.vmware.com/cn/cloud-computing/private-cloud.html

IBM的云计算介绍:

http://www.ibm.com/developerworks/cn/cloud/cloudbasic.html

微软的云计算介绍:

http://technet.microsoft.com/zh-cn/cloud/private-cloud

上面这几个个人感觉算是不错的资料,有比较系统的介绍,下面这几篇IBM开发都社区上的文章尤其好:

云计算服务模型,第 1 部分: 基础架构即服务(IaaS

http://www.ibm.com/developerworks/cn/cloud/library/cl-cloudservices1iaas/index.html

云计算服务模型,第 2 部分: 平台即服务

http://www.ibm.com/developerworks/cn/cloud/library/cl-cloudservices2paas/index.html

云计算服务模型,第 3 部分: 软件即服务

http://www.ibm.com/developerworks/cn/cloud/library/cl-cloudservices3saas/index.html

云百科:这里有许多关于云的概念性的介绍,非常不错

http://www.zdnet.com.cn/wiki-index

关于多租户

云计算多租户最佳实践

http://www.ibm.com/developerworks/cn/cloud/library/cl-multitenantcloud/

将您的 web 应用程序转化为多租户 SaaS 解决方案

http://www.ibm.com/developerworks/cn/cloud/library/cl-multitenantsaas/

浅析多租户在 Java 平台和某些 PaaS 上的实现

http://www.ibm.com/developerworks/cn/java/j-lo-mutiltenancy/

将单租户应用程序转换为多租户应用程序

http://www.ibm.com/developerworks/cn/cloud/library/cl-tenantconversion/

一些解决方案PPT

微软私有云解决方案实例分享

http://wenku.baidu.com/view/d9002c076c85ec3a87c2c539.html

微软私有云解决方案案例分享

http://wenku.baidu.com/view/e8f36dcd5fbfc77da269b12d.html

通过 Oracle 融合中间件实现云计算

http://wenku.baidu.com/view/1eed8985bceb19e8b8f6ba3a.html

Cloud Foundry

基于Cloud_Foundry的大企业私有云方案

http://wenku.baidu.com/view/5c8e98cf89eb172ded63b7a8.html

Cloudfoundry自动化部署工具BoshCPI研究分析

http://blog.csdn.net/alan90121/article/details/8177242

cloud controller v2源码解析

http://blog.csdn.net/tibelf/article/details/13295443

DEA_NG 剖析

http://blog.csdn.net/tibelf/article/details/12998145

单机实例安装Cloud Foundry V2

https://github.com/yudai/cf_nise_installer

liferay中的文件下载

觉得Liferay中的文件下载其实很简单,所以一直没有特别写相关的内容,其实主要的的内容在前面介绍ajax的时候已经介绍完了,参考《Liferay 6.1开发学习(十):在Liferay中使用Ajax》,但今天在群上碰到有人问文件下载在Liferay中怎么实现,以本篇文章为例简单说明一下。

现在的场景如下,在页面上有一个链接接或按钮,点击之后希望能够下载指定的文件。

portlet的JSP页面的书写

<portlet:resourceURL var="download"/>
<input type="button" value="下载" onclick="download();">
<script type="text/javascript">
function download(){
 window.location = '${download}';
}
</script>

上面是JSP页面,内容很简单上面只有一个按钮,点击此按钮后进入下载。注意在这里使用的是<portlet:resourceURL>标签,而不是portlet:actionURL

后台业务逻辑处理代码

public void serveResource(ResourceRequest resourceRequest,
 ResourceResponse resourceResponse) throws IOException,
 PortletException {
 HttpServletRequest request = PortalUtil.getHttpServletRequest(resourceRequest);
 HttpServletResponse response = PortalUtil.getHttpServletResponse(resourceResponse);
 
 File file = new File("d:\\cas-server-3.5.2.rar");
 byte[] bytes = FileUtil.getBytes(file);
 
 ServletResponseUtil.sendFile(request, response, "CAS_Server_3.5.2.rar", bytes, ContentTypes.APPLICATION_OCTET_STREAM);
 
 super.serveResource(resourceRequest, resourceResponse);
 }

在这里为了演示,我下载的文件来源是我本地的一个文件。核心代码其实只有一句,就是ServletResponseUtil.sendFile方法,此方法算是Liferay封装的一个工具类,我们也可以像前文ajax那篇文章介绍的一样获取到PrintWriter ,然后使用它写出也一样。下面来介绍一下sendFile的方法里面的参数,前两个参数为httpservletRequestresponse,这两个参数可以参考上面的代码获取,这里不详细说明。

第三个参数:fileName,这个的名称为希望下载的时候显示的是什么名称,此名称最终会在http的头信息的,出现这样的内容Content-Disposition:attachment; filename="CAS_Server_3.5.2.rar"。这样当浏览器保存我们这个文件时就知道文件的名称是什么。

第四个参数:byte数组的文件体。这个没什么说明的。

第五个参数:contentType,这个参数是用来标明文件类型的,liferay的工具类ContentTypes里面对常用的contentType都有说明。如果我们希望文件能够直接下载则就选用APPLICATION_OCTET_STREAM这样的类型,标明是二进制类型,这样浏览器会自动启用下载。如果是期望不同的类型不同的处理,这里可以根据文件的类型,不同的类型写入不同的contentType。如果将contentType标名是图片,则会在浏览器里直接显示图片,如果是PDF,则会在浏览器调用PDF查看器直接在浏览器中打开。

前面这所以说文件下载和AJAX的处理是一样的,现在看整个逻辑确实是一样,区别其实只在contentType上,其实ajax处理也是一种特殊的文件下载,ajaxcontentType,一般是text/plain

其实文件下载的原理就是获取到文件,然后将文件在response里面输出。上面的代码,在实际中我们可能会需要传入一个参数,比如文件id,然后根据文件id获取到文件的内容,再写出。参数的传递和普通的portlt处理一样,同样也可以参考前文的ajax处理那篇博客。

iferay 6.2的更新内容

在写本篇文章的时候,liferay 6.2已经更新到了RC4版本http://sourceforge.net/projects/lportal/files/Liferay%20Portal/,估计6.2正式版发布在即,从sourceforge上面下载了RC4版本,大概体验了也,写一下明显感觉到的6.2的新变化(相对于6.1)。

1、全新的UI6.2UI采用bootstrap的风格,进行了完全的重写。同时对AUI的许多组件进行更新,比如日期组件、富文本编辑器,AUI版本在6.2发布时应该会升级到2.0(6.1.11.7)。对之前控制面板的布局进行了调整,参考官方博客:

http://www.liferay.com/zh/web/jorge.ferrer/blog/-/blogs/new-in-6-2-streamlined-administration-through-site-administration-and-the-new-control-panel-1-2-

http://www.liferay.com/zh/web/jorge.ferrer/blog/-/blogs/new-in-6-2-streamlined-administration-through-site-administration-and-the-new-control-panel-2-2-

改进的主题等,比如屏幕适配,参考如下官方博客:

http://www.liferay.com/zh/web/ryan.schuhler/blog/-/blogs/theming-responsively

2、改进对手机的支持,随着移动设备的普及,liferay现在对手机浏览器的支持有了明显的增强。参考官方博客:

http://www.liferay.com/zh/web/juan.fernandez/blog/-/blogs/liferay-6-2-new-mobile-features-pt-1-

http://www.liferay.com/zh/web/juan.fernandez/blog/-/blogs/liferay-6-2-new-mobile-features-pt-2-

3、增强的模板编辑增强等,添加应用程序模板,将模板管理单独拿出来做设计,参考官方博客:

http://www.liferay.com/zh/web/eduardo.garcia/blog/-/blogs/new-ways-of-customization-with-application-display-templates-part-i-

4、改进对maven的支持。6.1时采用maven开发也可以,但总之不太方便,6.2提供了对maven的更好的支持。包手将会随6.2一起发布的Liferay IDE 2.0也提供了相应的maven开发支持插件。

5、站点支持上下级,现在可以建立树状结构的站点群。

6、在控制面板里面添加了回收站的功能。

7、应用上传安装又回来了,在Liferay6.0时,我们还可以通过后台上传一个War包进行安装portlet,在6.1时此功能被取消了,现在在6.2里面此功能又回来了,路径为控制面板-->应用程序-->App Manager-->安装。

8、权限管理更清晰,现在控制面板的权限管理的界面比之前的看着更清晰了,更方便的在角色处统一管理权限。

9、其他功能的调整增强,如日历管理、站点管理、portal管理、内容管理、文档管理、二次开发的支持、基础框架的升级(主要为liferay依赖的jar包)、Power User的权限调整等。

现在就暂时看到了这么多,等官方正式发布6.2的时候应该会有更新日志的。

Cloud not reserve enough space for object heap

Liferay中执行Builder Service时,有时候会出现下面的错误:

Java Result:1
Cloud not create the Java virtual machine.
Error occureed during initalization of VM
Cloud not reserve enough space for object heap

此问题的原因在编译的时候为JVM分配的内存大于了剩余的物理内存大小。本质原因就是物理内存太小了。解决方法就有两个:

加大物理内在

1、直接升级电脑,添加内存。

2、如果内存够大(4G+),则检查操作系统是不是64位,安装的JDK是不是64位。

调整Liferay编译时所分配的内存

执行Service Builder时,执行的JVM的内存分配是在ant文件里面配置的,在SDK目录下面的build-common-plugin.xml文件里面。找到类似下面的片断:

 

1.     <java  

2.         classname="com.liferay.portal.tools.servicebuilder.ServiceBuilder"  

3.         classpathref="service.classpath"  

4.         fork="true"  

5.         newenvironment="true"  

6.         outputproperty="service.test.output"  

7.     >  

8.         <jvmarg value="-Xms512m" />  

9.         <jvmarg value="-Xmx1024m" />  

10.      <jvmarg value="-Xss2048k" />  

11.      <jvmarg value="-XX:MaxPermSize=512m" />  

这里即是Liferay Service Builder时所设置的JVM参数,将里面的-xmx1024改为512m-XX:MaxPermSize改为256m,然后保存,重新执行Service Builder,问题应该即可以解决。

Liferay中的ActionRequest与RenderRequest

常见到有同学问liferay中的ActionRequestRenderRequest是什么关系,有什么样的区别,我尝试着用我的理解解释一下。

ActionRequest与RenderRequest简介

首先ActionRequestRenderRequest都是PortletRequest的子类。但是他们两个又是不同的两个对象。

PortletRequest的整个对象结构如下图:

ActionRequestActionRequest的作用域是portlet在动作处理阶段,在此阶段中,portlet不知道应该怎么显示他自己,不知道是以html还是vml等内容呈现,不知道portlet的窗口模式是最大化,还是最小化。

RenderRequestRenderRequest的作用域是portlet的渲染阶段,在此阶段中portlet知道他自己要怎么显示,显示哪些内容,portlet的窗口状态是什么,是否允许显示等。

简单的说就是RenderRequest负责显示,ActionRequest负责处理。

Liferay中的使用

Liferay中什么时候使用RenderRequestActionRequest的呢?

当我们在portlet中使用标签<portlet:renderURL><defineObjects>时,一般后台调用就是对应的doview/doedit等方法,里面的requestRenderRequest

当我们在Portlet中使用标签<portlet:actionURL>时,后台对应的方法为processAction或者我们在actionURL里面定义的name的方法名,里面的requestActionRequest

那我们什么时候使用<portlet:renderURL>和什么时候使用<portlet:actionURL>呢?

比如我现在有一个portlet,此portlet功能为默认显示一个新闻列表,在此页面上有新闻的搜索查询,有新闻的新增、修改等功能。

上面的默认显示新闻列表,这个时候是走的后台的doview方法,里面的requestRenderRequest。在此列表上有一个根据关键词查询新闻的功能,希望查询后还是在此页面显示,这个时候我们一般是将这个查询的请求的地址使用<portlet:renderURL>,doview里面接收参数,进行查询的处理逻辑后显示。

当我点击新建新闻的按钮时,需要跳转到新增页面,这个时候这个新增的按钮触发时请求的页面,我们一般对此地址使用<portlet:actionURL>。在此新增页面上执行表单提交时,一般也是使用<portlet:actionURL>

总结一下:

当在portletdoviewdoeditdohelp等窗口模式下的页面,执行完动作后不跳转其他页面,需要返回这些页面或者需要重新渲染这些页面的时候,使用<portlet:renderURL>

当我们在portlet中的doviewdoeditdohelp等窗口模式下需要跳转到其他页面的时候使用<portlet:actionURL>,在其他页面中执行表单的处理、跳转等操作时,也使用<portlet:actionURL>

Liferay SDK目录结构说明

本文详细说明一下Liferay SDK下面的目录结构的作用,以Liferay 6.2.0版本的SDK为说明。

Liferay SDK的作用

初次接触Liferay的同学可能会有疑问,开发的时候为啥需要Liferay SDK,这个东西是干嘛的,不要行不行?

首先Liferay SDK的是干嘛的?

SDK(Software Development Kit)软件开发工具包,从字面理解Liferay SDK也即是Liferay的软件开发工具包,准确来讲是Liferay二次开发工具包。它的作用是辅助我们方便的进行Liferay的二次开发。

不要行不行?

有人问过我,SDK这个东西不要行不行呀。准确来说不要可以的,就像上面所言SDK的作用是辅助我们方便的进行二次开发,既然是辅助的,不要当然可以。但是如果不使用SDK,进行Liferay的二次开发就会复杂点,会花费更多的时间。

Liferay SDK的目录结构

Liferay 6.2.0 SDK的目录结构如下图所示,我们对主要的目录的来进行说明:

exthookslayouttplportletsthemes:这几个目录的作用一样,放在一起说明。这几个目录是用来存放Liferay不同的工程类型的,在前面《Liferay 6.1开发学习(二):创建一个Portlet工程》中有对不同的工程类型有大概的说明,hooks目录下面放hook工程,themes目录下面放theme工程,同理的ext目录下面放ext工程,普通的portlet工程放到portlets目录下面。这里务必要说明的是,如果我们使用Liferay IDE开发,从SVN上检出或从网上下载的代码,根据不同的工程类型主到不同的目录下面,如果放错在eclipse里面就会出现找不到SDK的错误。

这几个目录下面一般都有四个默认的文件,build.xmlbuild-common-xxx.xml这两个文件是ant的文件,里面根据不同的工程类型有一些不同的ant脚本,以方便工程的编译部署。create.batcreat.sh分别为windowsLinux下面的批处理和shell脚本,用来创建指定的工程。我们在Liferay IDE里面创建一个工程的时候就类似执行了一个这样的脚本 。

lib:此目录下面的jar包为ant执行时所依赖的jar包。

misc/jalopy.xmljalopy的格式描述文件。用以Service Builder时代码格式说明。

tools:此目录下面保存的为Liferay不同插件工程的模块,我们使用antLiferay IDE创建工程后生成的内容都是基于此目录下面的模板进行构建。如果项目有特殊的需求,可以修改这里面的模板以更方便的符合我们项目实际需求。

build.properties:构建时一些属性的配置信息,比如SDK的路径,Liferay Tomcat的路径等信息等,都在此文件里面使用,此文件里面的属性信息供Liferay IDEant等使用。

其他的文件都是ant的构建文件,比如如何打包编译不同的插件工程,阅读时需要一定的ant知识。比如有人在执行Service Builder时会提时内存不足,那这个所需要的内容的大小是在哪里面定义的呢?

我们在Liferay IDE里面执行的ant操作都是在这些ant文件中的,比如执行Service builder时,是执行build-common-plugin.xml文件中的build-service操作,可以打开build-common-plugin.xml在其中找到<target name="build-service">。通过阅读此文件,我们就可以知道在执行Service Builder时做了些什么操作,在这个里面就可以看到里面对JVM的参数设置:

<jvmarg value="-Xms512m" />
 <jvmarg value="-Xmx1024m" />
 <jvmarg value="-Xss2048k" />

builder Service时,内存不足时,就可以根据适当的情况在此将JVM的参数设置小一点。

同样的我们通过阅读这样的ant文件,也可以知道在点击deploy的时候做了些什么操作。通过对ant文件的阅读能够加深我们的Liferay的理解。主要的ant文件为build-common-plugin.xml,这里存的是主要的ant的操作,不同的插件工程不同的ant操作是在各自己的模块下面的,比如hook工程的一些特殊的ant操作是在hooks/build-common-hook.xml里面定义,portlets工程的一些特殊操作是在portlets/build-common-portlet.xml里面定义的。

Liferay与CAS及LDAP

CAS与LDAPLiferay实现单点登录时经常会用到的东西,本篇文章分享一下个人对LiferayCASLDAP等三者之间的联系与集成。在前面转载过一篇发表在IBM的开发者技术社区网站的文章《转:Liferay 集成 CAS 实现单点登录与应用系统集成》,里面有些原理的内容阐述的不多,本文将试着从理论层面对liferayCASLDAP的关系进行一个比较清晰的描述,本文暂不涉及技术实现细节,主要讲理论。

单点登录

单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。(百度百科

随着企业信息化的发展,在一个企业中经常会碰到一个业务人员需要登录多个业务系统,比如OAHRERPCRMEAMEIP等,业务人员对此烦不胜烦,同时对企业的安全、管理等形成了挑战。单点登录的目标就是用户只需要登录了其中的一个系统,其他的系统也都不用登录了,用户在一个系统进行了注销,其他系统也都注销了。

实现单点登录的技术有许多,比如CASADFSOpenID等等。我们今天要介绍的CAS只是单点登录实现方案的一种选择。

CAS简介

CAS是Yale(耶鲁)大学发起的一个开源项目,旨在为Web应用系统提供一种可靠的单点登录方法,CAS200412月正式成为JA-SIG的一个项目。

CAS特点:

1、开源的企业级单点登录解决方案,有非常多的成功案例。

2、CAS Server根据需要可以独立部署成Web应用。

3、CAS Client支持众多的客户端(这里指单点登录系统中的各个Web应用),包括Java.NetPHPPerlRuby等。

LDAP简介

LDAP是轻量目录访问协议,英文全称是Lightweight Directory Access Protocol,一般都简称为LDAP。我们可以简单的把他看作是一种特殊的数据库。为读查询做了非常多的优化,拥有非常高的读性能,但是写的性能相对较低。

在单点登录系统中,通常选择LDAP做统一用户的存储,优势有如下:

1、 符合目录X.500标准,易于系统间整合

2、有效保证资源类产品与外界业务间的共享和整合

3、发挥了目录存储的查询效率,提升平台性能

Liferay与CAS整合

Liferay通常被当作门户使用,门户通常是当作一个企业中系统的入口,当用户在Liferay门户上登录之后,就能访问其他的系统而不必再次登录,以达到“一点登录,多点漫游”的目标。

Liferay里面,默认整合了CAS,可以方便的让我们将LiferayCAS进行集成,以实现一个单点登录系统。

CAS从大的方面看有两部分组成,CAS ClientCAS Server,一个客户端,一个服务端。

CAS Server

CAS Server 负责完成对用户的认证工作, CAS Server一般需要独立部署,我们可以将他部署到单独的服务器,也可以和Liferay放到同一个Tomcat下面(这种从逻辑上来看其实也是独立的),推荐前者,应用和CAS Server是分开的,所以《转:Liferay 集成 CAS 实现单点登录与应用系统集成》这篇文章中介绍的集成方式一般是不推荐的,作为演示测试还可以使用,生产环境强烈不推荐。

CAS Server处理用户名、密码等凭证 (Credentials) ,它可能会到数据库检索一条用户帐号信息,也可能在 XML 文件中检索用户密码,也可能从LDAP中检索。无论哪种方式, CAS均提供一种灵活但统一的接口与实现分离的方式, CAS 采用的认证方式跟 CAS 协议是分离的,也就是,这个认证的实现细节可以自己定制和扩展。这段话怎么理解呢?也就是说CAS从用户数据源(可以是关系数据库、xmlldapnosql等)中查询出用户名和密码,和用户输入的进行比较,如果匹配就认为认证成功。数据源的类型可以根据我们的实际需求进行定义,他不有关心我们的用户数据存在哪,甚至存在文本文件中也行,只要我们写相应的适配器即可,所以我们从这里可以看到LDAP其实并不是必须的(统一用户部分后面说明)。认证方式跟CAS协议分离的意思是,我们在数据源中保存的密码可能是加密的,比如MD5加密、SHA加密等,而用户输入的帐号密码是没加密,我们在做这个验证的时候,这个加密或比较的过程可以自己实现,CAS里面只提供的是一种接口,《转:Liferay 集成 CAS 实现单点登录与应用系统集成》在这篇文章中就基于CAS的接口实际了Liferay密码的加密。

CAS Client

CAS Client 负责部署在客户端,也就是待认证的应用,Liferay其实就是一个CAS Client,所以我们在liferayjar包里面可以看到CAS Clientjar包,CAS Client 的作用是当对本地 Web 应用的受保护资源有访问请求,并且需要对请求进行身份认证时,Web 应用不再接受任何的用户名密码等类似的 Credentials ,而是重定向到 CAS Server 进行认证。

回到LiferayCAS的整合,当我们整合成功之后,我们在浏览器里面输入http://localhost:8080/c/portal/login这样的登录地址,或者访问一个需要登录才能访问的页面时,会跳转到CAS Server(前面说了CAS Server可以和Liferay部署到同一个tomcat下面--但是不推荐,但是更推荐部署成不同的tomcat下面,尤其推荐和Liferay就不部署在一个服务器上)上的登录地址,如:http://localhost:8020/cas/login类似这样的页面,我们需要在CAS Server上输入帐号密码进行认证,认证成功后返回到Liferay的页面上。

CAS怎么判断用户有没有登录呢?CAS ClientLiferay或业务应用整合的时候,需要在业务应用(如Liferay)的web.xml里面添加一个filter,来判断请求中是否有token

CAS的一个认证过程如下图所示:

图片来源于网络

CAS Client与受保护的客户端应用部署在一起,以Filter方式保护受保护的资源。对于访问受保护资源的每个Web请求,CAS Client会分析该请求的Http请求中是否包含ServiceTicket,如果没有,则说明当前用户尚未登录,于是将请求重定向到指定好的CASServer登录地址,并传递Service(也就是要访问的目的资源地址),以便登录成功过后转回该地址。用户在第3步中输入认证信息,如果登录成功,CAS Server随机产生一个相当长度、唯一、不可伪造的ServiceTicket,并缓存以待将来验证,之后系统自动重定向到Service所在地址,并为客户端浏览器设置一个TicketGrantedCookieTGC),CASClient在拿到Service和新产生的Ticket过后,在第56步中与CAS Server进行身份合适,以确保ServiceTicket的合法性。

LDAP干啥

上面貌似没有提LDAP的事,他干嘛呢?比如我们现在有OAHRERPCRMEAMEIP等六个系统,如果这六系统里面的用户帐号都不一样,张三在OA里面的登录帐号是zhangsan,在HR里面的登录帐号是工号:00356ERP里面是中文名称张三,在ERP里面是email地址:zhangsan,在CRM里面是userid50291等等,不同的系统里面的帐号不一样,各自是独立的体系,现在OA里面的zhangsan登录了,HR里面并没有zhangsan这个用户呀,自然OA里面的zhangsan也就没有办法形成统一认证,统一登录了。

所以在实现单点登录的前提,一般是要实现统一用户,我们将OAHRERPCRMEAMEIP这些系统里面的用户信息都统一了或者以某一个系统的为准,企业里面一般以人力资源系统的数据为准,用户在HR里面是00356,在其他系统里面也都是00356

由于LDAP优秀的读性能以及符合目录标准,我们一般将统一用户之后的用户认证信息保存到LDAP里面所有的业务系统OAHRERPCRMEAMEIP等里面的用户认证时的数据都从LDAP里面来取。这个时候可能就有同学问了,我就是不想用LDAP行不,行呀,我们只要将这些用户信息统一的保存一份,就行了,保存到哪无所谓。可以是LDAP,也可以是关系数据库、NOSQL数据库、KV数据库等各种数据源都行。

那我们为啥选择使用LDAP呢?前面提过LDAP的优势,认证的过程主要是查询,LDAP有非常高的读性能;其次我们做统一用户的时候是希望做系统数据整合的,有许多第三方的系统都默认支持LDAP,如果我们采用其他的方式,做用户数据的导入时可能就需要再重新开发接口或适配器。

LDAP与CAS的关系

还是以我们与Liferay整合为例子,当用户登录的时候,跳转到了cas的登录界面比如:http://localhost:8020/cas/login,这个时候用户输入了帐号密码,CAS接收到此帐号密码的时候,根据帐号名称从统一用户数据源(LDAP)中查询一条记录,查询出来之后,获取到了这个帐号的信息,将里面的密码取出来和用户输入的做比较,这个比较的过程就是认证了,这个比较的过程我们可以自己来写实现,比如我们的数据源中的用户数据保存的是MD5加密的,我们就要在这里对用户的密码进行一次MD5加密,再和是查询出来的比较。这也是前面说的认证和CAS协议分离。如果这个比较的过程我们不做二次开发,CAS默认的实现是只要相同就通过了,所以有加密的我们都要再根据接口写一下相应的实现。

LDAP与Liferay的关系

LDAP里面存的是标准的用户数据,Liferay里面的用户应该从LDAP里面导入。所以LDAP里面的用户数据和Liferay里面的用户数据的基础属性是相同的,我们在LDAP里面的配置就是配置将LDAP的数据导入到Liferay里面来。当我们在liferay里面编辑一条用户数据的时候,在保存成功后也会同步到LDAP里面(可以配置为是否开启,一般统一用户的时候只允许一个地方写数据,其他的节点都是只读数据)。

在这个过程中LDAP相当于一个中央仓库,Liferay其实也是一个客户端。

Liferay、CASLDAP的关系

现在假设我们在LDAP里面添加了一条数据,这个时候LDAP不会通知LiferayLiferay里面是肯定没有这个用户,但是这个用户要登录怎么办呢?此时当此用户登录的时候,Liferay检测到此用户不存在,后台会从LDAP里面将用户导入到Liferay中,此过程对用户是不可见的。新建的用户登录和老用户登录的体验是一致的。

相关问题

可能有同学会问,上面说的单点登录都是统一用户的,如果是老系统没办法做统一用户,是不是就不能做单点登录了?

一般做单点登录肯定是最好上统一用户,这样好管理,开发也方便,但对于没办法做的老系统或老用户,如果要做单点登录一般就是做的用户映射,要知道我们前面说的张三、zhangsan00356等等是同一个人,这些之前要建立一个映射关系。

CAS Client需要在web.xml里面添加过滤器,还是需要对系统做改造,老系统没法改造怎么办?

对于没法改造的老系统一般可以采用模拟登录的方式,这个时候就不用CAS了。

人力资源的数据与统一用户的数据啥关系?

一般一个企业里面的人力资源的数据是最标准的用户数据(前提是管理规范的公司),比如一个新员工入公司之后,员工的数据必然是先进入到人力资源系统里面的,这个时候需要人力资源和统一用户的之间有数据的同步。

人力资源的数据保存的是员工的属性信息,比如员工的岗位、工资、名称、绩效成绩等;统一用户里面的数据只是用户的基础数据,统一用户里面的数据是人力资源里面的员工数据的一个子集。

不想使用LDAP,也不建立统一用户,就使用Liferay里面的用户作为标准行不?

完成可行,不使用LDAP,我们就使用Liferay里面的用户数据作为标准,其他的系统比如OAERPCRM等这些的用户数据都从Liferay里面进行导入,并和Liferay里面的用户数据保持一致。我们认证的时候就让CASLiferay的数据库中进行帐号的验证。其实这个时候Liferay里面的用户已经算是统一用户了。

统一用户的关键是以一个系统的数据作为标准,只要保持全局的统一即可,我们也可以使用OA里面的用户数据作为标准,也可以使用ERP里面的用户数据作为标准都行。

Liferay中让portlet不显示边框

在很多的应用场景下我们不希望显示portlet的边框,要去掉Portlet边框大概有以下三种方法:

一、手动设置

Portlet的右上角,点击设置--外观和风格--是否显示边框,配置成不显示。

适用范围:

此方法适用于,当一个页面中只有几个Portlet需要调整边框的时候。

二、在Portlet的配置文件中

portlet.xml中中添加以下配置文件,将此信息添加相要默认不显示边框的portlet里面。

1.     <portlet-preferences>   

2.         <preference>   

3.             <name>portletSetupShowBorders</name>   

4.             <value>false</value>   

5.         </preference>   

6.     </portlet-preferences>  

适用范围:

此方法适用于,让portlet添加到页中就不显示边框。当然也可以通过方法1的配置,让他再显示边框。

三、通过主题控制

我们可以在主题里面添加一个配置,让我们在主题里面来配置是否显示边框。

在主题的liferay-look-and-feel.xml文件里面,在theme的标签里面添加配置settings,达到类似如下的效果:

1.     <theme id="themexx" name="ThemeXX" >   

2.         <settings>   

3.                <setting configurable="true" key="portlet-setup-show-borders-default" type="checkbox" value="false"></setting>   

4.            </settings>   

5.     </theme>  

上面的配置可以让我们在主题里面控制当前主题范围内容的Portlet是否显示边框。

适用范围:

此方法适用于我们要批量的调整Portlet的边框,如一个站点、一个页面中的所有Portlet等。

总结

不过在实际的开发中,我们一般是结合方法1和方法3使用。方法2只是针对特殊的情况,Portlet默认加载的时候是显示边框的(如果在方法3里面配置了不显示边框,此时也不显示),如果想要portlet在被添加到页面时就不显示边框,则使用方法2

方法1和方法2的原理是一样的,最终都是将这个状态保存到数据库的portletpreferences表里面。

你可能感兴趣的:(liferay)