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