diff --git a/migration/i7.1/oracle/201608191500_Ticket_1006528.sql b/migration/i7.1/oracle/201608191500_Ticket_1006528.sql new file mode 100644 index 0000000000..da3e3479a4 --- /dev/null +++ b/migration/i7.1/oracle/201608191500_Ticket_1006528.sql @@ -0,0 +1,43 @@ +SET SQLBLANKLINES ON +SET DEFINE OFF + +-- Aug 18, 2016 9:03:49 PM MYT +-- 1006528 Implement Image Storage Provider +INSERT INTO AD_Element (AD_Element_ID,ColumnName,Updated,Name,Description,PrintName,AD_Element_UU,IsActive,Created,AD_Org_ID,CreatedBy,UpdatedBy,AD_Client_ID,EntityType) VALUES (203038,'StorageImage_ID',TO_DATE('2016-08-18 21:03:42','YYYY-MM-DD HH24:MI:SS'),'Image Store','Storage provider for Image','Image Store','a4df3881-4d7e-4b3b-a71a-3380bdebf371','Y',TO_DATE('2016-08-18 21:03:42','YYYY-MM-DD HH24:MI:SS'),0,100,100,0,'D') +; + +-- Aug 18, 2016 9:05:23 PM MYT +INSERT INTO AD_Column (AD_Column_ID,SeqNoSelection,IsSyncDatabase,Version,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsParent,FieldLength,IsSelectionColumn,IsKey,IsAutocomplete,IsAllowLogging,AD_Column_UU,Updated,IsUpdateable,ColumnName,Description,Name,IsAllowCopy,IsActive,CreatedBy,UpdatedBy,IsToolbarButton,IsAlwaysUpdateable,AD_Client_ID,AD_Org_ID,Created,EntityType,IsEncrypted,IsSecure,FKConstraintType,AD_Element_ID,AD_Reference_ID,AD_Reference_Value_ID,AD_Table_ID) VALUES (212831,0,'N',0,'N','N','N',0,'N',22,'N','N','N','Y','a3d7a4ea-9d2e-4da6-b48f-7ee5242719ee',TO_DATE('2016-08-18 21:05:07','YYYY-MM-DD HH24:MI:SS'),'Y','StorageImage_ID','Storage provider for Image','Image Store','Y','Y',100,100,'N','N',0,0,TO_DATE('2016-08-18 21:05:07','YYYY-MM-DD HH24:MI:SS'),'D','N','N','N',203038,18,200023,227) +; + +-- Aug 18, 2016 9:05:59 PM MYT +UPDATE AD_Column SET FKConstraintType='N', FKConstraintName='StorageImage_ADClientInfo',Updated=TO_DATE('2016-08-18 21:05:59','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=212831 +; + +-- Aug 18, 2016 9:05:59 PM MYT +ALTER TABLE AD_ClientInfo ADD StorageImage_ID NUMBER(10) DEFAULT NULL +; + +-- Aug 18, 2016 9:06:00 PM MYT +ALTER TABLE AD_ClientInfo ADD CONSTRAINT StorageImage_ADClientInfo FOREIGN KEY (StorageImage_ID) REFERENCES ad_storageprovider(ad_storageprovider_id) DEFERRABLE INITIALLY DEFERRED +; + +-- Aug 18, 2016 9:07:51 PM MYT +INSERT INTO AD_Field (SortNo,AD_Field_ID,IsEncrypted,DisplayLength,IsSameLine,IsHeading,SeqNo,IsCentrallyMaintained,IsReadOnly,AD_Org_ID,Updated,Description,Name,AD_Field_UU,IsDisplayed,IsFieldOnly,CreatedBy,UpdatedBy,IsActive,IsDisplayedGrid,SeqNoGrid,XPosition,IsQuickEntry,AD_Client_ID,Created,ColumnSpan,NumLines,IsAdvancedField,IsDefaultFocus,AD_Column_ID,EntityType,AD_Tab_ID) VALUES (0,204272,'N',0,'N','N',1020,'Y','N',0,TO_DATE('2016-08-18 21:07:45','YYYY-MM-DD HH24:MI:SS'),'Storage provider for Image','Image Store','471cc623-0460-4b6a-8e24-74055858ce8a','Y','N',100,100,'Y','Y',1020,1,'N',0,TO_DATE('2016-08-18 21:07:45','YYYY-MM-DD HH24:MI:SS'),2,1,'N','N',212831,'D',169) +; + +-- Aug 18, 2016 9:08:51 PM MYT +UPDATE AD_Field SET SeqNo=120, IsDisplayed='Y', XPosition=1,Updated=TO_DATE('2016-08-18 21:08:51','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=204272 +; + +-- Aug 18, 2016 9:08:51 PM MYT +UPDATE AD_Field SET SeqNo=125,Updated=TO_DATE('2016-08-18 21:08:51','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=202532 +; + +-- Aug 18, 2016 9:08:51 PM MYT +UPDATE AD_Field SET SeqNo=130,Updated=TO_DATE('2016-08-18 21:08:51','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=202533 +; + +SELECT register_migration_script('201608191500_Ticket_1006528.sql') FROM dual +; + diff --git a/migration/i7.1/postgresql/201608191500_Ticket_1006528.sql b/migration/i7.1/postgresql/201608191500_Ticket_1006528.sql new file mode 100644 index 0000000000..09b933e5f0 --- /dev/null +++ b/migration/i7.1/postgresql/201608191500_Ticket_1006528.sql @@ -0,0 +1,40 @@ +-- Aug 18, 2016 9:03:49 PM MYT +-- 1006528 Implement Image Storage Provider +INSERT INTO AD_Element (AD_Element_ID,ColumnName,Updated,Name,Description,PrintName,AD_Element_UU,IsActive,Created,AD_Org_ID,CreatedBy,UpdatedBy,AD_Client_ID,EntityType) VALUES (203038,'StorageImage_ID',TO_TIMESTAMP('2016-08-18 21:03:42','YYYY-MM-DD HH24:MI:SS'),'Image Store','Storage provider for Image','Image Store','a4df3881-4d7e-4b3b-a71a-3380bdebf371','Y',TO_TIMESTAMP('2016-08-18 21:03:42','YYYY-MM-DD HH24:MI:SS'),0,100,100,0,'D') +; + +-- Aug 18, 2016 9:05:23 PM MYT +INSERT INTO AD_Column (AD_Column_ID,SeqNoSelection,IsSyncDatabase,Version,IsMandatory,IsTranslated,IsIdentifier,SeqNo,IsParent,FieldLength,IsSelectionColumn,IsKey,IsAutocomplete,IsAllowLogging,AD_Column_UU,Updated,IsUpdateable,ColumnName,Description,Name,IsAllowCopy,IsActive,CreatedBy,UpdatedBy,IsToolbarButton,IsAlwaysUpdateable,AD_Client_ID,AD_Org_ID,Created,EntityType,IsEncrypted,IsSecure,FKConstraintType,AD_Element_ID,AD_Reference_ID,AD_Reference_Value_ID,AD_Table_ID) VALUES (212831,0,'N',0,'N','N','N',0,'N',22,'N','N','N','Y','a3d7a4ea-9d2e-4da6-b48f-7ee5242719ee',TO_TIMESTAMP('2016-08-18 21:05:07','YYYY-MM-DD HH24:MI:SS'),'Y','StorageImage_ID','Storage provider for Image','Image Store','Y','Y',100,100,'N','N',0,0,TO_TIMESTAMP('2016-08-18 21:05:07','YYYY-MM-DD HH24:MI:SS'),'D','N','N','N',203038,18,200023,227) +; + +-- Aug 18, 2016 9:05:59 PM MYT +UPDATE AD_Column SET FKConstraintType='N', FKConstraintName='StorageImage_ADClientInfo',Updated=TO_TIMESTAMP('2016-08-18 21:05:59','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Column_ID=212831 +; + +-- Aug 18, 2016 9:05:59 PM MYT +ALTER TABLE AD_ClientInfo ADD COLUMN StorageImage_ID NUMERIC(10) DEFAULT NULL +; + +-- Aug 18, 2016 9:06:00 PM MYT +ALTER TABLE AD_ClientInfo ADD CONSTRAINT StorageImage_ADClientInfo FOREIGN KEY (StorageImage_ID) REFERENCES ad_storageprovider(ad_storageprovider_id) DEFERRABLE INITIALLY DEFERRED +; + +-- Aug 18, 2016 9:07:51 PM MYT +INSERT INTO AD_Field (SortNo,AD_Field_ID,IsEncrypted,DisplayLength,IsSameLine,IsHeading,SeqNo,IsCentrallyMaintained,IsReadOnly,AD_Org_ID,Updated,Description,Name,AD_Field_UU,IsDisplayed,IsFieldOnly,CreatedBy,UpdatedBy,IsActive,IsDisplayedGrid,SeqNoGrid,XPosition,IsQuickEntry,AD_Client_ID,Created,ColumnSpan,NumLines,IsAdvancedField,IsDefaultFocus,AD_Column_ID,EntityType,AD_Tab_ID) VALUES (0,204272,'N',0,'N','N',1020,'Y','N',0,TO_TIMESTAMP('2016-08-18 21:07:45','YYYY-MM-DD HH24:MI:SS'),'Storage provider for Image','Image Store','471cc623-0460-4b6a-8e24-74055858ce8a','Y','N',100,100,'Y','Y',1020,1,'N',0,TO_TIMESTAMP('2016-08-18 21:07:45','YYYY-MM-DD HH24:MI:SS'),2,1,'N','N',212831,'D',169) +; + +-- Aug 18, 2016 9:08:51 PM MYT +UPDATE AD_Field SET SeqNo=120, IsDisplayed='Y', XPosition=1,Updated=TO_TIMESTAMP('2016-08-18 21:08:51','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=204272 +; + +-- Aug 18, 2016 9:08:51 PM MYT +UPDATE AD_Field SET SeqNo=125,Updated=TO_TIMESTAMP('2016-08-18 21:08:51','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=202532 +; + +-- Aug 18, 2016 9:08:51 PM MYT +UPDATE AD_Field SET SeqNo=130,Updated=TO_TIMESTAMP('2016-08-18 21:08:51','YYYY-MM-DD HH24:MI:SS'),UpdatedBy=100 WHERE AD_Field_ID=202533 +; + +SELECT register_migration_script('201608191500_Ticket_1006528.sql') FROM dual +; + diff --git a/org.adempiere.base/OSGI-INF/imagedb.xml b/org.adempiere.base/OSGI-INF/imagedb.xml new file mode 100644 index 0000000000..19499d68c7 --- /dev/null +++ b/org.adempiere.base/OSGI-INF/imagedb.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.adempiere.base/OSGI-INF/imagefile.xml b/org.adempiere.base/OSGI-INF/imagefile.xml new file mode 100644 index 0000000000..b4ebf69169 --- /dev/null +++ b/org.adempiere.base/OSGI-INF/imagefile.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/org.adempiere.base/src/org/compiere/model/ArchiveDB.java b/org.adempiere.base/src/org/compiere/model/ArchiveDB.java index 9db0d3d30a..29dcb97c4d 100644 --- a/org.adempiere.base/src/org/compiere/model/ArchiveDB.java +++ b/org.adempiere.base/src/org/compiere/model/ArchiveDB.java @@ -113,4 +113,13 @@ public class ArchiveDB implements IArchiveStore { return true; } + @Override + public boolean isPendingFlush() { + return false; + } + + @Override + public void flush(MArchive archive, MStorageProvider prov) { + } + } diff --git a/org.adempiere.base/src/org/compiere/model/ArchiveFileSystem.java b/org.adempiere.base/src/org/compiere/model/ArchiveFileSystem.java index ca2e3f978b..e491c013be 100644 --- a/org.adempiere.base/src/org/compiere/model/ArchiveFileSystem.java +++ b/org.adempiere.base/src/org/compiere/model/ArchiveFileSystem.java @@ -53,6 +53,8 @@ public class ArchiveFileSystem implements IArchiveStore { private static final CLogger log = CLogger.getCLogger(ArchiveFileSystem.class); + //temporary buffer when AD_Archive_ID=0; + private byte[] buffer; /* (non-Javadoc) * @see org.compiere.model.IArchiveStore#loadLOBData(org.compiere.model.MArchive, org.compiere.model.MStorageProvider) @@ -63,6 +65,7 @@ public class ArchiveFileSystem implements IArchiveStore { if ("".equals(archivePathRoot)) { throw new IllegalArgumentException("no attachmentPath defined"); } + buffer = null; byte[] data = archive.getByteData(); if (data == null) { return null; @@ -144,24 +147,29 @@ public class ArchiveFileSystem implements IArchiveStore { * @see org.compiere.model.IArchiveStore#save(org.compiere.model.MArchive, org.compiere.model.MStorageProvider) */ @Override - public void save(MArchive archive, MStorageProvider prov,byte[] inflatedData) { - String archivePathRoot = getArchivePathRoot(prov); - if ("".equals(archivePathRoot)) { - throw new IllegalArgumentException("no attachmentPath defined"); - } + public void save(MArchive archive, MStorageProvider prov,byte[] inflatedData) { if (inflatedData == null || inflatedData.length == 0) { throw new IllegalArgumentException("InflatedData is NULL"); } if(archive.get_ID()==0){ //set binary data otherwise save will fail archive.setByteData(new byte[]{'0'}); - if(!archive.save()) { - throw new IllegalArgumentException("unable to save MArchive"); - } + buffer = inflatedData; + } else { + write(archive, prov, inflatedData); } - final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + } + + private void write(MArchive archive, MStorageProvider prov, + byte[] inflatedData) { BufferedOutputStream out = null; try { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + String archivePathRoot = getArchivePathRoot(prov); + if ("".equals(archivePathRoot)) { + throw new IllegalArgumentException("no attachmentPath defined"); + } // create destination folder StringBuilder msgfile = new StringBuilder().append(archivePathRoot) .append(archive.getArchivePathSnippet()); @@ -203,6 +211,7 @@ public class ArchiveFileSystem implements IArchiveStore { } catch (Exception e) { log.log(Level.SEVERE, "saveLOBData", e); archive.setByteData(null); + throw new RuntimeException(e); } finally { if(out != null){ try { @@ -210,7 +219,6 @@ public class ArchiveFileSystem implements IArchiveStore { } catch (Exception e) { } } } - } private String getArchivePathRoot(MStorageProvider prov) { @@ -245,4 +253,17 @@ public class ArchiveFileSystem implements IArchiveStore { return true; } + @Override + public boolean isPendingFlush() { + return buffer != null && buffer.length > 0; + } + + @Override + public void flush(MArchive archive, MStorageProvider prov) { + if (buffer != null && buffer.length > 0) { + write(archive, prov, buffer); + buffer = null; + } + } + } diff --git a/org.adempiere.base/src/org/compiere/model/IArchiveStore.java b/org.adempiere.base/src/org/compiere/model/IArchiveStore.java index 7a6a598be2..f766168694 100644 --- a/org.adempiere.base/src/org/compiere/model/IArchiveStore.java +++ b/org.adempiere.base/src/org/compiere/model/IArchiveStore.java @@ -21,4 +21,7 @@ public interface IArchiveStore { public boolean deleteArchive(MArchive archive, MStorageProvider prov); + public boolean isPendingFlush(); + + public void flush(MArchive archive,MStorageProvider prov); } diff --git a/org.adempiere.base/src/org/compiere/model/IImageStore.java b/org.adempiere.base/src/org/compiere/model/IImageStore.java new file mode 100644 index 0000000000..bed0e409af --- /dev/null +++ b/org.adempiere.base/src/org/compiere/model/IImageStore.java @@ -0,0 +1,27 @@ +/****************************************************************************** + * Product: iDempiere ERP & CRM Smart Business Solution * + * Copyright (C) 2012 Trek Global * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.compiere.model; + +public interface IImageStore { + + public byte[] load(MImage image, MStorageProvider prov); + + public void save(MImage image, MStorageProvider prov, byte[] inflatedData); + + public boolean delete(MImage image, MStorageProvider prov); + + public boolean isPendingFlush(); + + public void flush(MImage image,MStorageProvider prov); +} diff --git a/org.adempiere.base/src/org/compiere/model/I_AD_ClientInfo.java b/org.adempiere.base/src/org/compiere/model/I_AD_ClientInfo.java index 716e523afe..a9a7e5a4ea 100644 --- a/org.adempiere.base/src/org/compiere/model/I_AD_ClientInfo.java +++ b/org.adempiere.base/src/org/compiere/model/I_AD_ClientInfo.java @@ -449,6 +449,21 @@ public interface I_AD_ClientInfo public org.compiere.model.I_AD_StorageProvider getStorageArchive() throws RuntimeException; + /** Column name StorageImage_ID */ + public static final String COLUMNNAME_StorageImage_ID = "StorageImage_ID"; + + /** Set Image Store. + * Storage provider for Image + */ + public void setStorageImage_ID (int StorageImage_ID); + + /** Get Image Store. + * Storage provider for Image + */ + public int getStorageImage_ID(); + + public org.compiere.model.I_AD_StorageProvider getStorageImage() throws RuntimeException; + /** Column name Updated */ public static final String COLUMNNAME_Updated = "Updated"; diff --git a/org.adempiere.base/src/org/compiere/model/ImageDBStorageImpl.java b/org.adempiere.base/src/org/compiere/model/ImageDBStorageImpl.java new file mode 100644 index 0000000000..67e6fd3433 --- /dev/null +++ b/org.adempiere.base/src/org/compiere/model/ImageDBStorageImpl.java @@ -0,0 +1,125 @@ +/****************************************************************************** + * Product: iDempiere ERP & CRM Smart Business Solution * + * Copyright (C) 2012 Trek Global * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ +package org.compiere.model; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.logging.Level; +import java.util.zip.Deflater; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import org.compiere.util.CLogger; + +/** + * @author hengsin + * + */ +public class ImageDBStorageImpl implements IImageStore { + + + private final CLogger log = CLogger.getCLogger(getClass()); + + @Override + public byte[] load(MImage image, MStorageProvider prov) { + byte[] deflatedData = image.getByteData(); + if (deflatedData == null) + return null; + // + if (log.isLoggable(Level.FINE)) log.fine("ZipSize=" + deflatedData.length); + if (deflatedData.length == 0) + return null; + + byte[] inflatedData = null; + try { + ByteArrayInputStream in = new ByteArrayInputStream(deflatedData); + ZipInputStream zip = new ZipInputStream(in); + ZipEntry entry = zip.getNextEntry(); + if (entry != null) // just one entry + { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[2048]; + int length = zip.read(buffer); + while (length != -1) { + out.write(buffer, 0, length); + length = zip.read(buffer); + } + // + inflatedData = out.toByteArray(); + if (log.isLoggable(Level.FINE)) log.fine("Size=" + inflatedData.length + " - zip=" + entry.getCompressedSize() + + "(" + entry.getSize() + ") " + + (entry.getCompressedSize() * 100 / entry.getSize()) + "%"); + } else { + //not zip stream, legacy data + inflatedData = deflatedData; + } + } catch (Exception e) { + log.log(Level.SEVERE, "", e); + inflatedData = null; + } + return inflatedData; + } + + @Override + public void save(MImage image, MStorageProvider prov,byte[] inflatedData) { + if (inflatedData == null || inflatedData.length == 0) { + image.setByteData(null); + return; + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(out); + zip.setMethod(ZipOutputStream.DEFLATED); + zip.setLevel(Deflater.BEST_COMPRESSION); + zip.setComment("idempiere"); + // + byte[] deflatedData = null; + try { + ZipEntry entry = new ZipEntry("IdempiereImage"); + entry.setTime(System.currentTimeMillis()); + entry.setMethod(ZipEntry.DEFLATED); + zip.putNextEntry(entry); + zip.write(inflatedData, 0, inflatedData.length); + zip.closeEntry(); + if (log.isLoggable(Level.FINE)) log.fine(entry.getCompressedSize() + " (" + entry.getSize() + ") " + + (entry.getCompressedSize() * 100 / entry.getSize()) + "%"); + // + // zip.finish(); + zip.close(); + deflatedData = out.toByteArray(); + if (log.isLoggable(Level.FINE)) log.fine("Length=" + inflatedData.length); + } catch (Exception e) { + log.log(Level.SEVERE, "saveLOBData", e); + deflatedData = null; + } + image.setByteData(deflatedData); + } + + @Override + public boolean delete(MImage image, MStorageProvider prov) { + + return true; + } + + @Override + public boolean isPendingFlush() { + return false; + } + + @Override + public void flush(MImage image, MStorageProvider prov) { + } + +} diff --git a/org.adempiere.base/src/org/compiere/model/ImageFileStorageImpl.java b/org.adempiere.base/src/org/compiere/model/ImageFileStorageImpl.java new file mode 100644 index 0000000000..371018aa0c --- /dev/null +++ b/org.adempiere.base/src/org/compiere/model/ImageFileStorageImpl.java @@ -0,0 +1,278 @@ +/****************************************************************************** + * Product: iDempiere ERP & CRM Smart Business Solution * + * Copyright (C) 2012 Trek Global * + * This program is free software; you can redistribute it and/or modify it * + * under the terms version 2 of the GNU General Public License as published * + * by the Free Software Foundation. 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, write to the Free Software Foundation, Inc., * + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. * + *****************************************************************************/ + +package org.compiere.model; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.logging.Level; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.Result; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; + +import org.compiere.util.CLogger; +import org.compiere.util.Util; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; +import org.w3c.dom.Element; + +/** + * @author hengsin + * + */ +public class ImageFileStorageImpl implements IImageStore { + + private String IMAGE_FOLDER_PLACEHOLDER = "%IMAGE_FOLDER%"; + + private final CLogger log = CLogger.getCLogger(getClass()); + + //temporary buffer when AD_Image_ID=0 + private byte[] buffer = null; + + @Override + public byte[] load(MImage image, MStorageProvider prov) { + String imagePathRoot = getImagePathRoot(prov); + if ("".equals(imagePathRoot)) { + throw new IllegalArgumentException("no path defined"); + } + buffer = null; + byte[] data = image.getByteData(); + if (data == null) { + return null; + } + + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + try { + final DocumentBuilder builder = factory.newDocumentBuilder(); + final Document document = builder.parse(new ByteArrayInputStream(data)); + final NodeList entries = document.getElementsByTagName("entry"); + if(entries.getLength()!=1){ + log.severe("no image entry found"); + } + final Node entryNode = entries.item(0); + final NamedNodeMap attributes = entryNode.getAttributes(); + final Node fileNode = attributes.getNamedItem("file"); + if(fileNode==null ){ + log.severe("no filename for entry"); + return null; + } + String filePath = fileNode.getNodeValue(); + if (log.isLoggable(Level.FINE)) log.fine("filePath: " + filePath); + if(filePath!=null){ + filePath = filePath.replaceFirst(IMAGE_FOLDER_PLACEHOLDER, imagePathRoot.replaceAll("\\\\","\\\\\\\\")); + //just to be shure... + String replaceSeparator = File.separator; + if(!replaceSeparator.equals("/")){ + replaceSeparator = "\\\\"; + } + filePath = filePath.replaceAll("/", replaceSeparator); + filePath = filePath.replaceAll("\\\\", replaceSeparator); + } + if (log.isLoggable(Level.FINE)) 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(); + } + return dataEntry; + } else { + log.severe("file not found: " + file.getAbsolutePath()); + return null; + } + + } 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 null; + } + + @Override + public void save(MImage image, MStorageProvider prov,byte[] inflatedData) { + if (inflatedData == null || inflatedData.length == 0) { + image.setByteData(null); + delete(image, prov); + return; + } + + if(image.get_ID()==0){ + //set binary data otherwise save will fail + image.setByteData(new byte[]{'0'}); + buffer = inflatedData; + } else { + write(image, prov, inflatedData); + } + + } + + private void write(MImage image, MStorageProvider prov, byte[] inflatedData) { + BufferedOutputStream out = null; + try { + final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + String imagePathRoot = getImagePathRoot(prov); + if ("".equals(imagePathRoot)) { + throw new IllegalArgumentException("no storage path defined"); + } + // create destination folder + StringBuilder msgfile = new StringBuilder().append(imagePathRoot) + .append(image.getImageStoragePath()); + final File destFolder = new File(msgfile.toString()); + if (!destFolder.exists()) { + if (!destFolder.mkdirs()) { + log.warning("unable to create folder: " + destFolder.getPath()); + } + } + + String ext = getExtension(image); + // write to path + msgfile = new StringBuilder().append(imagePathRoot).append(File.separator) + .append(image.getImageStoragePath()).append(image.get_ID()).append(ext); + final File destFile = new File(msgfile.toString()); + + out = new BufferedOutputStream(new FileOutputStream(destFile)); + out.write(inflatedData); + out.flush(); + + //create xml entry + final DocumentBuilder builder = factory.newDocumentBuilder(); + final Document document = builder.newDocument(); + final Element root = document.createElement("image"); + document.appendChild(root); + document.setXmlStandalone(true); + final Element entry = document.createElement("entry"); + StringBuilder msgsat = new StringBuilder(IMAGE_FOLDER_PLACEHOLDER).append(image.getImageStoragePath()).append(image.get_ID()).append(ext); + entry.setAttribute("file", msgsat.toString()); + 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(); + if (log.isLoggable(Level.FINE)) log.fine(bos.toString()); + //store xml in db + image.setByteData(xmlData); + + } catch (Exception e) { + log.log(Level.SEVERE, "saveLOBData", e); + image.setByteData(null); + throw new RuntimeException(e); + } finally { + if(out != null){ + try { + out.close(); + } catch (Exception e) { } + } + } + } + + private String getImagePathRoot(MStorageProvider prov) { + String imagePathRoot = prov.getFolder(); + if (imagePathRoot == null) + imagePathRoot = ""; + if (Util.isEmpty(imagePathRoot)) { + log.severe("no image Path defined"); + } else if (!imagePathRoot.endsWith(File.separator)){ + imagePathRoot = imagePathRoot + File.separator; + log.fine(imagePathRoot); + } + return imagePathRoot; + } + + @Override + public boolean delete(MImage image, MStorageProvider prov) { + String imagePathRoot = getImagePathRoot(prov); + if ("".equals(imagePathRoot)) { + throw new IllegalArgumentException("no image path defined"); + } + String ext = getExtension(image); + StringBuilder msgfile = new StringBuilder().append(imagePathRoot) + .append(image.getImageStoragePath()).append(image.get_ID()).append(ext); + + File file=new File(msgfile.toString()); + if (file !=null && file.exists()) { + if (!file.delete()) { + log.warning("unable to delete " + file.getAbsolutePath()); + return false; + } + } + return true; + } + + private String getExtension(MImage image) { + String name = image.getName(); + String ext = ""; + if (name.lastIndexOf(".") > 0) { + ext = name.substring(name.lastIndexOf(".")); + } + return ext; + } + + @Override + public boolean isPendingFlush() { + return buffer != null && buffer.length > 0; + } + + @Override + public void flush(MImage image, MStorageProvider prov) { + if (buffer != null && buffer.length > 0) { + write(image, prov, buffer); + buffer = null; + } + } + +} diff --git a/org.adempiere.base/src/org/compiere/model/MArchive.java b/org.adempiere.base/src/org/compiere/model/MArchive.java index 3d9207517c..1b89af5d49 100644 --- a/org.adempiere.base/src/org/compiere/model/MArchive.java +++ b/org.adempiere.base/src/org/compiere/model/MArchive.java @@ -278,4 +278,11 @@ public class MArchive extends X_AD_Archive { } + @Override + protected void saveNew_afterSetID() + { + IArchiveStore prov = provider.getArchiveStore(); + if (prov != null && prov.isPendingFlush()) + prov.flush(this,provider); + } } // MArchive diff --git a/org.adempiere.base/src/org/compiere/model/MImage.java b/org.adempiere.base/src/org/compiere/model/MImage.java index b0f965684a..fda6e4c9bc 100644 --- a/org.adempiere.base/src/org/compiere/model/MImage.java +++ b/org.adempiere.base/src/org/compiere/model/MImage.java @@ -21,6 +21,7 @@ import java.awt.Image; import java.awt.MediaTracker; import java.awt.Toolkit; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; @@ -49,6 +50,8 @@ public class MImage extends X_AD_Image */ private static final long serialVersionUID = -7361463683427300715L; + private MStorageProvider provider; + /** * Get MImage from Cache * @param ctx context @@ -84,6 +87,7 @@ public class MImage extends X_AD_Image super (ctx, AD_Image_ID, trxName); if (AD_Image_ID < 1) setName("-"); + initImageStoreDetails(ctx, trxName); } // MImage /** @@ -95,6 +99,7 @@ public class MImage extends X_AD_Image public MImage (Properties ctx, ResultSet rs, String trxName) { super(ctx, rs, trxName); + initImageStoreDetails(ctx, trxName); } // MImage @@ -233,20 +238,31 @@ public class MImage extends X_AD_Image * Set Binary Data * @param BinaryData data */ + @Override public void setBinaryData (byte[] BinaryData) { m_image = null; m_icon = null; - super.setBinaryData (BinaryData); + IImageStore prov = provider.getImageStore(); + if (prov != null) + prov.save(this,provider,BinaryData); } // setBinaryData + @Override + public byte[] getBinaryData() { + IImageStore prov = provider.getImageStore(); + if (prov != null) + return prov.load(this,provider); + return null; + } + /** * Get Data * @return data */ public byte[] getData() { - byte[] data = super.getBinaryData (); + byte[] data = getBinaryData (); if (data != null) return data; // From URL @@ -311,5 +327,50 @@ public class MImage extends X_AD_Image setAD_Org_ID(0); return true; } // beforeSave + + public String getImageStoragePath() { + StringBuilder path = new StringBuilder().append(this.getAD_Client_ID()).append(File.separator).append(this.getAD_Org_ID()) + .append(File.separator); + return path.toString(); + } + /** + * @param ctx + * @param trxName + */ + private void initImageStoreDetails(Properties ctx, String trxName) { + MClientInfo clientInfo = MClientInfo.get(ctx); + provider=new MStorageProvider(ctx, clientInfo.getStorageImage_ID(), trxName); + } + + public void setStorageProvider(MStorageProvider p) { + provider = p; + } + + public byte[] getByteData(){ + return super.getBinaryData(); + } + + public void setByteData(byte[] BinaryData){ + super.setBinaryData(BinaryData); + } + + @Override + protected boolean afterDelete (boolean success) { + if (success) { + IImageStore prov = provider.getImageStore(); + if (prov != null) + return prov.delete(this,provider); + } + return success; + + } + + @Override + protected void saveNew_afterSetID() + { + IImageStore prov = provider.getImageStore(); + if (prov != null && prov.isPendingFlush()) + prov.flush(this, provider); + } } // MImage diff --git a/org.adempiere.base/src/org/compiere/model/MStorageProvider.java b/org.adempiere.base/src/org/compiere/model/MStorageProvider.java index c3fa25aad5..af50b9b0f3 100644 --- a/org.adempiere.base/src/org/compiere/model/MStorageProvider.java +++ b/org.adempiere.base/src/org/compiere/model/MStorageProvider.java @@ -63,4 +63,16 @@ public class MStorageProvider extends X_AD_StorageProvider { return store; } + public IImageStore getImageStore() { + ServiceQuery query=new ServiceQuery(); + String method = this.getMethod(); + if (method == null) + method = "DB"; + query.put("method", method); + IImageStore store = Service.locator().locate(IImageStore.class, query).getService(); + if (store == null) { + throw new AdempiereException("No image storage provider found"); + } + return store; + } } diff --git a/org.adempiere.base/src/org/compiere/model/PO.java b/org.adempiere.base/src/org/compiere/model/PO.java index 885c3636fd..a2b1f8c61a 100644 --- a/org.adempiere.base/src/org/compiere/model/PO.java +++ b/org.adempiere.base/src/org/compiere/model/PO.java @@ -2782,6 +2782,7 @@ public abstract class PO } m_IDs[0] = Integer.valueOf(no); set_ValueNoCheck(m_KeyColumns[0], m_IDs[0]); + saveNew_afterSetID(); } //uuid secondary key int uuidIndex = p_info.getColumnIndex(getUUIDColumnName()); @@ -3096,6 +3097,13 @@ public abstract class PO return 0; } // saveNew_getID + /** + * Call after ID have been assigned for new record + */ + protected void saveNew_afterSetID() + { + + } /** * Create Single/Multi Key Where Clause diff --git a/org.adempiere.base/src/org/compiere/model/PO_Record.java b/org.adempiere.base/src/org/compiere/model/PO_Record.java index c1e58926f9..1c8ca9db95 100644 --- a/org.adempiere.base/src/org/compiere/model/PO_Record.java +++ b/org.adempiere.base/src/org/compiere/model/PO_Record.java @@ -109,15 +109,27 @@ public class PO_Record if (s_cascades[i] != AD_Table_ID) { Object[] params = new Object[]{Integer.valueOf(AD_Table_ID), Integer.valueOf(Record_ID)}; - StringBuffer sql = new StringBuffer ("DELETE FROM ") - .append(s_cascadeNames[i]) - .append(" WHERE AD_Table_ID=? AND Record_ID=?"); - int no = DB.executeUpdate(sql.toString(), params, false, trxName); - if (no > 0) { - if (log.isLoggable(Level.CONFIG)) log.config(s_cascadeNames[i] + " (" + AD_Table_ID + "/" + Record_ID + ") #" + no); - } else if (no < 0) { - log.severe(s_cascadeNames[i] + " (" + AD_Table_ID + "/" + Record_ID + ") #" + no); - return false; + if (s_cascadeNames[i].equals(X_AD_Attachment.Table_Name) || s_cascadeNames[i].equals(X_AD_Archive.Table_Name)) + { + Query query = new Query(Env.getCtx(), s_cascadeNames[i], "AD_Table_ID=? AND Record_ID=?", trxName); + List list = query.setParameters(params).list(); + for(PO po : list) + { + po.deleteEx(true); + } + } + else + { + StringBuffer sql = new StringBuffer ("DELETE FROM ") + .append(s_cascadeNames[i]) + .append(" WHERE AD_Table_ID=? AND Record_ID=?"); + int no = DB.executeUpdate(sql.toString(), params, false, trxName); + if (no > 0) { + if (log.isLoggable(Level.CONFIG)) log.config(s_cascadeNames[i] + " (" + AD_Table_ID + "/" + Record_ID + ") #" + no); + } else if (no < 0) { + log.severe(s_cascadeNames[i] + " (" + AD_Table_ID + "/" + Record_ID + ") #" + no); + return false; + } } } } diff --git a/org.adempiere.base/src/org/compiere/model/X_AD_ClientInfo.java b/org.adempiere.base/src/org/compiere/model/X_AD_ClientInfo.java index 2d08349c1d..3f67179894 100644 --- a/org.adempiere.base/src/org/compiere/model/X_AD_ClientInfo.java +++ b/org.adempiere.base/src/org/compiere/model/X_AD_ClientInfo.java @@ -30,7 +30,7 @@ public class X_AD_ClientInfo extends PO implements I_AD_ClientInfo, I_Persistent /** * */ - private static final long serialVersionUID = 20191121L; + private static final long serialVersionUID = 20200226L; /** Standard Constructor */ public X_AD_ClientInfo (Properties ctx, int AD_ClientInfo_ID, String trxName) @@ -767,4 +767,32 @@ public class X_AD_ClientInfo extends PO implements I_AD_ClientInfo, I_Persistent return 0; return ii.intValue(); } + + public org.compiere.model.I_AD_StorageProvider getStorageImage() throws RuntimeException + { + return (org.compiere.model.I_AD_StorageProvider)MTable.get(getCtx(), org.compiere.model.I_AD_StorageProvider.Table_Name) + .getPO(getStorageImage_ID(), get_TrxName()); } + + /** Set Image Store. + @param StorageImage_ID + Storage provider for Image + */ + public void setStorageImage_ID (int StorageImage_ID) + { + if (StorageImage_ID < 1) + set_Value (COLUMNNAME_StorageImage_ID, null); + else + set_Value (COLUMNNAME_StorageImage_ID, Integer.valueOf(StorageImage_ID)); + } + + /** Get Image Store. + @return Storage provider for Image + */ + public int getStorageImage_ID () + { + Integer ii = (Integer)get_Value(COLUMNNAME_StorageImage_ID); + if (ii == null) + return 0; + return ii.intValue(); + } } \ No newline at end of file