引入:

在opencms中,有些资源是刚创建的,有些资源是创建并发布的,这些资源创建时候做了哪些工作呢,他们的存储又如何呢,这是这篇文章需要解决的话题。


分析:

以最简单的文本文件为例,当我们在某目录下(比如叫 /charlesstudy)用向导创建某文本文件时,如下:

opencms创建资源深度分析_第1张图片

它发送的请求为 POST http://localhost:8080/opencms/opencms/system/workplace/commons/newresource.jsp


服务器端的响应入口是在CmsNewResource的actionDialog()方法内的case ACTION_SUBMITFORM分支:

public void actionDialog() throwsJspException, ServletException, IOException {
 
        super.actionDialog();
 
        switch (getAction()) {
            caseACTION_SUBMITFORM:
                actionCreateResource();
                if (isResourceCreated()) {
                    actionEditProperties();// redirects only if the edit propertiesoption was checked
                }
                break;
…


在actionCreateResource()方法中:

publicvoidactionCreateResource() throws JspException {
 
        try {
            // calculate the new resource Title property value
            String title = computeNewTitleProperty();
 
            // create the full resource name
            String fullResourceName =computeFullResourceName();
 
            // create the Title and Navigation properties if configured
            I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(getParamNewResourceType());
            List properties = createResourceProperties(fullResourceName, resType.getTypeName(), title);
 
            // create the resource            
            getCms().createResource(fullResourceName, resType.getTypeId(), null, properties);
            setParamResource(fullResourceName);
 
            setResourceCreated(true);
        } catch (Throwable e) {
 
            // error creating file, show error dialog
            includeErrorpage(this, e);
        }

我们可以看到,宏观上,它先获取资源标题(title),资源的全路径(fullResourceName),资源类型(plain,其type=1)和配置的额外属性(properties),如下:

opencms创建资源深度分析_第2张图片

然后调用getCms().createResource(fullResourceName,resType.getTypeId(),null,properties)来做创建工作,它会委托A_CmsResourceType类的createResource()来做资源创建,在其中它又会去委托CmsSecurityManager的createResource()方法对需要创建的资源做存在性校验,最终会调用CmsDriverManager的createResource()方法来做实际的资源创建工作,此方法如下:

public CmsResource createResource(
        CmsDbContext dbc,
        String resourcename,
        inttype,
        byte[] content,
        List properties) throws CmsException,CmsIllegalArgumentException {
 
        String targetName = resourcename;
 
        if (content == null){
            // name based resource creation MUST have a content
            content = newbyte[0];
        }
        intsize;
 
        if (CmsFolder.isFolderType(type)) {
            // must cut of trailing '/' for folder creation
            if (CmsResource.isFolder(targetName)) {
                targetName = targetName.substring(0, targetName.length() - 1);
            }
            size = -1;
        } else {
            size = content.length;
        }
 
        // create a new resource
        CmsResource newResource = new CmsResource(CmsUUID.getNullUUID(), // uuids will be "corrected" later
            CmsUUID.getNullUUID(),
            targetName,
            type,
            CmsFolder.isFolderType(type),
            0,
            dbc.currentProject().getUuid(),
            CmsResource.STATE_NEW,
            0,
            dbc.currentUser().getId(),
            0,
            dbc.currentUser().getId(),
            CmsResource.DATE_RELEASED_DEFAULT,
            CmsResource.DATE_EXPIRED_DEFAULT,
            1,
            size,
            0, // version number does not matter since it will be computed later
            0); // content time will be corrected later
 
        return createResource(dbc, targetName, newResource, content, properties, false);
   }

此方法从宏观上分为2部分,一是原型阶段(代码中标记为 //create a new resource的部分),二是实际创建阶段(它会调用重载的createResource()方法来创建真实的资源并写数据库),我们对其做详细分析。


讨论点1:资源创建的原型阶段:

它会仅仅会填入从actionCreateResource中带入的一般信息,比如project UUID,type,path等,然后其他信息全赋空值,这一步创建的Resource我们称为资源的原型,它创建完只“活”在计算机的内存中,并不做持久化,它的信息如下:

[org.opencms.file.CmsResource, path: /sites/default/charles_study/text-text-file-5,structure id 00000000-0000-0000-0000-000000000000, resource id:00000000-0000-0000-0000-000000000000, type id: 1, folder: false, flags: 0,project: ae97ff1d-7824-11e4-8c0e-28e347ffa92b, state: 2, date created: Thu Jan01 08:00:00 CST 1970, user created: c300ba5c-01e8-3727-b305-5dcc9ccae1ee, datelastmodified: Thu Jan 01 08:00:00 CST 1970, 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: Thu Jan 0108:00:00 CST 1970, size: 0, sibling count: 1, version: 0]


讨论点2:资源实际创建阶段:

这段事情很多,因为篇幅关系,我不贴全部代码了,从宏观上它大体做了如下事情:

a. 获取要创建资源的父目录(parentFolder):

[org.opencms.file.CmsFolder, path:/sites/default/charles_study/, structure id 4bf8b750-785d-11e4-8289-28e347ffa92b,resource id: 4bf8b751-785d-11e4-8289-28e347ffa92b, type id: 0, folder: true,flags: 0, project: ae97ff1d-7824-11e4-8c0e-28e347ffa92b, state: 2, datecreated: Sun Nov 30 14:51:23 CST 2014, user created:c300ba5c-01e8-3727-b305-5dcc9ccae1ee, date lastmodified: Sun Nov 30 14:51:35CST 2014, user lastmodified: c300ba5c-01e8-3727-b305-5dcc9ccae1ee, datereleased: Thu Jan 01 08:00:00 CST 1970, date expired: Sun Aug 17 15:12:55 CST292278994, date content: Thu Jan 01 07:59:59 CST 1970, size: -1, sibling count:1, version: 1]


b.判断父目录的锁(lock)。因为父目录如果被其他用户锁住,则我们不可以在这个目录下创建资源。反过来,如果父目录没加锁或者虽然加了但是锁的owner为当前用户,则可以在其下创建资源。


c.  判断资源的名字是否使用了forbidden字符。


d.  在内存中构建新的resource对象。因为有了structureId,resourceId和其他信息,现在构建的新对象要比原型阶段丰富多了:

[org.opencms.file.CmsResource, path:/sites/default/charles_study/text-text-file-5, structure id5b97ca24-786d-11e4-8289-28e347ffa92b, resource id:5d577b85-786d-11e4-8289-28e347ffa92b, type id: 1, folder: false, flags: 0,project: ae97ff1d-7824-11e4-8c0e-28e347ffa92b, state: 2, date created: Thu Jan01 08:00:00 CST 1970, user created: c300ba5c-01e8-3727-b305-5dcc9ccae1ee, datelastmodified: Thu Jan 01 08:00:00 CST 1970, 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: Thu Jan 0108:00:00 CST 1970, size: 0, sibling count: 1, version: 0]


e.把resource对象写入数据库。写的代码位于CmsVfsDriver的createResource()方法中,从宏观上,它又做了几个步骤:

  1. 据库层面的路径检查

  2. 设置资源的状态和修改日期

  3. 把内存中的resource对象更新到CMS_OFFLINE_STRUCTURE表中。它执行的SQL语句是:

INSERT INTO CMS_OFFLINE_STRUCTURE(STRUCTURE_ID,RESOURCE_ID,RESOURCE_PATH,STRUCTURE_STATE,DATE_RELEASED,DATE_EXPIRED,PARENT_ID,STRUCTURE_VERSION)VALUES ('273e3066-786e-11e4-8289-28e347ffa92b','273e3067-786e-11e4-8289-28e347ffa92b','/sites/default/charles_study/test-text-file-5',2,0,9223372036854775807,'4bf8b750-785d-11e4-8289-28e347ffa92b',0)

  我们查询数据库可以看到今天做的实验,都是一些未发布的资源:

opencms创建资源深度分析_第3张图片

他们和我们在cms workplace中看到的是精确一致的:

opencms创建资源深度分析_第4张图片

4. 创建文件内容。它是通过CmsVfsDriver()的createContent()方法来实现的,并且对于文件内容<2KB则直接读取,对于>2KB的文件内容通过打开一个输入流来读取,最后把该文件的内容写入CMS_OFFLINE_CONTENTS表中,它执行的SQL语句是:

INSERT INTO CMS_OFFLINE_CONTENTS (RESOURCE_ID,FILE_CONTENT)VALUES ('273e3067-786e-11e4-8289-28e347ffa92b',x'')

写入的文件以BLOB形式存储:

opencms创建资源深度分析_第5张图片

5.  修复所有引用该资源的关系。它通过调用repairBrokenRelations()方法实现,它会更新CMS_OFFLINE_RESOURCE_RELATIONS表。它执行的SQL语句是:

UPDATE CMS_OFFLINE_RESOURCE_RELATIONS LEFT JOINCMS_OFFLINE_STRUCTURE ON CMS_OFFLINE_RESOURCE_RELATIONS.RELATION_TARGET_ID =CMS_OFFLINE_STRUCTURE.STRUCTURE_ID SET RELATION_TARGET_ID ='273e3066-786e-11e4-8289-28e347ffa92b' WHERECMS_OFFLINE_RESOURCE_RELATIONS.RELATION_TARGET_PATH ='/sites/default/charles_study/test-text-file-5' ANDCMS_OFFLINE_STRUCTURE.STRUCTURE_ID IS NULL


f.锁住刚创建的新资源,锁类型为排它锁(CmsLockType.EXCLUSIVE)


总结:

从以上过程可以看出,有以下重要结论:

  1. 资源Resource的创建分两个阶段,其中原型阶段创建的资源对象只存在内存中,而实际创建阶段创建的资源对象既会存在在内存中,又会持久化到数据库中。

  2. 撇开一些不是太关键的环节,比如资源权限校验,资源唯一性校验,锁校验,创建阶段主要会更新CMS_OFFLINE_STRUCTURE,CMS_OFFLINE_CONTENTS,CMS_OFFLINE_RESOURCE_RELATIONS 三张表,这三张表起的作用分别是存储未发布的资源的结构,实际内容,引用关系。