您的位置:首页 > Web前端

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中如下:

<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起来。

我们追述到PortletLocalServiceUtilgetEARDisplay方法中:

public static com.liferay.portal.model.PortletCategory getEARDisplay(
java.lang.String xml)
throws com.liferay.portal.kernel.exception.SystemException {
return getService().getEARDisplay(xml);
}


它最终会去调用PortletLocalServiceImplgetEARDisplay方法:

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方法中,而它最终会调用PortalImplgetPortletTitle方法中:

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的配置文件的文件名传入是在MainServletinitPortlets()方法声明的:

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-nameportlet.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>元素的值。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息