=== modified file 'src/com/goldencode/p2j/net/Protocol.java'
--- old/src/com/goldencode/p2j/net/Protocol.java	2021-03-25 18:19:27 +0000
+++ new/src/com/goldencode/p2j/net/Protocol.java	2022-06-09 14:31:31 +0000
@@ -2,7 +2,7 @@
 ** Module   : Protocol.java
 ** Abstract : low level driver of the P2J protocol
 **
-** Copyright (c) 2005-2021, Golden Code Development Corporation.
+** Copyright (c) 2005-2022, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
 ** 001 SIY 20050120   @19501 Created initial version.
@@ -76,6 +76,7 @@
 ** 026 IAS 20200930          Added JMX counters.
 ** 027 IAS 20201001          Ged rid of byte arrays allocations in objToByteArray().
 **     IAS 20210325          Added SSLException catch
+**     TJD 20220607          Writer/ByteArrayOutputStream memory leak fix for LOBs
 */
 /*
 ** This program is free software: you can redistribute it and/or modify
@@ -464,6 +465,9 @@
                // form)
                Message msg = byteArrayToObj(data);
                
+               // we can already release data memory here
+               data = null;
+               
                // hand the message to the dispatcher for processing
                queue.enqueueInbound(msg);
             }
@@ -597,8 +601,20 @@
    private class Writer
    implements Runnable
    {
-      private final ByteArrayOutputStream2 baos = new ByteArrayOutputStream2(8192);
-      private final ByteArrayOutputStream res = new ByteArrayOutputStream(8192);
+      /**
+       * Default size of buffer for object flattening
+       */
+      private static final int BUFFER_SIZE = 8192;
+
+      /**
+       * Pre-allocated helper buffers for object flattening
+       */
+      private ByteArrayOutputStream2 baos = new ByteArrayOutputStream2(BUFFER_SIZE);
+      private ByteArrayOutputStream res = new ByteArrayOutputStream(BUFFER_SIZE);
+
+      /**
+       * Holder for data to be written out 
+       */
       private byte[] data;
       
       /**
@@ -640,6 +656,9 @@
                   OUTGOING.update(data.length);
                }
                queue.write(data);
+
+               // allow GC to collect memory allocated for data
+               data = null;
             }
          }
          
@@ -723,7 +742,21 @@
          res.write(obj.getClass() == Message.class ? TYPE_MESSAGE : TYPE_SYNC_MESSAGE);
          res.write(baos.buf(), 0, baos.size());
          
-         return res.toByteArray();
+         // in case baos grows too big -> reallocate it
+         if (baos.size() > BUFFER_SIZE)
+         {
+            baos = new ByteArrayOutputStream2(BUFFER_SIZE);
+         }
+         
+         byte[] result = res.toByteArray();
+         
+         // in case res grows too big -> reallocate it
+         if (res.size() > BUFFER_SIZE)
+         {
+            res = new ByteArrayOutputStream(BUFFER_SIZE);
+         }
+         
+         return result;
       }
    }
 }

=== modified file 'src/com/goldencode/p2j/util/OutputStreamWrapper.java'
--- old/src/com/goldencode/p2j/util/OutputStreamWrapper.java	2021-03-22 09:00:52 +0000
+++ new/src/com/goldencode/p2j/util/OutputStreamWrapper.java	2022-06-09 14:33:03 +0000
@@ -2,7 +2,7 @@
 ** Module   : OutputStreamWrapper.java
 ** Abstract : provides an external OutputStream wrapper for a P2J Stream
 **
-** Copyright (c) 2006-2017, Golden Code Development Corporation.
+** Copyright (c) 2006-2022, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- --JPRM-- ----------------------------Description-----------------------------
 ** 001 GES 20060215   @24675 First version which provides an external
@@ -10,6 +10,7 @@
 ** 002 ECF 20171029          Made write(byte[], int, int) more efficient; implemented close to
 **                           close underlying stream.
 ** 003 ME  20210322          Proxy flush to the underlying stream.
+**     TJD 20220608          Chunked write implementation
 */
 /*
 ** This program is free software: you can redistribute it and/or modify
@@ -80,6 +81,12 @@
    /** The wrapped stream. */
    private Stream out = null;
    
+   /** 
+    * The chunk to write at once to the remote {@link #stream}, via in the {@link #write(byte[], int, int)} 
+    * implementation. 
+    */
+   private static final int WRITE_CHUNK_SIZE = 1024 * 1024; // 1MB
+   
    /**
     * Creates an instance with the associated stream.
     */
@@ -103,6 +110,10 @@
    public void write(int b)
    throws IOException
    {
+      if (out == null)
+      {
+         throw new IOException("Stream is not open.");
+      }
       out.writeByte((byte) (b & 0xFF));
    }
    
@@ -124,6 +135,10 @@
    throws IOException,
           NullPointerException
    {
+      if (out == null)
+      {
+         throw new IOException("Stream is not open.");
+      }
       write(b, 0, b.length);
    }
    
@@ -154,7 +169,35 @@
           NullPointerException,
           IndexOutOfBoundsException
    {
-      out.write(b, off, len);
+      if (out == null)
+      {
+         throw new IOException("Stream is not open.");
+      }
+      // Bounds check
+      if ((off + len) > b.length || off < 0 || len < 0)
+      {
+         throw new IndexOutOfBoundsException();
+      }
+      int remaining = len - off;
+      if (remaining == 0)
+      {
+         return;
+      }
+      byte[] buffer = new byte[Math.min(remaining, WRITE_CHUNK_SIZE)];
+      
+      while (remaining > 0)
+      {
+         System.arraycopy(b, off, buffer, 0, buffer.length);
+         // write at most WRITE_CHUNK_SIZE of data and at most the remaining bytes
+         out.write(buffer, 0, buffer.length);
+         remaining -= buffer.length;
+         off += buffer.length;
+         // reallocate memory in case buffer can be smaller
+         if (remaining > 0 && remaining < WRITE_CHUNK_SIZE)
+         {
+            buffer = new byte[remaining];
+         }
+      }
    }
    
    /**
@@ -170,6 +213,9 @@
       }
    }
 
+   /**
+    * Flushes the underlying stream.
+    */
    @Override
    public void flush() throws IOException
    {

=== modified file 'src/com/goldencode/p2j/util/TargetLobFile.java'
--- old/src/com/goldencode/p2j/util/TargetLobFile.java	2022-04-07 02:42:27 +0000
+++ new/src/com/goldencode/p2j/util/TargetLobFile.java	2022-06-09 14:43:21 +0000
@@ -10,6 +10,7 @@
 ** 003 OM  20210328 Improved COPY-LOB engine and validations.
 **     OM  20210404 Used a getter to expose the file name.
 **     EVL 20220406 Catching more exceptions to inform the target file is missing or invalid.
+**     TJD 20220607 Switch to OutputStreamWriter to allow chunked write
 */
 
 /*
@@ -136,43 +137,27 @@
    @Override
    public void write(byte[] data)
    {
-      Stream stream = null;
-      try
-      {
-         stream = StreamFactory.openFileStream(filename, true, append);
-      }
-      catch (DeferredLegacyErrorException dlee)
-      {
-         ErrorManager.recordOrThrowError(43, filename, "3");
-         // ** Cannot find or open file <file-name>, errno = <number>. (43)
-         return;
-      }
-      catch (ErrorConditionException ece)
-      {
-         ErrorManager.recordOrThrowError(43, filename, "3");
-         // ** Cannot find or open file <file-name>, errno = <number>. (43)
-         return;
-      }
-      
       if (data == null)
       {
          // if the source is unknown (so the [data] is null) the file will still be created but empty 
          data = new byte[0];
       }
-      
-      try
-      {
-         stream.write(data);
+      Stream stream = openStream();
+      if (stream == null)
+      {
+         return;
+      }
+      // underlying stream will be closed by wrapper at the end of write
+      try (OutputStreamWrapper os = new OutputStreamWrapper(stream))
+      {
+         os.write(data);
       }
       catch (IOException exc)
       {
          ErrorManager.recordOrThrowError(43, filename, "13");
          // ** Cannot find or open file <file-name>, errno = <number>. (43)
       }
-      finally
-      {
-         stream.close();
-      }
+
    }
    
    /**
@@ -208,9 +193,13 @@
          }
       }
       
-      Stream stream = StreamFactory.openFileStream(filename, true, append);
-      
-      try
+      Stream stream = openStream();
+      if (stream == null)
+      {
+         return;
+      }
+      // underlying stream will be closed by wrapper at the end of write
+      try (OutputStreamWrapper os = new OutputStreamWrapper(stream))
       {
          stream.write(data.getBytes(javaCS));
       }
@@ -219,10 +208,6 @@
          ErrorManager.recordOrThrowError(43, filename, "13");
          // ** Cannot find or open file <file-name>, errno = <number>. (43)
       }
-      finally
-      {
-         stream.close();
-      }
    }
    
    /**
@@ -255,4 +240,30 @@
       
       return filename.toStringMessage();
    }
+   
+   /**
+    * Open stream using StreamFactory for writing, optionally in append mode 
+    * @return Stream object instance or null in case of failure
+    */
+   private Stream openStream()
+   {
+      /** File stream for output file, which is a remote resource */
+      Stream stream = null;
+      
+      try
+      {
+         stream = StreamFactory.openFileStream(filename, true, append);
+      }
+      catch (DeferredLegacyErrorException dlee)
+      {
+         ErrorManager.recordOrThrowError(43, filename, "2");
+         // ** Cannot find or open file <file-name>, errno = <number>. (43)
+      }
+      catch (ErrorConditionException ece)
+      {
+         ErrorManager.recordOrThrowError(43, filename, "3");
+         // ** Cannot find or open file <file-name>, errno = <number>. (43)
+      }
+      return stream;
+   }
 }

