引入:
比起创建Resource,发布过程要困难很多,我上周在support team时候曾经设想不通过调试器,光走读代码来明白其中的奥秘,后来因为堆栈太深而放弃了,现在有了调试器,终于把这些细节弄明白了,果然非常复杂。
细节分析:
在发布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()方法来做发布的全部工作。这就是我们要讨论的重点。
重点探讨1: 从CmsDriverManager的publishObject()方法来研究发布过程。
发布过程代码很多也很深,从宏观上看,它做了这么几件事情:
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: [] ] , [email protected],[email protected],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)中分别创建新条目。