opencms 发布过程深入研究

简介:

引入:

比起创建Resource,发布过程要困难很多,我上周在support team时候曾经设想不通过调试器,光走读代码来明白其中的奥秘,后来因为堆栈太深而放弃了,现在有了调试器,终于把这些细节弄明白了,果然非常复杂。


细节分析:

在发布Resource时,它的入口是CmsPublishProject类的actionPublish()方法,发布过程复杂到变态,全包装在performDialogOperation()方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
      * @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中所有资源的发布列表,因为本例中只有一个资源,所以调试的列表如下:

1
2
3
4
5
6
7
[
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,用于存储发布失败的错误),该事件中的数据如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{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对象,它其中包含了发布所需的全部细节:

1
[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语句是:

1
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表:

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

写入CMS_HISTORY_PROJECTS表的SQL语句如下:

1
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语句如下:

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


2.它会执行具体的内容变更,它会遍历发布列表publishList, 然后对其中的目录和文件分别发布,其中目录在先。对于目录的发布,它是调用以下代码来实现:

1
2
3
4
5
6
7
8
9
projectDriver.publishFolder(
                             dbc,
                             report,
                             ++publishedFolderCount,
                             foldersSize,
                             onlineProject,
                             new  CmsFolder(currentFolder),
                             publishList.getPublishHistoryId(),
publishTag);

而对于文件的发布,它是调用以下代码来实现:

1
2
3
4
5
6
7
8
9
10
projectDriver.publishFile(
                         dbc,
                         report,
                         ++publishedFileCount,
                         filesSize,
                         onlineProject,
                         currentResource,
                         publishedContentIds,
                         publishList.getPublishHistoryId(),
publishTag);



重点探讨2:发布文件的过程(其实发布目录差不多一样,只不过因为我用文件做例子,所以只能调试出文件的发布细节了)

总的来说,CmsProjectDriver类的publishFile()方法会调用CmsProjectDriver类的publishNewFile()方法,进而调用CmsProjectDriver类的publishFileContent()方法来执行具体发布的,具体步骤如下:

a. 从CMS_OFFLINE_CONTENTS表中获取给定ResourceID的内容。

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

它执行的SQL语句是:

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


b.从上步骤的Resource构建offlineFile, 并且克隆到newFile中。

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


c.创建cms的online resources和online structure 。通过以下代码

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

其中更新CMS_ONLINE_RESOURCES的SQL语句如下:

1
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语句如下:

1
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的版本号。通过以下代码:

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

其中更新CMS_ONLINE_RESOURCES的版本号的SQL语句如下:

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

更新CMS_ONLINE_STRUCTURE的SQL语句 如下:

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


e.创建online文件的内容。通过以下代码:

1
2
3
4
5
6
7
8
// create/update the content
   m_driverManager.getVfsDriver(dbc).createOnlineContent(
                 dbc,
                 offlineFile.getResourceId(),
                 offlineFile.getContents(),
                 publishTag,
                 true ,
                 needToUpdateContent);

它执行的SQL语句如下:

1
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信息。通过以下代码:

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

它会把去写CMS_ONLINE_PROPERTIES文件,因为我的发布的文件没有配置properties,所以调试器跳过了这一段。


g.写入新的online访问控制列表。通过以下代码:

1
2
3
4
5
6
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)中分别创建新条目。





本文转自 charles_wang888 51CTO博客,原文链接:http://blog.51cto.com/supercharles888/1584877,如需转载请自行联系原作者
目录
相关文章
|
2月前
|
供应链 Java 测试技术
开发Java应用时如何用好Log
开发Java应用时如何用好Log
76 3
|
8月前
|
JavaScript
作者经历过的各种报错处理--干货持续分享
作者经历过的各种报错处理--干货持续分享
36 0
jira学习案例39-加载中和错误的处理
jira学习案例39-加载中和错误的处理
63 0
jira学习案例39-加载中和错误的处理
|
Kubernetes API 数据安全/隐私保护
[kustz] 从零开始写一个 k8s 应用发布工具(含源码和过程)
你有没有想过, 如果要在 kubernetes 集群中 **发布** 一个最基本的 **无状态服务**, 并 **提供** 给用户访问, 最少需要配置几个 `K8S Config API` ? 自己写一个, 提升自己。
180 0
[kustz] 从零开始写一个 k8s 应用发布工具(含源码和过程)
|
测试技术
软件测试面试题:BUG管理工具的跟踪过程(用BugZilla为例子)
软件测试面试题:BUG管理工具的跟踪过程(用BugZilla为例子)
106 0