diff --git a/dbPort/src/org/compiere/model/MAttachment.java b/dbPort/src/org/compiere/model/MAttachment.java index b2f0bee81e..13919c76c4 100644 --- a/dbPort/src/org/compiere/model/MAttachment.java +++ b/dbPort/src/org/compiere/model/MAttachment.java @@ -17,11 +17,30 @@ package org.compiere.model; import java.io.*; +import java.nio.channels.FileChannel; import java.sql.*; import java.util.*; import java.util.logging.*; import java.util.zip.*; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + import org.compiere.util.*; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + /** * Attachment Model. @@ -86,6 +105,8 @@ public class MAttachment extends X_AD_Attachment public MAttachment(Properties ctx, int AD_Attachment_ID, String trxName) { super (ctx, AD_Attachment_ID, trxName); + initAttachmentStoreDetails(ctx, trxName); + } // MAttachment /** @@ -100,6 +121,7 @@ public class MAttachment extends X_AD_Attachment this (ctx, 0, trxName); setAD_Table_ID (AD_Table_ID); setRecord_ID (Record_ID); + initAttachmentStoreDetails(ctx, trxName); } // MAttachment /** @@ -111,6 +133,7 @@ public class MAttachment extends X_AD_Attachment public MAttachment(Properties ctx, ResultSet rs, String trxName) { super(ctx, rs, trxName); + initAttachmentStoreDetails(ctx, trxName); } // MAttachment /** Indicator for no data */ @@ -121,6 +144,40 @@ public class MAttachment extends X_AD_Attachment /** List of Entry Data */ private ArrayList m_items = null; + /** is this client using the file system for attachments */ + private boolean isStoreAttachmentsOnFileSystem = false; + + /** attachment (root) path - if file system is used */ + private String m_attachmentPathRoot = ""; + + /** string replaces the attachment root in stored xml file + * to allow the changing of the attachment root. */ + private final String ATTACHMENT_FOLDER_PLACEHOLDER = "%ATTACHMENT_FOLDER%"; + + /** + * Get the isStoreAttachmentsOnFileSystem and attachmentPath for the client. + * @param ctx + * @param trxName + */ + private void initAttachmentStoreDetails(Properties ctx, String trxName){ + final MClient client = new MClient(ctx, this.getAD_Client_ID(), trxName); + isStoreAttachmentsOnFileSystem = client.isStoreAttachmentsOnFileSystem(); + if(isStoreAttachmentsOnFileSystem){ + if(File.separatorChar == '\\'){ + m_attachmentPathRoot = client.getWindowsAttachmentPath(); + } else { + m_attachmentPathRoot = client.getUnixAttachmentPath(); + } + if("".equals(m_attachmentPathRoot)){ + log.severe("no attachmentPath defined"); + } else if (!m_attachmentPathRoot.endsWith(File.separator)){ + log.warning("attachment path doesn't end with " + File.separator); + m_attachmentPathRoot = m_attachmentPathRoot + File.separator; + log.fine(m_attachmentPathRoot); + } + } + } + /** * Set Client Org * @param AD_Client_ID client @@ -276,21 +333,32 @@ public class MAttachment extends X_AD_Attachment } // getEntries /** - * Delete Entry - * @param index index - * @return true if deleted + * Delete Entry + * + * @param index + * index + * @return true if deleted */ - public boolean deleteEntry (int index) - { - if (index >= 0 && index < m_items.size()) - { + public boolean deleteEntry(int index) { + if (index >= 0 && index < m_items.size()) { + if(isStoreAttachmentsOnFileSystem){ + //remove files + final MAttachmentEntry entry = m_items.get(index); + final File file = entry.getFile(); + log.fine("delete: " + file.getAbsolutePath()); + if(file !=null && file.exists()){ + if(!file.delete()){ + log.warning("unable to delete " + file.getAbsolutePath()); + } + } + } m_items.remove(index); log.config("Index=" + index + " - NewSize=" + m_items.size()); return true; } log.warning("Not deleted Index=" + index + " - Size=" + m_items.size()); return false; - } // deleteEntry + } // deleteEntry /** * Get Entry Count @@ -305,17 +373,24 @@ public class MAttachment extends X_AD_Attachment /** - * Get Entry Name - * @param index index - * @return name or null + * Get Entry Name + * + * @param index + * index + * @return name or null */ - public String getEntryName (int index) - { + public String getEntryName(int index) { MAttachmentEntry item = getEntry(index); - if (item != null) - return item.getName(); + if (item != null){ + //strip path + String name = item.getName(); + if(name!=null && isStoreAttachmentsOnFileSystem){ + name = name.substring(name.lastIndexOf(File.separator)+1); + } + return name; + } return null; - } // getEntryName + } // getEntryName /** * Dump Entry Names @@ -375,11 +450,24 @@ public class MAttachment extends X_AD_Attachment return null; } // getEntryFile + /** * Save Entry Data in Zip File format * @return true if saved */ private boolean saveLOBData() + { + if(isStoreAttachmentsOnFileSystem){ + return saveLOBDataToFileSystem(); + } + return saveLOBDataToDB(); + } + + /** + * Save Entry Data in Zip File format into the database. + * @return true if saved + */ + private boolean saveLOBDataToDB() { if (m_items == null || m_items.size() == 0) { @@ -422,12 +510,119 @@ public class MAttachment extends X_AD_Attachment setBinaryData(null); return false; } // saveLOBData + + /** + * Save Entry Data to the file system. + * @return true if saved + */ + private boolean saveLOBDataToFileSystem() + { + if("".equals(m_attachmentPathRoot)){ + log.severe("no attachmentPath defined"); + return false; + } + if (m_items == null || m_items.size() == 0) { + setBinaryData(null); + return true; + } + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + try { + final DocumentBuilder builder = factory.newDocumentBuilder(); + final Document document = builder.newDocument(); + final Element root = document.createElement("attachments"); + document.appendChild(root); + document.setXmlStandalone(true); + // create xml entries + for (int i = 0; i < m_items.size(); i++) { + log.fine(m_items.get(i).toString()); + File entryFile = m_items.get(i).getFile(); + final String path = entryFile.getAbsolutePath(); + // if local file - copy to central attachment folder + log.fine(path + " - " + m_attachmentPathRoot); + if (!path.startsWith(m_attachmentPathRoot)) { + log.fine("move file: " + path); + FileChannel in = null; + FileChannel out = null; + try { + //create destination folder + final File destFolder = new File(m_attachmentPathRoot + File.separator + getAttachmentPathSnippet()); + if(!destFolder.exists()){ + if(!destFolder.mkdirs()){ + log.warning("unable to create folder: " + destFolder.getPath()); + } + } + final File destFile = new File(m_attachmentPathRoot + File.separator + + getAttachmentPathSnippet() + File.separator + entryFile.getName()); + in = new FileInputStream(entryFile).getChannel(); + out = new FileOutputStream(destFile).getChannel(); + in.transferTo(0, in.size(), out); + in.close(); + out.close(); + if(entryFile.exists()){ + if(!entryFile.delete()){ + entryFile.deleteOnExit(); + } + } + entryFile = destFile; + } catch (IOException e) { + e.printStackTrace(); + log.severe("unable to copy file " + entryFile.getAbsolutePath() + " to " + + m_attachmentPathRoot + File.separator + + getAttachmentPathSnippet() + File.separator + entryFile.getName()); + } finally { + if (in != null && in.isOpen()) { + in.close(); + } + if (out != null && out.isOpen()) { + out.close(); + } + } + } + final Element entry = document.createElement("entry"); + //entry.setAttribute("name", m_items.get(i).getName()); + entry.setAttribute("name", getEntryName(i)); + String filePathToStore = entryFile.getAbsolutePath(); + filePathToStore = filePathToStore.replaceFirst(m_attachmentPathRoot.replaceAll("\\\\","\\\\\\\\"), ATTACHMENT_FOLDER_PLACEHOLDER); + log.fine(filePathToStore); + entry.setAttribute("file", filePathToStore); + root.appendChild(entry); + } + + final Source source = new DOMSource(document); + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + final Result result = new StreamResult(bos); + final Transformer xformer = TransformerFactory.newInstance().newTransformer(); + xformer.transform(source, result); + final byte[] xmlData = bos.toByteArray(); + log.fine(bos.toString()); + setBinaryData(xmlData); + return true; + } catch (Exception e) { + log.log(Level.SEVERE, "saveLOBData", e); + } + setBinaryData(null); + return false; + + } + /** * Load Data into local m_data * @return true if success */ private boolean loadLOBData () + { + if(isStoreAttachmentsOnFileSystem){ + return loadLOBDataFromFileSystem(); + } + return loadLOBDataFromDB(); + } + + /** + * Load Data from database + * @return true if success + */ + private boolean loadLOBDataFromDB () { // Reset m_items = new ArrayList(); @@ -482,6 +677,110 @@ public class MAttachment extends X_AD_Attachment return true; } // loadLOBData + /** + * Load Data from file system + * @return true if success + */ + private boolean loadLOBDataFromFileSystem(){ + if("".equals(m_attachmentPathRoot)){ + log.severe("no attachmentPath defined"); + return false; + } + // Reset + m_items = new ArrayList(); + // + byte[] data = getBinaryData(); + if (data == null) + return true; + log.fine("TextFileSize=" + data.length); + if (data.length == 0) + return true; + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + try { + final DocumentBuilder builder = factory.newDocumentBuilder(); + final Document document = builder.parse(new ByteArrayInputStream(data)); + final NodeList entries = document.getElementsByTagName("entry"); + for (int i = 0; i < entries.getLength(); i++) { + final Node entryNode = entries.item(i); + final NamedNodeMap attributes = entryNode.getAttributes(); + final Node fileNode = attributes.getNamedItem("file"); + final Node nameNode = attributes.getNamedItem("name"); + if(fileNode==null || nameNode==null){ + log.severe("no filename for entry " + i); + m_items = null; + return false; + } + log.fine("name: " + nameNode.getNodeValue()); + String filePath = fileNode.getNodeValue(); + log.fine("filePath: " + filePath); + if(filePath!=null){ + filePath = filePath.replaceFirst(ATTACHMENT_FOLDER_PLACEHOLDER, m_attachmentPathRoot.replaceAll("\\\\","\\\\\\\\")); + //just to be shure... + String replaceSeparator = File.separator; + if(!replaceSeparator.equals("/")){ + replaceSeparator = "\\\\"; + } + filePath = filePath.replaceAll("/", replaceSeparator); + filePath = filePath.replaceAll("\\\\", replaceSeparator); + } + log.fine("filePath: " + filePath); + final File file = new File(filePath); + if (file.exists()) { + // read files into byte[] + final byte[] dataEntry = new byte[(int) file.length()]; + try { + final FileInputStream fileInputStream = new FileInputStream(file); + fileInputStream.read(dataEntry); + fileInputStream.close(); + } catch (FileNotFoundException e) { + log.severe("File Not Found."); + e.printStackTrace(); + } catch (IOException e1) { + log.severe("Error Reading The File."); + e1.printStackTrace(); + } + final MAttachmentEntry entry = new MAttachmentEntry(filePath, + dataEntry, m_items.size() + 1); + m_items.add(entry); + } else { + log.severe("file not found: " + file.getAbsolutePath()); + } + } + + } catch (SAXException sxe) { + // Error generated during parsing) + Exception x = sxe; + if (sxe.getException() != null) + x = sxe.getException(); + x.printStackTrace(); + log.severe(x.getMessage()); + + } catch (ParserConfigurationException pce) { + // Parser with specified options can't be built + pce.printStackTrace(); + log.severe(pce.getMessage()); + + } catch (IOException ioe) { + // I/O error + ioe.printStackTrace(); + log.severe(ioe.getMessage()); + } + + return true; + + } + + /** + * Returns a path snippet, containing client, org, table and record id. + * @return String + */ + private String getAttachmentPathSnippet(){ + return this.getAD_Client_ID() + File.separator + + this.getAD_Org_ID() + File.separator + + this.getAD_Table_ID() + File.separator + this.getRecord_ID(); + } /** * Before Save @@ -495,6 +794,32 @@ public class MAttachment extends X_AD_Attachment return saveLOBData(); // save in BinaryData } // beforeSave + /** + * Executed before Delete operation. + * @return true if record can be deleted + */ + protected boolean beforeDelete () + { + if(isStoreAttachmentsOnFileSystem){ + //delete all attachment files and folder + for(int i=0; i 255) +{ +log.warning("Length > 255 - truncated"); +UnixAttachmentPath = UnixAttachmentPath.substring(0,254); +} +set_Value ("UnixAttachmentPath", UnixAttachmentPath); +} +/** Get Unix Attachment Path. +@return Unix Attachment Path - If you change this value make sure to copy the attachments to the new path! */ +public String getUnixAttachmentPath() +{ +return (String)get_Value("UnixAttachmentPath"); +} +/** Column name UnixAttachmentPath */ +public static final String COLUMNNAME_UnixAttachmentPath = "UnixAttachmentPath"; /** Set Search Key. @param Value Search key for the record in the format required - must be unique */ public void setValue (String Value) @@ -479,4 +557,25 @@ public String getValue() { return (String)get_Value("Value"); } +/** Column name Value */ +public static final String COLUMNNAME_Value = "Value"; +/** Set Windows Attachment Path. +@param WindowsAttachmentPath Windows Attachment Path - If you change this value make sure to copy the attachments to the new path! */ +public void setWindowsAttachmentPath (String WindowsAttachmentPath) +{ +if (WindowsAttachmentPath != null && WindowsAttachmentPath.length() > 255) +{ +log.warning("Length > 255 - truncated"); +WindowsAttachmentPath = WindowsAttachmentPath.substring(0,254); +} +set_Value ("WindowsAttachmentPath", WindowsAttachmentPath); +} +/** Get Windows Attachment Path. +@return Windows Attachment Path - If you change this value make sure to copy the attachments to the new path! */ +public String getWindowsAttachmentPath() +{ +return (String)get_Value("WindowsAttachmentPath"); +} +/** Column name WindowsAttachmentPath */ +public static final String COLUMNNAME_WindowsAttachmentPath = "WindowsAttachmentPath"; }