From 0871b81c87c151095a8eeeca35f4315d419476d5 Mon Sep 17 00:00:00 2001 From: hengsin Date: Sun, 23 Feb 2020 20:59:15 +0800 Subject: [PATCH 1/8] IDEMPIERE-4154 ServerPush Adjustments. AtmosphereResource: use recommended suspend method for infinite suspend. ZkAtmosphereHandler: add server log for errors. serverpush.js: use POST to avoid cache issue, include session parameter to be consistent with other zkau request. --- .../jawwa/zk/atmosphere/AtmosphereServerPush.java | 2 +- .../jawwa/zk/atmosphere/ZkAtmosphereHandler.java | 12 ++++++++++-- .../src/web/js/jawwa/atmosphere/serverpush.js | 3 ++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/AtmosphereServerPush.java b/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/AtmosphereServerPush.java index ff7d397c87..8fc426418a 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/AtmosphereServerPush.java +++ b/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/AtmosphereServerPush.java @@ -268,7 +268,7 @@ public class AtmosphereServerPush implements ServerPush { } if (!resource.isSuspended()) { - resource.suspend(-1); + resource.suspend(); } this.resource.set(resource); diff --git a/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/ZkAtmosphereHandler.java b/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/ZkAtmosphereHandler.java index 0d5aedbdb6..948025f23a 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/ZkAtmosphereHandler.java +++ b/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/ZkAtmosphereHandler.java @@ -29,6 +29,8 @@ import org.atmosphere.cpr.AtmosphereRequest; import org.atmosphere.cpr.AtmosphereResource; import org.atmosphere.cpr.AtmosphereResourceEvent; import org.atmosphere.cpr.AtmosphereResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.zkoss.zk.ui.Desktop; import org.zkoss.zk.ui.Session; import org.zkoss.zk.ui.http.WebManager; @@ -41,6 +43,8 @@ import org.zkoss.zk.ui.sys.WebAppCtrl; */ public class ZkAtmosphereHandler implements AtmosphereHandler { + private final Logger log = LoggerFactory.getLogger(this.getClass()); + @Override public void destroy() { } @@ -49,6 +53,7 @@ public class ZkAtmosphereHandler implements AtmosphereHandler { if (session.getWebApp() instanceof WebAppCtrl) { WebAppCtrl webAppCtrl = (WebAppCtrl) session.getWebApp(); Desktop desktop = webAppCtrl.getDesktopCache(session).getDesktopIfAny(dtid); + log.warn("Could not find desktop: " + dtid); return new Either("Could not find desktop", desktop); } return new Either("Webapp does not implement WebAppCtrl", null); @@ -101,10 +106,12 @@ public class ZkAtmosphereHandler implements AtmosphereHandler { private Either getSession(AtmosphereResource resource, HttpServletRequest request) { Session session = WebManager.getSession(resource.getAtmosphereConfig().getServletContext(), request, false); - if (session == null) + if (session == null) { + log.warn("Could not find session: " + request.getRequestURI()); return new Either("Could not find session", null); - else + } else { return new Either(null, session); + } } @Override @@ -116,6 +123,7 @@ public class ZkAtmosphereHandler implements AtmosphereHandler { Either serverPushEither = getServerPush(resource); String error = serverPushEither.getLeftValue(); if (error != null && serverPushEither.getRightValue() == null) { + log.warn("Bad Request. Error="+error+", Request="+resource.getRequest().getRequestURI()); response.setStatus(HttpServletResponse.SC_BAD_REQUEST); response.getWriter().write(error); response.getWriter().flush(); diff --git a/org.adempiere.ui.zk/WEB-INF/src/web/js/jawwa/atmosphere/serverpush.js b/org.adempiere.ui.zk/WEB-INF/src/web/js/jawwa/atmosphere/serverpush.js index 45f9fd78aa..eabe053fe0 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/web/js/jawwa/atmosphere/serverpush.js +++ b/org.adempiere.ui.zk/WEB-INF/src/web/js/jawwa/atmosphere/serverpush.js @@ -23,7 +23,7 @@ trace: false, ajaxOptions: { url: zk.ajaxURI("/comet", {au: true}), - type: "GET", + type: "POST", cache: false, async: true, global: false, @@ -35,6 +35,7 @@ this.timeout = timeout; this.ajaxOptions.data = { dtid: this.desktop.id }; this.ajaxOptions.timeout = this.timeout; + this.ajaxOptions.url = zk.ajaxURI("/comet", {au: true,desktop:this.desktop.id,ignoreSession:false}), this.trace = trace; var me = this; this.ajaxOptions.error = function(jqxhr, textStatus, errorThrown) { From 04001e1055de4b7358efbe78957cfdb6cb131791 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Mon, 24 Feb 2020 14:14:34 +0100 Subject: [PATCH 2/8] =?UTF-8?q?IDEMPIERE-4189=20Fix=20potential=20NPE=20in?= =?UTF-8?q?=20generate=20shipment=20-=20based=20on=20patch=20from=20Martin?= =?UTF-8?q?=20Sch=C3=B6nbeck?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/org/compiere/process/InOutGenerate.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java b/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java index c3dba63b85..6b0bc97153 100644 --- a/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java +++ b/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java @@ -257,14 +257,11 @@ public class InOutGenerate extends SvrProcess BigDecimal onHand = Env.ZERO; BigDecimal toDeliver = line.getQtyOrdered() .subtract(line.getQtyDelivered()); - MProduct product = line.getProduct(); // Nothing to Deliver - if (product != null && toDeliver.signum() == 0) - continue; - - // or it's a charge - Bug#: 1603966 - if (line.getC_Charge_ID()!=0 && toDeliver.signum() == 0) + if (toDeliver.signum() == 0) continue; + + MProduct product = line.getProduct(); // Check / adjust for confirmations BigDecimal unconfirmedShippedQty = Env.ZERO; @@ -298,6 +295,11 @@ public class InOutGenerate extends SvrProcess createLine (order, line, toDeliver, null, false); continue; } + if (product == null) + { + // code must never arrive here - but for safety against NPE + continue; + } // Stored Product String MMPolicy = product.getMMPolicy(); From 5952da8f9721676fcd04deea904375f87d209d53 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Mon, 24 Feb 2020 19:21:34 +0100 Subject: [PATCH 3/8] =?UTF-8?q?Revert=20"IDEMPIERE-4189=20Fix=20potential?= =?UTF-8?q?=20NPE=20in=20generate=20shipment=20-=20based=20on=20patch=20fr?= =?UTF-8?q?om=20Martin=20Sch=C3=B6nbeck"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 04001e1055de4b7358efbe78957cfdb6cb131791. --- .../src/org/compiere/process/InOutGenerate.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java b/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java index 6b0bc97153..c3dba63b85 100644 --- a/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java +++ b/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java @@ -257,11 +257,14 @@ public class InOutGenerate extends SvrProcess BigDecimal onHand = Env.ZERO; BigDecimal toDeliver = line.getQtyOrdered() .subtract(line.getQtyDelivered()); - // Nothing to Deliver - if (toDeliver.signum() == 0) - continue; - MProduct product = line.getProduct(); + // Nothing to Deliver + if (product != null && toDeliver.signum() == 0) + continue; + + // or it's a charge - Bug#: 1603966 + if (line.getC_Charge_ID()!=0 && toDeliver.signum() == 0) + continue; // Check / adjust for confirmations BigDecimal unconfirmedShippedQty = Env.ZERO; @@ -295,11 +298,6 @@ public class InOutGenerate extends SvrProcess createLine (order, line, toDeliver, null, false); continue; } - if (product == null) - { - // code must never arrive here - but for safety against NPE - continue; - } // Stored Product String MMPolicy = product.getMMPolicy(); From 71138cdef962a7d0fbc9386fa1a74ff29c54bf77 Mon Sep 17 00:00:00 2001 From: mschnbeck Date: Thu, 23 Jan 2020 12:13:22 +0100 Subject: [PATCH 4/8] IDEMPIERE-4189 skip empty product anyway to avoid null pointer exceptions; --- .../src/org/compiere/process/InOutGenerate.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java b/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java index c3dba63b85..557f06c911 100644 --- a/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java +++ b/org.adempiere.base.process/src/org/compiere/process/InOutGenerate.java @@ -298,6 +298,10 @@ public class InOutGenerate extends SvrProcess createLine (order, line, toDeliver, null, false); continue; } + if (product == null) + { + continue; + } // Stored Product String MMPolicy = product.getMMPolicy(); From 174ff192a46531591a6c1d4b041a2c2f3c3cf00a Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Tue, 25 Feb 2020 17:51:19 +0100 Subject: [PATCH 5/8] IDEMPIERE-3545 Add plugin information in About window / fix index out of bounds issue - and avoid starting a fragment --- org.adempiere.ui.zk/META-INF/MANIFEST.MF | 1 + .../adempiere/webui/window/AboutWindow.java | 33 ++++++++++++++++--- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/org.adempiere.ui.zk/META-INF/MANIFEST.MF b/org.adempiere.ui.zk/META-INF/MANIFEST.MF index 753d50b479..3573bd2a46 100644 --- a/org.adempiere.ui.zk/META-INF/MANIFEST.MF +++ b/org.adempiere.ui.zk/META-INF/MANIFEST.MF @@ -48,6 +48,7 @@ Import-Package: groovy.transform.stc;version="2.4.7", org.jfree.data.time, org.jfree.util, org.osgi.framework;version="1.7.0", + org.osgi.framework.wiring;version="1.2.0", org.osgi.service.component.annotations;version="1.3.0", org.osgi.service.event;version="1.3.0", org.osgi.util.tracker;version="1.5.0", diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/AboutWindow.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/AboutWindow.java index 93ad881d7b..86e876bf91 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/AboutWindow.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/window/AboutWindow.java @@ -65,6 +65,7 @@ import org.compiere.util.Util; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleException; +import org.osgi.framework.wiring.BundleRevision; import org.zkoss.util.media.AMedia; import org.zkoss.zhtml.Pre; import org.zkoss.zhtml.Text; @@ -445,16 +446,37 @@ public class AboutWindow extends Window implements EventListener { if (bundle == null) return; int state = bundle.getState(); + boolean isFragment = false; + BundleRevision rev = bundle.adapt(BundleRevision.class); + if (rev != null) { + isFragment = (rev.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0; + } + /* + boolean hasFragments = false; + if (!isFragment) { + if (rev.getWiring() != null) { + if (rev.getWiring().getProvidedWires(BundleRevision.HOST_NAMESPACE).size() > 0) { + hasFragments = true; + } + } + } + */ if (bundle.getBundleId() == 0) { // bundle 0 cannot be stopped } else if (state == Bundle.ACTIVE) { pluginActions.getItemAtIndex(PLUGIN_ACTION_STOP).setVisible(true); } else if (state == Bundle.RESOLVED) { - pluginActions.getItemAtIndex(PLUGIN_ACTION_START).setVisible(true); + if (!isFragment) { + pluginActions.getItemAtIndex(PLUGIN_ACTION_START).setVisible(true); + } } else if (state == Bundle.INSTALLED) { - // no options yet for installed + if (!isFragment) { + pluginActions.getItemAtIndex(PLUGIN_ACTION_START).setVisible(true); + } } else if (state == Bundle.STARTING) { - // no options yet for starting + if (!isFragment) { + pluginActions.getItemAtIndex(PLUGIN_ACTION_START).setVisible(true); + } } else if (state == Bundle.STOPPING) { // no options yet for stopping } else if (state == Bundle.UNINSTALLED) { @@ -466,8 +488,7 @@ public class AboutWindow extends Window implements EventListener { Bundle retValue = null; int idx = pluginsTable.getSelectedIndex(); if (idx >= 0) { - Integer selectedPlugin = (Integer) pluginsTable.getModel().getDataAt(idx, 1); - Vector pluginVector = pluginData.get(selectedPlugin); + Vector pluginVector = pluginData.get(idx); int pluginId = ((IDColumn)pluginVector.get(0)).getRecord_ID(); BundleContext bundleCtx = WebUIActivator.getBundleContext(); retValue = bundleCtx.getBundle(pluginId); @@ -505,6 +526,7 @@ public class AboutWindow extends Window implements EventListener { } private void refreshPluginTable() { + int idx = pluginsTable.getSelectedIndex(); pluginsTable.getModel().removeAll(pluginData); pluginData.removeAllElements(); @@ -521,6 +543,7 @@ public class AboutWindow extends Window implements EventListener { } ListModelTable model = new ListModelTable(pluginData); pluginsTable.setData(model, pluginColumnNames); + pluginsTable.setSelectedIndex(idx); } protected Tabpanel createInfo() { From ea1f86837d180bfed4bf3bc95ab165be5094c4f8 Mon Sep 17 00:00:00 2001 From: hengsin Date: Wed, 26 Feb 2020 11:01:09 +0800 Subject: [PATCH 6/8] IDEMPIERE-4154 ServerPush Adjustments. Fix logging error. commitResponse should be ok as long as resource is available. --- .../src/fi/jawsy/jawwa/zk/atmosphere/AtmosphereServerPush.java | 2 +- .../src/fi/jawsy/jawwa/zk/atmosphere/ZkAtmosphereHandler.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/AtmosphereServerPush.java b/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/AtmosphereServerPush.java index 8fc426418a..4891a7fb85 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/AtmosphereServerPush.java +++ b/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/AtmosphereServerPush.java @@ -122,7 +122,7 @@ public class AtmosphereServerPush implements ServerPush { private boolean commitResponse() throws IOException { AtmosphereResource resource = this.resource.getAndSet(null); - if (resource != null && resource.isSuspended()) { + if (resource != null) { resource.resume(); return true; } diff --git a/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/ZkAtmosphereHandler.java b/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/ZkAtmosphereHandler.java index 948025f23a..4dd9f6a713 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/ZkAtmosphereHandler.java +++ b/org.adempiere.ui.zk/WEB-INF/src/fi/jawsy/jawwa/zk/atmosphere/ZkAtmosphereHandler.java @@ -53,7 +53,8 @@ public class ZkAtmosphereHandler implements AtmosphereHandler { if (session.getWebApp() instanceof WebAppCtrl) { WebAppCtrl webAppCtrl = (WebAppCtrl) session.getWebApp(); Desktop desktop = webAppCtrl.getDesktopCache(session).getDesktopIfAny(dtid); - log.warn("Could not find desktop: " + dtid); + if (desktop == null) + log.warn("Could not find desktop: " + dtid); return new Either("Could not find desktop", desktop); } return new Either("Webapp does not implement WebAppCtrl", null); From 55356515b5140412f0bbc85b28f25fcd4bcf5760 Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Thu, 27 Feb 2020 21:37:32 +0100 Subject: [PATCH 7/8] IDEMPIERE-4190 Implement Image Storage Provider (1006528) (FHCA-1165) integrate development from Heng Sin --- .../oracle/201608191500_Ticket_1006528.sql | 43 +++ .../201608191500_Ticket_1006528.sql | 40 +++ org.adempiere.base/OSGI-INF/imagedb.xml | 8 + org.adempiere.base/OSGI-INF/imagefile.xml | 8 + .../src/org/compiere/model/ArchiveDB.java | 9 + .../org/compiere/model/ArchiveFileSystem.java | 41 ++- .../src/org/compiere/model/IArchiveStore.java | 3 + .../src/org/compiere/model/IImageStore.java | 27 ++ .../org/compiere/model/I_AD_ClientInfo.java | 15 + .../compiere/model/ImageDBStorageImpl.java | 125 ++++++++ .../compiere/model/ImageFileStorageImpl.java | 278 ++++++++++++++++++ .../src/org/compiere/model/MArchive.java | 7 + .../src/org/compiere/model/MImage.java | 65 +++- .../org/compiere/model/MStorageProvider.java | 12 + .../src/org/compiere/model/PO.java | 8 + .../src/org/compiere/model/PO_Record.java | 30 +- .../org/compiere/model/X_AD_ClientInfo.java | 30 +- 17 files changed, 727 insertions(+), 22 deletions(-) create mode 100644 migration/i7.1/oracle/201608191500_Ticket_1006528.sql create mode 100644 migration/i7.1/postgresql/201608191500_Ticket_1006528.sql create mode 100644 org.adempiere.base/OSGI-INF/imagedb.xml create mode 100644 org.adempiere.base/OSGI-INF/imagefile.xml create mode 100644 org.adempiere.base/src/org/compiere/model/IImageStore.java create mode 100644 org.adempiere.base/src/org/compiere/model/ImageDBStorageImpl.java create mode 100644 org.adempiere.base/src/org/compiere/model/ImageFileStorageImpl.java 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 From c4cef3b8de7adf10040a4b2b18539cee7e5702bd Mon Sep 17 00:00:00 2001 From: Carlos Ruiz Date: Thu, 27 Feb 2020 22:17:17 +0100 Subject: [PATCH 8/8] IDEMPIERE-4190 Implement Image Storage Provider (1006528) (FHCA-1165) Peer review and tests: * PO -> implement postDelete method to be called after the database delete is committed - fix issue about file being deleted from filesystem and commit failing because of referential integrity (for example deleting an image that is associated to a user) * MArchive, MAttachment, MImage -> change deletion of file from before/afterDelete to postDelete * MImage -> change getImageStoragePath from clientID/orgID to AD_Image/clientID (this is to allow using the same storage that attachments and archives uses, and avoid issues when changing org) * MBPartner, MUser, MPOSKey -> delete associated image with postDelete * ArchiveFileSystem -> fix issue not deleting the file in the filesystem when deleting the archive (using get_ID() on a deleted record returns zero) * WImageEditor -> save automatically when creating a new image * AttachmentFileSystem -> avoid trying to save attachment after is deleted * ImageDBStorageImpl, MSysConfig -> Implement SysConfig IMAGE_DB_STORAGE_SAVE_AS_ZIP to save images in DB zipped - defaults to false which is the actual behavior * ImageFileStorageImpl -> do not save file with extension, to fix error where previous file was not deleted when uploading a new file with different extension --- .../org/compiere/model/ArchiveFileSystem.java | 2 +- .../compiere/model/AttachmentFileSystem.java | 3 +- .../compiere/model/ImageDBStorageImpl.java | 66 +++++++++++-------- .../compiere/model/ImageFileStorageImpl.java | 17 +---- .../src/org/compiere/model/MArchive.java | 5 +- .../src/org/compiere/model/MAttachment.java | 18 ++--- .../src/org/compiere/model/MBPartner.java | 14 +++- .../src/org/compiere/model/MImage.java | 21 +++--- .../src/org/compiere/model/MPOSKey.java | 15 ++++- .../src/org/compiere/model/MSysConfig.java | 5 +- .../src/org/compiere/model/MUser.java | 16 ++++- .../src/org/compiere/model/PO.java | 14 +++- .../adempiere/webui/editor/WImageEditor.java | 4 ++ 13 files changed, 120 insertions(+), 80 deletions(-) diff --git a/org.adempiere.base/src/org/compiere/model/ArchiveFileSystem.java b/org.adempiere.base/src/org/compiere/model/ArchiveFileSystem.java index e491c013be..94ed800075 100644 --- a/org.adempiere.base/src/org/compiere/model/ArchiveFileSystem.java +++ b/org.adempiere.base/src/org/compiere/model/ArchiveFileSystem.java @@ -241,7 +241,7 @@ public class ArchiveFileSystem implements IArchiveStore { throw new IllegalArgumentException("no attachmentPath defined"); } StringBuilder msgfile = new StringBuilder().append(archivePathRoot) - .append(archive.getArchivePathSnippet()).append(archive.get_ID()).append(".pdf"); + .append(archive.getArchivePathSnippet()).append(archive.getAD_Archive_ID()).append(".pdf"); File file=new File(msgfile.toString()); if (file !=null && file.exists()) { diff --git a/org.adempiere.base/src/org/compiere/model/AttachmentFileSystem.java b/org.adempiere.base/src/org/compiere/model/AttachmentFileSystem.java index 009c4a7df2..b50404260f 100644 --- a/org.adempiere.base/src/org/compiere/model/AttachmentFileSystem.java +++ b/org.adempiere.base/src/org/compiere/model/AttachmentFileSystem.java @@ -288,7 +288,8 @@ public class AttachmentFileSystem implements IAttachmentStore { } } attach.m_items.remove(index); - attach.saveEx(); // must save here as the operation cannot be rolled back on filesystem + if (attach.get_ID() > 0) // the attachment has not been deleted + attach.saveEx(); // must save here as the operation cannot be rolled back on filesystem if (log.isLoggable(Level.CONFIG)) log.config("Index=" + index + " - NewSize=" + attach.m_items.size()); return true; } diff --git a/org.adempiere.base/src/org/compiere/model/ImageDBStorageImpl.java b/org.adempiere.base/src/org/compiere/model/ImageDBStorageImpl.java index 67e6fd3433..95809ac62d 100644 --- a/org.adempiere.base/src/org/compiere/model/ImageDBStorageImpl.java +++ b/org.adempiere.base/src/org/compiere/model/ImageDBStorageImpl.java @@ -44,11 +44,16 @@ public class ImageDBStorageImpl implements IImageStore { byte[] inflatedData = null; try { - ByteArrayInputStream in = new ByteArrayInputStream(deflatedData); - ZipInputStream zip = new ZipInputStream(in); - ZipEntry entry = zip.getNextEntry(); - if (entry != null) // just one entry + ZipInputStream zip = null; + ZipEntry entry = null; + if (MSysConfig.getBooleanValue(MSysConfig.IMAGE_DB_STORAGE_SAVE_AS_ZIP, false)) { + ByteArrayInputStream in = new ByteArrayInputStream(deflatedData); + zip = new ZipInputStream(in); + entry = zip.getNextEntry(); + } + if (entry != null) { + // just one entry per zip ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buffer = new byte[2048]; int length = zip.read(buffer); @@ -62,7 +67,7 @@ public class ImageDBStorageImpl implements IImageStore { + "(" + entry.getSize() + ") " + (entry.getCompressedSize() * 100 / entry.getSize()) + "%"); } else { - //not zip stream, legacy data + //not zip stream, legacy data, or saving as raw inflatedData = deflatedData; } } catch (Exception e) { @@ -73,36 +78,39 @@ public class ImageDBStorageImpl implements IImageStore { } @Override - public void save(MImage image, MStorageProvider prov,byte[] inflatedData) { + 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()) + "%"); + if (MSysConfig.getBooleanValue(MSysConfig.IMAGE_DB_STORAGE_SAVE_AS_ZIP, false)) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(out); + zip.setMethod(ZipOutputStream.DEFLATED); + zip.setLevel(Deflater.BEST_COMPRESSION); + zip.setComment("idempiere"); // - // 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; + try { + ZipEntry entry = new ZipEntry(image.getName()); + 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; + } + } else { + deflatedData = inflatedData; } image.setByteData(deflatedData); } diff --git a/org.adempiere.base/src/org/compiere/model/ImageFileStorageImpl.java b/org.adempiere.base/src/org/compiere/model/ImageFileStorageImpl.java index 371018aa0c..3d3178f615 100644 --- a/org.adempiere.base/src/org/compiere/model/ImageFileStorageImpl.java +++ b/org.adempiere.base/src/org/compiere/model/ImageFileStorageImpl.java @@ -177,10 +177,9 @@ public class ImageFileStorageImpl implements IImageStore { } } - String ext = getExtension(image); // write to path msgfile = new StringBuilder().append(imagePathRoot).append(File.separator) - .append(image.getImageStoragePath()).append(image.get_ID()).append(ext); + .append(image.getImageStoragePath()).append(image.get_ID()); final File destFile = new File(msgfile.toString()); out = new BufferedOutputStream(new FileOutputStream(destFile)); @@ -194,7 +193,7 @@ public class ImageFileStorageImpl implements IImageStore { 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); + StringBuilder msgsat = new StringBuilder(IMAGE_FOLDER_PLACEHOLDER).append(image.getImageStoragePath()).append(image.get_ID()); entry.setAttribute("file", msgsat.toString()); root.appendChild(entry); final Source source = new DOMSource(document); @@ -239,9 +238,8 @@ public class ImageFileStorageImpl implements IImageStore { 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); + .append(image.getImageStoragePath()).append(image.getAD_Image_ID()); File file=new File(msgfile.toString()); if (file !=null && file.exists()) { @@ -253,15 +251,6 @@ public class ImageFileStorageImpl implements IImageStore { 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; diff --git a/org.adempiere.base/src/org/compiere/model/MArchive.java b/org.adempiere.base/src/org/compiere/model/MArchive.java index 1b89af5d49..4948d11089 100644 --- a/org.adempiere.base/src/org/compiere/model/MArchive.java +++ b/org.adempiere.base/src/org/compiere/model/MArchive.java @@ -39,7 +39,7 @@ public class MArchive extends X_AD_Archive { /** * */ - private static final long serialVersionUID = 3217541537768473865L; + private static final long serialVersionUID = -9116541441191978777L; /** * Get Archives @@ -269,7 +269,8 @@ public class MArchive extends X_AD_Archive { return true; } // beforeSave - protected boolean beforeDelete () + @Override + protected boolean postDelete() { IArchiveStore prov = provider.getArchiveStore(); if (prov != null) diff --git a/org.adempiere.base/src/org/compiere/model/MAttachment.java b/org.adempiere.base/src/org/compiere/model/MAttachment.java index 1bcb950e79..e1cb5aaee5 100644 --- a/org.adempiere.base/src/org/compiere/model/MAttachment.java +++ b/org.adempiere.base/src/org/compiere/model/MAttachment.java @@ -57,7 +57,7 @@ public class MAttachment extends X_AD_Attachment /** * */ - private static final long serialVersionUID = -8261865873158774665L; + private static final long serialVersionUID = 6596285414376249694L; /** * @@ -502,28 +502,20 @@ 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 () - { - return deleteLOBData(); - } - /** * Delete Entry Data in Zip File format * @return true if saved */ - private boolean deleteLOBData() + @Override + protected boolean postDelete() { if (m_items == null) loadLOBData(); IAttachmentStore prov = provider.getAttachmentStore(); if (prov != null) return prov.delete(this,provider); - return false; - } // beforeDelete + return true; + } // postDelete /************************************************************************** * Test diff --git a/org.adempiere.base/src/org/compiere/model/MBPartner.java b/org.adempiere.base/src/org/compiere/model/MBPartner.java index 6e0e0293ba..f48e61ece5 100644 --- a/org.adempiere.base/src/org/compiere/model/MBPartner.java +++ b/org.adempiere.base/src/org/compiere/model/MBPartner.java @@ -47,7 +47,7 @@ public class MBPartner extends X_C_BPartner /** * */ - private static final long serialVersionUID = -255154524310324997L; + private static final long serialVersionUID = 5534148976588041343L; /** * Get Empty Template Business Partner @@ -1006,4 +1006,16 @@ public class MBPartner extends X_C_BPartner return success; } // afterDelete + @Override + protected boolean postDelete() { + if (getLogo_ID() > 0) { + MImage img = new MImage(getCtx(), getLogo_ID(), get_TrxName()); + if (!img.delete(true)) { + log.warning("Associated image could not be deleted for bpartner - AD_Image_ID=" + getLogo_ID()); + return false; + } + } + return true; + } + } // MBPartner diff --git a/org.adempiere.base/src/org/compiere/model/MImage.java b/org.adempiere.base/src/org/compiere/model/MImage.java index fda6e4c9bc..026bed6d0c 100644 --- a/org.adempiere.base/src/org/compiere/model/MImage.java +++ b/org.adempiere.base/src/org/compiere/model/MImage.java @@ -329,8 +329,8 @@ public class MImage extends X_AD_Image } // beforeSave public String getImageStoragePath() { - StringBuilder path = new StringBuilder().append(this.getAD_Client_ID()).append(File.separator).append(this.getAD_Org_ID()) - .append(File.separator); + StringBuilder path = new StringBuilder("AD_Image").append(File.separator) + .append(this.getAD_Client_ID()).append(File.separator); return path.toString(); } @@ -354,18 +354,15 @@ public class MImage extends X_AD_Image 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; - + protected boolean postDelete() { + IImageStore prov = provider.getImageStore(); + if (prov != null) + return prov.delete(this,provider); + return true; } - + @Override protected void saveNew_afterSetID() { diff --git a/org.adempiere.base/src/org/compiere/model/MPOSKey.java b/org.adempiere.base/src/org/compiere/model/MPOSKey.java index 207ccb3396..361a997c9d 100644 --- a/org.adempiere.base/src/org/compiere/model/MPOSKey.java +++ b/org.adempiere.base/src/org/compiere/model/MPOSKey.java @@ -28,11 +28,10 @@ import java.util.Properties; */ public class MPOSKey extends X_C_POSKey { - /** * */ - private static final long serialVersionUID = -5810613982853803837L; + private static final long serialVersionUID = 2595668386249398840L; /** * Standard Constructor @@ -56,4 +55,16 @@ public class MPOSKey extends X_C_POSKey super(ctx, rs, trxName); } // MPOSKey + @Override + protected boolean postDelete() { + if (getAD_Image_ID() > 0) { + MImage img = new MImage(getCtx(), getAD_Image_ID(), get_TrxName()); + if (!img.delete(true)) { + log.warning("Associated image could not be deleted for POS Key - AD_Image_ID=" + getAD_Image_ID()); + return false; + } + } + return true; + } + } // MPOSKey diff --git a/org.adempiere.base/src/org/compiere/model/MSysConfig.java b/org.adempiere.base/src/org/compiere/model/MSysConfig.java index 327bee7dc1..3e89625d8d 100644 --- a/org.adempiere.base/src/org/compiere/model/MSysConfig.java +++ b/org.adempiere.base/src/org/compiere/model/MSysConfig.java @@ -39,10 +39,10 @@ import org.compiere.util.DisplayType; */ public class MSysConfig extends X_AD_SysConfig { - /** + /** * */ - private static final long serialVersionUID = 6662424546913925975L; + private static final long serialVersionUID = -9208749663408576569L; public static final String ADDRESS_VALIDATION = "ADDRESS_VALIDATION"; public static final String ALERT_SEND_ATTACHMENT_AS_XLS = "ALERT_SEND_ATTACHMENT_AS_XLS"; @@ -99,6 +99,7 @@ public class MSysConfig extends X_AD_SysConfig public static final String HTML_REPORT_THEME = "HTML_REPORT_THEME"; public static final String IBAN_VALIDATION = "IBAN_VALIDATION"; public static final String IDENTIFIER_SEPARATOR = "IDENTIFIER_SEPARATOR"; + public static final String IMAGE_DB_STORAGE_SAVE_AS_ZIP = "IMAGE_DB_STORAGE_SAVE_AS_ZIP"; public static final String INFO_DEFAULTSELECTED = "INFO_DEFAULTSELECTED"; public static final String INFO_DOUBLECLICKTOGGLESSELECTION = "INFO_DOUBLECLICKTOGGLESSELECTION"; public static final String Invoice_ReverseUseNewNumber = "Invoice_ReverseUseNewNumber"; diff --git a/org.adempiere.base/src/org/compiere/model/MUser.java b/org.adempiere.base/src/org/compiere/model/MUser.java index 5c6410730b..163cfaf6f5 100644 --- a/org.adempiere.base/src/org/compiere/model/MUser.java +++ b/org.adempiere.base/src/org/compiere/model/MUser.java @@ -33,7 +33,6 @@ import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import org.adempiere.exceptions.DBException; -import org.codehaus.groovy.classgen.GeneratorContext; import org.compiere.util.CCache; import org.compiere.util.CLogger; import org.compiere.util.DB; @@ -59,7 +58,7 @@ public class MUser extends X_AD_User /** * */ - private static final long serialVersionUID = 7996468236476384128L; + private static final long serialVersionUID = 1366564982801896588L; /** * Get active Users of BPartner @@ -1105,4 +1104,17 @@ public class MUser extends X_AD_User } return super.afterSave(newRecord, success); } + + @Override + protected boolean postDelete() { + if (getAD_Image_ID() > 0) { + MImage img = new MImage(getCtx(), getAD_Image_ID(), get_TrxName()); + if (!img.delete(true)) { + log.warning("Associated image could not be deleted for user - AD_Image_ID=" + getAD_Image_ID()); + return false; + } + } + return true; + } + } // MUser diff --git a/org.adempiere.base/src/org/compiere/model/PO.java b/org.adempiere.base/src/org/compiere/model/PO.java index a2b1f8c61a..ef7f0b204c 100644 --- a/org.adempiere.base/src/org/compiere/model/PO.java +++ b/org.adempiere.base/src/org/compiere/model/PO.java @@ -110,7 +110,7 @@ public abstract class PO /** * */ - private static final long serialVersionUID = -1743619574547406959L; + private static final long serialVersionUID = -1330388218446118451L; public static final String LOCAL_TRX_PREFIX = "POSave"; @@ -3504,6 +3504,10 @@ public abstract class PO // Reset if (success) { + if (!postDelete()) { + log.warning("postDelete failed"); + } + //osgi event handler Event event = EventManager.newEvent(IEventTopics.PO_POST_DELETE, this); EventManager.getInstance().postEvent(event); @@ -3603,6 +3607,14 @@ public abstract class PO return success; } // afterDelete + /** + * Executed after the Delete operation is committed in the database. + * @return true if post delete is a success + */ + protected boolean postDelete() + { + return true; + } /** * Insert (missing) Translation Records diff --git a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WImageEditor.java b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WImageEditor.java index 8fd0185df7..534e194440 100644 --- a/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WImageEditor.java +++ b/org.adempiere.ui.zk/WEB-INF/src/org/adempiere/webui/editor/WImageEditor.java @@ -178,6 +178,10 @@ public class WImageEditor extends WEditor // ValueChangeEvent vce = new ValueChangeEvent(WImageEditor.this, gridField.getColumnName(), oldValue, newValue); fireValueChange(vce); + if (oldValue == null && newValue != null && getGridField() != null && getGridField().getGridTab() != null) { + // save automatically when creating a new image + getGridField().getGridTab().dataSave(false); + } } }