Skip to content

Commit 1d8db73

Browse files
authored
fix(java): support skip optional sql serializers for java11+ (#3553)
## Why? ## What does this PR do? - move optional SQL serializer access behind reflective helpers so startup does not touch `java.sql` unless those classes are actually used - add a JPMS regression test for building without `java.sql` and preserve time ref-tracking behavior for lazy serializer registrations ## Related issues Fixes #1695 ## AI Contribution Checklist - [ ] Substantial AI assistance was used in this PR: `yes` / `no` - [ ] If `yes`, I included a completed [AI Contribution Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs) in this PR description and the required `AI Usage Disclosure`. - [ ] If `yes`, my PR description includes the required `ai_review` summary and screenshot evidence of the final clean AI review results from both fresh reviewers on the current PR diff or current HEAD after the latest code changes. ## Does this PR introduce any user-facing change? - [ ] Does this PR introduce any public API change? - [ ] Does this PR introduce any binary protocol compatibility change?
1 parent a8562e1 commit 1d8db73

9 files changed

Lines changed: 319 additions & 113 deletions

File tree

java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import java.math.BigInteger;
3636
import java.nio.ByteBuffer;
3737
import java.nio.charset.Charset;
38-
import java.sql.Timestamp;
3938
import java.time.Instant;
4039
import java.time.LocalDateTime;
4140
import java.time.ZoneId;
@@ -127,6 +126,7 @@
127126
import org.apache.fory.serializer.SerializerFactory;
128127
import org.apache.fory.serializer.Serializers;
129128
import org.apache.fory.serializer.Shareable;
129+
import org.apache.fory.serializer.SqlTimeSerializers;
130130
import org.apache.fory.serializer.TimeSerializers;
131131
import org.apache.fory.serializer.UnknownClass;
132132
import org.apache.fory.serializer.UnknownClass.UnknownEmptyStruct;
@@ -329,6 +329,16 @@ private void addDefaultSerializers() {
329329
ArraySerializers.registerDefaultSerializers(this);
330330
PrimitiveListSerializers.registerDefaultSerializers(this);
331331
TimeSerializers.registerDefaultSerializers(this);
332+
if (SqlTimeSerializers.isSqlModuleAvailable()) {
333+
SqlTimeSerializers.registerDefaultSerializers(this);
334+
} else {
335+
Preconditions.checkArgument(
336+
extRegistry.classIdGenerator + SqlTimeSerializers.getNumReservedTypeIds()
337+
<= INTERNAL_NATIVE_ID_LIMIT,
338+
"Internal type id overflow: %s",
339+
extRegistry.classIdGenerator + SqlTimeSerializers.getNumReservedTypeIds());
340+
extRegistry.classIdGenerator += SqlTimeSerializers.getNumReservedTypeIds();
341+
}
332342
OptionalSerializers.registerDefaultSerializers(this);
333343
CollectionSerializers.registerDefaultSerializers(this);
334344
MapSerializers.registerDefaultSerializers(this);
@@ -374,7 +384,7 @@ private void addDefaultSerializer(Class type, Serializer serializer) {
374384
private void registerCommonUsedClasses() {
375385
registerInternal(LinkedList.class, TreeSet.class);
376386
registerInternal(LinkedHashMap.class, TreeMap.class);
377-
registerInternal(Date.class, Timestamp.class, LocalDateTime.class, Instant.class);
387+
registerInternal(Date.class, LocalDateTime.class, Instant.class);
378388
registerInternal(BigInteger.class, BigDecimal.class);
379389
registerInternal(Optional.class, OptionalInt.class);
380390
registerInternal(Boolean[].class, Byte[].class, Short[].class, Character[].class);

java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929

3030
import java.math.BigDecimal;
3131
import java.math.BigInteger;
32-
import java.sql.Timestamp;
3332
import java.time.Duration;
3433
import java.time.Instant;
3534
import java.time.LocalDate;
@@ -87,6 +86,7 @@
8786
import org.apache.fory.serializer.Serializer;
8887
import org.apache.fory.serializer.Serializers;
8988
import org.apache.fory.serializer.Shareable;
89+
import org.apache.fory.serializer.SqlTimeSerializers;
9090
import org.apache.fory.serializer.TimeSerializers;
9191
import org.apache.fory.serializer.UnionSerializer;
9292
import org.apache.fory.serializer.UnknownClass;
@@ -943,9 +943,16 @@ private void registerDefaultTypes() {
943943
registerType(Types.DURATION, Duration.class, new TimeSerializers.DurationSerializer(config));
944944
registerType(Types.TIMESTAMP, Instant.class, new TimeSerializers.InstantSerializer(config));
945945
registerType(Types.TIMESTAMP, Date.class, new TimeSerializers.DateSerializer(config));
946-
registerType(
947-
Types.TIMESTAMP, java.sql.Date.class, new TimeSerializers.SqlDateSerializer(config));
948-
registerType(Types.TIMESTAMP, Timestamp.class, new TimeSerializers.TimestampSerializer(config));
946+
if (SqlTimeSerializers.isSqlModuleAvailable()) {
947+
registerType(
948+
Types.TIMESTAMP,
949+
TypeUtils.SQL_DATE_TYPE.getRawType(),
950+
SqlTimeSerializers.newSqlDateSerializer(config));
951+
registerType(
952+
Types.TIMESTAMP,
953+
TypeUtils.TIMESTAMP_TYPE.getRawType(),
954+
SqlTimeSerializers.newTimestampSerializer(config));
955+
}
949956
registerType(
950957
Types.TIMESTAMP, LocalDateTime.class, new TimeSerializers.LocalDateTimeSerializer(config));
951958
registerType(Types.DATE, LocalDate.class, new TimeSerializers.LocalDateSerializer(config));
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.fory.serializer;
21+
22+
import java.sql.Time;
23+
import java.sql.Timestamp;
24+
import java.time.Instant;
25+
import org.apache.fory.config.Config;
26+
import org.apache.fory.context.CopyContext;
27+
import org.apache.fory.context.ReadContext;
28+
import org.apache.fory.context.WriteContext;
29+
import org.apache.fory.memory.MemoryBuffer;
30+
import org.apache.fory.resolver.TypeResolver;
31+
32+
/** Optional SQL time serializers loaded only when {@code java.sql} types are actually used. */
33+
public final class SqlTimeSerializers {
34+
private static final String SQL_DATE_CLASS_NAME = "java.sql.Date";
35+
private static final String SQL_TIME_CLASS_NAME = "java.sql.Time";
36+
private static final String SQL_TIMESTAMP_CLASS_NAME = "java.sql.Timestamp";
37+
private static final int NUM_RESERVED_TYPE_IDS = 3;
38+
private static final boolean SQL_MODULE_AVAILABLE =
39+
isClassAvailable(SQL_DATE_CLASS_NAME)
40+
&& isClassAvailable(SQL_TIME_CLASS_NAME)
41+
&& isClassAvailable(SQL_TIMESTAMP_CLASS_NAME);
42+
43+
private SqlTimeSerializers() {}
44+
45+
public static boolean isSqlModuleAvailable() {
46+
return SQL_MODULE_AVAILABLE;
47+
}
48+
49+
public static int getNumReservedTypeIds() {
50+
return NUM_RESERVED_TYPE_IDS;
51+
}
52+
53+
public static Serializer<?> newSqlDateSerializer(Config config) {
54+
return new SqlDateSerializer(config);
55+
}
56+
57+
public static Serializer<?> newTimestampSerializer(Config config) {
58+
return new TimestampSerializer(config);
59+
}
60+
61+
public static void registerDefaultSerializers(TypeResolver resolver) {
62+
Config config = resolver.getConfig();
63+
resolver.registerInternalSerializer(java.sql.Date.class, newSqlDateSerializer(config));
64+
resolver.registerInternalSerializer(Time.class, new SqlTimeSerializer(config));
65+
resolver.registerInternalSerializer(Timestamp.class, newTimestampSerializer(config));
66+
}
67+
68+
private static boolean isClassAvailable(String className) {
69+
try {
70+
Class.forName(className, false, SqlTimeSerializers.class.getClassLoader());
71+
return true;
72+
} catch (ClassNotFoundException e) {
73+
return false;
74+
}
75+
}
76+
77+
public static final class SqlDateSerializer
78+
extends TimeSerializers.BaseDateSerializer<java.sql.Date> {
79+
public SqlDateSerializer(Config config) {
80+
super(config, java.sql.Date.class);
81+
}
82+
83+
public SqlDateSerializer(Config config, boolean needToWriteRef) {
84+
super(config, java.sql.Date.class, needToWriteRef);
85+
}
86+
87+
@Override
88+
protected java.sql.Date newInstance(long time) {
89+
return new java.sql.Date(time);
90+
}
91+
92+
@Override
93+
public java.sql.Date copy(CopyContext copyContext, java.sql.Date value) {
94+
return newInstance(value.getTime());
95+
}
96+
}
97+
98+
public static final class SqlTimeSerializer extends TimeSerializers.BaseDateSerializer<Time> {
99+
public SqlTimeSerializer(Config config) {
100+
super(config, Time.class);
101+
}
102+
103+
public SqlTimeSerializer(Config config, boolean needToWriteRef) {
104+
super(config, Time.class, needToWriteRef);
105+
}
106+
107+
@Override
108+
protected Time newInstance(long time) {
109+
return new Time(time);
110+
}
111+
112+
@Override
113+
public Time copy(CopyContext copyContext, Time value) {
114+
return newInstance(value.getTime());
115+
}
116+
}
117+
118+
public static final class TimestampSerializer extends TimeSerializers.TimeSerializer<Timestamp> {
119+
public TimestampSerializer(Config config) {
120+
super(config, Timestamp.class);
121+
}
122+
123+
public TimestampSerializer(Config config, boolean needToWriteRef) {
124+
super(config, Timestamp.class, needToWriteRef);
125+
}
126+
127+
@Override
128+
public void write(WriteContext writeContext, Timestamp value) {
129+
MemoryBuffer buffer = writeContext.getBuffer();
130+
if (config.isXlang()) {
131+
Instant instant = value.toInstant();
132+
buffer.writeInt64(instant.getEpochSecond());
133+
buffer.writeInt32(instant.getNano());
134+
} else {
135+
long time = value.getTime() - (value.getNanos() / 1_000_000);
136+
buffer.writeInt64(time);
137+
buffer.writeInt32(value.getNanos());
138+
}
139+
}
140+
141+
@Override
142+
public Timestamp read(ReadContext readContext) {
143+
MemoryBuffer buffer = readContext.getBuffer();
144+
if (config.isXlang()) {
145+
long seconds = buffer.readInt64();
146+
int nanos = buffer.readInt32();
147+
return Timestamp.from(Instant.ofEpochSecond(seconds, nanos));
148+
}
149+
Timestamp t = new Timestamp(buffer.readInt64());
150+
t.setNanos(buffer.readInt32());
151+
return t;
152+
}
153+
154+
@Override
155+
public Timestamp copy(CopyContext copyContext, Timestamp value) {
156+
return new Timestamp(value.getTime());
157+
}
158+
}
159+
}

java/fory-core/src/main/java/org/apache/fory/serializer/TimeSerializers.java

Lines changed: 2 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919

2020
package org.apache.fory.serializer;
2121

22-
import java.sql.Time;
23-
import java.sql.Timestamp;
2422
import java.time.Duration;
2523
import java.time.Instant;
2624
import java.time.LocalDate;
@@ -44,7 +42,7 @@
4442
import org.apache.fory.context.ReadContext;
4543
import org.apache.fory.context.WriteContext;
4644
import org.apache.fory.memory.MemoryBuffer;
47-
import org.apache.fory.resolver.TypeResolver;
45+
import org.apache.fory.resolver.ClassResolver;
4846
import org.apache.fory.util.DateTimeUtils;
4947

5048
/** Serializers for all time related types. */
@@ -119,91 +117,6 @@ public Date copy(CopyContext copyContext, Date value) {
119117
}
120118
}
121119

122-
public static final class SqlDateSerializer extends BaseDateSerializer<java.sql.Date> {
123-
public SqlDateSerializer(Config config) {
124-
super(config, java.sql.Date.class);
125-
}
126-
127-
public SqlDateSerializer(Config config, boolean needToWriteRef) {
128-
super(config, java.sql.Date.class, needToWriteRef);
129-
}
130-
131-
@Override
132-
protected java.sql.Date newInstance(long time) {
133-
return new java.sql.Date(time);
134-
}
135-
136-
@Override
137-
public java.sql.Date copy(CopyContext copyContext, java.sql.Date value) {
138-
return newInstance(value.getTime());
139-
}
140-
}
141-
142-
public static final class SqlTimeSerializer extends BaseDateSerializer<Time> {
143-
144-
public SqlTimeSerializer(Config config) {
145-
super(config, Time.class);
146-
}
147-
148-
public SqlTimeSerializer(Config config, boolean needToWriteRef) {
149-
super(config, Time.class, needToWriteRef);
150-
}
151-
152-
@Override
153-
protected Time newInstance(long time) {
154-
return new Time(time);
155-
}
156-
157-
@Override
158-
public Time copy(CopyContext copyContext, Time value) {
159-
return newInstance(value.getTime());
160-
}
161-
}
162-
163-
public static final class TimestampSerializer extends TimeSerializer<Timestamp> {
164-
165-
public TimestampSerializer(Config config) {
166-
// conflict with instant
167-
super(config, Timestamp.class);
168-
}
169-
170-
public TimestampSerializer(Config config, boolean needToWriteRef) {
171-
super(config, Timestamp.class, needToWriteRef);
172-
}
173-
174-
@Override
175-
public void write(WriteContext writeContext, Timestamp value) {
176-
MemoryBuffer buffer = writeContext.getBuffer();
177-
if (config.isXlang()) {
178-
Instant instant = value.toInstant();
179-
buffer.writeInt64(instant.getEpochSecond());
180-
buffer.writeInt32(instant.getNano());
181-
} else {
182-
long time = value.getTime() - (value.getNanos() / 1_000_000);
183-
buffer.writeInt64(time);
184-
buffer.writeInt32(value.getNanos());
185-
}
186-
}
187-
188-
@Override
189-
public Timestamp read(ReadContext readContext) {
190-
MemoryBuffer buffer = readContext.getBuffer();
191-
if (config.isXlang()) {
192-
long seconds = buffer.readInt64();
193-
int nanos = buffer.readInt32();
194-
return Timestamp.from(Instant.ofEpochSecond(seconds, nanos));
195-
}
196-
Timestamp t = new Timestamp(buffer.readInt64());
197-
t.setNanos(buffer.readInt32());
198-
return t;
199-
}
200-
201-
@Override
202-
public Timestamp copy(CopyContext copyContext, Timestamp value) {
203-
return new Timestamp(value.getTime());
204-
}
205-
}
206-
207120
public static final class LocalDateSerializer extends ImmutableTimeSerializer<LocalDate>
208121
implements Shareable {
209122
public LocalDateSerializer(Config config) {
@@ -723,12 +636,9 @@ public OffsetDateTime read(ReadContext readContext) {
723636
}
724637
}
725638

726-
public static void registerDefaultSerializers(TypeResolver resolver) {
639+
public static void registerDefaultSerializers(ClassResolver resolver) {
727640
Config config = resolver.getConfig();
728641
resolver.registerInternalSerializer(Date.class, new DateSerializer(config));
729-
resolver.registerInternalSerializer(java.sql.Date.class, new SqlDateSerializer(config));
730-
resolver.registerInternalSerializer(Time.class, new SqlTimeSerializer(config));
731-
resolver.registerInternalSerializer(Timestamp.class, new TimestampSerializer(config));
732642
resolver.registerInternalSerializer(LocalDate.class, new LocalDateSerializer(config));
733643
resolver.registerInternalSerializer(LocalTime.class, new LocalTimeSerializer(config));
734644
resolver.registerInternalSerializer(LocalDateTime.class, new LocalDateTimeSerializer(config));

0 commit comments

Comments
 (0)