Related to issue 31580: Reduced Java Heap Usage even more
authorVíctor Martínez Romanos <victor.martinez@openbravo.com>
Thu, 21 Jan 2016 16:52:52 +0100
changeset 28540 7dafd0844a3d
parent 28539 e27db29dcaa6
child 28541 0171ef423986
Related to issue 31580: Reduced Java Heap Usage even more

DocFINFinAccTransaction and DocFINPayment:
+ Avoid storing useless objects like ps and pd, and replaced by String Ids, so we can safely clear the Hibernate session
+ Save cInvoiceId when loading lines in field provider so it's available latter on when loading the document lines, thus we avoid an extra query to the database to get the invoice from the payment detail
+ Forces a session clear every 100 records when working with the payment details. This is only possible because DAL objects are not stored anywhere.

DocLine_FINFinAccTransaction and DocLine_FINPayment:
+ Declare a String invoiceId parameter instead of using an Invoice object. This improves the memory consumption and allows to manage external session clears when required.
+ Declare as deprecated all the methods that used the invoice object and adapt the getInvoice() to be backward compatible (returns the invoice object is previously declared in the object, or a new object based on the invoiceId)
+ For DocLine_FINPayment only, adapted the Order object usage following the same logic.

FIN_Utility.getOrderedPaymentDetailList() query uses an inner join to the FPSD because having a linked FPSD is a requirement in the accounting classes.


Note that DocFINReconciliation class should be also adapted following the same logics described above. However, this class would require deeper refactor mainly because of the attribute Set<FIN_FinaccTransaction> transactionsToBePosted, that avoids safely session clears.
modules/org.openbravo.advpaymentmngt/src/org/openbravo/advpaymentmngt/utility/FIN_Utility.java
src/org/openbravo/erpCommon/ad_forms/DocFINFinAccTransaction.java
src/org/openbravo/erpCommon/ad_forms/DocFINPayment.java
src/org/openbravo/erpCommon/ad_forms/DocLine_FINFinAccTransaction.java
src/org/openbravo/erpCommon/ad_forms/DocLine_FINPayment.java
--- a/modules/org.openbravo.advpaymentmngt/src/org/openbravo/advpaymentmngt/utility/FIN_Utility.java	Fri Jan 22 08:11:32 2016 +0100
+++ b/modules/org.openbravo.advpaymentmngt/src/org/openbravo/advpaymentmngt/utility/FIN_Utility.java	Thu Jan 21 16:52:52 2016 +0100
@@ -11,7 +11,7 @@
  * under the License.
  * The Original Code is Openbravo ERP.
  * The Initial Developer of the Original Code is Openbravo SLU
- * All portions are Copyright (C) 2010-2015 Openbravo SLU
+ * All portions are Copyright (C) 2010-2016 Openbravo SLU
  * All Rights Reserved.
  * Contributor(s):  ______________________________________.
  *************************************************************************
@@ -1358,8 +1358,8 @@
       final StringBuilder whereClause = new StringBuilder();
       whereClause.append(" select pd." + FIN_PaymentDetail.PROPERTY_ID);
       whereClause.append(" from " + FIN_PaymentDetail.ENTITY_NAME + " as pd");
-      whereClause.append(" left join pd." + FIN_PaymentDetail.PROPERTY_FINPAYMENTSCHEDULEDETAILLIST
-          + " as psd");
+      whereClause.append(" inner join pd."
+          + FIN_PaymentDetail.PROPERTY_FINPAYMENTSCHEDULEDETAILLIST + " as psd");
       whereClause
           .append(" where pd." + FIN_PaymentDetail.PROPERTY_FINPAYMENT + ".id = :paymentId ");
       whereClause.append(" and pd." + FIN_PaymentDetail.PROPERTY_ACTIVE + " = true");
--- a/src/org/openbravo/erpCommon/ad_forms/DocFINFinAccTransaction.java	Fri Jan 22 08:11:32 2016 +0100
+++ b/src/org/openbravo/erpCommon/ad_forms/DocFINFinAccTransaction.java	Thu Jan 21 16:52:52 2016 +0100
@@ -11,7 +11,7 @@
  * under the License.
  * The Original Code is Openbravo ERP.
  * The Initial Developer of the Original Code is Openbravo SLU
- * All portions are Copyright (C) 2010-2015 Openbravo SLU
+ * All portions are Copyright (C) 2010-2016 Openbravo SLU
  * All Rights Reserved.
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -29,6 +29,7 @@
 
 import javax.servlet.ServletException;
 
+import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
 import org.hibernate.criterion.Restrictions;
 import org.openbravo.advpaymentmngt.utility.FIN_Utility;
@@ -116,8 +117,8 @@
     FIN_Payment payment = transaction.getFinPayment();
     List<String> paymentDetails = FIN_Utility.getOrderedPaymentDetailList(payment.getId());
     FieldProviderFactory[] data = new FieldProviderFactory[paymentDetails.size()];
-    FIN_PaymentSchedule ps = null;
-    FIN_PaymentDetail pd = null;
+    String psId = null;
+    String pdId = null;
     OBContext.setAdminMode();
     try {
       for (int i = 0; i < data.length; i++) {
@@ -133,10 +134,10 @@
         }
 
         // If the Payment Detail has already been processed, skip it
-        if (paymentDetail.equals(pd)) {
+        if (StringUtils.equals(paymentDetail.getId(), pdId)) {
           continue;
         }
-        pd = paymentDetail;
+        pdId = paymentDetail.getId();
 
         data[i] = new FieldProviderFactory(null);
         FIN_PaymentSchedule psi = paymentDetail.getFINPaymentScheduleDetailList().get(0)
@@ -155,17 +156,18 @@
           paymentDetailPreviousId = paymentDetails.get(i - 1);
         }
         HashMap<String, BigDecimal> amountAndWriteOff = getPaymentDetailWriteOffAndAmount(
-            paymentDetail, paymentDetailNextId, paymentDetailPreviousId, ps, psi, pso, data[i]);
+            paymentDetail, paymentDetailNextId, paymentDetailPreviousId, psId != null ? OBDal
+                .getInstance().get(FIN_PaymentSchedule.class, psId) : null, psi, pso, data[i]);
         BigDecimal amount = amountAndWriteOff.get("amount");
         BigDecimal writeOff = amountAndWriteOff.get("writeoff");
         if (amount == null) {
           data[i] = null;
-          ps = psi;
+          psId = psi != null ? psi.getId() : null;
           continue;
         } else {
           FieldProviderFactory.setField(data[i], "Amount", amount.toString());
         }
-        ps = psi;
+        psId = psi != null ? psi.getId() : null;
 
         FieldProviderFactory.setField(data[i], "FIN_Finacc_Transaction_ID", transaction.getId());
         FieldProviderFactory.setField(data[i], "AD_Client_ID", paymentDetail.getClient().getId());
@@ -204,6 +206,8 @@
         FieldProviderFactory.setField(data[i], "adOrgId", paymentDetail.getOrganization().getId());
         FieldProviderFactory.setField(data[i], "description", transaction.getDescription());
         FieldProviderFactory.setField(data[i], "cCurrencyId", transaction.getCurrency().getId());
+        FieldProviderFactory.setField(data[i], "cInvoiceId", psi != null
+            && psi.getInvoice() != null ? psi.getInvoice().getId() : null);
         FieldProviderFactory.setField(data[i], "cProjectId", paymentDetail
             .getFINPaymentScheduleDetailList().get(0).getInvoicePaymentSchedule() != null
             && paymentDetail.getFINPaymentScheduleDetailList().get(0).getInvoicePaymentSchedule()
@@ -294,6 +298,9 @@
             paymentDetail.isPrepayment() ? (pso != null ? pso.getId() : "") : (psi != null ? psi
                 .getId() : ""));
 
+        if (i % 100 == 0) {
+          OBDal.getInstance().getSession().clear();
+        }
       }
     } finally {
       OBContext.restorePreviousMode();
@@ -368,14 +375,7 @@
         if (strPaymentId != null && !strPaymentId.equals("")) {
           docLine.setFinPaymentId(strPaymentId);
           // docLine.m_Record_Id2 = strPaymentId;
-          FIN_PaymentDetail detail = OBDal.getInstance().get(FIN_PaymentDetail.class,
-              paymentDetail_ID);
-          docLine
-              .setInvoice(detail.getFINPaymentScheduleDetailList() != null
-                  && detail.getFINPaymentScheduleDetailList().get(0).getInvoicePaymentSchedule() != null ? detail
-                  .getFINPaymentScheduleDetailList().get(0).getInvoicePaymentSchedule()
-                  .getInvoice()
-                  : null);
+          docLine.setInvoiceId(data[i].getField("cInvoiceId"));
           docLine.setDoubtFulDebtAmount(new BigDecimal(data[i].getField("DoubtFulDebtAmount")));
 
           docLine.setInvoiceTaxCashVAT_V(paymentDetail_ID);
@@ -631,6 +631,10 @@
             }
           }
         }
+
+        if (i % 100 == 0) {
+          OBDal.getInstance().getSession().clear();
+        }
       }
       // Pre-payment is consumed when Used Credit Amount not equals Zero. When consuming Credit no
       // credit is generated
--- a/src/org/openbravo/erpCommon/ad_forms/DocFINPayment.java	Fri Jan 22 08:11:32 2016 +0100
+++ b/src/org/openbravo/erpCommon/ad_forms/DocFINPayment.java	Thu Jan 21 16:52:52 2016 +0100
@@ -11,7 +11,7 @@
  * under the License.
  * The Original Code is Openbravo ERP.
  * The Initial Developer of the Original Code is Openbravo SLU
- * All portions are Copyright (C) 2010-2015 Openbravo SLU
+ * All portions are Copyright (C) 2010-2016 Openbravo SLU
  * All Rights Reserved.
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -27,6 +27,7 @@
 
 import javax.servlet.ServletException;
 
+import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
 import org.hibernate.criterion.Restrictions;
 import org.openbravo.advpaymentmngt.utility.FIN_Utility;
@@ -91,8 +92,8 @@
       return null;
 
     FieldProviderFactory[] data = new FieldProviderFactory[paymentDetails.size()];
-    FIN_PaymentSchedule ps = null;
-    FIN_PaymentDetail pd = null;
+    String psId = null;
+    String pdId = null;
     OBContext.setAdminMode();
     try {
       for (int i = 0; i < data.length; i++) {
@@ -106,10 +107,10 @@
         }
 
         // If the Payment Detail has already been processed, skip it
-        if (paymentDetail.equals(pd)) {
+        if (StringUtils.equals(paymentDetail.getId(), pdId)) {
           continue;
         }
-        pd = paymentDetail;
+        pdId = paymentDetail.getId();
 
         data[i] = new FieldProviderFactory(null);
         FIN_PaymentSchedule psi = paymentDetail.getFINPaymentScheduleDetailList().get(0)
@@ -128,17 +129,18 @@
           paymentDetailPreviousId = paymentDetails.get(i - 1);
         }
         HashMap<String, BigDecimal> amountAndWriteOff = getPaymentDetailWriteOffAndAmount(
-            paymentDetail, paymentDetailNextId, paymentDetailPreviousId, ps, psi, pso, data[i]);
+            paymentDetail, paymentDetailNextId, paymentDetailPreviousId, psId != null ? OBDal
+                .getInstance().get(FIN_PaymentSchedule.class, psId) : null, psi, pso, data[i]);
         BigDecimal amount = amountAndWriteOff.get("amount");
         BigDecimal writeOff = amountAndWriteOff.get("writeoff");
         if (amount == null) {
           data[i] = null;
-          ps = psi;
+          psId = psi != null ? psi.getId() : null;
           continue;
         } else {
           FieldProviderFactory.setField(data[i], "Amount", amount.toString());
         }
-        ps = psi;
+        psId = psi != null ? psi.getId() : null;
 
         FieldProviderFactory.setField(data[i], "AD_Client_ID", paymentDetail.getClient().getId());
         FieldProviderFactory
@@ -169,6 +171,8 @@
             : (isPaymentDatePriorToInvoiceDate ? "Y" : "N"));
         FieldProviderFactory.setField(data[i], "isPaymentDatePriorToInvoiceDate",
             isPaymentDatePriorToInvoiceDate && !paymentDetail.isPrepayment() ? "Y" : "N");
+        FieldProviderFactory.setField(data[i], "cInvoiceId", psi != null
+            && psi.getInvoice() != null ? psi.getInvoice().getId() : null);
         FieldProviderFactory.setField(data[i], "cProjectId", paymentDetail
             .getFINPaymentScheduleDetailList().get(0).getInvoicePaymentSchedule() != null
             && paymentDetail.getFINPaymentScheduleDetailList().get(0).getInvoicePaymentSchedule()
@@ -258,6 +262,9 @@
             paymentDetail.isPrepayment() ? (pso != null ? pso.getId() : "") : (psi != null ? psi
                 .getId() : ""));
 
+        if (i % 100 == 0) {
+          OBDal.getInstance().getSession().clear();
+        }
       }
     } finally {
       OBContext.restorePreviousMode();
@@ -276,7 +283,6 @@
       String Line_ID = data[i].getField("FIN_Payment_Detail_ID");
       OBContext.setAdminMode();
       try {
-        FIN_PaymentDetail detail = OBDal.getInstance().get(FIN_PaymentDetail.class, Line_ID);
         DocLine_FINPayment docLine = new DocLine_FINPayment(DocumentType, Record_ID, Line_ID);
         docLine.loadAttributes(data[i], this);
         docLine.setAmount(data[i].getField("Amount"));
@@ -286,11 +292,7 @@
         docLine.setC_GLItem_ID(data[i].getField("C_GLItem_ID"));
         docLine.setPrepaymentAgainstInvoice("Y".equals(data[i]
             .getField("isPaymentDatePriorToInvoiceDate")) ? true : false);
-        docLine
-            .setInvoice(detail.getFINPaymentScheduleDetailList() != null
-                && detail.getFINPaymentScheduleDetailList().get(0).getInvoicePaymentSchedule() != null ? detail
-                .getFINPaymentScheduleDetailList().get(0).getInvoicePaymentSchedule().getInvoice()
-                : null);
+        docLine.setInvoiceId(data[i].getField("cInvoiceId"));
         docLine.m_Record_Id2 = data[i].getField("recordId2");
         docLine.setInvoiceTaxCashVAT_V(Line_ID);
         docLine.setInvoiceTaxCashVAT_V(data[i].getField("MergedPaymentDetailId"));
@@ -472,6 +474,10 @@
                   isReceipt, conn), C_Currency_ID, (isReceipt ? "" : bpAmount),
               (isReceipt ? bpAmount : ""), Fact_Acct_Group_ID, nextSeqNo(SeqNo), DocumentType, conn);
         }
+
+        if (i % 100 == 0) {
+          OBDal.getInstance().getSession().clear();
+        }
       }
       FIN_Payment payment = OBDal.getInstance().get(FIN_Payment.class, Record_ID);
       if (BigDecimal.ZERO.compareTo(new BigDecimal(Amounts[AMTTYPE_Gross])) != 0) {
--- a/src/org/openbravo/erpCommon/ad_forms/DocLine_FINFinAccTransaction.java	Fri Jan 22 08:11:32 2016 +0100
+++ b/src/org/openbravo/erpCommon/ad_forms/DocLine_FINFinAccTransaction.java	Thu Jan 21 16:52:52 2016 +0100
@@ -11,7 +11,7 @@
  * under the License.
  * The Original Code is Openbravo ERP.
  * The Initial Developer of the Original Code is Openbravo SLU
- * All portions are Copyright (C) 2010-2013 Openbravo SLU
+ * All portions are Copyright (C) 2010-2016 Openbravo SLU
  * All Rights Reserved.
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -20,11 +20,14 @@
 
 import java.math.BigDecimal;
 
+import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
+import org.openbravo.dal.core.OBContext;
 import org.openbravo.dal.service.OBDal;
 import org.openbravo.model.common.invoice.Invoice;
 
-public class DocLine_FINFinAccTransaction extends DocLineCashVATReady_PaymentTransactionReconciliation {
+public class DocLine_FINFinAccTransaction extends
+    DocLineCashVATReady_PaymentTransactionReconciliation {
   static Logger log4jDocLine_FINFinAccTransaction = Logger
       .getLogger(DocLine_FINFinAccTransaction.class);
 
@@ -37,7 +40,10 @@
   String WriteOffAmt = "";
   boolean isPrepaymentAgainstInvoice = false;
   BigDecimal doubtFulDebtAmount = BigDecimal.ZERO;
+
+  @Deprecated
   Invoice invoice = null;
+  private String invoiceId;
 
   public String getcGlItemId() {
     return cGlItemId;
@@ -48,18 +54,35 @@
   }
 
   public Invoice getInvoice() {
-    return invoice;
+    if (invoice != null) {
+      return invoice;
+    } else if (StringUtils.isNotBlank(invoiceId)) {
+      try {
+        OBContext.setAdminMode(false);
+        return OBDal.getInstance().get(Invoice.class, invoiceId);
+      } finally {
+        OBContext.restorePreviousMode();
+      }
+    } else {
+      return null;
+    }
   }
 
+  /**
+   * @deprecated Use {@link #setInvoiceId(String)} instead, which avoids to store a object in memory
+   *             so we can control from outside when to flush and/or clear the session to avoid Out
+   *             Of Memory errors
+   */
   public void setInvoice(Invoice invoice) {
     this.invoice = invoice;
   }
 
-  @Deprecated
-  public String getInvoiceId() {
-    return invoice.getId();
-  }
-
+  /**
+   * @deprecated Use {@link #setInvoiceId(String)} instead, which avoids to store a object in memory
+   *             so we can control from outside when to flush and/or clear the session to avoid Out
+   *             Of Memory errors
+   * 
+   */
   @Deprecated
   public void setInvoice_ID(String invoiceId) {
     this.invoice = OBDal.getInstance().get(Invoice.class, invoiceId);
@@ -193,6 +216,14 @@
     this.doubtFulDebtAmount = doubtFulDebtAmount;
   }
 
+  public String getInvoiceId() {
+    return invoiceId;
+  }
+
+  public void setInvoiceId(String invoiceId) {
+    this.invoiceId = invoiceId;
+  }
+
   public DocLine_FINFinAccTransaction(String DocumentType, String TrxHeader_ID, String TrxLine_ID) {
     super(DocumentType, TrxHeader_ID, TrxLine_ID);
     Line_ID = TrxLine_ID;
--- a/src/org/openbravo/erpCommon/ad_forms/DocLine_FINPayment.java	Fri Jan 22 08:11:32 2016 +0100
+++ b/src/org/openbravo/erpCommon/ad_forms/DocLine_FINPayment.java	Thu Jan 21 16:52:52 2016 +0100
@@ -11,7 +11,7 @@
  * under the License.
  * The Original Code is Openbravo ERP.
  * The Initial Developer of the Original Code is Openbravo SLU
- * All portions are Copyright (C) 2010-2013 Openbravo SLU
+ * All portions are Copyright (C) 2010-2016 Openbravo SLU
  * All Rights Reserved.
  * Contributor(s):  ______________________________________.
  ************************************************************************
@@ -20,6 +20,9 @@
 
 import java.math.BigDecimal;
 
+import org.apache.commons.lang.StringUtils;
+import org.openbravo.dal.core.OBContext;
+import org.openbravo.dal.service.OBDal;
 import org.openbravo.model.common.invoice.Invoice;
 import org.openbravo.model.common.order.Order;
 
@@ -32,22 +35,59 @@
   String C_GLItem_ID = "";
   String isPrepayment = "";
   boolean isPrepaymentAgainstInvoice = false;
+  BigDecimal doubtFulDebtAmount = BigDecimal.ZERO;
+
+  @Deprecated
   Invoice invoice = null;
-  BigDecimal doubtFulDebtAmount = BigDecimal.ZERO;
+  private String invoiceId;
+  @Deprecated
   Order order = null;
+  private String orderId;
 
   public Invoice getInvoice() {
-    return invoice;
+    if (invoice != null) {
+      return invoice;
+    } else if (StringUtils.isNotBlank(invoiceId)) {
+      try {
+        OBContext.setAdminMode(false);
+        return OBDal.getInstance().get(Invoice.class, invoiceId);
+      } finally {
+        OBContext.restorePreviousMode();
+      }
+    } else {
+      return null;
+    }
   }
 
+  /**
+   * @deprecated Use {@link #setInvoiceId(String)} instead, which avoids to store a object in memory
+   *             so we can control from outside when to flush and/or clear the session to avoid Out
+   *             Of Memory errors
+   */
   public void setInvoice(Invoice invoice) {
     this.invoice = invoice;
   }
 
   public Order getOrder() {
-    return order;
+    if (order != null) {
+      return order;
+    } else if (StringUtils.isNotBlank(orderId)) {
+      try {
+        OBContext.setAdminMode(false);
+        return OBDal.getInstance().get(Order.class, orderId);
+      } finally {
+        OBContext.restorePreviousMode();
+      }
+    } else {
+      return null;
+    }
   }
 
+  /**
+   * @deprecated Use {@link #setOrderId(String)} instead, which avoids to store a object in memory
+   *             so we can control from outside when to flush and/or clear the session to avoid Out
+   *             Of Memory errors
+   */
   public void setOrder(Order order) {
     this.order = order;
   }
@@ -175,4 +215,20 @@
   public String getServletInfo() {
     return "Servlet for accounting";
   } // end of getServletInfo() method
+
+  public String getInvoiceId() {
+    return invoiceId;
+  }
+
+  public void setInvoiceId(String invoiceId) {
+    this.invoiceId = invoiceId;
+  }
+
+  public String getOrderId() {
+    return orderId;
+  }
+
+  public void setOrderId(String orderId) {
+    this.orderId = orderId;
+  }
 }