经过同事的帮忙,加上4天的努力,这个问题现在终于水落石出了。

 

为什么这问题只在Liferay Enterprise Edition发生而在Community Edition上不发生?

因为,在访问这个Portlet之前会走一系列的拦截器:

对于Community Edition,拦截器的调用顺序为:

 

而对于Enterprise Edition,拦截器调用顺序为:

 

所以这里可以看出来,在执行完最后一个拦截器之后,在CE版本,成功的把请求发给了portlet,而在EE版本,请求没有发送给portlet.

 

我们断点跟进,终于发现了这2个的区别:

在CE版本,当最终调用到LayoutAction类的processPortletRequest方法时,其代码如下(省略很多不重要的代码):

 
  
  1. protected Portlet processPortletRequest(HttpServletRequest request, HttpServletResponse response, String lifecycle) 
  2.     throws Exception 
  3.   { 
  4.     HttpSession session = request.getSession(); 
  5.  
  6.     .... 
  7.  
  8.     if (lifecycle.equals("RESOURCE_PHASE")) { 
  9.       PortletDisplay portletDisplay = themeDisplay.getPortletDisplay(); 
  10.  
  11.       String portletPrimaryKey = PortletPermissionUtil.getPrimaryKey( 
  12.         layout.getPlid(), portletId); 
  13.  
  14.       portletDisplay.setId(portletId); 
  15.       portletDisplay.setRootPortletId(portlet.getRootPortletId()); 
  16.       portletDisplay.setInstanceId(portlet.getInstanceId()); 
  17.       portletDisplay.setResourcePK(portletPrimaryKey); 
  18.       portletDisplay.setPortletName(portletConfig.getPortletName()); 
  19.       portletDisplay.setNamespace( 
  20.         PortalUtil.getPortletNamespace(portletId)); 
  21.  
  22.       WebDAVStorage webDAVStorage = portlet.getWebDAVStorageInstance(); 
  23.  
  24.       if (webDAVStorage != null) { 
  25.         portletDisplay.setWebDAVEnabled(true); 
  26.       } 
  27.       else { 
  28.         portletDisplay.setWebDAVEnabled(false); 
  29.       } 
  30.  
  31.       ResourceRequestImpl resourceRequestImpl =  
  32.         ResourceRequestFactory.create( 
  33.         request, portlet, invokerPortlet, portletContext,  
  34.         windowState, portletMode, portletPreferences,  
  35.         layout.getPlid()); 
  36.  
  37.       ResourceResponseImpl resourceResponseImpl =  
  38.         ResourceResponseFactory.create( 
  39.         resourceRequestImpl, response, portletId, companyId); 
  40.  
  41.       resourceRequestImpl.defineObjects( 
  42.         portletConfig, resourceResponseImpl); 
  43.       try 
  44.       { 
  45.         ServiceContext serviceContext =  
  46.           ServiceContextFactory.getInstance(resourceRequestImpl); 
  47.  
  48.         ServiceContextThreadLocal.pushServiceContext(serviceContext); 
  49.  
  50.         invokerPortlet.serveResource( 
  51.           resourceRequestImpl, resourceResponseImpl); 
  52.       } 
  53.       finally { 
  54.         ServiceContextThreadLocal.popServiceContext(); 
  55.       } 
  56.     } 
  57.  
  58.     return portlet; 
  59.   } 

 

而在Enterprise Edition版本,同类名的方法如下:

 
  
  1. protected Portlet processPortletRequest(HttpServletRequest request, HttpServletResponse response, String lifecycle) 
  2.     throws Exception 
  3.   { 
  4.     HttpSession session = request.getSession(); 
  5.  
  6.     .... 
  7.  
  8.     if (lifecycle.equals("RESOURCE_PHASE")) { 
  9.       PortletDisplay portletDisplay = themeDisplay.getPortletDisplay(); 
  10.  
  11.       String portletPrimaryKey = PortletPermissionUtil.getPrimaryKey( 
  12.         layout.getPlid(), portletId); 
  13.  
  14.       portletDisplay.setId(portletId); 
  15.       portletDisplay.setRootPortletId(portlet.getRootPortletId()); 
  16.       portletDisplay.setInstanceId(portlet.getInstanceId()); 
  17.       portletDisplay.setResourcePK(portletPrimaryKey); 
  18.       portletDisplay.setPortletName(portletConfig.getPortletName()); 
  19.       portletDisplay.setNamespace( 
  20.         PortalUtil.getPortletNamespace(portletId)); 
  21.  
  22.       WebDAVStorage webDAVStorage = portlet.getWebDAVStorageInstance(); 
  23.  
  24.       if (webDAVStorage != null) { 
  25.         portletDisplay.setWebDAVEnabled(true); 
  26.       } 
  27.       else { 
  28.         portletDisplay.setWebDAVEnabled(false); 
  29.       } 
  30.  
  31.       ResourceRequestImpl resourceRequestImpl =  
  32.         ResourceRequestFactory.create( 
  33.         request, portlet, invokerPortlet, portletContext,  
  34.         windowState, portletMode, portletPreferences,  
  35.         layout.getPlid()); 
  36.  
  37.       ResourceResponseImpl resourceResponseImpl =  
  38.         ResourceResponseFactory.create( 
  39.         resourceRequestImpl, response, portletId, companyId); 
  40.  
  41.       resourceRequestImpl.defineObjects( 
  42.         portletConfig, resourceResponseImpl); 
  43.       try 
  44.       { 
  45.         ServiceContext serviceContext =  
  46.           ServiceContextFactory.getInstance(resourceRequestImpl); 
  47.  
  48.         ServiceContextThreadLocal.pushServiceContext(serviceContext); 
  49.  
  50.         boolean access = PortletPermissionUtil.hasAccessPermission( 
  51.           permissionChecker, scopeGroupId, layout, portlet,  
  52.           portletMode); 
  53.  
  54.         if (!access) break label947; 
  55.         label947: invokerPortlet.serveResource( 
  56.           resourceRequestImpl, resourceResponseImpl); 
  57.       } 
  58.       finally 
  59.       { 
  60.         ServiceContextThreadLocal.popServiceContext(); 
  61.       } 
  62.     } 
  63.  
  64.     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值的获取:

 
  
  1. boolean access = PortletPermissionUtil.hasAccessPermission( 
  2.           permissionChecker, scopeGroupId, layout, portlet,  
  3.           portletMode); 

 

它会去调用下面的代码:

 
  
  1. public boolean hasAccessPermission(PermissionChecker permissionChecker, long scopeGroupId, Layout layout, Portlet portlet, PortletMode portletMode) 
  2.     throws PortalException, SystemException 
  3.   { 
  4.     if ((layout != null) && (layout.isTypeControlPanel())) { 
  5.       String category = portlet.getControlPanelEntryCategory(); 
  6.  
  7.       if (Validator.equals(category, "content")) { 
  8.         layout = null
  9.       } 
  10.     } 
  11.  
  12.     boolean access = contains( 
  13.       permissionChecker, scopeGroupId, layout, portlet, "VIEW"); 
  14.  
  15.     if ((access) && (!PropsValues.TCK_URL) &&  
  16.       (portletMode.equals(PortletMode.EDIT))) 
  17.     { 
  18.       access = contains( 
  19.         permissionChecker, scopeGroupId, layout, portlet,  
  20.         "PREFERENCES"); 
  21.     } 
  22.  
  23.     return access; 
  24.   } 

 

而这段代码中的又多了一个布尔字段叫access,它是通过以下代码获得的:

 
  
  1. public boolean contains(PermissionChecker permissionChecker, long groupId, Layout layout, Portlet portlet, String actionId, boolean strict) 
  2.     throws PortalException, SystemException 
  3.   { 
  4.     if (portlet.isUndeployedPortlet()) { 
  5.       return false
  6.     } 
  7.  
  8.     if ((portlet.isSystem()) && (actionId.equals("VIEW"))) { 
  9.       return true
  10.     } 
  11.  
  12.     return contains( 
  13.       permissionChecker, groupId, layout, portlet.getPortletId(),  
  14.       actionId, strict); 
  15.   } 

 

从08行我们就清楚了,只要我们吧这个portlet设为isSystem()返回true就行了,那么我们如何做到这点呢?

 

继续跟踪我们可以看到这个isSystem()实际是由下面这段代码设置的:

(在PortletLocalServiceImpl的_readLiferayPortletXML方法中)见第06-08行:

 
  
  1. private void _readLiferayPortletXML( 
  2.         String servletContextName, Map<String, Portlet> portletsPool, 
  3.         Set<String> liferayPortletIds, Map<String, String> roleMappers, 
  4.         Element portletElement) { 
  5. .. 
  6.     portletModel.setSystem( 
  7.             GetterUtil.getBoolean( 
  8.                 portletElement.elementText("system"), portletModel.isSystem())); 
  9.  
  10. portletModel.setActive( 
  11.             GetterUtil.getBoolean( 
  12.                 portletElement.elementText("active"), portletModel.isActive())); 
  13.         portletModel.setInclude( 
  14.             GetterUtil.getBoolean(portletElement.elementText("include"), 
  15.             portletModel.isInclude())); 
  16.  

 

因为这段代码是读取portlet的liferay-portlet.xml配置文件的,所以我们需要做的只是在portlet中加一个元素:

 
  
  1. <system>true</system> 

就可以了。

 

 

经过测试,当我们为portlet添加了这个元素之后,果然在Enterprise Edition上,这个portlet的resourceURL可以被正确的访问。