Skip to content

Commit 75b7a9d

Browse files
authored
[ISSUE #14466] Remove Jackson byte buffer stream from GrpcUtils (#15368)
Use the neutral JSON facade in GrpcUtils and add a Nacos-owned ByteBufferInputStream helper for payload parsing. Update the Jackson adapter migration TODO with stage 5 validation results. Assisted-by: Claude Code
1 parent 3823101 commit 75b7a9d

5 files changed

Lines changed: 159 additions & 9 deletions

File tree

common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcUtils.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,10 @@
2323
import com.alibaba.nacos.api.remote.request.RequestMeta;
2424
import com.alibaba.nacos.api.remote.response.Response;
2525
import com.alibaba.nacos.api.utils.NetUtils;
26+
import com.alibaba.nacos.api.utils.json.JsonUtils;
2627
import com.alibaba.nacos.common.remote.PayloadRegistry;
2728
import com.alibaba.nacos.common.remote.exception.RemoteException;
28-
import com.alibaba.nacos.common.utils.JacksonUtils;
29-
import com.fasterxml.jackson.databind.util.ByteBufferBackedInputStream;
29+
import com.alibaba.nacos.common.utils.ByteBufferInputStream;
3030
import com.google.protobuf.Any;
3131
import com.google.protobuf.ByteString;
3232
import com.google.protobuf.UnsafeByteOperations;
@@ -96,7 +96,7 @@ public static Payload convert(Request request) {
9696
* @return payload.
9797
*/
9898
public static Payload convert(Response response) {
99-
byte[] jsonBytes = JacksonUtils.toJsonBytes(response);
99+
byte[] jsonBytes = JsonUtils.toJsonBytes(response);
100100

101101
Metadata.Builder metaBuilder =
102102
Metadata.newBuilder().setType(response.getClass().getSimpleName());
@@ -108,7 +108,7 @@ public static Payload convert(Response response) {
108108
private static byte[] convertRequestToByte(Request request) {
109109
Map<String, String> requestHeaders = new HashMap<>(request.getHeaders());
110110
request.clearHeaders();
111-
byte[] jsonBytes = JacksonUtils.toJsonBytes(request);
111+
byte[] jsonBytes = JsonUtils.toJsonBytes(request);
112112
request.putAllHeader(requestHeaders);
113113
return jsonBytes;
114114
}
@@ -124,7 +124,7 @@ public static Object parse(Payload payload) {
124124
if (classType != null) {
125125
ByteString byteString = payload.getBody().getValue();
126126
ByteBuffer byteBuffer = byteString.asReadOnlyByteBuffer();
127-
Object obj = JacksonUtils.toObj(new ByteBufferBackedInputStream(byteBuffer), classType);
127+
Object obj = JsonUtils.toObj(new ByteBufferInputStream(byteBuffer), classType);
128128
if (obj instanceof Request) {
129129
((Request) obj).putAllHeader(payload.getMetadata().getHeadersMap());
130130
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 1999-2026 Alibaba Group Holding Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.alibaba.nacos.common.utils;
18+
19+
import java.io.InputStream;
20+
import java.nio.ByteBuffer;
21+
import java.util.Objects;
22+
23+
/**
24+
* Input stream backed by a {@link ByteBuffer}.
25+
*
26+
* @author nacos
27+
*/
28+
public class ByteBufferInputStream extends InputStream {
29+
30+
private final ByteBuffer byteBuffer;
31+
32+
public ByteBufferInputStream(ByteBuffer byteBuffer) {
33+
this.byteBuffer = Objects.requireNonNull(byteBuffer, "byteBuffer").slice();
34+
}
35+
36+
@Override
37+
public int read() {
38+
if (!byteBuffer.hasRemaining()) {
39+
return -1;
40+
}
41+
return byteBuffer.get() & 0xff;
42+
}
43+
44+
@Override
45+
public int read(byte[] bytes, int offset, int length) {
46+
Objects.requireNonNull(bytes, "bytes");
47+
if (offset < 0 || length < 0 || length > bytes.length - offset) {
48+
throw new IndexOutOfBoundsException();
49+
}
50+
if (length == 0) {
51+
return 0;
52+
}
53+
if (!byteBuffer.hasRemaining()) {
54+
return -1;
55+
}
56+
int lengthToRead = Math.min(length, byteBuffer.remaining());
57+
byteBuffer.get(bytes, offset, lengthToRead);
58+
return lengthToRead;
59+
}
60+
61+
@Override
62+
public int available() {
63+
return byteBuffer.remaining();
64+
}
65+
}

common/src/test/java/com/alibaba/nacos/common/remote/client/grpc/GrpcUtilsTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.util.HashMap;
3232

3333
import static org.junit.jupiter.api.Assertions.assertEquals;
34+
import static org.junit.jupiter.api.Assertions.assertNotNull;
3435
import static org.junit.jupiter.api.Assertions.assertThrows;
3536
import static org.mockito.Mockito.mock;
3637
import static org.mockito.Mockito.when;
@@ -74,6 +75,11 @@ private ServiceQueryRequest createRequest() {
7475
return request;
7576
}
7677

78+
@Test
79+
void testConstructor() {
80+
assertNotNull(new GrpcUtils());
81+
}
82+
7783
@Test
7884
void testConvertRequest() {
7985
Payload convert = GrpcUtils.convert(request);
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright 1999-2026 Alibaba Group Holding Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.alibaba.nacos.common.utils;
18+
19+
import org.junit.jupiter.api.Test;
20+
21+
import java.nio.ByteBuffer;
22+
23+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
24+
import static org.junit.jupiter.api.Assertions.assertEquals;
25+
import static org.junit.jupiter.api.Assertions.assertThrows;
26+
27+
class ByteBufferInputStreamTest {
28+
29+
@Test
30+
void testReadSingleByteAndBytes() {
31+
ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[] {0, 1, 2, 3});
32+
byteBuffer.position(1);
33+
byteBuffer.limit(3);
34+
ByteBufferInputStream inputStream = new ByteBufferInputStream(byteBuffer);
35+
36+
assertEquals(2, inputStream.available());
37+
assertEquals(1, inputStream.read());
38+
assertEquals(1, inputStream.available());
39+
40+
byte[] bytes = new byte[] {9, 9, 9};
41+
assertEquals(1, inputStream.read(bytes, 1, 2));
42+
assertArrayEquals(new byte[] {9, 2, 9}, bytes);
43+
assertEquals(0, inputStream.available());
44+
assertEquals(-1, inputStream.read());
45+
assertEquals(-1, inputStream.read(bytes, 0, bytes.length));
46+
assertEquals(1, byteBuffer.position());
47+
}
48+
49+
@Test
50+
void testReadZeroLength() {
51+
ByteBufferInputStream inputStream = new ByteBufferInputStream(ByteBuffer.allocate(0));
52+
53+
assertEquals(0, inputStream.read(new byte[0], 0, 0));
54+
assertEquals(-1, inputStream.read());
55+
}
56+
57+
@Test
58+
void testConstructorRejectsNull() {
59+
assertThrows(NullPointerException.class, () -> new ByteBufferInputStream(null));
60+
}
61+
62+
@Test
63+
void testReadRejectsInvalidArguments() {
64+
ByteBufferInputStream inputStream =
65+
new ByteBufferInputStream(ByteBuffer.wrap(new byte[] {1}));
66+
67+
assertThrows(NullPointerException.class, () -> inputStream.read(null, 0, 1));
68+
assertThrows(IndexOutOfBoundsException.class, () -> inputStream.read(new byte[1], -1, 1));
69+
assertThrows(IndexOutOfBoundsException.class, () -> inputStream.read(new byte[1], 0, 2));
70+
}
71+
}

docs/jackson-json-adapter-migration-todo.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,13 @@ Last updated: 2026-06-16.
6767
`ConfigBasicInfo` have `LINE_MISSED=0`.
6868
- `mvn -pl api apache-rat:check`
6969
- `mvn -pl api dependency:tree -Dincludes=com.fasterxml.jackson.core`
70+
- 2026-06-16, stage 5 common gRPC JSON cleanup:
71+
- `mvn -pl common spotless:apply`
72+
- `mvn -pl common spotless:check`
73+
- `mvn -pl common -am -Dtest=GrpcUtilsTest,ByteBufferInputStreamTest -Dsurefire.failIfNoSpecifiedTests=false test`
74+
- `common/target/site/jacoco/jacoco.csv`: `GrpcUtils` and
75+
`ByteBufferInputStream` have `LINE_MISSED=0`.
76+
- `mvn -pl common apache-rat:check`
7077

7178
## Implementation Principles
7279

@@ -243,17 +250,18 @@ Last updated: 2026-06-16.
243250

244251
### 4. Migrate Common and Client Runtime Usage
245252

246-
- `[ ]` Replace Jackson `ByteBufferBackedInputStream`.
253+
- `[x]` Replace Jackson `ByteBufferBackedInputStream`.
247254
- Files:
248255
- `common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcUtils.java`
249256
- New helper such as
250257
`common/src/main/java/com/alibaba/nacos/common/utils/ByteBufferInputStream.java`
251258
- Plan:
252-
- Add a small Nacos-owned `InputStream` over `ByteBuffer` or switch to a
253-
byte-array path if the copy is acceptable.
254-
- Use neutral `JsonUtils.toObj(...)` in the parse path.
259+
- Add a small Nacos-owned `InputStream` over `ByteBuffer`.
260+
- Use neutral `JsonUtils` in the gRPC payload serialization and parse
261+
paths.
255262
- Validation:
256263
- Existing gRPC payload conversion tests.
264+
- Unit tests for the Nacos-owned `ByteBufferInputStream`.
257265

258266
- `[ ]` Remove `JavaType` from `AbstractNacosRestTemplate`.
259267
- Files:

0 commit comments

Comments
 (0)