引入:

比起创建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: []
]
,
 
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。


最终,发布过程成功,前端会有一个模态对话框总结下这次发布:

opencms 发布过程深入研究_第1张图片


总结:

(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)中分别创建新条目。