结论:

当在Liferay中用管理员登录,导航到控制面板->Documents and Media ,在指定文件夹下添加BasicDocument时,服务器做了如下的事情:

(1) 在DLFILEENTRY表中添加一条记录代表被添加的文档。

(2) 在DLFILEENTRYVERSION表中添加一条记录,通过fileEntryId外键关联到DLFILEENTRY表,用来记录刚被添加文档的版本信息。

(3) 在DLFOLDER表中更新最新post提交的时间戳。

(4) 根据服务器对于com.liferay.portlet.documentlibrary.store的具体实现的不同,先对文件进行病毒扫描(在portal.properties 中有开关),然后把上传的资源文件存入到Store的某个具体位置。

Store有5种实现,我至少可以保证,如果用的是FileSystemStore,那么这个资源文件被放入$liferay_home/data/document_library目录,并且最终文件名不再是上传的文件名,而是<version_number>.如果使用的是DBStore,那么这个资源文件最终会被放在数据库中DLCONTENT表中,并且资源文件(比如图片)以BLOB的形式存储。

(5)在ASSETENTRY表中添加一条记录,它通过classpk外键关联到DLFILEENTRY,因为File也是一种资产,所以必须在这张表中也留下记录。

 

 

具体分析:

 Liferay控制面板中,当创建了文件夹,然后要在其中添加某个Basic Document:

 

查看浏览器debug信息:

可以发现,它调用的struts_action/document_library/edit_file_entry

 

我们去struts-config.xml中去找:

 
  
  1. <action path="/document_library/edit_file_entry" type="com.liferay.portlet.documentlibrary.action.EditFileEntryAction"> 
  2.             <forward name="portlet.document_library.edit_file_entry" path="portlet.document_library.edit_file_entry" /> 
  3.             <forward name="portlet.document_library.error" path="portlet.document_library.error" /> 
  4.         </action> 

可以发现, 它会forwardportlet.document_library.edit_file_entry的路径名下找页面:

我们去tiles-def.xml中找匹配:

 
  
  1. <definition name="portlet.document_library.edit_file_entry" extends="portlet.document_library"> 
  2.         <put name="portlet_content" value="/portlet/document_library/edit_file_entry.jsp" /> 
  3.     </definition> 

 

所以,它最终会访问/portlet/document_library/edit_file_entry.jsp页面:

这个页面会吧你刚才选择的目录列出来,比如我们刚才选择了abcde目录,那么在这里,它就会显示出来:

对应的显示代码是

 
  
  1. <aui:field-wrapper label="folder"> 
  2.             <aui:a href="<%= viewFolderURL %>" id="folderName"><%= folderName %></aui:a> 
  3.  
  4.             <c:if test="<%= referringPortletResourceRootPortletId.equals(PortletKeys.ASSET_PUBLISHER) %>"> 
  5.                 <aui:button name="openFolderSelectorButton" onClick='<%= renderResponse.getNamespace() + "openFolderSelector();" %>' value="select" /> 
  6.  
  7.                 <
  8.                 String taglibRemoveFolder = "Liferay.Util.removeFolderSelection('folderId', 'folderName', '" + renderResponse.getNamespace() + "');"; 
  9.                 %> 
  10.  
  11.                 <aui:button disabled="<%= folderId <= 0 %>" name="removeFolderButton" onClick="<%= taglibRemoveFolder %>" value="remove" /> 
  12.             </c:if> 
  13.         </aui:field-wrapper> 

然后,它会有一个文件上传框:

 
  
  1. <aui:input name="file" type="file"> 
  2.             <aui:validator name="acceptFiles"> 
  3.                 '<%= StringUtil.merge(PrefsPropsUtil.getStringArray(PropsKeys.DL_FILE_EXTENSIONS, StringPool.COMMA)) %>
  4.             </aui:validator> 
  5.         </aui:input> 

它会通过aui框架的校验器来校验被上传的文件扩展名和大小。

输入文件标题和正文的部分我就略过了,不是重点,我们现在想关注的是,到底当点击最下方的"Publish"按钮时发生了什么。

 

深入分析点击"Publish"按钮后发生的事情

对应的代码是:

 
  
  1. <aui:button disabled="<%= checkedOut && !hasLock || (pending && PropsValues.DL_FILE_ENTRY_DRAFTS_ENABLED) %>" name="publishButton" type="submit" value="<%= publishButtonLabel %>" /> 

它会吧整个表单提交,对应代码是:

 
  
  1. <aui:form action="<%= editFileEntryURL %>" cssClass="lfr-dynamic-form" enctype="multipart/form-data" method="post" name="fm" onSubmit='<%= "event.preventDefault(); " + renderResponse.getNamespace() + "saveFileEntry(false);" %>'> 
  2.     <aui:input name="<%= Constants.CMD %>" type="hidden" /> 
  3.     <aui:input name="redirect" type="hidden" value="<%= redirect %>" /> 
  4.     <aui:input name="backURL" type="hidden" value="<%= backURL %>" /> 
  5.     <aui:input name="referringPortletResource" type="hidden" value="<%= referringPortletResource %>" /> 
  6.     <aui:input name="uploadProgressId" type="hidden" value="<%= uploadProgressId %>" /> 
  7.     <aui:input name="repositoryId" type="hidden" value="<%= repositoryId %>" /> 
  8.     <aui:input name="folderId" type="hidden" value="<%= folderId %>" /> 
  9.     <aui:input name="fileEntryId" type="hidden" value="<%= fileEntryId %>" /> 
  10.     <aui:input name="workflowAction" type="hidden" value="<%= WorkflowConstants.ACTION_PUBLISH %>" /> 
  11. .. 

而因为用post提交,所以这些信息都在payload中:

 
  
  1. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  2. Content-Disposition: form-data; name="_20_formDate" 
  3.  
  4. 1341536588996 
  5. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  6. Content-Disposition: form-data; name="_20_cmd" 
  7.  
  8. add 
  9. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  10. Content-Disposition: form-data; name="_20_redirect" 
  11.  
  12. http://localhost:8080/group/control_panel/manage?p_p_id=20&p_p_lifecycle=0&p_p_state=maximized&p_p_mode=view&doAsGroupId=19&refererPlid=12655&_20_refererPlid=12655&_20_doAsGroupId=19&_20_struts_action=%2Fdocument_library%2Fview&_20_folderId=16904 
  13. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  14. Content-Disposition: form-data; name="_20_backURL" 
  15.  
  16.  
  17. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  18. Content-Disposition: form-data; name="_20_referringPortletResource" 
  19.  
  20.  
  21. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  22. Content-Disposition: form-data; name="_20_uploadProgressId" 
  23.  
  24. dlFileEntryUploadProgress 
  25. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  26. Content-Disposition: form-data; name="_20_repositoryId" 
  27.  
  28. 19 
  29. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  30. Content-Disposition: form-data; name="_20_folderId" 
  31.  
  32. 16904 
  33. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  34. Content-Disposition: form-data; name="_20_fileEntryId" 
  35.  
  36. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  37. Content-Disposition: form-data; name="_20_workflowAction" 
  38.  
  39. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  40. Content-Disposition: form-data; name="_20_file"; filename="charles_wang.jpg" 
  41. Content-Type: p_w_picpath/jpeg 
  42.  
  43.  
  44. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  45. Content-Disposition: form-data; name="_20_title" 
  46.  
  47. title of new document 
  48. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  49. Content-Disposition: form-data; name="_20_description" 
  50.  
  51. a new document which contains a p_w_picpath and resides in abcde folder 
  52. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  53. Content-Disposition: form-data; name="_20_fileEntryTypeId" 
  54.  
  55. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  56. Content-Disposition: form-data; name="_20_assetTagNames" 
  57.  
  58.  
  59. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  60. Content-Disposition: form-data; name="_20_assetLinkSearchContainerPrimaryKeys" 
  61.  
  62.  
  63. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  64. Content-Disposition: form-data; name="_20_assetLinkEntryIds" 
  65.  
  66.  
  67. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  68. Content-Disposition: form-data; name="_20_inputPermissionsShowOptions" 
  69.  
  70. false 
  71. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  72. Content-Disposition: form-data; name="_20_inputPermissionsViewRole" 
  73.  
  74. Guest 
  75. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  76. Content-Disposition: form-data; name="_20_guestPermissions" 
  77.  
  78. ADD_DISCUSSION 
  79. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  80. Content-Disposition: form-data; name="_20_guestPermissions" 
  81.  
  82. VIEW 
  83. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  84. Content-Disposition: form-data; name="_20_groupPermissions" 
  85.  
  86. ADD_DISCUSSION 
  87. ------WebKitFormBoundaryHrjra277wXTqeY5D 
  88. Content-Disposition: form-data; name="_20_groupPermissions" 
  89.  
  90. VIEW 
  91. ------WebKitFormBoundaryHrjra277wXTqeY5D-- 

所以,action还是自身页面,不难在struts-config.xml中找到Action类名为EditFileEntryAction

因为我们的cmd是add,所以它会走以下流程:

 
  
  1. public void processAction( 
  2.             ActionMapping mapping, ActionForm form, PortletConfig portletConfig, 
  3.             ActionRequest actionRequest, ActionResponse actionResponse) 
  4.         throws Exception { 
  5.  
  6.         String cmd = ParamUtil.getString(actionRequest, Constants.CMD); 
  7.  
  8.         .. 
  9.             else if (cmd.equals(Constants.ADD) || cmd.equals(Constants.UPDATE) 
  10.                 || cmd.equals(Constants.UPDATE_AND_CHECKIN)) { 
  11.  
  12.                 updateFileEntry(portletConfig, actionRequest, actionResponse); 
  13.             } 
  14.             .. 

 

它会去调用updateFileEntry方法,这就是我们研究的重心:

 
  
  1. protected void updateFileEntry( 
  2.             PortletConfig portletConfig, ActionRequest actionRequest, 
  3.             ActionResponse actionResponse) 
  4.         throws Exception { 
  5.  
  6.         UploadPortletRequest uploadPortletRequest = 
  7.             PortalUtil.getUploadPortletRequest(actionRequest); 
  8.  
  9.         ThemeDisplay themeDisplay = (ThemeDisplay)actionRequest.getAttribute( 
  10.             WebKeys.THEME_DISPLAY); 
  11.  
  12.         String cmd = ParamUtil.getString(uploadPortletRequest, Constants.CMD); 
  13.  
  14.         long fileEntryId = ParamUtil.getLong( 
  15.             uploadPortletRequest, "fileEntryId"); 
  16.  
  17.         long repositoryId = ParamUtil.getLong( 
  18.             uploadPortletRequest, "repositoryId"); 
  19.         long folderId = ParamUtil.getLong(uploadPortletRequest, "folderId"); 
  20.         String sourceFileName = uploadPortletRequest.getFileName("file"); 
  21.         String title = ParamUtil.getString(uploadPortletRequest, "title"); 
  22.         String description = ParamUtil.getString( 
  23.             uploadPortletRequest, "description"); 
  24.         String changeLog = ParamUtil.getString( 
  25.             uploadPortletRequest, "changeLog"); 
  26.         boolean majorVersion = ParamUtil.getBoolean( 
  27.             uploadPortletRequest, "majorVersion"); 
  28.  
  29.         if (folderId > 0) { 
  30.             Folder folder = DLAppServiceUtil.getFolder(folderId); 
  31.  
  32.             if (folder.getGroupId() != themeDisplay.getScopeGroupId()) { 
  33.                 throw new NoSuchFolderException(); 
  34.             } 
  35.         } 
  36.  
  37.         InputStream inputStream = null
  38.  
  39.         try { 
  40.             String contentType = uploadPortletRequest.getContentType("file"); 
  41.  
  42.             long size = uploadPortletRequest.getSize("file"); 
  43.  
  44.             if (cmd.equals(Constants.ADD) && (size == 0)) { 
  45.                 contentType = MimeTypesUtil.getContentType(title); 
  46.             } 
  47.  
  48.             if (cmd.equals(Constants.ADD) || (size > 0)) { 
  49.                 String portletName = portletConfig.getPortletName(); 
  50.  
  51.                 if (portletName.equals(PortletKeys.MEDIA_GALLERY_DISPLAY)) { 
  52.                     String portletResource = ParamUtil.getString( 
  53.                         actionRequest, "portletResource"); 
  54.  
  55.                     PortletPreferences portletPreferences = null
  56.  
  57.                     if (Validator.isNotNull(portletResource)) { 
  58.                         PortletPreferencesFactoryUtil.getPortletSetup( 
  59.                             actionRequest, portletResource); 
  60.                     } 
  61.                     else { 
  62.                         portletPreferences = actionRequest.getPreferences(); 
  63.                     } 
  64.  
  65.                     String[] mimeTypes = DLUtil.getMediaGalleryMimeTypes( 
  66.                         portletPreferences, actionRequest); 
  67.  
  68.                     if (Arrays.binarySearch(mimeTypes, contentType) < 0) { 
  69.                         throw new FileMimeTypeException(contentType); 
  70.                     } 
  71.                 } 
  72.             } 
  73.  
  74.             inputStream = uploadPortletRequest.getFileAsStream("file"); 
  75.  
  76.             ServiceContext serviceContext = ServiceContextFactory.getInstance( 
  77.                 DLFileEntry.class.getName(), actionRequest); 
  78.  
  79.             FileEntry fileEntry = null
  80.  
  81.             if (cmd.equals(Constants.ADD)) { 
  82.                 if (Validator.isNull(title)) { 
  83.                     title = sourceFileName; 
  84.                 } 
  85.  
  86.                 // Add file entry 
  87.  
  88.                 fileEntry = DLAppServiceUtil.addFileEntry( 
  89.                     repositoryId, folderId, sourceFileName, contentType, title, 
  90.                     description, changeLog, inputStream, size, serviceContext); 
  91.  
  92.                 AssetPublisherUtil.addAndStoreSelection( 
  93.                     actionRequest, DLFileEntry.class.getName(), 
  94.                     fileEntry.getFileEntryId(), -1); 
  95.             } 
  96.             else if (cmd.equals(Constants.UPDATE_AND_CHECKIN)) { 
  97.  
  98.                 // Update file entry and checkin 
  99.  
  100.                 fileEntry = DLAppServiceUtil.updateFileEntryAndCheckIn( 
  101.                     fileEntryId, sourceFileName, contentType, title, 
  102.                     description, changeLog, majorVersion, inputStream, 
  103.                     size, serviceContext); 
  104.             } 
  105.             else { 
  106.  
  107.                 // Update file entry 
  108.  
  109.                 fileEntry = DLAppServiceUtil.updateFileEntry( 
  110.                     fileEntryId, sourceFileName, contentType, title, 
  111.                     description, changeLog, majorVersion, inputStream, 
  112.                     size, serviceContext); 
  113.             } 
  114.  
  115.             AssetPublisherUtil.addRecentFolderId( 
  116.                 actionRequest, DLFileEntry.class.getName(), folderId); 
  117.         } 
  118.         finally { 
  119.             StreamUtil.cleanUp(inputStream); 
  120.         } 
  121.     } 

 

从第06-27行就是从post的请求的payload中获取一些参数信息,然后第29行对folderId进行判断,因为我们的folderId为19604,它大于0,所以第30行用DLAppServiceUtil的方法获取这个folder的实例,它最终会调用DLFolderLocalServiceImplgetFolder(folderId)方法:

 
  
  1. public DLFolder getFolder(long folderId) 
  2.         throws PortalException, SystemException { 
  3.  
  4.         return dlFolderPersistence.findByPrimaryKey(folderId); 
  5.     } 

这会发起一个数据库的查询,然后把查出来的结果封装成DLFolder对象:

从这里看出来,这个folder就是我们昨天创建的名字叫"abcde"的folder.

 

然后从40行开始,正式对于这个文档中的文件进行操作。先从40-72行做一些信息提取工作,然后从74行开始正式上传文件,因为我们的cmd是“add",所以它会执行86-95行,我们详细分析:

 

添加FileEntry:

第88-90行最终会调用DLAppServiceImpl类的addFileEntry方法:

 
  
  1. public FileEntry addFileEntry( 
  2.             long repositoryId, long folderId, String sourceFileName, 
  3.             String mimeType, String title, String description, String changeLog, 
  4.             InputStream is, long size, ServiceContext serviceContext) 
  5.         throws PortalException, SystemException { 
  6.  
  7.         if (is == null) { 
  8.             is = new UnsyncByteArrayInputStream(new byte[0]); 
  9.             size = 0
  10.         } 
  11.  
  12.         Repository repository = getRepository(repositoryId); 
  13.  
  14.         FileEntry fileEntry = repository.addFileEntry( 
  15.             folderId, sourceFileName, mimeType, title, description, changeLog, 
  16.             is, size, serviceContext); 
  17.  
  18.         dlAppHelperLocalService.addFileEntry( 
  19.             getUserId(), fileEntry, fileEntry.getFileVersion(), serviceContext); 
  20.  
  21.         return fileEntry; 
  22.     } 

它一共做了2件事情:

 

事情1:

第14行-15行,它最终会调用DLFileEntryLocalServiceImpl类的addFileEntry方法:

 
  
  1. public DLFileEntry addFileEntry( 
  2.             long userId, long groupId, long repositoryId, long folderId, 
  3.             String sourceFileName, String mimeType, String title, 
  4.             String description, String changeLog, long fileEntryTypeId, 
  5.             Map<String, Fields> fieldsMap, File file, InputStream is, long size, 
  6.             ServiceContext serviceContext) 
  7.         throws PortalException, SystemException { 
  8.  
  9.         if ((size == 0) && Validator.isNull(title)) { 
  10.             throw new FileNameException(); 
  11.         } 
  12.  
  13.         // File entry 
  14.  
  15.         User user = userPersistence.findByPrimaryKey(userId); 
  16.         folderId = dlFolderLocalService.getFolderId( 
  17.             user.getCompanyId(), folderId); 
  18.         String name = String.valueOf( 
  19.             counterLocalService.increment(DLFileEntry.class.getName())); 
  20.         String extension = getExtension(title, sourceFileName); 
  21.         fileEntryTypeId = getFileEntryTypeId( 
  22.             DLUtil.getGroupIds(groupId), folderId, fileEntryTypeId); 
  23.         Date now = new Date(); 
  24.  
  25.         validateFile(groupId, folderId, title, extension, file, is); 
  26.  
  27.         long fileEntryId = counterLocalService.increment(); 
  28.  
  29.         DLFileEntry dlFileEntry = dlFileEntryPersistence.create(fileEntryId); 
  30.  
  31.         dlFileEntry.setUuid(serviceContext.getUuid()); 
  32.         dlFileEntry.setGroupId(groupId); 
  33.         dlFileEntry.setCompanyId(user.getCompanyId()); 
  34.         dlFileEntry.setUserId(user.getUserId()); 
  35.         dlFileEntry.setUserName(user.getFullName()); 
  36.         dlFileEntry.setVersionUserId(user.getUserId()); 
  37.         dlFileEntry.setVersionUserName(user.getFullName()); 
  38.         dlFileEntry.setCreateDate(serviceContext.getCreateDate(now)); 
  39.         dlFileEntry.setModifiedDate(serviceContext.getModifiedDate(now)); 
  40.         dlFileEntry.setRepositoryId(repositoryId); 
  41.         dlFileEntry.setFolderId(folderId); 
  42.         dlFileEntry.setName(name); 
  43.         dlFileEntry.setExtension(extension); 
  44.         dlFileEntry.setMimeType(mimeType); 
  45.         dlFileEntry.setTitle(title); 
  46.         dlFileEntry.setDescription(description); 
  47.         dlFileEntry.setFileEntryTypeId(fileEntryTypeId); 
  48.         dlFileEntry.setVersion(DLFileEntryConstants.VERSION_DEFAULT); 
  49.         dlFileEntry.setSize(size); 
  50.         dlFileEntry.setReadCount(DLFileEntryConstants.DEFAULT_READ_COUNT); 
  51.  
  52.         dlFileEntryPersistence.update(dlFileEntry, false); 
  53.  
  54.         // File version 
  55.  
  56.         DLFileVersion dlFileVersion = addFileVersion( 
  57.             user, dlFileEntry, serviceContext.getModifiedDate(now), extension, 
  58.             mimeType, title, description, null, StringPool.BLANK, 
  59.             fileEntryTypeId, fieldsMap, DLFileEntryConstants.VERSION_DEFAULT, 
  60.             size, WorkflowConstants.STATUS_DRAFT, serviceContext); 
  61.  
  62.         dlFileEntry.setFileVersion(dlFileVersion); 
  63.  
  64.         // Folder 
  65.  
  66.         if (folderId != DLFolderConstants.DEFAULT_PARENT_FOLDER_ID) { 
  67.             dlFolderLocalService.updateLastPostDate( 
  68.                 dlFileEntry.getFolderId(), dlFileEntry.getModifiedDate()); 
  69.         } 
  70.  
  71.         // File 
  72.  
  73.         if (file != null) { 
  74.             DLStoreUtil.addFile( 
  75.                 user.getCompanyId(), dlFileEntry.getDataRepositoryId(), name, 
  76.                 false, file); 
  77.         } 
  78.         else { 
  79.             DLStoreUtil.addFile( 
  80.                 user.getCompanyId(), dlFileEntry.getDataRepositoryId(), name, 
  81.                 false, is); 
  82.         } 
  83.  
  84.         return dlFileEntry; 
  85.     } 

所以,总结来说就是(13-52行)在DLFILEENTRY表中添加一条记录,代表了我们刚创建的文件:

然后(54-62行)在DLFILEVERSION表中添加一条记录:

然后(64-69行)在DLFOLDER表中更新最新post提交的时间戳:

然后(73-82行)会添加这个我们被上传的文件(比如 charles_wang.jpg), 如何添加呢?可以一直跟进直到DLStoreImpl类的addFile方法:

 
  
  1. public void addFile( 
  2.             long companyId, long repositoryId, String fileName, 
  3.             boolean validateFileExtension, File file) 
  4.         throws PortalException, SystemException { 
  5.  
  6.         validate(fileName, validateFileExtension, file); 
  7.  
  8.         if (PropsValues.DL_STORE_ANTIVIRUS_ENABLED) { 
  9.             AntivirusScannerUtil.scan(file); 
  10.         } 
  11.  
  12.         store.addFile(companyId, repositoryId, fileName, file); 
  13.     } 

它首先会判断是否开启了store的病毒扫描机制,这个变量定义在portal.properties文件中:

 
  
  1.    # Set this property to true to enable execution of antivirus check when 
  2.    # files are submitted into a store. Setting this value to true will prevent 
  3.    # any potential virus files from entering the store but will not allow for 
  4.    # file quarantines. 
  5.    # 
  6.    dl.store.antivirus.enabled=false 

然后调用store的addFile方法,它会具体根据我们Store的实现来决定如何添加这个资源文件。

 那么我们的服务器上用的是什么样的store呢?可以参见portal.properties中的定义:

 
  
  1.    # Set the name of a class that implements 
  2.    # com.liferay.portlet.documentlibrary.store.Store. The 
  3.    # document library server will use this to persist documents. 
  4.    # 
  5.    #dl.store.impl=com.liferay.portlet.documentlibrary.store.AdvancedFileSystemStore 
  6.    #dl.store.impl=com.liferay.portlet.documentlibrary.store.CMISStore 
  7.    #dl.store.impl=com.liferay.portlet.documentlibrary.store.DBStore 
  8.    dl.store.impl=com.liferay.portlet.documentlibrary.store.FileSystemStore 
  9.    #dl.store.impl=com.liferay.portlet.documentlibrary.store.JCRStore 
  10.    #dl.store.impl=com.liferay.portlet.documentlibrary.store.S3Store 

 

所以,我们会用FileSystemStore作为Store的实现,这也是默认的实现。

 
  
  1. public void addFile( 
  2.             long companyId, long repositoryId, String fileName, InputStream is) 
  3.         throws PortalException, SystemException { 
  4.  
  5.         try { 
  6.             File fileNameVersionFile = getFileNameVersionFile( 
  7.                 companyId, repositoryId, fileName, VERSION_DEFAULT); 
  8.  
  9.             if (fileNameVersionFile.exists()) { 
  10.                 throw new DuplicateFileException(fileNameVersionFile.getPath()); 
  11.             } 
  12.  
  13.             FileUtil.write(fileNameVersionFile, is); 
  14.         } 
  15.         catch (IOException ioe) { 
  16.             throw new SystemException(ioe); 
  17.         } 
  18.     } 

从调试信息上来看,它会先去获取companyId,repositoryId,fileName等信息,最终File对象是服务器节点上一个带版本号的url:

而被上传的文件也被盖头换面了,居然在$tomcat_home/temp目录下,而且名字叫upload_000010.jpg,我们在这个目录下找,是我们所要上传的文件:

 

所以,最终这个文件被保存到了D:\Liferay_Cluster_Enterprise\Node1\liferay-portal-tomcat-6.1.10-ee-ga1\liferay-portal-6.1.10-ee-ga1\data\document_library\1\16904\501 目录下,并且文件名不再是charles_wang.png,而是就叫<version_number>.并且后面不带扩展名了。我们比较这charles_wang.png和这个1.0文件的大小,发现他们是一致的(都是153kb),所以这说明文件已经被复制到了Liferay节点上。

 

进一步研究,如果不使用FileSystemStore会如何。我们在portal-ext.properties dl.store.impl改为DBStore:

 
  
  1. dl.store.impl=com.liferay.portlet.documentlibrary.store.DBStore 

它会去调用DBStore类的addFile代码,最终会调用DBStore类的updateFile代码:

 
  
  1. public void updateFile( 
  2.             long companyId, long repositoryId, String fileName, 
  3.             String versionLabel, File file) 
  4.         throws PortalException, SystemException { 
  5.  
  6.         if (DLContentLocalServiceUtil.hasContent( 
  7.                 companyId, repositoryId, fileName, versionLabel)) { 
  8.  
  9.             throw new DuplicateFileException(fileName); 
  10.         } 
  11.  
  12.         InputStream inputStream = null
  13.  
  14.         try { 
  15.              inputStream = new FileInputStream(file); 
  16.         } 
  17.         catch (FileNotFoundException fnfe) { 
  18.             throw new SystemException(fnfe); 
  19.         } 
  20.  
  21.         DLContentLocalServiceUtil.addContent( 
  22.             companyId, repositoryId, fileName, versionLabel, inputStream, 
  23.             file.length()); 
  24.     } 

它最终会调用DLContentLocalServiceImpl的addContent方法:

 
  
  1. public DLContent addContent( 
  2.             long companyId, long repositoryId, String path, String version, 
  3.             InputStream inputStream, long size) 
  4.         throws SystemException { 
  5.  
  6.         try { 
  7.             long contentId = counterLocalService.increment(); 
  8.  
  9.             DLContent dlContent = dlContentPersistence.create(contentId); 
  10.  
  11.             dlContent.setCompanyId(companyId); 
  12.             dlContent.setRepositoryId(repositoryId); 
  13.             dlContent.setPath(path); 
  14.             dlContent.setVersion(version); 
  15.  
  16.             OutputBlob dataOutputBlob = new OutputBlob(inputStream, size); 
  17.  
  18.             dlContent.setData(dataOutputBlob); 
  19.  
  20.             dlContent.setSize(size); 
  21.  
  22.             dlContentPersistence.update(dlContent, false); 
  23.  
  24.             return dlContent; 
  25.         } 
  26.         finally { 
  27.             StreamUtil.cleanUp(inputStream); 
  28.         } 
  29.     } 

从这里我们可以很清楚的看到,它会吧这个文件的内容以Blob的形式保存,然后连同companyId,repositoryId,version等信息一起写在DLCONTENT数据库表中:

我们复制上述动作时候,不幸发生了如下的异常:

 
  
  1. Caused by: java.lang.ClassCastException: com.liferay.portal.kernel.dao.jdbc.OutputBlob cannot be cast to oracle.sql.BLOB 
  2.     at oracle.jdbc.driver.OraclePreparedStatement.setBlob(OraclePreparedStatement.java:6663) 
  3.     at oracle.jdbc.driver.OraclePreparedStatementWrapper.setBlob(OraclePreparedStatementWrapper.java:128) 

http://issues.liferay.com/browse/LPS-26375上我找到了解决方法,就是修改portal-hbm.xml文件,然后把类型配置正确就可以了:

 
  
  1. <class name="com.liferay.portlet.documentlibrary.model.DLContentDataBlobModel" table="DLContent" lazy="true"> 
  2.         <id name="contentId" column="contentId"> 
  3.             <generator class="foreign"> 
  4.                 <param name="property">com.liferay.portlet.documentlibrary.model.impl.DLContentImpl</param> 
  5.             </generator> 
  6.         </id> 
  7.         <property column="data_" name="dataBlob" type="org.hibernate.type.BlobType" /> 
  8.     </class> 

 把这里的dataBlob的type改为java.sql.Blob就可以了。

为此,我们新建一个文件叫portal-hbm-new.xml,让其复制portal-hbm.xml的所有内容,除了dataBlob的type改为java.sql.Blob。

然后我们在portal-ext.properties中添加如下行,让hibernate映射文件指向我们新建的这个文件:

 
  
  1. #added by charles to fix the dbStore problem ,use the new portal hibernate mapping file 
  2.   hibernate.configs=\ 
  3.         META-INF/mail-hbm.xml,\ 
  4.         META-INF/portal-hbm-new.xml,
  5.         META-INF/ext-hbm.xml 

 这样,我们就上传成功了:

我们检查数据库,在DLCONTENT表中果然找到了blob形式存在的图片:

 

事情2:

第18行-19行,它最终会调用DLAppHelperLocalServiceImpladdFileEntry方法,具体不展开了,结论就是,它会在AssetEntry数据库表中添加一条记录:

 

到此,我们全部分析完了。