Skip to content

Commit c41f503

Browse files
authored
Merge branch 'master' into feature/context
2 parents 398398d + 0ebb44f commit c41f503

File tree

4 files changed

+316
-0
lines changed

4 files changed

+316
-0
lines changed

grpc-contrib/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@
3535
<groupId>io.grpc</groupId>
3636
<artifactId>grpc-context</artifactId>
3737
</dependency>
38+
<dependency>
39+
<groupId>com.google.code.gson</groupId>
40+
<artifactId>gson</artifactId>
41+
<scope>provided</scope>
42+
</dependency>
3843
<dependency>
3944
<groupId>io.grpc</groupId>
4045
<artifactId>grpc-testing</artifactId>
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Copyright (c) 2017, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
package com.salesforce.grpc.contrib;
9+
10+
import com.google.common.reflect.TypeToken;
11+
import com.google.gson.Gson;
12+
import com.google.protobuf.GeneratedMessageV3;
13+
import com.google.protobuf.InvalidProtocolBufferException;
14+
import io.grpc.Metadata;
15+
16+
import java.io.IOException;
17+
import java.io.StringWriter;
18+
import java.io.Writer;
19+
import java.lang.reflect.InvocationTargetException;
20+
import java.lang.reflect.Method;
21+
22+
/**
23+
* {@code MoreMetadata} provides additional utilities for working with gRPC {@code Metadata}.
24+
*/
25+
//CHECKSTYLE:OFF: MethodName
26+
public final class MoreMetadata {
27+
private MoreMetadata() { }
28+
29+
/**
30+
* A metadata marshaller that encodes objects as JSON using the google-gson library.
31+
*
32+
* <p>All non-ascii characters are unicode escaped to comply with {@code AsciiMarshaller}'s character range
33+
* requirements.
34+
*
35+
* @param clazz the type to serialize
36+
* @param <T>
37+
*/
38+
public static final <T> Metadata.AsciiMarshaller<T> JSON_MARSHALLER(Class<T> clazz) {
39+
return new Metadata.AsciiMarshaller<T>() {
40+
TypeToken<T> typeToken = TypeToken.of(clazz);
41+
private Gson gson = new Gson();
42+
43+
@Override
44+
public String toAsciiString(T value) {
45+
try {
46+
try (StringWriter sw = new StringWriter()) {
47+
gson.toJson(value, typeToken.getType(), new UnicodeEscapingAsciiWriter(sw));
48+
return sw.toString();
49+
}
50+
} catch (IOException e) {
51+
throw new IllegalArgumentException(e);
52+
}
53+
}
54+
55+
@Override
56+
public T parseAsciiString(String serialized) {
57+
return gson.fromJson(serialized, typeToken.getType());
58+
}
59+
};
60+
}
61+
62+
/**
63+
* See: https://github.com/google/gson/issues/388.
64+
*/
65+
private static final class UnicodeEscapingAsciiWriter extends Writer {
66+
private final Writer out;
67+
68+
private UnicodeEscapingAsciiWriter(Writer out) {
69+
this.out = out;
70+
}
71+
72+
@Override public void write(char[] buffer, int offset, int count) throws IOException {
73+
for (int i = 0; i < count; i++) {
74+
char c = buffer[i + offset];
75+
if (c >= ' ' && c <= '~') {
76+
out.write(c);
77+
} else {
78+
out.write(String.format("\\u%04x", (int) c));
79+
}
80+
}
81+
}
82+
83+
@Override public void flush() throws IOException {
84+
out.flush();
85+
}
86+
87+
@Override public void close() throws IOException {
88+
out.close();
89+
}
90+
}
91+
92+
/**
93+
* A metadata marshaller that encodes objects as protobuf according to their proto IDL specification.
94+
*
95+
* @param clazz the type to serialize
96+
* @param <T>
97+
*/
98+
public static <T extends GeneratedMessageV3> Metadata.BinaryMarshaller<T> PROTOBUF_MARSHALLER(Class<T> clazz) {
99+
try {
100+
Method defaultInstance = clazz.getMethod("getDefaultInstance");
101+
GeneratedMessageV3 instance = (GeneratedMessageV3) defaultInstance.invoke(null);
102+
103+
return new Metadata.BinaryMarshaller<T>() {
104+
@Override
105+
public byte[] toBytes(T value) {
106+
return value.toByteArray();
107+
}
108+
109+
@Override
110+
public T parseBytes(byte[] serialized) {
111+
try {
112+
return (T) instance.getParserForType().parseFrom(serialized);
113+
} catch (InvalidProtocolBufferException ipbe) {
114+
throw new IllegalArgumentException(ipbe);
115+
}
116+
}
117+
};
118+
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
119+
throw new IllegalStateException(ex);
120+
}
121+
}
122+
123+
/**
124+
* A metadata marshaller that encodes boolean values.
125+
*/
126+
public static final Metadata.AsciiMarshaller<Boolean> BOOLEAN_MARSHALLER = new Metadata.AsciiMarshaller<Boolean>() {
127+
@Override
128+
public String toAsciiString(Boolean value) {
129+
return value.toString();
130+
}
131+
132+
@Override
133+
public Boolean parseAsciiString(String serialized) {
134+
return Boolean.parseBoolean(serialized);
135+
}
136+
};
137+
138+
/**
139+
* A metadata marshaller that encodes integer-type values.
140+
*/
141+
public static final Metadata.AsciiMarshaller<Long> LONG_MARSHALLER = new Metadata.AsciiMarshaller<Long>() {
142+
@Override
143+
public String toAsciiString(Long value) {
144+
return value.toString();
145+
}
146+
147+
@Override
148+
public Long parseAsciiString(String serialized) {
149+
return Long.parseLong(serialized);
150+
}
151+
};
152+
153+
/**
154+
* A metadata marshaller that encodes floating-point-type values.
155+
*/
156+
public static final Metadata.AsciiMarshaller<Double> DOUBLE_MARSHALLER = new Metadata.AsciiMarshaller<Double>() {
157+
@Override
158+
public String toAsciiString(Double value) {
159+
return value.toString();
160+
}
161+
162+
@Override
163+
public Double parseAsciiString(String serialized) {
164+
return Double.parseDouble(serialized);
165+
}
166+
};
167+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* Copyright (c) 2017, salesforce.com, inc.
3+
* All rights reserved.
4+
* Licensed under the BSD 3-Clause license.
5+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
8+
package com.salesforce.grpc.contrib;
9+
10+
import com.google.common.base.Objects;
11+
import io.grpc.Metadata;
12+
import org.junit.Test;
13+
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
19+
public class MoreMetadataTest {
20+
@Test
21+
public void jsonMarshallerRoundtrip() {
22+
Foo foo = new Foo();
23+
foo.country = "France";
24+
List<Bar> bars = new ArrayList<>();
25+
Bar bar1 = new Bar();
26+
bar1.cheese = "Brë";
27+
bar1.age = 2;
28+
bars.add(bar1);
29+
Bar bar2 = new Bar();
30+
bar2.cheese = "Guda<>'";
31+
bar2.age = 4;
32+
bars.add(bar2);
33+
foo.bars = bars;
34+
35+
Metadata.AsciiMarshaller<Foo> marshaller = MoreMetadata.JSON_MARSHALLER(Foo.class);
36+
String str = marshaller.toAsciiString(foo);
37+
assertThat(str).doesNotContain("ë");
38+
39+
Foo foo2 = marshaller.parseAsciiString(str);
40+
assertThat(foo2).isEqualTo(foo);
41+
}
42+
43+
private class Foo {
44+
String country;
45+
List<Bar> bars;
46+
47+
@Override
48+
public boolean equals(Object o) {
49+
if (this == o) return true;
50+
if (o == null || getClass() != o.getClass()) return false;
51+
Foo foo = (Foo) o;
52+
return Objects.equal(country, foo.country) &&
53+
Objects.equal(bars, foo.bars);
54+
}
55+
}
56+
57+
private class Bar {
58+
String cheese;
59+
int age;
60+
61+
@Override
62+
public boolean equals(Object o) {
63+
if (this == o) return true;
64+
if (o == null || getClass() != o.getClass()) return false;
65+
Bar bar = (Bar) o;
66+
return age == bar.age &&
67+
Objects.equal(cheese, bar.cheese);
68+
}
69+
}
70+
71+
@Test
72+
public void protobufMarshallerRoundtrip() {
73+
HelloRequest request = HelloRequest.newBuilder().setName("World").build();
74+
75+
Metadata.BinaryMarshaller<HelloRequest> marshaller = MoreMetadata.PROTOBUF_MARSHALLER(HelloRequest.class);
76+
byte[] bytes = marshaller.toBytes(request);
77+
HelloRequest request2 = marshaller.parseBytes(bytes);
78+
79+
assertThat(request2).isEqualTo(request);
80+
}
81+
82+
@Test
83+
public void booleanMarshallerRountrip() {
84+
Metadata.AsciiMarshaller<Boolean> marshaller = MoreMetadata.BOOLEAN_MARSHALLER;
85+
String s = marshaller.toAsciiString(Boolean.TRUE);
86+
assertThat(s).isEqualTo("true");
87+
88+
Boolean b = marshaller.parseAsciiString(s);
89+
assertThat(b).isTrue();
90+
}
91+
92+
@Test
93+
public void longMarshallerRountrip() {
94+
Metadata.AsciiMarshaller<Long> marshaller = MoreMetadata.LONG_MARSHALLER;
95+
String s = marshaller.toAsciiString(42L);
96+
assertThat(s).isEqualTo("42");
97+
98+
Long l = marshaller.parseAsciiString(s);
99+
assertThat(l).isEqualTo(42L);
100+
}
101+
102+
@Test
103+
public void doubleMarshallerRountrip() {
104+
Metadata.AsciiMarshaller<Double> marshaller = MoreMetadata.DOUBLE_MARSHALLER;
105+
String s = marshaller.toAsciiString(42.42);
106+
assertThat(s).isEqualTo("42.42");
107+
108+
Double d = marshaller.parseAsciiString(s);
109+
assertThat(d).isEqualTo(42.42);
110+
}
111+
112+
@Test
113+
public void changeMetadataKeyType() {
114+
Metadata.Key<String> stringKey = Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER);
115+
Metadata.Key<Long> longKey = Metadata.Key.of("key", MoreMetadata.LONG_MARSHALLER);
116+
117+
Metadata metadata = new Metadata();
118+
metadata.put(stringKey, "12345");
119+
120+
Long bool = metadata.get(longKey);
121+
assertThat(bool).isEqualTo(12345);
122+
}
123+
124+
@Test
125+
public void rawJsonToTypedJson() {
126+
Metadata.Key<String> stringKey = Metadata.Key.of("key", Metadata.ASCII_STRING_MARSHALLER);
127+
Metadata.Key<Bar> barKey = Metadata.Key.of("key", MoreMetadata.JSON_MARSHALLER(Bar.class));
128+
129+
Metadata metadata = new Metadata();
130+
metadata.put(stringKey, "{'cheese': 'swiss', 'age': 42}");
131+
132+
Bar bar = metadata.get(barKey);
133+
assertThat(bar).isNotNull();
134+
assertThat(bar.cheese).isEqualTo("swiss");
135+
assertThat(bar.age).isEqualTo(42);
136+
}
137+
}

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
<grpc.version>1.6.1</grpc.version>
7171
<protoc.version>3.3.0</protoc.version> <!-- Same version as grpc-proto -->
7272
<slf4j.version>1.7.21</slf4j.version>
73+
<gson.version>2.7</gson.version> <!-- Same version as grpc-proto -->
7374
<mustache-java.version>0.9.4</mustache-java.version>
7475
<spring.version>4.2.0.RELEASE</spring.version>
7576

@@ -173,6 +174,12 @@
173174
<version>${slf4j.version}</version>
174175
<scope>provided</scope>
175176
</dependency>
177+
<dependency>
178+
<groupId>com.google.code.gson</groupId>
179+
<artifactId>gson</artifactId>
180+
<version>${gson.version}</version>
181+
<scope>provided</scope>
182+
</dependency>
176183
<!-- Test dependencies -->
177184
<dependency>
178185
<groupId>io.grpc</groupId>

0 commit comments

Comments
 (0)