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.