Java tutorial
/** * Copyright 2015-2016 The OpenZipkin Authors * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package me.j360.trace.core.internal; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.MalformedJsonException; import me.j360.trace.core.*; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.Inet6Address; import java.nio.ByteBuffer; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import static java.lang.Double.doubleToRawLongBits; import static me.j360.trace.core.internal.Buffer.*; import static me.j360.trace.core.internal.Util.assertionError; import static me.j360.trace.core.internal.Util.checkArgument; import static me.j360.trace.core.internal.Util.lowerHexToUnsignedLong; /** * This explicitly constructs instances of model classes via manual parsing for a number of * reasons. * * <ul> * <li>Eliminates the need to keep separate model classes for thrift vs json</li> * <li>Avoids magic field initialization which, can miss constructor guards</li> * <li>Allows us to safely re-use the json form in toString methods</li> * <li>Encourages logic to be based on the thrift shape of objects</li> * <li>Ensures the order and naming of the fields in json is stable</li> * </ul> * * <p> There is the up-front cost of creating this, and maintenance of this to consider. However, * this should be easy to justify as these objects don't change much at all. */ public final class JsonCodec implements Codec { static final JsonAdapter<Endpoint> ENDPOINT_ADAPTER = new JsonAdapter<Endpoint>() { @Override public Endpoint fromJson(JsonReader reader) throws IOException { Endpoint.Builder result = Endpoint.builder(); reader.beginObject(); while (reader.hasNext()) { String nextName = reader.nextName(); if (nextName.equals("serviceName")) { result.serviceName(reader.nextString()); } else if (nextName.equals("ipv4")) { String[] ipv4String = reader.nextString().split("\\.", 5); int ipv4 = 0; for (String part : ipv4String) { ipv4 = ipv4 << 8 | (Integer.parseInt(part) & 0xff); } result.ipv4(ipv4); } else if (nextName.equals("ipv6")) { String input = reader.nextString(); // Shouldn't hit DNS, because it's an IP string literal. result.ipv6(Inet6Address.getByName(input).getAddress()); } else if (nextName.equals("port")) { result.port((short) reader.nextInt()); } else { reader.skipValue(); } } reader.endObject(); return result.build(); } @Override public int sizeInBytes(Endpoint value) { int sizeInBytes = 0; sizeInBytes += asciiSizeInBytes(",\"endpoint\":{\"serviceName\":\""); sizeInBytes += jsonEscapedSizeInBytes(value.serviceName) + 1; // for end quote if (value.ipv4 != 0) { sizeInBytes += asciiSizeInBytes(",\"ipv4\":\""); sizeInBytes += asciiSizeInBytes(value.ipv4 >> 24 & 0xff) + 1; // for dot sizeInBytes += asciiSizeInBytes(value.ipv4 >> 16 & 0xff) + 1; // for dot sizeInBytes += asciiSizeInBytes(value.ipv4 >> 8 & 0xff) + 1; // for dot sizeInBytes += asciiSizeInBytes(value.ipv4 & 0xff) + 1; // for end quote } if (value.port != null && value.port != 0) { sizeInBytes += asciiSizeInBytes(",\"port\":") + asciiSizeInBytes(value.port & 0xffff); } if (value.ipv6 != null) { sizeInBytes += asciiSizeInBytes(",\"ipv6\":\"") + ipv6SizeInBytes(value.ipv6) + 1; } return ++sizeInBytes;// end curly-brace } @Override public void write(Endpoint value, Buffer b) { b.writeAscii(",\"endpoint\":{\"serviceName\":\""); b.writeJsonEscaped(value.serviceName).writeByte('"'); if (value.ipv4 != 0) { b.writeAscii(",\"ipv4\":\""); b.writeAscii(value.ipv4 >> 24 & 0xff).writeByte('.'); b.writeAscii(value.ipv4 >> 16 & 0xff).writeByte('.'); b.writeAscii(value.ipv4 >> 8 & 0xff).writeByte('.'); b.writeAscii(value.ipv4 & 0xff).writeByte('"'); } if (value.port != null && value.port != 0) { b.writeAscii(",\"port\":").writeAscii(value.port & 0xffff); } if (value.ipv6 != null) { b.writeAscii(",\"ipv6\":\"").writeIpV6(value.ipv6).writeByte('"'); } b.writeByte('}'); } }; static final JsonAdapter<Annotation> ANNOTATION_ADAPTER = new JsonAdapter<Annotation>() { @Override public Annotation fromJson(JsonReader reader) throws IOException { Annotation.Builder result = Annotation.builder(); reader.beginObject(); while (reader.hasNext()) { String nextName = reader.nextName(); if (nextName.equals("timestamp")) { result.timestamp(reader.nextLong()); } else if (nextName.equals("value")) { result.value(reader.nextString()); } else if (nextName.equals("endpoint") && reader.peek() != JsonToken.NULL) { result.endpoint(ENDPOINT_ADAPTER.fromJson(reader)); } else { reader.skipValue(); } } reader.endObject(); return result.build(); } @Override public int sizeInBytes(Annotation value) { int sizeInBytes = 0; sizeInBytes += asciiSizeInBytes("{\"timestamp\":") + asciiSizeInBytes(value.timestamp); sizeInBytes += asciiSizeInBytes(",\"value\":\"") + jsonEscapedSizeInBytes(value.value) + 1; if (value.endpoint != null) sizeInBytes += ENDPOINT_ADAPTER.sizeInBytes(value.endpoint); return ++sizeInBytes;// end curly-brace } @Override public void write(Annotation value, Buffer b) { b.writeAscii("{\"timestamp\":").writeAscii(value.timestamp); b.writeAscii(",\"value\":\"").writeJsonEscaped(value.value).writeByte('"'); if (value.endpoint != null) ENDPOINT_ADAPTER.write(value.endpoint, b); b.writeByte('}'); } }; static final JsonAdapter<BinaryAnnotation> BINARY_ANNOTATION_ADAPTER = new JsonAdapter<BinaryAnnotation>() { @Override public BinaryAnnotation fromJson(JsonReader reader) throws IOException { BinaryAnnotation.Builder result = BinaryAnnotation.builder(); String number = null; String string = null; BinaryAnnotation.Type type = BinaryAnnotation.Type.STRING; reader.beginObject(); while (reader.hasNext()) { String nextName = reader.nextName(); if (nextName.equals("key")) { result.key(reader.nextString()); } else if (nextName.equals("value")) { switch (reader.peek()) { case BOOLEAN: type = BinaryAnnotation.Type.BOOL; result.value(reader.nextBoolean() ? new byte[] { 1 } : new byte[] { 0 }); break; case STRING: string = reader.nextString(); break; case NUMBER: number = reader.nextString(); break; default: throw new MalformedJsonException("Expected value to be a boolean, string or number but was " + reader.peek() + " at path " + reader.getPath()); } } else if (nextName.equals("type")) { type = BinaryAnnotation.Type.valueOf(reader.nextString()); } else if (nextName.equals("endpoint") && reader.peek() != JsonToken.NULL) { result.endpoint(ENDPOINT_ADAPTER.fromJson(reader)); } else { reader.skipValue(); } } reader.endObject(); result.type(type); switch (type) { case BOOL: return result.build(); case STRING: return result.value(string.getBytes(Util.UTF_8)).build(); case BYTES: return result.value(Base64.decode(string)).build(); default: break; } final byte[] value; if (type == BinaryAnnotation.Type.I16) { short v = Short.parseShort(number); value = ByteBuffer.allocate(2).putShort(0, v).array(); } else if (type == BinaryAnnotation.Type.I32) { int v = Integer.parseInt(number); value = ByteBuffer.allocate(4).putInt(0, v).array(); } else if (type == BinaryAnnotation.Type.I64 || type == BinaryAnnotation.Type.DOUBLE) { long v = type == BinaryAnnotation.Type.I64 ? Long.parseLong(number) : doubleToRawLongBits(Double.parseDouble(number)); value = ByteBuffer.allocate(8).putLong(0, v).array(); } else { throw new AssertionError("BinaryAnnotationType " + type + " was added, but not handled"); } return result.value(value).build(); } @Override public int sizeInBytes(BinaryAnnotation value) { int sizeInBytes = 0; sizeInBytes += asciiSizeInBytes("{\"key\":\"") + jsonEscapedSizeInBytes(value.key); sizeInBytes += asciiSizeInBytes("\",\"value\":"); switch (value.type) { case BOOL: sizeInBytes += asciiSizeInBytes(value.value[0] == 1 ? "true" : "false"); break; case STRING: sizeInBytes += jsonEscapedSizeInBytes(value.value) + 2; //for quotes break; case BYTES: sizeInBytes += base64UrlSizeInBytes(value.value) + 2; //for quotes break; case I16: sizeInBytes += asciiSizeInBytes(ByteBuffer.wrap(value.value).getShort()); break; case I32: sizeInBytes += asciiSizeInBytes(ByteBuffer.wrap(value.value).getInt()); break; case I64: sizeInBytes += asciiSizeInBytes(ByteBuffer.wrap(value.value).getLong()); break; case DOUBLE: double wrapped = Double.longBitsToDouble(ByteBuffer.wrap(value.value).getLong()); sizeInBytes += asciiSizeInBytes(Double.toString(wrapped)); break; default: } if (value.type != BinaryAnnotation.Type.STRING && value.type != BinaryAnnotation.Type.BOOL) { sizeInBytes += asciiSizeInBytes(",\"type\":\"") + utf8SizeInBytes(value.type.name()) + 1; } if (value.endpoint != null) sizeInBytes += ENDPOINT_ADAPTER.sizeInBytes(value.endpoint); return ++sizeInBytes;// end curly-brace } @Override public void write(BinaryAnnotation value, Buffer b) { b.writeAscii("{\"key\":\"").writeJsonEscaped(value.key); b.writeAscii("\",\"value\":"); switch (value.type) { case BOOL: b.writeAscii(value.value[0] == 1 ? "true" : "false"); break; case STRING: b.writeByte('"').writeJsonEscaped(value.value).writeByte('"'); break; case BYTES: b.writeByte('"').writeBase64Url(value.value).writeByte('"'); break; case I16: b.writeAscii(ByteBuffer.wrap(value.value).getShort()); break; case I32: b.writeAscii(ByteBuffer.wrap(value.value).getInt()); break; case I64: b.writeAscii(ByteBuffer.wrap(value.value).getLong()); break; case DOUBLE: double wrapped = Double.longBitsToDouble(ByteBuffer.wrap(value.value).getLong()); b.writeAscii(Double.toString(wrapped)); break; default: } if (value.type != BinaryAnnotation.Type.STRING && value.type != BinaryAnnotation.Type.BOOL) { b.writeAscii(",\"type\":\"").writeAscii(value.type.name()).writeByte('"'); } if (value.endpoint != null) ENDPOINT_ADAPTER.write(value.endpoint, b); b.writeByte('}'); } }; static final JsonAdapter<Span> SPAN_ADAPTER = new JsonAdapter<Span>() { @Override public Span fromJson(JsonReader reader) throws IOException { Span.Builder result = Span.builder(); reader.beginObject(); while (reader.hasNext()) { String nextName = reader.nextName(); if (nextName.equals("traceId")) { result.traceId(lowerHexToUnsignedLong(reader.nextString())); } else if (nextName.equals("name")) { result.name(reader.nextString()); } else if (nextName.equals("id")) { result.id(lowerHexToUnsignedLong(reader.nextString())); } else if (nextName.equals("parentId") && reader.peek() != JsonToken.NULL) { result.parentId(lowerHexToUnsignedLong(reader.nextString())); } else if (nextName.equals("timestamp") && reader.peek() != JsonToken.NULL) { result.timestamp(reader.nextLong()); } else if (nextName.equals("duration") && reader.peek() != JsonToken.NULL) { result.duration(reader.nextLong()); } else if (nextName.equals("annotations")) { reader.beginArray(); while (reader.hasNext()) { result.addAnnotation(ANNOTATION_ADAPTER.fromJson(reader)); } reader.endArray(); } else if (nextName.equals("binaryAnnotations")) { reader.beginArray(); while (reader.hasNext()) { result.addBinaryAnnotation(BINARY_ANNOTATION_ADAPTER.fromJson(reader)); } reader.endArray(); } else if (nextName.equals("debug") && reader.peek() != JsonToken.NULL) { if (reader.nextBoolean()) result.debug(true); } else { reader.skipValue(); } } reader.endObject(); return result.build(); } @Override public int sizeInBytes(Span value) { int sizeInBytes = 0; sizeInBytes += asciiSizeInBytes("{\"traceId\":\"") + 16; // fixed-width hex sizeInBytes += asciiSizeInBytes("\",\"id\":\"") + 16; sizeInBytes += asciiSizeInBytes("\",\"name\":\"") + jsonEscapedSizeInBytes(value.name) + 1; if (value.parentId != null) { sizeInBytes += asciiSizeInBytes(",\"parentId\":\"") + 16 + 1; } if (value.timestamp != null) { sizeInBytes += asciiSizeInBytes(",\"timestamp\":") + asciiSizeInBytes(value.timestamp); } if (value.duration != null) { sizeInBytes += asciiSizeInBytes(",\"duration\":") + asciiSizeInBytes(value.duration); } if (!value.annotations.isEmpty()) { sizeInBytes += asciiSizeInBytes(",\"annotations\":"); sizeInBytes += JsonCodec.sizeInBytes(ANNOTATION_ADAPTER, value.annotations); } if (!value.binaryAnnotations.isEmpty()) { sizeInBytes += asciiSizeInBytes(",\"binaryAnnotations\":"); sizeInBytes += JsonCodec.sizeInBytes(BINARY_ANNOTATION_ADAPTER, value.binaryAnnotations); } if (value.debug != null && value.debug) { sizeInBytes += asciiSizeInBytes(",\"debug\":true"); } return ++sizeInBytes;// end curly-brace } @Override public void write(Span value, Buffer b) { b.writeAscii("{\"traceId\":\"").writeLowerHex(value.traceId); b.writeAscii("\",\"id\":\"").writeLowerHex(value.id); b.writeAscii("\",\"name\":\"").writeJsonEscaped(value.name).writeByte('"'); if (value.parentId != null) { b.writeAscii(",\"parentId\":\"").writeLowerHex(value.parentId).writeByte('"'); } if (value.timestamp != null) { b.writeAscii(",\"timestamp\":").writeAscii(value.timestamp); } if (value.duration != null) { b.writeAscii(",\"duration\":").writeAscii(value.duration); } if (!value.annotations.isEmpty()) { b.writeAscii(",\"annotations\":"); writeList(ANNOTATION_ADAPTER, value.annotations, b); } if (!value.binaryAnnotations.isEmpty()) { b.writeAscii(",\"binaryAnnotations\":"); writeList(BINARY_ANNOTATION_ADAPTER, value.binaryAnnotations, b); } if (value.debug != null && value.debug) { b.writeAscii(",\"debug\":true"); } b.writeByte('}'); } @Override public String toString() { return "Span"; } }; @Override public Span readSpan(byte[] bytes) { checkArgument(bytes.length > 0, "Empty input reading Span"); try { return SPAN_ADAPTER.fromJson(jsonReader(bytes)); } catch (Exception e) { throw exceptionReading("Span", bytes, e); } } @Override public int sizeInBytes(Span value) { return SPAN_ADAPTER.sizeInBytes(value); } @Override public byte[] writeSpan(Span value) { return write(SPAN_ADAPTER, value); } @Override public List<Span> readSpans(byte[] bytes) { checkArgument(bytes.length > 0, "Empty input reading List<Span>"); return readList(SPAN_ADAPTER, bytes); } @Override public byte[] writeSpans(List<Span> value) { if (value.isEmpty()) return new byte[] { '[', ']' }; Buffer result = new Buffer(sizeInBytes(SPAN_ADAPTER, value)); writeList(SPAN_ADAPTER, value, result); return result.toByteArray(); } @Override public byte[] writeTraces(List<List<Span>> traces) { // Get the encoded size of the nested list so that we don't need to grow the buffer int sizeInBytes = overheadInBytes(traces); for (int i = 0; i < traces.size(); i++) { List<Span> spans = traces.get(i); sizeInBytes += overheadInBytes(spans); for (int j = 0; j < spans.size(); j++) { sizeInBytes += SPAN_ADAPTER.sizeInBytes(spans.get(j)); } } Buffer out = new Buffer(sizeInBytes); out.writeByte('['); // start list of traces for (Iterator<List<Span>> trace = traces.iterator(); trace.hasNext();) { writeList(SPAN_ADAPTER, trace.next(), out); if (trace.hasNext()) out.writeByte(','); } out.writeByte(']'); // stop list of traces return out.toByteArray(); } public List<List<Span>> readTraces(byte[] bytes) { JsonReader reader = jsonReader(bytes); List<List<Span>> result = new LinkedList<List<Span>>(); // cause we don't know how long it will be try { reader.beginArray(); while (reader.hasNext()) { reader.beginArray(); List<Span> trace = new LinkedList<Span>(); // cause we don't know how long it will be while (reader.hasNext()) { trace.add(SPAN_ADAPTER.fromJson(reader)); } reader.endArray(); result.add(trace); } reader.endArray(); return result; } catch (Exception e) { throw exceptionReading("List<List<Span>>", bytes, e); } } static final JsonAdapter<DependencyLink> DEPENDENCY_LINK_ADAPTER = new JsonAdapter<DependencyLink>() { @Override public DependencyLink fromJson(JsonReader reader) throws IOException { DependencyLink.Builder result = DependencyLink.builder(); reader.beginObject(); while (reader.hasNext()) { String nextName = reader.nextName(); if (nextName.equals("parent")) { result.parent(reader.nextString()); } else if (nextName.equals("child")) { result.child(reader.nextString()); } else if (nextName.equals("callCount")) { result.callCount(reader.nextLong()); } else { reader.skipValue(); } } reader.endObject(); return result.build(); } @Override public int sizeInBytes(DependencyLink value) { int sizeInBytes = 0; sizeInBytes += asciiSizeInBytes("{\"parent\":\"") + jsonEscapedSizeInBytes(value.parent); sizeInBytes += asciiSizeInBytes("\",\"child\":\"") + jsonEscapedSizeInBytes(value.child); sizeInBytes += asciiSizeInBytes("\",\"callCount\":") + asciiSizeInBytes(value.callCount); return ++sizeInBytes;// end curly-brace } @Override public void write(DependencyLink value, Buffer b) { b.writeAscii("{\"parent\":\"").writeJsonEscaped(value.parent); b.writeAscii("\",\"child\":\"").writeJsonEscaped(value.child); b.writeAscii("\",\"callCount\":").writeAscii(value.callCount).writeByte('}'); } @Override public String toString() { return "DependencyLink"; } }; @Override public DependencyLink readDependencyLink(byte[] bytes) { checkArgument(bytes.length > 0, "Empty input reading DependencyLink"); try { return DEPENDENCY_LINK_ADAPTER.fromJson(jsonReader(bytes)); } catch (Exception e) { throw exceptionReading("Span", bytes, e); } } @Override public byte[] writeDependencyLink(DependencyLink value) { return write(DEPENDENCY_LINK_ADAPTER, value); } @Override public List<DependencyLink> readDependencyLinks(byte[] bytes) { checkArgument(bytes.length > 0, "Empty input reading List<DependencyLink>"); return readList(DEPENDENCY_LINK_ADAPTER, bytes); } @Override public byte[] writeDependencyLinks(List<DependencyLink> value) { Buffer result = new Buffer(sizeInBytes(DEPENDENCY_LINK_ADAPTER, value)); writeList(DEPENDENCY_LINK_ADAPTER, value, result); return result.toByteArray(); } static final JsonAdapter<String> STRING_ADAPTER = new JsonAdapter<String>() { @Override public String fromJson(JsonReader reader) throws IOException { return reader.nextString(); } @Override public int sizeInBytes(String value) { return jsonEscapedSizeInBytes(value) + 2; // For quotes } @Override public void write(String value, Buffer buffer) { buffer.writeByte('"').writeJsonEscaped(value).writeByte('"'); } }; public List<String> readStrings(byte[] bytes) { checkArgument(bytes.length > 0, "Empty input reading List<String>"); return readList(STRING_ADAPTER, bytes); } public byte[] writeStrings(List<String> value) { Buffer result = new Buffer(sizeInBytes(STRING_ADAPTER, value)); writeList(STRING_ADAPTER, value, result); return result.toByteArray(); } static <T> List<T> readList(JsonAdapter<T> adapter, byte[] bytes) { JsonReader reader = jsonReader(bytes); List<T> result; try { reader.beginArray(); if (reader.hasNext()) { result = new LinkedList<T>(); // cause we don't know how long it will be } else { result = Collections.emptyList(); } while (reader.hasNext()) { result.add(adapter.fromJson(reader)); } reader.endArray(); return result; } catch (Exception e) { throw exceptionReading("List<" + adapter + ">", bytes, e); } } private static JsonReader jsonReader(byte[] bytes) { return new JsonReader(new InputStreamReader(new ByteArrayInputStream(bytes))); } /** Inability to encode is a programming bug. */ static <T> byte[] write(Buffer.Writer<T> writer, T value) { Buffer b = new Buffer(writer.sizeInBytes(value)); try { writer.write(value, b); } catch (RuntimeException e) { throw assertionError("Could not write " + value + " as json", e); } return b.toByteArray(); } static <T> int sizeInBytes(Buffer.Writer<T> writer, List<T> value) { int sizeInBytes = overheadInBytes(value); for (int i = 0, length = value.size(); i < length; i++) { sizeInBytes += writer.sizeInBytes(value.get(i)); } return sizeInBytes; } static <T> int overheadInBytes(List<T> value) { int sizeInBytes = 2; // brackets if (value.size() > 1) sizeInBytes += value.size() - 1; // comma to join elements return sizeInBytes; } static <T> void writeList(Buffer.Writer<T> writer, List<T> value, Buffer b) { b.writeByte('['); for (int i = 0, length = value.size(); i < length;) { writer.write(value.get(i++), b); if (i < length) b.writeByte(','); } b.writeByte(']'); } static IllegalArgumentException exceptionReading(String type, byte[] bytes, Exception e) { String cause = e.getMessage() == null ? "Error" : e.getMessage(); if (cause.indexOf("malformed") != -1) cause = "Malformed"; String message = String.format("%s reading %s from json: %s", cause, type, new String(bytes, Util.UTF_8)); throw new IllegalArgumentException(message, e); } static abstract class JsonAdapter<T> implements Buffer.Writer<T> { abstract T fromJson(JsonReader reader) throws IOException; } }