/******************************************************************************* * Copyright 2Fi Business Solutions Ltd. * * This code is copyrighted. Under no circumstances should any party, people, * or organization should redistribute any portions of this code in any form, * either verbatim or through electronic media, to any third parties, unless * under explicit written permission by 2Fi Business Solutions Ltd. ******************************************************************************/ package com.ffii.tbms.file.service; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.List; import java.util.Map; import javax.imageio.ImageIO; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; import com.ffii.core.dao.JdbcDao; import com.ffii.core.utils.FileUtils; import com.ffii.core.utils.MapUtils; import com.ffii.core.web.AbstractService; import com.ffii.core.web.view.AbstractView; import com.ffii.tbms.file.File; import com.ffii.tbms.file.FileBlob; import com.ffii.tbms.file.FileRef; import com.ffii.tbms.file.dao.FileBlobDao; import com.ffii.tbms.file.dao.FileDao; import com.ffii.tbms.file.dao.FileRefDao; /** * @author Patrick */ @Service public class FileService extends AbstractService { @Autowired private JdbcDao jdbcDao; @Autowired private FileDao fileDao; @Autowired private FileBlobDao fileBlobDao; @Autowired private FileRefDao fileRefDao; /** * Save File, FileBlob, and FileRef in one go * * @param filename * the filename, cannot be null * @param description * optional File description * @param refType * the File reference type, should not be empty * @param refId * mandatory, use 0 if N/A * @param refCode * optional * @param bytes * the File byte array * * @return File ID */ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) public Integer saveFile(String filename, String description, String refType, int refId, String refCode, byte[] bytes) { File file = new File(); file.setFilename(filename); file.setDescription(description); file.setMimetype(FileUtils.guessMimetype(filename)); file.setFilesize(bytes.length); file.setSkey(RandomStringUtils.randomAlphanumeric(16)); FileBlob fileBlob = new FileBlob(); fileBlob.setBytes(bytes); FileRef fileRef = new FileRef(); fileRef.setRefId(refId); fileRef.setRefType(refType); fileRef.setRefCode(refCode); // try to get width and height if mimetype is png or jpeg if (AbstractView.CONTENT_TYPE_PNG.equals(file.getMimetype()) || AbstractView.CONTENT_TYPE_JPEG.equals(file.getMimetype())) { BufferedImage image; try { image = ImageIO.read(new ByteArrayInputStream(bytes)); if (image != null) { file.setImageWidth(image.getWidth()); file.setImageHeight(image.getHeight()); } } catch (IOException e) { // ignore } } // save File saveFile(file); // save FileBlob fileBlob.setFileId(file.getId()); saveFileBlob(fileBlob); // save FileRef fileRef.setFileId(file.getId()); saveFileRef(fileRef); return file.getId(); } @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) public Integer saveFile(File instance) { return fileDao.saveOrUpdate(instance); } @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) public Integer saveFileBlob(FileBlob instance) { return fileBlobDao.saveOrUpdate(instance); } @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) public Integer saveFileRef(FileRef instance) { return fileRefDao.saveOrUpdate(instance); } @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) public File findFileByIdAndKey(int id, String skey) { return fileDao.findByQuery("from com.ffii.tbms.file.File f where f.id = ? and f.skey = ?", id, skey); } @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) public File findFileById(int id) { return fileDao.findByQuery("from com.ffii.tbms.file.File f where f.id = ? ", id); } @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) public FileRef findFileRefByTypeAndId(String refType, int refId) { return fileRefDao.findByQuery("from com.ffii.tbms.file.FileRef where refType = ? and refId = ?", refType, refId); } @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) public FileRef findFileRefByfileId(int fileId) { return fileRefDao.findByQuery("from com.ffii.tbms.file.FileRef where fileId = ?", fileId); } @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) public FileBlob findFileBlobByFileId(int fileId) { return fileBlobDao.findByQuery("from com.ffii.tbms.file.FileBlob fb where fb.fileId = ?", fileId); } @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) public boolean isFileExists(int id, String skey) { String sql = "SELECT" + " COUNT(1)" + " FROM files f" + " WHERE f.deleted = 0" + " AND f.id = :id" + " AND f.skey = :skey"; int count = jdbcDao.queryForInt(sql, MapUtils.toHashMap("id", id, "skey", skey)); return (count > 0); } @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) public List> searchFiles(Map args) { StringBuilder sql = new StringBuilder("SELECT" + " f.id," + " f.filename," + " f.filesize," + " f.mimetype, " + " f.skey," + " fr.refId," + " fr.refType," + " f.created," + " f.imageWidth," + " f.imageHeight," + " u.fullname AS createdByName," + " f.description" + " FROM files f" + " LEFT JOIN files_ref fr ON f.id = fr.fileId" + " LEFT JOIN users u ON f.createdBy = u.id" + " WHERE f.deleted = 0 AND fr.deleted =0 "); if (args.containsKey("filename")) sql.append(" AND f.filename = :filename"); if (args.containsKey("refType")) sql.append(" AND fr.refType = :refType"); if (args.containsKey("refId")) sql.append(" AND fr.refId = :refId"); if (args.containsKey("startDate")) sql.append(" AND f.created >= :startDate"); if (args.containsKey("endDate")) sql.append(" AND f.created < :endDate"); if (args.containsKey("fileId")) sql.append(" AND f.id = :fileId"); sql.append(" ORDER BY f.created DESC"); return jdbcDao.queryForList(sql.toString(), args); } @Transactional(isolation = Isolation.READ_COMMITTED, rollbackFor = Exception.class, readOnly = true) public List> searchFilesFromDocument(Map args) { StringBuilder sql = new StringBuilder("SELECT" + " f.id," + " f.filename," + " f.filesize," + " f.mimetype," + " f.skey," + " fr.refId," + " fr.refType," + " f.created," + " u.fullname AS createdByName," + " f.description" + " FROM files_ref fr" + " LEFT JOIN files f ON f.id = fr.fileId" + " LEFT JOIN users u ON f.createdBy = u.id" + " LEFT JOIN document_ref dr ON dr.documentId = fr.refId AND fr.refType = 'document'" + " WHERE 1 = 1" + " AND f.deleted = 0" + " AND fr.deleted = 0" + " AND dr.deleted = 0"); if (args != null) { if (args.containsKey("filename")) sql.append(" AND f.filename = :filename"); if (args.containsKey("refType")) sql.append(" AND fr.refType = :refType"); if (args.containsKey("refId")) sql.append(" AND fr.refId = :refId"); if (args.containsKey("startDate")) sql.append(" AND f.created >= :startDate"); if (args.containsKey("endDate")) sql.append(" AND f.created < :endDate"); if (args.containsKey("documentRefType")) sql.append(" AND dr.refType = :documentRefType"); if (args.containsKey("documentRefId")) sql.append(" AND dr.refId = :documentRefId"); } sql.append(" GROUP BY f.id" + " ORDER BY f.created DESC"); return jdbcDao.queryForList(sql.toString(), args); } /** * Delete FileRef by fileId, refId, refType, and skey */ @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) public void deleteFile(Integer fileId, Integer refId, String refType, String skey) { Map args = MapUtils.toHashMap("fileId", fileId, "refId", refId, "refType", refType, "skey", skey); jdbcDao.executeUpdate("DELETE FROM files_ref" + " WHERE fileId = :fileId" + " AND refId = :refId" + " AND refType = :refType" + " AND EXISTS (SELECT 1 FROM files WHERE id = files_ref.fileId AND skey = :skey)", args); jdbcDao.executeUpdate("DELETE FROM files_blob WHERE id = :fileId ",args); } /** * Scheduled daily job * * Delete all orphan (without any FileRef) Files and FileBlobs */ @Scheduled(cron = "0 0 0 * * ?") // everyday at 00:00:00 @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false) public void deleteOrphanFiles() { jdbcDao.executeUpdate("DELETE FROM files_blob WHERE NOT EXISTS (SELECT 1 FROM files_ref WHERE deleted = 0 AND fileId = files_blob.fileId)"); jdbcDao.executeUpdate("DELETE FROM files WHERE NOT EXISTS (SELECT 1 FROM files_ref WHERE deleted = 0 AND fileId = files.id)"); } @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true) public List> getCustImageList() { return jdbcDao.queryForList(" Select cp.* From cust_photo cp ", null); } @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = true) public List> getOrderImageList() { return jdbcDao.queryForList(" Select op.* From order_photo cp ", null); } }