引入:前端
比起建立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()方法來記錄下發佈的計劃任務,併產生具體的發佈報告。具體來講:
它建立一個具體的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()方法來執行具體的發佈。
它首先調用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。
最終,發佈過程成功,前端會有一個模態對話框總結下此次發佈:
總結:
(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)中分別建立新條目。