Liferay Dockbar点击Add->More后弹出的Portlet以及分类文本分析
2012-06-28 09:41
309 查看
因为我们以前项目中经常遇到配置了Portlet但是在对话框中找不到的情况,所以我们就来细致分析这里显示的文本是从哪里得来的。
对于整个dialog,它的代码是/html/portlet/layout_configuration/view_category.jsp.我们只想关注2个,一个是分类从哪里来,一个是Portlet 从哪里来:
分类文本:
对于黑色字的分类(比如我们这里的WalmartPlatformPortalDemo),其对应的代码在view_category.jsp中如下:
所以它最后会去调用LanguageUtil的get方法,而它最终内容会根据你的locale去读取resource bundle资源包,而传入的portletCategory.getName()则是当资源包中找不到匹配项时候的defaultName,因为大多数名字(我们为分类起的名字)都是项目特有的,不太可能和资源包中的key重名,所以大多数情况下最终显示的内容就是portletCategory.getName()返回的字符串值。
所以我们现在焦点在于portletCategory.getName()值是如何取来的。
在view_category.jsp的上方我们找到了:
所以,我们一定想到,这个request域上的属性值设置肯定是在包含这个jsp的文件中给出的。因为view_category.jsp中是被循环引入到layout_configuration/view.jsp的,果然在view.jsp中我们找到了对应的代码:
所以解开我们的钥匙就在WebAppPool的get方法,它的值肯定是设置进去的(否则怎么取呢),所以我们找到WebAppPool的put方法:
而这个方法是被PortalInstances的_initCompany方法所调用的:
从这段代码可以看出来,它08-09行会先去判断这个值是否已经在WebAppPool中有,对于第一次访问,池中显然是没有的,所以第15行构造了一个新的PortletCategory对象,并且让其读取WEB-INF/liferay-display.xml中的设定,最后和空的PortletCategory对象merge起来。
我们追述到PortletLocalServiceUtil的getEARDisplay方法中:
它最终会去调用PortletLocalServiceImpl的getEARDisplay方法:
进入调用_readLiferayDisplayXML方法,然后调用重载的_readLiferayDisplayXML(servletContextName,xml)方法:
从这段代码我们可以清晰的对于category的处理分为两种情况:
(1)对于liferay-display.xml中有<category>元素的分类,我们跟进第18行:
可以看出,它会循环的遍历liferay-display.xml中所有<category>元素,然后依次读取它们的<name>属性,并且把这些name属性都分别封装在PortletCategory对象中,然后把这些对象放入参数的PortletCategory中,然后递归继续读下去(PS:这是GOF中典型的Composite 设计模式)
(2) 对于liferay-display.xml中没有分类的Portlet,会归结到category.undefined分类中
PortletCategory undefinedCategory = new PortletCategory(
"category.undefined");
Portlet名字文本:
对于有紫色正方形标示的Portlet名字文本,比如我们这里的(ClusterNodeInfoPortlet),对照页面:
因为没有 portletItemId属性,所以我们断定走的是以下代码:
所以,显示文本就在 PortalUtil.getPortletTitle方法中,而它最终会调用PortalImpl的getPortletTitle方法中:
所以,文本实际在09行的getString方法中获取的。我们跟进到ResourceBundle的getString(key)方法,它会调用ResourceBundle的getObject(key)方法:
进而会调用PortletResourceBundle的handleGetObject(key)方法:
从这里看出因为满足03行的条件,所以会调用PortletResourceBundle的_getJavaxPortletString(key)方法:
因为我们传入的参数为 JavaConstants.JAVAX_PORTLET_TITLE,所以返回的结果是_portletInfo.getTitle()的字符串值。这个值是在PortletInfo构造函数中被赋值的:
而这个构造函数的最终也是在PortletLocalServiceImpl的_readPortletXML方法中被调用的:
从这里可以看出,这个title是在08行从中获取<title>元素,而这个title元素是从portletElement中获取<portlet-info>元素得来的。
那么portletInfoElement如何得来的呢?参见PortletLocalServiceImpl的_readLiferayDisplay方法:
所以,从这里可以看出它是从配置文件中<portlet>资源数中获得的。
而这个配置文件是哪个文件呢?
从我highlight的一行点入可以看到,它传入的是下标为0的xml文件,这个调用位于PortletLocalServiceImpl类中:
而下标为0的配置文件的文件名传入是在MainServlet的initPortlets()方法声明的:
现在一切真相大白了,原来,显示页面上的portlet的display-name是portlet.xml(被portlet-ext.xml覆盖后,如果有)的<portlet>下面的<portlet-info>下面的<title>元素的值。
总结:
经过3个多小时的探索,我们得到以下结论:
在Dockbar->Add->More显示的Portlet名称以及所属分类是按照 以下规则得到的:
对于分类:如果定义了Portlet的分类,则在liferay-display.xml中的<category>元素的值,否则,这个Portlet被加在 category.undefined 分类中。
对于Portlet名字,它是定义在portlet.xml的<portlet>元素下面的<portlet-info>元素下面的<title>元素的值。
对于整个dialog,它的代码是/html/portlet/layout_configuration/view_category.jsp.我们只想关注2个,一个是分类从哪里来,一个是Portlet 从哪里来:
分类文本:
对于黑色字的分类(比如我们这里的WalmartPlatformPortalDemo),其对应的代码在view_category.jsp中如下:
<h2> <span><%= Validator.isNotNull(externalPortletCategory) ? externalPortletCategory : LanguageUtil.get(pageContext, portletCategory.getName()) %></span> </h2>
所以它最后会去调用LanguageUtil的get方法,而它最终内容会根据你的locale去读取resource bundle资源包,而传入的portletCategory.getName()则是当资源包中找不到匹配项时候的defaultName,因为大多数名字(我们为分类起的名字)都是项目特有的,不太可能和资源包中的key重名,所以大多数情况下最终显示的内容就是portletCategory.getName()返回的字符串值。
所以我们现在焦点在于portletCategory.getName()值是如何取来的。
在view_category.jsp的上方我们找到了:
<%@ include file="/html/portlet/layout_configuration/init.jsp" %> <% PortletCategory portletCategory = (PortletCategory)request.getAttribute(WebKeys.PORTLET_CATEGORY); ..
所以,我们一定想到,这个request域上的属性值设置肯定是在包含这个jsp的文件中给出的。因为view_category.jsp中是被循环引入到layout_configuration/view.jsp的,果然在view.jsp中我们找到了对应的代码:
PortletCategory portletCategory = (PortletCategory)WebAppPool.get(company.getCompanyId(), WebKeys.PORTLET_CATEGORY); portletCategory = _getRelevantPortletCategory(permissionChecker, portletCategory, panelSelectedPortlets, layoutTypePortlet, layout, user); List categories = ListUtil.fromCollection(portletCategory.getCategories()); categories = ListUtil.sort(categories, new PortletCategoryComparator(locale)); int portletCategoryIndex = 0; Iterator itr = categories.iterator(); while (itr.hasNext()) { PortletCategory curPortletCategory = (PortletCategory)itr.next(); if (curPortletCategory.isHidden()) { continue; } request.setAttribute(WebKeys.PORTLET_CATEGORY, curPortletCategory); ...
所以解开我们的钥匙就在WebAppPool的get方法,它的值肯定是设置进去的(否则怎么取呢),所以我们找到WebAppPool的put方法:
public static void put(Long webAppId, String key, Object obj) { _instance._put(webAppId, key, obj); }
而这个方法是被PortalInstances的_initCompany方法所调用的:
private long _initCompany(ServletContext servletContext, String webId) { ... try { String xml = HttpUtil.URLtoString(servletContext.getResource( "/WEB-INF/liferay-display.xml")); PortletCategory portletCategory = (PortletCategory)WebAppPool.get( companyId, WebKeys.PORTLET_CATEGORY); if (portletCategory == null) { portletCategory = new PortletCategory(); } PortletCategory newPortletCategory = PortletLocalServiceUtil.getEARDisplay(xml); portletCategory.merge(newPortletCategory); for (int i = 0; i < _companyIds.length; i++) { long currentCompanyId = _companyIds[i]; PortletCategory currentPortletCategory = (PortletCategory)WebAppPool.get( currentCompanyId, WebKeys.PORTLET_CATEGORY); if (currentPortletCategory != null) { portletCategory.merge(currentPortletCategory); } } WebAppPool.put( companyId, WebKeys.PORTLET_CATEGORY, portletCategory); } ..
从这段代码可以看出来,它08-09行会先去判断这个值是否已经在WebAppPool中有,对于第一次访问,池中显然是没有的,所以第15行构造了一个新的PortletCategory对象,并且让其读取WEB-INF/liferay-display.xml中的设定,最后和空的PortletCategory对象merge起来。
我们追述到PortletLocalServiceUtil的getEARDisplay方法中:
public static com.liferay.portal.model.PortletCategory getEARDisplay( java.lang.String xml) throws com.liferay.portal.kernel.exception.SystemException { return getService().getEARDisplay(xml); }
它最终会去调用PortletLocalServiceImpl的getEARDisplay方法:
public PortletCategory getEARDisplay(String xml) throws SystemException { try { return _readLiferayDisplayXML(xml); } .. }
进入调用_readLiferayDisplayXML方法,然后调用重载的_readLiferayDisplayXML(servletContextName,xml)方法:
private PortletCategory _readLiferayDisplayXML( String servletContextName, String xml) throws Exception { PortletCategory portletCategory = new PortletCategory(); if (xml == null) { xml = ContentUtil.get( "com/liferay/portal/deploy/dependencies/liferay-display.xml"); } Document document = SAXReaderUtil.read(xml, true); Element rootElement = document.getRootElement(); Set<String> portletIds = new HashSet<String>(); _readLiferayDisplay( servletContextName, rootElement, portletCategory, portletIds); // Portlets that do not belong to any categories should default to the // Undefined category Set<String> undefinedPortletIds = new HashSet<String>(); for (Portlet portlet : _getPortletsPool().values()) { String portletId = portlet.getPortletId(); PortletApp portletApp = portlet.getPortletApp(); if ((servletContextName != null) && (portletApp.isWARFile()) && (portletId.endsWith( PortletConstants.WAR_SEPARATOR + PortalUtil.getJsSafePortletId(servletContextName)) && (!portletIds.contains(portletId)))) { undefinedPortletIds.add(portletId); } else if ((servletContextName == null) && (!portletApp.isWARFile()) && (portletId.indexOf( PortletConstants.WAR_SEPARATOR) == -1) && (!portletIds.contains(portletId))) { undefinedPortletIds.add(portletId); } } if (!undefinedPortletIds.isEmpty()) { PortletCategory undefinedCategory = new PortletCategory( "category.undefined"); portletCategory.addCategory(undefinedCategory); undefinedCategory.getPortletIds().addAll(undefinedPortletIds); } return portletCategory; }
从这段代码我们可以清晰的对于category的处理分为两种情况:
(1)对于liferay-display.xml中有<category>元素的分类,我们跟进第18行:
private void _readLiferayDisplay( String servletContextName, Element element, PortletCategory portletCategory, Set<String> portletIds) { for (Element categoryElement : element.elements("category")) { String name = categoryElement.attributeValue("name"); PortletCategory curPortletCategory = new PortletCategory(name); portletCategory.addCategory(curPortletCategory); Set<String> curPortletIds = curPortletCategory.getPortletIds(); for (Element portletElement : categoryElement.elements("portlet")) { String portletId = portletElement.attributeValue("id"); if (Validator.isNotNull(servletContextName)) { portletId = portletId + PortletConstants.WAR_SEPARATOR + servletContextName; } portletId = PortalUtil.getJsSafePortletId(portletId); portletIds.add(portletId); curPortletIds.add(portletId); } _readLiferayDisplay( servletContextName, categoryElement, curPortletCategory, portletIds); } }
可以看出,它会循环的遍历liferay-display.xml中所有<category>元素,然后依次读取它们的<name>属性,并且把这些name属性都分别封装在PortletCategory对象中,然后把这些对象放入参数的PortletCategory中,然后递归继续读下去(PS:这是GOF中典型的Composite 设计模式)
(2) 对于liferay-display.xml中没有分类的Portlet,会归结到category.undefined分类中
PortletCategory undefinedCategory = new PortletCategory(
"category.undefined");
Portlet名字文本:
对于有紫色正方形标示的Portlet名字文本,比如我们这里的(ClusterNodeInfoPortlet),对照页面:
因为没有 portletItemId属性,所以我们断定走的是以下代码:
<div class="lfr-portlet-item <c:if test="<%= portletLocked %>">lfr-portlet-used</c:if> <c:if test="<%= portletInstanceable %>">lfr-instanceable</c:if>" id="<portlet:namespace />portletItem<%= portlet.getPortletId() %>" instanceable="<%= portletInstanceable %>" plid="<%= plid %>" portletId="<%= portlet.getPortletId() %>" title="<%= PortalUtil.getPortletTitle(portlet, application, locale) %>" > <p><%= PortalUtil.getPortletTitle(portlet, application, locale) %> <a href="javascript:;"><liferay-ui:message key="add" /></a></p> </div>
所以,显示文本就在 PortalUtil.getPortletTitle方法中,而它最终会调用PortalImpl的getPortletTitle方法中:
public String getPortletTitle( Portlet portlet, ServletContext servletContext, Locale locale) { PortletConfig portletConfig = PortletConfigFactoryUtil.create( portlet, servletContext); ResourceBundle resourceBundle = portletConfig.getResourceBundle(locale); return resourceBundle.getString(JavaConstants.JAVAX_PORTLET_TITLE); }
所以,文本实际在09行的getString方法中获取的。我们跟进到ResourceBundle的getString(key)方法,它会调用ResourceBundle的getObject(key)方法:
public final Object getObject(String key) { Object obj = handleGetObject(key); if (obj == null) { if (parent != null) { obj = parent.getObject(key); } ... }
进而会调用PortletResourceBundle的handleGetObject(key)方法:
protected Object handleGetObject(String key) { .. if ((value == null) || (value == ResourceBundleUtil.NULL_VALUE)) { value = _getJavaxPortletString(key); } .. return value; }
从这里看出因为满足03行的条件,所以会调用PortletResourceBundle的_getJavaxPortletString(key)方法:
private String _getJavaxPortletString(String key) { if (key.equals(JavaConstants.JAVAX_PORTLET_TITLE)) { return _portletInfo.getTitle(); } else if (key.equals(JavaConstants.JAVAX_PORTLET_SHORT_TITLE)) { return _portletInfo.getShortTitle(); } else if (key.equals(JavaConstants.JAVAX_PORTLET_KEYWORDS)) { return _portletInfo.getKeywords(); } else if (key.equals(JavaConstants.JAVAX_PORTLET_DESCRIPTION)) { return _portletInfo.getDescription(); } return null; }
因为我们传入的参数为 JavaConstants.JAVAX_PORTLET_TITLE,所以返回的结果是_portletInfo.getTitle()的字符串值。这个值是在PortletInfo构造函数中被赋值的:
public PortletInfo( String title, String shortTitle, String keywords, String description) { _title = title; _shortTitle = shortTitle; _keywords = keywords; _description = description; }
而这个构造函数的最终也是在PortletLocalServiceImpl的_readPortletXML方法中被调用的:
private void _readPortletXML( String servletContextName, Map<String, Portlet> portletsPool, PluginPackage pluginPackage, PortletApp portletApp, Set<String> portletIds, long timestamp, Element portletElement) { Element portletInfoElement = portletElement.element("portlet-info"); .. if (portletInfoElement != null) { portletInfoTitle = portletInfoElement.elementText("title"); portletInfoShortTitle = portletInfoElement.elementText( "short-title"); portletInfoKeyWords = portletInfoElement.elementText("keywords"); } PortletInfo portletInfo = new PortletInfo( portletInfoTitle, portletInfoShortTitle, portletInfoKeyWords, portletInfoDescription); ..
从这里可以看出,这个title是在08行从中获取<title>元素,而这个title元素是从portletElement中获取<portlet-info>元素得来的。
那么portletInfoElement如何得来的呢?参见PortletLocalServiceImpl的_readLiferayDisplay方法:
for (Element portletElement : categoryElement.elements("portlet")) { String portletId = portletElement.attributeValue("id"); ...
所以,从这里可以看出它是从配置文件中<portlet>资源数中获得的。
而这个配置文件是哪个文件呢?
从我highlight的一行点入可以看到,它传入的是下标为0的xml文件,这个调用位于PortletLocalServiceImpl类中:
Set<String> portletIds = _readPortletXML( servletContext, xmls[0], portletsPool, servletURLPatterns, pluginPackage);
而下标为0的配置文件的文件名传入是在MainServlet的initPortlets()方法声明的:
protected List<Portlet> initPortlets(PluginPackage pluginPackage) throws Exception { ServletContext servletContext = getServletContext(); String[] xmls = new String[] { HttpUtil.URLtoString( servletContext.getResource( "/WEB-INF/" + Portal.PORTLET_XML_FILE_NAME_CUSTOM)), HttpUtil.URLtoString( servletContext.getResource("/WEB-INF/portlet-ext.xml")), HttpUtil.URLtoString( servletContext.getResource("/WEB-INF/liferay-portlet.xml")), HttpUtil.URLtoString( servletContext.getResource("/WEB-INF/liferay-portlet-ext.xml")), HttpUtil.URLtoString( servletContext.getResource("/WEB-INF/web.xml")) }; PortletLocalServiceUtil.initEAR(servletContext, xmls, pluginPackage); ...
现在一切真相大白了,原来,显示页面上的portlet的display-name是portlet.xml(被portlet-ext.xml覆盖后,如果有)的<portlet>下面的<portlet-info>下面的<title>元素的值。
总结:
经过3个多小时的探索,我们得到以下结论:
在Dockbar->Add->More显示的Portlet名称以及所属分类是按照 以下规则得到的:
对于分类:如果定义了Portlet的分类,则在liferay-display.xml中的<category>元素的值,否则,这个Portlet被加在 category.undefined 分类中。
对于Portlet名字,它是定义在portlet.xml的<portlet>元素下面的<portlet-info>元素下面的<title>元素的值。
相关文章推荐
- Liferay 从Dockbar 添加Portlet的事件细节研究
- Liferay:在Dockbar中增加语言Portlet方法
- Liferay:在Dockbar中增加语言Portlet方法
- 文本分类之降维技术之特征抽取之LDA线性判别分析
- IPhone手机页面中点击文本输入框,弹出键盘,网页会放大,如何解决
- Yoon Kim的textCNN讲解,以及tensorflow实现,CNN文本分类
- awk<文本分析、指定分隔符、显示指定域>
- Liferay Dockbar 解析
- 基于朴素贝叶斯分类器的文本分类算法的实现过程分析
- java虚拟机运行时的内存分类以及出现异常分析
- layer弹出ifream,提交之后自动关闭;无限级分类排序以及无限级分类子孙图树形展示
- 在li>a>div>img结构下出现ie6下点击无效以及无形空隙的解决方法
- General>>Content Types,选择Text>>Html,点击Add,输入*.vm,保存。
- UILabel点击调用电话以及邮箱,UILabel超链接效果 NSMutableAttributedString文本样式设置
- android长按home键源码分析以及模拟长按home事件--弹出近期任务.
- LSTM 文本情感分析/序列分类 Keras
- 基于贝叶斯算法文本分析之新闻分类
- 安卓自动化测试工具MonkeyRunner之使用ID进行参数化,以及List选择某项和弹出框点击确定的写法
- 【机器学习PAI实践七】文本分析算法实现新闻自动分类
- 仿QQ新浪主界面底部导航与中间按钮点击弹出以及返回键点击返回效果