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." );
-    }
-}