引入:
在opencms中,有些资源是刚创建的,有些资源是创建并发布的,这些资源创建时候做了哪些工作呢,他们的存储又如何呢,这是这篇文章需要解决的话题。
分析:
以最简单的文本文件为例,当我们在某目录下(比如叫 /charlesstudy)用向导创建某文本文件时,如下:
它发送的请求为 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<CmsProperty> 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),如下:
然后调用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<CmsProperty> 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()方法中,从宏观上,它又做了几个步骤:
数据库层面的路径检查
设置资源的状态和修改日期
把内存中的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)
我们查询数据库可以看到今天做的实验,都是一些未发布的资源:
他们和我们在cms workplace中看到的是精确一致的:
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形式存储:
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)
总结:
从以上过程可以看出,有以下重要结论:
资源Resource的创建分两个阶段,其中原型阶段创建的资源对象只存在内存中,而实际创建阶段创建的资源对象既会存在在内存中,又会持久化到数据库中。
撇开一些不是太关键的环节,比如资源权限校验,资源唯一性校验,锁校验,创建阶段主要会更新CMS_OFFLINE_STRUCTURE,CMS_OFFLINE_CONTENTS,CMS_OFFLINE_RESOURCE_RELATIONS 三张表,这三张表起的作用分别是存储未发布的资源的结构,实际内容,引用关系。