经过同事的帮忙,加上4天的努力,这个问题现在终于水落石出了。
为什么这问题只在Liferay Enterprise Edition发生而在Community Edition上不发生?
因为,在访问这个Portlet之前会走一系列的拦截器:
对于Community Edition,拦截器的调用顺序为:
而对于Enterprise Edition,拦截器调用顺序为:
所以这里可以看出来,在执行完最后一个拦截器之后,在CE版本,成功的把请求发给了portlet,而在EE版本,请求没有发送给portlet.
我们断点跟进,终于发现了这2个的区别:
在CE版本,当最终调用到LayoutAction类的processPortletRequest方法时,其代码如下(省略很多不重要的代码):
- protected Portlet processPortletRequest(HttpServletRequest request, HttpServletResponse response, String lifecycle)
- throws Exception
- {
- HttpSession session = request.getSession();
- ....
- if (lifecycle.equals("RESOURCE_PHASE")) {
- PortletDisplay portletDisplay = themeDisplay.getPortletDisplay();
- String portletPrimaryKey = PortletPermissionUtil.getPrimaryKey(
- layout.getPlid(), portletId);
- portletDisplay.setId(portletId);
- portletDisplay.setRootPortletId(portlet.getRootPortletId());
- portletDisplay.setInstanceId(portlet.getInstanceId());
- portletDisplay.setResourcePK(portletPrimaryKey);
- portletDisplay.setPortletName(portletConfig.getPortletName());
- portletDisplay.setNamespace(
- PortalUtil.getPortletNamespace(portletId));
- WebDAVStorage webDAVStorage = portlet.getWebDAVStorageInstance();
- if (webDAVStorage != null) {
- portletDisplay.setWebDAVEnabled(true);
- }
- else {
- portletDisplay.setWebDAVEnabled(false);
- }
- ResourceRequestImpl resourceRequestImpl =
- ResourceRequestFactory.create(
- request, portlet, invokerPortlet, portletContext,
- windowState, portletMode, portletPreferences,
- layout.getPlid());
- ResourceResponseImpl resourceResponseImpl =
- ResourceResponseFactory.create(
- resourceRequestImpl, response, portletId, companyId);
- resourceRequestImpl.defineObjects(
- portletConfig, resourceResponseImpl);
- try
- {
- ServiceContext serviceContext =
- ServiceContextFactory.getInstance(resourceRequestImpl);
- ServiceContextThreadLocal.pushServiceContext(serviceContext);
- invokerPortlet.serveResource(
- resourceRequestImpl, resourceResponseImpl);
- }
- finally {
- ServiceContextThreadLocal.popServiceContext();
- }
- }
- return portlet;
- }
而在Enterprise Edition版本,同类名的方法如下:
- protected Portlet processPortletRequest(HttpServletRequest request, HttpServletResponse response, String lifecycle)
- throws Exception
- {
- HttpSession session = request.getSession();
- ....
- if (lifecycle.equals("RESOURCE_PHASE")) {
- PortletDisplay portletDisplay = themeDisplay.getPortletDisplay();
- String portletPrimaryKey = PortletPermissionUtil.getPrimaryKey(
- layout.getPlid(), portletId);
- portletDisplay.setId(portletId);
- portletDisplay.setRootPortletId(portlet.getRootPortletId());
- portletDisplay.setInstanceId(portlet.getInstanceId());
- portletDisplay.setResourcePK(portletPrimaryKey);
- portletDisplay.setPortletName(portletConfig.getPortletName());
- portletDisplay.setNamespace(
- PortalUtil.getPortletNamespace(portletId));
- WebDAVStorage webDAVStorage = portlet.getWebDAVStorageInstance();
- if (webDAVStorage != null) {
- portletDisplay.setWebDAVEnabled(true);
- }
- else {
- portletDisplay.setWebDAVEnabled(false);
- }
- ResourceRequestImpl resourceRequestImpl =
- ResourceRequestFactory.create(
- request, portlet, invokerPortlet, portletContext,
- windowState, portletMode, portletPreferences,
- layout.getPlid());
- ResourceResponseImpl resourceResponseImpl =
- ResourceResponseFactory.create(
- resourceRequestImpl, response, portletId, companyId);
- resourceRequestImpl.defineObjects(
- portletConfig, resourceResponseImpl);
- try
- {
- ServiceContext serviceContext =
- ServiceContextFactory.getInstance(resourceRequestImpl);
- ServiceContextThreadLocal.pushServiceContext(serviceContext);
- boolean access = PortletPermissionUtil.hasAccessPermission(
- permissionChecker, scopeGroupId, layout, portlet,
- portletMode);
- if (!access) break label947;
- label947: invokerPortlet.serveResource(
- resourceRequestImpl, resourceResponseImpl);
- }
- finally
- {
- ServiceContextThreadLocal.popServiceContext();
- }
- }
- return portlet;
- }
所以,我们对比CE的50-51行和EE的第50-57行可以发现:在CE版本,它总是把请求交给serveResource()方法,而EE版本,则会有一个boolean变量access,要这个变量的值为true时才会吧请求交给serveResource()方法,这就是为什么在CE版本下,任何用户,包括未登录用户都可以访问最终资源,而在EE版本,只有注册用户才可以访问。因为Guest用户在执行这段代码时候access布尔值总为false.
解决方案:
那么我们如何在EE版本中解决这个问题呢?
很简单,我们只要设法让这个access值永远为true就可以确保在EE版本上,就算是Guest用户也能访问这个资源(JSON资源)
为此,我们继续看这段access值的获取:
- boolean access = PortletPermissionUtil.hasAccessPermission(
- permissionChecker, scopeGroupId, layout, portlet,
- portletMode);
它会去调用下面的代码:
- public boolean hasAccessPermission(PermissionChecker permissionChecker, long scopeGroupId, Layout layout, Portlet portlet, PortletMode portletMode)
- throws PortalException, SystemException
- {
- if ((layout != null) && (layout.isTypeControlPanel())) {
- String category = portlet.getControlPanelEntryCategory();
- if (Validator.equals(category, "content")) {
- layout = null;
- }
- }
- boolean access = contains(
- permissionChecker, scopeGroupId, layout, portlet, "VIEW");
- if ((access) && (!PropsValues.TCK_URL) &&
- (portletMode.equals(PortletMode.EDIT)))
- {
- access = contains(
- permissionChecker, scopeGroupId, layout, portlet,
- "PREFERENCES");
- }
- return access;
- }
而这段代码中的又多了一个布尔字段叫access,它是通过以下代码获得的:
- public boolean contains(PermissionChecker permissionChecker, long groupId, Layout layout, Portlet portlet, String actionId, boolean strict)
- throws PortalException, SystemException
- {
- if (portlet.isUndeployedPortlet()) {
- return false;
- }
- if ((portlet.isSystem()) && (actionId.equals("VIEW"))) {
- return true;
- }
- return contains(
- permissionChecker, groupId, layout, portlet.getPortletId(),
- actionId, strict);
- }
从08行我们就清楚了,只要我们吧这个portlet设为isSystem()返回true就行了,那么我们如何做到这点呢?
继续跟踪我们可以看到这个isSystem()实际是由下面这段代码设置的:
(在PortletLocalServiceImpl的_readLiferayPortletXML方法中)见第06-08行:
- private void _readLiferayPortletXML(
- String servletContextName, Map<String, Portlet> portletsPool,
- Set<String> liferayPortletIds, Map<String, String> roleMappers,
- Element portletElement) {
- ..
- portletModel.setSystem(
- GetterUtil.getBoolean(
- portletElement.elementText("system"), portletModel.isSystem()));
- portletModel.setActive(
- GetterUtil.getBoolean(
- portletElement.elementText("active"), portletModel.isActive()));
- portletModel.setInclude(
- GetterUtil.getBoolean(portletElement.elementText("include"),
- portletModel.isInclude()));
- }
因为这段代码是读取portlet的liferay-portlet.xml配置文件的,所以我们需要做的只是在portlet中加一个元素:
- <system>true</system>
就可以了。
经过测试,当我们为portlet添加了这个元素之后,果然在Enterprise Edition上,这个portlet的resourceURL可以被正确的访问。