Java tutorial
/** * Genji Scrum Tool and Issue Tracker * Copyright (C) 2015 Steinbeis GmbH & Co. KG Task Management Solutions * <a href="http://www.trackplus.com">Genji Scrum Tool</a> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /* $Id:$ */ package com.aurel.track.attachment; import java.awt.Graphics; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.HeadlessException; import java.awt.Image; import java.awt.Toolkit; import java.awt.Transparency; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.PixelGrabber; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.DecimalFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeSet; import javax.activation.MimetypesFileTypeMap; import javax.imageio.ImageIO; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import javax.swing.ImageIcon; import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import org.apache.struts2.ServletActionContext; import com.aurel.track.Constants; import com.aurel.track.admin.customize.category.filter.tree.design.FilterUpperTO; import com.aurel.track.admin.customize.category.filter.tree.design.RACIBean; import com.aurel.track.admin.user.person.PersonBL; import com.aurel.track.beans.TAttachmentBean; import com.aurel.track.beans.TPersonBean; import com.aurel.track.beans.TSiteBean; import com.aurel.track.beans.TWorkItemBean; import com.aurel.track.cluster.ClusterBL.CHANGE_TYPE; import com.aurel.track.cluster.ClusterMarkChangesBL; import com.aurel.track.dao.AttachmentDAO; import com.aurel.track.dao.DAOFactory; import com.aurel.track.dbase.HandleHome; import com.aurel.track.errors.ErrorData; import com.aurel.track.fieldType.constants.BooleanFields; import com.aurel.track.fieldType.runtime.base.LookupContainer; import com.aurel.track.fieldType.runtime.base.WorkItemContext; import com.aurel.track.item.ItemBL; import com.aurel.track.item.ItemPersisterException; import com.aurel.track.item.history.HistorySaverBL; import com.aurel.track.lucene.index.associatedFields.AttachmentIndexer; import com.aurel.track.prop.ApplicationBean; import com.aurel.track.resources.LocalizeUtil; import com.aurel.track.util.GeneralUtils; import com.aurel.track.util.ImageUtils; import com.aurel.track.util.LabelValueBean; import com.aurel.track.util.emailHandling.EmailAttachment; /** * Business logic for attachment * * @author Adrian Bojani * */ /** * @author friedj * */ public class AttachBL { private static final Logger LOGGER = LogManager.getLogger(AttachBL.class); private static AttachmentDAO attachmentDAO = DAOFactory.getFactory().getAttachmentDAO(); private static MimetypesFileTypeMap mimeTypeMap = null; private static int attachTempIndex = 1; // folder name for the attachments public static final String AttachmentsName = "attachments"; // The delimiter for the creating temporary attachment's folder private static final String tempDelimiter = "_"; //parameter for key of the issue for attachment download private static final String AttachDbFname = "tattach"; private static final String AttachDbFnameType = ".dat"; private static final String AttachDbFnameTypeNew = ".new"; public static final String tmpAttachments = "tmpAttachments"; public static final String excelImportDir = "excelImport"; public static final String docxImportDir = "docxImport"; public static final String msProjectImportDir = "msProjectImport"; private static final Set<String> imgExt = new TreeSet<String>(); static { imgExt.add("JPG"); imgExt.add("GIF"); imgExt.add("PNG"); } /** * Gets all attachments * @return */ public static List<TAttachmentBean> loadAll() { List<TAttachmentBean> allAttachmnents = attachmentDAO.loadAll(); for (TAttachmentBean attachmentBean : allAttachmnents) { Integer attachmentID = attachmentBean.getObjectID(); Integer workItemID = attachmentBean.getWorkItem(); attachmentBean.setFullFileNameOnDisk(getFullFileName(null, attachmentID, workItemID)); attachmentBean.setFileNameOnDisk(getFileNameAttachment(attachmentID, workItemID)); } return allAttachmnents; } public static List<TAttachmentBean> getExistingAttachments(List<TAttachmentBean> attachmentBeansList) { List<TAttachmentBean> existingAttachments = new ArrayList<TAttachmentBean>(); if (attachmentBeansList != null) { Iterator<TAttachmentBean> iterator = attachmentBeansList.iterator(); while (iterator.hasNext()) { TAttachmentBean attachmentBean = iterator.next(); String diskFileName = getFullFileName(null, attachmentBean.getObjectID(), attachmentBean.getWorkItem()); LOGGER.debug("diskFileName=" + diskFileName); File file = new File(diskFileName); if (file.exists()) { existingAttachments.add(attachmentBean); } } } return existingAttachments; } /** * Gets the attachments filtered by filterSelectsTO and raciBean * @param filterUpperTO * @param raciBean * @param personID * @return */ public static List<TAttachmentBean> loadTreeFilterAttachments(FilterUpperTO filterUpperTO, RACIBean raciBean, Integer personID) { return attachmentDAO.loadTreeFilterAttachments(filterUpperTO, raciBean, personID); } /** * Get the attachments for a TQL expression * @param tqlExpression * @param personBean * @param locale * @param errors * @return */ public static List<TAttachmentBean> loadTQLFilterAttachments(String tqlExpression, TPersonBean personBean, Locale locale, List<ErrorData> errors) { return attachmentDAO.loadTQLFilterAttachments(tqlExpression, personBean, locale, errors); } /** * Obtain the attachments for given attachment ids * @param attachmentIDs * @return */ public static List<TAttachmentBean> loadByAttachmentIDs(List<Integer> attachmentIDs) { List<TAttachmentBean> attachmentBeans = attachmentDAO.loadByAttachmentIDs(attachmentIDs); return filterNotOnDisk(attachmentBeans); } /** * Obtain the attachment for given attachment id * @param attachmentID the object id of the attachment * @return */ public static TAttachmentBean loadByAttachmentID(Integer attachmentID) { TAttachmentBean attachmentBean = attachmentDAO.loadByID(attachmentID); return attachmentBean; } public static List<TAttachmentBean> loadByWorkItems(int[] workItemIDs) { List<TAttachmentBean> attachmentBeans = attachmentDAO.loadByWorkItemKeys(workItemIDs); List<TPersonBean> changedByPersons = PersonBL.getAttachmentPersons(workItemIDs); addChangedByName(attachmentBeans, changedByPersons); return filterNotOnDisk(attachmentBeans); } private static List<TAttachmentBean> loadByWorkItem(Integer workItemID) { List<TAttachmentBean> attachmentBeans = attachmentDAO.loadByWorkItemKey(workItemID); List<TPersonBean> changedByPersons = PersonBL.getAttachmentPersons(new int[] { workItemID }); return addChangedByName(attachmentBeans, changedByPersons); } public static int countByWorkItemID(Integer workItemID) { List<TAttachmentBean> attachList = AttachBL.getAttachments(workItemID); return attachList.size(); } private static List<TAttachmentBean> filterNotOnDisk(List<TAttachmentBean> attachmentBeanList) { if (attachmentBeanList == null) { return new ArrayList<TAttachmentBean>(); } //the attachment must have a file on disk for (Iterator<TAttachmentBean> iterator = attachmentBeanList.iterator(); iterator.hasNext();) { TAttachmentBean attachmentBean = iterator.next(); if (!BooleanFields.fromStringToBoolean(attachmentBean.getIsUrl())) { boolean fileOnDisk = ensureFileOnDisk(attachmentBean, attachmentBean.getWorkItem()); if (fileOnDisk) { ensureMimeType(attachmentBean); } else { iterator.remove(); } } } return attachmentBeanList; } private static List<TAttachmentBean> addChangedByName(List<TAttachmentBean> attachmentBeans, List<TPersonBean> changedByPersons) { if (attachmentBeans != null) { Map<Integer, TPersonBean> personBeansMap = GeneralUtils.createMapFromList(changedByPersons); for (TAttachmentBean attachmentBean : attachmentBeans) { TPersonBean personBean = personBeansMap.get(attachmentBean.getChangedBy()); if (personBean != null) { attachmentBean.setChangedByName(personBean.getLabel()); } } } return attachmentBeans; } /** * Obtain the attachment list from db for an item * and the attachment must have a file on disk. * @param itemID the id of item * @return list of TAttachmentBean */ public static List<TAttachmentBean> getAttachments(Integer itemID) { List<TAttachmentBean> attachmentBeansDB = loadByWorkItem(itemID); if (attachmentBeansDB == null || attachmentBeansDB.isEmpty()) { return new ArrayList<TAttachmentBean>(); } //the attachment must have a file on disk List<TAttachmentBean> realAttachments = new ArrayList<TAttachmentBean>(); for (int i = 0; i < attachmentBeansDB.size(); i++) { //verify every attachment if exist file on disk system TAttachmentBean attach = attachmentBeansDB.get(i); if (BooleanFields.fromStringToBoolean(attach.getIsUrl())) { realAttachments.add(attach); } else { boolean fileOnDisk = ensureFileOnDisk(attach, itemID); if (fileOnDisk) { ensureMimeType(attach); realAttachments.add(attach); } } } return realAttachments; } public static List<TAttachmentBean> getAttachmentsImage(Integer itemID) { List<TAttachmentBean> allAttachments = getAttachments(itemID); //the attachment must have a file on disk List<TAttachmentBean> realAttachments = gettAttachmentImages(allAttachments); return realAttachments; } public static List<TAttachmentBean> gettAttachmentImages(List<TAttachmentBean> allAttachments) { List<TAttachmentBean> realAttachments = new ArrayList<TAttachmentBean>(); if (allAttachments != null) { for (int i = 0; i < allAttachments.size(); i++) { //verify every attachment if exist file on disk system TAttachmentBean attach = allAttachments.get(i); boolean image = AttachBL.isImage(attach); if (image) { realAttachments.add(attach); } } } return realAttachments; } public static Map<Integer, List<TAttachmentBean>> getGroupedAttachemnts(List<TAttachmentBean> attachList) { Map<Integer, List<TAttachmentBean>> itemAttachmentsMap = new HashMap<Integer, List<TAttachmentBean>>(); if (attachList != null && !attachList.isEmpty()) { for (TAttachmentBean attachmentBean : attachList) { Integer workItemID = attachmentBean.getWorkItem(); List<TAttachmentBean> itemAttachList = itemAttachmentsMap.get(workItemID); if (itemAttachList == null) { itemAttachList = new LinkedList<TAttachmentBean>(); itemAttachmentsMap.put(workItemID, itemAttachList); } itemAttachList.add(attachmentBean); } } return itemAttachmentsMap; } public static List<TAttachmentBean> getAttachments(int[] itemIDs) { return getAttachments(itemIDs, true); } public static List<TAttachmentBean> getAttachments(int[] itemIDs, boolean verifyFileOnDisk) { List<TAttachmentBean> attachmentBeansDB = attachmentDAO.loadByWorkItemKeys(itemIDs); if (verifyFileOnDisk) { return filterNotOnDisk(attachmentBeansDB); } else { return attachmentBeansDB; } } /** * Load the attachment with given id * @param attachmentID * @return */ public static TAttachmentBean loadByID(Integer attachmentID) { return attachmentDAO.loadByID(attachmentID); } /** * Delete the attachment form DB * @param attachmentID */ public static void delete(Integer attachmentID) { attachmentDAO.delete(attachmentID); } /** * Load the attachment from DB * and also verify the file on disk * @param attachmentID * @param itemID * @return */ public static TAttachmentBean loadAttachment(Integer attachmentID, Integer itemID, boolean setMimeType) { TAttachmentBean attach = loadByID(attachmentID); if (attach == null) { LOGGER.warn("Attachment with id:" + attachmentID + " for item:" + itemID + " not found in database!"); return null; } //verify the file on disk if (BooleanFields.fromStringToBoolean(attach.getIsUrl())) { return attach; } else { if (ensureFileOnDisk(attach, itemID)) { if (setMimeType) { ensureMimeType(attach); } return attach; } } return null; } public static TAttachmentBean loadLocalAttachment(Integer attachmentID, List<TAttachmentBean> attachments) { return findLocalAttachment(attachments, attachmentID); } /** * Download the attachment * @param attach * @param outStream */ public static void download(TAttachmentBean attach, OutputStream outStream) throws AttachBLException { download(attach.getFullFileNameOnDisk(), outStream); } public static void downloadThumb(String sessionID, TAttachmentBean attach, OutputStream outStream) throws AttachBLException { download(getFullThumbFileName(sessionID, attach), outStream); } public static void download(String fileName, OutputStream outStream) /*throws AttachBLException*/ { // open the file BufferedInputStream instream = null; try { //retrieve the file data File file = new File(fileName); instream = new BufferedInputStream(new FileInputStream(file)); } catch (FileNotFoundException fnfe) { LOGGER.debug(ExceptionUtils.getStackTrace(fnfe)); return; } catch (Exception ioe) { LOGGER.debug(ExceptionUtils.getStackTrace(ioe)); return; } // copy the file to output stream int BUF_SIZE = Constants.MAXFILESIZE; byte[] buffer = new byte[BUF_SIZE]; try { long totalByteCount = 0; int size = -1; while (-1 != (size = instream.read(buffer, 0, BUF_SIZE))) { outStream.write(buffer, 0, size); totalByteCount += size; LOGGER.debug("Sent " + (size >> 10) + " KB | Total: " + (totalByteCount >> 10) + "KB"); } } catch (Exception t) { LOGGER.info("Download aborted: " + t.getMessage()); } finally { try { instream.close(); } catch (Exception t) { // just ignore } } } public static Integer saveLocal(Integer itemID, String description, String fileName, InputStream is, List<TAttachmentBean> attachments, String sessionID, Integer userId) throws AttachBLException { if (fileName == null || fileName.length() == 0) { throw new IllegalArgumentException("The file name must be not null!"); } TAttachmentBean oldAttach = findLocalAttachment(attachments, fileName); if (oldAttach != null) { //delete the existing attachment LOGGER.debug("Found attach with the same name.Delete attachment " + oldAttach.getFileName() + " for item " + itemID); deleteLocalAttachment(attachments, oldAttach.getObjectID(), sessionID); } //store the new attachment LOGGER.debug("Save attachment " + fileName + " for item " + itemID); //save in the DB TAttachmentBean attach = new TAttachmentBean(); attach.setWorkItem(itemID); attach.setFileName(fileName); attach.setDescription(description); attach.setChangedBy(userId); attach.setLastEdit(new Date()); Integer attachID = new Integer(getAttachTempIndex()); attach.setObjectID(attachID); //ensure the disk file boolean parentDirOk = ensureDir(getFullDirName(sessionID, itemID)); if (!parentDirOk) { LOGGER.error("Error accessing/create directory for attachments"); return null; } //set the fileNameOnDisk attach.setFullFileNameOnDisk(getFullFileName(sessionID, attachID, itemID)); attach.setFileNameOnDisk(getFileNameAttachment(attachID, itemID)); //store on disk the attach storeOnDisk(attach, is); //add attach attachments.add(attach); //no add to lucene the local attachments return attachID; } private static TAttachmentBean findLocalAttachment(List<TAttachmentBean> attachments, String fileName) { if (attachments == null || attachments.isEmpty()) { return null; } for (int i = 0; i < attachments.size(); i++) { TAttachmentBean attach = (TAttachmentBean) attachments.get(i); if (attach.getFileName().equals(fileName)) { return attach; } } return null; } private static TAttachmentBean findLocalAttachment(List<TAttachmentBean> attachments, Integer attachID) { if (attachments == null || attachments.isEmpty()) { return null; } for (int i = 0; i < attachments.size(); i++) { TAttachmentBean attach = (TAttachmentBean) attachments.get(i); if (attach.getObjectID().equals(attachID)) { ensureMimeType(attach); return attach; } } return null; } /** * Save an attachment * @param itemID * @param description * @param fileName * @param is */ public static TAttachmentBean save(Integer itemID, String description, String fileName, InputStream is, Integer userId) throws AttachBLException { if (fileName == null || fileName.length() == 0) { throw new AttachBLException("The fileName must be not null!"); } LOGGER.debug("Save attachment " + fileName + " for item " + itemID); //save in the DB TAttachmentBean attach = new TAttachmentBean(); attach.setWorkItem(itemID); attach.setFileName(fileName); attach.setDescription(description); attach.setChangedBy(userId); attach.setLastEdit(new Date()); Integer attachID = save(attach); if (attachID == null) { throw new AttachBLException("Can't save attachemnt in DB"); } attach.setObjectID(attachID); //ensure the disk file boolean parentDirOk = ensureDir(getFullDirName(null, itemID)); if (!parentDirOk) { LOGGER.error("Error accessing/create directory for attachments"); throw new AttachBLException("Error accessing/create directory for attachments"); } //set the fileNameOnDisk attach.setFullFileNameOnDisk(getFullFileName(null, attachID, itemID)); attach.setFileNameOnDisk(getFileNameAttachment(attachID, itemID)); TAttachmentBean attachDB = null; //store on disk the attach try { storeOnDisk(attach, is); attachDB = loadByID(attachID); attachDB.setFileSize(attach.getFileSize()); attachDB.setFullFileNameOnDisk(attach.getFullFileNameOnDisk()); attachDB.setFileNameOnDisk(attach.getFileNameOnDisk()); //do save again to persist the size save(attachDB); //add to lucene if (LOGGER.isDebugEnabled()) { LOGGER.debug("Add attachment " + attachDB.getObjectID() + " to the existing workItem " + attachDB.getWorkItem()); } AttachmentIndexer.getInstance().addToIndex(attachDB, true); //possible lucene update in other cluster nodes ClusterMarkChangesBL.markDirtyAttachmentInCluster(attachID, CHANGE_TYPE.ADD_TO_INDEX); } catch (AttachBLException e) { //can't store on disk. delete the attachment from db ant throw the ex delete(attachID); throw e; } return attachDB; } /** * Save an attachment as a link. * @param itemID * @param description * @param url * @param userId * @return * @throws AttachBLException */ public static Integer saveAttachmentAsLink(Integer itemID, String description, String url, Integer userId) throws AttachBLException { //save in the DB TAttachmentBean attach = new TAttachmentBean(); attach.setWorkItem(itemID); attach.setFileName(url); attach.setDescription(description); attach.setChangedBy(userId); attach.setLastEdit(new Date()); attach.setIsUrl("Y"); Integer attachID = AttachBL.save(attach); return attachID; } /** * Delete an attachment * @param attachID * @param itemID */ public static void deleteAttachment(Integer attachID, Integer itemID) { delete(attachID); String fullFileName = getFullFileName(null, attachID, itemID); File file = null; try { file = new File(fullFileName); } catch (NullPointerException e) { return; } LOGGER.debug("Trying to delete " + fullFileName); try { if (file.exists()) { file.delete(); } String thumbName = getFullThumbFileName(fullFileName); File fileThumb = new File(thumbName); if (fileThumb.exists()) { fileThumb.delete(); } // check id directory needs to be removed String fullDirName = getAttachDirBase() + File.separator + itemID; List<TAttachmentBean> fileList = getAttachments(itemID); if (fileList.isEmpty()) { deleteDir(fullDirName); } } catch (SecurityException e) { LOGGER.error("Security Exception when trying to delete " + "file " + fullFileName); } //delete from lucene index AttachmentIndexer.getInstance().deleteByKey(attachID); //possible lucene update in other cluster nodes ClusterMarkChangesBL.markDirtyAttachmentInCluster(attachID, CHANGE_TYPE.DELETE_FROM_INDEX); } public static void deleteLocalAttachment(List<TAttachmentBean> attachments, Integer attachID, String sessionID) { TAttachmentBean attach = findLocalAttachment(attachments, attachID); if (attach == null) { LOGGER.error("Attachment not found locally:" + attachID); return; } String fileName = attach.getFullFileNameOnDisk(); File file = null; try { file = new File(fileName); } catch (NullPointerException e) { return; } LOGGER.debug("Trying to delete " + fileName); try { if (file.exists()) { file.delete(); } } catch (SecurityException e) { LOGGER.error("Security Exception when trying to delete " + "file " + fileName); } attachments.remove(attach); } private static boolean ensureFileOnDisk(TAttachmentBean attach, Integer itemID) { String diskFileName = getFullFileName(null, attach.getObjectID(), itemID); LOGGER.debug("diskFileName=" + diskFileName); File file = new File(diskFileName); if (file.exists()) { //ensure length attach.setFileSize(file.length() + ""); attach.setFullFileNameOnDisk(file.getAbsolutePath()); attach.setFileNameOnDisk(getFileNameAttachment(attach.getObjectID(), itemID)); return true; } else { LOGGER.info("The attachment for issue no. " + itemID + " with attachment id " + attach.getObjectID() + " and name " + attach.getFileName() + " does not exist on disk!"); return false; } } public static String getFullFileName(String sessionId, Integer attachID, Integer itemID) { return getFullDirName(sessionId, itemID) + File.separator + getFileNameAttachment(attachID, itemID); } public static String getFullDirName(String sessionId, Integer itemID) { if (sessionId != null) { //localy attach if (itemID == null) { itemID = new Integer(-1); } return getAttachDirBase() + File.separator + sessionId + tempDelimiter + itemID; } return getAttachDirBase() + File.separator + itemID; } /** * @return name of the attachment on disk */ public static String getFileNameAttachment(Integer attachID, Integer itemID) { NumberFormat nf = new DecimalFormat("00000"); String dbId = nf.format(attachID); // choose different suffix for attachments of new WorkItems String fileSuffix; if (itemID == null || itemID.intValue() == -1) { fileSuffix = AttachDbFnameTypeNew; } else { fileSuffix = AttachDbFnameType; } return AttachDbFname + dbId + fileSuffix; } private static boolean ensureDir(String dirName) { File f = new File(dirName); if (!f.exists()) { f.mkdirs(); } return f.exists() && f.isDirectory(); } private static void deleteDir(String fullDirName) { // now go ahead and delete empty directory File dir = null; try { dir = new File(fullDirName); } catch (NullPointerException e) { return; } try { if (dir.exists() && dir.isDirectory()) { if (!dir.delete()) { LOGGER.error("Couldn't delete directory " + fullDirName); } } dir = null; } catch (SecurityException e) { LOGGER.error("Security Exception when trying to delete " + "directory " + fullDirName); } } static public boolean deleteDirectory(File path) { if (path.exists() && path.isDirectory()) { File[] files = path.listFiles(); for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { deleteDirectory(files[i]); } else { files[i].delete(); } } } return path.delete(); } private static void storeOnDisk(TAttachmentBean attach, InputStream inStream) throws AttachBLException { String fullFileName = attach.getFullFileNameOnDisk(); LOGGER.debug("Full Filename for attachment: " + fullFileName); try { //retrieve the file data FileOutputStream faos = new FileOutputStream(fullFileName); byte[] buffer = new byte[8192]; int bytesRead = 0; while ((bytesRead = inStream.read(buffer, 0, 8192)) != -1) { faos.write(buffer, 0, bytesRead); } //close the stream faos.flush(); inStream.close(); faos.close(); } catch (FileNotFoundException fnfe) { LOGGER.error( "Storing the attachment on disk failed: File not found(if the file exists but is a directory rather than a regular file, does not exist but cannot be created, or cannot be opened for any other reason. FullFileName=" + fullFileName, fnfe); throw new AttachBLException("File:'" + fullFileName + "' not found on server. " + fnfe.getMessage(), fnfe); } catch (IOException ioe) { LOGGER.debug(ExceptionUtils.getStackTrace(ioe)); LOGGER.error("Storing the attachment on disk failed: IOException fullFileName=" + fullFileName, ioe); throw new AttachBLException("Can't save attachment:" + ioe.getMessage(), ioe); } attach.setFileSize(Long.toString(new File(fullFileName).length())); //TODO save mimeType in DB after change the DB to allow storing mimeType with more characters } private static synchronized int getAttachTempIndex() { int index = attachTempIndex; attachTempIndex += 1; return index; } /** * Delete all the files and the directory, where the temporary attchments * for a new item were stored * @param sessionID */ public static void deleteTempFiles(String sessionID) { LOGGER.debug("Cleanup attachments for session:" + sessionID); File attachDir = new File(getFullDirName(sessionID, new Integer(-1))); LOGGER.debug("attachDir=" + attachDir.getAbsolutePath()); if (attachDir.exists()) { // remove all files first File[] files = attachDir.listFiles(); for (int i = 0; i < files.length; ++i) { if (files[i].isFile()) { files[i].delete(); } else { LOGGER.error("Unexpected directory found!"); } } // remove the directory if (!attachDir.delete()) { LOGGER.error("Deleting the directory failed!"); } } } /** * Save the attachments for new item * @param attachList List<TAttachmentBean> * @param sessionID * @param itemID */ public static List<Integer> approve(List<TAttachmentBean> attachList, String sessionID, Integer itemID) { if (attachList == null || attachList.isEmpty()) { LOGGER.debug("No attachments were stored for the new item"); return null; } List<Integer> result = new ArrayList<Integer>(attachList.size()); LOGGER.debug("Approve " + attachList.size() + " attachments for item#" + itemID); // create the directory where the attachments will be copied(renamed) to File newDir = new File(getFullDirName(null, itemID)); boolean ok = newDir.mkdir(); if (!ok) { LOGGER.error("Creating new attachment dir " + newDir.getAbsolutePath() + " failed!"); } //ensure the disk file boolean parentDirOk = ensureDir(getFullDirName(null, itemID)); if (!parentDirOk) { LOGGER.error("Error accessing or creating directory for attachments"); return null; } // work through all the attachments Iterator<TAttachmentBean> attachIter = attachList.iterator(); while (attachIter.hasNext()) { TAttachmentBean attach = attachIter.next(); // get the old filename on disk String fileNameOnDiskOld = attach.getFullFileNameOnDisk(); attach.setWorkItem(itemID); //save the attach in DB Integer attachID = save(attach); result.add(attachID); //set the fileNameOnDisk attach.setFullFileNameOnDisk(getFullFileName(null, attachID, itemID)); attach.setFileNameOnDisk(getFileNameAttachment(attachID, itemID)); String fileNameOnDiskNew = attach.getFullFileNameOnDisk(); LOGGER.debug("Rename " + fileNameOnDiskOld + " to " + fileNameOnDiskNew); // rename all the attachments File fileOld = new File(fileNameOnDiskOld); File fileNew = new File(fileNameOnDiskNew); ok = fileOld.renameTo(fileNew); if (!ok) { LOGGER.error( "Renaming " + fileOld.getAbsolutePath() + " to " + fileNew.getAbsolutePath() + " failed!"); } } // remove the old temp-directory deleteTempFiles(sessionID); return result; } /** * get the basename of the attachments direcory * @return */ public static String getAttachDirBase() { // will set path to save from the runtime env. StringBuffer sb = new StringBuffer(); sb.append(trackDataDirBase()); sb.append(AttachmentsName); return sb.toString(); } /** * Gets the basename of the excel imports directory * @return */ public static String getExcelImportDirBase() { // will set path to save from the runtime env. StringBuffer sb = new StringBuffer(); sb.append(trackDataDirBase()); sb.append(excelImportDir); sb.append(File.separator); return sb.toString(); } /** * Gets the basename of the excel imports directory * @return */ public static String getDocxImportDirBase() { // will set path to save from the runtime env. StringBuffer sb = new StringBuffer(); sb.append(trackDataDirBase()); sb.append(docxImportDir); sb.append(File.separator); return sb.toString(); } /** * Gets the basename of the excel imports directory * @return */ /** * Gets the basename of the msProject imports directory * @return */ public static String getMsProjectImportDirBase() { StringBuffer sb = new StringBuffer(); sb.append(trackDataDirBase()); sb.append(msProjectImportDir); sb.append(File.separator); return sb.toString(); } public static String trackDataDirBase() { StringBuffer sb = new StringBuffer(); String trackplusHome = HandleHome.getTrackplus_Home(); sb.append(trackplusHome); if (trackplusHome != null && trackplusHome.length() > 0 && trackplusHome.charAt(trackplusHome.length() - 1) != File.separatorChar) { sb.append(File.separator); } sb.append(HandleHome.DATA_DIR); sb.append(File.separator); return sb.toString(); } /** * Add attachments for an Item * @param attachList List<File> * @param itemID */ public static List<Integer> storeEmailAttachments(List<EmailAttachment> attachList, Integer itemID) { if (attachList == null || attachList.isEmpty()) { return null; } List<Integer> result = new ArrayList<Integer>(attachList.size()); Iterator<EmailAttachment> attachIter = attachList.iterator(); File newDir = new File(getFullDirName(null, itemID)); boolean ok = newDir.mkdirs(); while (attachIter.hasNext()) { EmailAttachment emailAttachment = attachIter.next(); File attachFile = emailAttachment.getFile(); // get the old filename String fileName = attachFile.getName(); // set the new workItemKey -> attachment is added to the DB TAttachmentBean attachBean = new TAttachmentBean(); // set the properties attachBean.setFileName(fileName); attachBean.setWorkItem(itemID); attachBean.setLastEdit(new Date()); Integer attachID = save(attachBean); attachBean.setObjectID(attachID); result.add(attachID); //ensure the disk file boolean parentDirOk = ensureDir(getFullDirName(null, itemID)); if (!parentDirOk) { LOGGER.error("Error accessing or creating directory for attachments"); return null; } //set the fileNameOnDisk attachBean.setFullFileNameOnDisk(getFullFileName(null, attachID, itemID)); attachBean.setFileNameOnDisk(getFileNameAttachment(attachID, itemID)); // rename the attachments File fileNew = new File(attachBean.getFullFileNameOnDisk()); ok = attachFile.renameTo(fileNew); if (!ok) { LOGGER.error("Renaming " + attachFile.getAbsolutePath() + " to " + fileNew.getAbsolutePath() + " failed!"); } } return result; } public static void replaceInlineImagesDescription(Integer workItemID, List<TAttachmentBean> attachList, List<Integer> attachIDList) { TWorkItemBean workItemBean = null; try { workItemBean = ItemBL.loadWorkItem(workItemID); } catch (Exception ex) { return; } if (workItemBean == null) { return; } String originalDescription = workItemBean.getDescription(); if (originalDescription == null || originalDescription.length() == 0) { return; } String description = originalDescription; description = replaceInlineImagesText(attachList, attachIDList, workItemID, description); if (!originalDescription.equals(description)) { workItemBean.setDescription(description); try { DAOFactory.getFactory().getWorkItemDAO().saveSimple(workItemBean); } catch (ItemPersisterException e) { LOGGER.debug(ExceptionUtils.getStackTrace(e)); } } } public static void replaceEmailInlineImagesDescription(Integer workItemID, List<EmailAttachment> attachList, List<Integer> emailAttachmentIDList) { TWorkItemBean workItemBean = null; try { workItemBean = ItemBL.loadWorkItem(workItemID); } catch (Exception ex) { return; } if (workItemBean == null) { return; } String originalDescription = workItemBean.getDescription(); if (originalDescription == null || originalDescription.length() == 0) { return; } String description = originalDescription; description = replaceInlineImagesTextMail(attachList, emailAttachmentIDList, workItemID, description); if (!originalDescription.equals(description)) { workItemBean.setDescription(description); try { DAOFactory.getFactory().getWorkItemDAO().saveSimple(workItemBean); } catch (ItemPersisterException e) { LOGGER.debug(ExceptionUtils.getStackTrace(e)); } } } public static String replaceInlineImagesText(List<TAttachmentBean> attachList, List<Integer> attachIDList, Integer workItemID, String description) { for (int i = 0; i < attachList.size(); i++) { TAttachmentBean attachment = attachList.get(i); Integer attachKey = attachIDList.get(i); Integer tmpID = attachment.getObjectID(); String srcTxt = "src=\"downloadAttachment.action?attachKey=" + tmpID + "\"";//"src=\"downloadAttachment.action?attachKey="+tmpID+"\""; String srcReplace = "src=\"downloadAttachment.action?workItemID=" + workItemID + "&attachKey=" + attachKey + "\""; if (description.indexOf(srcTxt) != -1) { description = description.replaceAll(srcTxt, srcReplace); } } return description; } public static String replaceInlineImagesTextMail(List<EmailAttachment> attachList, List<Integer> emailAttachmentIDList, Integer workItemID, String description) { for (int i = 0; i < attachList.size(); i++) { EmailAttachment attachment = attachList.get(i); String cid = attachment.getCid(); String srcTxt = "src=\"cid:" + cid + "\""; Integer attachKey = emailAttachmentIDList.get(i); String srcReplace = "src=\"downloadAttachment.action?workItemID=" + workItemID + "&attachKey=" + attachKey + "\""; if (description.indexOf(srcTxt) != -1) { description = description.replaceAll(srcTxt, srcReplace); } } return description; } public static Integer save(TAttachmentBean tAttachmentBean) { return attachmentDAO.save(tAttachmentBean); } public static boolean isImage(TAttachmentBean attachBean) { String fileName = attachBean.getFileName(); String ext = null; int idx = fileName.lastIndexOf("."); if (idx > 0 && idx < fileName.length() - 1) { ext = fileName.substring(idx + 1); } return (ext != null && imgExt.contains(ext.toUpperCase())); } public static Integer getThumbnailHeight(String sessionID, TAttachmentBean attachBean) { Integer result = null; if (isImage(attachBean)) { boolean b = createTumbnail(sessionID, attachBean); if (b) { String thumbFileName = getFullThumbFileName(sessionID, attachBean); Image image = Toolkit.getDefaultToolkit().getImage(thumbFileName); return image.getHeight(null); } } return result; } public static boolean hasTumbnail(String sessionID, TAttachmentBean attach) { String thumbName = getFullThumbFileName(sessionID, attach); File f = new File(thumbName); return f.exists(); } public static boolean createTumbnail(String sessionID, TAttachmentBean attach) { if (hasTumbnail(sessionID, attach)) { return true; } String fileName = attach.getFullFileNameOnDisk(); String thumbFileName = getFullThumbFileName(sessionID, attach); return createThumbFile(fileName, thumbFileName, 100, 100, 0); } /** * @param fileName the file name of the original image * @param thumbFileName the file name of the thumb created from the original * @param thumbWidth * @param thumbHeight * @return */ public static boolean createThumbFile(String fileName, String thumbFileName, int thumbWidth, int thumbHeight, int cornerRadius) { BufferedImage image = null; try { image = ImageIO.read(new File(fileName)); } catch (Exception e) { // too bad... return false; } BufferedImage newImg = ImageUtils.scaleImage(image, thumbWidth, thumbHeight, cornerRadius); File outfile = new File(thumbFileName); try { ImageIO.write(newImg, "png", outfile); return true; } catch (IOException e) { LOGGER.debug(ExceptionUtils.getStackTrace(e)); } return false; } private static boolean hasAlpha(Image image) { // If buffered image, the color model is readily available if (image instanceof BufferedImage) { BufferedImage bimage = (BufferedImage) image; return bimage.getColorModel().hasAlpha(); } // grabbing a single pixel is usually sufficient PixelGrabber pg = new PixelGrabber(image, 0, 0, 1, 1, false); try { pg.grabPixels(); } catch (InterruptedException e) { } // Get the image's color model ColorModel cm = pg.getColorModel(); return cm.hasAlpha(); } private static BufferedImage toBufferedImage(Image image) { if (image instanceof BufferedImage) { return (BufferedImage) image; } // This code ensures that all the pixels in the image are loaded image = new ImageIcon(image).getImage(); // Determine if the image has transparent pixels; for this method's // implementation, see Determining If an Image Has Transparent Pixels boolean hasAlpha = hasAlpha(image); // Create a buffered image with a format that's compatible with the screen BufferedImage bimage = null; GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); try { // Determine the type of transparency of the new buffered image int transparency = Transparency.OPAQUE; if (hasAlpha) { transparency = Transparency.BITMASK; } // Create the buffered image GraphicsDevice gs = ge.getDefaultScreenDevice(); GraphicsConfiguration gc = gs.getDefaultConfiguration(); bimage = gc.createCompatibleImage(image.getWidth(null), image.getHeight(null), transparency); } catch (HeadlessException e) { // The system does not have a screen } if (bimage == null) { // Create a buffered image using the default color model int type = BufferedImage.TYPE_INT_RGB; if (hasAlpha) { type = BufferedImage.TYPE_INT_ARGB; } bimage = new BufferedImage(image.getWidth(null), image.getHeight(null), type); } // Copy image to buffered image Graphics g = bimage.createGraphics(); // Paint the image onto the buffered image g.drawImage(image, 0, 0, null); g.dispose(); return bimage; } private static String getFullThumbFileName(String sessionID, TAttachmentBean attach) { String fullFileName = getFullFileName(sessionID, attach.getObjectID(), attach.getWorkItem()); return getFullThumbFileName(fullFileName); } private static String getFullThumbFileName(String fullFileName) { int idxDot = fullFileName.lastIndexOf("."); String thumbName = fullFileName.substring(0, idxDot) + "_thumb.png"; return thumbName; } //-------------------Mime types private static void ensureMimeType(TAttachmentBean attach) { String mimeType = null; if (mimeTypeMap == null) { createMimeTypeMap( ApplicationBean.getInstance().getServletContext().getResourceAsStream("/WEB-INF/mime.types")); } if (mimeTypeMap != null) { mimeType = mimeTypeMap.getContentType(attach.getFileName().toLowerCase()); } if (mimeType == null) { // set default mimeType mimeType = "text/html"; } LOGGER.debug("MimeType for: " + attach.getFileName() + " is:" + mimeType); attach.setMimeType(mimeType); } /** * create the mime type-map * @param is, stream with fiel-based mime-definitions */ private static void createMimeTypeMap(InputStream is) { if (is != null) { mimeTypeMap = new MimetypesFileTypeMap(is); } else { mimeTypeMap = new MimetypesFileTypeMap(); } } /** * Copy the Attachments from the original workItemBean to the copied. * It generates the db and the file information * @param workItemBeanOriginalId * @param workItemBeanCopyId * @param userOrSessionId if it is known the copied id, the userId must be passed * else the sessionId * @return list of attachments if the copied id is null or -1, else null * @throws AttachBLException */ public static List<TAttachmentBean> copyAttachments(Integer workItemBeanOriginalId, Integer workItemBeanCopyId, String userOrSessionId, Integer userID) throws AttachBLException { //First all attachments of the original workItem are loaded List<TAttachmentBean> attachmentsList = loadByWorkItem(workItemBeanOriginalId); List<TAttachmentBean> attachmentsReturn = new ArrayList<TAttachmentBean>(); Iterator<TAttachmentBean> it = attachmentsList.iterator(); while (it.hasNext()) { TAttachmentBean attachOriginal = it.next(); //verify the file on disk if (ensureFileOnDisk(attachOriginal, workItemBeanOriginalId)) { } else { //The file not exist on the disk. Somebody delete it! continue; } try { if (workItemBeanCopyId == null || workItemBeanCopyId.intValue() == -1) { //If it is editCopy, we do not know the copied workItem id, so the attachments must be save in local List<TAttachmentBean> attachments = new ArrayList<TAttachmentBean>(); saveLocal(workItemBeanCopyId, attachOriginal.getDescription(), attachOriginal.getFileName(), new FileInputStream(new File(attachOriginal.getFullFileNameOnDisk())), attachments, userOrSessionId.toString(), userID); //and the return is prepared because it will be needed by the context attachmentsReturn.addAll(attachments); } else { //else, we know the copied workItem id, so we save it correctly save(workItemBeanCopyId, attachOriginal.getDescription(), attachOriginal.getFileName(), new FileInputStream(new File(attachOriginal.getFullFileNameOnDisk())), Integer.parseInt(userOrSessionId)); } } catch (FileNotFoundException fnfe) { LOGGER.debug(ExceptionUtils.getStackTrace(fnfe)); return null; } } return attachmentsReturn; } public static Double getMaxAttachmentSizeInMb(ApplicationBean applicationBean) { TSiteBean siteBean = applicationBean.getSiteBean(); Double maxAttachmentSizeInMb = siteBean.getMaxAttachmentSize(); if (maxAttachmentSizeInMb == null) { maxAttachmentSizeInMb = 4d; } return maxAttachmentSizeInMb; } public static int getMaxFileSize(ApplicationBean applicationBean) { TSiteBean siteBean = applicationBean.getSiteBean(); return getMaxFileSize(siteBean); } public static int getMaxFileSize(TSiteBean siteBean) { Double maxAttachmentSizeInMb = siteBean.getMaxAttachmentSize(); int max = 0; if (maxAttachmentSizeInMb == null || maxAttachmentSizeInMb.doubleValue() <= 0) { //default value 4MB max = new Double(4.0 * 1024 * 1024).intValue(); } else { max = new Double(maxAttachmentSizeInMb.doubleValue() * 1024 * 1024).intValue(); } return max; } public static Integer storeFile(Integer workItemID, Integer personID, String description, Locale locale, Map<String, Object> session, HttpServletResponse response, File file, String fileName, Double maxAttachmentSizeInMb, int MAXFILESIZE, List<LabelValueBean> errors, boolean addToHistory) { File f = file; LOGGER.debug("upload path for file'" + fileName + "':" + f.getAbsolutePath()); if (f.length() > MAXFILESIZE) { String err = getText("item.tabs.attachment.maxLengthExceeded", new String[] { maxAttachmentSizeInMb + "" }, locale); errors.add(new LabelValueBean(err, "theFile")); return null; } FileInputStream is; try { is = new FileInputStream(f); } catch (FileNotFoundException e) { LOGGER.debug(ExceptionUtils.getStackTrace(e)); LOGGER.error("Error upload file" + f.getAbsolutePath() + " file not found!"); String err = "File not found"; errors.add(new LabelValueBean(err, "theFile")); return null; } ApplicationBean appBean = ApplicationBean.getInstance(); if (appBean.isBackupInProgress()) { String err = getText("item.tabs.attachment.err.backupInProgress", locale); try { is.close(); } catch (IOException ioe) { LOGGER.error("Unable to close input stream."); } errors.add(new LabelValueBean(err, "theFile")); return null; } if (workItemID == null/*||workItemID.intValue()==-1*/) { HttpServletRequest request = ServletActionContext.getRequest(); HttpSession httpSession = request.getSession(); WorkItemContext ctx = (WorkItemContext) session.get("workItemContext"); if (ctx == null) { LOGGER.error("No context on session"); try { is.close(); } catch (IOException ioe) { LOGGER.error("Unable to close input stream."); } String err = "No context on session!"; errors.add(new LabelValueBean(err, "theFile")); return null; } List<TAttachmentBean> attachments = ctx.getAttachmentsList(); if (attachments == null) { attachments = new ArrayList<TAttachmentBean>(); } String sessionID = httpSession.getId(); Integer attachKey = null; try { attachKey = AttachBL.saveLocal(workItemID, description, fileName, is, attachments, sessionID, personID); } catch (AttachBLException e) { String err = ""; if (e.getLocalizedKey() != null) { err = getText(e.getLocalizedKey(), e.getLocalizedParameteres(), locale); } else { err = e.getMessage(); } errors.add(new LabelValueBean(err, "theFile")); return null; } ctx.setAttachmentsList(attachments); f.delete(); return attachKey; } else { try { TAttachmentBean attachmentBean = AttachBL.save(workItemID, description, fileName, is, personID); TPersonBean person = LookupContainer.getPersonBean(personID); LOGGER.debug("Saving attachment " + fileName + " by user " + person.getFullName()); //add to history if (addToHistory) { HistorySaverBL.addAttachment(workItemID, personID, locale, fileName, description, Long.valueOf(f.length()), false); } f.delete(); return attachmentBean.getObjectID(); } catch (AttachBLException e) { LOGGER.error("Can't save attachemnt", e); String err = ""; if (e.getLocalizedKey() != null) { err = getText(e.getLocalizedKey(), e.getLocalizedParameteres(), locale); } else { err = e.getMessage(); } errors.add(new LabelValueBean(err, "theFile")); return null; } } } private static String getText(String s, Locale locale) { return LocalizeUtil.getLocalizedTextFromApplicationResources(s, locale); } private static String getText(String key, String[] params, Locale locale) { return LocalizeUtil.getParametrizedString(key, params, locale); } }