This is an automated email from the ASF dual-hosted git repository.

tv pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jcs.git


The following commit(s) were added to refs/heads/master by this push:
     new 29d49bf4 Add optional JSON serializer/deserializer based on Jackson
29d49bf4 is described below

commit 29d49bf48e6d8937e7c1c0677886965dd79e10fb
Author: Thomas Vandahl <[email protected]>
AuthorDate: Fri Jan 30 17:16:54 2026 +0100

    Add optional JSON serializer/deserializer based on Jackson
---
 commons-jcs3-core/pom.xml                          |   7 +
 commons-jcs3-core/src/main/java/module-info.java   |  13 +-
 .../jcs3/utils/serialization/JSONSerializer.java   |  99 ++++++++++++++
 .../serialization/JSONSerializerUnitTest.java      | 143 +++++++++++++++++++++
 .../utils/serialization/SerializerUnitTest.java    |  90 ++++++++-----
 .../src/test/test-conf/TestElementSerializer.ccf   |  12 ++
 src/changes/changes.xml                            |   3 +
 7 files changed, 332 insertions(+), 35 deletions(-)

diff --git a/commons-jcs3-core/pom.xml b/commons-jcs3-core/pom.xml
index c0376525..9dac65c8 100644
--- a/commons-jcs3-core/pom.xml
+++ b/commons-jcs3-core/pom.xml
@@ -103,6 +103,13 @@
       <artifactId>jakarta.servlet-api</artifactId>
       <optional>true</optional>
     </dependency>
+
+    <dependency>
+      <groupId>com.fasterxml.jackson.core</groupId>
+      <artifactId>jackson-databind</artifactId>
+      <version>2.20.1</version>
+      <optional>true</optional>
+    </dependency>
   </dependencies>
 
   <build>
diff --git a/commons-jcs3-core/src/main/java/module-info.java 
b/commons-jcs3-core/src/main/java/module-info.java
index aed49d22..5d9a95b1 100644
--- a/commons-jcs3-core/src/main/java/module-info.java
+++ b/commons-jcs3-core/src/main/java/module-info.java
@@ -93,14 +93,14 @@ module org.apache.commons.jcs3.core {
 
     // Java platform modules - required
     requires java.base;
-    requires java.rmi;
-    requires java.sql;
     requires java.management;
-    requires java.naming;
     requires java.desktop;
+    requires transitive java.rmi;
+    requires transitive java.sql;
+    requires transitive java.naming;
 
     // Optional dependencies for remote HTTP caching
-    requires static jakarta.servlet;
+    requires transitive jakarta.servlet;
 
     // Optional dependencies for JDBC disk cache
     requires static org.apache.commons.dbcp2;
@@ -109,6 +109,11 @@ module org.apache.commons.jcs3.core {
     requires static org.apache.httpcomponents.httpclient;
     requires static org.apache.httpcomponents.httpcore;
 
+    // Optional dependencies for remote HTTP caching
+    requires static com.fasterxml.jackson.databind;
+    opens org.apache.commons.jcs3.utils.serialization to 
com.fasterxml.jackson.databind;
+    opens org.apache.commons.jcs3.engine to com.fasterxml.jackson.databind;
+
     // Uses and provides clauses
     uses org.apache.commons.jcs3.auxiliary.AuxiliaryCacheFactory;
 }
diff --git 
a/commons-jcs3-core/src/main/java/org/apache/commons/jcs3/utils/serialization/JSONSerializer.java
 
b/commons-jcs3-core/src/main/java/org/apache/commons/jcs3/utils/serialization/JSONSerializer.java
new file mode 100644
index 00000000..b563d060
--- /dev/null
+++ 
b/commons-jcs3-core/src/main/java/org/apache/commons/jcs3/utils/serialization/JSONSerializer.java
@@ -0,0 +1,99 @@
+package org.apache.commons.jcs3.utils.serialization;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+
+import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
+
+import com.fasterxml.jackson.core.exc.StreamReadException;
+import com.fasterxml.jackson.databind.DatabindException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.json.JsonMapper;
+
+/**
+ * Performs JSON serialization and de-serialization.
+ */
+public class JSONSerializer
+    implements IElementSerializer
+{
+    /** Jackson JSON mapper instance */
+    private static final ObjectMapper mapper = JsonMapper.builder().build();
+
+    /** Wrapper to save the class name information */
+    private record Wrapper<T>(String className, T element) {}
+
+    /**
+     * Uses JSON de-serialization to turn a byte array into an object. All 
exceptions are
+     * converted into IOExceptions.
+     *
+     * @param data data bytes
+     * @param loader class loader to use
+     * @return Object
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    @Override
+    public <T> T deSerialize(final byte[] data, final ClassLoader loader)
+        throws IOException, ClassNotFoundException
+    {
+        if (data == null || data.length == 0)
+        {
+            return null;
+        }
+
+        try
+        {
+            JsonNode root = mapper.readTree(data);
+            String className = root.at("/className").asText();
+
+            @SuppressWarnings("unchecked") // Need to cast from Object
+            Class<T> clazz = (Class<T>)Class.forName(className, false,
+                    loader == null ? this.getClass().getClassLoader() : 
loader);
+
+            return mapper.treeToValue(root.at("/element"), clazz);
+        }
+        catch (StreamReadException | DatabindException e)
+        {
+            throw new IOException("Error deserializing JSON", e);
+        }
+    }
+
+    /**
+     * Serializes an object using JSON serialization.
+     *
+     * @param obj
+     * @return byte[]
+     * @throws IOException
+     */
+    @Override
+    public <T> byte[] serialize(final T obj)
+        throws IOException
+    {
+        if (obj == null)
+        {
+            return null;
+        }
+
+        Wrapper<T> wrapper = new Wrapper<T>(obj.getClass().getName(), obj);
+        return mapper.writeValueAsBytes(wrapper);
+    }
+}
diff --git 
a/commons-jcs3-core/src/test/java/org/apache/commons/jcs3/utils/serialization/JSONSerializerUnitTest.java
 
b/commons-jcs3-core/src/test/java/org/apache/commons/jcs3/utils/serialization/JSONSerializerUnitTest.java
new file mode 100644
index 00000000..c786598a
--- /dev/null
+++ 
b/commons-jcs3-core/src/test/java/org/apache/commons/jcs3/utils/serialization/JSONSerializerUnitTest.java
@@ -0,0 +1,143 @@
+package org.apache.commons.jcs3.utils.serialization;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests the JSON serializer.
+ */
+class JSONSerializerUnitTest
+{
+    private JSONSerializer serializer;
+
+    /**
+     * Test setup
+     *
+     * @throws Exception
+     */
+    @BeforeEach
+    void setUp()
+        throws Exception
+    {
+        this.serializer = new JSONSerializer();
+    }
+
+    public static record Person(String name, int age, Date birthdate) {}
+
+    /**
+     * Test simple back and forth with an object.
+     *
+     * @throws Exception
+     */
+    @Test
+    void testObjectBackAndForth()
+        throws Exception
+    {
+        Date date = new GregorianCalendar(1977, Calendar.DECEMBER, 
15).getTime();
+        final Person before = new Person("joe", 21, date);
+
+        // DO WORK
+        byte[] serialized = serializer.serialize(before);
+        System.out.println(new String(serialized, StandardCharsets.UTF_8));
+        final Person after = serializer.deSerialize(serialized, null);
+
+        // VERIFY
+        assertEquals(before.name(), after.name(), "Before and after should be 
the same.");
+        assertEquals(before.age(), after.age(), "Before and after should be 
the same.");
+        assertEquals(before.birthdate(), after.birthdate(), "Before and after 
should be the same.");
+    }
+
+    /**
+     * Test simple back and forth with a string.
+     *
+     * @throws Exception
+     */
+    @Test
+    void testBigStringBackAndForth()
+        throws Exception
+    {
+        final String string = "This is my big string ABCDEFGH";
+        final StringBuilder sb = new StringBuilder();
+        sb.append( string );
+        for ( int i = 0; i < 4; i++ )
+        {
+            sb.append( " " + i + sb.toString() ); // big string
+        }
+        final String before = sb.toString();
+
+        // DO WORK
+        final String after = (String) serializer.deSerialize( 
serializer.serialize( before ), null );
+
+        // VERIFY
+        assertEquals( before, after, "Before and after should be the same." );
+    }
+
+    /**
+     * Test serialization with a null object. Verify that we don't get an 
error.
+     *
+     * @throws Exception
+     */
+    @Test
+    void testNullInput()
+        throws Exception
+    {
+        final String before = null;
+
+        // DO WORK
+        final byte[] serialized = serializer.serialize( before );
+        //System.out.println( "testNullInput " + serialized );
+
+        final String after = serializer.deSerialize( serialized, null );
+        //System.out.println( "testNullInput " + after );
+
+        // VERIFY
+        assertNull( after, "Should have nothing." );
+    }
+
+    /**
+     * Test simple back and forth with a string.
+     *
+     * @throws Exception
+     */
+    @Test
+    void testSimpleBackAndForth()
+        throws Exception
+    {
+        final String before = 
"adsfdsafdsafdsafdsafdsafdsafdsagfdsafdsafdsfdsafdsafsa333 31231";
+
+        // DO WORK
+        byte[] serialized = serializer.serialize(before);
+        System.out.println(new String(serialized, StandardCharsets.UTF_8));
+        final String after = serializer.deSerialize(serialized, null);
+
+        // VERIFY
+        assertEquals( before, after, "Before and after should be the same." );
+    }
+}
diff --git 
a/commons-jcs3-core/src/test/java/org/apache/commons/jcs3/utils/serialization/SerializerUnitTest.java
 
b/commons-jcs3-core/src/test/java/org/apache/commons/jcs3/utils/serialization/SerializerUnitTest.java
index a16e25b3..a3e87d80 100644
--- 
a/commons-jcs3-core/src/test/java/org/apache/commons/jcs3/utils/serialization/SerializerUnitTest.java
+++ 
b/commons-jcs3-core/src/test/java/org/apache/commons/jcs3/utils/serialization/SerializerUnitTest.java
@@ -23,8 +23,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 
 import org.apache.commons.jcs3.JCS;
 import org.apache.commons.jcs3.access.CacheAccess;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 
 /**
@@ -37,68 +37,96 @@ class SerializerUnitTest
      *
      * @throws Exception
      */
-    @BeforeEach
-    void setUp()
+    @BeforeAll
+    public static void setUp()
         throws Exception
     {
         JCS.setConfigFilename( "/TestElementSerializer.ccf" );
     }
 
-    @AfterEach
-    void tearDown()
+    @AfterAll
+    public static void tearDown()
         throws Exception
     {
         JCS.shutdown();
     }
 
     /**
-     * Verify that object reading and writing works
+     * Verify that object reading and writing with CompressingSerializer works
      *
      * @throws Exception
      */
     @Test
-    void testReadWrite()
+    public void testReadWriteCompressingSerializer()
         throws Exception
     {
-        final int count = 500; // 100 fit in memory
         // CompressingSerializer
-        final CacheAccess<String, String> jcs1 = JCS.getInstance( 
"blockRegion1" );
+        final CacheAccess<String, String> jcs = JCS.getInstance( 
"blockRegion1" );
 
-        for ( int i = 0; i < count; i++ )
-        {
-            jcs1.put( "key:" + i, "data" + i );
-        }
+        testReadWrite(jcs);
+    }
 
-        for ( int i = 0; i < count; i++ )
-        {
-            final String res = jcs1.get( "key:" + i );
-            assertNotNull( res, "[key:" + i + "] should not be null, " + 
jcs1.getStats() );
-        }
+    /**
+     * Verify that object reading and writing with EncryptingSerializer works
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testReadWriteEncryptingSerializer()
+        throws Exception
+    {
+        // EncryptingSerializer
+        final CacheAccess<String, String> jcs1 = JCS.getInstance( 
"blockRegion2" );
+
+        testReadWrite(jcs1);
+
+        JCS.shutdown();
 
+        // Re-init
         // EncryptingSerializer
         final CacheAccess<String, String> jcs2 = JCS.getInstance( 
"blockRegion2" );
 
-        for ( int i = 0; i < count; i++ )
-        {
-            jcs2.put( "key:" + i, "data" + i );
-        }
-
-        for ( int i = 0; i < count; i++ )
+        for ( int i = 0; i < 500; i++ )
         {
             final String res = jcs2.get( "key:" + i );
             assertNotNull( res, "[key:" + i + "] should not be null, " + 
jcs2.getStats() );
         }
+    }
 
-        JCS.shutdown();
+    /**
+     * Verify that object reading and writing with JSONSerializer works
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testReadWriteJSONSerializer()
+        throws Exception
+    {
+        // JSONSerializer
+        final CacheAccess<String, String> jcs = JCS.getInstance( 
"blockRegion3" );
 
-        // Re-init
-        // EncryptingSerializer
-        final CacheAccess<String, String> jcs3 = JCS.getInstance( 
"blockRegion2" );
+        testReadWrite(jcs);
+    }
+
+    /**
+     * Verify that object reading and writing works
+     *
+     * @throws Exception
+     */
+    private void testReadWrite(CacheAccess<String, String> jcs)
+        throws Exception
+    {
+        final int count = 500; // 100 fit in memory
+
+        for ( int i = 0; i < count; i++ )
+        {
+            jcs.put( "key:" + i, "data" + i );
+        }
 
         for ( int i = 0; i < count; i++ )
         {
-            final String res = jcs3.get( "key:" + i );
-            assertNotNull( res, "[key:" + i + "] should not be null, " + 
jcs3.getStats() );
+            final String res = jcs.get( "key:" + i );
+            assertNotNull( res, "[key:" + i + "] should not be null, " + 
jcs.getStats() );
         }
     }
 }
diff --git a/commons-jcs3-core/src/test/test-conf/TestElementSerializer.ccf 
b/commons-jcs3-core/src/test/test-conf/TestElementSerializer.ccf
index 6831c89f..562f80a8 100644
--- a/commons-jcs3-core/src/test/test-conf/TestElementSerializer.ccf
+++ b/commons-jcs3-core/src/test/test-conf/TestElementSerializer.ccf
@@ -41,6 +41,11 @@ 
jcs.region.blockRegion2.cacheattributes=org.apache.commons.jcs3.engine.Composite
 jcs.region.blockRegion2.cacheattributes.MaxObjects=100
 
jcs.region.blockRegion2.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
 
+jcs.region.blockRegion3=blockDiskCache3
+jcs.region.blockRegion3.cacheattributes=org.apache.commons.jcs3.engine.CompositeCacheAttributes
+jcs.region.blockRegion3.cacheattributes.MaxObjects=100
+jcs.region.blockRegion3.cacheattributes.MemoryCacheName=org.apache.commons.jcs3.engine.memory.lru.LRUMemoryCache
+
 # #### AUXILIARY CACHES
 
 # Block Disk Cache
@@ -59,6 +64,13 @@ 
jcs.auxiliary.blockDiskCache2.serializer=org.apache.commons.jcs3.utils.serializa
 jcs.auxiliary.blockDiskCache2.serializer.attributes.preSharedKey=my_secret
 
jcs.auxiliary.blockDiskCache2.serializer.attributes.aesCipherTransformation=AES/GCM/NoPadding
 
+# Block Disk Cache
+jcs.auxiliary.blockDiskCache3=org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheFactory
+jcs.auxiliary.blockDiskCache3.attributes=org.apache.commons.jcs3.auxiliary.disk.block.BlockDiskCacheAttributes
+jcs.auxiliary.blockDiskCache3.attributes.DiskPath=target/test-sandbox/block-disk-cache3
+jcs.auxiliary.blockDiskCache3.attributes.EventQueueType=POOLED
+jcs.auxiliary.blockDiskCache3.serializer=org.apache.commons.jcs3.utils.serialization.JSONSerializer
+
 # Default Cache Event Queue thread pool config, used by auxiliaries
 thread_pool.cache_event_queue.useBoundary=false
 #thread_pool.cache_event_queue.boundarySize=2000
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 4b243dc3..27c2a120 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -37,6 +37,9 @@
             <action dev="tv" type="add">
                Add module-info.java for JCS core
             </action>
+            <action dev="tv" type="add">
+               Add optional JSON serializer/deserializer based on Jackson
+            </action>
             <!-- REMOVE -->
             <action dev="tv" type="remove">
                Remove all deprecated code.

Reply via email to