引入:

在我们第一次打开openCMS主工作区的时候,我们的请求地址是:http://localhost:8080/opencms/opencms/system/workplace/views/workplace.jsp, 它会打开一个类似如下的工作区界面:

wKioL1R6iHCykAa0AAQP1JQwhwY057.jpg

细心的读者会发现,此工作区文件workplace.jsp在整个opencms.war中根本就没有,那么这个页面到底来自哪里呢?


解决:

我们做了调试,当请求到达opencms时候,它最早会进入OpenCmsServlet的doGet()方法,它进而会去调用OpenCmsCore的showResource()方法:

protected void showResource(HttpServletRequestreq,HttpServletResponse res) {
 
        CmsObject cms = null;
        try {
            cms = initCmsObject(req, res);
 
….
 
            // user is initialized, now deliver the requested resource
            CmsResource resource = initResource(cms, cms.getRequestContext().getUri(),req,res);
            if (resource != null){
                // a file was read, go on process it
                m_resourceManager.loadResource(cms, resource, req, res);
                m_sessionManager.updateSessionInfo(cms, req);
            }
 
        } catch (Throwable t) {
            errorHandling(cms, req, res, t);
        }
}

此方法从宏观上做了几件大事:

(1)初始化CMS对象和当前用户配置信息(这个我们没兴趣)

(2)根据请求URL获取Resource对象(这应该是一个对象ID)。

(3)让CmsResourceManager载入Resource对象(这应该是一个真实页面)

我们逐个分析。



亮点1: 如何根据请求URL获取Resource对象:

对于根据请求URL获取Resource对象,可以看到它首先调用了CmsResource的initResource()方法,而initResource()方法会调用CmsObject的readDefaultFile方法来从CMS的VFS(虚拟文件系统)中获取指定的Resource. 这个readDefaultFile会根据请求的资源URI来创建一个UUID,然后获取资源对象,如果获取不成功,则直接通过URI来获取资源对象。这里从调试过程可以看出,它是直接通过URI来获取资源对象:

wKiom1R6t_PAXyTxAAN29YBfRUo564.jpg

而获取方式则是通过CmsSecurityManager的readResource()方法,它进而会委托CmsDriverManager的readResource()方法来从数据库中获取Resource对象:

public CmsResource readResource(CmsDbContext dbc, String resourcePath,CmsResourceFilter filter)
   throwsCmsDataAccessException {
 
        CmsUUID projectId =getProjectIdForContext(dbc);
        // please note: the filter will be applied in the security manager later
        CmsResource resource =getVfsDriver(dbc).readResource(dbc, projectId, resourcePath,filter.includeDeleted());
 
        // context dates need to be updated
        updateContextDates(dbc, resource);
 
        // return the resource
        return resource;
}

从调试结果可以看,它会构建以下的查询语句:

SELECT CMS_OFFLINE_STRUCTURE.STRUCTURE_ID,CMS_OFFLINE_STRUCTURE.RESOURCE_ID,CMS_OFFLINE_STRUCTURE.RESOURCE_PATH,CMS_OFFLINE_STRUCTURE.STRUCTURE_STATE,CMS_OFFLINE_STRUCTURE.DATE_RELEASED,CMS_OFFLINE_STRUCTURE.DATE_EXPIRED,CMS_OFFLINE_STRUCTURE.STRUCTURE_VERSION,CMS_OFFLINE_RESOURCES.RESOURCE_ID,CMS_OFFLINE_RESOURCES.RESOURCE_TYPE,CMS_OFFLINE_RESOURCES.RESOURCE_FLAGS,CMS_OFFLINE_RESOURCES.RESOURCE_STATE,CMS_OFFLINE_RESOURCES.DATE_CREATED,CMS_OFFLINE_RESOURCES.DATE_LASTMODIFIED,CMS_OFFLINE_RESOURCES.USER_CREATED,CMS_OFFLINE_RESOURCES.USER_LASTMODIFIED,CMS_OFFLINE_RESOURCES.PROJECT_LASTMODIFIEDASLOCKED_IN_PROJECT,CMS_OFFLINE_RESOURCES.RESOURCE_SIZE,CMS_OFFLINE_RESOURCES.DATE_CONTENT,CMS_OFFLINE_RESOURCES.SIBLING_COUNT,CMS_OFFLINE_RESOURCES.RESOURCE_VERSION,CMS_OFFLINE_RESOURCES.PROJECT_LASTMODIFIEDFROM CMS_OFFLINE_STRUCTURE,CMS_OFFLINE_RESOURCES WHERECMS_OFFLINE_STRUCTURE.RESOURCE_PATH='/system/workplace/views/workplace.jsp' ANDCMS_OFFLINE_STRUCTURE.RESOURCE_ID=CMS_OFFLINE_RESOURCES.RESOURCE_ID ORDER BYCMS_OFFLINE_STRUCTURE.STRUCTURE_STATE ASC

此查询的用意是根据请求的RESOURCE_PATH来查找CMS离线结构表CMS_OFFLINE_STRUCTURE(很好理解,因为这块是框架层面的,不是对外作为站点内容给用户看的,所以这些资源应该是offline的结构资源),获取对应的RESOURCE_ID和其他一些相关信息:

wKiom1R6uQ3zjILCAAIR1V0qPY8512.jpg

这样,我们就获得了资源ID为9c0a79be-f058-11d8-ba65-db38cc8f19d7 的资源,这就是请求workplace.jsp的资源ID。此资源对象的信息从调试器中获知为:

[org.opencms.file.CmsResource, path:/system/workplace/views/workplace.jsp, structure id840ccc67-11b7-11db-91cd-fdbae480bac9, resource id:9c0a79be-f058-11d8-ba65-db38cc8f19d7, type id: 4, folder: false, flags: 0, project:00000000-0000-0000-0000-000000000000, state: 0, date created: Mon Jun 2716:00:00 CST 2005, user created: c300ba5c-01e8-3727-b305-5dcc9ccae1ee, datelastmodified: Mon Jan 29 18:25:58 CST 2007, 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 3008:06:18 CST 2014, size: 1546, sibling count: 2, version: 1]


亮点2:如何根据Resource对象获得真实Resource并进行渲染:

这就是靠CmsResourceManager的loadResource()方法来做具体的文件读取,然后通过CmsSessionManager来更新会话了。具体略,其实质就是通过刚才的ResourceID查找CMS_CONTENT表获取对应的页面JSP文件,然后利用合适的加载器来加载。因为fileType是jsp,所以最终使用的加载器是CmsJspLoader.

我们查找数据库CMS_CONTENTS表,果然找到了对应的记录:

wKioL1R6ux3CX4KxAAFYzrzsgrc328.jpg

将该BLOB打开,我们查看内容:

wKioL1R6iijzDAkVAAO75a1grhY292.jpg

发现它是用的frameset结构,之前会先外部引入一些js 文件,比如explorer.js,ajax.js,top_js.jsp,之后在frameset中,又会先包含top_head.jsp,主体和top_foot.jsp ,而这一切和我们从Firebug的网络视图中看到的是一样的:

wKiom1R6iljgUsX9AAIzEdjkGuo132.jpg


总结:

(1)这些最终请求的资源文件都以BLOB形式存放在数据库中而不是直接在opencms.war中。

(2)从请求URI到资源ID获取通过查找CMS_OFFLINE_STRUCTURE和CMS_OFFLINE_RESOURCES这两张表。CmsResource,CmsObject,CmsSecurityManager,CmsDriverManager都参与其中。

(3)从资源ID到真实页面的JSP文件通过查找CMS_CONTENTS表,CmsResourceManager参与其中。