From 809f57fe774b35e95b4d51c3580f21c2b3fd92e1 Mon Sep 17 00:00:00 2001 From: Heng Sin Low Date: Sun, 1 Jul 2012 09:27:13 +0800 Subject: [PATCH] IDEMPIERE-146 Performance: Report Engine always read all data into memory --- .../oracle/848_IDEMPIERE-146.sql | 16 ++ .../postgresql/848_IDEMPIERE-146.sql | 16 ++ .../src/org/compiere/print/PrintData.java | 115 ++++---- .../print/layout/Dimension2DImpl.java | 8 +- .../compiere/print/layout/HTMLRenderer.java | 48 +++- .../compiere/print/layout/LayoutEngine.java | 42 +-- .../compiere/print/layout/PrintElement.java | 9 +- .../compiere/print/layout/TableElement.java | 123 ++++++--- .../print/util/SerializableMatrix.java | 46 ++++ .../print/util/SerializableMatrixImpl.java | 250 +++++++++++++++++ .../src/org/compiere/print/util/SwapFile.java | 260 ++++++++++++++++++ .../compiere/print/util/SwapFileSegment.java | 40 +++ .../org/compiere/report/ReportStarter.java | 13 +- 13 files changed, 862 insertions(+), 124 deletions(-) create mode 100644 migration/360lts-release/oracle/848_IDEMPIERE-146.sql create mode 100644 migration/360lts-release/postgresql/848_IDEMPIERE-146.sql create mode 100644 org.adempiere.base/src/org/compiere/print/util/SerializableMatrix.java create mode 100644 org.adempiere.base/src/org/compiere/print/util/SerializableMatrixImpl.java create mode 100644 org.adempiere.base/src/org/compiere/print/util/SwapFile.java create mode 100644 org.adempiere.base/src/org/compiere/print/util/SwapFileSegment.java diff --git a/migration/360lts-release/oracle/848_IDEMPIERE-146.sql b/migration/360lts-release/oracle/848_IDEMPIERE-146.sql new file mode 100644 index 0000000000..a2f28133c8 --- /dev/null +++ b/migration/360lts-release/oracle/848_IDEMPIERE-146.sql @@ -0,0 +1,16 @@ +-- Jun 30, 2012 9:04:20 AM MYT +-- IDEMPIERE-146 Performance: Report Engine always read all data into memory +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_SysConfig_UU,ConfigurationLevel,Value,Description,EntityType,AD_Client_ID,Created,Updated,AD_Org_ID,CreatedBy,UpdatedBy,IsActive,Name) VALUES (200010,'794a49c8-c127-4e9c-bb3f-7be83f42aa48','S','2000','Use swap file when report data exceed the number of rows define here. Enter a value of zero or less if you don''t want to use swap file for all reports regarding the number of rows involved','D',0,TO_DATE('2012-06-30 09:04:18','YYYY-MM-DD HH24:MI:SS'),TO_DATE('2012-06-30 09:04:18','YYYY-MM-DD HH24:MI:SS'),0,100,100,'Y','REPORT_SWAP_MAX_ROWS') +; + +-- Jun 30, 2012 9:09:35 AM MYT +-- IDEMPIERE-146 Performance: Report Engine always read all data into memory +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_SysConfig_UU,ConfigurationLevel,Value,Description,EntityType,AD_Client_ID,Created,Updated,AD_Org_ID,CreatedBy,UpdatedBy,IsActive,Name) VALUES (200011,'2f885a17-b05f-4744-a617-20e8449c22df','S','100','Max number of pages to keep in memory when rendering a jasper report','D',0,TO_DATE('2012-06-30 09:09:34','YYYY-MM-DD HH24:MI:SS'),TO_DATE('2012-06-30 09:09:34','YYYY-MM-DD HH24:MI:SS'),0,100,100,'Y','JASPER_REPORT_SWAP_MAX_PAGES') +; + +UPDATE AD_System + SET LastMigrationScriptApplied='848_IDEMPIERE-146.sql' +WHERE LastMigrationScriptApplied<'848_IDEMPIERE-146.sql' + OR LastMigrationScriptApplied IS NULL +; + diff --git a/migration/360lts-release/postgresql/848_IDEMPIERE-146.sql b/migration/360lts-release/postgresql/848_IDEMPIERE-146.sql new file mode 100644 index 0000000000..fb7bbc83bf --- /dev/null +++ b/migration/360lts-release/postgresql/848_IDEMPIERE-146.sql @@ -0,0 +1,16 @@ +-- Jun 30, 2012 9:04:20 AM MYT +-- IDEMPIERE-146 Performance: Report Engine always read all data into memory +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_SysConfig_UU,ConfigurationLevel,Value,Description,EntityType,AD_Client_ID,Created,Updated,AD_Org_ID,CreatedBy,UpdatedBy,IsActive,Name) VALUES (200010,'794a49c8-c127-4e9c-bb3f-7be83f42aa48','S','2000','Use swap file when report data exceed the number of rows define here. Enter a value of zero or less if you don''t want to use swap file for all reports regarding the number of rows involved','D',0,TO_TIMESTAMP('2012-06-30 09:04:18','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2012-06-30 09:04:18','YYYY-MM-DD HH24:MI:SS'),0,100,100,'Y','REPORT_SWAP_MAX_ROWS') +; + +-- Jun 30, 2012 9:09:35 AM MYT +-- IDEMPIERE-146 Performance: Report Engine always read all data into memory +INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_SysConfig_UU,ConfigurationLevel,Value,Description,EntityType,AD_Client_ID,Created,Updated,AD_Org_ID,CreatedBy,UpdatedBy,IsActive,Name) VALUES (200011,'2f885a17-b05f-4744-a617-20e8449c22df','S','100','Max number of pages to keep in memory when rendering a jasper report','D',0,TO_TIMESTAMP('2012-06-30 09:09:34','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2012-06-30 09:09:34','YYYY-MM-DD HH24:MI:SS'),0,100,100,'Y','JASPER_REPORT_SWAP_MAX_PAGES') +; + +UPDATE AD_System + SET LastMigrationScriptApplied='848_IDEMPIERE-146.sql' +WHERE LastMigrationScriptApplied<'848_IDEMPIERE-146.sql' + OR LastMigrationScriptApplied IS NULL +; + diff --git a/org.adempiere.base/src/org/compiere/print/PrintData.java b/org.adempiere.base/src/org/compiere/print/PrintData.java index 6375e4b945..936b186ae1 100644 --- a/org.adempiere.base/src/org/compiere/print/PrintData.java +++ b/org.adempiere.base/src/org/compiere/print/PrintData.java @@ -19,6 +19,7 @@ package org.compiere.print; import java.io.File; import java.io.Serializable; import java.util.ArrayList; +import java.util.List; import java.util.Properties; import java.util.logging.Level; @@ -32,6 +33,8 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.compiere.Adempiere; +import org.compiere.print.util.SerializableMatrix; +import org.compiere.print.util.SerializableMatrixImpl; import org.compiere.util.CLogger; import org.compiere.util.DisplayType; import org.compiere.util.Trace; @@ -67,6 +70,7 @@ public class PrintData implements Serializable throw new IllegalArgumentException("Name cannot be null"); m_ctx = ctx; m_name = name; + m_matrix = new SerializableMatrixImpl(name); } // PrintData /** @@ -80,21 +84,18 @@ public class PrintData implements Serializable if (name == null) throw new IllegalArgumentException("Name cannot be null"); m_ctx = ctx; - m_name = name; + m_name = name; + m_matrix = new SerializableMatrixImpl(name); if (nodes != null) - m_nodes = nodes; + addRow(false, 0, nodes); } // PrintData + private SerializableMatrix m_matrix; + /** Context */ private Properties m_ctx; /** Data Structure Name */ private String m_name; - /** Data Structure rows */ - private ArrayList> m_rows = new ArrayList>(); - /** Current Row Data Structure elements */ - private ArrayList m_nodes = null; - /** Current Row */ - private int m_row = -1; /** List of Function Rows */ private ArrayList m_functionRows = new ArrayList(); @@ -207,7 +208,7 @@ public class PrintData implements Serializable public String toString() { StringBuffer sb = new StringBuffer("PrintData["); - sb.append(m_name).append(",Rows=").append(m_rows.size()); + sb.append(m_name).append(",Rows=").append(m_matrix.getRowCount()); if (m_TableName != null) sb.append(",TableName=").append(m_TableName); sb.append("]"); @@ -222,9 +223,7 @@ public class PrintData implements Serializable */ public boolean isEmpty() { - if (m_nodes == null) - return true; - return m_nodes.size() == 0; + return m_matrix.getRowCount() == 0 || m_matrix.getRowData().isEmpty(); } // isEmpty /** @@ -233,24 +232,27 @@ public class PrintData implements Serializable */ public int getNodeCount() { - if (m_nodes == null) + if (m_matrix.getRowCount() == 0) return 0; - return m_nodes.size(); + return m_matrix.getRowData().size(); } // getNodeCount + public void addRow (boolean functionRow, int levelNo) + { + addRow(functionRow, levelNo, new ArrayList()); + } + /************************************************************************** * Add Row * @param functionRow true if function row * @param levelNo Line detail Level Number 0=Normal */ - public void addRow (boolean functionRow, int levelNo) + public void addRow (boolean functionRow, int levelNo, List nodes) { - m_nodes = new ArrayList(); - m_row = m_rows.size(); - m_rows.add (m_nodes); + m_matrix.addRow(nodes); if (functionRow) - m_functionRows.add(new Integer(m_row)); + m_functionRows.add(new Integer(m_matrix.getRowIndex())); if (m_hasLevelNo && levelNo != 0) addNode(new PrintDataElement(LEVEL_NO, new Integer(levelNo), DisplayType.Integer, null)); } // addRow @@ -262,11 +264,7 @@ public class PrintData implements Serializable */ public boolean setRowIndex (int row) { - if (row < 0 || row >= m_rows.size()) - return false; - m_row = row; - m_nodes = m_rows.get(m_row); - return true; + return m_matrix.setRowIndex(row); } /** @@ -275,7 +273,7 @@ public class PrintData implements Serializable */ public boolean setRowNext() { - return setRowIndex(m_row+1); + return m_matrix.setRowNext(); } // setRowNext /** @@ -284,7 +282,7 @@ public class PrintData implements Serializable */ public int getRowCount() { - return m_rows.size(); + return m_matrix.getRowCount(); } // getRowCount /** @@ -293,7 +291,7 @@ public class PrintData implements Serializable */ public int getRowIndex() { - return m_row; + return m_matrix.getRowCount(); } // getRowIndex /** @@ -312,7 +310,7 @@ public class PrintData implements Serializable */ public boolean isFunctionRow () { - return m_functionRows.contains(new Integer(m_row)); + return m_functionRows.contains(new Integer(m_matrix.getRowIndex())); } // isFunctionRow /** @@ -322,11 +320,12 @@ public class PrintData implements Serializable public boolean isPageBreak () { // page break requires function and meta data - if (isFunctionRow() && m_nodes != null) + List nodes = m_matrix.getRowData(); + if (isFunctionRow() && nodes != null) { - for (int i = 0; i < m_nodes.size(); i++) + for (int i = 0; i < nodes.size(); i++) { - Object o = m_nodes.get(i); + Object o = nodes.get(i); if (o instanceof PrintDataElement) { PrintDataElement pde = (PrintDataElement)o; @@ -362,12 +361,13 @@ public class PrintData implements Serializable */ public int getLineLevelNo () { - if (m_nodes == null || !m_hasLevelNo) + List nodes = m_matrix.getRowData(); + if (nodes == null || !m_hasLevelNo) return 0; - for (int i = 0; i < m_nodes.size(); i++) + for (int i = 0; i < nodes.size(); i++) { - Object o = m_nodes.get (i); + Object o = nodes.get (i); if (o instanceof PrintDataElement) { PrintDataElement pde = (PrintDataElement)o; @@ -391,9 +391,12 @@ public class PrintData implements Serializable { if (parent == null) throw new IllegalArgumentException("Parent cannot be null"); - if (m_nodes == null) - addRow(false, 0); - m_nodes.add (parent); + List nodes = m_matrix.getRowData(); + if (nodes == null) { + nodes = new ArrayList(); + addRow(false, 0, nodes); + } + nodes.add (parent); } // addNode /** @@ -404,9 +407,12 @@ public class PrintData implements Serializable { if (node == null) throw new IllegalArgumentException("Node cannot be null"); - if (m_nodes == null) - addRow(false, 0); - m_nodes.add (node); + List nodes = m_matrix.getRowData(); + if (nodes == null) { + nodes = new ArrayList(); + addRow(false, 0, nodes); + } + nodes.add (node); } // addNode /** @@ -416,9 +422,10 @@ public class PrintData implements Serializable */ public Object getNode (int index) { - if (m_nodes == null || index < 0 || index >= m_nodes.size()) + List nodes = m_matrix.getRowData(); + if (nodes == null || index < 0 || index >= nodes.size()) return null; - return m_nodes.get(index); + return nodes.get(index); } // getNode /** @@ -431,7 +438,8 @@ public class PrintData implements Serializable int index = getIndex (name); if (index < 0) return null; - return m_nodes.get(index); + List nodes = m_matrix.getRowData(); + return nodes.get(index); } // getNode /** @@ -444,7 +452,8 @@ public class PrintData implements Serializable int index = getIndex (AD_Column_ID.intValue()); if (index < 0) return null; - return m_nodes.get(index); + List nodes = m_matrix.getRowData(); + return nodes.get(index); } // getNode /** @@ -453,11 +462,12 @@ public class PrintData implements Serializable */ public PrintDataElement getPKey() { - if (m_nodes == null) + List nodes = m_matrix.getRowData(); + if (nodes == null) return null; - for (int i = 0; i < m_nodes.size(); i++) + for (int i = 0; i < nodes.size(); i++) { - Object o = m_nodes.get(i); + Object o = nodes.get(i); if (o instanceof PrintDataElement) { PrintDataElement pde = (PrintDataElement)o; @@ -475,11 +485,12 @@ public class PrintData implements Serializable */ public int getIndex (String columnName) { - if (m_nodes == null) + List nodes = m_matrix.getRowData(); + if (nodes == null) return -1; - for (int i = 0; i < m_nodes.size(); i++) + for (int i = 0; i < nodes.size(); i++) { - Object o = m_nodes.get(i); + Object o = nodes.get(i); if (o instanceof PrintDataElement) { if (columnName.equals(((PrintDataElement)o).getColumnName())) @@ -540,7 +551,7 @@ public class PrintData implements Serializable */ public void dumpCurrentRow() { - dumpRow(this, m_row); + dumpRow(this, m_matrix.getRowIndex()); } // dump /** @@ -769,7 +780,7 @@ public class PrintData implements Serializable PrintData pdx = new PrintData(new Properties(), "test2"); pdx.addNode(new PrintDataElement("test2element1-1","testvalue11",0,null)); pdx.addNode(new PrintDataElement("test2element1-2","testvalue12",0,null)); - pdx.addRow(false, 0); + pdx.addRow(false, 0, new ArrayList()); pdx.addNode(new PrintDataElement("test2element2-1","testvalue21",0,null)); pdx.addNode(new PrintDataElement("test2element2-2","testvalue22",0,null)); diff --git a/org.adempiere.base/src/org/compiere/print/layout/Dimension2DImpl.java b/org.adempiere.base/src/org/compiere/print/layout/Dimension2DImpl.java index 6f1da401cd..2743557724 100644 --- a/org.adempiere.base/src/org/compiere/print/layout/Dimension2DImpl.java +++ b/org.adempiere.base/src/org/compiere/print/layout/Dimension2DImpl.java @@ -18,6 +18,7 @@ package org.compiere.print.layout; import java.awt.Dimension; import java.awt.geom.Dimension2D; +import java.io.Serializable; /** * 2D Dimesnion Implementation @@ -25,8 +26,13 @@ import java.awt.geom.Dimension2D; * @author Jorg Janke * @version $Id: Dimension2DImpl.java,v 1.3 2006/07/30 00:53:02 jjanke Exp $ */ -public class Dimension2DImpl extends Dimension2D +public class Dimension2DImpl extends Dimension2D implements Serializable { + /** + * generated serial id + */ + private static final long serialVersionUID = -6718670551461826020L; + /** * Constructor 0/0 */ diff --git a/org.adempiere.base/src/org/compiere/print/layout/HTMLRenderer.java b/org.adempiere.base/src/org/compiere/print/layout/HTMLRenderer.java index aa2c1fe9e9..e6e984f7d4 100644 --- a/org.adempiere.base/src/org/compiere/print/layout/HTMLRenderer.java +++ b/org.adempiere.base/src/org/compiere/print/layout/HTMLRenderer.java @@ -19,6 +19,12 @@ package org.compiere.print.layout; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.Shape; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectInputStream; +import java.io.ObjectOutput; +import java.io.ObjectOutputStream; import java.io.Reader; import java.io.StringReader; import java.util.logging.Level; @@ -41,8 +47,13 @@ import org.compiere.util.CLogger; * @author Jorg Janke * @version $Id: HTMLRenderer.java,v 1.3 2006/07/30 00:53:02 jjanke Exp $ */ -public class HTMLRenderer extends View +public class HTMLRenderer extends View implements Externalizable { + /** + * generated serial id + */ + private static final long serialVersionUID = 7180048200607805705L; + /** * Get View from HTML String * @param html html string @@ -74,6 +85,10 @@ public class HTMLRenderer extends View /** Logger */ private static CLogger log = CLogger.getCLogger(HTMLRenderer.class); + public HTMLRenderer() { + super(null); + } + /************************************************************************** * Constructor * @param f factory @@ -85,14 +100,18 @@ public class HTMLRenderer extends View m_factory = f; m_view = v; m_view.setParent(this); + m_element = m_view.getElement(); // initially layout to the preferred size setSize(m_view.getPreferredSpan(X_AXIS), m_view.getPreferredSpan(Y_AXIS)); } // HTMLRenderer private int m_width; - private View m_view; - private ViewFactory m_factory; + private View m_view; + private ViewFactory m_factory; + private Element m_element; private Rectangle m_allocation; + private float m_viewWidth; + private float m_viewHeight; /** @@ -378,6 +397,8 @@ public class HTMLRenderer extends View public void setSize(float width, float height) { this.m_width = (int) width; + this.m_viewWidth = width; + this.m_viewHeight = height; m_view.setSize(width, height); } @@ -396,4 +417,25 @@ public class HTMLRenderer extends View return m_factory; } + @Override + public void writeExternal(ObjectOutput out) throws IOException { + out.writeObject(m_element); + out.writeObject(m_allocation); + out.writeFloat(m_viewWidth); + out.writeFloat(m_viewHeight); + } + + @Override + public void readExternal(ObjectInput in) throws IOException, + ClassNotFoundException { + m_element = (Element) in.readObject(); + m_allocation = (Rectangle) in.readObject(); + HTMLEditorKit kit = new HTMLEditorKit(); + m_factory = kit.getViewFactory(); + m_view = m_factory.create(m_element); + m_view.setParent(this); + float width = in.readFloat(); + float height = in.readFloat(); + setSize(width, height); + } } // HTMLRenderer diff --git a/org.adempiere.base/src/org/compiere/print/layout/LayoutEngine.java b/org.adempiere.base/src/org/compiere/print/layout/LayoutEngine.java index 958d2ec01a..8465a7ff84 100644 --- a/org.adempiere.base/src/org/compiere/print/layout/LayoutEngine.java +++ b/org.adempiere.base/src/org/compiere/print/layout/LayoutEngine.java @@ -34,6 +34,7 @@ import java.awt.print.PrinterJob; import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.io.Serializable; import java.net.URL; import java.sql.Timestamp; import java.util.ArrayList; @@ -61,6 +62,8 @@ import org.compiere.print.MPrintPaper; import org.compiere.print.MPrintTableFormat; import org.compiere.print.PrintData; import org.compiere.print.PrintDataElement; +import org.compiere.print.util.SerializableMatrix; +import org.compiere.print.util.SerializableMatrixImpl; import org.compiere.util.CLogger; import org.compiere.util.DB; import org.compiere.util.DisplayType; @@ -1647,8 +1650,7 @@ public class LayoutEngine implements Pageable, Printable, Doc // The Data int rows = printData.getRowCount(); - // System.out.println("Rows=" + rows); - Object[][] data = new Object [rows][columnCount]; + SerializableMatrix elements = new SerializableMatrixImpl(m_PrintInfo.getName()); KeyNamePair[] pk = new KeyNamePair[rows]; String pkColumnName = null; ArrayList functionRows = new ArrayList(); @@ -1657,7 +1659,7 @@ public class LayoutEngine implements Pageable, Printable, Doc // for all rows for (int row = 0; row < rows; row++) { - // System.out.println("row=" + row); + ArrayList columns = new ArrayList(); printData.setRowIndex(row); if (printData.isFunctionRow()) { @@ -1690,33 +1692,33 @@ public class LayoutEngine implements Pageable, Printable, Doc } } // for all columns - col = 0; for (int c = 0; c < format.getItemCount(); c++) { + Serializable columnElement = null; MPrintFormatItem item = format.getItem(c); - Object dataElement = null; + Serializable dataElement = null; if (item.isPrinted()) // Text Columns { if (item.isTypeImage()) { if (item.isImageField()) - data[row][col] = createImageElement (item, printData); + columnElement = createImageElement (item, printData); else if (item.isImageIsAttached()) - data[row][col] = ImageElement.get (item.get_ID()); + columnElement = ImageElement.get (item.get_ID()); else - data[row][col] = ImageElement.get (item.getImageURL()); - if (data[row][col] != null) - ((PrintElement)data[row][col]).layout(item.getMaxWidth(), item.getMaxHeight(), false, item.getFieldAlignmentType()); + columnElement = ImageElement.get (item.getImageURL()); + if (columnElement != null) + ((PrintElement)columnElement).layout(item.getMaxWidth(), item.getMaxHeight(), false, item.getFieldAlignmentType()); } else if (item.isBarcode()) { - data[row][col] = createBarcodeElement(item, printData); - if (data[row][col] != null) - ((PrintElement)data[row][col]).layout(item.getMaxWidth(), item.getMaxHeight(), false, item.getFieldAlignmentType()); + columnElement = createBarcodeElement(item, printData); + if (columnElement != null) + ((PrintElement)columnElement).layout(item.getMaxWidth(), item.getMaxHeight(), false, item.getFieldAlignmentType()); } else if (item.isTypeText() ) { - data[row][col] = item.getPrintName(format.getLanguage()); + columnElement = item.getPrintName(format.getLanguage()); } else if (item.isTypeField()) { @@ -1729,22 +1731,22 @@ public class LayoutEngine implements Pageable, Printable, Doc { PrintDataElement pde = (PrintDataElement)obj; if (pde.isID() || pde.isYesNo()) - dataElement = pde.getValue(); + dataElement = (Serializable) pde.getValue(); else dataElement = pde.getValueDisplay(format.getLanguage()); - } + } else log.log(Level.SEVERE, "Element not PrintDataElement " + obj.getClass()); - // System.out.println(" row=" + row + ",col=" + col + " - " + item.getAD_Column_ID() + " => " + dataElement); - data[row][col] = dataElement; + columnElement = dataElement; } else // item.isTypeBox() or isTypePrintFormat() { log.warning("Unsupported: " + (item.isTypeBox() ? "Box" : "PrintFormat") + " in Table: " + item); } - col++; + columns.add(columnElement); } // printed } // for all columns + elements.addRow(columns); PrintDataElement pde = printData.getPKey(); if (pde != null) // for FunctionRows @@ -1761,7 +1763,7 @@ public class LayoutEngine implements Pageable, Printable, Doc TableElement table = new TableElement(columnHeader, columnMaxWidth, columnMaxHeight, columnJustification, fixedWidth, functionRows, multiLineHeader, - data, pk, pkColumnName, + elements, pk, pkColumnName, pageNoStart, firstPage, nextPages, repeatedColumns, additionalLines, rowColFont, rowColColor, rowColBackground, tf, pageBreak, colSuppressRepeats); diff --git a/org.adempiere.base/src/org/compiere/print/layout/PrintElement.java b/org.adempiere.base/src/org/compiere/print/layout/PrintElement.java index 2e4653a74b..a36575b17e 100644 --- a/org.adempiere.base/src/org/compiere/print/layout/PrintElement.java +++ b/org.adempiere.base/src/org/compiere/print/layout/PrintElement.java @@ -24,6 +24,7 @@ import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.geom.Point2D; import java.awt.image.ImageObserver; +import java.io.Serializable; import java.util.Properties; import java.util.logging.Level; @@ -38,8 +39,14 @@ import org.compiere.util.CLogger; * @author Jorg Janke * @version $Id: PrintElement.java,v 1.2 2006/07/30 00:53:02 jjanke Exp $ */ -public abstract class PrintElement implements ImageObserver +public abstract class PrintElement implements ImageObserver, Serializable { + /** + * generated serial id + */ + private static final long serialVersionUID = 5894090289966933777L; + + /** * Constructor */ diff --git a/org.adempiere.base/src/org/compiere/print/layout/TableElement.java b/org.adempiere.base/src/org/compiere/print/layout/TableElement.java index 9bf64fd2b0..c5d7799320 100644 --- a/org.adempiere.base/src/org/compiere/print/layout/TableElement.java +++ b/org.adempiere.base/src/org/compiere/print/layout/TableElement.java @@ -30,11 +30,13 @@ import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; +import java.io.Serializable; import java.text.AttributedCharacterIterator; import java.text.AttributedString; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Properties; import java.util.logging.Level; import java.util.regex.Pattern; @@ -45,6 +47,8 @@ import net.sourceforge.barbecue.output.OutputException; import org.compiere.model.MQuery; import org.compiere.print.MPrintFormatItem; import org.compiere.print.MPrintTableFormat; +import org.compiere.print.util.SerializableMatrix; +import org.compiere.print.util.SerializableMatrixImpl; import org.compiere.util.KeyNamePair; import org.compiere.util.NamePair; import org.compiere.util.Util; @@ -111,13 +115,16 @@ public class TableElement extends PrintElement public TableElement (ValueNamePair[] columnHeader, int[] columnMaxWidth, int[] columnMaxHeight, String[] columnJustification, boolean[] fixedWidth, ArrayList functionRows, boolean multiLineHeader, - Object[][] data, KeyNamePair[] pk, String pkColumnName, + SerializableMatrix data, KeyNamePair[] pk, String pkColumnName, int pageNoStart, Rectangle firstPage, Rectangle nextPages, int repeatedColumns, HashMap additionalLines, HashMap rowColFont, HashMap rowColColor, HashMap rowColBackground, MPrintTableFormat tFormat, ArrayList pageBreak, boolean[] colSuppressRepeats) { super(); - log.fine("Cols=" + columnHeader.length + ", Rows=" + data.length); + if (log.isLoggable(Level.FINE)) + { + log.fine("Cols=" + columnHeader.length + ", Rows=" + data.getRowCount()); + } m_colSuppressRepeats = colSuppressRepeats; m_columnHeader = columnHeader; m_columnMaxWidth = columnMaxWidth; @@ -191,7 +198,7 @@ public class TableElement extends PrintElement /** List of Function Rows */ private ArrayList m_functionRows; /** The Data */ - private Object[][] m_data; + private SerializableMatrix m_data; /** Primary Keys */ private KeyNamePair[] m_pk; /** Primary Key Column Name */ @@ -274,16 +281,16 @@ public class TableElement extends PrintElement return true; p_width = 0; - m_printRows = new ArrayList>>(m_data.length); // reset + m_printRows = new SerializableMatrixImpl>("TableElementPrintRows"); // reset // Max Column Width = 50% of available width (used if maxWidth not set) float dynMxColumnWidth = m_firstPage.width / 2; - // Width caolculation - int rows = m_data.length; + // Width calculation + int rows = m_data.getRowCount(); int cols = m_columnHeader.length; // Data Sizes and Header Sizes - Dimension2DImpl[][] dataSizes = new Dimension2DImpl[rows][cols]; + SerializableMatrix dataSizes = new SerializableMatrixImpl("TableElementDimensions"); Dimension2DImpl[] headerSizes = new Dimension2DImpl[cols]; FontRenderContext frc = new FontRenderContext(null, true, true); @@ -300,48 +307,62 @@ public class TableElement extends PrintElement float colWidth = 0; for (int row = 0; row < rows; row++) { - Object dataItem = m_data[row][dataCol]; + m_data.setRowIndex(row); + if (dataSizes.getRowCount() <= row) + { + dataSizes.addRow(new ArrayList()); + } + else + { + dataSizes.setRowIndex(row); + } + List dimensions = dataSizes.getRowData(); + if (dimensions.size() <= dataCol) + { + dimensions.add(null); + } + Serializable dataItem = m_data.getRowData().get(dataCol); if (dataItem == null) { - dataSizes[row][dataCol] = new Dimension2DImpl(); + dimensions.set(dataCol, new Dimension2DImpl()); continue; } String string = dataItem.toString(); if (string.length() == 0) { - dataSizes[row][dataCol] = new Dimension2DImpl(); + dimensions.set(dataCol, new Dimension2DImpl()); continue; } Font font = getFont(row, dataCol); // Print below existing column = (col != dataCol) addPrintLines(row, col, dataItem); - dataSizes[row][dataCol] = new Dimension2DImpl(); // don't print + dimensions.set(dataCol, new Dimension2DImpl()); if (dataItem instanceof Boolean) { - dataSizes[row][col].addBelow(LayoutEngine.IMAGE_SIZE); + dimensions.get(col).addBelow(LayoutEngine.IMAGE_SIZE); continue; } else if (dataItem instanceof ImageElement) { - dataSizes[row][col].addBelow( + dimensions.get(col).addBelow( new Dimension((int)((ImageElement)dataItem).getWidth(), (int)((ImageElement)dataItem).getHeight())); // Adjust the column width - teo_sarca, [ 1673620 ] - float width = (float)Math.ceil(dataSizes[row][col].getWidth()); + float width = (float)Math.ceil(dimensions.get(col).getWidth()); if (colWidth < width) colWidth = width; continue; } else if (dataItem instanceof BarcodeElement) { - dataSizes[row][col].addBelow( + dimensions.get(col).addBelow( new Dimension((int)((BarcodeElement)dataItem).getWidth(), (int)((BarcodeElement)dataItem).getHeight())); // Check if the overflow is allowed - teo_sarca, [ 1673590 ] if (!((BarcodeElement)dataItem).isAllowOverflow()) { - float width = (float)Math.ceil(dataSizes[row][col].getWidth()); + float width = (float)Math.ceil(dimensions.get(col).getWidth()); if (colWidth < width) colWidth = width; } @@ -362,12 +383,12 @@ public class TableElement extends PrintElement m_columnMaxWidth[col] = (int)Math.ceil(dynMxColumnWidth); else if (colWidth < width) colWidth = width; - if (dataSizes[row][col] == null) + if (dimensions.get(col) == null) { - dataSizes[row][col] = new Dimension2DImpl(); + dimensions.set(col, new Dimension2DImpl()); log.log(Level.WARNING, "No Size for r=" + row + ",c=" + col); } - dataSizes[row][col].addBelow(width, height); + dimensions.get(col).addBelow(width, height); } // Width limitations if (m_columnMaxWidth[col] != 0 && m_columnMaxWidth[col] != -1) @@ -385,7 +406,7 @@ public class TableElement extends PrintElement height = renderer.getHeight(); renderer.setAllocation((int)colWidth, (int)height); // log.finest( "calculateSize HTML - " + renderer.getAllocation()); - m_data[row][dataCol] = renderer; // replace for printing + m_data.getRowData().set(dataCol, renderer); } else { @@ -415,14 +436,17 @@ public class TableElement extends PrintElement } if (m_fixedWidth[col]) colWidth = Math.abs(m_columnMaxWidth[col]); - dataSizes[row][col].addBelow(colWidth, height); + dimensions.get(col).addBelow(colWidth, height); } - dataSizes[row][col].roundUp(); + dimensions.get(col).roundUp(); if (dataItem instanceof NamePair) m_rowColDrillDown.put(new Point(row, col), (NamePair)dataItem); // - log.finest("Col=" + col + ", row=" + row - + " => " + dataSizes[row][col] + " - ColWidth=" + colWidth); + if (log.isLoggable(Level.FINEST)) + { + log.finest("Col=" + col + ", row=" + row + + " => " + dimensions.get(col) + " - ColWidth=" + colWidth); + } } // for all data rows // Column Width for Header @@ -532,10 +556,12 @@ public class TableElement extends PrintElement for (int row = 0; row < rows; row++) { float rowHeight = 0f; + dataSizes.setRowIndex(row); + List dimensions = dataSizes.getRowData(); for (int col = 0; col < cols; col++) { - if (dataSizes[row][col].height > rowHeight) // max - rowHeight = (float)dataSizes[row][col].height; + if (dimensions.get(col).height > rowHeight) // max + rowHeight = (float)dimensions.get(col).height; } // for all columns rowHeight += m_tFormat.getLineStroke().floatValue() + (2*V_GAP); m_rowHeights.add(new Float(rowHeight)); @@ -1080,7 +1106,7 @@ public class TableElement extends PrintElement return -1; // above // int firstRow = ((Integer)m_firstRowOnPage.get(pageYindex)).intValue(); - int nextPageRow = m_data.length; // no of rows + int nextPageRow = m_data.getRowCount(); // no of rows if (pageYindex+1 < m_firstRowOnPage.size()) nextPageRow = ((Integer)m_firstRowOnPage.get(pageYindex+1)).intValue(); // @@ -1162,7 +1188,7 @@ public class TableElement extends PrintElement nextPageColumn = ((Integer)m_firstColumnOnPage.get(pageXindex+1)).intValue(); // int firstRow = ((Integer)m_firstRowOnPage.get(pageYindex)).intValue(); - int nextPageRow = m_data.length; // no of rows + int nextPageRow = m_data.getRowCount(); // no of rows if (pageYindex+1 < m_firstRowOnPage.size()) nextPageRow = ((Integer)m_firstRowOnPage.get(pageYindex+1)).intValue(); if (DEBUG_PRINT) @@ -1557,7 +1583,7 @@ public class TableElement extends PrintElement curX += m_tFormat.getVLineStroke().floatValue(); // X end line - if (row == m_data.length-1) // last Line + if (row == m_data.getRowCount()-1) // last Line { /** * Bug fix - Bottom line was always displayed whether or not header lines was set to be visible @@ -1608,30 +1634,34 @@ public class TableElement extends PrintElement * @param col col * @param data data */ - private void addPrintLines (int row, int col, Object data) + private void addPrintLines (int row, int col, Serializable data) { - while (m_printRows.size() <= row) - m_printRows.add(null); - ArrayList> columns = m_printRows.get(row); + while (m_printRows.getRowCount() <= row) + m_printRows.addRow(null); + m_printRows.setRowIndex(row); + List> columns = m_printRows.getRowData(); if (columns == null) - columns = new ArrayList>(m_columnHeader.length); + columns = new ArrayList>(m_columnHeader.length); while (columns.size() <= col) columns.add(null); // - ArrayList coordinate = columns.get(col); + ArrayList coordinate = columns.get(col); if (coordinate == null) - coordinate = new ArrayList(); + coordinate = new ArrayList(); coordinate.add(data); // columns.set(col, coordinate); - m_printRows.set(row, columns); - log.finest("row=" + row + ", col=" + col - + " - Rows=" + m_printRows.size() + ", Cols=" + columns.size() - + " - " + data); + m_printRows.setRowData(columns); + if (log.isLoggable(Level.FINEST)) + { + log.finest("row=" + row + ", col=" + col + + " - Rows=" + m_printRows.getRowCount() + ", Cols=" + columns.size() + + " - " + data); + } } // addAdditionalLines /** Print Data */ - private ArrayList>> m_printRows = new ArrayList>>(); + private SerializableMatrix> m_printRows = new SerializableMatrixImpl>("PrintRows"); /** * Insert empty Row after current Row @@ -1651,12 +1681,15 @@ public class TableElement extends PrintElement */ private Object[] getPrintItems (int row, int col) { - ArrayList> columns = null; - if (m_printRows.size() > row) - columns = m_printRows.get(row); + List> columns = null; + if (m_printRows.getRowCount() > row) + { + m_printRows.setRowIndex(row); + columns = m_printRows.getRowData(); + } if (columns == null) return new Object[]{}; - ArrayList coordinate = null; + ArrayList coordinate = null; if (columns.size() > col) coordinate = columns.get(col); if (coordinate == null) diff --git a/org.adempiere.base/src/org/compiere/print/util/SerializableMatrix.java b/org.adempiere.base/src/org/compiere/print/util/SerializableMatrix.java new file mode 100644 index 0000000000..147ef7a651 --- /dev/null +++ b/org.adempiere.base/src/org/compiere/print/util/SerializableMatrix.java @@ -0,0 +1,46 @@ +/****************************************************************************** + * Copyright (C) 2012 Heng Sin Low * + * 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.print.util; + +import java.io.Serializable; +import java.util.List; + +public interface SerializableMatrix { + + public void addRow(List data); + + public boolean setRowIndex(int row); + + /** + * Set Row Index to next + * @return true if success + */ + public boolean setRowNext(); // setRowNext + + /** + * Get Row Count + * @return row count + */ + public int getRowCount(); // getRowCount + + /** + * Get Current Row Index + * @return row index + */ + public int getRowIndex(); // getRowIndex + + public List getRowData(); + + public void setRowData(List data); +} \ No newline at end of file diff --git a/org.adempiere.base/src/org/compiere/print/util/SerializableMatrixImpl.java b/org.adempiere.base/src/org/compiere/print/util/SerializableMatrixImpl.java new file mode 100644 index 0000000000..83cc63b166 --- /dev/null +++ b/org.adempiere.base/src/org/compiere/print/util/SerializableMatrixImpl.java @@ -0,0 +1,250 @@ +/****************************************************************************** + * Copyright (C) 2012 Heng Sin Low * + * 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.print.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; + +import org.compiere.model.MSysConfig; +import org.compiere.util.CLogger; + +/** + * + * @author hengsin + * + */ +public class SerializableMatrixImpl implements SerializableMatrix { + /** default 4k block size **/ + private static final int DEFAULT_BLOCK_SIZE = 4*1024; + + /** Default to start swapping after 2k row **/ + private static final int DEFAULT_SWAP_MAX_ROWS = 2000; + + /** set to 0 or smaller to disable swap file usage **/ + private static final String REPORT_SWAP_MAX_ROWS = "REPORT_SWAP_MAX_ROWS"; + + private static final CLogger log = CLogger.getCLogger(SerializableMatrixImpl.class); + + /** Data Structure rows */ + private ArrayList> m_rows = new ArrayList>(); + /** Current Row */ + private int m_currentRow = -1; + + private int m_pageSize = 0; + + private int m_size = 0; + + private Page currentPage = null; + + private List segments = new ArrayList(); + + private List pages = new ArrayList(); + + private SwapFile swapFile; + private String prefix; + + public SerializableMatrixImpl(String name) { + this.prefix = name; + int pageSize = MSysConfig.getIntValue(REPORT_SWAP_MAX_ROWS, DEFAULT_SWAP_MAX_ROWS); + if (pageSize <= 0) { + m_pageSize = Integer.MAX_VALUE; + } else { + m_pageSize = pageSize; + } + } + + /* (non-Javadoc) + * @see org.compiere.print.util.SerializableDataTable#addRow(java.util.ArrayList) + */ + @Override + public void addRow(List data) { + m_size++; + if (currentPage == null) { + currentPage = new Page(); + pages.add(currentPage); + currentPage.first = 0; + currentPage.last = 0; + currentPage.size = 1; + currentPage.pageNo = pages.size() - 1; + + m_rows.add(data); + m_currentRow = 0; + } else { + Page lastPage = pages.get(pages.size() - 1); + if (lastPage.size == m_pageSize) { + pageout(currentPage); + currentPage = new Page(); + pages.add(currentPage); + currentPage.pageNo = pages.size() - 1; + currentPage.first = lastPage.last + 1; + currentPage.last = currentPage.first; + currentPage.size = 1; + + m_rows = new ArrayList>(); + m_rows.add(data); + m_currentRow = currentPage.first; + } else { + if (currentPage.pageNo != lastPage.pageNo) { + pageout(currentPage); + pagein(lastPage.pageNo); + } + m_rows.add(data); + currentPage.last++; + currentPage.size++; + m_currentRow = currentPage.last; + } + } + } + + /* (non-Javadoc) + * @see org.compiere.print.util.SerializableDataTable#setRowIndex(int) + */ + @Override + public boolean setRowIndex (int row) { + if (row < 0 || row >= m_size) + return false; + + if (row >= currentPage.first && row <= currentPage.last) { + m_currentRow = row; + return true; + } else { + Page tmp = currentPage; + for(Page page : pages) { + if (row >= page.first && row <= page.last) { + currentPage = page; + pageout(tmp); + pagein(currentPage.pageNo); + m_currentRow = row; + return true; + } + } + return false; + } + } + + /* (non-Javadoc) + * @see org.compiere.print.util.SerializableDataTable#setRowNext() + */ + @Override + public boolean setRowNext() + { + return setRowIndex(m_currentRow+1); + } // setRowNext + + /* (non-Javadoc) + * @see org.compiere.print.util.SerializableDataTable#getRowCount() + */ + @Override + public int getRowCount() + { + return m_size; + } // getRowCount + + /* (non-Javadoc) + * @see org.compiere.print.util.SerializableDataTable#getRowIndex() + */ + @Override + public int getRowIndex() + { + return m_currentRow; + } // getRowIndex + + /* (non-Javadoc) + * @see org.compiere.print.util.SerializableDataTable#getRowData() + */ + @Override + public List getRowData() + { + return m_rows.isEmpty() ? null : m_rows.get(m_currentRow - currentPage.first); + } + + @Override + public void setRowData(List data) { + if (currentPage != null) { + int index = m_currentRow - currentPage.first; + if (index < m_rows.size()) { + m_rows.set(index, data); + } + } + } + + private void pageout(Page currentPage) { + ByteArrayOutputStream bas = new ByteArrayOutputStream(); + try { + ObjectOutputStream ous = new ObjectOutputStream(bas); + ous.writeObject(m_rows); + if (swapFile == null) { + swapFile = new SwapFile(makePrefix(prefix), DEFAULT_BLOCK_SIZE, 2); + } + swapFile.open(); + SwapFileSegment segment = swapFile.write(bas.toByteArray()); + if (currentPage.pageNo < segments.size()) + segments.set(currentPage.pageNo, segment); + else + segments.add(segment); + } catch (IOException e) { + log.log(Level.SEVERE, e.getLocalizedMessage(), e); + } finally { + if (swapFile != null) + swapFile.close(); + } + } + + private void pagein(int index) { + SwapFileSegment segment = segments.get(index); + try { + swapFile.open(); + byte[] data = swapFile.read(segment); + swapFile.free(segment); + ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data)); + ArrayList> rows = (ArrayList>) ois.readObject(); + this.m_rows = rows; + currentPage = pages.get(index); + m_currentRow = currentPage.first; + } catch (IOException e) { + log.log(Level.SEVERE, e.getLocalizedMessage(), e); + } catch (ClassNotFoundException e) { + log.log(Level.SEVERE, e.getLocalizedMessage(), e); + } finally { + swapFile.close(); + } + } + + private String makePrefix(String name) { + StringBuffer prefix = new StringBuffer(); + char[] nameArray = name.toCharArray(); + for (char ch : nameArray) { + if (Character.isLetterOrDigit(ch)) { + prefix.append(ch); + } else { + prefix.append("_"); + } + } + return prefix.toString(); + } + + class Page { + protected int pageNo; + protected int first; + protected int last; + protected int size; + } +} diff --git a/org.adempiere.base/src/org/compiere/print/util/SwapFile.java b/org.adempiere.base/src/org/compiere/print/util/SwapFile.java new file mode 100644 index 0000000000..51c0764980 --- /dev/null +++ b/org.adempiere.base/src/org/compiere/print/util/SwapFile.java @@ -0,0 +1,260 @@ +/****************************************************************************** + * Copyright (C) 2012 Heng Sin Low * + * 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.print.util; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.util.LinkedList; +import java.util.logging.Level; + +import org.compiere.util.CLogger; + +/** + * + * @author hengsin + * + */ +public class SwapFile +{ + private static final CLogger log = CLogger.getCLogger(SwapFile.class); + + private final File swapFile; + private RandomAccessFile randomAccessFile; + private final int blockSize; + private final int minBlockToGrow; + private final LinkedList freeBlocks; + + + /** + * Creates a swap file. + * + * The file name is generated automatically. + * + * @param prefix the swap file prefix + * @param blockSize the size of the blocks allocated by the swap file + * @param minBlockToGrow the minimum number of blocks by which the swap file grows when full + */ + public SwapFile(String prefix, int blockSize, int minBlockToGrow) + { + try + { + swapFile = File.createTempFile(prefix, ".swap"); + if (log.isLoggable(Level.INFO)) + { + log.info("Creating swap file " + swapFile.getPath()); + } + swapFile.deleteOnExit(); + + this.blockSize = blockSize; + this.minBlockToGrow = minBlockToGrow; + freeBlocks = new LinkedList(); + } + catch (FileNotFoundException e) + { + throw new RuntimeException(e); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + /** + * open for read write + */ + public synchronized void open() { + try { + randomAccessFile = new RandomAccessFile(swapFile, "rw"); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * close and release file handle + */ + public synchronized void close() { + if (randomAccessFile != null) { + try { + randomAccessFile.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + /** + * Allocates an area in the swap file and writes data in it. + * + * @param data the data for which to allocate an area in the file + * @return a handle to the allocated area + * @throws IOException + */ + public SwapFileSegment write(byte[] data) throws IOException + { + verifyOpen(); + int blockCount = (data.length - 1) / blockSize + 1; + long[] offsets = allocateFreeBlocks(blockCount); + int lastBlockSize = (data.length - 1) % blockSize + 1; + SwapFileSegment segment = new SwapFileSegment(offsets, lastBlockSize); + for (int i = 0; i < blockCount; ++i) + { + int dataSize = i < blockCount - 1 ? blockSize : lastBlockSize; + int dataOffset = i * blockSize; + write(data, dataSize, dataOffset, offsets[i]); + } + + return segment; + } + + + private synchronized void write(byte[] data, int dataSize, int dataOffset, long fileOffset) throws IOException + { + randomAccessFile.seek(fileOffset); + randomAccessFile.write(data, dataOffset, dataSize); + } + + + /** + * Reads all the data from an allocated area. + * + * @param segment the allocated segment + * @return the whole data saved in an allocated area + * @throws IOException + */ + public byte[] read(SwapFileSegment segment) throws IOException + { + verifyOpen(); + long[] offsets = segment.getOffsets(); + int totalLength = (offsets.length - 1) * blockSize + segment.getLastBlockSize(); + byte[] data = new byte[totalLength]; + + for (int i = 0; i < offsets.length; ++i) + { + int dataOffset = i * blockSize; + int dataLength = i < offsets.length - 1 ? blockSize : segment.getLastBlockSize(); + read(data, dataOffset, dataLength, offsets[i]); + } + + return data; + } + + + private synchronized void read(byte[] data, int dataOffset, int dataLength, long fileOffset) throws IOException + { + randomAccessFile.seek(fileOffset); + randomAccessFile.readFully(data, dataOffset, dataLength); + } + + + /** + * Frees an allocated area. + * + * @param segment the allocated segment + */ + public void free(SwapFileSegment segment) + { + verifyOpen(); + freeBlocks(segment.getOffsets()); + } + + private void verifyOpen() { + if (randomAccessFile == null) { + throw new RuntimeException("Swap file not open for read write access"); + } + } + + + /** + * Closes and deletes the swap file. + */ + public void dispose() + { + synchronized (this) + { + if (swapFile.exists()) + { + if (log.isLoggable(Level.INFO)) + { + log.info("Disposing swap file " + swapFile.getPath()); + } + + try + { + close(); + } + catch (Exception e) + { + log.warning("Not able to close swap file " + swapFile.getPath()); + } + + if (!swapFile.delete()) + { + log.warning("Not able to delete swap file " + swapFile.getPath()); + } + } + } + } + + + protected void finalize() throws Throwable //NOSONAR + { + dispose(); + super.finalize(); + } + + + private synchronized long[] allocateFreeBlocks(int blockCount) throws IOException + { + int growCount = blockCount - freeBlocks.size(); + if (growCount > 0) + { + if (growCount < minBlockToGrow) + { + growCount = minBlockToGrow; + } + + long length = randomAccessFile.length(); + long newLength = length + growCount * blockSize; + if (log.isLoggable(Level.INFO)) + { + log.info("Growing swap file " + swapFile.getPath() + " with " + growCount + " blocks x " + blockSize + " bytes to size " + newLength); + } + randomAccessFile.setLength(newLength); + + for (int i = 0; i < growCount; ++i) + { + freeBlocks.addLast(length + i * blockSize); + } + } + + long[] offsets = new long[blockCount]; + for (int i = 0; i < blockCount; i++) + { + offsets[i] = freeBlocks.pollFirst(); + } + return offsets; + } + + + private synchronized void freeBlocks(long []offsets) + { + for (int i = offsets.length - 1; i >= 0; --i) + { + freeBlocks.addFirst(offsets[i]); + } + } +} diff --git a/org.adempiere.base/src/org/compiere/print/util/SwapFileSegment.java b/org.adempiere.base/src/org/compiere/print/util/SwapFileSegment.java new file mode 100644 index 0000000000..004a60b22f --- /dev/null +++ b/org.adempiere.base/src/org/compiere/print/util/SwapFileSegment.java @@ -0,0 +1,40 @@ +/****************************************************************************** + * Copyright (C) 2012 Heng Sin Low * + * 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.print.util; + +/** + * + * @author hengsin + * + */ +public class SwapFileSegment { + private final long[] offsets; + private final int lastBlockSize; + + public SwapFileSegment(long[] offsets, int lastSize) + { + this.offsets = offsets; + this.lastBlockSize = lastSize; + } + + public long[] getOffsets() + { + return offsets; + } + + public int getLastBlockSize() + { + return lastBlockSize; + } +} diff --git a/org.adempiere.report.jasper/src/org/compiere/report/ReportStarter.java b/org.adempiere.report.jasper/src/org/compiere/report/ReportStarter.java index b7fe688de5..a92b5cdc8f 100644 --- a/org.adempiere.report.jasper/src/org/compiere/report/ReportStarter.java +++ b/org.adempiere.report.jasper/src/org/compiere/report/ReportStarter.java @@ -69,6 +69,7 @@ import org.adempiere.util.IProcessUI; import org.compiere.model.MAttachment; import org.compiere.model.MAttachmentEntry; import org.compiere.model.MProcess; +import org.compiere.model.MSysConfig; import org.compiere.model.PrintInfo; import org.compiere.model.X_AD_PInstance_Para; import org.compiere.print.MPrintFormat; @@ -103,6 +104,8 @@ import org.compiere.utils.DigestOfFile; */ public class ReportStarter implements ProcessCall, ClientProcess { + private static final int DEFAULT_SWAP_MAX_PAGES = 100; + private static final String JASPER_SWAP_MAX_PAGES = "JASPER_REPORT_SWAP_MAX_PAGES"; /** Logger */ private static CLogger log = CLogger.getCLogger(ReportStarter.class); private static File REPORT_HOME = null; @@ -515,12 +518,14 @@ public class ReportStarter implements ProcessCall, ClientProcess } Connection conn = null; + JRSwapFileVirtualizer virtualizer = null; + int maxPages = MSysConfig.getIntValue(JASPER_SWAP_MAX_PAGES, DEFAULT_SWAP_MAX_PAGES); try { conn = getConnection(); String swapPath = System.getProperty("java.io.tmpdir"); JRSwapFile swapFile = new JRSwapFile(swapPath, 1024, 1024); - JRSwapFileVirtualizer virtualizer = new JRSwapFileVirtualizer(2, swapFile, true); + virtualizer = new JRSwapFileVirtualizer(maxPages, swapFile, true); params.put(JRParameter.REPORT_VIRTUALIZER, virtualizer); JRProperties.setProperty("net.sf.jasperreports.awt.ignore.missing.font", true); @@ -593,11 +598,15 @@ public class ReportStarter implements ProcessCall, ClientProcess } catch (JRException e) { log.severe("ReportStarter.startProcess: Can not run report - "+ e.getMessage()); } finally { - if (conn != null) + if (conn != null) { try { conn.close(); } catch (SQLException e) { } + } + if (virtualizer != null) { + virtualizer.cleanup(); + } } }