opencms 發佈過程深刻研究

引入:前端

比起建立Resource,發佈過程要困難不少,我上週在support team時候曾經設想不經過調試器,光走讀代碼來明白其中的奧祕,後來由於堆棧太深而放棄了,如今有了調試器,終於把這些細節弄明白了,果真很是複雜。java


細節分析:sql

在發佈Resource時,它的入口是CmsPublishProject類的actionPublish()方法,發佈過程複雜到變態,全包裝在performDialogOperation()方法中:數據庫

/**
     * @seeorg.opencms.workplace.CmsMultiDialog#performDialogOperation()
     */
   protected boolean performDialogOperation() throws CmsException {
 
        CmsPublishList publishList =getSettings().getPublishList();
        if (publishList == null){
            thrownew CmsException(Messages.get().container(
                org.opencms.db.Messages.ERR_GET_PUBLISH_LIST_PROJECT_1,
                getProjectname()));
        }
        OpenCms.getPublishManager().publishProject(
            getCms(),
            new CmsHtmlReport(getLocale(),getCms().getRequestContext().getSiteRoot()),
            publishList);
        // wait 2 seconds, may be it finishes fast
        OpenCms.getPublishManager().waitWhileRunning(1500);
        returntrue;
}

宏觀上看,它先得到workplace中全部資源的發佈列表,由於本例中只有一個資源,因此調試的列表以下:設計模式

[
publish of project:ae97ff1d-7824-11e4-8c0e-28e347ffa92b
publish history ID:f5d6e16a-787e-11e4-8289-28e347ffa92b
resources: [[org.opencms.file.CmsResource, path:/sites/default/charles_study/publish_test1, structure idea051006-787e-11e4-8289-28e347ffa92b, resource id:ea051007-787e-11e4-8289-28e347ffa92b, type id: 1, folder: false, flags: 0,project: ae97ff1d-7824-11e4-8c0e-28e347ffa92b, state: 2, date created: Sun Nov30 18:52:01 CST 2014, user created: c300ba5c-01e8-3727-b305-5dcc9ccae1ee, datelastmodified: Sun Nov 30 18:52:09 CST 2014, user lastmodified:c300ba5c-01e8-3727-b305-5dcc9ccae1ee, date released: Thu Jan 01 08:00:00 CST1970, date expired: Sun Aug 17 15:12:55 CST 292278994, date content: Sun Nov 3018:52:01 CST 2014, size: 0, sibling count: 1, version: 1]]
folders: []
deletedFolders: []
]

其次,它經過CmsPublishManager的publishProject()方法來作對於待發佈列表的資源的發佈工做。多線程

CmsPublishManager會調用CmsSecurityManager的publishProject()方法,最終調用CmsDriverManager的publishObject()方法來作發佈的所有工做。這就是咱們要討論的重點。ide



重點探討1: 從CmsDriverManager的publishObject()方法來研究發佈過程。idea

發佈過程代碼不少也很深,從宏觀上看,它作了這麼幾件事情:spa

a.  檢查父目錄,這主要用因而否存在該資產的父目錄還沒被髮布的狀況。線程


b.發起一個CmsEvent類的發佈前事件(beforePublishEvent),該事件中除包含上面的發佈資源列表(publishList)外還包含projectId,dbContext和一個空的發佈報告對象(CmsHtmlReport,用於存儲發佈失敗的錯誤),該事件中的數據以下:

{publishList=
[
publish of project: ae97ff1d-7824-11e4-8c0e-28e347ffa92b
publish history ID: c0488fed-7882-11e4-8289-28e347ffa92b
resources:
 [[org.opencms.file.CmsResource, 
path:/sites/default/charles_study/publish_test4, structure 
idbb3dab29-7882-11e4-8289-28e347ffa92b, resource 
id:bb3dab2a-7882-11e4-8289-28e347ffa92b, type id: 1, folder: false, 
flags: 0,project: ae97ff1d-7824-11e4-8c0e-28e347ffa92b, state: 2, date 
created: Sun Nov30 19:19:21 CST 2014, user created: 
c300ba5c-01e8-3727-b305-5dcc9ccae1ee, datelastmodified: Sun Nov 30 
19:19:24 CST 2014, user 
lastmodified:c300ba5c-01e8-3727-b305-5dcc9ccae1ee, date released: Thu 
Jan 01 08:00:00 CST1970, date expired: Sun Aug 17 15:12:55 CST 
292278994, date content: Sun Nov 3019:19:21 CST 2014, size: 0, sibling 
count: 1, version: 1]]
folders: []
deletedFolders: []
]
,
 
dbContext=org.opencms.db.CmsDbContext@14b787a,report=org.opencms.report.CmsHtmlReport@1d2b627,projectId=ae97ff1d-7824-11e4-8c0e-28e347ffa92b}

而後用CmsEventManager的fireCmsEvent(beforePublishEvent)方法來執行此次發佈事件。

由於一個資源的發佈會影響到opencms的其餘組件的更新,因此這裏使用「Observer」設計模式,它吧多個cms的其餘組件加到CmsEventManager的監聽器列表中,監聽器有幾百個,我就不一一列出了。


c.用發佈鎖CmsLock鎖住全部發佈列表中的資源。


d.調用CmsPublishEngine的enqueuePublishJob()方法來記錄下發佈的計劃任務,併產生具體的發佈報告。具體來講:

  1.   它建立一個具體的CmsPublishJobInfoBean對象,它其中包含了發佈所需的所有細節:

[org.opencms.publish.CmsPublishJobInfoBean, history id:c0488fed-7882-11e4-8289-28e347ffa92b, project idae97ff1d-7824-11e4-8c0e-28e347ffa92b, project name: Offline, user id:c300ba5c-01e8-3727-b305-5dcc9ccae1ee, locale: en, flags: 0, size: 1, enqueue time:0, start time: 0, finish time: 0]

  2. 調用CmsPublishQueue的add方法吧這個發佈做業對象添加進去,它在其中會調用CmsDriverManager的createPublishJob()方法來建立具體的發佈做業,最後會調用CmsProjectDriver類的createPublishJob()方法來作具體的數據庫層面的操做。

其最後執行的SQL語句是:

INSERT INTO CMS_PUBLISH_JOBS(HISTORY_ID,PROJECT_ID,PROJECT_NAME,USER_ID,PUBLISH_LOCALE,PUBLISH_FLAGS,RESOURCE_COUNT,ENQUEUE_TIME,START_TIME,FINISH_TIME,PUBLISH_LIST) VALUES ('6709c8a5-7887-11e4-8289-28e347ffa92b','ae97ff1d-7824-11e4-8c0e-28e347ffa92b','Offline','c300ba5c-01e8-3727-b305-5dcc9ccae1ee','en',0,1,1417348377907,0,0,x'ACED00057372001D6F72672E6F70656E636D732E64622E436D735075626C6973684C697374DC35DFB34A32E7310C0000787077486709C8A5788711E4828928E347FFA92BAE97FF1D782411E48C0E28E347FFA92B0000000000000001FFFFFFFF000000016244C681788711E4828928E347FFA92B000000000000000078')

因此從這裏看出,它會更新CMS_PUBLISH_JOBS表,把發佈的若干信息,好比發佈歷史ID,項目ID,名稱,用戶ID,發佈國際化標示,入發佈隊列時間,發佈開始時間,結束時間,以及發佈的列表都插入數據庫。

3.調用CmsPublishListenerCollection的fireEnqueued()方法來把此次發佈事件通知全部的監聽組件,若是發佈過程失敗,則從CmsPublishJob的publishReport中能夠拿到發佈錯誤信息。


e.調用checkCurrentPublishJobThread()方法來作具體的站點發布。由於這裏是多線程操做而不是同步走下去的,因此開始幾回調試每次都找不到執行點,後來研究了下,發現具體的站點發布代碼放在CmsPublishThread的run()中,它最終會調用CmsProjectDriver類的publishProject()方法來執行具體的發佈。

  1. 它首先調用CmsHistoryDriver的writeProject()方法把指定的Project寫入CMS_HISTORY_PROJECTS和CMS_HISTORY_PROJECTRESOURCES表:

// write an entry in the publish project log
m_driverManager.getHistoryDriver(dbc).writeProject(dbc, publishTag, System.currentTimeMillis());

寫入CMS_HISTORY_PROJECTS表的SQL語句以下:

NSERT INTO CMS_HISTORY_PROJECTS(PUBLISH_TAG,PROJECT_ID,PROJECT_NAME,PROJECT_PUBLISHDATE,PROJECT_PUBLISHED_BY,USER_ID,GROUP_ID,MANAGERGROUP_ID,PROJECT_DESCRIPTION,DATE_CREATED,PROJECT_TYPE,PROJECT_OU)VALUES(62,'ae97ff1d-7824-11e4-8c0e-28e347ffa92b','Offline',1417354083096,'c300ba5c-01e8-3727-b305-5dcc9ccae1ee','c300ba5c-01e8-3727-b305-5dcc9ccae1ee','6dfffebb-0985-3cbd-8d53-a5df8681a9f3','4d9b473f-3b73-34f7-b80c-15f068c3b2be','TheOffline Project',1417305967147,0,'/')

寫入CMS_HISTORY_PROJECTRESOURCES表的SQL語句以下:

INSERT INTO CMS_HISTORY_PROJECTRESOURCES (PUBLISH_TAG,PROJECT_ID,RESOURCE_PATH)VALUES (62,'ae97ff1d-7824-11e4-8c0e-28e347ffa92b','/')


2.它會執行具體的內容變動,它會遍歷發佈列表publishList, 而後對其中的目錄和文件分別發佈,其中目錄在先。對於目錄的發佈,它是調用如下代碼來實現:

projectDriver.publishFolder(
                            dbc,
                            report,
                            ++publishedFolderCount,
                            foldersSize,
                            onlineProject,
                            new CmsFolder(currentFolder),
                            publishList.getPublishHistoryId(),
publishTag);

而對於文件的發佈,它是調用如下代碼來實現:

projectDriver.publishFile(
                        dbc,
                        report,
                        ++publishedFileCount,
                        filesSize,
                        onlineProject,
                        currentResource,
                        publishedContentIds,
                        publishList.getPublishHistoryId(),
publishTag);



重點探討2:發佈文件的過程(其實發布目錄差很少同樣,只不過由於我用文件作例子,因此只能調試出文件的發佈細節了)

總的來講,CmsProjectDriver類的publishFile()方法會調用CmsProjectDriver類的publishNewFile()方法,進而調用CmsProjectDriver類的publishFileContent()方法來執行具體發佈的,具體步驟以下:

a. 從CMS_OFFLINE_CONTENTS表中獲取給定ResourceID的內容。

byte[] offlineContent= m_driverManager.getVfsDriver(dbc).readContent(
                dbc,
                projectIdForReading,
                offlineResource.getResourceId());

它執行的SQL語句是:

SELECT CMS_OFFLINE_CONTENTS.FILE_CONTENT FROMCMS_OFFLINE_CONTENTS WHERECMS_OFFLINE_CONTENTS.RESOURCE_ID='d46e46f4-7893-11e4-8289-28e347ffa92b'


b.從上步驟的Resource構建offlineFile, 而且克隆到newFile中。

// create the file online
            newFile = (CmsFile)offlineFile.clone();
newFile.setState(CmsResource.STATE_UNCHANGED);


c.建立cms的online resources和online structure 。經過如下代碼

// update the online/offlinestructure and resource records of the file
 m_driverManager.getVfsDriver(dbc).publishResource(dbc, onlineProject, newFile, offlineFile)

其中更新CMS_ONLINE_RESOURCES的SQL語句以下:

INSERT INTO CMS_ONLINE_RESOURCES(RESOURCE_ID,RESOURCE_TYPE,RESOURCE_FLAGS,DATE_CREATED,USER_CREATED,DATE_LASTMODIFIED,USER_LASTMODIFIED,RESOURCE_STATE,RESOURCE_SIZE,DATE_CONTENT,PROJECT_LASTMODIFIED,SIBLING_COUNT,RESOURCE_VERSION)VALUES('d46e46f4-7893-11e4-8289-28e347ffa92b',1,0,1417353704758,'c300ba5c-01e8-3727-b305-5dcc9ccae1ee',1417353704758,'c300ba5c-01e8-3727-b305-5dcc9ccae1ee',0,0,1417353704758,'ae97ff1d-7824-11e4-8c0e-28e347ffa92b',1,1)

更新CMS_ONLINE_STRUCTURE的SQL語句以下:

INSERT INTO CMS_ONLINE_STRUCTURE(STRUCTURE_ID,RESOURCE_ID,RESOURCE_PATH,STRUCTURE_STATE,DATE_RELEASED,DATE_EXPIRED,PARENT_ID,STRUCTURE_VERSION)VALUES ('d46e46f3-7893-11e4-8289-28e347ffa92b','d46e46f4-7893-11e4-8289-28e347ffa92b','/sites/default/charles_study/publish-test-11',0,0,9223372036854775807,'4bf8b750-785d-11e4-8289-28e347ffa92b',0)


d.接着上一步,更新cms的online resources和online structure的版本號。經過如下代碼:

// update version numbers
 m_driverManager.getVfsDriver(dbc).publishVersions(dbc, offlineResource, !alreadyPublished);

其中更新CMS_ONLINE_RESOURCES的版本號的SQL語句以下:

UPDATE CMS_ONLINE_RESOURCES SET RESOURCE_VERSION = 1WHERE CMS_ONLINE_RESOURCES.RESOURCE_ID ='d46e46f4-7893-11e4-8289-28e347ffa92b'

更新CMS_ONLINE_STRUCTURE的SQL語句 以下:

UPDATE CMS_ONLINE_STRUCTURE SET STRUCTURE_VERSION = 0 WHERECMS_ONLINE_STRUCTURE.STRUCTURE_ID = 'd46e46f3-7893-11e4-8289-28e347ffa92b'


e.建立online文件的內容。經過如下代碼:

// create/update the content
  m_driverManager.getVfsDriver(dbc).createOnlineContent(
                dbc,
                offlineFile.getResourceId(),
                offlineFile.getContents(),
                publishTag,
                true,
                needToUpdateContent);

它執行的SQL語句以下:

INSERT INTO CMS_CONTENTS(RESOURCE_ID,FILE_CONTENT,PUBLISH_TAG_FROM,PUBLISH_TAG_TO,ONLINE_FLAG) VALUES('d46e46f4-7893-11e4-8289-28e347ffa92b',x'',62,62,1)


f.建立online文件的properties信息。經過如下代碼:

m_driverManager.getVfsDriver(dbc).writePropertyObjects(dbc, onlineProject, newFile, offlineProperties);

它會把去寫CMS_ONLINE_PROPERTIES文件,由於個人發佈的文件沒有配置properties,因此調試器跳過了這一段。


g.寫入新的online訪問控制列表。經過如下代碼:

m_driverManager.getUserDriver(dbc).publishAccessControlEntries(
                dbc,
                dbc.currentProject(),
                onlineProject,
                offlineResource.getResourceId(),
                newFile.getResourceId());

它最終會去寫CMS_ONLINE_ACCESSCONTROL。


最終,發佈過程成功,前端會有一個模態對話框總結下此次發佈:

wKioL1R7MsuzvdQlAACaoSaMPRs080.jpg


總結:

(1)發佈過程宏觀上分爲發佈做業的記錄和實際站點發布工做。

(2)發佈過程是以事件驅動的,其發佈涉及到的信息資源記錄在beforePublishEvent中。

(3)發佈做業會維護一個做業隊列,而後把發佈事件添加到發佈隊列中。發佈做業的執行會更新CMS_PUBLISH_JOBS表。

(4)發佈做業的結果會通知全部監聽器,這些監聽器是opencms的自帶組件。

(5)對於實際站點發布的工做,是另開了線程來完成的,其線程使用的執行體是CmsPublishThread類。

(6)發佈線程會把當前Project信息寫入CMS_HISTORY_PROJECTS和CMS_HISTORY_PROJECTRESOURCES表。

(7)具體目錄和文件的發佈工做,是經過發佈線程遍歷待發佈列表來依次執行的,先執行目錄發佈工做,再執行文件發佈工做。但二者相似。

(8)執行文件的發佈會從CMS_OFFLINE_CONTENTS表中獲取給定ResourceID的內容,構建offlineFile,並克隆到newFile中,而後依次建立CMS_ONLINE_RESOURCES,CMS_ONLINE_STRUCTURE文件,並更新其版本號。再在如下表(CMS_CONTENTS,CMS_ONLINE_PROPERTIES,CMS_ONLINE_ACCESSCONTROL)中分別建立新條目。

相關文章
相關標籤/搜索