Liferay研究-smilingleo
http://blog.csdn.net/smilingleo/article/details/1859908
Liferay研究之一:Ext環境的搭建
本篇主要介紹如何搭建Ext開發環境。網上也有很多介紹,不過這里和別的還是有些不同的。我用的版本是最新的4.3.3。
1、在portal源碼目錄下建立:release.Leo.properties,其中Leo是我在Windowx XP中的賬號名,你需要根據自己的情況更改(下同),內容如下:
lp.ext.dir=C:/Java/MyEclipse5.5.1GA/workspace/ext
2、tools/ext_tmpl/servers/tomcat/conf/Catalina/localhost/Root.xml
修改數據庫連接,比如修改為MySQL
3、portal-impl/classes/system.properties (注意,是Web層的文件夾,不是src目錄)
修改user.country,user.language等,電話格式 com.liferay.util.format.PhoneNumberFormat
上傳文件最大限制:com.liferay.util.servlet.UploadServletRequest.max.size
配置Layout緩存機制來提升頁面的訪問速度:com.liferay.portal.servlet.filters.layoutcache.LayoutCacheFilter, 具體配置參見ehcache.xml
設置頁面響應等待時間:com.liferay.util.Http.timeout
一般來說,不需要做變更。
4、Build, clean→start→build-ext, 系統就會自動創建Ext目錄
5、在ext目錄下創建app.server.Leo.properties,內容如下:
lp.ext.dir=C:/Java/MyEclipse5.5.1GA/workspace/ext
app.server.type=tomcat
app.server.tomcat.dir=C:/Java/liferay
至此,就可以通過ant deploy將Ext工程發布到Tomcat了。
【高級話題】
6、在portal/portal-impl/portal.properties中有如何對liferay進行擴展的描述,具體可以建立一個ext/ext-impl/portal-ext.properties,進行擴展。比如使用Spring, hibernate?
Liferay研究之二:引入Spring
首先Suppose你已經搭建好了ext環境。
1、在Ext/ext-web/docroot/WEB-INF/web.xml中引入spring
?
??<display-name>Spring?Portal</display-name>??<description>Spring?Portlet?sample?application</description>
??<!--
????-?Location?of?the?XML?file?that?defines?the?root?application?context.
????-?Applied?by?ContextLoaderServlet.
????-->
??<context-param>
?<param-name>contextConfigLocation</param-name>
?<param-value>/WEB-INF/context/applicationContext.xml</param-value>
??</context-param>?
??<listener>
?<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
??</listener>
??<servlet>
?????<servlet-name>view-servlet</servlet-name>
?????<servlet-class>org.springframework.web.servlet.ViewRendererServlet</servlet-class>
?????<load-on-startup>1</load-on-startup>
??</servlet>
??<servlet-mapping>
?????<servlet-name>view-servlet</servlet-name>
?????<url-pattern>/WEB-INF/servlet/view</url-pattern>
??</servlet-mapping>?
?
?
2、在/WEB-INF/下建立一個目錄(當然也可以不建)context,并在其下建立一個文件applicationContext.xml,配置spring的缺省行為,如下所示:
?
<!--?Default?View?Resolver?--><bean?id="viewResolver"?class="org.springframework.web.servlet.view.InternalResourceViewResolver">
?<property?name="viewClass"?value="org.springframework.web.servlet.view.JstlView"/>
?<property?name="prefix"?value="/WEB-INF/jsp/"/>
?<property?name="suffix"?value=".jsp"/>
</bean>
<!--?This?interceptor?forwards?the?mapping?request?parameter?from?an?ActionRequest?to?be?used?as?a?render?parameter.?-->
<bean?id="parameterMappingInterceptor"?class="org.springframework.web.portlet.handler.ParameterMappingInterceptor"/>
<!--?Abstract?Default?ExceptionHandler?-->
<bean?id="defaultExceptionHandlerTemplate"?class="org.springframework.web.portlet.handler.SimpleMappingExceptionResolver"?abstract="true">
?<property?name="defaultErrorView"?value="error"/>
?<property?name="exceptionMappings">
??<props>
???<prop?key="javax.portlet.PortletSecurityException">unauthorized</prop>
???<prop?key="javax.portlet.UnavailableException">unavailable</prop>
??</props>
?</property>??
</bean>
?
3、編寫一個Spring Portlet。 在ext-impl/新建一個Class,目錄自選,命名為HelloWorldPortlet, 該類繼承于GenericPortlet。
?
public?class?HelloWorldPortlet?extends?GenericPortlet?...{????public?void?doView(RenderRequest?request,?RenderResponse?response)
?????throws?PortletException,?IOException?...{
????????System.out.println("Entering?HelloWorldPortlet.doView");
????????response.setContentType("text/html");
????????PrintWriter?out?=?response.getWriter();
????????out.println("<h1>Hello?World</h1>");
????????out.println("<p>This?portlet?demonstrates?how?to?delegate?to?"+
????????????????"an?existing?JSR-168?portlet?via?a?HandlerAdapter</p>");
????????out.println("<p>Portlet?Name:?"?+?this.getPortletName()?+?"</p>");
????????out.println("<p>Init?Parameters:</p><ul>");
????????for?(Enumeration?e?=?this.getInitParameterNames();?e.hasMoreElements();)?...{
????????????String?name?=?(String)e.nextElement();
????????????out.println("<li>"?+?name?+?"?=?"?+?this.getInitParameter(name)?+?"</li>");
????????}
????????out.println("</ul>");
????}
}
?
4、編寫HelloWorldPortlet對應的Spring bean配置。在context目錄下建立一個helloworld.xml,并配置其內容如下:
?
<?xml?version="1.0"?encoding="UTF-8"?><!DOCTYPE?beans?PUBLIC?"-//SPRING//DTD?BEAN?2.0//EN"?"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
?<bean?id="helloWorldPortlet"?class="org.springframework.web.portlet.mvc.PortletWrappingController">
??<property?name="portletClass">
???<value>com.ext.portlet.helloworld.HelloWorldPortlet</value>
??</property>
??<property?name="useSharedPortletConfig">
???<value>false</value>
??</property>
??<property?name="portletName">
???<value>wrapped-hello-world</value>
??</property>
??<property?name="initParameters">
???<props>
????<prop?key="HarryPotter">The?magic?property</prop>
????<prop?key="JerrySeinfeld">The?funny?property</prop>
???</props>
??</property>
?</bean>
?
?<!--?Handler?Mapping?-->
?
?<bean?id="portletModeHandlerMapping"?class="org.springframework.web.portlet.handler.PortletModeHandlerMapping">
??<property?name="portletModeMap">
???<map>
????<entry?key="view"><ref?bean="helloWorldPortlet"/></entry>
???</map>
??</property>
?</bean>?
?<!--?Exceptions?Handler?-->
?<bean?id="defaultExceptionHandler"?parent="defaultExceptionHandlerTemplate"/>?
</beans>
?
注意,此處需要配置一個缺省的handler.
5、至此,一個Spring Portlet就開發好了,剩下的,就是需要將其配置成為一個Liferay的Portlet,具體步驟如下:
6、先在portlet-ext.xml中定義Portlet:
?
<portlet>????<portlet-name>helloworld</portlet-name>
????<portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>
????<init-param>
?<name>contextConfigLocation</name>
?<value>/WEB-INF/context/helloworld.xml</value>
????</init-param>
????<supports>
????????<mime-type>text/html</mime-type>
????????<portlet-mode>view</portlet-mode>
????</supports>
????<portlet-info>
????????<title>Hello?World</title>
????</portlet-info>??????
</portlet>
?
注意,上面加粗部分顯示了liferay如何將spring bean定義配置文件導入的。有一些文章介紹需要在portal-ext.properties中配置,可能是版本升級了,在我測試過程中,沒有用那種方式。
7、然后在將其定義給Liferay,在liferay-portlet-ext.xml中定義:
<portlet>
?<portlet-name>helloworld</portlet-name>
</portlet>???
8、將其顯示到Add Content列表中。 在liferay-display.xml中定義:
<category name="category.sample">
?<portlet id="helloworld" />
</category>
至此,Spring Portlet開發完成。最后,還需要將spring-portlet.jar拷貝到classpath中。OK, ant deploy→login→add content→sample.helloworld。
打完,收工!?
Liferay研究之三:通過LDAP設置連接Novell eDirectory
1、先通過LDAP瀏覽器進行連接測試。這里使用JXPlorer.
連接設置如下:
Provider URL:ldap://192.168.0.12:389
Protocol:LDAP v3
Base DN:o=yourOrg
Level:User + Password
User DN:cn=admin,ou=系統,o=yourOrg
Password=yourpassword
2、連接成功之后,就可以通過設置Liferay的LDAP設置進行數據導入了。
測試用戶有效性過濾條件:
(&(objectClass=Person)(mail=@email_address@))
LDAP密碼加密算法:空
userMappings設置:
screenName=cn
password=sn
emailAddress=mail
firstName=fullName
lastName=fullName
jobTitle=title
這里需要注意:NDS里面的password不能直接取出來,只能通過一些別的途徑,比如將密碼在sn中進行備份,然后通過sn取出來。
導入設置:
可導入:true
import-user-search-filter:(objectClass=inetOrgPerson)
要導入對象的objectClass類型;需要在LDAP瀏覽器中查看具體對象的objectClass;
import-group-search-filter:(objectClass=organizationalUnit)
要導入對象的group類型條件。
導出設置:
用戶DN:o=yourOrg
描述導出導入的數據結構,可以在LDAP瀏覽器中選中一個用戶,右鍵“Copy DN”來獲取,注意將第一個cn去除;
用戶缺省對象類:top,person,organizationalPerson,inetOrgPerson
根據LDAP中相應值進行設定;
【技巧】
在Liferay調試時,可以在Admin Portlet中→“服務器”→“日志級別”中對應的Portlet或Java Class的Log級別修改為Debug級,或All,這樣就能在控制臺看到更多信息。?
userMappings里面如何做一些自定義的事情,你可以跟蹤一下LDAPUtil里面對應的方法,看看attr里面到底有什么映射名可以用。
另外,NDS中存儲的密碼一般會是加密的,你需要根據加密算法先將其進行解密,然后再用Liferay自己的加密方法進行加密。
Liferay研究之四:列表顯示jsp分析
顯示文章列表的jsp頁面是journal_articles/view.jsp,其中用了一個liferay自定義的標簽:
<liferay-ui:search-iterator searchContainer="<%= searchContainer %>" />
該標簽由:liferay-ui.tld定義,是由:com.liferay.taglib.ui.SearchIteratorTag 來解析的。
從SearchIteratorTag代碼中可以知道,里面會調用一個標簽屬性:paginate,且缺省頁面是html/taglib/ui/search_iterator/page.jsp。
從中可以知道,需要先判斷是否分頁:
<c:if test="<%= paginate %>">
如果需要分頁,則調用分頁專用的tag標簽。
<liferay-ui:search-paginator searchContainer="<%= searchContainer %>" />
之后,通過headerNames來顯示表頭,如果將其clear,那么就不顯示表頭。
接下來判斷,如果沒有查詢結果,則顯示無結果的顯示內容。比如“沒有查詢結果”之類;
然后設置每行顯示的className 樣式表類;之后就開始顯示表中的內容。注意,row.getEntries()獲得一行的所有列,entry.print(pageContext)顯示列的內容;
最后顯示列表的bottom內容。
?<c:if test="<%= (resultRows.size() > 10) && paginate %>">
??<div class="taglib-search-iterator-page-iterator-bottom">
???<liferay-ui:search-paginator searchContainer="<%= searchContainer %>" />
??</div>
?</c:if>
這里有點迷糊,不知道這個bottom的具體作用,可能只是添加一個html </form>標記。或者還有什么內容,或者只是與taglib-search-iterator-page-iterator-top成對出現?
此外,有一個Bug, 如果分頁點擊下一頁時會顯示下一個記錄的詳細內容,而不是下一頁列表,查看源代碼可能是沒有設置searchContainer.iteratorURL
顯示文章列表信息的相關頁面有:
html/taglib/ui/page_iterator/start.jsp
Near 85 line:
?<div class="search-results">
??<c:choose>
???<c:when test="<%= total > resultRowsSize %>">
????<%= LanguageUtil.format(pageContext, "showing-x-x-of-x-results", new Object[] {String.valueOf(start + 1), String.valueOf(end), String.valueOf(total)}) %>
???</c:when>
???<c:otherwise>
????<c:choose>
?????<c:when test="<%= total != 1 %>">
??????<%= LanguageUtil.format(pageContext, "showing-x-results", String.valueOf(total)) %>
?????</c:when>
?????<c:otherwise>
??????<%= LanguageUtil.format(pageContext, "showing-x-result", String.valueOf(total)) %>
?????</c:otherwise>
????</c:choose>
???</c:otherwise>
??</c:choose>
?</div>
將上面代碼刪除,可不顯示:當前共xx條。的文字;
顯示:頁 xx 的xx ,選擇第幾頁的代碼:
Near 105 line (original code):
???<div class="page-selector">
????<liferay-ui:message key="page" />
????<select class="pages <%= namespace %>pageIteratorValue">
?????<%
?????for (int i = 1; i <= pages; i++) {
?????%>
??????<option <%= (i == curValue) ? "selected=/"selected/"" : "" %> value="<%= i %>"><%= i %></option>
?????<%
?????}
?????%>
????</select>
????<liferay-ui:message key="of" />
????<%= pages %>
????<input class="page-iterator-submit" type="submit" value="<liferay-ui:message key="submit" />" />
???</div>
顯示分頁控制的代碼:
Near 129 line(original code):
???<div class="page-links">
????<c:choose>
?????<c:when test="<%= curValue != 1 %>">
??????<a class="first" href="<%= _getHREF(formName, curParam, 1, jsCall, url, urlAnchor) %>" target="<%= target %>">
?????</c:when>
?????<c:otherwise>
??????<span class="first">
?????</c:otherwise>
????</c:choose>
????<liferay-ui:message key="first" />
????<c:choose>
?????<c:when test="<%= curValue != 1 %>">
??????</a>
?????</c:when>
?????<c:otherwise>
??????</span>
?????</c:otherwise>
????</c:choose>
????<c:choose>
?????<c:when test="<%= curValue != 1 %>">
??????<a class="previous" href="<%= _getHREF(formName, curParam, curValue - 1, jsCall, url, urlAnchor) %>" target="<%= target %>">
?????</c:when>
?????<c:otherwise>
??????<span class="previous">
?????</c:otherwise>
????</c:choose>
????<liferay-ui:message key="previous" />
????<c:choose>
?????<c:when test="<%= curValue != 1 %>">
??????</a>
?????</c:when>
?????<c:otherwise>
??????</span>
?????</c:otherwise>
????</c:choose>
????<c:choose>
?????<c:when test="<%= curValue != pages %>">
??????<a class="next" href="<%= _getHREF(formName, curParam, curValue + 1, jsCall, url, urlAnchor) %>" target="<%= target %>">
?????</c:when>
?????<c:otherwise>
??????<span class="next">
?????</c:otherwise>
????</c:choose>
????<liferay-ui:message key="next" />
????<c:choose>
?????<c:when test="<%= curValue != pages %>">
??????</a>
?????</c:when>
?????<c:otherwise>
??????</span>
?????</c:otherwise>
????</c:choose>
????<c:choose>
?????<c:when test="<%= curValue != pages %>">
??????<a class="last" href="<%= _getHREF(formName, curParam, pages, jsCall, url, urlAnchor) %>" target="<%= target %>">
?????</c:when>
?????<c:otherwise>
??????<span class="last">
?????</c:otherwise>
????</c:choose>
????<liferay-ui:message key="last" />
????<c:choose>
?????<c:when test="<%= curValue != pages %>">
??????</a>
?????</c:when>
?????<c:otherwise>
??????</span>
?????</c:otherwise>
????</c:choose>
???</div>
?
html/portlet/journal_articles/view.jsp
Near 56 line:
??//headerNames.add("name");
??//headerNames.add("display-date");
??//headerNames.add("author");
將上述代碼注釋掉之后,可以不顯示表的表頭。“文章、作者、日期”等。?
Liferay研究之五:Liferay的MDA開發模式
Liferay其實不單單是一個開源的門戶產品,同時也是一個很好的開發框架。
Liferay采用了MDA(模型驅動開發架構)的開發模式,具體來講,就是開發一個模塊前,需要先通過配置定義Model, Service,通過ServiceBuilder工具根據定義自動創建dao, service interface, service impelmention, util facade, 此外,也通過custom-sql機制讓用戶進行擴展。有了這個MDA的概念之后,你就能比較順利的了解Liferay的開發模式。smilingleo原創
1、對于簡單的應用,可直接通過service定義后build-service-xxx;
2、對于復雜的應用,可以先在ServiceImpl類中實現對應的類,然后build-service-xxx將這個方法(接口),自動繁衍到所有的Service, Util類中。(從底層到頂層的驅動)
具體來講:
1、在service.xml中定義Relationship及Finder方法(可以不定義),之后運行ant build-service,這樣就可以生成對應的Util, Persistence, PersistenceImpl類及對應方法;
具體來說,是在Persistence中添加addRelatedObject,addRelatedObjects, getRelatedObjectList,getRelatedObjectListSize, containRelatedObjects,clear, remove等對關系的增、刪、改、查對應方法; smilingleo原創
注意:此處的Finder方法,僅僅是根據Entity的某個或某些域來查詢Entity結果; 與Custom-SQL中定義的find方法不一樣。前者是定義之后自動生成的,也就是通過ServiceBuilder創建的;而后者必須手工添加。
從邏輯關系上講:XXXXFinder與XXXXPersistanceImpl在層次上是對等的,都直接被XXXXServiceImpl調用。
2、如果你想定制查詢,比如多表查詢,則需要定義custom-sql/xxx.xml. ,手工編寫XXXXFinder對應的finderBy方法(或者countBy方法),然后再在ServiceImpl類中封裝對應的方法;之后ant build-service-xxxxx.這樣就將對應的業務方法“繁衍propagate”到所有Service, Util類中了。smilingleo原創
3、編寫JSP頁面通過Util類實現業務方法調用。
Liferay研究之六:Liferay技巧幾則
在Liferay中添加鏈接的方式
1、通過liferay-ui:icon標簽;
?
??<%...????PortletURL?moreURL?=?renderResponse.createRenderURL();
???moreURL.setParameter("struts_action",?"/journal_articles/view");
???moreURL.setParameter("type",?type);
???moreURL.setParameter("group-id",?String.valueOf(groupId));
???moreURL.setWindowState(WindowState.MAXIMIZED);????
??%>
??<table?width="100%"><tr><td?align="right">
???<liferay-ui:icon?src="/images/more.png"?message="More"?url="<%=?moreURL.toString()?%>"?/>
??</td></tr></table>??
?
2、通過Struts標簽來添加鏈接。
修改每個Portlet的圖標。在WEB-INF/liferay-portlet.xml中定義,默認是用icon.png?
Liferay連接Oracle的問題
用Liferay 4.3.3直接修改數據源后訪問Oracle,在保存Clob時,會報ORA-01483的錯誤,說什么DATE, NUMBER的長度無效。
具體解決辦法是:
1.portal-impl.jar包查找META-INF/portal-hbm.xml, 將所有clob字段的配置類型修改為org.springframework.orm.hibernate3.support.ClobStringType(原來為com.liferay.util.dao.hibernate.StringType)。
2.修改counter-ejb.jar包里面的counter-spring-professional.xml文件,添加如下內容:?
?
?
<bean?id="lobHandler"?lazy-init="true"??class="org.springframework.jdbc.support.lob.OracleLobHandler">
??<property?name="nativeJdbcExtractor">
???<ref?bean="nativeJdbcExtractor"?/>
??</property>
?</bean>
?<bean?id="nativeJdbcExtractor"?lazy-init="true"
??class="org.springframework.jdbc.support.nativejdbc.SimpleNativeJdbcExtractor"?/>
?<bean?id="liferaySessionFactory"
??class="com.liferay.portal.spring.hibernate.HibernateConfiguration"
??lazy-init="true">
??<property?name="lobHandler"?ref="lobHandler"?/>
??<property?name="dataSource">
???<ref?bean="liferayDataSource"?/>
??</property>
?</bean>
?
并把原來的配置liferaySessionFactory 刪除。
在JSP中獲取資源文件內容
在Liferay jsp開發中通過LanguageUtil.get(pageContext, "search") 來獲取屬性資源;
查看日志
在Liferay調試時,可以在Admin Portlet中→“服務器”→“日志級別”中對應的Portlet或Java Class的Log級別修改為Debug級,或All,這樣就能在控制臺看到更多信息。
Liferay研究之七:網站客戶化定制
Liferay是一個優秀的Portal平臺,可以對其進行定制來實現客戶的需求。有一些定制可以通過在界面上操作,來添加用戶所需要的組件,但有些就需要從系統底層進行定制。
下面簡單羅列了一些常用的,在網站定制方面所需要的一些設置,主要是對portal.properties或者system.properties來進行修改。如果你還不了解這兩個配置文件,那么仔細通篇閱讀一遍吧,里面的文檔對讓你更好地了解Liferay很有幫助。
1、在系統安裝時,就為每個單位建立一個Community,及相應的用戶組,適合網站群的建設。
在portal.properties中
system.groups=
2、為每個Community分配可訪問成員組為所有組;
3、將系統支持的Locale精簡為en, zh
portal.properties:
locales=zh_CN,en_US
4、取消OpenID支持
open.id.auth.enabled=false
5、設定默認首頁為:
default.landing.page.path=/web/guest/home
6、設定電子郵件系統:
//TODO:
??? smtp.server.enabled=true
??? smtp.server.port=48625
??? smtp.server.subdomain=events
7、設置不顯示私有頁
在My Places Portlet地方設置,比如設置組織的頁面不顯示:
my.places.show.organization.public.sites.with.no.layouts=false
8、設置缺省用戶角色為User
admin.default.role.name=User
9、修改系統默認管理員信息
admin.email.from.address=admin@liushining.cn
admin.email.from.name也替換為/u7ba1/u7406/u5458(查找替換所有的)
需要修改對應的數據庫
10、修改默認的Web Id
company.default.web.id=liushining.cn
需要修改對應的數據庫
11、修改默認密碼為111111
修改portal.properties中##Passwords的passwords.passwordpolicytoolkit.generator=static; passwords.passwordpolicytoolkit.static=111111
12、修改初始化系統后,界面的初始Portlet,如何去掉那個HelloWorld Portlet?
修改portal.properties中##Default Guest
default.guest.layout.template.id=2_columns_ii, 將這個修改為你需要的Layout
default.guest.layout.column-1=58, //Login Portlet
default.guest.layout.column-2=47, //Hello World Portlet, 將這個修改為你需要的模板即可;
13、修改文章類別的可選項
原來默認是“一般”,“博客”,“新聞”,“新聞發表”,“測試”等等,不符合系統的需要,可以在portal.properties#Journal Portlet中定義。
journal.article.types=announcements,blogs,general,news,press-release,test
逗點分割的properties資源名與language.properties中對應。
14、設置新用戶注冊后系統顯示的Portlet類別
在Portlet.properties中## Default User設置:
## 缺省的用戶“版面”設置:
default.user.layout.template.id=2_columns_ii
## 缺省版面中,每欄中的Portlet 編號
default.user.layout.column-1=71_INSTANCE_OY0d,82,23,61,65,
default.user.layout.column-2=11,29,8,19
default.user.layout.column-3=
default.user.layout.column-4=?
Liferay研究之八:Liferay的層次劃分---持久化及服務
在Liferay學習時,剛開始可能你會不習慣Liferay的目錄結構,對里面那么多的目錄弄暈,更暈的是會有很多同名的Service, LocalService, Util類,讓你搞不清楚應該用哪個。
其實這些都很簡單,如果你有J2EE設計模式的經驗,應該不難發現,這些其實都是在分層,而分層的目的,就是以增加“復雜性”為代價,提供更加靈活的擴展性(呵呵,個人意見)。
下面就是對Liferay包結構的一個簡單總結。copyright by smilingleo
| 包 | 類或頁面 | 說明 |
| webroot/html/portlet/xxxx | xxxx.jsp, XXXAction | ?在頁面或Action中調用服務 |
| portal-service/../[portlet]/service | XXXXLocalServiceUtil | ?服務Facade封裝 |
| portal-service/../[portlet]/service | XXXXLocalService | ?服務接口 |
| portal-impl/../[portlet]/service/impl | XXXXLocalServiceImpl | ?接口實現 |
| portal-impl/../service/persistence | XXXXFinder, XXXXPersistence(Impl) | ?持久層實現 |
| util-java/../dao/hibernate | QueryPos etc. | 一些輔助類工具 |
在Liferay源碼中,分了兩個包portal/portlet,無論是在src中,還是在docroot/html中,portal主要是門戶系統框架必須的一些內核,比如處理登錄、布局、處理session、flash等等;而portlet目錄就是各個內核之外的一些可添加的Portlet資源。?
Liferay研究之九:開發技巧(2)
Liferay頁面中如何引入Tab頁?
liferay中tab1為一級tab,tabs2為二級標簽,引入時需要使用一個liferay的taglib:liferay-ui:tabs,比如:
<liferay-ui:tabs names="articles-referenced,articles-could-be-referenced" url="<%= portletURL.toString() %>"?param="tabs2" />
其中,names中為資源文件中的資源名,逗號分割,但逗號前后都不能有多余空格。
tabs2是一個String.
String tabs2 = ParamUtil.getString(request, "tabs2", "articles-referenced")
至于如何實現點擊Tab顯示不同內容,可以通過:
<c:choose>
?<c:when test='<%= tabs2.equals("articles-referenced") %>'>
?...
?</c:when>
</c:choose>
來確認。注意:此處是通過資源名articles-referenced,而不是實際的值來判斷的。?smilingleo原創
關于Liferay中的RowChecker
在Liferay中,如果要生成一個帶check box的列表table,需要使用RowChecker,通過searchContainer.setRowChecker(new RowChecker(renderResponse))來指定;
RowChecker其實就是將生成HTML <input name="your_portlet_name_rowIds" type="checkbox" ...>進行了封裝,但是需要注意,rowIds的值是每行的primaryKey,而這個PrimaryKey是通過ResultRow的構造函數來實現的。smilingleo原創
如何在Liferay中引入你自己的Javascript?
一個比較笨的方法是在html/theme/里面查找所有的portal_normal.vm,然后在<head>標簽下添加,
<script type="text/javascript" src="/html/js/jsgraphics.js"></script>
注意src需要用絕對路徑。
一個更好的辦法,具體就是在portal.properties中:javascript.files=/中引入,然后build就OK了。
Liferay研究之十:定制Portlet風格
Liferay通過Theme實現界面換膚的功能,但對于某個Portlet,我們在實際項目中可能還是想做一些適當的調整,以實現特殊的效果。
這里主要是需要熟悉CSS樣式表相關知識,比如我在界面上做了一個滾動條,想把這個滾動條放到“菜單條”右側空白區域。
對于滾動條,肯定是可以通過添加一個文章來實現的,但是如何將內容添加到合適的位置呢?
可以通過自定義Portlet的CSS來實現,比如:
#p_p_id_56_INSTANCE_W43d_ {
?Z-INDEX: 100;
?POSITION: absolute;
?left: expression(document.getElementById("banner").offsetWidth+ document.getElementById("banner").offsetLeft - document.getElementById("p_p_id_56_INSTANCE_W43d_").offsetWidth + "px");
?TOP:expression(document.getElementById("navigation").offsetTop+ "px");
?height:expression(document.getElementById("navigation").offsetHeight+ "px");
} smilingleo原創
上述代碼中在CSS中應用了表達式,可以通過表達式調用javascript,從而實現獲取界面上其他元素的精確位置。?
利用這種思路,你可以擴展每個Portlet的樣式,比如,把“搜索”挪到頁面頂端。
Liferay研究之十一:Portlet與Struts Action Path的關系引發的問題
Liferay開發中出現The struts path xxx does not belong to portlet xx. Check the definition in liferay-portlet.xml問題如何解決?
一種情況是因為在一個Portlet中引用另外一個portlet的URL導致的。比如在Configuration Portlet中想執行View Article Content的操作。
問題發生點:PortletRequestProcesser.java, line 321.
原因:你引用的portletURL struts-path與你當前的portlet struts-path不匹配,這種情況下,如果你是以guest身份登錄,而且這些資源都是對guest開放的,就不會報錯。如果你登錄了,就會因為權限問題報錯。
因為在Liferay中對權限的定義較嚴格。
比如在Journal_articles基礎上,你想做一個文章共享的Portlet 其Struts-path為journal_shared, 這與JournalArticles的journal_articles不同。所以,你在journal_shared/view.jsp就需要修改portletURL.struts_action為journal_shared/view。而不能直接拷貝journal_articles/view。
相應的,你需要在struts-conf中定義對應的配置。
Liferay研究之十二:對Liferay框架的幾點分析總結
一、JSP中如何使用Tab
Liferay的jsp頁面中,如果用了tab的選項,則tab1為第一層,tab2為第二層,tab3為第三層,從上到下,為父子關系。
如何開發一個帶選擇框的列表應用?
1、建立數據庫模型(廢話)
2、建立dao及相關portlet.service服務框架,接口和Util
3、實現服務接口;
4、開發對應的JSP頁面;
5、開發相應的Action;
6、在liferay中進行配置Portlet
這里重點說一下如何開發JSP頁面的內容。
頁面一:初始化頁面init.jsp
引入需要的包,類以及一些初始化工作;
頁面二:查詢、顯示頁面 view.jsp, search.jsp,search_contents.jsp
可以根據group, article name進行查詢,查詢結果在同一個頁面中顯示,用帶選擇框的列表。
二、內置Portlet
有一些Portlet是內置的,并沒有在liferay-display.xml中配置進行顯示,但是卻會被其他Portlet引用,比如:ID:87, Layout Configuration; ID 88, Layout Management; ID 90:, Portal; ID 92:Messaging, ID 103:Tags Compiler, ID 113:Portlet CSS?
三、如何自動生成初始化數據?
在系統中,通過一些系統變量的設置,以及VM模板的應用,可以通過DBBuilder來產生默認的系統初始化數據SQL,具體請參見DBBuilder, DBUtil.buildTemplate(), evaluateVM()的實現,以及portal-data-sample.vm
四、如何實現客戶化定制查詢?
portal-impl/classes/custom-sql/default.xml中定義自定義查詢的SQL列表,具體哪個模塊有對應的xml做定義,比如journal.xml,<sql id="查詢類的查詢方法">
五、數據結構分析
liferay中每個page(layout)的界面順序是通過layout表的priority,layoutId, parentLayoutId字段來確定的。
organization_表中存儲組織和場所,通過location=0 or 1來區分是組織還是場所;
usergroup 存儲用戶組,user group與community, organization, locations不同,僅僅是用來做實現管理的便捷性。比如,將用戶分組,然后對這個組進行分配角色,這樣所有組中的用戶就都有了該角色,在數據庫存儲中,通過usergrouprole的關聯,與users_roles并列。
group_表中存儲communities, organizations, user
role_表存儲所有角色,其中type_ = 1表示是常規角色, type_=2表示是communities角色
account_ company_ 一起保存instance中存儲的數據;
user_表和contact_表:如果是自己注冊的用戶(或者系統自帶的默認用戶),則user_.userid = contact_.contactId - 1;如果是管理員創建的 contact_.userid = 創建人id;因此從另外一個角度來說,創建用戶的步驟應該是先在user_表中創建記錄,然后再到contact_表中插入;
用戶注冊,如果指定了一個組織的話,則會在注冊時users_orgs中插入對應記錄
Liferay研究之十三:使用WebDAV
Liferay中的Document Library和Journal兩個Portlet開始支持WebDAV(關于什么是WebDAV,請見下面的介紹)。
簡單講,WebDAV是一套協議,實現Web文件夾的功能。
在文檔庫Portlet中,創建一個文件夾之后,點擊“編輯”,出現的界面中就會包含一個WebDAV的鏈接。
將這個地址復制,然后在“網上鄰居”上右鍵,映射本地驅動器,將該地址粘貼到目標地址中,之后會提示輸入用戶名及密碼。smilingleo原創http://blog.csdn.net/smilingleo
此時主要,用戶名不是Liferay的登錄名,而是其用戶ID,也就是類似10129的編號(可以在“My Profile”中獲取這個值),用ID而不用賬號的原因是:Liferay支持多個Company, 同樣的一個email可能注冊于不同的Company,這時email就不是unique的了。
“完成”之后,在網上鄰居中,你就能看到你剛才建立的文件夾了,雙擊進去看看,你會發現,基本和本地文件夾差不多。
MS的Office已經支持WebDAV,打開Word, “打開”,選擇網上鄰居,找到剛剛建立的Web Folder,找到一個包含Word文檔的目錄,你就能看到并打開里面的Word文件。
按下面介紹,Word應該可以直接編輯,并保存Web Folder中的文檔,但我測試時總是以Read Only模式打開,不能保存。哪位大俠知道,請告知如何解決?(個人猜測是WebDAV的實現引擎配置問題,沒時間多研究,先這樣吧)smilingleo原創http://blog.csdn.net/smilingleo
=============以下引用http://www.javaeye.com/topic/6568===========================
WebDAV(Web-based Distributed Authoring and Versioning)是基于 HTTP 1.1 的一個通信協議。它為 HTTP 1.1 添加了一些擴展(就是在 GET、POST、HEAD 等幾個 HTTP 標準方法以外添加了一些新的方法),使得應用程序可以直接將文件寫到 Web Server 上,并且在寫文件時候可以對文件加鎖,寫完后對文件解鎖,還可以支持對文件所做的版本控制。這個協議的出現極大地增加了 Web 作為一種創作媒體對于我們的價值。基于 WebDAV 可以實現一個功能強大的內容管理系統或者配置管理系統。
我這里不想詳細介紹 WebDAV 的協議,感興趣的可以在這里找到相關的資料:
http://www.webdav.org
其中首先應該看的是這份 WebDAV FAQ:
http://www.webdav.org/other/faq.html
WebDAV 本身是一個類似于 HTTP 的通信協議(IETF RFC 2518)。它與 HTTP 類似,需要實現服務器和客戶端兩部分軟件。目前 WebDAV 已經有了大量相關的軟件實現。
在這里是一些與 WebDAV 相關的軟件項目:
http://www.webdav.org/projects/
在這些項目中,我們最感興趣的當然是那些用 Java 實現的開源項目,Slide 是其中最重要的一個項目。Slide 是 Jakarta 項目的一個子項目(又是 Apache 山頭的),提供了一套 WebDAV 的服務器端和客戶端的開發庫和 API,目前已經出到了 2.0 版。
http://jakarta.apache.org/slide/
在這里下載最新的 Slide 2.0 的 Binary 包。
http://jakarta.apache.org/site/binindex.cgi
Slide 分成服務器端和客戶端兩部分:
服務器端:
http://apache.linuxforum.net/dist/jakarta/slide/binaries/jakarta-slide-server-bin-2.0.zip
客戶端:
http://apache.linuxforum.net/dist/jakarta/slide/binaries/jakarta-slide-webdavclient-bin-2.0.zip
我先講講服務器端如何配置:
解壓縮,假設在 D:/tmp/jakarta-slide-server-2.0 下,你會在
D:/tmp/jakarta-slide-server-2.0/slide/webapp/
下找到兩個 war 文件:
slide.war:Slide 服務器端配置,用 Servlet 實現。
slide-doc.war:Slide 文檔。
把這兩個 war 文件 copy 到你的 Web Container(Tomcat、Jetty、Resin、etc.) 的部署目錄(一般是 webapps 目錄)下,然后重新啟動 Web Container。
在我現在寫的這個文檔中服務器端的配置就是這么簡單。
再講講在客戶端如何配置。
WebDAV 有非常多的客戶端,用 Slide 客戶端的庫可以非常容易地寫出一個 WebDAV 客戶端程序。感興趣的可以看看這篇文檔:
http://www.onjava.com/lpt/a/4387
我主要講講如何用 Windows 2000/XP 自帶的 Web Folder 功能來訪問 Web 文件夾。
Windows 2000/XP 安裝后已經具備訪問基于 WebDAV 協議的 Web 文件夾的功能,而且可以把 Web 文件夾映射為一個本地文件夾,支持拖放、拷貝/粘貼等等功能,使用起來非常方便。
在 Windows 2000/XP 中添加 Web 文件夾的方法是:
打開“網上鄰居”,添加網上鄰居,在“請鍵入網上鄰居的位置”中輸入 Web 文件夾的 URL,例如我剛才用 Slide 配置好的 WebDAV 服務器在:
http://localhost:8000/slide/
然后按照向導的提示繼續做就可以了,非常的簡單。
配置好了以后你就可以把這個 Web 文件夾當作本地文件夾一樣使用了。拖幾個文件進去試試吧。關于上述 Web Folder 的配置可以參考這些文檔:
http://chapters.marssociety.org/webdav/
(幾個閑著沒事孜孜不倦地研究人類如何移民火星的酷哥寫的文檔)
還有 M$ 網站上的相關文檔:
http://www.microsoft.com/windowsxp/home/using/productdoc/en/default.asp?url=/windowsxp/home/using/productdoc/en/using_webfolders_for_file_transfer.asp
M$ 的很多產品都內置有 WebDAV 的支持。例如:Office 2000、IE 5/6、Exchange Server、Frontpage。我配置好 WebDAV 服務器后,當我訪問這個 URL
http://localhost:8000/slide/files/23.doc
時,Word 2000 可以識別出 Web 服務器支持 WebDAV 協議。于是 Word 2000 可以直接編輯服務器上的這個文檔,編輯完后可以直接保存在 Web 服務器上。這個是不是比你習慣的 download->modify->upload 要方便的多?
WebDAV 還有很多話題,比如 WebDAV 完全可以取代 FTP。WebDAV 至少在以下幾個方面對 FTP 具有壓倒性優勢:
1、FTP 需要申請操作系統帳號。WebDAV 不需要申請任何操作系統帳號,它使用一套自己定義的安全完善的身份驗證機制。
2、FTP 的所有數據(包括登錄信息)全部使用明文傳送,加密必須要自己來實現,例如:可以手工用 GPG 來做這件事,但是畢竟還是不方便。用 WebDAV 就可以使用 HTTPS 來傳輸數據,加密解密的操作完全是在低層自動完成的。
3、FTP 傳輸數據的傳輸效率比較低,每傳送一個文件需要打開一個新的 TCP 連接,而 WebDAV 傳輸所有文件只需要一個 TCP 連接。
4、FTP 不象 HTTP 那樣容易穿越防火墻,在廣域網的應用范圍比 HTTP 要小的多。而 WebDAV 因為是基于 HTTP 的,所以具有 HTTP 的所有優點。
5、FTP 客戶端工具沒有 WebDAV 客戶端工具使用方便。你剛才已經看到 WebDAV 服務器配置好后,通過 Windows 2000/XP 的 Web Folder 方式訪問 Web 文件夾就和訪問本地文件夾沒有多少區別。如果應用程序支持 WebDAV 協議(例如 Word 2000),就可以直接打開 Web 文件夾中的文件并且編輯,然后直接保存在原先的 Web 文件夾中。這個用起來簡直就和 Samba 完全一樣。你知道哪一個 FTP 客戶端使用起來有這么方便嗎?
關于 WebDAV 更多的話題,以后慢慢再說吧。?
Liferay研究之十四:子窗口向父窗口的值傳遞(字典項的實現)
描述:在文檔庫中,“Add Shortcut”會彈出一個先選擇Group,后選擇文檔的對話框;
實現機制:
在document_library/edit_file_shortcut.jsp中
| <input type="button" value="<liferay-ui:message key="select" />" onClick="var toGroupWindow = window.open('<portlet:renderURL windowState="<%= LiferayWindowState.POP_UP.toString() %>"><portlet:param name="struts_action" value="/document_library/select_group" /></portlet:renderURL>', 'toGroup', 'directories=no,height=640,location=no,menubar=no,resizable=yes,scrollbars=no,status=no,toolbar=no,width=680'); void(''); toGroupWindow.focus();" /> |
在onClick事件中,打開一個窗口,該窗口打開一個renderURL,該URL會執行對應的Action.render方法,find the right forwarding, 最終打開select_group.jsp頁面。為什么不直接打開select_group.jsp頁面呢?這樣可以用liferay的方式來進行參數傳遞,還可以指定窗口的模式;
需要注意的是使用renderURL時,引號的使用方法。一般是:在外圍用但引號,里面正常的使用雙引號, <portlet:renderURL>中的雙引號與html input中的雙引號不會沖突,因為系統會將<Portlet:renderURL>解析為對應的http://xxxxxxx字符串。
執行正確的話,上面會打開彈出窗口。
在select_xxxx.jsp頁面中,查詢到一個list之后,會通過:
?StringMaker sm = new StringMaker();
?sm.append("javascript: opener.");
?sm.append(renderResponse.getNamespace());
?sm.append("selectGroup('");
?sm.append(group.getGroupId());
?sm.append("', '");
?sm.append(UnicodeFormatter.toString(group.getName()));
?sm.append("'); window.close();");
?String rowHREF = sm.toString();
來調用父窗口中的selectXxxx JS 代碼,向父窗口傳遞一個id參數,然后將本窗口關閉。
在父窗口(edit_file_shortcut.jsp)中,聲明JS function如下:
?function <portlet:namespace />selectGroup(groupId, groupName) {
??if (document.<portlet:namespace />fm.<portlet:namespace />toGroupId.value != groupId) {
???<portlet:namespace />selectFileEntry("", "", "");
??}
??document.<portlet:namespace />fm.<portlet:namespace />toGroupId.value = groupId;
??document.<portlet:namespace />fm.<portlet:namespace />toFolderId.value = "";
??document.<portlet:namespace />fm.<portlet:namespace />toName.value = "";
??var nameEl = document.getElementById("<portlet:namespace />toGroupName");
??nameEl.innerHTML = groupName + " ";
??document.getElementById("<portlet:namespace />selectToFileEntryButton").disabled = false;
?}
這樣就完成了子窗口向父窗口的值傳遞過程。?
eray研究之十五:Liferay如何對外提供Service,以及如何調用
Liferay研究之十五:Liferay如何對外提供Service,以及如何調用
Liferay是基于SOA理念設計的,很容易通過Web Services對外提供服務接口,下面簡單介紹一下。
Liferay如何對外提供服務?
1、在service.xml中編輯,增加一個<entity name="xx" local-service="false" remote-service="true" />
2、ant build-service-xxxx (portal-impl/build.xml)
3、修改XXServiceImpl, 寫入你要對外提供的方法邏輯;
4、ant build-service-xxxx (重復2)
5、ant build-wsdd-xxxx in portal-impl/build.xml
6、ant clean deploy in portal-impl/build.xml
這樣你就成功發布以了一個服務,在tunnel-web/doc-root/WEB-INF/server-config.wsdd 中查找是否發布成功
?如何調用Liferay發布的服務?smilingleo原創
1、新建一個項目(或者打開你要調用服務的項目)
2、將Apache AXIS的所有lib文件拷貝到<your-webapp>/WEB-INF/lib下面;
3、將portal-client.jar拷貝到上述目錄,如果沒有,在portal-client/build.xml中ant build-client(注意,這時,服務器要在開啟狀態,而且上面編寫的服務已經成功deploy到服務器)
4、編寫代碼,例如;
import?com.company.portal.service.http.MyUserServiceSoap;
import?com.company.portal.service.http.MyUserServiceSoapServiceLocator;
public?class?LiferayClient?...{
?public?static?void?main(String?[]?args)?...{
??long?userId?=?54321L;
??long?companyId?=?12345L;
??String?email?=?"me@company.com";
??String?password?=?"notTellingYou";
??
??try?...{
???MyUserServiceSoapServiceLocator?locator?=?new?MyUserServiceSoapServiceLocator();
???MyUserServiceSoap?soap?=?locator.getPortal_MyUserService(_getURL(Long.toString(userId),?"Portal_MyUserService"));
???int?isAuthenticated?=?soap.authenticateByEmailAddress(companyId,?email,?password);
???System.out.println("is?user?authenticated??"?+?isAuthenticated);
??}?catch?(Exception?e)?...{
???System.err.println(e.toString());
??}
??
?}
?private?static?URL?_getURL(String?remoteUser,?String?serviceName)?throws?Exception?...{
??String?password?=?"secret";
??url?=?"http://"?+?remoteUser?+?":"?+?password?+?"@localhost:8080/tunnel-web/secure/axis/"?+?serviceName;
??return?new?URL(url);
?}
}
Liferay研究之十六:FCKeditor如何插入服務器上的資源?
1、點擊FCKeditor上的插入圖片時,從地址欄中知道,是訪問的brower.html
2、brower.html使用了框架。左側使用frmFolder.html,主工作區使用frmresourceslist.html。
在brower中調用了fckxml.js,這是一個AJAX的封裝,用來向服務器發送Command.
3、服務器端通過portal-impl/com.liferay.portal.editor.fckeditor.**來響應。
具體來說:GetFoldersAndFilesCommand.execute 會通過工廠方式來產生一個CommandReceiver,共有三類Receiver, ImageCommandReceiver, DocumentCommandReceiver, PageCommandReceiver. 也就是說,可以插入三類資源,圖片,文檔,頁面鏈接。
【注意】liferay 4.3.3時,ImageCommandReceiver, DocumentCommandRecievier的_getFolder方法有Bug,需要在開始時,將folderName進行UTF-8編碼轉換,否則不支持中文字符。smilingleo原創
4、在服務器端向客戶端返回相應之后,客戶端通過CallBack函數來進行內容處理,比如frmfolders.html中的GetFoldersCallBack,就是打開選中的Folder.
Liferay遇到的兩個問題
環境一:機器上安裝了兩個liferay系統,4..3.3和4.3.5, 只運行4.3.3?。剛開始啟動時,一切正常運行,一段時間之后CPU變為重負載,其中java引擎占用了99%。
網上查點資料,說可能與select 語句中的limit有關,或者與資源釋放有關,或者說與日志文件大小有關,都不是正解。
---------------------------------------------
找到問題了:
因為Liferay在運行時,會在user.home目錄下建立一個liferay目錄,存放全文檢索的索引,以及Document_library中存儲的文件,多個版本的Liferay運行時,會造成這些索引與dl的混亂。因而導致一訪問Docuemnt_Library Portlet,就會使java.exe進程占用的內容越來越大,直到JVM自動回收,這樣一個生產,一個回收,就造成CPU的空轉。
解決辦法很簡單,就把user.home目錄下的liferay目錄刪除,然后將數據庫中的Document_library數據也刪除,然后重新啟動liferay,并re-index就好了。
---------------------------------------------
?
環境二:安裝了多個liferay系統(5,6個),最近運行系統時,點擊登錄之后,系統無反應,刷新后能進入系統(顯示已經登錄),不刷新則一直顯示為正在登錄的狀態。
可能原因:
1、多個liferay引起cookie問題;
2、liferay運行時自動創建的deploy, eheache, lucence, 等文件夾有沖突(刪除后還不行,基本排除)
3、用jprofiler查看,可能是FriendURL或者HibernateSessionFactory有關,因為這幾個部分占用CPU時間特別的長,但還沒有找到具體原因。
---------------------------------------------------
第二個問題找到原因了。主要是在portal.properties中配置出現沖突了。
默認用戶角色設為User,將Power User去掉;
然后又將my.places.show.user.private.sites.with.no.layouts=false,這樣,而在Default Landing Page設置中,又將缺省登錄頁設到了private site。
呵呵,這樣就出問題了,不讓人家看到private site,又將default landing page設為private site,當然登錄時,系統就會迷惑了,“到底讓我去哪里???”,呵呵。
------------------------------------------------------------------
如果哪位有類似經歷,請共同探討一下。
發現一個國內Liferay開發的站點
http://jctj.sxinfo.net
山西省科技基礎條件平臺。(還是俺們老家的,嘿嘿)
這個站點給我的第一影響是網站風格、界面布局設計的不錯。
因為不能注冊,不能從后端分析其結構及設計。只能從網頁源代碼中發現一些端倪。
1、可能自己開發了一個“新聞”的portlet;
2、界面上大量的用了文章來構成這個首頁。?
3、通過樣式表將navigation的那個下拉菜單隱藏起來。
4、通過CAS-Web實現了單點登錄
雖然用到Liferay的部分比較少,但應該說已經做的不錯了。起碼比liferay.cn強(客觀評價,雖然liferay.cn在國內為推廣liferay做出不可磨滅的功績)。
以上僅代表個人觀點,如有出入,請多見諒。
期待發現更多國內用liferay開發的站點。
Liferay研究之十七:由Velocity模板的國際化問題引出的“大秘密”
事由:
想在每個layout(page)增加一個統一的Footer。這時,就需要修改portal-normal.vm。
對于復雜的,可以參考Liferay默認的一個Theme liferay-noir-theme的實現方式,通過:
$serviceLocator.findService("com.liferay.portlet.journal.service.JournalArticleLocalService"))
獲取一個文章的服務,并通過liferay-look-and-feel.xml中的文章編號配置,來顯示一篇文章;
對于簡單的,我們只是寫幾個文字就可以了,比如說,聯系我們,copyright等等。
如果直接在portal-normal.vm中增加文字,英文還可以,但如果增加中文,那么就會出現亂碼,需要通過Liferay的國際化機制來實現。
具體的辦法是:
$languageUtil.get($locale, "msgKey")
這里的msgKey就是你的資源文件中的鍵值。
再深入分析一下,liferay是如何實現$languageUtil.get調用的呢?
首先,查找得知,languageUtil是在VelocityVariables中定義的, 而在VelocityFilter中,將所有的這些變量寫入到HttpRequest中。
VelocityFilter在哪里定義?當然是web.xml中了。
有一個問題是:在VelocityVariables中,定義的theme是一個Theme對象,但是在vm中,$theme卻好像是VelocityTaglib對象,其方法匹配不上,比如runtime方法。
答案可以在ThemeUtil.includeVM中找到, 在該方法中,確實是用VelocityTaglib的一個對象,替換了原來的theme屬性值。
打破砂鍋問到底,什么地方調用ThemeUtil.includeVM? 這個就牽扯出更深層的“秘密”:liferay是如何實現Portlet的。
WrapPortletTag調用了ThemeUtil.includeVM,而這個Tag是由<liferay-theme:wrap-portlet >來觸發的。哪里調用liferay-theme:wrap-portlet ? Portlet.jsp!
閱讀portal-web/doc-root/html/common/theme/portlet.jsp中,可以知道很多事情,比如如何顯示一個Portlet?如何調用tile中的頁面等等。具體的以后再詳細分析。
另外,在系統的init.jsp中,有一個<liferay-theme:defineObjects/>標簽,該標簽對應的DefineObjectsTag就是將很多liferay的變量放到pageContext。?
Liferay研究之十八:Page Rendering
上一篇文章中提出了一個問題:在liferay中什么時候,什么地方/c變成/c/portal/layout。
晚上加班,順便查了查資料,終于找到了問題的答案。
在http://wiki.liferay.com/index.php/Page_Rendering?明確的給出了答案。下面簡單重復一下:
1、請求由MainServlet進行處理。一些屬性被存儲到了session和request中。WebKeys.CURRENT_URL保存了當前請求的路徑。
2、ServicePreAction被調用,決定調用那個layout, theme進行顯示。當前使用的layout被存儲在WebKeys.LAYOUT的request屬性中。其他可用的layouts存儲在request的WebKeys.LAYOUTS屬性中。Theme在WebKeys.THEME中,顏色在WebKeys.COLOR_SCHEME中。
3、調用Struts?來處理請求。liferay中使用com.liferay.portal.struts.PortalRequestProcessor。這個類的getLastPath方法,就會返回<protocol>://<hostName>:<port>/portal/layout,p_l_id是一個可選參數。
4、/portal/layout請求就可以通過struts_config的定義來調用LayoutAction執行。http://www.smilingleo.cn
5、其他部分,不在重復了。自己看代碼或看別人的資料吧。
Liferay研究之十九:ServiceBuilder的一個Bug
如果在liferay的某個portlet目錄下的service.xml中進行修改,比如想讓不同的Company的JournalArticle能共享,則需要編寫一個relationship class,以及相關的Entity類。
因為liferay是MDA的,所以,只需要修改對應的service.xml,然后通過ant build-service-portlet-xxxx即可生成對應的代碼,這里不再細說,請參見我以前的文章。
http://www.smilingleo.cn/web/guest/3?p_p_id=62_INSTANCE_94No&p_p_action=0&p_p_state=maximized&p_p_mode=view&_62_INSTANCE_94No_struts_action=%2Fjournal_articles%2Fview&_62_INSTANCE_94No_keywords=&_62_INSTANCE_94No_advancedSearch=false&_62_INSTANCE_94No_andOperator=true&_62_INSTANCE_94No_groupId=16725&_62_INSTANCE_94No_searchArticleId=&_62_INSTANCE_94No_version=1.0&_62_INSTANCE_94No_name=&_62_INSTANCE_94No_description=&_62_INSTANCE_94No_content=&_62_INSTANCE_94No_type=Portal&_62_INSTANCE_94No_structureId=&_62_INSTANCE_94No_templateId=&_62_INSTANCE_94No_status=approved&_62_INSTANCE_94No_articleId=16886
?這里要說的是ServiceBuilder有一個Bug,就是在做數據庫字段設計的時候,有一些bad field需要做轉換,但是卻轉換的不完全。
修改如下:
ServiceBuilder 4709行Bug,需要判斷是否badField,添加下面代碼:
?String _pkVarName = pkVarName;
?if (_badCmpFields.contains(pkVarName)) {
??_pkVarName += "_";
?}
然后將下面出現的pkVarName替換為_pkVarName。
Entity.java line:156, getPkVarName()方法中,也需要判斷主鍵是否是badField
???String pkVarName = col.getName();
???if (ServiceBuilder.getBadCmpFields().contains(pkVarName)){
????pkVarName += "_";
???}
???return pkVarName;
另外:
默認build.xml中build-service-portlet-xxx,執行的是lib/portal-impl.jar中的ServiceBuilder.class,如果修改了ServiceBuilder源碼,還需要先通過ant jar將修改打包到jar中,然后再執行。
Liferay研究之二十:如何防止連續重復提交
一般的解決連續重復提交的辦法有以下幾種。
方法一、提交后 禁用提交按鈕(大部分人都是這樣做的)
方法二、使用Session, session里面加令牌,第一次設置一個值,以后請求先與這個令牌進行比較;
方法三、數據處理成功馬上Redirect到另外一個頁面
Liferay中前臺使用了客戶端腳本(可能是JQuery,沒仔細研究)、Session,后臺采用同步、多線程等來解決這個問題。服務端解決的具體思路如下:
在Session中放一個DoubleClickControlor的實例,然后對這個實例進行同步,來判斷是否重復提交請求。
如果重復提交請求,則判斷哪個是firstRequest,哪個不是,不是的話就以DoubleClickControlor的實例為同步依據,進入一個等待狀態,直到firstRequest執行完,調用notifyAll方法,激活第二次請求。
在DoubleClickFilter中,controller.control(httpReq, httpRes, chain);第二次請求會沒有任何異常結束,這樣會執行ok = true; 進而在控制臺打印出阻止一次重復提交等信息。
另外,補充溫習一個FilterChain的知識點。
向服務器發起一個請求時,在訪問所請求的資源之前,會先通過Filter Mapping配置來匹配有哪些Filter需要被執行。所有的Filter根據filter-mapping定義的順序形成一個FilterChain,依次進行調用。
這個調用有點類似于遞歸,在調用到chain.doFilter()時,程序執行主線會跳轉到下一個Filter的doFilter方法中,直到最后一個Filter, 最后一個filter執行chain.doFilter時不執行操作,也不會跳轉到其他Filter,會將chain.doFilter之后的代碼執行完,退出doFilter方法,然后執行倒數第二個Filter的chain.doFilter后面的代碼,依此類推。
Liferay中通過system.properties來設定開啟或關閉哪個Filter.
Liferay研究之廿一:Liferay集成Jbpm
網上有很多這方面的資料,不過大部分都出自一個人的文章,而且應該大部分人是做不成功的。參考了Tyler Zhou的資料。簡單測試了一下,成功。by smilingleo
http://tyler-zhou.javaeye.com/blog/163928
?
liferay集成了jbpm工作流,他是可以先擇通過兩種組件來調用JBPM的,servicemix和mule,根據Liferay的官方建議,最好選擇mule.
1.Liferay官方網站下載liferay-portal-jbpm-web-4.3.2.war,liferay-portal-mule-web-4.3.2.war. 重命名為jbpm-web.war,mule-web.war 。(我測試時,用的Liferay版本是5.0, jbpm-web和mule是4.4.2, 沒問題)
2.兩個包都放到/webapps下面,liferay啟動后會自動的解壓,或者用WINRAR也可以解壓。
3.webapps/ROOT/WEB-INF/classes/portal-ext.properties增加jbi.workflow.url=http://localhost:8080/mule-web/workflow注意端口號要和自已的相同。
4.如果要修改端口號那么還要修改webapps/mule-web/WEB-INF/mule-config.xml下
<property name="url" value="http://localhost:8080/jbpm-web/servlet" />端口號;
5.webapps/jbpm-web/WEB-INF/classes/hibernate.cfg.xml,先擇自已用到的數據庫類型并配置, webapps/jbpm-web/WEB-INF/sql下面有各種數據庫腳本,選擇自已的數據庫。by smilingleo
6. 用下面的測試流程定義進行測試。
?
<?xml?version="1.0"?encoding="UTF-8"?><process-definition?xmlns="urn:jbpm.org:jpdl-3.1"?name="Example?Process">
????<start-state?name="start">
????????<task>?
????????????<assignment?class="com.liferay.jbpm.handler.IdentityAssignmentHandler"?>
????????????????<companyId>liferay.com</companyId>
????????????????<type>user</type>
????????????????<name>test@liferay.com</name>
????????????</assignment>
????????????<controller>
????????????????<variable?name="text:color"?/>?
????????????????<variable?name="text:size"?/>
????????????</controller>
????????</task>
????????<transition?name="to_t"?to="t"/>
????</start-state>
????<task-node?name="t">
????????<task?name="t"?>
????????????<controller?>?
????????????????<variable?name="text:color"?access="read"?/>
????????????????<variable?name="text:size"??access="read"/>
????????????</controller>
????????????<assignment?class="com.liferay.jbpm.handler.IdentityAssignmentHandler"?>
????????????????<companyId>liferay.com</companyId>
????????????????<type>user</type>
????????????????<name>test@liferay.com</name>?
????????????</assignment>
????????</task>
????????<transition?name="to_end"?to='end'?/>
????</task-node>
????<end-state?name="end"></end-state>
</process-definition>
?
?
7. 在definitions中就可以看到定義的工作流了。點擊后面的“Add instance”就可以創建一個工作流實例。
分類: Portal 開源框架 2008-04-29 23:52410人閱讀 評論(2) 收藏舉報今天有網友問到關于Liferay的updateXxxx(entity)方法比較怪異的問題,比如UserPersistanceImpl.update(user, false).
光看源碼,好像根本就沒有執行save or saveOrUpdate或者merge等Hibernate的方法,那又是如何保存的呢?
分析了一下,做了一個簡單的research,發現還真是很有意思的。
http://www.liferay.com/web/guest/community/forums/-/message_boards/message/569279
其中Ray提到:smilingleo原創, www.smilingleo.cn
Not true! We use the parameterless call everywhere in our core
services... and the reason is DOES work is because the object IS in the
hibernate session and flushing the changes is handled by hibernate...
using merge() basically tell hibernate NOT TO WAIT for it's natural
Flush to be invoked. merge() forces a cache flush...
也就是說,只要執行一個flush()方法就可以實現物理層與內存中的同步了,而不再需要更加耗費資源的merge方法了。smilingleo原創,www.smilingleo.cn
此外,還涉及到一些Spring的事務的知識,可以查閱一下《Spring in Action》第5章內容,或者網上一些文章,比如:
http://blog.csdn.net/luying777/archive/2008/04/29/2344557.aspx
http://itfuture.javaeye.com/blog/182201
這里面還真的很有研究的必要的。
在Liferay中,一般對一個entry進行update操作時,都是通過XXXLocalServiceUtil.getEntry(entryId)獲取到entry的實例,這個時候entry是persistent的。所以,在更新時不再需要saveOrUpdate或者merge,通過flush就可以實現更新了。
提高liferay性能
轉載自:http://www.liferay.com/web/wanpeng/blog/-/blogs/777189 提高liferay性能 By Gavin Wan, On 5/7/08 6:28 AM似乎總能聽到liferay性能的抱怨, 在一個開源軟件中這其實不難理解,就想一臺公用的電腦,有人用oracle就在上面裝個oracle,有人用DB2就又裝了一個DB2…… 這些服務都啟動自然就會慢。我們要做的就是停掉一些用不到的功能和方便開發人員debug的一些選項。
- 啟動速度相關參數:
在不需要自動創建數據庫結構的時候可以停掉此項:
| # ? # Set this to true to automatically create tables and populate with default ? # data if the database is empty. ? # ? schema.run.enabled=false |
????? 在不需要重新建立luence索引時
| ##Lucene Search #Set the following to true if you want to index your entire library of files on startup. index.on.startup=false |
?
- 提高運行時性能
????? 開啟layout cache
| # The layout cache filter will cache pages to speed up page rendering for # guest users. See ehcache.xml to modify the cache expiration time to live. com.liferay.portal.servlet.filters.layoutcache.LayoutCacheFilter=true |
?
???? 壓縮javascript文件
| # Set this property to true to load the combined JavaScript files from the # property "javascript.files" into one compacted file for faster loading for # production. Set this property to false for easier debugging for # development. You can also disable fast loading by setting the URL # parameter "js_fast_load" to "0". javascript.fast.load=true |
?
???? 壓縮 css 文件
| # Set this property to true to load the theme's merged CSS files for faster # loading for production. Set this property to false for easier debugging # for development. You can also disable fast loading by setting the URL # parameter "css_fast_load" to "0". theme.css.fast.load=true |
?
??? 開啟vm模板cache
| ?? # Set this to true in production so that VM templates are cached ? velocity.engine.resource.manager.cache.enabled=true |
?
還有一些像auto.deploy.enabled這樣的屬性在不需要的時候也可以關閉,減少系統掃描的開銷。
Liferay研究之廿三:JSP中可直接引用的Liferay對象
init.jsp中通過<liferay-theme:defineObjects /> 將這些對象注入到pageContext中。
然后在頁面中就可以直接引用了,這些對象包括:
- themeDisplay - com.liferay.portal.theme.ThemeDisplay
- company - com.liferay.portal.model.Company
- account - com.liferay.portal.model.Account (deprecated)
- user - com.liferay.portal.model.User
- realUser - com.liferay.portal.model.User
- contact - com.liferay.portal.model.Contact
- layout - com.liferay.portal.model.Layout
- layouts - List<com.liferay.portal.model.Layout>
- plid - java.lang.Long
- layoutTypePortlet - com.liferay.portal.model.LayoutTypePortlet
- portletGroupId - java.lang.Long
- permissionChecker - com.liferay.portal.security.permission.PermissionChecker
- locale - java.util.Locale
- timeZone - java.util.TimeZone
- theme - com.liferay.portal.model.Theme
- colorScheme - com.liferay.portal.model.ColorScheme
- portletDisplay - com.liferay.portal.theme.PortletDisplay
全國哀悼日期間將網站風格變為黑白
使用Liferay做的網站,做這個修改很簡單。在頁面設置設置風格與樣式的地方,有一個CSS標簽,在其中添加下面代碼:
html { filter:progid:DXImageTransform.Microsoft.BasicImage(grayscale=1); }
保存即可。
為災區做力所能及的事情吧。
Liferay研究之廿四:如何實現配置模式
Liferay的Portlet Configuration頁面,都有幾個固定的Tab頁,比如“權限”,“導出導入”等,這些是在什么地方定義的呢?
我們知道,portal/render_portlet.jsp負責界面上一個portlet的解析功能,包括這個portlet再編輯模式下的最大、最小、配置、外觀等MODE的實現。
仔細分析一下這個頁面,點擊一個portlet的configuration按鈕時,執行的邏輯如下:
if (Validator.isNotNull(portlet.getConfigurationActionClass())) {urlConfiguration.setParameter("struts_action", "/portlet_configuration/edit_configuration"); } else {urlConfiguration.setParameter("struts_action", "/portlet_configuration/edit_permissions"); }
就是如果再portlet的配置信息中(Liferay-portlet.xml)中包含了configuration的信息,那么就執行edit_configuration action, 也就是再配置頁面中包含客戶化的portlet配置信息的頁面(configuration.jsp),具體如何實現,我們稍后分析。如果沒有客戶化的信息需要定制,那么,我們就可以通過一個標準的配置實現edit_permissions來實現。這里面只有權限、導入導出等通用的設置功能。
再Portlet Configuration這個Portlet下,如何調用其他Portlet的Configuration.jsp, 這個就需要查看EditConfigurationAction里定義了。代碼如下:
try {portlet = getPortlet(req); } catch (PrincipalException pe) { }ConfigurationAction configurationAction = getConfigurationAction(portlet);if (configurationAction != null) {configurationAction.processAction(config, req, res); }
看到了吧,這里首先獲取是對哪個Portlet進行Config的,獲取其ConfigurationAction(統一的Interface),然后調用其processAction方法。
而在顯示jsp頁面時,根據定義執行了edit_configuration.jsp,分析其源碼可以知道,這個頁面引入了tab1.jsp, tab2.jsp兩個頁面。
<liferay-util:include page="/html/portlet/portlet_configuration/tabs1.jsp">
?<liferay-util:param name="tabs1" value="setup" />
</liferay-util:include>
默認的tab選項是setup頁,也就是你編寫的configuration.jsp所在的那個tab。在tab2.jsp中對這個Tab進行了詳細的定義:
// Configuration PortletURL configurationURL = renderResponse.createRenderURL();configurationURL.setWindowState(WindowState.MAXIMIZED);configurationURL.setParameter("struts_action", "/portlet_configuration/edit_configuration"); configurationURL.setParameter("redirect", redirect); configurationURL.setParameter("returnToFullPageURL", returnToFullPageURL); configurationURL.setParameter("portletResource", portletResource); configurationURL.setParameter("previewWidth", previewWidth);
注意:上面的portletResource就是你自己的Portlet的portletId,通過這個portletId才能讓EditConfigurationAction獲知這個被配置的Portlet的ConfigurationActionClass.
?<liferay-ui:tabs
?names="current,archived"
?param="tabs2"
?url0="<%= configurationURL.toString() %>"
?url1="<%= archivedSetupsURL.toString() %>"
/>
Liferay研究之廿五:緩存技術的使用
?
緩存是一個良好設計架構的必須元素,因為使用具有通用目的的架構機制,勢必會造成一些計算的冗余,造成性能的降低,通過緩存機制,就可以彌補這方面的問題。
Liferay中大量的使用了緩存機制,其核心都是MultiVMPool機制,但在具體使用上有兩種方式。
方式一:
使用FinderCache類,具體用法:
1、放入緩存 FinderCache.putResult(classNameCacheEnable, className, methodName, params, args, result),其中params是參數類型,Args是參數值,兩者需要進行匹配;
2、從緩存中獲取 FinderCache.getResult(className, methodName, params, args)。
方式二:
使用各個應用自己的緩存機制。比如在CalEvent中,參考CalEventLocalUtil,需要注意的是,這個Util方法是在service.impl目錄中的,也就是說是需要自己編寫的。
這些代碼都是對MultiVMPoolUtil進行了封裝,具體操作步驟如下:
1、放入緩存 MultiVMPoolUtil.put(cache, key, object), 其中cache是一個PortalCache類型的對象,PortalCache只是一個接口,具體的實現需要根據不同的緩存機制來編寫實現,比如Ehcache.
2、從緩存中讀取 MultiVMPoolUtil.get(cache, key)
具體請參見Liferay源碼
Liferay研究之廿六:5.1更新分析
1、Portlet Container Implement.
Add sun container implement for furture. JSR268?
?
2、Theme
Add a shortcut icon for a theme。
A flag to decorate portlets by default.
?
3、Spring
remove active-mq from spring.
?
4、Hibernate
reconstruct the package for hibernate, from "spring" to "dao.orm"
?
5. Javascript
A great improvement.
Changed to jQuery UI.
?
6. Users
New function that record the user's update in last login.
?
7. Look and Feel
You can specify the default layout template, for instance, all new layout can be set to 1-column template.
?
8. Permissions
refactor the permissions system, remove PermissionCheckerImpl and add AdvancedPermissionChecker
add a new user check algorithm, based on role.
?
9. Captcha
more flexible.
you can determin which action should use captcha and which don't need.
?
10. Default Layouts
More powerful improvement.
you can customize the new group's layouts setting, the new version supports multiple layout from a lar file.
guest public layouts / user public layouts / user private layouts.
?
11. Layouts
add a new portlet type, Panel.It's very useful. it's a container for any portlet, you need choose portlets? in "manage pages", which will be displayed in the panel.(have bug when using dl upload.)
static portlet can be added according to friendly url.
?
12. Upload Servlet Request
more flexible
?
13. Journal Portlet
new journal transformer listener, can filter regex words and replace them.
Liferay研究之廿七:一些有用的API分析
?
- com.liferay.portal.util.PropsUtil
在Portal.properties中設置的每個屬性,都有一個類的屬性與之對應,這個類就是:PropsUtil,每個屬性都是靜態的。
對應的還有一個類是:PropsValues可以直接獲取屬性對應的值。
如果想擴展portal.properties,在其中加上自己的值,那么就需要修改這兩個類。
?
- com.liferay.portal.util.WebAppPool.java
描述這樣一個數據結構<companyId, key, value>
用來存儲所有Company可以使用的所有應用列表。
WebAppPool.get(companyId, WebKeys.PORTLET_CATEGORY)就是獲取某個company的應用分類;
獲取之后先緩存,如果后來有了新的變化,還可以新舊一起merge。
- com.liferay.portal.util.PortalInstances.java
用來描述Portal上所有的實例,也就是與服務器管理中相關的那些,每個Company會有一個portal instance。
- com.liferay.portal.util.Portal.java, PortalImpl.java
描述了一個Portal Server, 通過這個類,在Portal加載后可以獲知服務器的相關信息,比如portal的lib路徑,Server Name,系統角色,系統組,組織,保留參數關鍵字等等。
(通過分析該類,還知道了,系統可以不部署在/ROOT目錄下面,可以部署在任何路徑下,只需對應的改變portal.properites中的portal.ctx屬性即可)
該類不但具有名詞屬性,還具有動詞屬性,比如renderPortlet方法。具體的邏輯是:在獲取了portlet對象,及其位置屬性(columnPos, columnId)之后,將其放到request中,然后include render_portlet.jsp來顯示一個Portlet.
- com.liferay.portal.model.Portlet.java, model.impl.PortletImpl.java
描述了一個Portlet的所有屬性。其值的設置都來源于liferay-portlet.xml中的定義,比如這個portlet是否是system portlet(用戶不能操作)
- com.liferay.portal.velocity.VelocityVariables.java
所有Velocity模板template中引用的對象變量,均在此處進行定義。
并且,該類定義了一個特殊的變量$init,這個變量對應一個公用的init.vm,也就是_unstyled/template/init.vm,這個模板中定義了各種Velocity的變量(比如是否顯示my_place),類似init.jsp,被其他所有的模板來引用,比如在portal_normal.vm中,第一行一般就是:#parse($init)
一個例外:在template中$theme并不是在VelocityVariables中定義的Theme類型,而是通過ThemeUtil.includeVM,替換為了VelocityTagLib對象。
- com.liferay.portal.servlet.taglib.portlet.DefineObjectsTagUtil.java
根據pageContext可以獲取Portal相關的變量有:
lifecycle = (String)req.getAttribute(PortletRequest.LIFECYCLE_PHASE); 有幾個可能的值:
PortletRequest.ACTION_PHASE
PortletRequest.EVENT_PHASE
PortletRequest.RENDER_PHASE
PortletRequest.RESOURCE_PHASE
portletConfig = (PortletConfigImpl)req.getAttribute(JavaConstants.JAVAX_PORTLET_CONFIG);
portletRequest = (PortletRequest)req.getAttribute(JavaConstants.JAVAX_PORTLET_REQUEST); 在不同階段其在Request中的屬性名也不同,分別是:
actionRequest, eventRequest, renderRequest, resourceRequest
portletPreferences = (PortletPreferences)pageContext.getAttribute("portletPreferences");
portletSession = (PortletSession)pageContext.setAttribute("portletSession");
portletResponse = (PortletResponse)pageContext.setAttribute(portletResponseAttrName); 其中portletResponseAttrName在不同的Lifecycle階段值也不同,分別是:
actionResponse, eventResponse, renderResponse, resourceResponse
- com.liferay.portal.model.LayoutTypePortlet.java, model.impl.LayoutTypePortletImpl.java
生成portlet instance id的方法:portletId + getFullInstanceSeparator()
處理typeSettings的方法:先通過LayoutTypeImpl調用Layout.getTypeSettingsProperties(),將數據庫中存儲的typeSettings內容轉換為Properties類型。然后就可以根據LayoutTypePortletImpl中所定義的各個屬性來取typeSettings的值了。
setLayoutTemplateId(long userId, String newLayoutTemplateId, boolean checkPermission)的邏輯
重新設定一個layout的template時,可能涉及到column的變化,以及再oldTempate column上的portlet位置的變化。
因此,重新設定一個templateId之后,還需要更新其上面的portlet. reorganizePortlets(newColumns, oldColumns);
重新設定的邏輯就是:如果新模板中的列數比舊模板的少,就將舊模板中多出來的列中的portlets全部放到新模板的最后一列。
- com.liferay.portlet.layoutconfiguration.util.xml.RuntimeLogic, (PortletLogic, ActionURLLogic [ RenderURLLogic])
PortletLogic:
在文章中可以動態的嵌入Portlet.具體的做法是,在文章編輯中,源代碼中,插入下面代碼:
<runtime-portlet name="3" instance="" queryString="" />
具體參數說明:name→Portlet的ID,可以參考liferay-custom.xml中定義;instance→如果portlet是可以instancable的,那么這里需要指明其instanceId的后4位,如果不清楚最好到數據庫中進行查找一下。比如從portletPreferences表中;queryString是附帶的參數。
這樣,文章中就可以動態的嵌入一個查詢了。
ActionURLLogic:
<runtime-action-url portlet-name="" param-name-1="" param-value-1="" param-name-2="" param-value-2="" .... />
RenderURLLogic:
<runtime-render-url portlet-name="" param-name-1="" param-value-1="" param-name-2="" param-value-2="" .... />
- com.liferay.portal.kernal.search.Indexer
該接口有兩個方法:
DocumentSummary getDocumentSummary(Document, PortletURL)
void reindex(String[] ids)
DocumentSummary是一個簡要檢索結果的描述,也就是標題是什么,什么內容,鏈接到什么地方;
getDocumentSummary的過程,也就是從Lucene Document中獲取title, content以及entityId三個對應的域內容,并構造DocumentSummary, 其中entityId用來構造顯示該document的鏈接地址,一般是調用一個renderURL。
要想開發自己的Indexer,那么首先需要繼承kernal.search.Indexer,實現上面的兩個方法。
并添加addEntity, updateEntity, deleteEntity等方法。用來實現對全文檢索內容的增加,更新及刪除。
以構造過程為例,主要就是先新建一個lucene Document對象,并插入不同keywords的content(類似一個Map),然后用LuceneUtil.getWriter(companyId)獲取IndexerWriter, 將doc寫如索引。
- com.liferay.portal.theme.ThemeDisplay
這個類描述了界面顯示的絕大部分資源;
包括:layout, group, user, theme,colorScheme, locale, language, path, logo, URL, showIcon等等;
Liferay研究之廿八:為同類Portlet設置不同的ICON
根據Portlet的定義,每個Portlet都是會有一個icon的。
?
但是,一些Portlet是可以instancable的,這樣如果一個頁面上放置了多個同一Portlet的實例的化,頁面上都是同樣的圖標,都是同樣的背景就會比較難看。
?
用下面的技巧,可以解決這個問題。
?
可以通過自定義外觀來實現相同PortletID的instance的不同樣式。
?
在Portlet的“外觀與風格”按鈕對話框中→高級樣式,為本Portlet設置樣式,比如:
#portlet-wrapper-28 .portlet-topper{
???? background:#ffDDff url(http://mail.sohu.com/images/pic21.gif) no-repeat scroll left center;
}
?
#portlet-wrapper-28 .portlet-title{
??? padding-left:30px;
}
?
這樣,就會有一個不一樣的Portlet樣式出來了。
Liferay研究之廿九:Liferay5.2基礎架構變動
前幾天Liferay正式發布了5.2, 抽空Down下來研究了一天,感覺還是有不少變化的,很多底層的東西都發生了變化。因為現在重點關注于MDD的研究,所以這次研究重點在基礎架構(liferay infrastructure)方面,其他方面略有涉及,簡單總結如下:http://www.smilingleo.cn原創,轉載請標明出處。
一、環境變動
- 將更多的配置內容放到了portal.properties中,比如JDBC連接。
- Spring升級到了2.5, 大量采用了AOP, 比如將原來的liferayDataSource, liferayTransactionManager等等都用transactionAdvice代替;
- 簡化了portal-spring.xml的配置方式原來采用的.impl, .transaction, 等統一由.impl實現,并且支持Entity不使用liferayDataSource, liferaySessionFactory了。
- 并且除了portal.properties中定義的需要read-only事務的方法之外,其他方法都應該是required的了。
- 另外,不再使用JNDI連接數據源,連接池采用c3p0(這樣數據庫關閉再打開就不會有無法連接的問題了),并采用區分readDataSource和writeDataSource的方式來增加系統的擴展性(雖然缺省的實現還是相同的一個數據源,但是可以自己擴展了,很棒的一個改進)。
二、?ServiceBuilder相關變動
- 對于Spring事務配置,增加了對只讀方法的自定義。可以在portal.properties中定義什么前綴的方法名是read-only事務;
- persistence_impl.ftl
增加了一個BatchSession的手工控制,在portal.properties中定義了一個Session中批量操作的條數(默認是20個),原來的控制應該是交給Hibernate缺省實現的,這里通過BatchSessionCounter等實現了精確控制;
- model_impl.ftl
添加了Expando的支持。
關于Expando, 就是解決所謂動態字段問題的一個技術。這個方案并不真正的修改原來的表,去增加一個字段,而是采用用四個表結構來存儲所有其他表的擴展字段及值的一種方法。這四個表是:ExpandoTable, ExpandoColumn, ExpandoValue, ExpandoRow, 共同模擬:表、字段、行、一行中的某字段值。
和Counter等一樣,Expando也是以ServiceBuilder自動構建的方式創建的,有自己的模型定義文件,所以使用方式上和其他的Service沒有兩樣。
- service_base_impl.ftl
取消了InitializingBean;使用了Annotation, BeanReference,用來指向某個Spring中的managed bean
三、Web層
- 修改了Search-Container標簽,更加容易使用了。
- 使用了CacheFilter, 提高了通訊性能;
四、應用
- 控制面板:將管理相關的Portlet全部放到Control Panel中。
- Journal Portlet 改名為Web Content. Journal Structure的定義行類型時增加了很多選項,可以訪問Document Library中的文件,還可以跳轉到某一個page.
- 權限管理:由于在5.x中采用了基于角色的權限系統,5.2里面可以在相對集中的界面中對Portal, Portlet給用戶或者角色、用戶組授權了。(感覺還是達不到實際應用的需求。)
Liferay研究之卅:5.2中通過SharePoint協議與MS Office整合
Liferay5.2 可以與MS Office集成,實現了SharePoint協議。
從Word中“打開”輸入http://localhost:8080/sharepoint 就可以訪問到你的DL(中間需要輸入用戶名密碼),然后就可以像打開本地文檔一樣找到你要編輯的文檔。
至于實現,那就是在web.xml中有一個sharepoint/*的ServletMapping,用來解析sharepoint的協議。
編輯之后,保存,會自動更新到DL中,新建一個版本。
很不錯,只是現在打開的速度有點慢。(暫時不知道什么原因)
另外,在幾個人同時編輯的時候的沖突、鎖定等問題好像也還沒有方案。
?
====6.19更新=====
關于文檔鎖定
需要先在頁面中,文檔的Action選項中“編輯”,然后有一個按鈕,“鎖定”。這樣,再通過上面的方式打開這個文檔,就會提示以只讀方式打開,如果不以只讀,選通知,將在文檔被解鎖時,通知Excel以“讀寫”的方式打開文檔。
?
關于文檔的權限
在Liferay中設定的用戶權限可作用與這些文檔(JCR),如果用戶無權更新文檔,則保存時,將提示“沒有保存成功”。
?
關于速度
另外,如果通過“從我的桌面訪問”拷貝WebDAV URL,然后再到office中打開URL,速度會很快,也很方便。
Liferay研究之卅一:Database Sharding(數據庫分片)
Database Sharding是什么?
?
其實就是一種分布式計算,通過業務邏輯將不同的分類的數據保存到不同的數據庫(具有相同的表結構)中。簡單的說是一種負載均衡技術,因為每個表中的數據少了,查詢速度就快了,系統能承受的負載也就大了。很多大公司都在用這種技術,比如Google, Facebook, Wikipedia等等。
?
Liferay的實現策略是什么?
Liferay實現的是根據Portal Instance來進行分布的,也就是說,不同的portal instance的數據,保存在不同的數據庫中。同一個portal instance的數據,只在一個數據庫中。當然,一些全局的數據,比如ClassName,Company, Counter等數據是保存在一個節點(default)上的。
Liferay中的配置方式?
1、首先你的系統上要有多個Portal Instances
比如babaodi.com, liushining.cn, smilingleo.cn等
2、安裝多個數據庫(也可以是一個服務器上的多個實例),可以是異構的,通過create_minimal的sql腳本創建最小數據庫。
3、在portal.properties中將shard-data-source-spring.xml的注釋去掉。并添加對應的配置內容,比如:
view plaincopy to clipboardprint?
- jdbc.default.driverClassName=com.mysql.jdbc.Driver??
- jdbc.default.url=jdbc:mysql://localhost/lportal?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false ??
- jdbc.default.username=??
- jdbc.default.password=??
- jdbc.one.driverClassName=com.mysql.jdbc.Driver??
- jdbc.one.url=jdbc:mysql://localhost/lportal1?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false ??
- jdbc.one.username=??
- jdbc.one.password=??
- jdbc.two.driverClassName=com.mysql.jdbc.Driver??
- jdbc.two.url=jdbc:mysql://localhost/lportal2?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false ??
- jdbc.two.username=??
- jdbc.two.password=??
- shard.available.names=default,one,two??
- <aop:config>??
- ?<aop:aspect?ref="com.liferay.portal.dao.shard.ShardAdvice">??
- ??<aop:around?pointcut="bean(com.liferay.portal.service.AccountLocalService.impl)"?method="invokeAccountService"?/>??
- ??<aop:around?pointcut="bean(com.liferay.portal.service.CompanyLocalService.impl)"?method="invokeCompanyService"?/>??
- ??<aop:around?pointcut="bean(com.liferay.portal.service.UserLocalService.impl)"?method="invokeUserService"?/>??
- ??<aop:around?pointcut="bean(*Persistence.impl)?or?bean(*Finder.impl)"?method="invokePersistence"?/>??
- ??<aop:around?pointcut="execution(void?com.liferay.portal.convert.messaging.ConvertProcessMessageListener.receive(..))"?method="invokeGlobally"?/>??
- ??<aop:around?pointcut="execution(void?com.liferay.portal.events.StartupHelper.*(..))"?method="invokeGlobally"?/>??
- ?</aop:aspect>??
- </aop:config>??
jdbc.default.driverClassName=com.mysql.jdbc.Driver jdbc.default.url=jdbc:mysql://localhost/lportal?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false jdbc.default.username= jdbc.default.password= jdbc.one.driverClassName=com.mysql.jdbc.Driver jdbc.one.url=jdbc:mysql://localhost/lportal1?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false jdbc.one.username= jdbc.one.password= jdbc.two.driverClassName=com.mysql.jdbc.Driver jdbc.two.url=jdbc:mysql://localhost/lportal2?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false jdbc.two.username= jdbc.two.password= shard.available.names=default,one,two
?
這樣,你的系統就可以將不同的portal instance的數據保存到不同的數據庫中了。
?
實現原理
?
具體的實現Liferay采用了AOP, 在shard-data-source-spring.xml中定義了一個ShardAdvice,
<aop:config><aop:aspect ref="com.liferay.portal.dao.shard.ShardAdvice"><aop:around pointcut="bean(com.liferay.portal.service.AccountLocalService.impl)" method="invokeAccountService" /><aop:around pointcut="bean(com.liferay.portal.service.CompanyLocalService.impl)" method="invokeCompanyService" /><aop:around pointcut="bean(com.liferay.portal.service.UserLocalService.impl)" method="invokeUserService" /><aop:around pointcut="bean(*Persistence.impl) or bean(*Finder.impl)" method="invokePersistence" /><aop:around pointcut="execution(void com.liferay.portal.convert.messaging.ConvertProcessMessageListener.receive(..))" method="invokeGlobally" /><aop:around pointcut="execution(void com.liferay.portal.events.StartupHelper.*(..))" method="invokeGlobally" /></aop:aspect></aop:config>
可以看出,所有持久化方法都調用invokePersistence方法,而該方法中負責通過instances % shards的簡單hash算法獲取一個數據源(被保持在ThreadLocal中)。
?
替代技術
Database Sharding是與業務相關的分布式數據處理技術,不夠通用,而且多個數據源就存在多個單點問題。
一個比較好的通用解決方案是C-JDBC(Sequoia),把硬件RAID的概念演化為軟件的Raidb, 即解決了分布式運算的問題,又可以通過RAIDb1的方式解決單點問題。
當然,C-JDBC是一種沒錢買“磁盤陣列”的廉價替換方案,如果你已經有了陣列,那么就不需要這個技術了。(選購服務器等基礎設施時,就需要考慮好用什么樣的軟件技術,否則會浪費錢,很多錢。)
Liferay研究之卅二: ext 開發環境下遇到java.lang.VerifyError問題
以前一直都是直接修改源碼的, 沒怎么用過ext方式, 不過這種最底層的方式非常不利于liferay版本升級,因此決定用ext環境(因為要修改liferay core,所以不能用plugins SDK).
今天忽然出現一個問題,運行時報了個java.lang.VerifyError, 看JDK Doc原文是:
Thrown when the "verifier" detects that a class file, though well formed, contains some sort of internal inconsistency or security problem.?
也就是說檢測到class文件, 格式良好,但存在內部不一致或安全問題.?
安全問題應該談不上了,因為沒有任何涉及到安全的東西, 內部不一致是個什么概念??
查了下資料, 有一種可能是javac版本不一致的問題, a版本編譯的class, 在b版本下運行, class格式可能會不一致. 一般出現在高本版編譯,低版本運行. 但我的情況中是1.5編譯, 1.6運行, 應該排除;
另外一種可能是jar地獄問題, 也就是class loader的問題, 初始化時tomcat classloader 加載了一個類, 運行時webapp classloader優先又加載了一個同名類(具體參見Tomcat文檔) .檢查了一下, 原來在ext環境中, 我override了一個liferay core java文件, 將這個類放到了ext-impl-src目錄下了, 而ext-impl目錄在deploy時發布到webapps/.../WEB-INF/lib下,又webapp classloader加載, 但是這個同名Class因為在portal-kernel.jar中也存在, 也就是說在Tomcat classloader也加載了一個. 因此出現jar地獄問題.
將目錄移動到ext-service/src中, 重新deploy, 運行, 搞定!
Liferay研究卅三:多歷法Calendar
經過多天的折騰,終于將Liferay Calendar Portlet打造成一個完美支持多種歷法(目前界面上支持GregorianCalendar和ChineseCalendar)的一個組件了。
折騰的初衷是因為目前所有的日歷組件中,很多都有定期提示功能,但是都只是在GregorianCalendar,也就是我們用的公歷上,比如每年幾月幾日。而我們國內還有很多事情是用農歷的,比如好友的生日,很多人的生日都是只過農歷生日。這樣就給惦記他/她的人造成很大的麻煩,經常記不住今年生日到底是哪天(目前只見過一個叫掌上萬年歷的軟件支持農歷生日提醒)。還有其他周期的農歷重復事件就更難以實現了,比如,每個農歷的初一的提醒等等。
在當前這個網站的Calendar組件中也支持農歷了,但是支持的很不好,只能實現每年定期,而不能實現其他的重復條件。
偶爾間發現了ibm的一個開源組件,ICU (International Components for Unicode),這是一個實現國際化的基礎工具包,其中就包含關于各種歷法的支持。而且更爽的是其API設計是完全按照JDK Calendar的命名來的,也就是說JDK Calendar有的api, ICU都有,類名也相同。通過繼承實現了不同歷法的支持。
(這里不得不感慨一下,這種國際大公司的競爭力確實不是我們一些本土企業所能比擬的,他們在很多領域遇到的問題是我們沒有遇到過的,而且他們多積累的這些基礎代碼,以及里面所相關的知識更是很難在短時間內能獲得的。雖然我們可以通過各種開源工具來彌補與這些巨頭之間的差距,但是,對于這些開源工具的掌握本身就不是一個容易的事情。另外,外國公司對于我們中國的農歷的知識積累絲毫不遜于我們國內一些研究機構,看看里面一些關于ChineseCalendar相關的論文就知道了,不得不讓我們再次汗顏啊)
我的工作正是基于IBM ICU來實現的。雖說已經是站在巨人的肩膀上了,但是很多東西還是沒有那么順利。主要就是歷法轉換,比較,時區等等問題。
如果說要列選一下編程中比較難搞的算法,與時間相關的肯定不在少數(是不是因為人們只生活在一個三維空間,對于四維的思考能力就很差?)。尤其是涉及到時間轉換,比較的,如果再加上時區轉換就更加要命了。
Liferay Calendar的核心算法在Recurrence中,里面有一個isInRecurrence,用來判斷一個日期是否是在重復周期上的算法邏輯。該算法能滿足iCalendar標準的所有重復方式,按天,月,年,不同frequency, interval, duration, until等等,其算法描述如下:
?
| 1. 先統一為某個TimeZone; 2. 如果current為歷史時間,返回false 3. 如果Current正好在startDate到startDate + Duration之間,返回真(還在持續期); 否則: [按最小步頻向startDate靠攏,如果最終日期落在startDate ~ Duration之間(或者符合按頻率的日期,比如每周五,但startDate不是周五), 返回真,否則返回假;] 4. 根據current計算下一個可能的日期candidate ??? 4.1. 獲取一個最小interval (遞減的步頻) ??? 4.2. 將秒/分/時撥回到與startDate一樣; ??? 4.3. 按照minInterval 向回滾動,返回滾動后日期 5. 如果candidate是重復日期,返回真, 否則: ??? 5.1. candidate超過(晚于)until, 返回假; ??? 5.2. interval間隔不匹配,或者重復次數已經超出,返回假; ??? 5.3. 如果不是任何一個重復規則,返回假; ??? 5.4. 否則返回真 6. 將candidate向回一秒,值作為新的candidate,如果candidate早于startDate,則返回假; 7. 重復5 |
?
這個只是算法的框架,還有很多細節就只能參考源碼來了解了。
原有的代碼只是支持兩個日期都是GregorianCalendar, 并不支持其他的歷法。采用ICU之后,就可以比較方便地實現其他歷法了,只要將兩個要比較的日期轉換為相同的歷法。
關于日期的比較,需要滿足兩個前提:1)相同歷法, 2)相同TimeZone.? 在北京的時間與紐約時間比較是沒有意義的。
而TimeZone的問題,就比較復雜了,需要看看Calendar源碼才能搞清楚。下面把我總結的一些經驗羅列一下:
1)Calendar 對象只要創建之后,其timeInMillies屬性就是確定的,這個屬性是一個long值,記錄了到1970.1.1(好像是)的毫秒差。該屬性值與時區無關。這里用了類似信息與展現分離的設計思想,信息就是不變的timeInMillies,展現就是各種不同的歷法,不同的時區。
2)不同時區通過ZONE_OFFSET值與timeInMillies值相加,可以計算calendar對象在當前時區的相對時間,比如年,月,日,小時、分、秒。
3)Calendar的set(field, value)方法只進行對應field的設置,不計算由于這個field變化引起其他field的變化,只有在下一次get操作時,才會進行計算。
4)有兩種需求會用到setTimeZone(zone):時區轉換和日歷比較,時區轉換只需要設置為目標時區的TimeZone就好了,而日歷比較則還需要將ZONE_OFFSET, DST_OFFSET等field清空。否則計算其他field時,這兩個fields會參與運算,得出你不想要的值出來。
這個工作是網站改版的一部分,不久大家就能在我的網站 上用上這個功能了,呵呵,敬請期待。
?
?
總結
以上是生活随笔為你收集整理的Liferay研究-smilingleo的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ldap - 设置一个基本的OpenLD
- 下一篇: 数据库中的DML,DCL,DDL分别是那