You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by tv...@apache.org on 2026/10/18 21:09:02 UTC
(commons-jcs) 01/02: Fix JSON serialization
This is an automated email from the ASF dual-hosted git repository.
asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jcs.git
commit b8d4fce1bfa9fa9e883c79b35e55a69951977b46
Author: Thomas Vandahl <tv...@apache.org>
AuthorDate: Sun Oct 18 23:07:40 2026 +0200
Fix JSON serialization
---
.../jcs4/utils/serialization/JSONSerializer.java | 187 ++++++++++++++++++++-
.../serialization/JSONSerializerUnitTest.java | 142 ----------------
2 files changed, 183 insertions(+), 146 deletions(-)
diff --git a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/utils/serialization/JSONSerializer.java b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/utils/serialization/JSONSerializer.java
index 66bd6f43..077118ef 100644
--- a/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/utils/serialization/JSONSerializer.java
+++ b/commons-jcs4-core/src/main/java/org/apache/commons/jcs4/utils/serialization/JSONSerializer.java
@@ -20,15 +20,27 @@ package org.apache.commons.jcs4.utils.serialization;
*/
import java.io.IOException;
+import java.time.Duration;
+import org.apache.commons.jcs4.engine.CacheElement;
+import org.apache.commons.jcs4.engine.ElementAttributes;
+import org.apache.commons.jcs4.engine.behavior.IElementAttributes;
import org.apache.commons.jcs4.engine.behavior.IElementSerializer;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.exc.StreamReadException;
import com.fasterxml.jackson.databind.DatabindException;
+import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.json.JsonMapper;
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
/**
@@ -44,13 +56,183 @@ public class JSONSerializer
{
mapper = JsonMapper.builder()
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
+ .configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false)
.build();
mapper.registerModule(new JavaTimeModule());
+ SimpleModule jcsModule = new SimpleModule();
+ jcsModule.addDeserializer(IElementAttributes.class, new IElementAttributesDeserializer());
+ jcsModule.addSerializer(IElementAttributes.class, new IElementAttributesSerializer());
+ jcsModule.addDeserializer(CacheElement.class, new CacheElementDeserializer<>());
+ jcsModule.addSerializer(CacheElement.class, new CacheElementSerializer());
+ mapper.registerModule(jcsModule);
+ }
+
+ /**
+ * Returns the singleton instance of the ObjectMapper for further customization
+ *
+ * @return the mapper
+ */
+ public static ObjectMapper getObjectMapper()
+ {
+ return mapper;
}
/** Wrapper to save the class name information */
private record Wrapper<T>(String className, T element) {}
+ private static <T> Class<T> getClazz(String className, ClassLoader loader) throws IOException
+ {
+ try
+ {
+ @SuppressWarnings("unchecked") // Need to cast from Object
+ Class<T> clazz = (Class<T>) Class.forName(className, false, loader);
+
+ return clazz;
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new IOException(e);
+ }
+ }
+
+ /**
+ * Custom deserializer for IElementAttributes
+ */
+ private static class IElementAttributesDeserializer extends StdDeserializer<IElementAttributes>
+ {
+ private static final long serialVersionUID = 452815375027560351L;
+
+ public IElementAttributesDeserializer()
+ {
+ this(null);
+ }
+
+ public IElementAttributesDeserializer(Class<?> vc)
+ {
+ super(vc);
+ }
+
+ @Override
+ public IElementAttributes deserialize(JsonParser jp, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException
+ {
+ ElementAttributes defaults = ElementAttributes.defaults();
+ JsonNode node = jp.getCodec().readTree(jp);
+ boolean isSpool = node.get("spool").asBoolean(defaults.IsSpool());
+ boolean isLateral = node.get("lateral").asBoolean(defaults.IsLateral());
+ boolean isRemote = node.get("remote").asBoolean(defaults.IsRemote());
+ boolean isEternal = node.get("eternal").asBoolean(defaults.IsEternal());
+ String maxLifeString = node.get("maxLife").asText(defaults.MaxLife().toString());
+ Duration maxLife = Duration.parse(maxLifeString);
+ String maxIdleTimeString = node.get("maxIdleTime").asText(defaults.MaxIdleTime().toString());
+ Duration maxIdleTime = Duration.parse(maxIdleTimeString);
+
+ return new ElementAttributes(isSpool, isLateral, isRemote, isEternal, maxLife, maxIdleTime);
+ }
+ }
+
+ /**
+ * Custom serializer for IElementAttributes
+ */
+ private static class IElementAttributesSerializer extends StdSerializer<IElementAttributes>
+ {
+ private static final long serialVersionUID = 452815375027560351L;
+
+ public IElementAttributesSerializer()
+ {
+ this(null);
+ }
+
+ public IElementAttributesSerializer(Class<IElementAttributes> vc)
+ {
+ super(vc);
+ }
+
+ @Override
+ public void serialize(IElementAttributes value, JsonGenerator gen, SerializerProvider provider)
+ throws IOException
+ {
+ gen.writeStartObject();
+ gen.writeBooleanField("spool", value.IsSpool());
+ gen.writeBooleanField("lateral", value.IsLateral());
+ gen.writeBooleanField("remote", value.IsRemote());
+ gen.writeBooleanField("eternal", value.IsEternal());
+ gen.writeStringField("maxLife", value.MaxLife().toString());
+ gen.writeStringField("maxIdleTime", value.MaxIdleTime().toString());
+ gen.writeEndObject();
+ }
+ }
+
+ /**
+ * Custom deserializer for CacheElement
+ */
+ private static class CacheElementDeserializer<K, V> extends StdDeserializer<CacheElement<K, V>>
+ {
+ private static final long serialVersionUID = 452815375027560351L;
+
+ public CacheElementDeserializer()
+ {
+ this(null);
+ }
+
+ public CacheElementDeserializer(Class<?> vc)
+ {
+ super(vc);
+ }
+
+ @Override
+ public CacheElement<K, V> deserialize(JsonParser jp, DeserializationContext ctxt)
+ throws IOException, JsonProcessingException
+ {
+ JsonNode node = jp.getCodec().readTree(jp);
+ String cacheName = node.get("cacheName").asText();
+
+ JsonNode keyNode = node.get("key");
+ String keyClassName = keyNode.get("className").asText();
+ Class<K> keyClazz = getClazz(keyClassName, this.getClass().getClassLoader());
+ K key = mapper.treeToValue(keyNode.get("element"), keyClazz);
+
+ JsonNode valueNode = node.get("value");
+ String valueClassName = valueNode.get("className").asText();
+ Class<V> valueClazz = getClazz(valueClassName, this.getClass().getClassLoader());
+ V value = mapper.treeToValue(valueNode.get("element"), valueClazz);
+
+ IElementAttributes elementAttributes = mapper.treeToValue(node.get("elementAttributes"),
+ IElementAttributes.class);
+ return new CacheElement<>(cacheName, key, value, elementAttributes);
+ }
+ }
+
+ /**
+ * Custom serializer for CacheElement
+ */
+ private static class CacheElementSerializer<K, V> extends StdSerializer<CacheElement<K, V>>
+ {
+ private static final long serialVersionUID = 452815375027560351L;
+
+ public CacheElementSerializer()
+ {
+ this(null);
+ }
+
+ public CacheElementSerializer(Class<CacheElement<K, V>> vc)
+ {
+ super(vc);
+ }
+
+ @Override
+ public void serialize(CacheElement<K, V> value, JsonGenerator gen, SerializerProvider provider)
+ throws IOException
+ {
+ gen.writeStartObject();
+ gen.writeStringField("cacheName", value.cacheName());
+ gen.writeObjectField("key", new Wrapper<K>(value.key().getClass().getName(), value.key()));
+ gen.writeObjectField("value", new Wrapper<V>(value.value().getClass().getName(), value.value()));
+ gen.writeObjectField("elementAttributes", value.elementAttributes());
+ gen.writeEndObject();
+ }
+ }
+
/**
* Uses JSON de-serialization to turn a byte array into an object. All exceptions are
* converted into IOExceptions.
@@ -74,10 +256,7 @@ public class JSONSerializer
{
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);
+ Class<T> clazz = getClazz(className, loader == null ? this.getClass().getClassLoader() : loader);
return mapper.treeToValue(root.at("/element"), clazz);
}
diff --git a/commons-jcs4-core/src/test/java/org/apache/commons/jcs4/utils/serialization/JSONSerializerUnitTest.java b/commons-jcs4-core/src/test/java/org/apache/commons/jcs4/utils/serialization/JSONSerializerUnitTest.java
deleted file mode 100644
index 88a0d3db..00000000
--- a/commons-jcs4-core/src/test/java/org/apache/commons/jcs4/utils/serialization/JSONSerializerUnitTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package org.apache.commons.jcs4.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.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." );
- }
-}