From 268a6b9603a77ed3f59e25cf0354c444a6b0d850 Mon Sep 17 00:00:00 2001 From: srosse <none@none> Date: Tue, 28 May 2013 14:28:57 +0200 Subject: [PATCH] OO-617: move the BLOBs from emails attachments to the file system --- .../persistence/_spring/core_persistence.xml | 4 +- .../olat/core/util/mail/MailAttachment.java | 21 ++ .../org/olat/core/util/mail/MailModule.java | 18 ++ .../core/util/mail/manager/MailManager.java | 188 +++++++++++++++--- .../util/mail/model/DBMailAttachment.hbm.xml | 15 +- .../util/mail/model/DBMailAttachment.java | 32 ++- .../util/mail/ui/MailAttachmentMapper.java | 64 +----- .../util/vfs}/FileStorage.java | 61 +++--- .../ims/qti/qpool/QTIExportProcessor.java | 6 +- .../ims/qti/qpool/QTIImportProcessor.java | 8 +- .../qti/qpool/QTIQPoolServiceProvider.java | 4 +- .../qpool/impl/FileQPoolServiceProvider.java | 6 +- .../qpool/impl/TextQPoolServiceProvider.java | 6 +- .../manager/AbstractQPoolServiceProvider.java | 2 +- .../qpool/manager/QPoolFileStorage.java | 63 ++++++ .../qpool/manager/QuestionItemDAO.java | 2 +- .../org/olat/upgrade/OLATUpgrade_9_0_0.java | 145 ++++++++++++++ .../olat/upgrade/_spring/upgradeContext.xml | 1 + .../model/DBMailAttachmentData.hbm.xml | 22 ++ .../model/DBMailAttachmentData.java | 35 +++- .../database/mysql/alter_8_4_0_to_9_0_0.sql | 10 + .../database/mysql/setupDatabase.sql | 22 +- .../database/oracle/alter_8_4_0_to_9_0_0.sql | 9 + .../database/oracle/setupDatabase.sql | 6 + .../postgresql/alter_8_4_0_to_9_0_0.sql | 8 + .../database/postgresql/setupDatabase.sql | 22 +- .../ims/qti/qpool/QTIExportProcessorTest.java | 4 +- .../ims/qti/qpool/QTIImportProcessorTest.java | 4 +- .../qpool/manager/FileStorageTest.java | 2 +- 29 files changed, 626 insertions(+), 164 deletions(-) create mode 100644 src/main/java/org/olat/core/util/mail/MailAttachment.java rename src/main/java/org/olat/{modules/qpool/manager => core/util/vfs}/FileStorage.java (78%) create mode 100644 src/main/java/org/olat/modules/qpool/manager/QPoolFileStorage.java create mode 100644 src/main/java/org/olat/upgrade/OLATUpgrade_9_0_0.java create mode 100644 src/main/java/org/olat/upgrade/model/DBMailAttachmentData.hbm.xml rename src/main/java/org/olat/{core/util/mail => upgrade}/model/DBMailAttachmentData.java (76%) diff --git a/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml b/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml index 0b2db1bc748..775bf283801 100644 --- a/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml +++ b/src/main/java/org/olat/core/commons/persistence/_spring/core_persistence.xml @@ -55,7 +55,6 @@ <mapping-file>org/olat/modules/openmeetings/model/OpenMeetingsReference.hbm.xml</mapping-file> <mapping-file>org/olat/properties/Property.hbm.xml</mapping-file> <mapping-file>org/olat/catalog/CatalogEntryImpl.hbm.xml</mapping-file> - <mapping-file>org/olat/upgrade/model/BookmarkImpl.hbm.xml</mapping-file> <mapping-file>org/olat/notifications/PublisherImpl.hbm.xml</mapping-file> <mapping-file>org/olat/notifications/SubscriberImpl.hbm.xml</mapping-file> <mapping-file>org/olat/registration/TemporaryKeyImpl.hbm.xml</mapping-file> @@ -87,6 +86,9 @@ <mapping-file>org/olat/course/db/impl/CourseDBEntryImpl.hbm.xml</mapping-file> <mapping-file>org/olat/modules/coach/model/EfficiencyStatementStatEntry.hbm.xml</mapping-file> + <mapping-file>org/olat/upgrade/model/BookmarkImpl.hbm.xml</mapping-file> + <mapping-file>org/olat/upgrade/model/DBMailAttachmentData.hbm.xml</mapping-file> + <class>org.olat.core.dispatcher.mapper.model.PersistedMapper</class> <class>org.olat.group.model.BusinessGroupParticipantViewImpl</class> <class>org.olat.group.model.BusinessGroupOwnerViewImpl</class> diff --git a/src/main/java/org/olat/core/util/mail/MailAttachment.java b/src/main/java/org/olat/core/util/mail/MailAttachment.java new file mode 100644 index 00000000000..fccdbd9a625 --- /dev/null +++ b/src/main/java/org/olat/core/util/mail/MailAttachment.java @@ -0,0 +1,21 @@ +package org.olat.core.util.mail; + +/** + * + * Initial date: 28.05.2013<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public interface MailAttachment { + + public String getName(); + + public String getPath(); + + public Long getChecksum(); + + public Long getSize(); + + public String getMimetype(); + +} diff --git a/src/main/java/org/olat/core/util/mail/MailModule.java b/src/main/java/org/olat/core/util/mail/MailModule.java index aa8b7926e9d..b105e143465 100644 --- a/src/main/java/org/olat/core/util/mail/MailModule.java +++ b/src/main/java/org/olat/core/util/mail/MailModule.java @@ -19,7 +19,10 @@ */ package org.olat.core.util.mail; +import java.io.File; + import org.olat.core.CoreSpringFactory; +import org.olat.core.commons.modules.bc.FolderConfig; import org.olat.core.configuration.AbstractOLATModule; import org.olat.core.configuration.PersistedProperties; import org.olat.core.extensions.action.GenericActionExtension; @@ -28,6 +31,8 @@ import org.olat.core.util.StringHelper; import org.olat.core.util.WebappHelper; import org.olat.core.util.event.FrameworkStartedEvent; import org.olat.core.util.event.FrameworkStartupEventChannel; +import org.olat.core.util.vfs.LocalFolderImpl; +import org.olat.core.util.vfs.VFSContainer; /** * @@ -47,6 +52,9 @@ public class MailModule extends AbstractOLATModule { private boolean receiveRealMailUserDefaultSetting; private int maxSizeOfAttachments = 5; + private static final String ATTACHMENT_DEFAULT = "/mail"; + private String attachmentsRoot = ATTACHMENT_DEFAULT; + private WebappHelper webappHelper; public MailModule() { @@ -164,6 +172,16 @@ public class MailModule extends AbstractOLATModule { } return maxSizeOfAttachments; } + + public VFSContainer getRootForAttachments() { + String root = FolderConfig.getCanonicalRoot() + attachmentsRoot; + File rootFile = new File(root); + if(!rootFile.exists()) { + rootFile.mkdirs(); + } + VFSContainer rootContainer = new LocalFolderImpl(rootFile); + return rootContainer; + } /** * @return the configured mail host. Can be null, indicating that the system diff --git a/src/main/java/org/olat/core/util/mail/manager/MailManager.java b/src/main/java/org/olat/core/util/mail/manager/MailManager.java index b6c979edcd1..5b1fa75e500 100644 --- a/src/main/java/org/olat/core/util/mail/manager/MailManager.java +++ b/src/main/java/org/olat/core/util/mail/manager/MailManager.java @@ -25,12 +25,16 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.HashSet; import java.util.List; import java.util.Properties; +import java.util.Set; +import java.util.zip.Adler32; import javax.activation.DataHandler; import javax.activation.DataSource; @@ -48,10 +52,10 @@ import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; -import javax.mail.util.ByteArrayDataSource; import javax.persistence.TemporalType; import javax.persistence.TypedQuery; +import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.olat.core.commons.persistence.DB; import org.olat.core.commons.persistence.PersistentObject; @@ -60,16 +64,17 @@ import org.olat.core.id.Identity; import org.olat.core.id.OLATResourceable; import org.olat.core.id.UserConstants; import org.olat.core.manager.BasicManager; +import org.olat.core.util.Encoder; import org.olat.core.util.StringHelper; import org.olat.core.util.WebappHelper; import org.olat.core.util.mail.ContactList; +import org.olat.core.util.mail.MailAttachment; import org.olat.core.util.mail.MailContext; import org.olat.core.util.mail.MailModule; import org.olat.core.util.mail.MailerResult; import org.olat.core.util.mail.MailerSMTPAuthenticator; import org.olat.core.util.mail.model.DBMail; import org.olat.core.util.mail.model.DBMailAttachment; -import org.olat.core.util.mail.model.DBMailAttachmentData; import org.olat.core.util.mail.model.DBMailImpl; import org.olat.core.util.mail.model.DBMailRecipient; import org.olat.core.util.notifications.NotificationsManager; @@ -77,6 +82,11 @@ import org.olat.core.util.notifications.Publisher; import org.olat.core.util.notifications.PublisherData; import org.olat.core.util.notifications.Subscriber; import org.olat.core.util.notifications.SubscriptionContext; +import org.olat.core.util.vfs.FileStorage; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.core.util.vfs.VFSItem; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.core.util.vfs.VFSManager; /** * @@ -93,6 +103,7 @@ public class MailManager extends BasicManager { private final MailModule mailModule; private DB dbInstance; + private FileStorage attachmentStorage; private NotificationsManager notificationsManager; private static MailManager INSTANCE; @@ -127,6 +138,9 @@ public class MailManager extends BasicManager { * [used by Spring] */ public void init() { + VFSContainer root = mailModule.getRootForAttachments(); + attachmentStorage = new FileStorage(root); + PublisherData pdata = getPublisherData(); SubscriptionContext scontext = getSubscriptionContext(); notificationsManager.getOrCreatePublisher(scontext, pdata); @@ -187,17 +201,89 @@ public class MailManager extends BasicManager { .getResultList(); } - public DBMailAttachmentData getAttachmentWithData(Long key) { + public DBMailAttachment getAttachment(Long key) { StringBuilder sb = new StringBuilder(); - sb.append("select attachment from ").append(DBMailAttachmentData.class.getName()).append(" attachment") + sb.append("select attachment from ").append(DBMailAttachment.class.getName()).append(" attachment") .append(" where attachment.key=:attachmentKey"); - List<DBMailAttachmentData> mails = dbInstance.getCurrentEntityManager() - .createQuery(sb.toString(), DBMailAttachmentData.class) + List<DBMailAttachment> attachments = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), DBMailAttachment.class) .setParameter("attachmentKey", key) .getResultList(); - if(mails.isEmpty()) return null; - return mails.get(0); + + if(attachments.isEmpty()) { + return null; + } + return attachments.get(0); + } + + public String saveAttachmentToStorage(String name, String mimetype, long checksum, long size, InputStream stream) { + String hasSibling = getAttachmentSibling(name, mimetype, checksum, size); + if(StringHelper.containsNonWhitespace(hasSibling)) { + return hasSibling; + } + + String uuid = Encoder.encrypt(name + checksum); + String dir = attachmentStorage.generateDir(uuid, false); + VFSContainer container = attachmentStorage.getContainer(dir); + String uniqueName = VFSManager.similarButNonExistingName(container, name); + VFSLeaf file = container.createChildLeaf(uniqueName); + VFSManager.copyContent(stream, file); + return dir + uniqueName; + } + + public String getAttachmentSibling(String name, String mimetype, long checksum, long size) { + StringBuilder sb = new StringBuilder(); + sb.append("select attachment from ").append(DBMailAttachment.class.getName()).append(" attachment") + .append(" where attachment.checksum=:checksum and attachment.size=:size and attachment.name=:name"); + if(mimetype == null) { + sb.append(" and attachment.mimetype is null"); + } else { + sb.append(" and attachment.mimetype=:mimetype"); + } + + TypedQuery<DBMailAttachment> query = dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), DBMailAttachment.class) + .setParameter("checksum", new Long(checksum)) + .setParameter("size", new Long(size)) + .setParameter("name", name); + if(mimetype != null) { + query.setParameter("mimetype", mimetype); + } + + List<DBMailAttachment> attachments = query.getResultList(); + if(attachments.isEmpty()) { + return null; + } + return attachments.get(0).getPath(); + } + + public int countAttachment(String path) { + StringBuilder sb = new StringBuilder(); + sb.append("select count(attachment) from ").append(DBMailAttachment.class.getName()).append(" attachment") + .append(" where attachment.path=:path"); + + return dbInstance.getCurrentEntityManager() + .createQuery(sb.toString(), Number.class) + .setParameter("path", path) + .getSingleResult().intValue(); + } + + public VFSLeaf getAttachmentDatas(Long key) { + DBMailAttachment attachment = getAttachment(key); + return getAttachmentDatas(attachment); + } + + public VFSLeaf getAttachmentDatas(MailAttachment attachment) { + String path = attachment.getPath(); + if(StringHelper.containsNonWhitespace(path)) { + VFSContainer root = mailModule.getRootForAttachments(); + VFSItem item = root.resolve(path); + if(item instanceof VFSLeaf) { + return (VFSLeaf)item; + } + } + return null; } public boolean hasNewMail(Identity identity) { @@ -341,13 +427,29 @@ public class MailManager extends BasicManager { } if(delete) { + Set<String> paths = new HashSet<String>(); + //all marked as deleted -> delete the mail List<DBMailAttachment> attachments = getAttachments(mail); for(DBMailAttachment attachment: attachments) { mail = attachment.getMail();//reload from the hibernate session dbInstance.deleteObject(attachment); + if(StringHelper.containsNonWhitespace(attachment.getPath())) { + paths.add(attachment.getPath()); + } } dbInstance.deleteObject(mail); + + //try to remove orphans file + for(String path:paths) { + int count = countAttachment(path); + if(count == 0) { + VFSItem item = mailModule.getRootForAttachments().resolve(path); + if(item instanceof VFSLeaf) { + ((VFSLeaf)item).delete(); + } + } + } } else { for(DBMailRecipient update:updates) { dbInstance.updateObject(update); @@ -651,30 +753,34 @@ public class MailManager extends BasicManager { //add bcc recipients appendRecipients(mail, bccLists, toAddress, bccAddress, false, makeRealMail, result); - dbInstance.saveObject(mail); + dbInstance.getCurrentEntityManager().persist(mail); //save attachments if(attachments != null && !attachments.isEmpty()) { for(File attachment:attachments) { - DBMailAttachmentData data = new DBMailAttachmentData(); - data.setSize(attachment.length()); - data.setName(attachment.getName()); - data.setMimetype(WebappHelper.getMimeType(attachment.getName())); - data.setMail(mail); - - InputStream fis = null; + + FileInputStream in = null; try { - byte[] datas = new byte[(int)attachment.length()]; - fis = new FileInputStream(attachment); - fis.read(datas); - data.setDatas(datas); - dbInstance.saveObject(data); + DBMailAttachment data = new DBMailAttachment(); + data.setSize(attachment.length()); + data.setName(attachment.getName()); + + long checksum = FileUtils.checksum(attachment, new Adler32()).getValue(); + data.setChecksum(new Long(checksum)); + data.setMimetype(WebappHelper.getMimeType(attachment.getName())); + + in = new FileInputStream(attachment); + String path = saveAttachmentToStorage(data.getName(), data.getMimetype(), checksum, attachment.length(), in); + data.setPath(path); + data.setMail(mail); + + dbInstance.getCurrentEntityManager().persist(data); } catch (FileNotFoundException e) { logError("File attachment not found: " + attachment, e); } catch (IOException e) { logError("Error with file attachment: " + attachment, e); } finally { - IOUtils.closeQuietly(fis); + IOUtils.closeQuietly(in); } } } @@ -1055,9 +1161,9 @@ public class MailManager extends BasicManager { return msg; } messageBodyPart = new MimeBodyPart(); - - DBMailAttachmentData data = getAttachmentWithData(attachment.getKey()); - DataSource source = new ByteArrayDataSource(data.getDatas(), attachment.getMimetype()); + + VFSLeaf data = getAttachmentDatas(attachment); + DataSource source = new VFSDataSource(attachment.getName(), attachment.getMimetype(), data); messageBodyPart.setDataHandler(new DataHandler(source)); messageBodyPart.setFileName(attachment.getName()); multipart.addBodyPart(messageBodyPart); @@ -1224,5 +1330,37 @@ public class MailManager extends BasicManager { logWarn("Could not send mail", e); } } + + private static class VFSDataSource implements DataSource { + + private final String name; + private final String contentType; + private final VFSLeaf file; + + public VFSDataSource(String name, String contentType, VFSLeaf file) { + this.name = name; + this.contentType = contentType; + this.file = file; + } + + @Override + public String getContentType() { + return contentType; + } + @Override + public InputStream getInputStream() throws IOException { + return file.getInputStream(); + } + + @Override + public String getName() { + return name; + } + + @Override + public OutputStream getOutputStream() throws IOException { + return null; + } + } } diff --git a/src/main/java/org/olat/core/util/mail/model/DBMailAttachment.hbm.xml b/src/main/java/org/olat/core/util/mail/model/DBMailAttachment.hbm.xml index 49956e12f29..7d4dfeb92bb 100644 --- a/src/main/java/org/olat/core/util/mail/model/DBMailAttachment.hbm.xml +++ b/src/main/java/org/olat/core/util/mail/model/DBMailAttachment.hbm.xml @@ -12,18 +12,9 @@ <property name="size" column="datas_size" type="long"/> <property name="mimetype" column="mimetype" type="string" not-null="false" length="255"/> <property name="name" column="datas_name" type="string" not-null="false" length="255"/> - <many-to-one name="mail" column="fk_att_mail_id" class="org.olat.core.util.mail.model.DBMailImpl" fetch="join" unique="false" cascade="none"/> - </class> - - <class name="org.olat.core.util.mail.model.DBMailAttachmentData" table="o_mail_attachment"> - <id name="key" column="attachment_id" type="long" unsaved-value="null"> - <generator class="hilo"/> - </id> - <property name="creationDate" column="creationdate" type="timestamp" /> - <property name="datas" column="datas" type="binary" length="16777215"/> - <property name="size" column="datas_size" type="long"/> - <property name="mimetype" column="mimetype" type="string" not-null="false" length="255"/> - <property name="name" column="datas_name" type="string" not-null="false" length="255"/> + <property name="checksum" column="datas_checksum" type="long" not-null="false"/> + <property name="path" column="datas_path" type="string" not-null="false" length="1024"/> + <property name="lastModified" column="datas_lastmodified" type="timestamp" /> <many-to-one name="mail" column="fk_att_mail_id" class="org.olat.core.util.mail.model.DBMailImpl" fetch="join" unique="false" cascade="none"/> </class> diff --git a/src/main/java/org/olat/core/util/mail/model/DBMailAttachment.java b/src/main/java/org/olat/core/util/mail/model/DBMailAttachment.java index 19a914d48ac..b688e22c226 100644 --- a/src/main/java/org/olat/core/util/mail/model/DBMailAttachment.java +++ b/src/main/java/org/olat/core/util/mail/model/DBMailAttachment.java @@ -19,8 +19,11 @@ */ package org.olat.core.util.mail.model; +import java.util.Date; + import org.olat.core.commons.persistence.PersistentObject; import org.olat.core.gui.util.CSSHelper; +import org.olat.core.util.mail.MailAttachment; /** * @@ -31,13 +34,16 @@ import org.olat.core.gui.util.CSSHelper; * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com */ -public class DBMailAttachment extends PersistentObject { +public class DBMailAttachment extends PersistentObject implements MailAttachment { private static final long serialVersionUID = -1713863670528439651L; private Long size; private String name; private String mimetype; + private Long checksum; + private String path; + private Date lastModified; private DBMailImpl mail; public DBMailAttachment() { @@ -80,6 +86,30 @@ public class DBMailAttachment extends PersistentObject { this.mimetype = mimetype; } + public Long getChecksum() { + return checksum; + } + + public void setChecksum(Long checksum) { + this.checksum = checksum; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public Date getLastModified() { + return lastModified; + } + + public void setLastModified(Date lastModified) { + this.lastModified = lastModified; + } + @Override public int hashCode() { return getKey() == null ? 2951 : getKey().hashCode(); diff --git a/src/main/java/org/olat/core/util/mail/ui/MailAttachmentMapper.java b/src/main/java/org/olat/core/util/mail/ui/MailAttachmentMapper.java index ff0c0cc0da4..9e3bfd8fe64 100644 --- a/src/main/java/org/olat/core/util/mail/ui/MailAttachmentMapper.java +++ b/src/main/java/org/olat/core/util/mail/ui/MailAttachmentMapper.java @@ -20,19 +20,14 @@ package org.olat.core.util.mail.ui; -import java.io.ByteArrayInputStream; -import java.io.InputStream; - import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.olat.core.dispatcher.mapper.Mapper; import org.olat.core.gui.media.MediaResource; import org.olat.core.gui.media.NotFoundMediaResource; -import org.olat.core.util.StringHelper; -import org.olat.core.util.WebappHelper; import org.olat.core.util.mail.manager.MailManager; -import org.olat.core.util.mail.model.DBMailAttachmentData; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.core.util.vfs.VFSMediaResource; /** * @@ -62,8 +57,8 @@ public class MailAttachmentMapper implements Mapper { String attachmentKey = relPath.substring(startIndex + ATTACHMENT_CONTEXT.length(), endIndex); try { Long key = new Long(attachmentKey); - DBMailAttachmentData datas = mailManager.getAttachmentWithData(key); - BytesMediaResource resource = new BytesMediaResource(datas); + VFSLeaf datas = mailManager.getAttachmentDatas(key); + MediaResource resource = new VFSMediaResource(datas); return resource; } catch(NumberFormatException e) { return new NotFoundMediaResource(relPath); @@ -72,55 +67,4 @@ public class MailAttachmentMapper implements Mapper { } return new NotFoundMediaResource(relPath); } - - public class BytesMediaResource implements MediaResource { - - private final DBMailAttachmentData datas; - - public BytesMediaResource(DBMailAttachmentData datas) { - this.datas = datas; - } - - @Override - public String getContentType() { - if(StringHelper.containsNonWhitespace(datas.getMimetype())) { - return datas.getMimetype(); - } - if(StringHelper.containsNonWhitespace(datas.getName())) { - String mimeType = WebappHelper.getMimeType(datas.getName()); - if(StringHelper.containsNonWhitespace(mimeType)) { - return mimeType; - } - } - return "application/octet-stream"; - } - - @Override - public Long getSize() { - if(datas.getDatas() == null) return 0l; - return new Long(datas.getDatas().length); - } - - @Override - public InputStream getInputStream() { - return new ByteArrayInputStream(datas.getDatas()); - } - - @Override - public Long getLastModified() { - return null; - } - - @Override - public void prepare(HttpServletResponse hres) { - String fileName = datas.getName(); - hres.setHeader("Content-Disposition","filename=\"" + StringHelper.urlEncodeISO88591(fileName) + "\""); - hres.setHeader("Content-Description",StringHelper.urlEncodeISO88591(fileName)); - } - - @Override - public void release() { - // - } - } } diff --git a/src/main/java/org/olat/modules/qpool/manager/FileStorage.java b/src/main/java/org/olat/core/util/vfs/FileStorage.java similarity index 78% rename from src/main/java/org/olat/modules/qpool/manager/FileStorage.java rename to src/main/java/org/olat/core/util/vfs/FileStorage.java index bb4a89d6fe5..de0ba64437d 100644 --- a/src/main/java/org/olat/modules/qpool/manager/FileStorage.java +++ b/src/main/java/org/olat/core/util/vfs/FileStorage.java @@ -17,38 +17,31 @@ * frentix GmbH, http://www.frentix.com * <p> */ -package org.olat.modules.qpool.manager; +package org.olat.core.util.vfs; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; -import org.olat.core.CoreSpringFactory; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; -import org.olat.core.util.vfs.VFSContainer; -import org.olat.core.util.vfs.VFSItem; -import org.olat.core.util.vfs.VFSLeaf; -import org.olat.modules.qpool.QuestionPoolModule; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; /** * - * - * - * Initial date: 07.03.2013<br> + * Initial date: 28.05.2013<br> * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com * */ -@Service("qpoolFileStorage") public class FileStorage { private static final OLog log = Tracing.createLoggerFor(FileStorage.class); + + private VFSContainer rootContainer; - @Autowired - private QuestionPoolModule qpoolModule; + public FileStorage(VFSContainer rootContainer) { + this.rootContainer = rootContainer; + } public String generateDir() { String uuid = UUID.randomUUID().toString(); @@ -70,8 +63,29 @@ public class FileStorage { return path; } + public String generateDir(String uuid, boolean addNumberedDir) { + if(addNumberedDir) { + return generateDir(uuid); + } + + String cleanUuid = uuid.replace("-", ""); + String firstToken = cleanUuid.substring(0, 2); + String secondToken = cleanUuid.substring(2, 4); + String thirdToken = cleanUuid.substring(4, 6); + + VFSContainer firstContainer = getNextDirectory(rootContainer, firstToken); + VFSContainer secondContainer = getNextDirectory(firstContainer, secondToken); + getNextDirectory(secondContainer, thirdToken); + + StringBuilder sb = new StringBuilder(); + sb.append(firstToken).append("/") + .append(secondToken).append("/") + .append(thirdToken).append("/"); + String path = sb.toString(); + return path; + } + protected String createContainer(String firstToken, String secondToken, String thirdToken) { - VFSContainer rootContainer = qpoolModule.getRootContainer(); VFSContainer firstContainer = getNextDirectory(rootContainer, firstToken); VFSContainer secondContainer = getNextDirectory(firstContainer, secondToken); VFSContainer thirdContainer = getNextDirectory(secondContainer, thirdToken); @@ -94,6 +108,7 @@ public class FileStorage { } if(!names.contains(potentielName)) { lastToken = potentielName; + break; } } } @@ -106,16 +121,12 @@ public class FileStorage { } public VFSContainer getContainer(String dir) { - VFSContainer rootContainer = CoreSpringFactory.getImpl(QuestionPoolModule.class).getRootContainer(); - String[] tokens = dir.split("/"); - String firstToken = tokens[0]; - VFSContainer firstContainer = getNextDirectory(rootContainer, firstToken); - String secondToken = tokens[1]; - VFSContainer secondContainer = getNextDirectory(firstContainer, secondToken); - String thirdToken = tokens[2]; - VFSContainer thridContainer = getNextDirectory(secondContainer, thirdToken); - String forthToken = tokens[3]; - return getNextDirectory(thridContainer, forthToken); + String[] tokens = dir.split("/"); + VFSContainer container = rootContainer; + for(String token:tokens) { + container = getNextDirectory(container, token); + } + return container; } private VFSContainer getNextDirectory(VFSContainer container, String token) { diff --git a/src/main/java/org/olat/ims/qti/qpool/QTIExportProcessor.java b/src/main/java/org/olat/ims/qti/qpool/QTIExportProcessor.java index bfdea485676..6b0fc04c054 100644 --- a/src/main/java/org/olat/ims/qti/qpool/QTIExportProcessor.java +++ b/src/main/java/org/olat/ims/qti/qpool/QTIExportProcessor.java @@ -54,7 +54,7 @@ import org.olat.core.util.xml.XMLParser; import org.olat.ims.qti.QTIConstants; import org.olat.ims.resources.IMSEntityResolver; import org.olat.modules.qpool.QuestionItemFull; -import org.olat.modules.qpool.manager.FileStorage; +import org.olat.modules.qpool.manager.QPoolFileStorage; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.helpers.DefaultHandler; @@ -69,9 +69,9 @@ public class QTIExportProcessor { private static final OLog log = Tracing.createLoggerFor(QTIExportProcessor.class); - private final FileStorage qpoolFileStorage; + private final QPoolFileStorage qpoolFileStorage; - public QTIExportProcessor(FileStorage qpoolFileStorage) { + public QTIExportProcessor(QPoolFileStorage qpoolFileStorage) { this.qpoolFileStorage = qpoolFileStorage; } diff --git a/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java b/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java index b3547e9a058..cf653b5fd80 100644 --- a/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java +++ b/src/main/java/org/olat/ims/qti/qpool/QTIImportProcessor.java @@ -56,7 +56,7 @@ import org.olat.ims.qti.editor.beecom.parser.ItemParser; import org.olat.ims.resources.IMSEntityResolver; import org.olat.modules.qpool.QuestionItem; import org.olat.modules.qpool.QuestionType; -import org.olat.modules.qpool.manager.FileStorage; +import org.olat.modules.qpool.manager.QPoolFileStorage; import org.olat.modules.qpool.manager.QEducationalContextDAO; import org.olat.modules.qpool.manager.QItemTypeDAO; import org.olat.modules.qpool.manager.QuestionItemDAO; @@ -85,18 +85,18 @@ class QTIImportProcessor { private final File importedFile; private final QItemTypeDAO qItemTypeDao; - private final FileStorage qpoolFileStorage; + private final QPoolFileStorage qpoolFileStorage; private final QuestionItemDAO questionItemDao; private final QEducationalContextDAO qEduContextDao; public QTIImportProcessor(Identity owner, Locale defaultLocale, QuestionItemDAO questionItemDao, - QItemTypeDAO qItemTypeDao, QEducationalContextDAO qEduContextDao, FileStorage qpoolFileStorage) { + QItemTypeDAO qItemTypeDao, QEducationalContextDAO qEduContextDao, QPoolFileStorage qpoolFileStorage) { this(owner, defaultLocale, null, null, questionItemDao, qItemTypeDao, qEduContextDao, qpoolFileStorage); } public QTIImportProcessor(Identity owner, Locale defaultLocale, String importedFilename, File importedFile, QuestionItemDAO questionItemDao, QItemTypeDAO qItemTypeDao, QEducationalContextDAO qEduContextDao, - FileStorage qpoolFileStorage) { + QPoolFileStorage qpoolFileStorage) { this.owner = owner; this.defaultLocale = defaultLocale; this.importedFilename = importedFilename; diff --git a/src/main/java/org/olat/ims/qti/qpool/QTIQPoolServiceProvider.java b/src/main/java/org/olat/ims/qti/qpool/QTIQPoolServiceProvider.java index d3edc234731..c97838bb8d2 100644 --- a/src/main/java/org/olat/ims/qti/qpool/QTIQPoolServiceProvider.java +++ b/src/main/java/org/olat/ims/qti/qpool/QTIQPoolServiceProvider.java @@ -58,7 +58,7 @@ import org.olat.modules.qpool.QuestionItem; import org.olat.modules.qpool.QuestionItemFull; import org.olat.modules.qpool.QuestionItemShort; import org.olat.modules.qpool.ExportFormatOptions.Outcome; -import org.olat.modules.qpool.manager.FileStorage; +import org.olat.modules.qpool.manager.QPoolFileStorage; import org.olat.modules.qpool.manager.QEducationalContextDAO; import org.olat.modules.qpool.manager.QItemTypeDAO; import org.olat.modules.qpool.manager.QuestionItemDAO; @@ -84,7 +84,7 @@ public class QTIQPoolServiceProvider implements QPoolSPI { public static final String QTI_12_OO_TEST = "OpenOLAT Test"; @Autowired - private FileStorage qpoolFileStorage; + private QPoolFileStorage qpoolFileStorage; @Autowired private QItemTypeDAO qItemTypeDao; @Autowired diff --git a/src/main/java/org/olat/modules/qpool/impl/FileQPoolServiceProvider.java b/src/main/java/org/olat/modules/qpool/impl/FileQPoolServiceProvider.java index ebbde31f193..b17c271b69f 100644 --- a/src/main/java/org/olat/modules/qpool/impl/FileQPoolServiceProvider.java +++ b/src/main/java/org/olat/modules/qpool/impl/FileQPoolServiceProvider.java @@ -29,7 +29,7 @@ import org.olat.modules.qpool.QPoolService; import org.olat.modules.qpool.QuestionItem; import org.olat.modules.qpool.QuestionType; import org.olat.modules.qpool.manager.AbstractQPoolServiceProvider; -import org.olat.modules.qpool.manager.FileStorage; +import org.olat.modules.qpool.manager.QPoolFileStorage; import org.olat.modules.qpool.model.QItemType; import org.olat.modules.qpool.ui.FilePreviewController; import org.springframework.beans.factory.annotation.Autowired; @@ -48,10 +48,10 @@ public class FileQPoolServiceProvider extends AbstractQPoolServiceProvider { @Autowired private QPoolService qpoolService; @Autowired - private FileStorage qpoolFileStorage; + private QPoolFileStorage qpoolFileStorage; @Override - public FileStorage getFileStorage() { + public QPoolFileStorage getFileStorage() { return qpoolFileStorage; } diff --git a/src/main/java/org/olat/modules/qpool/impl/TextQPoolServiceProvider.java b/src/main/java/org/olat/modules/qpool/impl/TextQPoolServiceProvider.java index 6bb4001a746..d9720e7326d 100644 --- a/src/main/java/org/olat/modules/qpool/impl/TextQPoolServiceProvider.java +++ b/src/main/java/org/olat/modules/qpool/impl/TextQPoolServiceProvider.java @@ -29,7 +29,7 @@ import org.olat.modules.qpool.QPoolService; import org.olat.modules.qpool.QuestionItem; import org.olat.modules.qpool.QuestionType; import org.olat.modules.qpool.manager.AbstractQPoolServiceProvider; -import org.olat.modules.qpool.manager.FileStorage; +import org.olat.modules.qpool.manager.QPoolFileStorage; import org.olat.modules.qpool.model.QItemType; import org.olat.modules.qpool.ui.TextPreviewController; import org.springframework.beans.factory.annotation.Autowired; @@ -48,10 +48,10 @@ public class TextQPoolServiceProvider extends AbstractQPoolServiceProvider { @Autowired private QPoolService qpoolService; @Autowired - private FileStorage qpoolFileStorage; + private QPoolFileStorage qpoolFileStorage; @Override - public FileStorage getFileStorage() { + public QPoolFileStorage getFileStorage() { return qpoolFileStorage; } diff --git a/src/main/java/org/olat/modules/qpool/manager/AbstractQPoolServiceProvider.java b/src/main/java/org/olat/modules/qpool/manager/AbstractQPoolServiceProvider.java index ddc54fc72ee..3f2ad1f2570 100644 --- a/src/main/java/org/olat/modules/qpool/manager/AbstractQPoolServiceProvider.java +++ b/src/main/java/org/olat/modules/qpool/manager/AbstractQPoolServiceProvider.java @@ -68,7 +68,7 @@ public abstract class AbstractQPoolServiceProvider implements QPoolSPI { private static final OLog log = Tracing.createLoggerFor(AbstractQPoolServiceProvider.class); - public abstract FileStorage getFileStorage(); + public abstract QPoolFileStorage getFileStorage(); public abstract QItemType getDefaultType(); diff --git a/src/main/java/org/olat/modules/qpool/manager/QPoolFileStorage.java b/src/main/java/org/olat/modules/qpool/manager/QPoolFileStorage.java new file mode 100644 index 00000000000..866762e81db --- /dev/null +++ b/src/main/java/org/olat/modules/qpool/manager/QPoolFileStorage.java @@ -0,0 +1,63 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.modules.qpool.manager; + +import javax.annotation.PostConstruct; + +import org.olat.core.util.vfs.FileStorage; +import org.olat.core.util.vfs.VFSContainer; +import org.olat.modules.qpool.QuestionPoolModule; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * + * + * + * Initial date: 07.03.2013<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +@Service("qpoolFileStorage") +public class QPoolFileStorage { + + @Autowired + private QuestionPoolModule qpoolModule; + + private FileStorage fileStorage; + + @PostConstruct + public void init() { + VFSContainer rootContainer = qpoolModule.getRootContainer(); + fileStorage = new FileStorage(rootContainer); + } + + public String generateDir() { + return fileStorage.generateDir(); + } + + public String generateDir(String uuid) { + return fileStorage.generateDir(uuid); + } + + public VFSContainer getContainer(String dir) { + return fileStorage.getContainer(dir); + } +} \ No newline at end of file diff --git a/src/main/java/org/olat/modules/qpool/manager/QuestionItemDAO.java b/src/main/java/org/olat/modules/qpool/manager/QuestionItemDAO.java index 48c20ebf191..72d675c8929 100644 --- a/src/main/java/org/olat/modules/qpool/manager/QuestionItemDAO.java +++ b/src/main/java/org/olat/modules/qpool/manager/QuestionItemDAO.java @@ -66,7 +66,7 @@ public class QuestionItemDAO { @Autowired private DB dbInstance; @Autowired - private FileStorage qpoolFileStorage; + private QPoolFileStorage qpoolFileStorage; @Autowired private BaseSecurity securityManager; diff --git a/src/main/java/org/olat/upgrade/OLATUpgrade_9_0_0.java b/src/main/java/org/olat/upgrade/OLATUpgrade_9_0_0.java new file mode 100644 index 00000000000..8b8edf0ed84 --- /dev/null +++ b/src/main/java/org/olat/upgrade/OLATUpgrade_9_0_0.java @@ -0,0 +1,145 @@ +package org.olat.upgrade; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.List; +import java.util.zip.Adler32; +import java.util.zip.CheckedInputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.output.NullOutputStream; +import org.olat.core.commons.persistence.DB; +import org.olat.core.logging.OLog; +import org.olat.core.logging.Tracing; +import org.olat.core.util.mail.manager.MailManager; +import org.olat.core.util.vfs.VFSLeaf; +import org.olat.upgrade.model.DBMailAttachmentData; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Initial date: 28.05.2013<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class OLATUpgrade_9_0_0 extends OLATUpgrade { + + private static final OLog log = Tracing.createLoggerFor(OLATUpgrade_9_0_0.class); + + private static final int BATCH_SIZE = 20; + private static final String TASK_MAILS = "Upgrade mails"; + private static final String VERSION = "OLAT_9.0.0"; + + @Autowired + private DB dbInstance; + @Autowired + private MailManager mailManager; + + public OLATUpgrade_9_0_0() { + super(); + } + + @Override + public String getVersion() { + return VERSION; + } + + @Override + public boolean doPreSystemInitUpgrade(UpgradeManager upgradeManager) { + return false; + } + + @Override + public boolean doPostSystemInitUpgrade(UpgradeManager upgradeManager) { + UpgradeHistoryData uhd = upgradeManager.getUpgradesHistory(VERSION); + if (uhd == null) { + // has never been called, initialize + uhd = new UpgradeHistoryData(); + } else { + if (uhd.isInstallationComplete()) { + return false; + } + } + + boolean allOk = upgradeMailAttachments(upgradeManager, uhd); + + uhd.setInstallationComplete(allOk); + upgradeManager.setUpgradesHistory(uhd, VERSION); + if(allOk) { + log.audit("Finished OLATUpgrade_9_0_0 successfully!"); + } else { + log.audit("OLATUpgrade_9_0_0 not finished, try to restart OpenOLAT!"); + } + return allOk; + } + + private boolean upgradeMailAttachments(UpgradeManager upgradeManager, UpgradeHistoryData uhd) { + if (!uhd.getBooleanDataValue(TASK_MAILS)) { + int counter = 0; + List<Long> attachments; + do { + attachments = findAttachments(counter, BATCH_SIZE); + for(Long attachment:attachments) { + processAttachments(attachment); + } + counter += attachments.size(); + log.audit("Mail attachment processed: " + attachments.size()); + dbInstance.commitAndCloseSession(); + } while(attachments.size() == BATCH_SIZE); + uhd.setBooleanDataValue(TASK_MAILS, true); + upgradeManager.setUpgradesHistory(uhd, VERSION); + } + return true; + } + + private List<Long> findAttachments(int firstResult, int maxResults) { + StringBuilder sb = new StringBuilder(); + sb.append("select attachment.key from ").append(DBMailAttachmentData.class.getName()).append(" attachment order by key"); + return dbInstance.getCurrentEntityManager().createQuery(sb.toString(), Long.class) + .setFirstResult(firstResult) + .setMaxResults(maxResults) + .getResultList(); + } + + private void processAttachments(Long attachment) { + try { + DBMailAttachmentData data = dbInstance.getCurrentEntityManager() + .find(DBMailAttachmentData.class, attachment); + + byte[] binaryDatas = data.getDatas(); + if(binaryDatas == null || binaryDatas.length <= 0) { + return; + } + + long checksum = checksum(binaryDatas); + String name = data.getName(); + InputStream in = new ByteArrayInputStream(binaryDatas); + String path = mailManager.saveAttachmentToStorage(name, data.getMimetype(), checksum, data.getSize(), in); + data.setChecksum(new Long(checksum)); + data.setLastModified(new Date()); + data.setPath(path); + + VFSLeaf savedFile = mailManager.getAttachmentDatas(data); + if(savedFile != null && savedFile.exists() && savedFile.getSize() > 0) { + data.setDatas(null); + dbInstance.getCurrentEntityManager().merge(data); + } + } catch (IOException e) { + log.error("", e); + } + } + + private long checksum(byte[] binaryDatas) throws IOException { + InputStream in = null; + Adler32 checksum = new Adler32(); + try { + in = new CheckedInputStream(new ByteArrayInputStream(binaryDatas), checksum); + IOUtils.copy(in, new NullOutputStream()); + } finally { + IOUtils.closeQuietly(in); + } + return checksum.getValue(); + } +} diff --git a/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml b/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml index 3ed8c9c39d2..f58c4d75d54 100644 --- a/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml +++ b/src/main/java/org/olat/upgrade/_spring/upgradeContext.xml @@ -37,6 +37,7 @@ <bean id="upgrade_8_2_0" class="org.olat.upgrade.OLATUpgrade_8_2_0"/> <bean id="upgrade_8_3_0" class="org.olat.upgrade.OLATUpgrade_8_3_0"/> <bean id="upgrade_8_4_0" class="org.olat.upgrade.OLATUpgrade_8_4_0"/> + <bean id="upgrade_9_0_0" class="org.olat.upgrade.OLATUpgrade_9_0_0"/> </list> </property> </bean> diff --git a/src/main/java/org/olat/upgrade/model/DBMailAttachmentData.hbm.xml b/src/main/java/org/olat/upgrade/model/DBMailAttachmentData.hbm.xml new file mode 100644 index 00000000000..dcfd4850022 --- /dev/null +++ b/src/main/java/org/olat/upgrade/model/DBMailAttachmentData.hbm.xml @@ -0,0 +1,22 @@ +<?xml version="1.0"?> +<!DOCTYPE hibernate-mapping PUBLIC + "-//Hibernate/Hibernate Mapping DTD//EN" + "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> +<hibernate-mapping default-lazy="false"> + + <class name="org.olat.upgrade.model.DBMailAttachmentData" table="o_mail_attachment"> + <id name="key" column="attachment_id" type="long" unsaved-value="null"> + <generator class="hilo"/> + </id> + <property name="creationDate" column="creationdate" type="timestamp" /> + <property name="datas" column="datas" type="binary" length="16777215"/> + <property name="size" column="datas_size" type="long"/> + <property name="mimetype" column="mimetype" type="string" not-null="false" length="255"/> + <property name="name" column="datas_name" type="string" not-null="false" length="255"/> + <property name="checksum" column="datas_checksum" type="long" not-null="false"/> + <property name="path" column="datas_path" type="string" not-null="false" length="1024"/> + <property name="lastModified" column="datas_lastmodified" type="timestamp" /> + <many-to-one name="mail" column="fk_att_mail_id" class="org.olat.core.util.mail.model.DBMailImpl" fetch="join" unique="false" cascade="none"/> + </class> + +</hibernate-mapping> diff --git a/src/main/java/org/olat/core/util/mail/model/DBMailAttachmentData.java b/src/main/java/org/olat/upgrade/model/DBMailAttachmentData.java similarity index 76% rename from src/main/java/org/olat/core/util/mail/model/DBMailAttachmentData.java rename to src/main/java/org/olat/upgrade/model/DBMailAttachmentData.java index 9c9854eab40..691191fe7e8 100644 --- a/src/main/java/org/olat/core/util/mail/model/DBMailAttachmentData.java +++ b/src/main/java/org/olat/upgrade/model/DBMailAttachmentData.java @@ -17,9 +17,13 @@ * frentix GmbH, http://www.frentix.com * <p> */ -package org.olat.core.util.mail.model; +package org.olat.upgrade.model; + +import java.util.Date; import org.olat.core.commons.persistence.PersistentObject; +import org.olat.core.util.mail.MailAttachment; +import org.olat.core.util.mail.model.DBMailImpl; /** * @@ -30,13 +34,16 @@ import org.olat.core.commons.persistence.PersistentObject; * * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com */ -public class DBMailAttachmentData extends PersistentObject { +public class DBMailAttachmentData extends PersistentObject implements MailAttachment { private static final long serialVersionUID = -3741636430048220733L; private Long size; private String name; private String mimetype; + private Long checksum; + private String path; + private Date lastModified; private byte[] datas; private DBMailImpl mail; @@ -84,6 +91,30 @@ public class DBMailAttachmentData extends PersistentObject { this.mimetype = mimetype; } + public Long getChecksum() { + return checksum; + } + + public void setChecksum(Long checksum) { + this.checksum = checksum; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public Date getLastModified() { + return lastModified; + } + + public void setLastModified(Date lastModified) { + this.lastModified = lastModified; + } + @Override public int hashCode() { return getKey() == null ? 921536 : getKey().intValue(); diff --git a/src/main/resources/database/mysql/alter_8_4_0_to_9_0_0.sql b/src/main/resources/database/mysql/alter_8_4_0_to_9_0_0.sql index 6bff1cff0ab..5d97dd8569d 100644 --- a/src/main/resources/database/mysql/alter_8_4_0_to_9_0_0.sql +++ b/src/main/resources/database/mysql/alter_8_4_0_to_9_0_0.sql @@ -342,4 +342,14 @@ alter table o_lti_outcome add constraint idx_lti_outcome_rsrc_id foreign key (fk -- mapper alter table o_mapper add column expirationdate datetime; +-- mail +alter table o_mail_attachment add column datas_checksum bigint; +alter table o_mail_attachment add column datas_path varchar(1024); +alter table o_mail_attachment add column datas_lastmodified datetime; +create index idx_mail_att_checksum_idx on o_mail_attachment (datas_checksum); +create index idx_mail_path_idx on o_mail_attachment (datas_path); +create index idx_mail_att_siblings_idx on o_mail_attachment (datas_checksum, mimetype, datas_size, datas_name); + + + diff --git a/src/main/resources/database/mysql/setupDatabase.sql b/src/main/resources/database/mysql/setupDatabase.sql index fa5c5416432..d45f194c39b 100644 --- a/src/main/resources/database/mysql/setupDatabase.sql +++ b/src/main/resources/database/mysql/setupDatabase.sql @@ -822,14 +822,17 @@ create table if not exists o_mail_recipient ( -- mail attachments create table o_mail_attachment ( - attachment_id bigint NOT NULL, - creationdate datetime, - datas mediumblob, - datas_size bigint, - datas_name varchar(255), - mimetype varchar(255), - fk_att_mail_id bigint, - primary key (attachment_id) + attachment_id bigint NOT NULL, + creationdate datetime, + datas mediumblob, + datas_size bigint, + datas_name varchar(255), + datas_checksum bigint, + datas_path varchar(1024), + datas_lastmodified datetime, + mimetype varchar(255), + fk_att_mail_id bigint, + primary key (attachment_id) ); -- access control @@ -1988,6 +1991,9 @@ alter table o_mail_recipient add constraint FKF86663165A4FA5DG foreign key (fk_r alter table o_mail add constraint FKF86663165A4FA5DC foreign key (fk_from_id) references o_mail_recipient (recipient_id); alter table o_mail_to_recipient add constraint FKF86663165A4FA5DD foreign key (fk_recipient_id) references o_mail_recipient (recipient_id); alter table o_mail_attachment add constraint FKF86663165A4FA5DF foreign key (fk_att_mail_id) references o_mail (mail_id); +create index idx_mail_att_checksum_idx on o_mail_attachment (datas_checksum); +create index idx_mail_path_idx on o_mail_attachment (datas_path); +create index idx_mail_att_siblings_idx on o_mail_attachment (datas_checksum, mimetype, datas_size, datas_name); create index ac_offer_to_resource_idx on o_ac_offer (fk_resource_id); alter table o_ac_offer_access add constraint off_to_meth_meth_ctx foreign key (fk_method_id) references o_ac_method (method_id); diff --git a/src/main/resources/database/oracle/alter_8_4_0_to_9_0_0.sql b/src/main/resources/database/oracle/alter_8_4_0_to_9_0_0.sql index 4db7f9325c0..954111f7b0f 100644 --- a/src/main/resources/database/oracle/alter_8_4_0_to_9_0_0.sql +++ b/src/main/resources/database/oracle/alter_8_4_0_to_9_0_0.sql @@ -341,3 +341,12 @@ create index idx_lti_outcome_rsrc_id_idx on o_lti_outcome (fk_resource_id); -- mapper alter table o_mapper add (expirationdate date); + +-- mail +alter table o_mail_attachment add (datas_checksum number(20)); +alter table o_mail_attachment add (datas_path varchar2(1024 char)); +alter table o_mail_attachment add (datas_lastmodified date); +create index idx_mail_att_checksum_idx on o_mail_attachment (datas_checksum); +create index idx_mail_path_idx on o_mail_attachment (datas_path); +create index idx_mail_att_siblings_idx on o_mail_attachment (datas_checksum, mimetype, datas_size, datas_name); + diff --git a/src/main/resources/database/oracle/setupDatabase.sql b/src/main/resources/database/oracle/setupDatabase.sql index e57b554e256..fa73f65f13b 100644 --- a/src/main/resources/database/oracle/setupDatabase.sql +++ b/src/main/resources/database/oracle/setupDatabase.sql @@ -762,6 +762,9 @@ create table o_mail_attachment ( datas_size number(20), datas_name varchar(255 char), mimetype varchar(255 char), + datas_checksum number(20), + datas_path varchar2(1024 char), + datas_lastmodified date, fk_att_mail_id number(20), primary key (attachment_id) ); @@ -2036,6 +2039,9 @@ alter table o_mail_recipient add constraint FKF86663165A4FA5DG foreign key (fk_r alter table o_mail add constraint FKF86663165A4FA5DC foreign key (fk_from_id) references o_mail_recipient (recipient_id); alter table o_mail_to_recipient add constraint FKF86663165A4FA5DD foreign key (fk_recipient_id) references o_mail_recipient (recipient_id); alter table o_mail_attachment add constraint FKF86663165A4FA5DF foreign key (fk_att_mail_id) references o_mail (mail_id); +create index idx_mail_att_checksum_idx on o_mail_attachment (datas_checksum); +create index idx_mail_path_idx on o_mail_attachment (datas_path); +create index idx_mail_att_siblings_idx on o_mail_attachment (datas_checksum, mimetype, datas_size, datas_name); create index ac_offer_to_resource_idx on o_ac_offer (fk_resource_id); alter table o_ac_offer_access add constraint off_to_meth_meth_ctx foreign key (fk_method_id) references o_ac_method (method_id); diff --git a/src/main/resources/database/postgresql/alter_8_4_0_to_9_0_0.sql b/src/main/resources/database/postgresql/alter_8_4_0_to_9_0_0.sql index d66131ae633..fc417de44f7 100644 --- a/src/main/resources/database/postgresql/alter_8_4_0_to_9_0_0.sql +++ b/src/main/resources/database/postgresql/alter_8_4_0_to_9_0_0.sql @@ -344,4 +344,12 @@ create index idx_lti_outcome_rsrc_id_idx on o_lti_outcome (fk_resource_id); -- mapper alter table o_mapper add column expirationdate timestamp; +-- mail +alter table o_mail_attachment add column datas_checksum int8; +alter table o_mail_attachment add column datas_path varchar(1024); +alter table o_mail_attachment add column datas_lastmodified timestamp; +create index idx_mail_att_checksum_idx on o_mail_attachment (datas_checksum); +create index idx_mail_path_idx on o_mail_attachment (datas_path); +create index idx_mail_att_siblings_idx on o_mail_attachment (datas_checksum, mimetype, datas_size, datas_name); + diff --git a/src/main/resources/database/postgresql/setupDatabase.sql b/src/main/resources/database/postgresql/setupDatabase.sql index f0a9dfeee26..98fb9424e10 100644 --- a/src/main/resources/database/postgresql/setupDatabase.sql +++ b/src/main/resources/database/postgresql/setupDatabase.sql @@ -692,14 +692,17 @@ create table o_mail_recipient ( -- mail attachments create table o_mail_attachment ( - attachment_id int8 NOT NULL, - creationdate timestamp, - datas bytea, - datas_size int8, - datas_name varchar(255), - mimetype varchar(255), - fk_att_mail_id int8, - primary key (attachment_id) + attachment_id int8 NOT NULL, + creationdate timestamp, + datas bytea, + datas_size int8, + datas_name varchar(255), + datas_checksum int8, + datas_path varchar(1024), + datas_lastmodified timestamp, + mimetype varchar(255), + fk_att_mail_id int8, + primary key (attachment_id) ); -- access control @@ -1921,6 +1924,9 @@ alter table o_mail_recipient add constraint FKF86663165A4FA5DG foreign key (fk_r alter table o_mail add constraint FKF86663165A4FA5DC foreign key (fk_from_id) references o_mail_recipient (recipient_id); alter table o_mail_to_recipient add constraint FKF86663165A4FA5DD foreign key (fk_recipient_id) references o_mail_recipient (recipient_id); alter table o_mail_attachment add constraint FKF86663165A4FA5DF foreign key (fk_att_mail_id) references o_mail (mail_id); +create index idx_mail_att_checksum_idx on o_mail_attachment (datas_checksum); +create index idx_mail_path_idx on o_mail_attachment (datas_path); +create index idx_mail_att_siblings_idx on o_mail_attachment (datas_checksum, mimetype, datas_size, datas_name); create index ac_offer_to_resource_idx on o_ac_offer (fk_resource_id); alter table o_ac_offer_access add constraint off_to_meth_meth_ctx foreign key (fk_method_id) references o_ac_method (method_id); diff --git a/src/test/java/org/olat/ims/qti/qpool/QTIExportProcessorTest.java b/src/test/java/org/olat/ims/qti/qpool/QTIExportProcessorTest.java index b46a4e386cb..c34cd93ded8 100644 --- a/src/test/java/org/olat/ims/qti/qpool/QTIExportProcessorTest.java +++ b/src/test/java/org/olat/ims/qti/qpool/QTIExportProcessorTest.java @@ -40,7 +40,7 @@ import org.olat.core.commons.persistence.DB; import org.olat.core.id.Identity; import org.olat.modules.qpool.QuestionItem; import org.olat.modules.qpool.QuestionItemFull; -import org.olat.modules.qpool.manager.FileStorage; +import org.olat.modules.qpool.manager.QPoolFileStorage; import org.olat.modules.qpool.manager.QEducationalContextDAO; import org.olat.modules.qpool.manager.QItemTypeDAO; import org.olat.modules.qpool.manager.QuestionItemDAO; @@ -61,7 +61,7 @@ public class QTIExportProcessorTest extends OlatTestCase { @Autowired private DB dbInstance; @Autowired - private FileStorage qpoolFileStorage; + private QPoolFileStorage qpoolFileStorage; @Autowired private QItemTypeDAO qItemTypeDao; @Autowired diff --git a/src/test/java/org/olat/ims/qti/qpool/QTIImportProcessorTest.java b/src/test/java/org/olat/ims/qti/qpool/QTIImportProcessorTest.java index e8498221232..850d51dc4e4 100644 --- a/src/test/java/org/olat/ims/qti/qpool/QTIImportProcessorTest.java +++ b/src/test/java/org/olat/ims/qti/qpool/QTIImportProcessorTest.java @@ -48,7 +48,7 @@ import org.olat.modules.qpool.QuestionItem; import org.olat.modules.qpool.QuestionItemFull; import org.olat.modules.qpool.QuestionStatus; import org.olat.modules.qpool.QuestionType; -import org.olat.modules.qpool.manager.FileStorage; +import org.olat.modules.qpool.manager.QPoolFileStorage; import org.olat.modules.qpool.manager.QEducationalContextDAO; import org.olat.modules.qpool.manager.QItemTypeDAO; import org.olat.modules.qpool.manager.QuestionItemDAO; @@ -72,7 +72,7 @@ public class QTIImportProcessorTest extends OlatTestCase { @Autowired private DB dbInstance; @Autowired - private FileStorage qpoolFileStorage; + private QPoolFileStorage qpoolFileStorage; @Autowired private QItemTypeDAO qItemTypeDao; @Autowired diff --git a/src/test/java/org/olat/modules/qpool/manager/FileStorageTest.java b/src/test/java/org/olat/modules/qpool/manager/FileStorageTest.java index 44fb6c047a7..3cc21bd7e32 100644 --- a/src/test/java/org/olat/modules/qpool/manager/FileStorageTest.java +++ b/src/test/java/org/olat/modules/qpool/manager/FileStorageTest.java @@ -37,7 +37,7 @@ import org.springframework.beans.factory.annotation.Autowired; public class FileStorageTest extends OlatTestCase { @Autowired - private FileStorage qpoolFileStorage; + private QPoolFileStorage qpoolFileStorage; @Test public void testGenerateDir() { -- GitLab