结论:

当在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. <c:if test="<%= referringPortletResourceRootPortletId.equals(PortletKeys.ASSET_PUBLISHER) %>">
  4. <aui:button name="openFolderSelectorButton" onClick='<%= renderResponse.getNamespace() + "openFolderSelector();" %>' value="select" />
  5. <%
  6. String taglibRemoveFolder = "Liferay.Util.removeFolderSelection('folderId', 'folderName', '" + renderResponse.getNamespace() + "');";
  7. %>
  8. <aui:button disabled="<%= folderId <= 0 %>" name="removeFolderButton" onClick="<%= taglibRemoveFolder %>" value="remove" />
  9. </c:if>
  10. </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. 1341536588996
  4. ------WebKitFormBoundaryHrjra277wXTqeY5D
  5. Content-Disposition: form-data; name="_20_cmd"
  6. add
  7. ------WebKitFormBoundaryHrjra277wXTqeY5D
  8. Content-Disposition: form-data; name="_20_redirect"
  9. 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
  10. ------WebKitFormBoundaryHrjra277wXTqeY5D
  11. Content-Disposition: form-data; name="_20_backURL"
  12. ------WebKitFormBoundaryHrjra277wXTqeY5D
  13. Content-Disposition: form-data; name="_20_referringPortletResource"
  14. ------WebKitFormBoundaryHrjra277wXTqeY5D
  15. Content-Disposition: form-data; name="_20_uploadProgressId"
  16. dlFileEntryUploadProgress
  17. ------WebKitFormBoundaryHrjra277wXTqeY5D
  18. Content-Disposition: form-data; name="_20_repositoryId"
  19. 19
  20. ------WebKitFormBoundaryHrjra277wXTqeY5D
  21. Content-Disposition: form-data; name="_20_folderId"
  22. 16904
  23. ------WebKitFormBoundaryHrjra277wXTqeY5D
  24. Content-Disposition: form-data; name="_20_fileEntryId"
  25. 0
  26. ------WebKitFormBoundaryHrjra277wXTqeY5D
  27. Content-Disposition: form-data; name="_20_workflowAction"
  28. 1
  29. ------WebKitFormBoundaryHrjra277wXTqeY5D
  30. Content-Disposition: form-data; name="_20_file"; filename="charles_wang.jpg"
  31. Content-Type: p_w_picpath/jpeg
  32. ------WebKitFormBoundaryHrjra277wXTqeY5D
  33. Content-Disposition: form-data; name="_20_title"
  34. title of new document
  35. ------WebKitFormBoundaryHrjra277wXTqeY5D
  36. Content-Disposition: form-data; name="_20_description"
  37. a new document which contains a p_w_picpath and resides in abcde folder
  38. ------WebKitFormBoundaryHrjra277wXTqeY5D
  39. Content-Disposition: form-data; name="_20_fileEntryTypeId"
  40. 0
  41. ------WebKitFormBoundaryHrjra277wXTqeY5D
  42. Content-Disposition: form-data; name="_20_assetTagNames"
  43. ------WebKitFormBoundaryHrjra277wXTqeY5D
  44. Content-Disposition: form-data; name="_20_assetLinkSearchContainerPrimaryKeys"
  45. ------WebKitFormBoundaryHrjra277wXTqeY5D
  46. Content-Disposition: form-data; name="_20_assetLinkEntryIds"
  47. ------WebKitFormBoundaryHrjra277wXTqeY5D
  48. Content-Disposition: form-data; name="_20_inputPermissionsShowOptions"
  49. false
  50. ------WebKitFormBoundaryHrjra277wXTqeY5D
  51. Content-Disposition: form-data; name="_20_inputPermissionsViewRole"
  52. Guest
  53. ------WebKitFormBoundaryHrjra277wXTqeY5D
  54. Content-Disposition: form-data; name="_20_guestPermissions"
  55. ADD_DISCUSSION
  56. ------WebKitFormBoundaryHrjra277wXTqeY5D
  57. Content-Disposition: form-data; name="_20_guestPermissions"
  58. VIEW
  59. ------WebKitFormBoundaryHrjra277wXTqeY5D
  60. Content-Disposition: form-data; name="_20_groupPermissions"
  61. ADD_DISCUSSION
  62. ------WebKitFormBoundaryHrjra277wXTqeY5D
  63. Content-Disposition: form-data; name="_20_groupPermissions"
  64. VIEW
  65. ------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. String cmd = ParamUtil.getString(actionRequest, Constants.CMD);
  6. ..
  7. else if (cmd.equals(Constants.ADD) || cmd.equals(Constants.UPDATE)
  8. || cmd.equals(Constants.UPDATE_AND_CHECKIN)) {
  9. updateFileEntry(portletConfig, actionRequest, actionResponse);
  10. }
  11. ..

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

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

从第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. return dlFolderPersistence.findByPrimaryKey(folderId);
  4. }

这会发起一个数据库的查询,然后把查出来的结果封装成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. if (is == null) {
  7. is = new UnsyncByteArrayInputStream(new byte[0]);
  8. size = 0;
  9. }
  10. Repository repository = getRepository(repositoryId);
  11. FileEntry fileEntry = repository.addFileEntry(
  12. folderId, sourceFileName, mimeType, title, description, changeLog,
  13. is, size, serviceContext);
  14. dlAppHelperLocalService.addFileEntry(
  15. getUserId(), fileEntry, fileEntry.getFileVersion(), serviceContext);
  16. return fileEntry;
  17. }

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

所以,总结来说就是(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. validate(fileName, validateFileExtension, file);
  6. if (PropsValues.DL_STORE_ANTIVIRUS_ENABLED) {
  7. AntivirusScannerUtil.scan(file);
  8. }
  9. store.addFile(companyId, repositoryId, fileName, file);
  10. }

它首先会判断是否开启了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. #
  2. # Set the name of a class that implements
  3. # com.liferay.portlet.documentlibrary.store.Store. The
  4. # document library server will use this to persist documents.
  5. #
  6. #dl.store.impl=com.liferay.portlet.documentlibrary.store.AdvancedFileSystemStore
  7. #dl.store.impl=com.liferay.portlet.documentlibrary.store.CMISStore
  8. #dl.store.impl=com.liferay.portlet.documentlibrary.store.DBStore
  9. dl.store.impl=com.liferay.portlet.documentlibrary.store.FileSystemStore
  10. #dl.store.impl=com.liferay.portlet.documentlibrary.store.JCRStore
  11. #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. try {
  5. File fileNameVersionFile = getFileNameVersionFile(
  6. companyId, repositoryId, fileName, VERSION_DEFAULT);
  7. if (fileNameVersionFile.exists()) {
  8. throw new DuplicateFileException(fileNameVersionFile.getPath());
  9. }
  10. FileUtil.write(fileNameVersionFile, is);
  11. }
  12. catch (IOException ioe) {
  13. throw new SystemException(ioe);
  14. }
  15. }

从调试信息上来看,它会先去获取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. if (DLContentLocalServiceUtil.hasContent(
  6. companyId, repositoryId, fileName, versionLabel)) {
  7. throw new DuplicateFileException(fileName);
  8. }
  9. InputStream inputStream = null;
  10. try {
  11. inputStream = new FileInputStream(file);
  12. }
  13. catch (FileNotFoundException fnfe) {
  14. throw new SystemException(fnfe);
  15. }
  16. DLContentLocalServiceUtil.addContent(
  17. companyId, repositoryId, fileName, versionLabel, inputStream,
  18. file.length());
  19. }

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

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

从这里我们可以很清楚的看到,它会吧这个文件的内容以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数据库表中添加一条记录:

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

转载于:https://blog.51cto.com/supercharles888/921761

Liferay 控制面板在指定文件夹添加Basic Document流程分析相关推荐

  1. VB得到指定文件夹下的文件列表

    代码如下: Function GetFileList(ByVal Path As String, ByRef FileName() As String, Optional fExp As String ...

  2. android 自动下一首,Android播播放完SD卡指定文件夹音乐之后,自动播放下一首

    最近做一个项目,需要连续播放音乐,播放完一首歌之后,自动播放完下一首歌.不要重复播放. 代码如下: package com.example.asyncplayer_ex; import java.io ...

  3. SpringBoot 项目将文件图片资源上传到本地静态资源文件夹下(指定文件夹下)

    1.SpringBoot 项目将文件图片资源上传到本地静态资源文件夹下(指定文件夹下) 最终效果: 前端浏览本地文件,点击上传至本地resources/static/images/imgWall下 2 ...

  4. Winform中选取指定文件夹并获取其下所有文件

    场景 Winform中选取指定文件夹,并获取该文件夹下所有文件名,不包含子文件夹.考虑子文件夹可以使用递归实现. 注: 博客: BADAO_LIUMANG_QIZHI的博客_霸道流氓气质_CSDN博客 ...

  5. python解压到指定文件夹_在Python中压缩和解压文件

    Python部落(python.freelycode.com)组织翻译,禁止转载,欢迎转发. 如果你已经使用计算机一段时间,你可能遇到了.zip扩展名的文件.它们是可以保存许多其他文件,文件夹和子文件 ...

  6. XP下,文件夹添加右键命令行

    原文:XP下,文件夹添加右键命令行 总共有3种方式: --------------------1---------------------------------------------------- ...

  7. bat递归查找指定文件_批处理脚本遍历指定文件夹下的文件

    批处理脚本 1. 遍历指定文件夹下的文件 1.1 命令解释 命令: for [参数] %%变量名 in (匹配符) do (执行的命令) 切记:每个指令之间必须以空格隔开,in 与 ( 之间有空格,d ...

  8. 批量修改指定文件夹里面相同类型文件的扩展名(转自Github,原作者Crag Richards)

    batch_rename_file.py 批量修改指定文件夹里面相同类型文件的扩展名(转自Github,原作者Crag Richards) ''' 主要思路: 1.创建一个batch_rename函数 ...

  9. springboot使用FileAlterationMonitor完成对指定文件夹下面指定文件的动态监控

    使用common-io包 <dependency> <groupId>commons-io</groupId> <artifactId>commons- ...

最新文章

  1. qt工程在linux系统里颜色显示错误_【飞凌嵌入式RK3399开发板试用体验】+QT开发环境搭建测试(二)...
  2. 【c语言】输入两个数,交换这两个数后,再输出
  3. python接口测试-认识GET请求
  4. JDK/Java 14 正式发布!然而我还在用 Java 8...
  5. NMS和soft-nms算法
  6. Ecshop后台流量分析--地区分布的地名全是乱码
  7. 2014/08/13 – Backbonejs
  8. 后年将有60亿部手机!
  9. html页面显示html代码怎么写,求助这段代码如何转换成正常可看的HTML页面
  10. 学习强制删除正在运行的文件
  11. windows配置java运行环境
  12. python map、filter、reduce
  13. 光华科技光刻胶_光刻胶概念走强,6天5板!21只光刻胶概念出炉!(名单)
  14. Jenkins:项目配置
  15. factorybean 代理类不能按照类型注入_彻底搞懂依赖注入(一)Bean实例创建过程
  16. Win11添加新的Microsoft Teams集成:共享屏幕变得更容易
  17. 曲线拟合最小二乘法优缺点_最小二乘法、回归分析法、灰色预测法、决策论、神经网络等5个算法的使用范围及优缺点是什么?...
  18. Electron客户端的自动升级方案-2022版
  19. html中cursor的属性,cursor怎么用?CSS中cursor属性的使用方法以及可选值的解析
  20. Python期末考试题库

热门文章

  1. ios 设置属性的center_【从0到1的Stata图表学习1】图例设置
  2. css 实现一个尖角_一个讲述了 CSS 相关的技巧、动画实现 的开源项目(60篇相关文章)...
  3. python怎么存储数据_Python:如何在类中存储数据并继承
  4. 触发起名字使用正则_好名字一定在字音、字形、字意上比较吉利
  5. sqlserver连接字符串_【自学C#】|| 笔记 39 SQL server 连接数据库
  6. 计算机用语优秀怎么算,计算机专业用语
  7. 计算机仿真在哪学,计算机仿真软件有哪些
  8. C++ 二进制文件写操作
  9. 如何实现一个简单的RPC
  10. group by(mysql oracle的区别) 的基本用法