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 <[email protected]> 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." ); - } -}
