diff --git a/core/src/main/java/google/registry/cache/SimplifiedJedisClient.java b/core/src/main/java/google/registry/cache/SimplifiedJedisClient.java index 2f2acc04277..5f724211d25 100644 --- a/core/src/main/java/google/registry/cache/SimplifiedJedisClient.java +++ b/core/src/main/java/google/registry/cache/SimplifiedJedisClient.java @@ -25,10 +25,20 @@ import google.registry.model.EppResource; import google.registry.model.domain.Domain; import google.registry.model.host.Host; +import io.protostuff.Input; import io.protostuff.LinkedBuffer; +import io.protostuff.Output; +import io.protostuff.Pipe; import io.protostuff.ProtostuffIOUtil; import io.protostuff.Schema; +import io.protostuff.WireFormat; +import io.protostuff.runtime.DefaultIdStrategy; +import io.protostuff.runtime.Delegate; import io.protostuff.runtime.RuntimeSchema; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; import java.nio.charset.StandardCharsets; import java.util.Optional; import redis.clients.jedis.AbstractPipeline; @@ -52,11 +62,20 @@ public record JedisResource(String key, V value) {} Domain.class, "d_", Host.class, "h_"); + /** We need to inform Protostuff of the custom {@link InetAddress} delegates. */ + private static DefaultIdStrategy createIdStrategy() { + DefaultIdStrategy strategy = new DefaultIdStrategy(); + strategy.registerDelegate(new GenericInetAddressDelegate<>(InetAddress.class)); + strategy.registerDelegate(new GenericInetAddressDelegate<>(Inet4Address.class)); + strategy.registerDelegate(new GenericInetAddressDelegate<>(Inet6Address.class)); + return strategy; + } + private static final ImmutableMap, Schema> VALUE_SCHEMAS = ImmutableMap.of( Domain.class, RuntimeSchema.getSchema(Domain.class), - Host.class, RuntimeSchema.getSchema(Host.class)); + Host.class, RuntimeSchema.getSchema(Host.class, createIdStrategy())); private static final FluentLogger logger = FluentLogger.forEnclosingClass(); @@ -151,4 +170,46 @@ private Schema getValueSchema(Class clazz) { checkArgument(VALUE_SCHEMAS.containsKey(clazz), "Unknown class type %s", clazz); return (Schema) VALUE_SCHEMAS.get(clazz); } + + /** + * A custom Protostuff {@link Delegate} for {@link InetAddress} and its subclasses. + * + *

This is required in Java 17+ because Protostuff's default runtime schema serialization + * relies on reflection. Since {@link InetAddress} is part of the encapsulated {@code java.base} + * module, reflective access is restricted and throws {@link + * java.lang.reflect.InaccessibleObjectException}. + * + *

This delegate serializes the IP address as a raw byte array using {@link + * InetAddress#getAddress()} and reconstructs it using {@link InetAddress#getByAddress(byte[])} + */ + private record GenericInetAddressDelegate(Class clazz) + implements Delegate { + + @Override + public WireFormat.FieldType getFieldType() { + return WireFormat.FieldType.BYTES; + } + + @Override + public Class typeClass() { + return clazz; + } + + @SuppressWarnings("unchecked") + @Override + public T readFrom(Input input) throws IOException { + return (T) InetAddress.getByAddress(input.readByteArray()); + } + + @Override + public void writeTo(Output output, int number, T value, boolean repeated) throws IOException { + output.writeByteArray(number, value.getAddress(), repeated); + } + + @Override + public void transfer(Pipe pipe, Input input, Output output, int number, boolean repeated) + throws IOException { + output.writeByteArray(number, input.readByteArray(), repeated); + } + } } diff --git a/core/src/test/java/google/registry/cache/SimplifiedJedisClientTest.java b/core/src/test/java/google/registry/cache/SimplifiedJedisClientTest.java index c352a5a1699..3545f150902 100644 --- a/core/src/test/java/google/registry/cache/SimplifiedJedisClientTest.java +++ b/core/src/test/java/google/registry/cache/SimplifiedJedisClientTest.java @@ -19,6 +19,7 @@ import static google.registry.testing.DatabaseHelper.createTld; import static google.registry.testing.DatabaseHelper.persistActiveDomain; import static google.registry.testing.DatabaseHelper.persistActiveHost; +import static google.registry.testing.DatabaseHelper.persistActiveSubordinateHost; import static google.registry.testing.DatabaseHelper.persistDeletedDomain; import com.google.common.collect.ImmutableList; @@ -67,7 +68,8 @@ void testClient_roundTrip_domain() { @Test void testClient_roundTrip_host() { - Host host = persistActiveHost("ns1.example.tld"); + Domain domain = persistActiveDomain("example.tld"); + Host host = persistActiveSubordinateHost("ns1.example.tld", domain); SimplifiedJedisClient client = createJedisClient(); client.set(new SimplifiedJedisClient.JedisResource<>("repoId1", host)); assertThat(client.get(Host.class, "repoId1")).hasValue(host);