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' ”异常 。
|
(5) 配置 jaasconf 。讲 jaas.config 文件放置在 Tomcat_Homeconf 目录下。并在 catalina.bat 文件中增加 -Djava.security.auth.login.config=%CATALINA_HOME%/conf/jaas.config 来设置 jaas 的配置 。当然,这首先一定要保证在前面的 Root.xml 的 Context 中配置 JAASRealm 。
|
在 jaas.config 的文件中,设置如下内容:
|
是需要被正确配置的,否则系统不可以正确登陆,而且会一直不断的访问/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方式:
|
Redirect方式(保持同一个session):
|
有一种情况下,无所谓是否采用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文件,增加如下设置:
|
然后通过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方法:
|
然后,修改run方法中对原始addDefaultLayouts方法的调用方法:
|
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方式:
|
Redirect方式(保持同一个session):
|
有一种情况下,无所谓是否采用Redirect方式,这就是在Liferay和webapp在同一个JVM环境下。