2014年10月16日 星期四

Linux cnetOS 6.5 上Liferay Tomcat 使用Load Balancing 與 cluster

參考來源
http://www.liferay.com/zh/community/wiki/-/wiki/Main/JBoss-Tomcat-Liferay+portal+Clustering+-+what+and+how

  • 下載所需檔案
先下載 apr-1.5.1.tar.gz   apr-util-1.5.4.tar.gz   pcre-8.34.tar.gz   httpd-2.4.10
這三個檔案解壓縮,
安裝順序為
apr-1.5.1
apr-util-1.5.4
pcre-8.34
httpd-2.4.10

apr-1.5.1安裝 , cd到解壓後的dir
[root@localhost apr-1.5.1]# ./configure


下載安裝gcc組件。centos中安裝如下:

1、在centos 用yum install gcc 也很方便
2、不過先要用yum install yum-fastestmirror更新下源,不然太慢了

apr-util-1.5.4 cd安裝, cd到解壓後的dir 安裝時需要依賴 apr-1.5.1
[root@localhost apr-util-1.5.4]# ./configure –prefix=/root/Downloads/apr-util-1.5.1 –with-apr=/root/Downloads/apr-1.5.1

pcre-8.34 安裝, cd到解壓後的dir
[root@localhost pcre-8.34]# ./configure

httpd安裝, cd到解壓後的dir 安裝時需要依賴另外兩個檔案
[root@localhost apr-util-1.5.4]# ./configure --prefix=/root/Downloads/httpd-2.4.10 --enable-so --with-apr=/root/Downloads/apr-1.5.1 --with-apr-util=/root/Downloads/apr-util-1.5.4 --with-pcre=/root/Downloads/pcre-8.34/pcre-config


以上安裝好後,輸入
[root@localhost apr-util-1.5.4]# make
[root@localhost apr-util-1.5.4]# make install,即可完成安裝,打開localhost能否看到it works?


  • 安裝mod_jk.so
  • 方法1
連接Apache、Tomcat,安裝mod_jk.so,下載最新版tomcat-connectors-1.2.37-src.tar.gz
1、下載tomcat-connectors-1.2.37-src.tar.gz
2、解壓縮tomcat-connectors-1.2.37-src.tar.gz,cd到native文件夾,提示:
[root@localhost native]# chmod 755 buildconf.sh
[root@localhost native]#./buildconf.sh
buildconf: checking installation...
buildconf: autoconf not found.
You need autoconf version 2.59 or newer installed
to build mod_jk from SVN.
[root@sony619 native]# yum install autoconf

執行[root@sony619 native]#./buildconf.sh 再次提示
buildconf: libtool not found.
  You need libtool version 1.4 or newer installed
  to build mod_jk from SVN.

解决:

[root@sony619 native]#./yum install libtool
结束後,到apache-2.0下面找mod_jk.so文件,同時到apachemodules下面找應該會有,如果没有直接複製過去。


  • 方法2
1. 檢查 apxs 有没有安裝。"/usr/sbin/apxs"
2. 如果没有的話,先安裝apxs
[root@localhost ~]# cd /etc/
[root@localhost ~]# vi yum.conf
如果有關於 apache or httpd 的 "exclude"這樣一行,把它註解掉;如果没有,就直接退出就行
儲存並退出
[root@localhost ~]# yum install apr-util-devel
[root@localhost ~]# yum install httpd-devel
做完這幾步以後,就應該有 "/usr/sbin/apxs" 這個文件了。


3. 編譯 mod_jk.so
下載最新mod_jk源文件:http://tomcat.apache.org/download-connectors.cgi
[root@localhost ~]# tar –xvf tomcat-connectors-1.2.37-src.tar.gz
[root@localhost ~]# cd tomcat-connectors-1.2.37-src/native/
[root@localhost ~]# ./buildconfig.sh
[root@localhost ~]# ./configure --with-apxs=/usr/sbin/apxs
[root@localhost ~]# make

4. 編譯完畢以後就有mod_jk.so文件了,路徑是 tomcat-connectors-1.2.37-src/native/apache-2.0/mod_jk.so

5. cd tomcat-connectors-1.2.37-src/native/apache-2.0/

6. cp mod_jk.so /usr/local/apache/modules 【如果modules没有mod_jk.so的話,直接複製過去】

  • 配置mod_jk
  • workers.properties

mod_jk安裝完成後,apache-mod_jk-tomcat三個連接起来。
下面把mod_jk配置進去。

/etc/httpd文件中添加workers.properties文件,内容如下:

worker.list=balancer,stat  

  
worker.node1.type=ajp13  
worker.node1.port=8109                (TomcatAJP13port)  
worker.node1.host=localhost         (到時候這可以改成domainName IP)  
worker.node1.lbfactor=1                (數值越高取得請求比例越高)

worker.node2.type=ajp13  
worker.node2.port=8209 
worker.node2.host=localhost  
worker.node2.lbfactor=1

worker.balancer.type=lb                                           ( lb = Load Balancing)  
worker.balancer.balance_workers=node1,node2     (指派分擔請求的Tomcat)
worker.balancer.sticky_session=1                            (允許session 複製)
worker.stat.type=status 





  • httpd.conf

/etc/httpd/httpd.conf文件中加入:

LoadModule    jk_module  modules/mod_jk.so  
  
JkWorkersFile                   /etc/httpd/workers.properties         ( works.properties檔案的路徑)
JkLogFile                              logs/mod_jk.log                         (產生LOG的檔案路徑)
JkLogLevel                          emerg  
JkLogStampFormat           "[%a %b %d %H:%M:%S %Y] "  
JkOptions                             +ForwardKeySize +ForwardURICompat -ForwardDirectories  
JkRequestLogFormat            "%w %V %T"  
JkMount     /status    stat  
JkMount    /*  balancer

<Proxy balancer://myCluster>
     BalancerMember ajp://localhost:8109 loadfactor=10 route=node1
     BalancerMember ajp://localhost:8209 loadfactor=10 route=node2
     ProxySet lbmethod=byrequests
</Proxy>
<Location / >
     ProxyPass balancer://myCluster/ stickysession=JSESSIONID nofailover=Off
</Location>

<Location /balancer-manager>
    SetHandler balancer-manager
    Order Deny,Allow
    Allow from localhost
    Deny from all
</Location>






  • 配置Tomcat

可參考來源


  • server.xml



$TOMCAT_AS_DIR/conf/server.xml的檔案替換掉上面附圖標示那段,換成以下字段。

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
channelSendOptions="6">

<Manager className="org.apache.catalina.ha.session.BackupManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"
mapSendOptions="6"/>

<Channel className="org.apache.catalina.tribes.group.GroupChannel">
<Membership className="org.apache.catalina.tribes.membership.McastService"
address="228.0.0.4"
port="45564"
frequency="500"
dropTime="3000"/>

<Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
address="auto"
port="5000"
selectorTimeout="100"
maxThreads="6"/>

<Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
<Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
</Sender>

<Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>

<Interceptor className="org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor"/>

</Channel>

<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/>

<ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
</Cluster>


加入 jvmRoute=”node1”的參數; 識別Tomcat的代號
<Engine defaultHost=”localhost” name=”Catalina” jvmRoute=”node1”>






  • context.xml

$TOMCAT_AS_DIR/conf/context.xml的檔案下, 修改 <Context> <Context distributable="true">

  • portal-ext.properties
 接著在portal-ext.properties 加入以下兩行參數

cluster.link.enabled=true          (啟用Liferay的複製數據庫緩存和搜索索引)
cluster.link.autodetect.address=228.0.0.4:45564     (告訴Liferay的用於多播通信什麼網絡接口)
lucene.replicate.write=true       (Lucene來跨節點複製索引)

dl.store.file.system.root.dir=/data/liferay/cluster_document_library (使用文件系統存儲文檔和圖片)
web.server.display.node=true  (顯示Web Server的節點)





2014年9月3日 星期三

SearchContainer另一種寫法。

會這樣寫的用意是可以自由讓  header 放在想要的地方,只要在config裡面做設定就好了。


config.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@include file="/html/init.jsp"%>

<%

String tabs2 = ParamUtil.getString(request, "tabs2", "display-settings");
String redirect = ParamUtil.getString(request, "redirect");

%>

<liferay-portlet:renderURL portletConfiguration="true" var="portletURL">
<portlet:param name="tabs2" value="<%= tabs2 %>" />
<portlet:param name="redirect" value="<%= redirect %>" />
</liferay-portlet:renderURL>

<liferay-portlet:actionURL portletConfiguration="true"
var="configurationURL" />

<aui:form action="<%=configurationURL%>" method="post" name="fm" onSubmit='<%= "event.preventDefault(); " + renderResponse.getNamespace() + "saveConfiguration();" %>'>

<aui:input name="<%=Constants.CMD%>" type="hidden" value="<%=Constants.UPDATE%>" />
<aui:input name="tabs2" type="hidden" value="<%= tabs2 %>" />
<aui:input name="redirect" type="hidden" value="<%= redirect %>" />

<%
String tabs2Names = "display-settings,rss";
%>

<liferay-ui:tabs
names="<%= tabs2Names %>"
param="tabs2"
url="<%= portletURL %>"
/>

<c:choose>
<c:when test='<%= tabs2.equals("display-settings") %>'>
<aui:input name="preferences--newsColumns--" type="hidden"/>

<liferay-ui:panel-container extended="<%= true %>" id="newsSettingsPanelContainer" persistState="<%= true %>">
<liferay-ui:panel collapsible="<%= true %>" extended="<%= true %>" id="newsEntriesListingPanel" persistState="<%= true %>" title="新聞列表欄位顯示">
<aui:fieldset>
<aui:field-wrapper label="顯示的欄位">

<%
Set<String> availableNewsColumns = SetUtil.fromArray(StringUtil.split(allNewsColumns));

// Left list

List leftList = new ArrayList();

for (String newsColumn : newsColumns) {
leftList.add(new KeyValuePair(newsColumn, LanguageUtil.get(pageContext, newsColumn)));
}

// Right list

List rightList = new ArrayList();

Arrays.sort(newsColumns);

for (String entryColumn : availableNewsColumns) {
if (Arrays.binarySearch(newsColumns, entryColumn) < 0) {
rightList.add(new KeyValuePair(entryColumn, LanguageUtil.get(pageContext, entryColumn)));
}
}

rightList = ListUtil.sort(rightList, new KeyValuePairComparator(false, true));
%>

<liferay-ui:input-move-boxes
leftBoxName="currentEntryColumns"
leftList="<%= leftList %>"
leftReorder="true"
leftTitle="目前的"
rightBoxName="availableEntryColumns"
rightList="<%= rightList %>"
rightTitle="可用的"
/>
</aui:field-wrapper>
</aui:fieldset>
</liferay-ui:panel>
</liferay-ui:panel-container>
</c:when>

<c:when test='<%= tabs2.equals("rss") %>'>
<liferay-ui:rss-settings
delta="<%= rssDelta %>"
displayStyle="<%= rssDisplayStyle %>"
enabled="<%= enableRSS %>"
feedType="<%= rssFeedType %>"/>
</c:when>
</c:choose>
<aui:button type="submit" />
</aui:form>





<aui:script>
Liferay.provide(
window,
'<portlet:namespace />saveConfiguration',
function() {
<c:choose>
<c:when test='<%= tabs2.equals("display-settings") %>'>
document.<portlet:namespace />fm.<portlet:namespace />newsColumns.value = Liferay.Util.listSelect(document.<portlet:namespace />fm.<portlet:namespace />currentEntryColumns);
</c:when>
</c:choose>

submitForm(document.<portlet:namespace />fm);
},
['liferay-util-list-fields']
);
</aui:script>



然後在init.jsp裡面加入config需要的表頭設定

//searchContainer;
String defaultNewsColumns = "title,category,status,priority,action";
String allNewsColumns = defaultNewsColumns + ",createDate,userName,viewCount";
String[] newsColumns = StringUtil.split(PrefsParamUtil.getString(portletPreferences, request, "newsColumns", defaultNewsColumns));



再來這是list.jsp , SearchContainer不用tag的方式 , 改用JAVA的寫法。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
 
<%@ include file="/html/init.jsp"%>


<%
PortletURL listURL = renderResponse.createRenderURL();
listURL.setParameter("jspPage", "/html/news/latestnews_list.jsp");
listURL.setWindowState(WindowState.NORMAL);


String searchDateId = "searchDateId";
int listCount = 0 ;

ThemeDisplay display = (ThemeDisplay) request
.getAttribute(WebKeys.THEME_DISPLAY);


RowChecker rowChecker = null;

if (permissionChecker.isGroupAdmin(display.getScopeGroupId())
|| permissionChecker.isOmniadmin()) {
rowChecker = new RowChecker(renderResponse);
}

ResourceURL rssURL = liferayPortletResponse.createResourceURL();
//rssURL.setParameter("p_l_id", String.valueOf(plid));


SearchContainer searchContainer = new SearchContainer(liferayPortletRequest,
null, null, "news",
SearchContainer.DEFAULT_DELTA,
SearchContainer.DEFAULT_DELTA,
listURL,
null, null);



List<String> headerNames = new ArrayList<String>();

for (String headerName : newsColumns) {
headerNames.add(headerName);
}

searchContainer.setRowChecker(rowChecker);
searchContainer.setHeaderNames(headerNames);

List results = null;
int total = 0;

results = NewsLocalServiceUtil.findByTop();
total = results.size();

searchContainer.setResults(results);
searchContainer.setTotal(total);
List resultRows = searchContainer.getResultRows();

for(int i = 0 ; i < results.size(); i++){
News news = (News)results.get(i);
ResultRow row = new ResultRow(news, news.getPrimaryKey(), i);
PortletURL rowURL = liferayPortletResponse.createRenderURL();


rowURL.setParameter("jspPage", "/html/news/latestnews_view.jsp");  
rowURL.setParameter("redirect", currentURL);
rowURL.setParameter("newsId", String.valueOf(news.getNewsId()));

for (Object column : searchContainer.getHeaderNames()) {
String columnName = (String)column;


if (columnName.equals("action")) {
row.addJSP("/html/news/latestnews_action.jsp");
}

if (columnName.equals("createDate")) {
row.addDate(news.getCreateDate());
}

if (columnName.equals("title")) {
//TextSearchEntry fileEntryTitleSearchEntry = new TextSearchEntry();
//fileEntryTitleSearchEntry.setName(news.getTitle());
//row.addSearchEntry(fileEntryTitleSearchEntry);
row.addText(StringUtil.shorten(news.getTitle(), 100), rowURL);
}

if (columnName.equals("category")) {
row.addText(news.getCategory());
}

if (columnName.equals("viewCount")) {
row.addText(String.valueOf(news.getViewCount()));
}

if (columnName.equals("status")) {
row.addStatus(news.getStatus(),news.getStatusByUserId(),news.getStatusDate());
}
if (columnName.equals("userName")) {
row.addText(news.getUserName());
}
if (columnName.equals("priority")) {
row.addText(String.valueOf(news.getPriority()));
}
}
resultRows.add(row);

}

%>

<portlet:renderURL var="createNewURL">
<portlet:param name="backURL" value="<%=currentURL%>" />
<portlet:param name="<%=NewsConstants.CMD%>" value="<%=NewsConstants.ADD%>" />
<portlet:param name="jspPage" value="/html/news/latestnews_edit.jsp" />
</portlet:renderURL>

<portlet:renderURL var="categoryMgtURL" windowState="<%=LiferayWindowState.MAXIMIZED.toString() %>">
<portlet:param name="backURL" value="<%=currentURL %>" />
<portlet:param name="jspPage" value="/html/news/category/category_mgt.jsp" />
</portlet:renderURL>

<portlet:renderURL var="viewPermissionMgtURL" windowState="<%=LiferayWindowState.MAXIMIZED.toString() %>">
<portlet:param name="backURL" value="<%=currentURL %>" />
<portlet:param name="jspPage" value="/html/news/viewpermission/vp_list.jsp" />
</portlet:renderURL>

<portlet:actionURL name="deleteList" var="dellistURL"/>

<portlet:actionURL name="searchNews" var="searchURL"/>


<aui:form action="<%=searchURL%>" method="post">
<%--
<aui:input type="hidden" name="redirect" value="<%=currentURL %>"/>
 --%>

<aui:nav-bar>
<aui:nav>
<aui:nav-item href="${categoryMgtURL}" label="新聞類別管理" title="新聞類別管理"/>
<aui:nav-item href="${viewPermissionMgtURL}" label="檢視權限管理" title="檢視權限管理" name="addAnnouncementButton" />
</aui:nav>
<aui:nav-bar-search cssClass="pull-right">
<div class="form-search">
類別
<aui:select name="" label="" inlineField="true" inlineLabel="left" style="width:100px;">
</aui:select>
時間區間
<aui:input id="<%=searchDateId %>" name="startDate" value="" label="" inlineField="true" style="width:70px;"/>
~
<aui:input id="<%=searchDateId %>" name="endDate" value="" label="" inlineField="true" style="width:70px;"/>
<liferay-ui:input-search autoFocus="<%= windowState.equals(WindowState.MAXIMIZED) %>" id="keywords1" name="keywords" placeholder='<%=LanguageUtil.get(locale, "keywords")%>' title="關鍵字"/>

</div>
</aui:nav-bar-search>
</aui:nav-bar>
</aui:form>

<a class="btn btn-info btn-large" href="<%=createNewURL %>">
  <i class="icon-plus-sign icon-large"></i>新增新聞
  </a>

<br>

<div style="float: right;" >
  <liferay-ui:rss
delta="<%= rssDelta %>"
displayStyle="<%= rssDisplayStyle %>"
feedType="<%= rssFeedType %>"
resourceURL="<%= rssURL %>" />
</div>


<liferay-ui:search-iterator paginate="<%= false %>" searchContainer="<%= searchContainer %>" />





<aui:script>
AUI().use('aui-datepicker', function(A) {
new A.DatePicker({
trigger: '#<portlet:namespace />searchDateId',
popover: {zIndex: 1 },
calendar: {
dates: ['10/01/2014'],
dateFormat: '%Y/%m/%d'
},
on: {
          selectionChange: function(event) {
            console.log(event.newSelection)
          }
        }
});
});
</aui:script>











2014年9月2日 星期二

客製Portlet 產生RSS Feed的做法

環境Liferay6.2.1 + tomcat-7.0.42 + JAVA 7
使用postgreSQL 9版


本例以新聞發布為例。
先在 liferay-plugin-package.properties
加入 jdom.jar , dom4j.jar , rome.jar  這三個jar檔主要是用來產生RSS Feed。
連XML template都不用自己建。
如果沒有載入,<liferay-ui:rss> 與 <liferay-ui:rss-settings> 這兩個Tag會產生null的Exception。

portal-dependency-jars=\
    jstl-api.jar,\
    jstl-impl.jar,\
    jdom.jar,\
    dom4j.jar,\
    rome.jar



這是這次所使用的model 
<service-builder package-path="com.xxx.portlet">
<author>Danny</author>
<namespace>news</namespace>

<entity name="News" human-name="news" local-service="true"
remote-service="false" uuid="true">

<column name="newsId" type="long" primary="true" />
<column name="groupId" type="long" />
<column name="companyId" type="long" />
<column name="userId" type="long" />
<column name="userName" type="String" />
<column name="modifiedDate" type="Date" />
<column name="createDate" type="Date" />
<column name="title" type="String" />
<column name="content" type="String" />
<column name="viewCount" type="long" />
<column name="priority" type="boolean" />
<column name="category" type="String" />
<column name="copyToMainSite" type="boolean" />
<column name="urlTitle" type="String" />
    <column name="resourcePrimKey" type="long"/>
   <column name="status" type="int"/>
   <column name="statusByUserId" type="long"/>
   <column name="statusByUserName" type="String"/>
   <column name="statusDate" type="Date"/>

<order by="desc">
<order-column name="createDate" />
</order>

<finder name="CD_T" return-type="Collection">
<finder-column name="createDate" comparator="&gt;"/>
<finder-column name="title" comparator="LIKE"/>
</finder>
<finder name="CD_C" return-type="Collection">
<finder-column name="createDate" comparator="&gt;"/>
<finder-column name="content" comparator="LIKE"/>
</finder>
<finder name="R_S" return-type="Collection">
  <finder-column name="resourcePrimKey"/>
  <finder-column name="status"/>
</finder>
<finder name="GroupId" return-type="Collection">
<finder-column name="groupId" />
</finder>
<finder name="CompanyId" return-type="Collection">
<finder-column name="companyId" />
</finder>
<finder name="G_UT" return-type="News" unique="true">
<finder-column name="groupId" />
<finder-column name="urlTitle" />
</finder>
<finder name="G_NotS" return-type="Collection">
<finder-column name="groupId" />
<finder-column name="status" comparator="!=" />
</finder>
<finder name="G_S" return-type="Collection">
<finder-column name="groupId" />
<finder-column name="status" />
</finder>
<reference package-path="com.liferay.portal" entity="Group" />
<reference package-path="com.liferay.portal" entity="WorkflowInstanceLink"/>
<reference package-path="com.liferay.portlet.asset" entity="AssetEntry" />
<reference package-path="com.liferay.portlet.asset" entity="AssetLink" />
<reference package-path="com.liferay.portlet.asset" entity="AssetTag" />
</entity>
</service-builder>


在init.jsp 上加入以下這幾四個參數,這是使用<liferay-ui:rss>所需要使用到的參數。

boolean enableRSS = !PortalUtil.isRSSFeedsEnabled() ? false : GetterUtil.getBoolean(portletPreferences.getValue("enableRss", null), true);
int rssDelta = GetterUtil.getInteger(portletPreferences.getValue("rssDelta", StringPool.BLANK), SearchContainer.DEFAULT_DELTA);
String rssDisplayStyle = portletPreferences.getValue("rssDisplayStyle", RSSUtil.DISPLAY_STYLE_DEFAULT);
String rssFeedType = portletPreferences.getValue("rssFeedType", RSSUtil.FEED_TYPE_DEFAULT);




latestnews_view.jsp 就可以使用 rss的Tag了。

<liferay-portlet:resourceURL varImpl="rssURL" >
</liferay-portlet:resourceURL>
        <liferay-ui:rss
delta="<%= rssDelta %>"
displayStyle="<%= rssDisplayStyle %>"
feedType="<%= rssFeedType %>"
resourceURL="<%= rssURL %>" />
如以下,就會產生RSS的超連結出來。



當點下之後,會去呼叫 resourceURL。
就會導到Control的 public void serveResource(ResourceRequest request, ResourceResponse response)
方法。
在這裡取得RSS Feed的做法也就是在這裡開始產生。

如果是Struts的作法,就要去繼承 Struts.RSSAction.java 然後在裡面實作。

在這裡我們是採用MVCPortlet.java

public class NewsPortlet extends MVCPortlet{
@Override
public void serveResource(ResourceRequest request, ResourceResponse response)
            throws PortletException, IOException {

response.setContentType(ContentTypes.TEXT_XML_UTF8);
ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
WebKeys.THEME_DISPLAY);

Layout layout = themeDisplay.getLayout();
long plid = themeDisplay.getPlid();
long groupId = themeDisplay.getScopeGroupId();
int max = ParamUtil.getInteger(request, "max", SearchContainer.DEFAULT_DELTA);
String type = ParamUtil.getString(request, "type", RSSUtil.FORMAT_DEFAULT);
double version = ParamUtil.getDouble(request, "version", RSSUtil.VERSION_DEFAULT);
String displayStyle = ParamUtil.getString(request, "displayStyle", RSSUtil.DISPLAY_STYLE_DEFAULT);

String feedURL = themeDisplay.getPortalURL() + themeDisplay.getPathMain() +
"/news/find_entry?";
_log.info("themeDisplay.getPortalURL():" + themeDisplay.getPortalURL() + "==");
_log.info("themeDisplay.getPathMain():" + themeDisplay.getPathMain() + "==");
String entryURL = feedURL;
String rss = StringPool.BLANK;
try {

if (layout != null) {
groupId = themeDisplay.getScopeGroupId();

feedURL = PortalUtil.getLayoutFullURL(themeDisplay) +
Portal.FRIENDLY_URL_SEPARATOR + "news/rss";
_log.info("feedURL:" + feedURL + "==");
entryURL = feedURL;

rss = NewsLocalServiceUtil.getGroupNewsesRSS(
groupId, max, type, version, displayStyle,
feedURL, entryURL, themeDisplay);
}
} catch (SystemException e) {
e.printStackTrace();
} catch (PortalException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

// return rss.getBytes(StringPool.UTF8);
PrintWriter writer = response.getWriter();
writer.print(rss);
}
}
這當中有去呼叫到 NewsLocalServiceUtil.getGroupNewsesRSS();
這是自己定義的method。
所以在NewsLocalServiceImpl.java內要實作這個method

 public String getGroupNewsesRSS(
long groupId, int max, String type,
double version, String displayStyle, String feedURL,
String entryURL, ThemeDisplay themeDisplay)
throws PortalException, SystemException {

Group group = groupPersistence.findByPrimaryKey(groupId);

String name = group.getDescriptiveName();
List<News> newses = getGroupNewses(groupId, 0, max);

return exportToRSS(
name, name, type, version, displayStyle, feedURL, entryURL,
newses, themeDisplay);
}
 
protected String exportToRSS(
String name, String description, String type, double version,
String displayStyle, String feedURL, String entryURL,
List<News> newses, ThemeDisplay themeDisplay)
throws SystemException {

SyndFeed syndFeed = new SyndFeedImpl();

syndFeed.setDescription(description);

List<SyndEntry> syndEntries = new ArrayList<SyndEntry>();

syndFeed.setEntries(syndEntries);

for (News news : newses) {
SyndEntry syndEntry = new SyndEntryImpl();

String author = PortalUtil.getUserName(news);

syndEntry.setAuthor(author);

SyndContent syndContent = new SyndContentImpl();

syndContent.setType(RSSUtil.ENTRY_TYPE_DEFAULT);

String value = null;

if (displayStyle.equals(RSSUtil.DISPLAY_STYLE_ABSTRACT)) {
String summary = news.getTitle();

if (Validator.isNull(summary)) {
summary = StringUtil.shorten(
HtmlUtil.extractText(news.getContent()),
200, StringPool.BLANK);
}

value = StringUtil.shorten(
HtmlUtil.extractText(summary),
200, StringPool.BLANK);
}
else if (displayStyle.equals(RSSUtil.DISPLAY_STYLE_TITLE)) {
value = StringPool.BLANK;
}
else {
value = StringUtil.replace(
news.getContent(),
new String[] {
"href=\"/", "src=\"/"
},
new String[] {
"href=\"" + themeDisplay.getURLPortal() + "/",
"src=\"" + themeDisplay.getURLPortal() + "/"
});
}

syndContent.setValue(value);

syndEntry.setDescription(syndContent);

StringBundler sb = new StringBundler(4);

if (entryURL.endsWith("/news/rss")) {
sb.append(entryURL.substring(0, entryURL.length() -11 ));
sb.append(StringPool.QUESTION);
sb.append("p_p_id=");
sb.append(PortletKeys.LATESTNEWS_PORTLET);
sb.append(StringPool.AMPERSAND); //&
sb.append("p_p_lifecycle=0&p_p_state=normal&p_p_mode=view&p_p_col_id=column-1&p_p_col_count=1&");
sb.append(PortletKeys.NEWS_NAMESPACE + "jspPage=");
sb.append("%2Fhtml%2Fnews%2Flatestnews_view.jsp");
sb.append(StringPool.AMPERSAND); //&
sb.append(PortletKeys.NEWS_NAMESPACE + "newsId=");
sb.append(news.getNewsId());
_log.info("if=>sb:" + sb + "==");
}
else {
sb.append(entryURL);
_log.info("else=>sb:" + sb + "==");

if (!entryURL.endsWith(StringPool.QUESTION)) {
sb.append(StringPool.AMPERSAND);
}

}

String link = sb.toString();

syndEntry.setLink(link);

syndEntry.setPublishedDate(news.getCreateDate());
syndEntry.setTitle(news.getTitle());
syndEntry.setUpdatedDate(news.getModifiedDate());
syndEntry.setUri(link);

syndEntries.add(syndEntry);
}

syndFeed.setFeedType(RSSUtil.getFeedType(type, version));

List<SyndLink> syndLinks = new ArrayList<SyndLink>();

syndFeed.setLinks(syndLinks);

SyndLink selfSyndLink = new SyndLinkImpl();

syndLinks.add(selfSyndLink);

selfSyndLink.setHref(feedURL);
selfSyndLink.setRel("self");

if (feedURL.endsWith("/-/news/rss")) {
SyndLink alternateSyndLink = new SyndLinkImpl();

syndLinks.add(alternateSyndLink);

alternateSyndLink.setHref(
feedURL.substring(0, feedURL.length() - 11));
alternateSyndLink.setRel("alternate");
}

syndFeed.setPublishedDate(new Date());
syndFeed.setTitle(name);
syndFeed.setUri(feedURL);

try {
return RSSUtil.export(syndFeed);
}
catch (FeedException fe) {
throw new SystemException(fe);
}
}

當中的entryURL 和 FeedURL 是比較難搞的,
FeedURL = 消息來源網址。
entryURL = 消息來源之內部每單一筆的導向網址,點擊RSS其中一筆後,會開啟該篇新聞的來源網址。

上面每一筆的entryURL 都是仿照PortletURL在網址上的參數,
有用到portletKeys,我是這樣寫,
public static final String LATESTNEWS_PORTLET = "News_WAR_Newsportlet";
public static final String NEWS_NAMESPACE = "_News_WAR_Newsportlet_";
portletId 與 namespace的差別 在於 左右兩側的底線。



另外這是設定RSS的型態,可以寫在Portlet-Configuration上。

<%@include file="/html/init.jsp"%>
<liferay-portlet:actionURL portletConfiguration="true"
var="configurationURL" />

<aui:form action="<%=configurationURL%>" method="post">
<aui:input name="<%=Constants.CMD%>" type="hidden"
value="<%=Constants.UPDATE%>" />

<aui:fieldset>
<liferay-ui:rss-settings
delta="<%= rssDelta %>"
displayStyle="<%= rssDisplayStyle %>"
enabled="<%= enableRSS %>"
feedType="<%= rssFeedType %>"/>

<aui:button type="submit" />
</aui:fieldset>
</aui:form>






































2014年8月19日 星期二

遠端桌面登入:憑證失效或過期 [刪除憑證機碼]

參考
http://www.etzzy.com/?p=1195
http://www.coolaler.com/showthread.php/120804-Terminal-Client-Service-License%E9%81%8E%E6%9C%9F%E5%86%8D%E5%BB%B6%E7%BA%8C%E6%96%B9%E6%B3%95

最近遇到電腦一重開機,時間就會回歸2002/01/01的狀況,
導致遠端桌面憑證 過期或失效。
一般遠端桌面憑證只提供90天的期限,一旦超過憑證就會失效。
目前解決的方法就是將 舊憑證刪除,讓他再產生一個新的憑證 延期90天。
前提 要先將系統時間調成正常狀態。

step 1 :
先將windows的時間 與 time server自動同步
Windows 預設的time server 有 [time.windows.com , time.nist.gov]
如果同步發生錯誤,可以先試著手動調到目前日期時間,再做同步動作。

如果要調整時間與time server 校正頻率 可以到 執行-> regedit [登陸編輯程式]
電腦\HKEY_LOCAL_MACHINE\SYSTEM\CurrentContriolSet\Services\W32Time\TimeProviders\NtpClient
\SpecialPollInterval
修改頻率
例如 86400 = 1天

step 2 :
執行-> regedit [登陸編輯程式]
電腦\HKEY_LOCAL_MACHINE\SYSTEM\SOFTWARE\Microsoft\MSLicensing
將底下的HardwareID 與 Store 兩個文件夾內的檔案刪除,回歸預設值。








另外BIOS沒電會導致 時間不能儲存,回歸2002/01/01的狀況
可能換主機板水銀電池,或是主機板的控制元件壞了
https://tw.knowledge.yahoo.com/question/question?qid=1508011501891




2014年8月13日 星期三

Liferay5.2.3版,將原有的fckeditor 換成 tinyMCE3.2.1 並 升級成tinyMCE 4.1.3作法

Liferay Web Content(Journal), Blog 等, 使用的 所見及所得 HTML 編輯器
在Liferay 5.2.3預設是使用 fckeditor,有支援的例 :

#the available WYSIWYG editors : liferay , fckeditor , simple , tinymce , or tinymcesimple
參考
 http://www.liferay.com/zh/community/wiki/-/wiki/Main/Portal+Properties+5.2.3

想換成其他 editor就加入portal.properties

{$LIFERAY_HOME}\webapps\ROOT\WEB-INF\classes\portal-ext-properties

editor.wysiwyg.default=tinymce
    editor.wysiwyg.portal-web.docroot.html.portlet.blogs.edit_entry.jsp=tinymce
    editor.wysiwyg.portal-web.docroot.html.portlet.calendar.edit_configuration.jsp=tinymce
    editor.wysiwyg.portal-web.docroot.html.portlet.enterprise_admin.view.jsp=tinymce
    editor.wysiwyg.portal-web.docroot.html.portlet.invitation.edit_configuration.jsp=tinymce
    editor.wysiwyg.portal-web.docroot.html.portlet.journal.edit_article_content.jsp=tinymce
    editor.wysiwyg.portal-web.docroot.html.portlet.journal.edit_article_content_xsd_el.jsp=tinymce
    editor.wysiwyg.portal-web.docroot.html.portlet.journal.edit_configuration.jsp=tinymce
    editor.wysiwyg.portal-web.docroot.html.portlet.login.configuration.jsp=tinymce
    editor.wysiwyg.portal-web.docroot.html.portlet.mail.edit.jsp=tinymce
    editor.wysiwyg.portal-web.docroot.html.portlet.mail.edit_message.jsp=tinymce
    editor.wysiwyg.portal-web.docroot.html.portlet.message_boards.edit_configuration.jsp=tinymce
    editor.wysiwyg.portal-web.docroot.html.portlet.shopping.edit_configuration.jsp=tinymce
    editor.wysiwyg.portal-web.docroot.html.portlet.wiki.edit.html.jsp=tinymce


以上的參數宣告是 告訴Liferay 哪一些portlet要用的editor 種類
沒有以上這些參數,則是用預設fckeditor。

如果只想換成TinyMCE,而不要求TinyMCE版本(Liferay5.2.3是使用TinyMCE3.2.1),那以上就已完成。

Tomcat重新啟動即可。
=======================================================================

接下來是將Liferay 原本自帶的 TinyMCE 升級 TinyMCE4.1.3版本。
首先上面的步驟也是照做一次,將portal-ext.properties加入參數。

接著到TinyMCE官網下載 TinyMCE4.1.3.zip的版本
http://www.tinymce.com/download/download.php

解壓縮後,
假設解壓目錄為 {$TINYMCE_PATH},將{$TINYMCE_PATH}\tinymce\js\tinymce
複製到{$LIFERAY_HOME}\webapps\ROOT\html\js\editor。

再來就在該目錄修改 tinymce.jsp

<%@ page import="com.liferay.portal.kernel.util.HtmlUtil" %>
<%@ page import="com.liferay.portal.kernel.util.ParamUtil" %>
<%@ page import="com.liferay.portal.kernel.util.Validator" %>

<%
String initMethod = ParamUtil.get(request, "initMethod", DEFAULT_INIT_METHOD);
String onChangeMethod = ParamUtil.getString(request, "onChangeMethod");
%>

<html>
<head>
<title>Editor</title>

<script type="text/javascript" src="tinymce/tinymce.min.js"></script>
<script type="text/javascript">

var onChangeCallbackCounter = 0;

tinymce.init({
        selector: "textarea",
        theme: "modern",
        file_browser_callback : "fileBrowserCallback",
init_instance_callback : "initInstanceCallback",
onchange_callback : "onChangeCallback",
plugins: [
       "advlist autolink lists link image charmap print preview hr anchor pagebreak",
       "searchreplace wordcount visualblocks visualchars code fullscreen",
       "insertdatetime media nonbreaking save table contextmenu directionality",
       "emoticons template paste textcolor colorpicker textpattern"
   ],
   toolbar1: "insertfile undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image",
   toolbar2: "print preview media | forecolor backcolor emoticons",
   image_advtab: true,
  
});


function fileBrowserCallback(field_name, url, type) {
}

function getHTML() {
return tinymce.activeEditor.getContent();
}

function init(value) {
setHTML(decodeURIComponent(value));
}

function initInstanceCallback() {
init(parent.<%= initMethod %>());
}

function onChangeCallback(tinyMCE) {

// This purposely ignores the first callback event because each call
// to setContent triggers an undo level which fires the callback
// when no changes have yet been made.

// setContent is not really the correct way of initializing this
// editor with content. The content should be placed statically
// (from the editor's perspective) within the textarea. This is a
// problem from the portal's perspective because it's passing the
// content via a javascript method (initMethod).

if (onChangeCallbackCounter > 0) {

<%
if (Validator.isNotNull(onChangeMethod)) {
%>

parent.<%= HtmlUtil.escape(onChangeMethod) %>(getHTML());

<%
}
%>
}
onChangeCallbackCounter++;
}
function setHTML(value) {
tinymce.activeEditor.setContent(value);
}
</script>
</head>


<body leftmargin="0" marginheight="0" marginwidth="0" rightmargin="0" topmargin="0">
<textarea id="textArea" name="textArea" style="height: 100%; width: 100%;"></textarea>
</body>

</html>

<%!
public static final String DEFAULT_INIT_METHOD = "initEditor";
%>



TinyMCE4.1.3版 與 以往Tiny3.x.x版本內容有差別。
以上的JSP 是直接從原有的tinymce.jsp 做修改,需要注意的地方如下
EX:
1) import src 的 js 檔 從 tiny_mce.js 換成 tinymce.min.js
2) tinyMCE 變數名 換成 tinymce
3) theme 的內容從 advanced/simple 換成只有 modern
4) tinymce.init 的語法屬性有修改

上面的init 屬性可能還不是最適合的狀態,其他可參考
http://www.tinymce.com/wiki.php/Configuration
http://www.tinymce.com/tryit/basic.php


另外要換成繁體中文可以到官方下載語言包
http://www.tinymce.com/i18n/index.php
解壓後 zh_TW.js檔案 丟到 tinymce/langs資料夾底下
並在tinymce.init的屬性加上 language : 'zh_TW', 即可。