因为我们以前项目中经常遇到配置了Portlet但是在对话框中找不到的情况,所以我们就来细致分析这里显示的文本是从哪里得来的。
对于整个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>元素的值。