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/xxxxxxxx.jsp, XXXAction 在页面或Action中调用服务
portal-service/../[portlet]/serviceXXXXLocalServiceUtil 服务Facade封装
portal-service/../[portlet]/serviceXXXXLocalService 服务接口
portal-impl/../[portlet]/service/implXXXXLocalServiceImpl 接口实现
portal-impl/../service/persistenceXXXXFinder, XXXXPersistence(Impl) 持久层实现
util-java/../dao/hibernateQueryPos 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 + "&nbsp;";

  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  java.net.URL;

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 开源框架 410人阅读 评论(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性能

似乎总能听到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按钮时,执行的逻辑如下:

  1. if (Validator.isNotNull(portlet.getConfigurationActionClass())) {  
  2.  urlConfiguration.setParameter("struts_action""/portlet_configuration/edit_configuration");  
  3. }  
  4. else {  
  5.  urlConfiguration.setParameter("struts_action""/portlet_configuration/edit_permissions");  
  6. }  


就是如果再portlet的配置信息中(Liferay-portlet.xml)中包含了configuration的信息,那么就执行edit_configuration action, 也就是再配置页面中包含客户化的portlet配置信息的页面(configuration.jsp),具体如何实现,我们稍后分析。如果没有客户化的信息需要定制,那么,我们就可以通过一个标准的配置实现edit_permissions来实现。这里面只有权限、导入导出等通用的设置功能。

再Portlet Configuration这个Portlet下,如何调用其他Portlet的Configuration.jsp, 这个就需要查看EditConfigurationAction里定义了。代码如下:

  1. try {  
  2.  portlet = getPortlet(req);  
  3. }  
  4. catch (PrincipalException pe) {  
  5. }  
  6.   
  7. ConfigurationAction configurationAction = getConfigurationAction(portlet);  
  8.   
  9. if (configurationAction != null) {  
  10.  configurationAction.processAction(config, req, res);  
  11. }  


看到了吧,这里首先获取是对哪个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进行了详细的定义:

  1. // Configuration   
  2. PortletURL configurationURL = renderResponse.createRenderURL();  
  3.   
  4. configurationURL.setWindowState(WindowState.MAXIMIZED);  
  5.   
  6. configurationURL.setParameter("struts_action""/portlet_configuration/edit_configuration");  
  7. configurationURL.setParameter("redirect", redirect);  
  8. configurationURL.setParameter("returnToFullPageURL", returnToFullPageURL);  
  9. configurationURL.setParameter("portletResource", portletResource);  
  10. 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的注释去掉。并添加对应的配置内容,比如:

    1. jdbc.default.driverClassName=com.mysql.jdbc.Driver  
    2. jdbc.default.url=jdbc:mysql://localhost/lportal?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false   
    3. jdbc.default.username=  
    4. jdbc.default.password=  
    5. jdbc.one.driverClassName=com.mysql.jdbc.Driver  
    6. jdbc.one.url=jdbc:mysql://localhost/lportal1?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false   
    7. jdbc.one.username=  
    8. jdbc.one.password=  
    9. jdbc.two.driverClassName=com.mysql.jdbc.Driver  
    10. jdbc.two.url=jdbc:mysql://localhost/lportal2?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false   
    11. jdbc.two.username=  
    12. jdbc.two.password=  
    13. shard.available.names=default,one,two  

     

    这样,你的系统就可以将不同的portal instance的数据保存到不同的数据库中了。

     

    实现原理

     

    具体的实现Liferay采用了AOP, 在shard-data-source-spring.xml中定义了一个ShardAdvice,

    1. <aop:config>  
    2.  <aop:aspect ref="com.liferay.portal.dao.shard.ShardAdvice">  
    3.   <aop:around pointcut="bean(com.liferay.portal.service.AccountLocalService.impl)" method="invokeAccountService" />  
    4.   <aop:around pointcut="bean(com.liferay.portal.service.CompanyLocalService.impl)" method="invokeCompanyService" />  
    5.   <aop:around pointcut="bean(com.liferay.portal.service.UserLocalService.impl)" method="invokeUserService" />  
    6.   <aop:around pointcut="bean(*Persistence.impl) or bean(*Finder.impl)" method="invokePersistence" />  
    7.   <aop:around pointcut="execution(void com.liferay.portal.convert.messaging.ConvertProcessMessageListener.receive(..))" method="invokeGlobally" />  
    8.   <aop:around pointcut="execution(void com.liferay.portal.events.StartupHelper.*(..))" method="invokeGlobally" />  
    9.  </aop:aspect>  
    10. </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会参与运算,得出你不想要的值出来。

    这个工作是网站改版的一部分,不久大家就能在我的网站 上用上这个功能了,呵呵,敬请期待。


     

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值