Liferay Portal额外研究(一):初步在新Tomcat下部署

作者:银狐999


Liferay额外研究(一):初步在新Tomcat下部署

 



从现在开始,会把我最近在研究的开源Portal Liferay一些心得贴出来。研究 Liferay 版本: Liferay Professional 4.0.0




柯自聪 兄弟写过一系列的 Liferay Portal 开发文章,参考 http://www.blogjava.net/eamoi/ 。所以基础性内容,本文不再细说,可参考柯自聪的文章或 Blog 。


 



(一)部署Liferay Portlet



简单部署一个portlet并不是很复杂,通过WEB-INF目录下的portlet.xml、liferay-portlet.xml、liferay-display.xml描述即可。



 



 

描述

portlet.xml

portlet.xml 用来定义 Portlet 的诸如部署名称、初始化参数、支持模式、 resource bundle 等普通的初始化信息,包括: portlet-name 、 display-name 、 portlet-class 、 init-param 、 expiration-cathe 、 supports 、 portlet-info 、 security-role-ref 等等。其正式的规范请参考: http://java.sun.com/xml/ns/Portlet/Portlet-app_1_0.xsd

liferay-portlet.xml

定义 Portlet 默认可用的用户组、默认模板、是否支持多个实例等,规范由 http://www.liferay.com/dtd/liferay-Portlet-app_3_5_0.dtd

liferay-display.xml

定义 Portlet 默认的所属类别。 Liferay Portal 对 Portlet 实行按类别管理和划分用户权限。

 



是基于Struts Template页面模版技术来实现页面的,所以Liferay默认提供了com.liferay.portlet.StrutsPortlet这个Portlet Class来完成Portlet处理的;当然为了简单对普通jsp页面的处理,Liferay也提供了com.liferay.portlet.JSPPortlet等其他Portlet Class。



 



扩展了Portlet Mode,增加了about,config,preview,print等几种模式。所以,开发人员基于Liferay进行扩展Portlet Class需要基于com.liferay.portlet.LiferayPortlet。当然,允许直接继承javax.portlet.GenericPortlet进行扩展。



 



(二)部署Liferay应用在新下载的Tomcat上



 



       这一块耗费我不少时间,因为 Liferay 采用了其修改后的 Tomcat (实际上是 Tomcat5.5 ,这是我后来才知道的)。



       但我很不喜欢 Liferay 这种绑定方式,而且默认开发必须采用其层层 Ant Build 方式,并且绑定其 Tomcat 或其他其修改后提供的 server 。—— 当然,在 Liferay 可能认为这样可以减少开发人员对环境配置的关心。



 



       让我来看看,如何在一个新下载的 Tomcat5.0.28 上成功部署 Liferay 应用:



 



(1)       首先把 liferay 工程目录放置 Tomcat_Home/ 目录下,当然,你可以放置在 Tomcat_Home/web-apps 目录下。



(2)       其次,需要把 liferay 默认的目录下的 Common/lib/ext 目录下的一些 jar 包 copy 到 Tomcat_Home/common/lib 目录下。注意,不要放置在 ext 目录下,默认 tomcat5.0.28 是不自动加载 ext 目录下的 jar 包的,这跟 Tomcat5.5 不同。这些 jar 包主要是: hsql.jar , commons-logging.jar , log4j.jar 这三个。



(3)       把 liferay 默认的目录下的 Common/lib/ext 中的 portal-shared.jar 和 portlet.jar ,可以移到 liferay 应用的 WEB-INF/lib 中。



(4)       拷贝 Root.xml 到 Tomcat_HomeconfCatalinalocalhost 目录下,配置 webcontext 信息。并在此配置 DataSource 。注意, liferay 的默认运行中的配置是 Tomcat5.5 的写法,所以此处配置 DataSource 的写法,就必须更改为 Tomcat5.0 的写法。否则,运行过程中会抛“ Cannot create JDBC driver of class '' for connect URL 'null' ”异常 。



<Context docBase="D:       Tomcat5028 liferay       " path="" reloadable="true">


<Resource       name="jdbc/LiferayPool" auth="Container"


               type="javax.sql.DataSource"    />


<ResourceParams name="jdbc/LiferayPool">


           <parameter>


<name>factory</name>


               <value>org.apache.commons.dbcp.BasicDataSourceFactory</value>


</parameter>


XXX       (其他配置,此处略)


     </ResourceParams>


</Context>


有关       Liferay       对连接池的引用,可以去       Portal.properties       配置文件中修改。默认此配置文件已经被打在了       liferay       的       portal-ejb.jar       中。

 



(5)       配置 jaasconf 。讲 jaas.config 文件放置在 Tomcat_Homeconf 目录下。并在 catalina.bat 文件中增加 -Djava.security.auth.login.config=%CATALINA_HOME%/conf/jaas.config 来设置 jaas 的配置 。当然,这首先一定要保证在前面的 Root.xml 的 Context 中配置 JAASRealm 。



//       注意,暂时必须把liferay应用配置成为默认根应用,否则不可以正确运行。       
 <Context docBase="D:       Tomcat5028 liferay       " path=""

<Realm className="org.apache.catalina.realm.JAASRealm"


PortalRealm"


           userClassNames="com.liferay.portal.security.jaas.PortalPrincipal"


          roleClassNames="com.liferay.portal.security.jaas.PortalRole"


          debug="99"


          useContextClassLoader="false" />      


</Context>

              在 jaas.config 的文件中,设置如下内容:



PortalRealm        {


     com.liferay.portal.shared.security.jaas.PortalLoginModule required;


};

是需要被正确配置的,否则系统不可以正确登陆,而且会一直不断的访问/jaas_securit_check,而不能登陆。



 



基本上进行如上操作之后,即可在 Tomcat5.0.28 下单独运行。









Liferay Portal额外研究(二):对Liferay进行瘦身



       Liferay 受其企业版本影响不小,毕竟Liferay公司主要还是“企业行为”,而不是一个单纯的一个“开源团体”行为。所以Liferay Portal本身内嵌了大量的Portlet,而且这些Portlet和Liferay Portal Framework甚至还有些交融。这就是为什么liferay portal-ejb.jar会有3M多。



 



       虽然我们可以通过修改system.properties,portlet.properties,protal-spring这些基础配置文件来修改声明哪些服务不需要加载,也可以通过修改 WEB-INF目录下的portlet.xml,liferay-portlet.xml以及liferay-display.xml这些文件来删除一些不需要的portlet。



 



       但是,我想真正敢真么干的,估计很少。这是为什么呢,就是应为Liferay内部实现的结构和代码,并不是非常良好。而对于这一块的优化,也没有很详细的帮助使用手册。在Liferay的网站上,只有一些教开发人员如何部署在不同服务器和数据库,如何新增一些portlet和theme,以及如何修改权限方面的浅显参考手册。



 



让Liferay变成一个只提供最基本的Portal框架和部署机制的portal context,而去掉那些多余的portlet。—— 因为这些多余的portlet大多国内的项目没有任何利用价值,但是如果需要部署的话,则会大大影响系统响应性能,而且让开发也变得比较麻烦。



 



       当然,是否按照我今天说的方式对Liferay进行瘦身,这全凭习惯。有些朋友不愿改变Liferay的结构,只希望单纯通过配置来加载,也是可以的。



 



(1)保留Liferay最基本的一些组件



      



       Liferay提供了一些基本组件,这些组件不属于Portal框架之内,但是整个portal服务是基于这些组件。



包结构

说明

com.liferay.counter

主要提供主建操作服务,Liferay内部的提供的组织结构表维护,就是采用counter提供的主建自增机制

com.liferay.filters

提供一些基础的基于servlet filter的过滤器

com.liferay.taglib

提供最基本的web展示标签

com.liferay.util

提供最基本的一些公共组件

       以上这四个组件是必须保留的,另外还有两个基本组件:com.liferay.mail(提供邮件服务)和com.liferay.wsrp(提供webservice服务)。这个实际上应该属于可选的,如果觉得需要的话,也可以纳入。



 



(2)保留Liferay的Portal基础服务



       Liferay的基础服务是以com.liferay.portal作为基础的,基本属于此包内的都尽量保留。目前其子package说明如下:



包结构

说明

com.liferay.portal

此包下放置了portal服务相关的一些异常

com.liferay.portal.action

负责一些struts action处理,比如Login等

com.liferay.portal.definitions

此包不是类包,而是负责放置一些定义相关的dtd文件资源。具体需要哪些dtd,可以参考com.liferay.portal.util. EntityResolver类

com.liferay.portal.dependencies

此包也不是类包,而是负责一些依赖性的资源文件。

com.liferay.portal.deploy

负责自动部署和热部署

com.liferay.portal.events

这个包内主要是一些行为处理类

com.liferay.portal.im

即时消息的支持

com.liferay.portal.jcr

提供JSR-170 JCR的支持,并内部提供Jackrabbit的实现支持

com.liferay.portal.job

对一些时间调度性Job的支持

com.liferay.portal.language

对语言的支持包

com.liferay.portal.lucene

对全文检索的支持

com.liferay.portal.model

一些模型对象的集合

com.liferay.portal.security

 

com.liferay.portal.servlet

 

com.liferay.portal.spring

 

com.liferay.portal.struts

 

com.liferay.portal.theme

提供对“主题”,也就是界面风格的支持

com.liferay.portal.tools

 

com.liferay.portal.util

 

com.liferay.portal.velocity

 

com.liferay.portal.wsrp

 

 



       其实我们可以把Liferay这些portal服务再缩简一些,但刚开始建议大家不要随便的删减,保留原始的即可。



 



(3)缩减portlet应用



       Liferay提供了大量的portlet应用,有大概七八十个。正应为这些portlet的加载和部署,让Liferay启动缓慢,消耗系统资源多。实际上,保持Liferay Portal正常运行,只需要其中几个就可以了,剩下的,我们可以根据需要进行删减。



 



       下面列出了一些最基本的portlet,只需要保留如下的这些portlet即可保证liferay的正常启动和运行



包结构

说明

com.liferay.portlet

Liferay Portlet的一些基础类

com.liferay.portlet.admin

管理portlet

com.liferay.portlet.calendar

日期portlet虽然可以不需要,但是其内部提供对job的调度。

com.liferay.portlet.communities

这个是负责配置用户其所在的group,利用可以配置用户所拥有的工作区。

com.liferay.portlet.enterpriseadmin

 

com.liferay.portlet.language

 

com.liferay.portlet.layoutconfiguration

 

com.liferay.portlet.login

 

com.liferay.portlet.myaccount

用于配置用户信息

com.liferay.portlet.myplaces

用于控制用户的工作区选择

com.liferay.portlet.portletconfiguration

 

com.liferay.portlet.themegallery

用于控制主题风格

com.liferay.portlet.translator

这个包必须要,虽然portlet可以不用,但是被portal.language包引入了

com.liferay.portlet.wsrp

这个包必须要。

 



       只需要保证如上的portlet在系统中,即可保证Liferay的正常运行。



 



       但是,不以为只需要简单得讲起他的Porlet删除即可,那就大错特错了。Liferay在这一层面做的非常不友善,内部代码由一点点地耦合。很多地方依靠写if else来判断,所以造成了在portal这个服务包中,竟然存在很多地方引用portlet中的类。



       不过这个倒不难修改,只需要花费一点点时间,简单修改一下就可以了。比较容易,此处就不多说。



 



(4)从配置文件中删除无用portlet的部署



      



       这个主要是修改WEB-INF目录下的portlet.xml,liferay-portlet.xml以及liferay-display.xml这些文件。只需要依次把那些不需要的portlet删除即可。没有什么难度,细心点就可以了。



 



(5)修改服务配置文件



 



       Liferay的配置文件也是很多,放置也是在好几个地方:



位置

配置文件说明

/WEB-INF

portlet描述和struts等配置文件

/WEB-INF/classes

system.properties和portal.properties配置文件

/WEB-INF/classes/META-INF

portal-spring.xml,portal-hbm.xml,portal-log4j.xml配置文件。此位置可调,具体可以修改portal.properties内容。

 



       为了让系统正确运行,我们还需要修改system.properties和portal.properties配置文件,这是Liferay核心配置文件。Liferay的很多服务都是在此配置文件中声明和修改。具体修改哪些本文不细说,改篇专门作个专题讲。



 



       还需要修改portal-spring.xml(如果是professional版,则是portal-spring-professional.xml)。需要将那些已经没有的类bean删除。否则Liferay现在的加载机制,一旦碰到没有的类的bean,则加载不成功了。



       当然portal-hbm.xml也是需要修改的,去掉那些不需要的类即可。



 



       基本上经过以上五个步骤,Liferay即可完成手身了。在真实系统研发过程中,不一定非要选择这样的瘦身方式,毕竟这种方式,对后续Liferay版本的维护带来一定的工作量。—— 但是,如果这么尝试一把,则可以在通过调试过程中,对Liferay的配置体系和代码结构体系有个较为清晰的认识



 



Liferay Portal额外研究(三):IFrame Portlet地session丢失疑难处理




       Liferay提供了一种非常的简单web应用整合和单点登陆的方式:Iframe Portlet。利用Iframe Portlet可以很容易将一个已经存在的web应用纳入,并且支持利用form的post或get方式,实现用户的登陆。



 



       对于Liferay这样的机制没有任何问题,实现的也非常巧妙;但是对于很多web应用系统来说,使用Liferay IFrame Portlet的form方式实现登陆后,虽然可以成功登陆,但是在显示的新页面中,却发现用户信息丢失,或者更准确的说,是session丢失。



 



       其实,这种现象跟Liferay关系不大,而是应用本身决定的。事实上,所有的portal context的iframe 方式,都有可能发生这个情况。



 



       因为很多web应用系统,在执行Login操作的后,习惯性的选择redirect操作,这样会强制浏览器中的显示地址变更为转移的地址。事实上这是个很正确的做法,在正常境况下,不会有任何问题,而且还可以很好的防止页面刷新等所带来的问题。



 



       但是在Liferay的IFrame Portlet中,web应用这样的Redirect操作,造成了调转到新页面后,session变成了一个新的,从而造成放置在原有session中的login user信息丢失。



 



       跟踪并做了如下的一组测试(Liferay和webapp在不同的JVM环境下):



应用

位置

session id

(Liferay)

执行form post前

D03E1B828395EF5BCB1063A8290BD254

(APP_A)

Login操作

397BB3656E2A12A96CE3F16E0A89C607

(APP_A)

登陆后的新页面

58A1054C6EDE4A7D6CFA2FCDBB3E0736

       从上面可以明显看出来,redirect之后,web应用的新页面产生了新的sessionid      



 



       解决这个问题,有两种方式,这两种方式都依赖于被liferay portlet纳入的web应用自身。



       方式一:Login操作后,不采用Redirect方式,而是Dispatcher方式。



       方式二:Login操作后,依然采用redirect方式,但将当前的jsessionid赋予新的页面。



 



Dispatcher方式:



 



ServletContext sc = getServletContext();          
 RequestDispatcher rd = null;          
 rd = sc.getRequestDispatcher("/index.jsp");          
 rd.forward(request, response);

 



Redirect方式(保持同一个session):



      



response.sendRedirect(“         


          index.jsp;jsessionid=397BB3656E2A12A96CE3F16E0A89C607”)

       有一种情况下,无所谓是否采用Redirect方式,这就是在Liferay和webapp在同一个JVM环境下。



 



Liferay Portal额外研究(4):修改用户登录后的默认布局和内容

开发者在线 Builder.com.cn 更新时间:2008-03-19作者:银狐999 来源:CSDN

本文关键词: 用户登录 JAVA



 



    对我们自定的Company来说,这肯定是不合适的,我们需要自己定义用户登录后,默认显示的内容和布局。



 

    后来在Liferay forum中,找到了一篇帖子,给了解决此种问题的解决办法:在portlet-ext.properties中配置一个 default.user.layout.group 属性(自定义的),用于指明默认的layout-group。在ServicePreAction类的扩展修改 addDefaultLayouts 操作,满足特定的需求应用。这种扩展的方式就是,将指定layout-group拷贝给用户第一默认的,这样就不会再使用liferay默认的了。—— 当然这样的方式是比较简单的,可能在系统实施过程中,有比这更加复杂的需求。



 


新建默认的Community



首先,通过新增一个新的Community,命名为User_Default,并新增一个private的page与内容.



 



配置默认的Community



首先在ext项目的 ext-ejb目录下,修改portal-ext.properties文件,增加如下设置:



default.user.layout.group=User_Default

然后通过build,将portal-ext.properties移动到 ext项目的



    extserverstomcatwebappsROOTWEB-INFclasses 目录下。



 



构造LayoutCopy类

public class LayoutCopy ...{ 
        
     
        
    private static Log _log = LogFactory.getLog(LayoutCopy.class); 
        
     
        
    private User user; 
        

    private String ownerId; 
        

   private Group userGroup; 
        
   public LayoutCopy(User user) ...{ 
        
       super(); 
        

       this.user = user; 
        

        this.ownerId = getOwnerId(user.getGroup().getGroupId(), false); 
        
       this.userGroup = user.getGroup(); 
        
   } 
        

   public void copyDefaultUserLayout(HttpServletRequest httpReq) 
        
           throws SystemException, PortalException ...{ 
        
      // set in the portal-ext.properties: 
        
       // default.user.layout.group=Default User 
        
       // and create then group/community "Default User"  
        
       // CWPPropsUtil.DEFUALT_USER_LAYOUT_GROUP = "default.user.layout.group" 
        
       Group group = GroupLocalServiceUtil.getGroup(user.getCompanyId(), 
        
               PropsUtil.get("default.user.layout.group")); 
        

       try ...{ 
        
           String groupOwnerId = getOwnerId(group.getGroupId(), true); 
        
           List privateLayouts = LayoutLocalServiceUtil 
        
                   .getLayouts(groupOwnerId); 
        

           for (Iterator itr = privateLayouts.iterator(); itr.hasNext();) ...{ 
        
               Layout layout = (Layout) itr.next(); 
        
               Layout newLayout = copyLayout(layout); 
        
               copyPreferences(httpReq, newLayout, layout); 
        
           } 
        
       } catch (PortalException e) ...{ 
        
           _log.error("Cannot copy private layouts", e); 
        
       } catch (Exception e) ...{ 
        
           _log.error("Cannot copy public layouts", e); 
        
       } 
        

       try ...{ 
        
           String groupOwnerId = getOwnerId(group.getGroupId(), false); 
        
           List publicLayouts = LayoutLocalServiceUtil 
        
                   .getLayouts(groupOwnerId); 
        

           for (Iterator itr = publicLayouts.iterator(); itr.hasNext();) ...{ 
        
               Layout layout = (Layout) itr.next(); 
        
               Layout newLayout = copyLayout(layout); 
        
               copyPreferences(httpReq, newLayout, layout); 
        
           } 
        
       } catch (PortalException e) ...{ 
        
           _log.error("Cannot copy public layouts", e); 
        
       } catch (Exception e) ...{ 
        
           _log.error("Cannot copy public layouts", e); 
        
       } 
        

   } 
        

   public void resetLayout(HttpServletRequest httpReq) throws SystemException, PortalException ...{ 
        
       String ownerId = getOwnerId(user.getGroup().getGroupId(), false); 
        
       LayoutLocalServiceUtil.deleteLayouts(ownerId); 
        
       PortletPreferencesLocalServiceUtil.deletePortletPreferences(ownerId); 
        

       ownerId = getOwnerId(user.getGroup().getGroupId(), true); 
        
       LayoutLocalServiceUtil.deleteLayouts(ownerId); 
        
       PortletPreferencesLocalServiceUtil.deletePortletPreferences(ownerId); 
        

       copyDefaultUserLayout(httpReq); 
        
   } 
        

   public Layout copyLayout(Layout groupDefaultLayout) throws SystemException, 
        
           PortalException ...{ 
        
       Layout layout = LayoutLocalServiceUtil.addLayout( 
        
               userGroup.getGroupId(), user.getUserId(), groupDefaultLayout 
        
                      .isPrivateLayout(), groupDefaultLayout 
        
                       .getParentLayoutId(), groupDefaultLayout.getName(user 
        
                       .getLocale()), groupDefaultLayout.getType(), 
        
               groupDefaultLayout.isHidden(), null); 
        

       LayoutLocalServiceUtil.updateLayout(layout.getLayoutId(), layout 
        
        .getOwnerId(), groupDefaultLayout.getTypeSettings()); 
        
       layout = LayoutLocalServiceUtil.updateLookAndFeel(layout.getLayoutId(), 
        
               layout.getOwnerId(), groupDefaultLayout.getThemeId(), 
        
               groupDefaultLayout.getColorSchemeId()); 
        

       // layoutMapping.put(groupDefaultLayout.getPrimaryKey(), 
        
       // layout.getPrimaryKey()); 
        

       return layout; 
        
   } 
        

   protected void copyPreferences(HttpServletRequest httpReq, Layout layout, 
        
           Layout copyLayout) throws Exception ...{ 
        

       String companyId = layout.getCompanyId(); 
        

       LayoutTypePortlet copyLayoutTypePortlet = (LayoutTypePortlet) copyLayout 
        
               .getLayoutType(); 
        

       List copyPortletIds = copyLayoutTypePortlet.getPortletIds(); 
        

       for (int i = 0; i < copyPortletIds.size(); i++) ...{ 
        
           String copyPortletId = (String) copyPortletIds.get(i); 
        

           // Copy preference 
        

           PortletPreferencesPK prefsPK = PortletPreferencesFactory 
        
                   .getPortletPreferencesPK(httpReq, copyPortletId, layout 
        
                           .getPlid()); 
        

           PortletPreferencesLocalServiceUtil.getPreferences(companyId, 
        
                   prefsPK); 
        

           PortletPreferencesPK copyPrefsPK = PortletPreferencesFactory 
        
                   .getPortletPreferencesPK(httpReq, copyPortletId, copyLayout 
        
                           .getPlid()); 
        

           PortletPreferencesImpl copyPrefsImpl = (PortletPreferencesImpl) PortletPreferencesLocalServiceUtil 
        
                   .getPreferences(companyId, copyPrefsPK); 
        

           PortletPreferencesLocalServiceUtil.updatePreferences(prefsPK, 
        
                   copyPrefsImpl); 
        

           // Copy portlet setup 
        

           prefsPK = new PortletPreferencesPK(copyPortletId, layout 
        
                   .getLayoutId(), layout.getOwnerId()); 
        

           PortletPreferencesLocalServiceUtil.getPreferences(companyId, 
        
                   prefsPK); 
        

           copyPrefsPK = new PortletPreferencesPK(copyPortletId, copyLayout 
        
                   .getLayoutId(), copyLayout.getOwnerId()); 
        

           copyPrefsImpl = (PortletPreferencesImpl) PortletPreferencesLocalServiceUtil 
        
                   .getPreferences(companyId, copyPrefsPK); 
        

           PortletPreferencesLocalServiceUtil.updatePreferences(prefsPK, 
        
                   copyPrefsImpl); 
        
       } 
        
   } 
        

   private String getOwnerId(String groupId, boolean privateLayout) ...{ 
        
       if (privateLayout) ...{ 
        
           return Layout.PRIVATE + groupId; 
        
       } else ...{ 
        
           return Layout.PUBLIC + groupId; 
        
       } 
        
   } 
        

}

修改com.liferay.portal.events.ServicePreAction类


 


    是在 ext工程的 ext-ejb/src下修改,新建com.liferay.portal.events包,并把原始的ServicePreAction类拷贝至此,然后修改,修改后通过ext-ejb下的build.xml编译部署。


 


在ServicePreAction类中新增一个addDefaultLayouts方法:


protected void addDefaultLayouts(HttpServletRequest httpReq, User user)          

                           throws PortalException, SystemException {          

                       if (user.hasPrivateLayouts()) {          

                           return;          

                       }          

                      (new LayoutCopy(user)).copyDefaultUserLayout(httpReq);          

           }

 


然后,修改run方法中对原始addDefaultLayouts方法的调用方法:


if (layoutsRequired) {           

               String user_layout_group = PropsUtil.get("default.user.layout.group");          

               if(user_layout_group==null || user_layout_group.length()==0){          

                       addDefaultLayouts(user);          

               }else{          

                       addDefaultLayouts(req, user);          

               }          

           }


 


Liferay Portal额外研究(5):对多分发命令Action的支持(方案一)


作者:胡长城(银狐999)

     Liferay默认提供的基于 Struts Action扩展的PortletAction是不支持多分发命令的,也就是我们一般常用的DispatchAction。但在我们日常基于 Struts处理的操作中,已经大量的沿用了DispatchAction处理方式,采用“cmd=queryall”诸如此类的方式。


    本文就来给大家讲解如何通过扩展,让 Liferay实现对多分发命令Action的支持。


 


    首先让我们来看看 Liferay是如何处理的:


      在 portlet.XML中,我们一般会配置如下:


<          portlet-class 
         > 
         com.liferay.portlet.StrutsPortlet 
         </ 
         portlet-class 
         > 
         
          < 
         init-param 
         > 
         
              < 
         name 
         > 
         view-action 
         </ 
         name 
         > 
         
              < 
         value 
         > 
         /ext/reports/view_reports 
         </ 
         value 
         > 
         
          </ 
         init-param 
         >


       这样 Liferay面对一个Portlet请求的时候,会根据请求model来执行Portlet的doView或doEdit方式。当执行doView的时候就会请求其view-action所配置的Action URL所代表的Action来处理。


      其处理流程大致是: Portlet类——〉RequestProcessor——〉StrutsAction处理类
 

     我们可以通过两种扩展方案来实现对多分发的支持:


      方案(一):扩展 Liferay的StrutsPortlet类,并写一个DispatchPortletAction类,这样不用扩展RequestProcessor实现。


     方案(二):扩展 RequestProcessor与,并写一个DispatchPortletAction类,这样可以直接使用Liferay所提供的StrutsPortlet类。对于RequestProcessor的扩展,在通过portal.properties文件中通过配置“struts.portlet.request.processor”属性来设置。


 


    接下来就两种方案做分别的详细讲解(本篇先讲方案一):
 

方案(一)


 


首先让我们写一个 DispatchPortletAction类,此类可以通过扩展Liferay本身的PortletAction实现,也可以通过扩展Struts本身的DispatchAction实现。本人是选择后一种方式,这样扩展的代码量较少,都不要自己写execute方式,直接使用基类的即可。


对于 DispatchPortletAction主要扩展dispatchMethod和getMethodName方法。注意在getMechodName方法中,还追加了从request.getAttribute(parameter)获取方法名称,并注意unspecified方法,这个是在没有指明访问方法的时候默认执行的,所以开发人员在后续写自己的Action一定要实现这个。

public                           class 
             DispatchPortletAction  
            extends 
             DispatchAction  
            ... 
            {
    protected ActionForward dispatchMethod(ActionMapping mapping,
            ActionForm form, HttpServletRequest request,
            HttpServletResponse response, String name) throws Exception ...{

        PortletConfig portletConfig = (PortletConfig) request
                .getAttribute(WebKeys.JavaX_PORTLET_CONFIG);

        RenderRequest renderRequest = (RenderRequest) request
                .getAttribute(WebKeys.JAVAX_PORTLET_REQUEST);

        RenderResponse renderResponse = (RenderResponse) request
                .getAttribute(WebKeys.JAVAX_PORTLET_RESPONSE);

        if (name == null) ...{
            return this.unspecified(mapping, form, portletConfig,
                    renderRequest, renderResponse);
        }

        Method method = null;
        try ...{
            method = getMethod(name);

        } catch (NoSuchMethodException e) ...{
            String message = messages.getMessage("dispatch.method",
                    mapping.getPath(), name);
            log.error(message, e);

            String userMsg = messages.getMessage("dispatch.method.user",
                    mapping.getPath());
            throw new NoSuchMethodException(userMsg);
        }

        ActionForward forward = null;
        try ...{
            Object args[] = ...{ mapping, form, portletConfig, renderRequest,
                    renderResponse };
            forward = (ActionForward) method.invoke(this, args);

        } catch (ClassCastException e) ...{
            String message = messages.getMessage("dispatch.return",
                    mapping.getPath(), name);
            log.error(message, e);
            throw e;

        } catch (IllegalAccessException e) ...{
            String message = messages.getMessage("dispatch.error",
                    mapping.getPath(), name);
            log.error(message, e);
            throw e;

        } catch (InvocationTargetException e) ...{
            Throwable t = e.getTargetException();
            if (t instanceof Exception) ...{
                throw ((Exception) t);
            } else ...{
                String message = messages.getMessage("dispatch.error",
                        mapping.getPath(), name);
                log.error(message, e);
                throw new ServletException(t);
            }
        }
        return (forward);
    }

    protected String getMethodName(ActionMapping mapping, ActionForm form,
            HttpServletRequest request, HttpServletResponse response,
            String parameter) throws Exception ...{

        String methodName = request.getParameter(parameter);
        if (methodName == null || methodName.length() == 0) ...{
             methodName = (String) request.getAttribute(parameter);
        }
        return methodName;
    }

    public ActionForward unspecified(ActionMapping mapping, ActionForm form,
            PortletConfig config, RenderRequest req, RenderResponse res)
            throws Exception ...{
        return null;
    }
    private static Log log = LogFactory.getLog(DispatchPortletAction.class);
    protected Class[] types = ...{ ActionMapping.class, ActionForm.class,
            PortletConfig.class, RenderRequest.class, RenderResponse.class };
}             
           
            
         这样后续多分发            Action在书写的时候,只需要定义不同的方法即可,但是方法的参数需要依照如下规范,如下一个queryAll的方法:public               ActionForward queryAll(ActionMapping mapping, ActionForm form,
             PortletConfig config, RenderRequest req, RenderResponse res)
                           throws               Exception {
                   //              业务处理
              //              返回ActionForward即可              
              }

 

         在那些 portlet配置文件的view-action属性中,是不能够增加参数的,比如你不能够采用 /ext/reports/view_reports?cmd=queryAll这种方式。所以我们需要在扩展的Portlet中做一些拦截。

可能有人会说,我不需要在初始的 view-action中增加参数。事实上这个的确不是强制,如果不追加参数,则会访问unspecified方法。但是对于Portlet的显示,其Normal和Max页面显示,都会请求默认的view-action。所以我们需要在Portlet类实现上扩展,于是扩展了一个DispachStrutsPortlet,如下:

public                               class                DispachStrutsPortlet  
              extends 
               StrutsPortlet {

                       public                               void 
               doView(RenderRequest req, RenderResponse res) 
                                                                                   throws                IOException, PortletException {
                                //               注意我的命令参数是cmdx,而不是通常的cmd。               
                               String cmd                =                req.getParameter( 
              " 
              cmdx 
              " 
              );
                                if               (cmd               == 
              null 
                
              || 
               cmd.length() 
              == 
              0 
              ){
                                        if                (req.getWindowstate().equals(Windowstate. MAXIMIZED)) {
                         req.setAttribute(               "               cmdx               " 
              , 
              " 
              queryAll 
              " 
              );
                 }
                                super               .doView(req, res);
         }
 }

 

如上面的实现,则表示,如果 Portlet是Normal页面状态请求的时候,则在view-action的时候,则仅访问默认的unspecified方法;如果是Max页面状态,则执行queryAll方法。


 


有一个需要注意的地方,由于“ cmd”参数已经被Liferay使用,所以我们需要用另外的变量来表示方法。这里我采用的是“cmdx”。


 


Liferay Portal额外研究(六):Portlet附件上传处理的点滴


首先更正上一篇有关多分发Action处理的一些严重Bug。


 


更正一:在上篇中,我比较倾向于直接基于Struts DispatchAction扩展,这是错误的,应该尽量基于PortletAction,虽然这样会让扩展增加一些Struts DispatchAction本已有的代码,但是这才是符合Portlet Action处理思想的。并且在Liferay的内部处理中,对于actionurl处理的情况下,是强制必须基于PortletAction扩展的。


更正二:在上篇中,多分发处理的样例代码只侧重对Render的多分发,其实这是不完善的,也容易造成误导。在Porttal的Portlet请求处理,是分为:Action和Render两个阶段的。其实真正的多业务处理更应该在Action阶段来完成,Render则主要负责对push位置和页面数据进行渲染。


 


在Liferay中开发附件上传处理应用,要比在单纯Servlet和Struts Action中开发简单很多。但是需要注意的是,必须在Action阶段才能处理附件File对象,而在Render阶段已经不能处理了。


 


让我们首先来看一下 com.liferay.portal.servlet.MainServlet 类,此类是所有“/c”请求的转移控制类。在此类中,liferay对含有文件上传的请求做了拦截和封装,利用UploadServletRequest 类来包装原始的http request请求

if ((contentType != null) &&           
            (contentType.startsWith(           "multipart/form-data"           ))) {
             req = new            UploadServletRequest(req);
 }

        在UploadServletRequest类中,则将所有当前请求参数进行了提取,并对上传的文件做了临时存储。实际上内部是采用Apache Commons Upload进行的封装。
        这样在PortletAction的procesAction处理中,可以对上传附件进行业务处理和存储。       当然,你可以通过ActionRequest来强制自己获取原始的UploadServletRequest对象,可以如下操作:

 

public class NewFileUploadAction extends           PortletAction {
     public void           processAction(
             ActionMapping mapping, ActionForm form, PortletConfig config,
             ActionRequest req, ActionResponse res)
         throws           Exception {
         
          //注意这里面是获取UploadPortletRequest对象          
                  UploadPortletRequest urequest =           PortalUtil.getUploadPortletRequest(req);
          File f = urequest.getFile("file1"          );
 }        
 
 
 
        
//req是ActionRequest对象          
          ActionRequestImpl reqImpl =           (ActionRequestImpl)req;
 HttpServletRequest httpReq =           reqImpl.getHttpServletRequest();
 UploadServletRequest urequest = (UploadServletRequest)httpReq;


 

      置于在Action处理阶段,获取到UploadServletRequest对象后存储附件、过滤附件类型等操作就很容易实现了,此处不再累赘叙述。
      不过在附件上传中都会涉及到一个问题:控制附件上传的大小和类型。对于类型的控制,liferay没有提供任何控制和扩展实现机制,这个只能开发人员在外围自己实现。


    对于控制附件上传大小,则只能通过在system_ext.properties配置“com.liferay.util.servlet.UploadServletRequest.max.size”属性来统一设置。


    从这两个方面来讲,liferay对附件的处理不够灵活,在开发过程中,还需要做一定封装来辅助。


 


    稍微在这里补充一点,开发人员可以通过在页面请求url中,根据参数来决定是否执行action阶段。


renderResponse的createActionURL和createReanderURL来决定提交请求是否执行Action阶段,但注意,都会执行reader 阶段的。


 

<%          
PortletURL portletURL           =           renderResponse.createActionURL();
 portletURL.setWindowState(WindowState.MAXIMIZED);
 portletURL.setParameter("struts_action", "/venus/template/fileupload/FileUpoadAction"          );
 portletURL.setParameter("cmdx", "uploadfile"          );
 portletURL.setParameter("cmd", "add"          );
%>          

          <form name="testForm" method="post"           
 action="<%= portletURL.toString() %>"           
 enctype="multipart/form-data">          
          </form>

 

Liferay Portal额外研究(7):修改用户登录首页布局之方案二


 作者:胡长城 

Liferay Portal额外研究(7):修改用户登录首页布局之方案二


在第四篇《Liferay Portal额外研究(4):修改用户登录后的默认布局和内容》中,我讲了修改用户登录首页布局的方案,此篇再给提供一种更简洁的处理方案。

        在第四篇的方案,其实有些违背Liferay的扩展本意,Liferay提供了在外围配置的方式,来统一设置用户的第一次默认登陆首页。
        在portal_ext.properties中通过修改相应参数来控制:

# Set the layout name. 
          
 default.user.layout.name=Home           

 # Set the layout template id that matches an existing TPL.           
 default.user.layout.template.id=2_columns_ii           

 # Set the layout ids for the column specified in the layout template.           
 default.user.layout.column-1=82,23,61,65,           
 default.user.layout.column-2=8,11,36,33,           
 default.user.layout.column-3=           
 default.user.layout.column-4=           

 # Enter either "800x600" or "1024x768" to set the layout resolution.           
 default.user.layout.resolution=800x600

    可以通过修改“default.user.layout.template.id”属性来控制页面的布局格式;通过修改“default.user.layout.column-X”的属性来控制不同列防止的portlet。

   但如果为了更加符合一些商业性的需求,比如同一个Location的下的user默认布局相同,等等,这些则需要重载ServicePreAction类,重载 addDefaultLayouts 方法来实现,然后再在portal_ext.properties修改相应属性即可。


servlet.service.events.pre=com.liferay.portal.events.ServicePreAction


 


       Liferay提供了一种非常的简单web应用整合和单点登陆的方式:Iframe Portlet。利用Iframe Portlet可以很容易将一个已经存在的web应用纳入,并且支持利用form的post或get方式,实现用户的登陆。


 


       对于Liferay这样的机制没有任何问题,实现的也非常巧妙;但是对于很多web应用系统来说,使用Liferay IFrame Portlet的form方式实现登陆后,虽然可以成功登陆,但是在显示的新页面中,却发现用户信息丢失,或者更准确的说,是session丢失。


 


       其实,这种现象跟Liferay关系不大,而是应用本身决定的。事实上,所有的portal context的iframe 方式,都有可能发生这个情况。


 


       因为很多web应用系统,在执行Login操作的后,习惯性的选择redirect操作,这样会强制浏览器中的显示地址变更为转移的地址。事实上这是个很正确的做法,在正常境况下,不会有任何问题,而且还可以很好的防止页面刷新等所带来的问题。


 


       但是在Liferay的IFrame Portlet中,web应用这样的Redirect操作,造成了调转到新页面后,session变成了一个新的,从而造成放置在原有session中的login user信息丢失。


 


       跟踪并做了如下的一组测试(Liferay和webapp在不同的JVM环境下):


应用

位置

session id

(Liferay)

执行form post前

D03E1B828395EF5BCB1063A8290BD254

(APP_A)

Login操作

397BB3656E2A12A96CE3F16E0A89C607

(APP_A)

登陆后的新页面

58A1054C6EDE4A7D6CFA2FCDBB3E0736

       从上面可以明显看出来,redirect之后,web应用的新页面产生了新的sessionid      


 


       解决这个问题,有两种方式,这两种方式都依赖于被liferay portlet纳入的web应用自身。


       方式一:Login操作后,不采用Redirect方式,而是Dispatcher方式。


       方式二:Login操作后,依然采用redirect方式,但将当前的jsessionid赋予新的页面。


 


Dispatcher方式:


 


ServletContext sc = getServletContext();          
 RequestDispatcher rd = null;          
 rd = sc.getRequestDispatcher("/index.jsp");          
 rd.forward(request, response);

 


Redirect方式(保持同一个session):


      


response.sendRedirect(“         

          index.jsp;jsessionid=397BB3656E2A12A96CE3F16E0A89C607”)

       有一种情况下,无所谓是否采用Redirect方式,这就是在Liferay和webapp在同一个JVM环境下。