/* * Copyright (c) 2019, Salesforce.com, Inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ package com.salesforce.grpc.contrib; import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import com.google.protobuf.GeneratedMessageV3; import com.google.protobuf.InvalidProtocolBufferException; import io.grpc.Metadata; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * {@code MoreMetadata} provides additional utilities for working with gRPC {@code Metadata}. */ //CHECKSTYLE:OFF: MethodName public final class MoreMetadata { private MoreMetadata() { } /** * A metadata marshaller that encodes objects as JSON using the google-gson library. * * <p>All non-ascii characters are unicode escaped to comply with {@code AsciiMarshaller}'s character range * requirements. * * @param clazz the type to serialize * @param <T> */ public static <T> Metadata.AsciiMarshaller<T> JSON_MARSHALLER(Class<T> clazz) { return new Metadata.AsciiMarshaller<T>() { private TypeToken<T> typeToken = TypeToken.of(clazz); private Gson gson = new Gson(); @Override public String toAsciiString(T value) { try { try (StringWriter sw = new StringWriter()) { gson.toJson(value, typeToken.getType(), new UnicodeEscapingAsciiWriter(sw)); return sw.toString(); } } catch (IOException e) { throw new IllegalArgumentException(e); } } @Override public T parseAsciiString(String serialized) { return gson.fromJson(serialized, typeToken.getType()); } }; } /** * See: https://github.com/google/gson/issues/388. */ private static final class UnicodeEscapingAsciiWriter extends Writer { private final Writer out; private UnicodeEscapingAsciiWriter(Writer out) { this.out = out; } @Override public void write(char[] buffer, int offset, int count) throws IOException { for (int i = 0; i < count; i++) { char c = buffer[i + offset]; if (c >= ' ' && c <= '~') { out.write(c); } else { out.write(String.format("\\u%04x", (int) c)); } } } @Override public void flush() throws IOException { out.flush(); } @Override public void close() throws IOException { out.close(); } } /** * A metadata marshaller that encodes objects as protobuf according to their proto IDL specification. * * @param clazz the type to serialize * @param <T> */ public static <T extends GeneratedMessageV3> Metadata.BinaryMarshaller<T> PROTOBUF_MARSHALLER(Class<T> clazz) { try { Method defaultInstance = clazz.getMethod("getDefaultInstance"); GeneratedMessageV3 instance = (GeneratedMessageV3) defaultInstance.invoke(null); return new Metadata.BinaryMarshaller<T>() { @Override public byte[] toBytes(T value) { return value.toByteArray(); } @SuppressWarnings("unchecked") @Override public T parseBytes(byte[] serialized) { try { return (T) instance.getParserForType().parseFrom(serialized); } catch (InvalidProtocolBufferException ipbe) { throw new IllegalArgumentException(ipbe); } } }; } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { throw new IllegalStateException(ex); } } /** * A metadata marshaller that encodes boolean values. */ public static final Metadata.AsciiMarshaller<Boolean> BOOLEAN_MARSHALLER = new Metadata.AsciiMarshaller<Boolean>() { @Override public String toAsciiString(Boolean value) { return value.toString(); } @Override public Boolean parseAsciiString(String serialized) { return Boolean.parseBoolean(serialized); } }; /** * A metadata marshaller that encodes integer-type values. */ public static final Metadata.AsciiMarshaller<Long> LONG_MARSHALLER = new Metadata.AsciiMarshaller<Long>() { @Override public String toAsciiString(Long value) { return value.toString(); } @Override public Long parseAsciiString(String serialized) { return Long.parseLong(serialized); } }; /** * A metadata marshaller that encodes floating-point-type values. */ public static final Metadata.AsciiMarshaller<Double> DOUBLE_MARSHALLER = new Metadata.AsciiMarshaller<Double>() { @Override public String toAsciiString(Double value) { return value.toString(); } @Override public Double parseAsciiString(String serialized) { return Double.parseDouble(serialized); } }; }