diff options
Diffstat (limited to '')
107 files changed, 16431 insertions, 0 deletions
diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/AsyncProcessFunction.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/AsyncProcessFunction.java new file mode 100644 index 000000000..483c8d054 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/AsyncProcessFunction.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import org.apache.thrift.async.AsyncMethodCallback; +import org.apache.thrift.protocol.TMessage; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.server.AbstractNonblockingServer; + +public abstract class AsyncProcessFunction<I, T extends TBase, R> { + final String methodName; + + public AsyncProcessFunction(String methodName) { + this.methodName = methodName; + } + + protected abstract boolean isOneway(); + + public abstract void start(I iface, T args, AsyncMethodCallback<R> resultHandler) throws TException; + + public abstract T getEmptyArgsInstance(); + + public abstract AsyncMethodCallback<R> getResultHandler(final AbstractNonblockingServer.AsyncFrameBuffer fb, int seqid); + + public String getMethodName() { + return methodName; + } + + public void sendResponse(final AbstractNonblockingServer.AsyncFrameBuffer fb, final TSerializable result, final byte type, final int seqid) throws TException { + TProtocol oprot = fb.getOutputProtocol(); + + oprot.writeMessageBegin(new TMessage(getMethodName(), type, seqid)); + result.write(oprot); + oprot.writeMessageEnd(); + oprot.getTransport().flush(); + + fb.responseReady(); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/EncodingUtils.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/EncodingUtils.java new file mode 100644 index 000000000..bf14ef57e --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/EncodingUtils.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +/** + * Utility methods for use when encoding/decoding raw data as byte arrays. + */ +public class EncodingUtils { + + /** + * Encode <code>integer</code> as a series of 4 bytes into <code>buf</code> + * starting at position 0 within that buffer. + * + * @param integer + * The integer to encode. + * @param buf + * The buffer to write to. + */ + public static final void encodeBigEndian(final int integer, final byte[] buf) { + encodeBigEndian(integer, buf, 0); + } + + /** + * Encode <code>integer</code> as a series of 4 bytes into <code>buf</code> + * starting at position <code>offset</code>. + * + * @param integer + * The integer to encode. + * @param buf + * The buffer to write to. + * @param offset + * The offset within <code>buf</code> to start the encoding. + */ + public static final void encodeBigEndian(final int integer, final byte[] buf, int offset) { + buf[offset] = (byte) (0xff & (integer >> 24)); + buf[offset + 1] = (byte) (0xff & (integer >> 16)); + buf[offset + 2] = (byte) (0xff & (integer >> 8)); + buf[offset + 3] = (byte) (0xff & (integer)); + } + + /** + * Decode a series of 4 bytes from <code>buf</code>, starting at position 0, + * and interpret them as an integer. + * + * @param buf + * The buffer to read from. + * @return An integer, as read from the buffer. + */ + public static final int decodeBigEndian(final byte[] buf) { + return decodeBigEndian(buf, 0); + } + + /** + * Decode a series of 4 bytes from <code>buf</code>, start at + * <code>offset</code>, and interpret them as an integer. + * + * @param buf + * The buffer to read from. + * @param offset + * The offset with <code>buf</code> to start the decoding. + * @return An integer, as read from the buffer. + */ + public static final int decodeBigEndian(final byte[] buf, int offset) { + return ((buf[offset] & 0xff) << 24) | ((buf[offset + 1] & 0xff) << 16) + | ((buf[offset + 2] & 0xff) << 8) | ((buf[offset + 3] & 0xff)); + } + + /** + * Bitfield utilities. + * Returns true if the bit at position is set in v. + */ + public static final boolean testBit(byte v, int position) { + return testBit((int)v, position); + } + + public static final boolean testBit(short v, int position) { + return testBit((int)v, position); + } + + public static final boolean testBit(int v, int position) { + return (v & (1 << position)) != 0; + } + + public static final boolean testBit(long v, int position) { + return (v & (1L << position)) != 0L; + } + + /** + * Returns v, with the bit at position set to zero. + */ + public static final byte clearBit(byte v, int position) { + return (byte)clearBit((int)v, position); + } + + public static final short clearBit(short v, int position) { + return (short)clearBit((int)v, position); + } + + public static final int clearBit(int v, int position) { + return v & ~(1 << position); + } + + public static final long clearBit(long v, int position) { + return v & ~(1L << position); + } + + /** + * Returns v, with the bit at position set to 1 or 0 depending on value. + */ + public static final byte setBit(byte v, int position, boolean value) { + return (byte)setBit((int)v, position, value); + } + + public static final short setBit(short v, int position, boolean value) { + return (short)setBit((int)v, position, value); + } + + public static final int setBit(int v, int position, boolean value) { + if(value) + return v | (1 << position); + else + return clearBit(v, position); + } + + public static final long setBit(long v, int position, boolean value) { + if(value) + return v | (1L << position); + else + return clearBit(v, position); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/Option.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/Option.java new file mode 100644 index 000000000..d5cd309e8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/Option.java @@ -0,0 +1,125 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +/** + * Implementation of the Option type pattern + */ +public abstract class Option<T> { + + @SuppressWarnings("rawtypes") + private static final Option NONE = new None(); + + /** + * Whether the Option is defined or not + * @return + * true if the Option is defined (of type Some) + * false if the Option is not defined (of type None) + */ + public abstract boolean isDefined(); + + /** + * Get the value of the Option (if it is defined) + * @return the value + * @throws IllegalStateException if called on a None + */ + public abstract T get(); + + /** + * Get the contained value (if defined) or else return a default value + * @param other what to return if the value is not defined (a None) + * @return either the value, or other if the value is not defined + */ + public T or(T other) { + if (isDefined()) { + return get(); + } else { + return other; + } + } + /** + * The None type, representing an absent value (instead of "null") + */ + public static class None<T> extends Option<T> { + public boolean isDefined() { + return false; + } + + public T get() { + throw new IllegalStateException("Cannot call get() on None"); + } + + public String toString() { + return "None"; + } + } + + /** + * The Some type, representing an existence of some value + * @param <T> The type of value + */ + public static class Some<T> extends Option<T> { + private final T value; + public Some(T value) { + this.value = value; + } + + public boolean isDefined() { + return true; + } + + public T get() { + return value; + } + + public String toString() { + return "Some(" + value + ")"; + } + } + + /** + * Wraps value in an Option type, depending on whether or not value is null + * @param value + * @param <T> type of value + * @return Some(value) if value is not null, None if value is null + */ + public static <T> Option<T> fromNullable(T value) { + if (value != null) { + return some(value); + } else { + return none(); + } + } + + /** + * Wrap value in a Some type (NB! value must not be null!) + * @param value + * @param <T> type of value + * @return a new Some(value) + */ + public static <T> Some<T> some(T value) { + return new Some<T>(value); + } + + @SuppressWarnings("unchecked") + public static <T> None<T> none() { + return (None<T>) NONE; + } +}
\ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/ProcessFunction.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/ProcessFunction.java new file mode 100644 index 000000000..e6213dfa1 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/ProcessFunction.java @@ -0,0 +1,88 @@ +package org.apache.thrift; + +import org.apache.thrift.protocol.TMessage; +import org.apache.thrift.protocol.TMessageType; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.protocol.TProtocolException; +import org.apache.thrift.transport.TTransportException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class ProcessFunction<I, T extends TBase> { + private final String methodName; + + private static final Logger LOGGER = LoggerFactory.getLogger(ProcessFunction.class.getName()); + + public ProcessFunction(String methodName) { + this.methodName = methodName; + } + + public final void process(int seqid, TProtocol iprot, TProtocol oprot, I iface) throws TException { + T args = getEmptyArgsInstance(); + try { + args.read(iprot); + } catch (TProtocolException e) { + iprot.readMessageEnd(); + TApplicationException x = new TApplicationException(TApplicationException.PROTOCOL_ERROR, e.getMessage()); + oprot.writeMessageBegin(new TMessage(getMethodName(), TMessageType.EXCEPTION, seqid)); + x.write(oprot); + oprot.writeMessageEnd(); + oprot.getTransport().flush(); + return; + } + iprot.readMessageEnd(); + TSerializable result = null; + byte msgType = TMessageType.REPLY; + + try { + result = getResult(iface, args); + } catch (TTransportException ex) { + LOGGER.error("Transport error while processing " + getMethodName(), ex); + throw ex; + } catch (TApplicationException ex) { + LOGGER.error("Internal application error processing " + getMethodName(), ex); + result = ex; + msgType = TMessageType.EXCEPTION; + } catch (Exception ex) { + LOGGER.error("Internal error processing " + getMethodName(), ex); + if(rethrowUnhandledExceptions()) throw new RuntimeException(ex.getMessage(), ex); + if(!isOneway()) { + result = new TApplicationException(TApplicationException.INTERNAL_ERROR, + "Internal error processing " + getMethodName()); + msgType = TMessageType.EXCEPTION; + } + } + + if(!isOneway()) { + oprot.writeMessageBegin(new TMessage(getMethodName(), msgType, seqid)); + result.write(oprot); + oprot.writeMessageEnd(); + oprot.getTransport().flush(); + } + } + + private void handleException(int seqid, TProtocol oprot) throws TException { + if (!isOneway()) { + TApplicationException x = new TApplicationException(TApplicationException.INTERNAL_ERROR, + "Internal error processing " + getMethodName()); + oprot.writeMessageBegin(new TMessage(getMethodName(), TMessageType.EXCEPTION, seqid)); + x.write(oprot); + oprot.writeMessageEnd(); + oprot.getTransport().flush(); + } + } + + protected boolean rethrowUnhandledExceptions(){ + return false; + } + + protected abstract boolean isOneway(); + + public abstract TBase getResult(I iface, T args) throws TException; + + public abstract T getEmptyArgsInstance(); + + public String getMethodName() { + return methodName; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TApplicationException.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TApplicationException.java new file mode 100644 index 000000000..4d693d9ce --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TApplicationException.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import org.apache.thrift.protocol.TField; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.protocol.TProtocolUtil; +import org.apache.thrift.protocol.TStruct; +import org.apache.thrift.protocol.TType; + +/** + * Application level exception + * + */ +public class TApplicationException extends TException implements TSerializable { + + private static final TStruct TAPPLICATION_EXCEPTION_STRUCT = new TStruct("TApplicationException"); + private static final TField MESSAGE_FIELD = new TField("message", TType.STRING, (short)1); + private static final TField TYPE_FIELD = new TField("type", TType.I32, (short)2); + + private static final long serialVersionUID = 1L; + + public static final int UNKNOWN = 0; + public static final int UNKNOWN_METHOD = 1; + public static final int INVALID_MESSAGE_TYPE = 2; + public static final int WRONG_METHOD_NAME = 3; + public static final int BAD_SEQUENCE_ID = 4; + public static final int MISSING_RESULT = 5; + public static final int INTERNAL_ERROR = 6; + public static final int PROTOCOL_ERROR = 7; + public static final int INVALID_TRANSFORM = 8; + public static final int INVALID_PROTOCOL = 9; + public static final int UNSUPPORTED_CLIENT_TYPE = 10; + + protected int type_ = UNKNOWN; + private String message_ = null; + + public TApplicationException() { + super(); + } + + public TApplicationException(int type) { + super(); + type_ = type; + } + + public TApplicationException(int type, String message) { + super(message); + type_ = type; + } + + public TApplicationException(String message) { + super(message); + } + + public int getType() { + return type_; + } + + @Override + public String getMessage() { + if (message_ == null) { + return super.getMessage(); + } + else { + return message_; + } + } + + public void read(TProtocol iprot) throws TException + { + TField field; + iprot.readStructBegin(); + + String message = null; + int type = UNKNOWN; + + while (true) { + field = iprot.readFieldBegin(); + if (field.type == TType.STOP) { + break; + } + switch (field.id) { + case 1: + if (field.type == TType.STRING) { + message = iprot.readString(); + } else { + TProtocolUtil.skip(iprot, field.type); + } + break; + case 2: + if (field.type == TType.I32) { + type = iprot.readI32(); + } else { + TProtocolUtil.skip(iprot, field.type); + } + break; + default: + TProtocolUtil.skip(iprot, field.type); + break; + } + iprot.readFieldEnd(); + } + iprot.readStructEnd(); + type_ = type; + message_ = message; + } + + /** + * Convenience factory method for constructing a TApplicationException given a TProtocol input + */ + public static TApplicationException readFrom(TProtocol iprot) throws TException + { + TApplicationException result = new TApplicationException(); + result.read(iprot); + return result; + } + + public void write(TProtocol oprot) throws TException + { + oprot.writeStructBegin(TAPPLICATION_EXCEPTION_STRUCT); + if (getMessage() != null) { + oprot.writeFieldBegin(MESSAGE_FIELD); + oprot.writeString(getMessage()); + oprot.writeFieldEnd(); + } + oprot.writeFieldBegin(TYPE_FIELD); + oprot.writeI32(type_); + oprot.writeFieldEnd(); + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TAsyncProcessor.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TAsyncProcessor.java new file mode 100644 index 000000000..66f768896 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TAsyncProcessor.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import org.apache.thrift.server.AbstractNonblockingServer.AsyncFrameBuffer; + +public interface TAsyncProcessor { + /** + * Process a single frame. + + * <b>Note:</b> Implementations must call fb.responseReady() once processing + * is complete + * + * @throws TException if the frame cannot be processed + */ + public void process(final AsyncFrameBuffer fb) throws TException; +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TBase.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TBase.java new file mode 100644 index 000000000..e1489d530 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TBase.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import java.io.Serializable; + +import org.apache.thrift.protocol.TProtocol; + +/** + * Generic base interface for generated Thrift objects. + * + */ +public interface TBase<T extends TBase<T,F>, F extends TFieldIdEnum> extends Comparable<T>, TSerializable, Serializable { + + /** + * Get the F instance that corresponds to fieldId. + */ + public F fieldForId(int fieldId); + + /** + * Check if a field is currently set or unset. + * + * @param field + */ + public boolean isSet(F field); + + /** + * Get a field's value by field variable. Primitive types will be wrapped in + * the appropriate "boxed" types. + * + * @param field + */ + public Object getFieldValue(F field); + + /** + * Set a field's value by field variable. Primitive types must be "boxed" in + * the appropriate object wrapper type. + * + * @param field + */ + public void setFieldValue(F field, Object value); + + public T deepCopy(); + + /** + * Return to the state of having just been initialized, as though you had just + * called the default constructor. + */ + public void clear(); +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TBaseAsyncProcessor.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TBaseAsyncProcessor.java new file mode 100644 index 000000000..f13f068ef --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TBaseAsyncProcessor.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import org.apache.thrift.protocol.*; +import org.apache.thrift.async.AsyncMethodCallback; + +import org.apache.thrift.server.AbstractNonblockingServer.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.Map; + +public class TBaseAsyncProcessor<I> implements TAsyncProcessor, TProcessor { + protected final Logger LOGGER = LoggerFactory.getLogger(getClass().getName()); + + final I iface; + final Map<String,AsyncProcessFunction<I, ? extends TBase,?>> processMap; + + public TBaseAsyncProcessor(I iface, Map<String, AsyncProcessFunction<I, ? extends TBase,?>> processMap) { + this.iface = iface; + this.processMap = processMap; + } + + public Map<String,AsyncProcessFunction<I, ? extends TBase,?>> getProcessMapView() { + return Collections.unmodifiableMap(processMap); + } + + public void process(final AsyncFrameBuffer fb) throws TException { + + final TProtocol in = fb.getInputProtocol(); + final TProtocol out = fb.getOutputProtocol(); + + //Find processing function + final TMessage msg = in.readMessageBegin(); + AsyncProcessFunction fn = processMap.get(msg.name); + if (fn == null) { + TProtocolUtil.skip(in, TType.STRUCT); + in.readMessageEnd(); + + TApplicationException x = new TApplicationException(TApplicationException.UNKNOWN_METHOD, + "Invalid method name: '" + msg.name + "'"); + LOGGER.debug("Invalid method name", x); + + // this means it is a two-way request, so we can send a reply + if (msg.type == TMessageType.CALL) { + out.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid)); + x.write(out); + out.writeMessageEnd(); + out.getTransport().flush(); + } + fb.responseReady(); + return; + } + + //Get Args + TBase args = fn.getEmptyArgsInstance(); + + try { + args.read(in); + } catch (TProtocolException e) { + in.readMessageEnd(); + + TApplicationException x = new TApplicationException(TApplicationException.PROTOCOL_ERROR, + e.getMessage()); + LOGGER.debug("Could not retrieve function arguments", x); + + if (!fn.isOneway()) { + out.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid)); + x.write(out); + out.writeMessageEnd(); + out.getTransport().flush(); + } + fb.responseReady(); + return; + } + in.readMessageEnd(); + + if (fn.isOneway()) { + fb.responseReady(); + } + + //start off processing function + AsyncMethodCallback resultHandler = fn.getResultHandler(fb, msg.seqid); + try { + fn.start(iface, args, resultHandler); + } catch (Exception e) { + LOGGER.debug("Exception handling function", e); + resultHandler.onError(e); + } + return; + } + + @Override + public void process(TProtocol in, TProtocol out) throws TException { + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TBaseHelper.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TBaseHelper.java new file mode 100644 index 000000000..6f6c6ebf5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TBaseHelper.java @@ -0,0 +1,297 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import java.io.Serializable; +import java.nio.ByteBuffer; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; + +public final class TBaseHelper { + + private TBaseHelper(){} + + private static final Comparator comparator = new NestedStructureComparator(); + + public static int compareTo(Object o1, Object o2) { + if (o1 instanceof Comparable) { + return compareTo((Comparable)o1, (Comparable)o2); + } else if (o1 instanceof List) { + return compareTo((List)o1, (List)o2); + } else if (o1 instanceof Set) { + return compareTo((Set)o1, (Set)o2); + } else if (o1 instanceof Map) { + return compareTo((Map)o1, (Map)o2); + } else if (o1 instanceof byte[]) { + return compareTo((byte[])o1, (byte[])o2); + } else { + throw new IllegalArgumentException("Cannot compare objects of type " + o1.getClass()); + } + } + + public static int compareTo(boolean a, boolean b) { + return Boolean.compare(a, b); + } + + public static int compareTo(byte a, byte b) { + return Byte.compare(a, b); + } + + public static int compareTo(short a, short b) { + return Short.compare(a,b); + } + + public static int compareTo(int a, int b) { + return Integer.compare(a, b); + } + + public static int compareTo(long a, long b) { + return Long.compare(a, b); + } + + public static int compareTo(double a, double b) { + return Double.compare(a, b); + } + + public static int compareTo(String a, String b) { + return a.compareTo(b); + } + + public static int compareTo(byte[] a, byte[] b) { + int compare = compareTo(a.length, b.length); + if (compare == 0) { + for (int i = 0; i < a.length; i++) { + compare = compareTo(a[i], b[i]); + if (compare != 0) { + break; + } + } + } + return compare; + } + + public static int compareTo(Comparable a, Comparable b) { + return a.compareTo(b); + } + + public static int compareTo(List a, List b) { + int compare = compareTo(a.size(), b.size()); + if (compare == 0) { + for (int i = 0; i < a.size(); i++) { + compare = comparator.compare(a.get(i), b.get(i)); + if (compare != 0) { + break; + } + } + } + return compare; + } + + public static int compareTo(Set a, Set b) { + int compare = compareTo(a.size(), b.size()); + if (compare == 0) { + ArrayList sortedA = new ArrayList(a); + ArrayList sortedB = new ArrayList(b); + + Collections.sort(sortedA, comparator); + Collections.sort(sortedB, comparator); + + Iterator iterA = sortedA.iterator(); + Iterator iterB = sortedB.iterator(); + + // Compare each item. + while (iterA.hasNext() && iterB.hasNext()) { + compare = comparator.compare(iterA.next(), iterB.next()); + if (compare != 0) { + break; + } + } + } + return compare; + } + + public static int compareTo(Map a, Map b) { + int lastComparison = compareTo(a.size(), b.size()); + if (lastComparison != 0) { + return lastComparison; + } + + // Sort a and b so we can compare them. + SortedMap sortedA = new TreeMap(comparator); + sortedA.putAll(a); + Iterator<Map.Entry> iterA = sortedA.entrySet().iterator(); + SortedMap sortedB = new TreeMap(comparator); + sortedB.putAll(b); + Iterator<Map.Entry> iterB = sortedB.entrySet().iterator(); + + // Compare each item. + while (iterA.hasNext() && iterB.hasNext()) { + Map.Entry entryA = iterA.next(); + Map.Entry entryB = iterB.next(); + lastComparison = comparator.compare(entryA.getKey(), entryB.getKey()); + if (lastComparison != 0) { + return lastComparison; + } + lastComparison = comparator.compare(entryA.getValue(), entryB.getValue()); + if (lastComparison != 0) { + return lastComparison; + } + } + + return 0; + } + + /** + * Comparator to compare items inside a structure (e.g. a list, set, or map). + */ + private static class NestedStructureComparator implements Comparator, Serializable { + public int compare(Object oA, Object oB) { + if (oA == null && oB == null) { + return 0; + } else if (oA == null) { + return -1; + } else if (oB == null) { + return 1; + } else if (oA instanceof List) { + return compareTo((List)oA, (List)oB); + } else if (oA instanceof Set) { + return compareTo((Set)oA, (Set)oB); + } else if (oA instanceof Map) { + return compareTo((Map)oA, (Map)oB); + } else if (oA instanceof byte[]) { + return compareTo((byte[])oA, (byte[])oB); + } else { + return compareTo((Comparable)oA, (Comparable)oB); + } + } + } + + public static void toString(Collection<ByteBuffer> bbs, StringBuilder sb) { + Iterator<ByteBuffer> it = bbs.iterator(); + if (!it.hasNext()) { + sb.append("[]"); + } else { + sb.append("["); + while (true) { + ByteBuffer bb = it.next(); + org.apache.thrift.TBaseHelper.toString(bb, sb); + if (!it.hasNext()) { + sb.append("]"); + return; + } else { + sb.append(", "); + } + } + } + } + + public static void toString(ByteBuffer bb, StringBuilder sb) { + byte[] buf = bb.array(); + + int arrayOffset = bb.arrayOffset(); + int offset = arrayOffset + bb.position(); + int origLimit = arrayOffset + bb.limit(); + int limit = (origLimit - offset > 128) ? offset + 128 : origLimit; + + for (int i = offset; i < limit; i++) { + if (i > offset) { + sb.append(" "); + } + sb.append(paddedByteString(buf[i])); + } + if (origLimit != limit) { + sb.append("..."); + } + } + + public static String paddedByteString(byte b) { + int extended = (b | 0x100) & 0x1ff; + return Integer.toHexString(extended).toUpperCase().substring(1); + } + + public static byte[] byteBufferToByteArray(ByteBuffer byteBuffer) { + if (wrapsFullArray(byteBuffer)) { + return byteBuffer.array(); + } + byte[] target = new byte[byteBuffer.remaining()]; + byteBufferToByteArray(byteBuffer, target, 0); + return target; + } + + public static boolean wrapsFullArray(ByteBuffer byteBuffer) { + return byteBuffer.hasArray() + && byteBuffer.position() == 0 + && byteBuffer.arrayOffset() == 0 + && byteBuffer.remaining() == byteBuffer.capacity(); + } + + public static int byteBufferToByteArray(ByteBuffer byteBuffer, byte[] target, int offset) { + int remaining = byteBuffer.remaining(); + System.arraycopy(byteBuffer.array(), + byteBuffer.arrayOffset() + byteBuffer.position(), + target, + offset, + remaining); + return remaining; + } + + public static ByteBuffer rightSize(ByteBuffer in) { + if (in == null) { + return null; + } + if (wrapsFullArray(in)) { + return in; + } + return ByteBuffer.wrap(byteBufferToByteArray(in)); + } + + public static ByteBuffer copyBinary(final ByteBuffer orig) { + if (orig == null) { + return null; + } + ByteBuffer copy = ByteBuffer.wrap(new byte[orig.remaining()]); + if (orig.hasArray()) { + System.arraycopy(orig.array(), orig.arrayOffset() + orig.position(), copy.array(), 0, orig.remaining()); + } else { + orig.slice().get(copy.array()); + } + + return copy; + } + + public static byte[] copyBinary(final byte[] orig) { + return (orig == null) ? null : Arrays.copyOf(orig, orig.length); + } + + public static int hashCode(long value) { + return Long.hashCode(value); + } + + public static int hashCode(double value) { + return Double.hashCode(value); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TBaseProcessor.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TBaseProcessor.java new file mode 100644 index 000000000..55a0f15d3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TBaseProcessor.java @@ -0,0 +1,41 @@ +package org.apache.thrift; + +import java.util.Collections; +import java.util.Map; + +import org.apache.thrift.protocol.TMessage; +import org.apache.thrift.protocol.TMessageType; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.protocol.TProtocolUtil; +import org.apache.thrift.protocol.TType; + +public abstract class TBaseProcessor<I> implements TProcessor { + private final I iface; + private final Map<String,ProcessFunction<I, ? extends TBase>> processMap; + + protected TBaseProcessor(I iface, Map<String, ProcessFunction<I, ? extends TBase>> processFunctionMap) { + this.iface = iface; + this.processMap = processFunctionMap; + } + + public Map<String,ProcessFunction<I, ? extends TBase>> getProcessMapView() { + return Collections.unmodifiableMap(processMap); + } + + @Override + public void process(TProtocol in, TProtocol out) throws TException { + TMessage msg = in.readMessageBegin(); + ProcessFunction fn = processMap.get(msg.name); + if (fn == null) { + TProtocolUtil.skip(in, TType.STRUCT); + in.readMessageEnd(); + TApplicationException x = new TApplicationException(TApplicationException.UNKNOWN_METHOD, "Invalid method name: '"+msg.name+"'"); + out.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid)); + x.write(out); + out.writeMessageEnd(); + out.getTransport().flush(); + } else { + fn.process(msg.seqid, in, out, iface); + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TByteArrayOutputStream.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TByteArrayOutputStream.java new file mode 100644 index 000000000..3a2d56c88 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TByteArrayOutputStream.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import java.io.ByteArrayOutputStream; +import java.nio.charset.Charset; + +/** + * Class that allows access to the underlying buf without doing deep + * copies on it. + * + */ +public class TByteArrayOutputStream extends ByteArrayOutputStream { + + private final int initialSize; + + public TByteArrayOutputStream(int size) { + super(size); + this.initialSize = size; + } + + public TByteArrayOutputStream() { + this(32); + } + + public byte[] get() { + return buf; + } + + public void reset() { + super.reset(); + if (buf.length > initialSize) { + buf = new byte[initialSize]; + } + } + + public int len() { + return count; + } + + public String toString(Charset charset) { + return new String(buf, 0, count, charset); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TDeserializer.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TDeserializer.java new file mode 100644 index 000000000..d1d396609 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TDeserializer.java @@ -0,0 +1,339 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; + +import org.apache.thrift.protocol.TBinaryProtocol; +import org.apache.thrift.protocol.TField; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.protocol.TProtocolFactory; +import org.apache.thrift.protocol.TProtocolUtil; +import org.apache.thrift.protocol.TType; +import org.apache.thrift.transport.TMemoryInputTransport; + +/** + * Generic utility for easily deserializing objects from a byte array or Java + * String. + * + */ +public class TDeserializer { + private final TProtocol protocol_; + private final TMemoryInputTransport trans_; + + /** + * Create a new TDeserializer that uses the TBinaryProtocol by default. + */ + public TDeserializer() { + this(new TBinaryProtocol.Factory()); + } + + /** + * Create a new TDeserializer. It will use the TProtocol specified by the + * factory that is passed in. + * + * @param protocolFactory Factory to create a protocol + */ + public TDeserializer(TProtocolFactory protocolFactory) { + trans_ = new TMemoryInputTransport(); + protocol_ = protocolFactory.getProtocol(trans_); + } + + /** + * Deserialize the Thrift object from a byte array. + * + * @param base The object to read into + * @param bytes The array to read from + */ + public void deserialize(TBase base, byte[] bytes) throws TException { + deserialize(base, bytes, 0, bytes.length); + } + + /** + * Deserialize the Thrift object from a byte array. + * + * @param base The object to read into + * @param bytes The array to read from + * @param offset The offset into {@code bytes} + * @param length The length to read from {@code bytes} + */ + public void deserialize(TBase base, byte[] bytes, int offset, int length) throws TException { + try { + trans_.reset(bytes, offset, length); + base.read(protocol_); + } finally { + trans_.clear(); + protocol_.reset(); + } + } + + /** + * Deserialize the Thrift object from a Java string, using a specified + * character set for decoding. + * + * @param base The object to read into + * @param data The string to read from + * @param charset Valid JVM charset + */ + public void deserialize(TBase base, String data, String charset) throws TException { + try { + deserialize(base, data.getBytes(charset)); + } catch (UnsupportedEncodingException uex) { + throw new TException("JVM DOES NOT SUPPORT ENCODING: " + charset); + } finally { + protocol_.reset(); + } + } + + /** + * Deserialize only a single Thrift object (addressed by recursively using field id) + * from a byte record. + * @param tb The object to read into + * @param bytes The serialized object to read from + * @param fieldIdPathFirst First of the FieldId's that define a path tb + * @param fieldIdPathRest The rest FieldId's that define a path tb + * @throws TException + */ + public void partialDeserialize(TBase tb, byte[] bytes, TFieldIdEnum fieldIdPathFirst, TFieldIdEnum ... fieldIdPathRest) throws TException { + try { + if (locateField(bytes, fieldIdPathFirst, fieldIdPathRest) != null) { + // if this line is reached, iprot will be positioned at the start of tb. + tb.read(protocol_); + } + } catch (Exception e) { + throw new TException(e); + } finally { + trans_.clear(); + protocol_.reset(); + } + } + + /** + * Deserialize only a boolean field (addressed by recursively using field id) + * from a byte record. + * @param bytes The serialized object to read from + * @param fieldIdPathFirst First of the FieldId's that define a path to a boolean field + * @param fieldIdPathRest The rest FieldId's that define a path to a boolean field + * @throws TException + */ + public Boolean partialDeserializeBool(byte[] bytes, TFieldIdEnum fieldIdPathFirst, TFieldIdEnum ... fieldIdPathRest) throws TException { + return (Boolean) partialDeserializeField(TType.BOOL, bytes, fieldIdPathFirst, fieldIdPathRest); + } + + /** + * Deserialize only a byte field (addressed by recursively using field id) + * from a byte record. + * @param bytes The serialized object to read from + * @param fieldIdPathFirst First of the FieldId's that define a path to a byte field + * @param fieldIdPathRest The rest FieldId's that define a path to a byte field + * @throws TException + */ + public Byte partialDeserializeByte(byte[] bytes, TFieldIdEnum fieldIdPathFirst, TFieldIdEnum ... fieldIdPathRest) throws TException { + return (Byte) partialDeserializeField(TType.BYTE, bytes, fieldIdPathFirst, fieldIdPathRest); + } + + /** + * Deserialize only a double field (addressed by recursively using field id) + * from a byte record. + * @param bytes The serialized object to read from + * @param fieldIdPathFirst First of the FieldId's that define a path to a double field + * @param fieldIdPathRest The rest FieldId's that define a path to a double field + * @throws TException + */ + public Double partialDeserializeDouble(byte[] bytes, TFieldIdEnum fieldIdPathFirst, TFieldIdEnum ... fieldIdPathRest) throws TException { + return (Double) partialDeserializeField(TType.DOUBLE, bytes, fieldIdPathFirst, fieldIdPathRest); + } + + /** + * Deserialize only an i16 field (addressed by recursively using field id) + * from a byte record. + * @param bytes The serialized object to read from + * @param fieldIdPathFirst First of the FieldId's that define a path to an i16 field + * @param fieldIdPathRest The rest FieldId's that define a path to an i16 field + * @throws TException + */ + public Short partialDeserializeI16(byte[] bytes, TFieldIdEnum fieldIdPathFirst, TFieldIdEnum ... fieldIdPathRest) throws TException { + return (Short) partialDeserializeField(TType.I16, bytes, fieldIdPathFirst, fieldIdPathRest); + } + + /** + * Deserialize only an i32 field (addressed by recursively using field id) + * from a byte record. + * @param bytes The serialized object to read from + * @param fieldIdPathFirst First of the FieldId's that define a path to an i32 field + * @param fieldIdPathRest The rest FieldId's that define a path to an i32 field + * @throws TException + */ + public Integer partialDeserializeI32(byte[] bytes, TFieldIdEnum fieldIdPathFirst, TFieldIdEnum ... fieldIdPathRest) throws TException { + return (Integer) partialDeserializeField(TType.I32, bytes, fieldIdPathFirst, fieldIdPathRest); + } + + /** + * Deserialize only an i64 field (addressed by recursively using field id) + * from a byte record. + * @param bytes The serialized object to read from + * @param fieldIdPathFirst First of the FieldId's that define a path to an i64 field + * @param fieldIdPathRest The rest FieldId's that define a path to an i64 field + * @throws TException + */ + public Long partialDeserializeI64(byte[] bytes, TFieldIdEnum fieldIdPathFirst, TFieldIdEnum ... fieldIdPathRest) throws TException { + return (Long) partialDeserializeField(TType.I64, bytes, fieldIdPathFirst, fieldIdPathRest); + } + + /** + * Deserialize only a string field (addressed by recursively using field id) + * from a byte record. + * @param bytes The serialized object to read from + * @param fieldIdPathFirst First of the FieldId's that define a path to a string field + * @param fieldIdPathRest The rest FieldId's that define a path to a string field + * @throws TException + */ + public String partialDeserializeString(byte[] bytes, TFieldIdEnum fieldIdPathFirst, TFieldIdEnum ... fieldIdPathRest) throws TException { + return (String) partialDeserializeField(TType.STRING, bytes, fieldIdPathFirst, fieldIdPathRest); + } + + /** + * Deserialize only a binary field (addressed by recursively using field id) + * from a byte record. + * @param bytes The serialized object to read from + * @param fieldIdPathFirst First of the FieldId's that define a path to a binary field + * @param fieldIdPathRest The rest FieldId's that define a path to a binary field + * @throws TException + */ + public ByteBuffer partialDeserializeByteArray(byte[] bytes, TFieldIdEnum fieldIdPathFirst, TFieldIdEnum ... fieldIdPathRest) throws TException { + // TType does not have binary, so we use the arbitrary num 100 + return (ByteBuffer) partialDeserializeField((byte)100, bytes, fieldIdPathFirst, fieldIdPathRest); + } + + /** + * Deserialize only the id of the field set in a TUnion (addressed by recursively using field id) + * from a byte record. + * @param bytes The serialized object to read from + * @param fieldIdPathFirst First of the FieldId's that define a path to a TUnion + * @param fieldIdPathRest The rest FieldId's that define a path to a TUnion + * @throws TException + */ + public Short partialDeserializeSetFieldIdInUnion(byte[] bytes, TFieldIdEnum fieldIdPathFirst, TFieldIdEnum ... fieldIdPathRest) throws TException { + try { + TField field = locateField(bytes, fieldIdPathFirst, fieldIdPathRest); + if (field != null){ + protocol_.readStructBegin(); // The Union + return protocol_.readFieldBegin().id; // The field set in the union + } + return null; + } catch (Exception e) { + throw new TException(e); + } finally { + trans_.clear(); + protocol_.reset(); + } + } + + private Object partialDeserializeField(byte ttype, byte[] bytes, TFieldIdEnum fieldIdPathFirst, TFieldIdEnum ... fieldIdPathRest) throws TException { + try { + TField field = locateField(bytes, fieldIdPathFirst, fieldIdPathRest); + if (field != null) { + if (ttype == field.type) { + // if this point is reached, iprot will be positioned at the start of + // the field + switch (ttype) { + case TType.BOOL: + return protocol_.readBool(); + case TType.BYTE: + return protocol_.readByte(); + case TType.DOUBLE: + return protocol_.readDouble(); + case TType.I16: + return protocol_.readI16(); + case TType.I32: + return protocol_.readI32(); + case TType.I64: + return protocol_.readI64(); + case TType.STRING: + return protocol_.readString(); + default: + return null; + } + } + // hack to differentiate between string and binary + if (ttype == 100 && field.type == TType.STRING) { + return protocol_.readBinary(); + } + } + return null; + } catch (Exception e) { + throw new TException(e); + } finally { + trans_.clear(); + protocol_.reset(); + } + } + + private TField locateField(byte[] bytes, TFieldIdEnum fieldIdPathFirst, TFieldIdEnum ... fieldIdPathRest) throws TException { + trans_.reset(bytes); + + TFieldIdEnum[] fieldIdPath = new TFieldIdEnum[fieldIdPathRest.length + 1]; + fieldIdPath[0] = fieldIdPathFirst; + System.arraycopy(fieldIdPathRest, 0, fieldIdPath, 1, fieldIdPathRest.length); + + // index into field ID path being currently searched for + int curPathIndex = 0; + + // this will be the located field, or null if it is not located + TField field = null; + + protocol_.readStructBegin(); + + while (curPathIndex < fieldIdPath.length) { + field = protocol_.readFieldBegin(); + // we can stop searching if we either see a stop or we go past the field + // id we're looking for (since fields should now be serialized in asc + // order). + if (field.type == TType.STOP || field.id > fieldIdPath[curPathIndex].getThriftFieldId()) { + return null; + } + + if (field.id != fieldIdPath[curPathIndex].getThriftFieldId()) { + // Not the field we're looking for. Skip field. + TProtocolUtil.skip(protocol_, field.type); + protocol_.readFieldEnd(); + } else { + // This field is the next step in the path. Step into field. + curPathIndex++; + if (curPathIndex < fieldIdPath.length) { + protocol_.readStructBegin(); + } + } + } + return field; + } + + /** + * Deserialize the Thrift object from a Java string, using the default JVM + * charset encoding. + * + * @param base The object to read into + * @param data The string to read from + */ + public void fromString(TBase base, String data) throws TException { + deserialize(base, data.getBytes()); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TEnum.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TEnum.java new file mode 100644 index 000000000..325fdece7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TEnum.java @@ -0,0 +1,24 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +public interface TEnum { + public int getValue(); +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TEnumHelper.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TEnumHelper.java new file mode 100644 index 000000000..fbc778751 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TEnumHelper.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import java.lang.NoSuchMethodException; +import java.lang.IllegalAccessException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Utility class with static methods for interacting with TEnum + */ +public class TEnumHelper { + + /** + * Given a TEnum class and integer value, this method will return + * the associated constant from the given TEnum class. + * This method MUST be modified should the name of the 'findByValue' method + * change. + * + * @param enumClass TEnum from which to return a matching constant. + * @param value Value for which to return the constant. + * + * @return The constant in 'enumClass' whose value is 'value' or null if + * something went wrong. + */ + public static TEnum getByValue(Class<? extends TEnum> enumClass, int value) { + try { + Method method = enumClass.getMethod("findByValue", int.class); + return (TEnum) method.invoke(null, value); + } catch (NoSuchMethodException nsme) { + return null; + } catch (IllegalAccessException iae) { + return null; + } catch (InvocationTargetException ite) { + return null; + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TException.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TException.java new file mode 100644 index 000000000..f84f4812e --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TException.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +/** + * Generic exception class for Thrift. + * + */ +public class TException extends Exception { + + private static final long serialVersionUID = 1L; + + public TException() { + super(); + } + + public TException(String message) { + super(message); + } + + public TException(Throwable cause) { + super(cause); + } + + public TException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TFieldIdEnum.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TFieldIdEnum.java new file mode 100644 index 000000000..2956fba0b --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TFieldIdEnum.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +/** + * Interface for all generated struct Fields objects. + */ +public interface TFieldIdEnum { + /** + * Get the Thrift field id for the named field. + */ + public short getThriftFieldId(); + + /** + * Get the field's name, exactly as in the IDL. + */ + public String getFieldName(); +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TFieldRequirementType.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TFieldRequirementType.java new file mode 100644 index 000000000..74bac4eff --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TFieldRequirementType.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +/** + * Requirement type constants. + * + */ +public final class TFieldRequirementType { + public static final byte REQUIRED = 1; + public static final byte OPTIONAL = 2; + public static final byte DEFAULT = 3; +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TMultiplexedProcessor.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TMultiplexedProcessor.java new file mode 100644 index 000000000..c49486217 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TMultiplexedProcessor.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import org.apache.thrift.protocol.*; + +import java.util.Map; +import java.util.HashMap; + +/** + * <code>TMultiplexedProcessor</code> is a <code>TProcessor</code> allowing + * a single <code>TServer</code> to provide multiple services. + * + * <p>To do so, you instantiate the processor and then register additional + * processors with it, as shown in the following example:</p> + * + * <blockquote><code> + * TMultiplexedProcessor processor = new TMultiplexedProcessor(); + * + * processor.registerProcessor( + * "Calculator", + * new Calculator.Processor(new CalculatorHandler())); + * + * processor.registerProcessor( + * "WeatherReport", + * new WeatherReport.Processor(new WeatherReportHandler())); + * + * TServerTransport t = new TServerSocket(9090); + * TSimpleServer server = new TSimpleServer(processor, t); + * + * server.serve(); + * </code></blockquote> + */ +public class TMultiplexedProcessor implements TProcessor { + + private final Map<String,TProcessor> SERVICE_PROCESSOR_MAP + = new HashMap<String,TProcessor>(); + private TProcessor defaultProcessor; + + /** + * 'Register' a service with this <code>TMultiplexedProcessor</code>. This + * allows us to broker requests to individual services by using the service + * name to select them at request time. + * + * @param serviceName Name of a service, has to be identical to the name + * declared in the Thrift IDL, e.g. "WeatherReport". + * @param processor Implementation of a service, usually referred to + * as "handlers", e.g. WeatherReportHandler implementing WeatherReport.Iface. + */ + public void registerProcessor(String serviceName, TProcessor processor) { + SERVICE_PROCESSOR_MAP.put(serviceName, processor); + } + + /** + * Register a service to be called to process queries without service name + * @param processor + */ + public void registerDefault(TProcessor processor) { + defaultProcessor = processor; + } + + /** + * This implementation of <code>process</code> performs the following steps: + * + * <ol> + * <li>Read the beginning of the message.</li> + * <li>Extract the service name from the message.</li> + * <li>Using the service name to locate the appropriate processor.</li> + * <li>Dispatch to the processor, with a decorated instance of TProtocol + * that allows readMessageBegin() to return the original TMessage.</li> + * </ol> + * + * @throws TProtocolException If the message type is not CALL or ONEWAY, if + * the service name was not found in the message, or if the service + * name was not found in the service map. You called {@link #registerProcessor(String, TProcessor) registerProcessor} + * during initialization, right? :) + */ + public void process(TProtocol iprot, TProtocol oprot) throws TException { + /* + Use the actual underlying protocol (e.g. TBinaryProtocol) to read the + message header. This pulls the message "off the wire", which we'll + deal with at the end of this method. + */ + TMessage message = iprot.readMessageBegin(); + + if (message.type != TMessageType.CALL && message.type != TMessageType.ONEWAY) { + throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, + "This should not have happened!?"); + } + + // Extract the service name + int index = message.name.indexOf(TMultiplexedProtocol.SEPARATOR); + if (index < 0) { + if (defaultProcessor != null) { + // Dispatch processing to the stored processor + defaultProcessor.process(new StoredMessageProtocol(iprot, message), oprot); + return; + } + throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, + "Service name not found in message name: " + message.name + ". Did you " + + "forget to use a TMultiplexProtocol in your client?"); + } + + // Create a new TMessage, something that can be consumed by any TProtocol + String serviceName = message.name.substring(0, index); + TProcessor actualProcessor = SERVICE_PROCESSOR_MAP.get(serviceName); + if (actualProcessor == null) { + throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, + "Service name not found: " + serviceName + ". Did you forget " + + "to call registerProcessor()?"); + } + + // Create a new TMessage, removing the service name + TMessage standardMessage = new TMessage( + message.name.substring(serviceName.length()+TMultiplexedProtocol.SEPARATOR.length()), + message.type, + message.seqid + ); + + // Dispatch processing to the stored processor + actualProcessor.process(new StoredMessageProtocol(iprot, standardMessage), oprot); + } + + /** + * Our goal was to work with any protocol. In order to do that, we needed + * to allow them to call readMessageBegin() and get a TMessage in exactly + * the standard format, without the service name prepended to TMessage.name. + */ + private static class StoredMessageProtocol extends TProtocolDecorator { + TMessage messageBegin; + public StoredMessageProtocol(TProtocol protocol, TMessage messageBegin) { + super(protocol); + this.messageBegin = messageBegin; + } + @Override + public TMessage readMessageBegin() throws TException { + return messageBegin; + } + } + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TNonblockingMultiFetchClient.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TNonblockingMultiFetchClient.java new file mode 100644 index 000000000..382d978db --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TNonblockingMultiFetchClient.java @@ -0,0 +1,399 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + + +/** + * This class uses a single thread to set up non-blocking sockets to a set + * of remote servers (hostname and port pairs), and sends a same request to + * all these servers. It then fetches responses from servers. + * + * Parameters: + * int maxRecvBufBytesPerServer - an upper limit for receive buffer size + * per server (in byte). If a response from a server exceeds this limit, the + * client will not allocate memory or read response data for it. + * + * int fetchTimeoutSeconds - time limit for fetching responses from all + * servers (in second). After the timeout, the fetch job is stopped and + * available responses are returned. + * + * ByteBuffer requestBuf - request message that is sent to all servers. + * + * Output: + * Responses are stored in an array of ByteBuffers. Index of elements in + * this array corresponds to index of servers in the server list. Content in + * a ByteBuffer may be in one of the following forms: + * 1. First 4 bytes form an integer indicating length of following data, + * then followed by the data. + * 2. First 4 bytes form an integer indicating length of following data, + * then followed by nothing - this happens when the response data size + * exceeds maxRecvBufBytesPerServer, and the client will not read any + * response data. + * 3. No data in the ByteBuffer - this happens when the server does not + * return any response within fetchTimeoutSeconds. + * + * In some special cases (no servers are given, fetchTimeoutSeconds less + * than or equal to 0, requestBuf is null), the return is null. + * + * Note: + * It assumes all remote servers are TNonblockingServers and use + * TFramedTransport. + * + */ +public class TNonblockingMultiFetchClient { + + private static final Logger LOGGER = LoggerFactory.getLogger( + TNonblockingMultiFetchClient.class.getName() + ); + + // if the size of the response msg exceeds this limit (in byte), we will + // not read the msg + private int maxRecvBufBytesPerServer; + + // time limit for fetching data from all servers (in second) + private int fetchTimeoutSeconds; + + // store request that will be sent to servers + private ByteBuffer requestBuf; + private ByteBuffer requestBufDuplication; + + // a list of remote servers + private List<InetSocketAddress> servers; + + // store fetch results + private TNonblockingMultiFetchStats stats; + private ByteBuffer[] recvBuf; + + public TNonblockingMultiFetchClient(int maxRecvBufBytesPerServer, + int fetchTimeoutSeconds, ByteBuffer requestBuf, + List<InetSocketAddress> servers) { + this.maxRecvBufBytesPerServer = maxRecvBufBytesPerServer; + this.fetchTimeoutSeconds = fetchTimeoutSeconds; + this.requestBuf = requestBuf; + this.servers = servers; + + stats = new TNonblockingMultiFetchStats(); + recvBuf = null; + } + + public synchronized int getMaxRecvBufBytesPerServer() { + return maxRecvBufBytesPerServer; + } + + public synchronized int getFetchTimeoutSeconds() { + return fetchTimeoutSeconds; + } + + /** + * return a duplication of requestBuf, so that requestBuf will not + * be modified by others. + */ + public synchronized ByteBuffer getRequestBuf() { + if (requestBuf == null) { + return null; + } else { + if (requestBufDuplication == null) { + requestBufDuplication = requestBuf.duplicate(); + } + return requestBufDuplication; + } + } + + public synchronized List<InetSocketAddress> getServerList() { + if (servers == null) { + return null; + } + return Collections.unmodifiableList(servers); + } + + public synchronized TNonblockingMultiFetchStats getFetchStats() { + return stats; + } + + /** + * main entry function for fetching from servers + */ + public synchronized ByteBuffer[] fetch() { + // clear previous results + recvBuf = null; + stats.clear(); + + if (servers == null || servers.size() == 0 || + requestBuf == null || fetchTimeoutSeconds <= 0) { + return recvBuf; + } + + ExecutorService executor = Executors.newSingleThreadExecutor(); + MultiFetch multiFetch = new MultiFetch(); + FutureTask<?> task = new FutureTask(multiFetch, null); + executor.execute(task); + try { + task.get(fetchTimeoutSeconds, TimeUnit.SECONDS); + } catch(InterruptedException ie) { + // attempt to cancel execution of the task. + task.cancel(true); + LOGGER.error("interrupted during fetch: "+ie.toString()); + } catch(ExecutionException ee) { + // attempt to cancel execution of the task. + task.cancel(true); + LOGGER.error("exception during fetch: "+ee.toString()); + } catch(TimeoutException te) { + // attempt to cancel execution of the task. + task.cancel(true); + LOGGER.error("timeout for fetch: "+te.toString()); + } + + executor.shutdownNow(); + multiFetch.close(); + return recvBuf; + } + + /** + * Private class that does real fetch job. + * Users are not allowed to directly use this class, as its run() + * function may run forever. + */ + private class MultiFetch implements Runnable { + private Selector selector; + + /** + * main entry function for fetching. + * + * Server responses are stored in TNonblocingMultiFetchClient.recvBuf, + * and fetch statistics is in TNonblockingMultiFetchClient.stats. + * + * Sanity check for parameters has been done in + * TNonblockingMultiFetchClient before calling this function. + */ + public void run() { + long t1 = System.currentTimeMillis(); + + int numTotalServers = servers.size(); + stats.setNumTotalServers(numTotalServers); + + // buffer for receiving response from servers + recvBuf = new ByteBuffer[numTotalServers]; + // buffer for sending request + ByteBuffer sendBuf[] = new ByteBuffer[numTotalServers]; + long numBytesRead[] = new long[numTotalServers]; + int frameSize[] = new int[numTotalServers]; + boolean hasReadFrameSize[] = new boolean[numTotalServers]; + + try { + selector = Selector.open(); + } catch (IOException e) { + LOGGER.error("selector opens error: "+e.toString()); + return; + } + + for (int i = 0; i < numTotalServers; i++) { + // create buffer to send request to server. + sendBuf[i] = requestBuf.duplicate(); + // create buffer to read response's frame size from server + recvBuf[i] = ByteBuffer.allocate(4); + stats.incTotalRecvBufBytes(4); + + InetSocketAddress server = servers.get(i); + SocketChannel s = null; + SelectionKey key = null; + try { + s = SocketChannel.open(); + s.configureBlocking(false); + // now this method is non-blocking + s.connect(server); + key = s.register(selector, s.validOps()); + // attach index of the key + key.attach(i); + } catch (Exception e) { + stats.incNumConnectErrorServers(); + String err = String.format("set up socket to server %s error: %s", + server.toString(), e.toString()); + LOGGER.error(err); + // free resource + if (s != null) { + try {s.close();} catch (Exception ex) {} + } + if (key != null) { + key.cancel(); + } + } + } + + // wait for events + while (stats.getNumReadCompletedServers() + + stats.getNumConnectErrorServers() < stats.getNumTotalServers()) { + // if the thread is interrupted (e.g., task is cancelled) + if (Thread.currentThread().isInterrupted()) { + return; + } + + try{ + selector.select(); + } catch (Exception e) { + LOGGER.error("selector selects error: "+e.toString()); + continue; + } + + Iterator<SelectionKey> it = selector.selectedKeys().iterator(); + while (it.hasNext()) { + SelectionKey selKey = it.next(); + it.remove(); + + // get previously attached index + int index = (Integer)selKey.attachment(); + + if (selKey.isValid() && selKey.isConnectable()) { + // if this socket throws an exception (e.g., connection refused), + // print error msg and skip it. + try { + SocketChannel sChannel = (SocketChannel)selKey.channel(); + sChannel.finishConnect(); + } catch (Exception e) { + stats.incNumConnectErrorServers(); + String err = String.format("socket %d connects to server %s " + + "error: %s", + index, servers.get(index).toString(), e.toString()); + LOGGER.error(err); + } + } + + if (selKey.isValid() && selKey.isWritable()) { + if (sendBuf[index].hasRemaining()) { + // if this socket throws an exception, print error msg and + // skip it. + try { + SocketChannel sChannel = (SocketChannel)selKey.channel(); + sChannel.write(sendBuf[index]); + } catch (Exception e) { + String err = String.format("socket %d writes to server %s " + + "error: %s", + index, servers.get(index).toString(), e.toString()); + LOGGER.error(err); + } + } + } + + if (selKey.isValid() && selKey.isReadable()) { + // if this socket throws an exception, print error msg and + // skip it. + try { + SocketChannel sChannel = (SocketChannel)selKey.channel(); + int bytesRead = sChannel.read(recvBuf[index]); + + if (bytesRead > 0) { + numBytesRead[index] += bytesRead; + + if (!hasReadFrameSize[index] && + recvBuf[index].remaining()==0) { + // if the frame size has been read completely, then prepare + // to read the actual frame. + frameSize[index] = recvBuf[index].getInt(0); + + if (frameSize[index] <= 0) { + stats.incNumInvalidFrameSize(); + String err = String.format("Read an invalid frame size %d" + + " from %s. Does the server use TFramedTransport? ", + frameSize[index], servers.get(index).toString()); + LOGGER.error(err); + sChannel.close(); + continue; + } + + if (frameSize[index] + 4 > stats.getMaxResponseBytes()) { + stats.setMaxResponseBytes(frameSize[index]+4); + } + + if (frameSize[index] + 4 > maxRecvBufBytesPerServer) { + stats.incNumOverflowedRecvBuf(); + String err = String.format("Read frame size %d from %s," + + " total buffer size would exceed limit %d", + frameSize[index], servers.get(index).toString(), + maxRecvBufBytesPerServer); + LOGGER.error(err); + sChannel.close(); + continue; + } + + // reallocate buffer for actual frame data + recvBuf[index] = ByteBuffer.allocate(frameSize[index] + 4); + recvBuf[index].putInt(frameSize[index]); + + stats.incTotalRecvBufBytes(frameSize[index]); + hasReadFrameSize[index] = true; + } + + if (hasReadFrameSize[index] && + numBytesRead[index] >= frameSize[index]+4) { + // has read all data + sChannel.close(); + stats.incNumReadCompletedServers(); + long t2 = System.currentTimeMillis(); + stats.setReadTime(t2-t1); + } + } + } catch (Exception e) { + String err = String.format("socket %d reads from server %s " + + "error: %s", + index, servers.get(index).toString(), e.toString()); + LOGGER.error(err); + } + } + } + } + } + + /** + * dispose any resource allocated + */ + public void close() { + try { + if (selector.isOpen()) { + Iterator<SelectionKey> it = selector.keys().iterator(); + while (it.hasNext()) { + SelectionKey selKey = it.next(); + SocketChannel sChannel = (SocketChannel)selKey.channel(); + sChannel.close(); + } + + selector.close(); + } + } catch (IOException e) { + LOGGER.error("free resource error: "+e.toString()); + } + } + } +}
\ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TNonblockingMultiFetchStats.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TNonblockingMultiFetchStats.java new file mode 100644 index 000000000..90b86208b --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TNonblockingMultiFetchStats.java @@ -0,0 +1,80 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +/** + * This class keeps track of statistics for TNonblockinMultiFetchClient. + */ +public class TNonblockingMultiFetchStats { + private int numTotalServers; + private int numReadCompletedServers; + private int numConnectErrorServers; + private int totalRecvBufBytes; + private int maxResponseBytes; + private int numOverflowedRecvBuf; + private int numInvalidFrameSize; + // time from the beginning of fetch() function to the reading finish + // time of the last socket (in milli-second) + private long readTime; + + public TNonblockingMultiFetchStats() { + clear(); + } + + public void clear() { + numTotalServers = 0; + numReadCompletedServers = 0; + numConnectErrorServers = 0; + totalRecvBufBytes = 0; + maxResponseBytes = 0; + numOverflowedRecvBuf = 0; + numInvalidFrameSize = 0; + readTime = 0; + } + + public String toString() { + String stats = String.format("numTotalServers=%d, " + + "numReadCompletedServers=%d, numConnectErrorServers=%d, " + + "numUnresponsiveServers=%d, totalRecvBufBytes=%fM, " + + "maxResponseBytes=%d, numOverflowedRecvBuf=%d, " + + "numInvalidFrameSize=%d, readTime=%dms", + numTotalServers, numReadCompletedServers, numConnectErrorServers, + (numTotalServers-numReadCompletedServers-numConnectErrorServers), + totalRecvBufBytes/1024.0/1024, maxResponseBytes, numOverflowedRecvBuf, + numInvalidFrameSize, readTime); + return stats; + } + + public void setNumTotalServers(int val) { numTotalServers = val; } + public void setMaxResponseBytes(int val) { maxResponseBytes = val; } + public void setReadTime(long val) { readTime = val; } + public void incNumReadCompletedServers() { numReadCompletedServers++; } + public void incNumConnectErrorServers() { numConnectErrorServers++; } + public void incNumOverflowedRecvBuf() { numOverflowedRecvBuf++; } + public void incTotalRecvBufBytes(int val) { totalRecvBufBytes += val; } + public void incNumInvalidFrameSize() { numInvalidFrameSize++; } + + public int getMaxResponseBytes() { return maxResponseBytes; } + public int getNumReadCompletedServers() { return numReadCompletedServers; } + public int getNumConnectErrorServers() { return numConnectErrorServers; } + public int getNumTotalServers() { return numTotalServers; } + public int getNumOverflowedRecvBuf() { return numOverflowedRecvBuf;} + public int getTotalRecvBufBytes() { return totalRecvBufBytes;} + public int getNumInvalidFrameSize() { return numInvalidFrameSize; } + public long getReadTime() { return readTime; } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TProcessor.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TProcessor.java new file mode 100644 index 000000000..15ba9c0fe --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TProcessor.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import org.apache.thrift.protocol.TProtocol; + +/** + * A processor is a generic object which operates upon an input stream and + * writes to some output stream. + */ +public interface TProcessor { + public void process(TProtocol in, TProtocol out) throws TException; +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TProcessorFactory.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TProcessorFactory.java new file mode 100644 index 000000000..81933a211 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TProcessorFactory.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import org.apache.thrift.transport.TTransport; + +/** + * The default processor factory just returns a singleton + * instance. + */ +public class TProcessorFactory { + + private final TProcessor processor_; + + public TProcessorFactory(TProcessor processor) { + processor_ = processor; + } + + public TProcessor getProcessor(TTransport trans) { + return processor_; + } + + public boolean isAsyncProcessor() { + return processor_ instanceof TAsyncProcessor; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TSerializable.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TSerializable.java new file mode 100644 index 000000000..80002c761 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TSerializable.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import org.apache.thrift.protocol.TProtocol; + +/** + * Generic base interface for generated Thrift objects. + * + */ +public interface TSerializable { + + /** + * Reads the TObject from the given input protocol. + * + * @param iprot Input protocol + */ + public void read(TProtocol iprot) throws TException; + + /** + * Writes the objects out to the protocol + * + * @param oprot Output protocol + */ + public void write(TProtocol oprot) throws TException; + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TSerializer.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TSerializer.java new file mode 100644 index 000000000..4e1ce6129 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TSerializer.java @@ -0,0 +1,110 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; + +import org.apache.thrift.protocol.TBinaryProtocol; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.protocol.TProtocolFactory; +import org.apache.thrift.transport.TIOStreamTransport; + +/** + * Generic utility for easily serializing objects into a byte array or Java + * String. + * + */ +public class TSerializer { + + /** + * This is the byte array that data is actually serialized into + */ + private final ByteArrayOutputStream baos_ = new ByteArrayOutputStream(); + + /** + * This transport wraps that byte array + */ + private final TIOStreamTransport transport_ = new TIOStreamTransport(baos_); + + /** + * Internal protocol used for serializing objects. + */ + private TProtocol protocol_; + + /** + * Create a new TSerializer that uses the TBinaryProtocol by default. + */ + public TSerializer() { + this(new TBinaryProtocol.Factory()); + } + + /** + * Create a new TSerializer. It will use the TProtocol specified by the + * factory that is passed in. + * + * @param protocolFactory Factory to create a protocol + */ + public TSerializer(TProtocolFactory protocolFactory) { + protocol_ = protocolFactory.getProtocol(transport_); + } + + /** + * Serialize the Thrift object into a byte array. The process is simple, + * just clear the byte array output, write the object into it, and grab the + * raw bytes. + * + * @param base The object to serialize + * @return Serialized object in byte[] format + */ + public byte[] serialize(TBase base) throws TException { + baos_.reset(); + base.write(protocol_); + return baos_.toByteArray(); + } + + /** + * Serialize the Thrift object into a Java string, using a specified + * character set for encoding. + * + * @param base The object to serialize + * @param charset Valid JVM charset + * @return Serialized object as a String + */ + public String toString(TBase base, String charset) throws TException { + try { + return new String(serialize(base), charset); + } catch (UnsupportedEncodingException uex) { + throw new TException("JVM DOES NOT SUPPORT ENCODING: " + charset); + } + } + + /** + * Serialize the Thrift object into a Java string, using the default JVM + * charset encoding. + * + * @param base The object to serialize + * @return Serialized object as a String + */ + public String toString(TBase base) throws TException { + return new String(serialize(base)); + } +} + diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TServiceClient.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TServiceClient.java new file mode 100644 index 000000000..00a36ee7f --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TServiceClient.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import org.apache.thrift.protocol.TMessage; +import org.apache.thrift.protocol.TMessageType; +import org.apache.thrift.protocol.TProtocol; + +/** + * A TServiceClient is used to communicate with a TService implementation + * across protocols and transports. + */ +public abstract class TServiceClient { + public TServiceClient(TProtocol prot) { + this(prot, prot); + } + + public TServiceClient(TProtocol iprot, TProtocol oprot) { + iprot_ = iprot; + oprot_ = oprot; + } + + protected TProtocol iprot_; + protected TProtocol oprot_; + + protected int seqid_; + + /** + * Get the TProtocol being used as the input (read) protocol. + * @return the TProtocol being used as the input (read) protocol. + */ + public TProtocol getInputProtocol() { + return this.iprot_; + } + + /** + * Get the TProtocol being used as the output (write) protocol. + * @return the TProtocol being used as the output (write) protocol. + */ + public TProtocol getOutputProtocol() { + return this.oprot_; + } + + protected void sendBase(String methodName, TBase<?,?> args) throws TException { + sendBase(methodName, args, TMessageType.CALL); + } + + protected void sendBaseOneway(String methodName, TBase<?,?> args) throws TException { + sendBase(methodName, args, TMessageType.ONEWAY); + } + + private void sendBase(String methodName, TBase<?,?> args, byte type) throws TException { + oprot_.writeMessageBegin(new TMessage(methodName, type, ++seqid_)); + args.write(oprot_); + oprot_.writeMessageEnd(); + oprot_.getTransport().flush(); + } + + protected void receiveBase(TBase<?,?> result, String methodName) throws TException { + TMessage msg = iprot_.readMessageBegin(); + if (msg.type == TMessageType.EXCEPTION) { + TApplicationException x = new TApplicationException(); + x.read(iprot_); + iprot_.readMessageEnd(); + throw x; + } + if (msg.seqid != seqid_) { + throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID, + String.format("%s failed: out of sequence response: expected %d but got %d", methodName, seqid_, msg.seqid)); + } + result.read(iprot_); + iprot_.readMessageEnd(); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TServiceClientFactory.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TServiceClientFactory.java new file mode 100644 index 000000000..988e65591 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TServiceClientFactory.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import org.apache.thrift.protocol.TProtocol; + +/** + * A TServiceClientFactory provides a general way to get a TServiceClient + * connected to a remote TService via a protocol. + * @param <T> + */ +public interface TServiceClientFactory<T extends TServiceClient> { + /** + * Get a brand-new T using <i>prot</i> as both the input and output protocol. + * @param prot + * @return A brand-new T using <i>prot</i> as both the input and output protocol. + */ + public T getClient(TProtocol prot); + + /** + * Get a brand new T using the specified input and output protocols. The + * input and output protocols may be the same instance. + * @param iprot + * @param oprot + * @return a brand new T using the specified input and output protocols + */ + public T getClient(TProtocol iprot, TProtocol oprot); +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TUnion.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TUnion.java new file mode 100644 index 000000000..1ef11df49 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/TUnion.java @@ -0,0 +1,279 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.thrift.protocol.TField; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.protocol.TProtocolException; +import org.apache.thrift.protocol.TStruct; +import org.apache.thrift.scheme.IScheme; +import org.apache.thrift.scheme.SchemeFactory; +import org.apache.thrift.scheme.StandardScheme; +import org.apache.thrift.scheme.TupleScheme; + +public abstract class TUnion<T extends TUnion<T,F>, F extends TFieldIdEnum> implements TBase<T, F> { + + protected Object value_; + protected F setField_; + + protected TUnion() { + setField_ = null; + value_ = null; + } + + private static final Map<Class<? extends IScheme>, SchemeFactory> schemes = new HashMap<Class<? extends IScheme>, SchemeFactory>(); + static { + schemes.put(StandardScheme.class, new TUnionStandardSchemeFactory()); + schemes.put(TupleScheme.class, new TUnionTupleSchemeFactory()); + } + + protected TUnion(F setField, Object value) { + setFieldValue(setField, value); + } + + protected TUnion(TUnion<T, F> other) { + if (!other.getClass().equals(this.getClass())) { + throw new ClassCastException(); + } + setField_ = other.setField_; + value_ = deepCopyObject(other.value_); + } + + private static Object deepCopyObject(Object o) { + if (o instanceof TBase) { + return ((TBase)o).deepCopy(); + } else if (o instanceof ByteBuffer) { + return TBaseHelper.copyBinary((ByteBuffer)o); + } else if (o instanceof List) { + return deepCopyList((List)o); + } else if (o instanceof Set) { + return deepCopySet((Set)o); + } else if (o instanceof Map) { + return deepCopyMap((Map)o); + } else { + return o; + } + } + + private static Map deepCopyMap(Map<Object, Object> map) { + Map copy = new HashMap(map.size()); + for (Map.Entry<Object, Object> entry : map.entrySet()) { + copy.put(deepCopyObject(entry.getKey()), deepCopyObject(entry.getValue())); + } + return copy; + } + + private static Set deepCopySet(Set set) { + Set copy = new HashSet(set.size()); + for (Object o : set) { + copy.add(deepCopyObject(o)); + } + return copy; + } + + private static List deepCopyList(List list) { + List copy = new ArrayList(list.size()); + for (Object o : list) { + copy.add(deepCopyObject(o)); + } + return copy; + } + + public F getSetField() { + return setField_; + } + + public Object getFieldValue() { + return value_; + } + + public Object getFieldValue(F fieldId) { + if (fieldId != setField_) { + throw new IllegalArgumentException("Cannot get the value of field " + fieldId + " because union's set field is " + setField_); + } + + return getFieldValue(); + } + + public Object getFieldValue(int fieldId) { + return getFieldValue(enumForId((short)fieldId)); + } + + public boolean isSet() { + return setField_ != null; + } + + public boolean isSet(F fieldId) { + return setField_ == fieldId; + } + + public boolean isSet(int fieldId) { + return isSet(enumForId((short)fieldId)); + } + + public void read(TProtocol iprot) throws TException { + schemes.get(iprot.getScheme()).getScheme().read(iprot, this); + } + + public void setFieldValue(F fieldId, Object value) { + checkType(fieldId, value); + setField_ = fieldId; + value_ = value; + } + + public void setFieldValue(int fieldId, Object value) { + setFieldValue(enumForId((short)fieldId), value); + } + + public void write(TProtocol oprot) throws TException { + schemes.get(oprot.getScheme()).getScheme().write(oprot, this); + } + + /** + * Implementation should be generated so that we can efficiently type check + * various values. + * @param setField + * @param value + */ + protected abstract void checkType(F setField, Object value) throws ClassCastException; + + /** + * Implementation should be generated to read the right stuff from the wire + * based on the field header. + * @param field + * @return read Object based on the field header, as specified by the argument. + */ + protected abstract Object standardSchemeReadValue(TProtocol iprot, TField field) throws TException; + protected abstract void standardSchemeWriteValue(TProtocol oprot) throws TException; + + protected abstract Object tupleSchemeReadValue(TProtocol iprot, short fieldID) throws TException; + protected abstract void tupleSchemeWriteValue(TProtocol oprot) throws TException; + + protected abstract TStruct getStructDesc(); + + protected abstract TField getFieldDesc(F setField); + + protected abstract F enumForId(short id); + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("<"); + sb.append(this.getClass().getSimpleName()); + sb.append(" "); + + if (getSetField() != null) { + Object v = getFieldValue(); + sb.append(getFieldDesc(getSetField()).name); + sb.append(":"); + if(v instanceof ByteBuffer) { + TBaseHelper.toString((ByteBuffer)v, sb); + } else { + sb.append(v.toString()); + } + } + sb.append(">"); + return sb.toString(); + } + + public final void clear() { + this.setField_ = null; + this.value_ = null; + } + + private static class TUnionStandardSchemeFactory implements SchemeFactory { + public TUnionStandardScheme getScheme() { + return new TUnionStandardScheme(); + } + } + + private static class TUnionStandardScheme extends StandardScheme<TUnion> { + + @Override + public void read(TProtocol iprot, TUnion struct) throws TException { + struct.setField_ = null; + struct.value_ = null; + + iprot.readStructBegin(); + + TField field = iprot.readFieldBegin(); + + struct.value_ = struct.standardSchemeReadValue(iprot, field); + if (struct.value_ != null) { + struct.setField_ = struct.enumForId(field.id); + } + + iprot.readFieldEnd(); + // this is so that we will eat the stop byte. we could put a check here to + // make sure that it actually *is* the stop byte, but it's faster to do it + // this way. + iprot.readFieldBegin(); + iprot.readStructEnd(); + } + + @Override + public void write(TProtocol oprot, TUnion struct) throws TException { + if (struct.getSetField() == null || struct.getFieldValue() == null) { + throw new TProtocolException("Cannot write a TUnion with no set value!"); + } + oprot.writeStructBegin(struct.getStructDesc()); + oprot.writeFieldBegin(struct.getFieldDesc(struct.setField_)); + struct.standardSchemeWriteValue(oprot); + oprot.writeFieldEnd(); + oprot.writeFieldStop(); + oprot.writeStructEnd(); + } + } + + private static class TUnionTupleSchemeFactory implements SchemeFactory { + public TUnionTupleScheme getScheme() { + return new TUnionTupleScheme(); + } + } + + private static class TUnionTupleScheme extends TupleScheme<TUnion> { + + @Override + public void read(TProtocol iprot, TUnion struct) throws TException { + struct.setField_ = null; + struct.value_ = null; + short fieldID = iprot.readI16(); + struct.value_ = struct.tupleSchemeReadValue(iprot, fieldID); + if (struct.value_ != null) { + struct.setField_ = struct.enumForId(fieldID); + } + } + + @Override + public void write(TProtocol oprot, TUnion struct) throws TException { + if (struct.getSetField() == null || struct.getFieldValue() == null) { + throw new TProtocolException("Cannot write a TUnion with no set value!"); + } + oprot.writeI16(struct.setField_.getThriftFieldId()); + struct.tupleSchemeWriteValue(oprot); + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/annotation/Nullable.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/annotation/Nullable.java new file mode 100644 index 000000000..a34b01ebb --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/annotation/Nullable.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation indicating a field, method return, or method parameter may be {@code null}. + * We package our own annotation to avoid a mandatory third-party dependency. + */ +@Retention(RetentionPolicy.CLASS) +public @interface Nullable { + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/AsyncMethodCallback.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/AsyncMethodCallback.java new file mode 100644 index 000000000..4ebde0741 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/AsyncMethodCallback.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.async; + +/** + * A handler interface asynchronous clients can implement to receive future + * notice of the results of an asynchronous method call. + * + * @param <T> The return type of the asynchronously invoked method. + */ +public interface AsyncMethodCallback<T> { + /** + * This method will be called when the remote side has completed invoking + * your method call and the result is fully read. For {@code oneway} method + * calls, this method will be called as soon as we have completed writing out + * the request. + * + * @param response The return value of the asynchronously invoked method; + * {@code null} for void methods which includes + * {@code oneway} methods. + */ + void onComplete(T response); + + /** + * This method will be called when there is either an unexpected client-side + * exception like an IOException or else when the remote method raises an + * exception, either declared in the IDL or due to an unexpected server-side + * error. + * + * @param exception The exception encountered processing the the asynchronous + * method call, may be a local exception or an unmarshalled + * remote exception. + */ + void onError(Exception exception); +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/TAsyncClient.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/TAsyncClient.java new file mode 100644 index 000000000..005018a83 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/TAsyncClient.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.async; + +import org.apache.thrift.protocol.TProtocolFactory; +import org.apache.thrift.transport.TNonblockingTransport; + +public abstract class TAsyncClient { + protected final TProtocolFactory ___protocolFactory; + protected final TNonblockingTransport ___transport; + protected final TAsyncClientManager ___manager; + protected TAsyncMethodCall ___currentMethod; + private Exception ___error; + private long ___timeout; + + public TAsyncClient(TProtocolFactory protocolFactory, TAsyncClientManager manager, TNonblockingTransport transport) { + this(protocolFactory, manager, transport, 0); + } + + public TAsyncClient(TProtocolFactory protocolFactory, TAsyncClientManager manager, TNonblockingTransport transport, long timeout) { + this.___protocolFactory = protocolFactory; + this.___manager = manager; + this.___transport = transport; + this.___timeout = timeout; + } + + public TProtocolFactory getProtocolFactory() { + return ___protocolFactory; + } + + public long getTimeout() { + return ___timeout; + } + + public boolean hasTimeout() { + return ___timeout > 0; + } + + public void setTimeout(long timeout) { + this.___timeout = timeout; + } + + /** + * Is the client in an error state? + * @return If client in an error state? + */ + public boolean hasError() { + return ___error != null; + } + + /** + * Get the client's error - returns null if no error + * @return Get the client's error. <p> returns null if no error + */ + public Exception getError() { + return ___error; + } + + protected void checkReady() { + // Ensure we are not currently executing a method + if (___currentMethod != null) { + throw new IllegalStateException("Client is currently executing another method: " + ___currentMethod.getClass().getName()); + } + + // Ensure we're not in an error state + if (___error != null) { + throw new IllegalStateException("Client has an error!", ___error); + } + } + + /** + * Called by delegate method when finished + */ + protected void onComplete() { + ___currentMethod = null; + } + + /** + * Called by delegate method on error + */ + protected void onError(Exception exception) { + ___transport.close(); + ___currentMethod = null; + ___error = exception; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/TAsyncClientFactory.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/TAsyncClientFactory.java new file mode 100644 index 000000000..28feb73d1 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/TAsyncClientFactory.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.async; + +import org.apache.thrift.transport.TNonblockingTransport; + +public interface TAsyncClientFactory<T extends TAsyncClient> { + public T getAsyncClient(TNonblockingTransport transport); +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/TAsyncClientManager.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/TAsyncClientManager.java new file mode 100644 index 000000000..c07ccd540 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/TAsyncClientManager.java @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.async; + +import java.io.IOException; +import java.io.Serializable; +import java.nio.channels.ClosedSelectorException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.spi.SelectorProvider; +import java.util.Comparator; +import java.util.Iterator; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeoutException; + +import org.apache.thrift.TException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Contains selector thread which transitions method call objects + */ +public class TAsyncClientManager { + private static final Logger LOGGER = LoggerFactory.getLogger(TAsyncClientManager.class.getName()); + + private final SelectThread selectThread; + private final ConcurrentLinkedQueue<TAsyncMethodCall> pendingCalls = new ConcurrentLinkedQueue<TAsyncMethodCall>(); + + public TAsyncClientManager() throws IOException { + this.selectThread = new SelectThread(); + selectThread.start(); + } + + public void call(TAsyncMethodCall method) throws TException { + if (!isRunning()) { + throw new TException("SelectThread is not running"); + } + method.prepareMethodCall(); + pendingCalls.add(method); + selectThread.getSelector().wakeup(); + } + + public void stop() { + selectThread.finish(); + } + + public boolean isRunning() { + return selectThread.isAlive(); + } + + private class SelectThread extends Thread { + private final Selector selector; + private volatile boolean running; + private final TreeSet<TAsyncMethodCall> timeoutWatchSet = new TreeSet<TAsyncMethodCall>(new TAsyncMethodCallTimeoutComparator()); + + public SelectThread() throws IOException { + this.selector = SelectorProvider.provider().openSelector(); + this.running = true; + this.setName("TAsyncClientManager#SelectorThread " + this.getId()); + + // We don't want to hold up the JVM when shutting down + setDaemon(true); + } + + public Selector getSelector() { + return selector; + } + + public void finish() { + running = false; + selector.wakeup(); + } + + public void run() { + while (running) { + try { + try { + if (timeoutWatchSet.size() == 0) { + // No timeouts, so select indefinitely + selector.select(); + } else { + // We have a timeout pending, so calculate the time until then and select appropriately + long nextTimeout = timeoutWatchSet.first().getTimeoutTimestamp(); + long selectTime = nextTimeout - System.currentTimeMillis(); + if (selectTime > 0) { + // Next timeout is in the future, select and wake up then + selector.select(selectTime); + } else { + // Next timeout is now or in past, select immediately so we can time out + selector.selectNow(); + } + } + } catch (IOException e) { + LOGGER.error("Caught IOException in TAsyncClientManager!", e); + } + transitionMethods(); + timeoutMethods(); + startPendingMethods(); + } catch (Exception exception) { + LOGGER.error("Ignoring uncaught exception in SelectThread", exception); + } + } + + try { + selector.close(); + } catch (IOException ex) { + LOGGER.warn("Could not close selector. This may result in leaked resources!", ex); + } + } + + // Transition methods for ready keys + private void transitionMethods() { + try { + Iterator<SelectionKey> keys = selector.selectedKeys().iterator(); + while (keys.hasNext()) { + SelectionKey key = keys.next(); + keys.remove(); + if (!key.isValid()) { + // this can happen if the method call experienced an error and the + // key was cancelled. can also happen if we timeout a method, which + // results in a channel close. + // just skip + continue; + } + TAsyncMethodCall methodCall = (TAsyncMethodCall)key.attachment(); + methodCall.transition(key); + + // If done or error occurred, remove from timeout watch set + if (methodCall.isFinished() || methodCall.getClient().hasError()) { + timeoutWatchSet.remove(methodCall); + } + } + } catch (ClosedSelectorException e) { + LOGGER.error("Caught ClosedSelectorException in TAsyncClientManager!", e); + } + } + + // Timeout any existing method calls + private void timeoutMethods() { + Iterator<TAsyncMethodCall> iterator = timeoutWatchSet.iterator(); + long currentTime = System.currentTimeMillis(); + while (iterator.hasNext()) { + TAsyncMethodCall methodCall = iterator.next(); + if (currentTime >= methodCall.getTimeoutTimestamp()) { + iterator.remove(); + methodCall.onError(new TimeoutException("Operation " + methodCall.getClass() + " timed out after " + (currentTime - methodCall.getStartTime()) + " ms.")); + } else { + break; + } + } + } + + // Start any new calls + private void startPendingMethods() { + TAsyncMethodCall methodCall; + while ((methodCall = pendingCalls.poll()) != null) { + // Catch registration errors. method will catch transition errors and cleanup. + try { + methodCall.start(selector); + + // If timeout specified and first transition went smoothly, add to timeout watch set + TAsyncClient client = methodCall.getClient(); + if (client.hasTimeout() && !client.hasError()) { + timeoutWatchSet.add(methodCall); + } + } catch (Exception exception) { + LOGGER.warn("Caught exception in TAsyncClientManager!", exception); + methodCall.onError(exception); + } + } + } + } + + /** Comparator used in TreeSet */ + private static class TAsyncMethodCallTimeoutComparator implements Comparator<TAsyncMethodCall>, Serializable { + public int compare(TAsyncMethodCall left, TAsyncMethodCall right) { + if (left.getTimeoutTimestamp() == right.getTimeoutTimestamp()) { + return (int)(left.getSequenceId() - right.getSequenceId()); + } else { + return (int)(left.getTimeoutTimestamp() - right.getTimeoutTimestamp()); + } + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/TAsyncMethodCall.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/TAsyncMethodCall.java new file mode 100644 index 000000000..3bf1747f4 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/async/TAsyncMethodCall.java @@ -0,0 +1,284 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.async; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.thrift.TException; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.protocol.TProtocolFactory; +import org.apache.thrift.transport.TFramedTransport; +import org.apache.thrift.transport.TMemoryBuffer; +import org.apache.thrift.transport.TNonblockingTransport; +import org.apache.thrift.transport.TTransportException; + +/** + * Encapsulates an async method call. + * <p> + * Need to generate: + * <ul> + * <li>protected abstract void write_args(TProtocol protocol)</li> + * <li>protected abstract T getResult() throws <Exception_1>, <Exception_2>, ...</li> + * </ul> + * + * @param <T> The return type of the encapsulated method call. + */ +public abstract class TAsyncMethodCall<T> { + + private static final int INITIAL_MEMORY_BUFFER_SIZE = 128; + private static AtomicLong sequenceIdCounter = new AtomicLong(0); + + public static enum State { + CONNECTING, + WRITING_REQUEST_SIZE, + WRITING_REQUEST_BODY, + READING_RESPONSE_SIZE, + READING_RESPONSE_BODY, + RESPONSE_READ, + ERROR; + } + + /** + * Next step in the call, initialized by start() + */ + private State state = null; + + protected final TNonblockingTransport transport; + private final TProtocolFactory protocolFactory; + protected final TAsyncClient client; + private final AsyncMethodCallback<T> callback; + private final boolean isOneway; + private long sequenceId; + private final long timeout; + + private ByteBuffer sizeBuffer; + private final byte[] sizeBufferArray = new byte[4]; + private ByteBuffer frameBuffer; + + private long startTime = System.currentTimeMillis(); + + protected TAsyncMethodCall(TAsyncClient client, TProtocolFactory protocolFactory, TNonblockingTransport transport, AsyncMethodCallback<T> callback, boolean isOneway) { + this.transport = transport; + this.callback = callback; + this.protocolFactory = protocolFactory; + this.client = client; + this.isOneway = isOneway; + this.sequenceId = TAsyncMethodCall.sequenceIdCounter.getAndIncrement(); + this.timeout = client.getTimeout(); + } + + protected State getState() { + return state; + } + + protected boolean isFinished() { + return state == State.RESPONSE_READ; + } + + protected long getStartTime() { + return startTime; + } + + protected long getSequenceId() { + return sequenceId; + } + + public TAsyncClient getClient() { + return client; + } + + public boolean hasTimeout() { + return timeout > 0; + } + + public long getTimeoutTimestamp() { + return timeout + startTime; + } + + protected abstract void write_args(TProtocol protocol) throws TException; + + protected abstract T getResult() throws Exception; + + /** + * Initialize buffers. + * @throws TException if buffer initialization fails + */ + protected void prepareMethodCall() throws TException { + TMemoryBuffer memoryBuffer = new TMemoryBuffer(INITIAL_MEMORY_BUFFER_SIZE); + TProtocol protocol = protocolFactory.getProtocol(memoryBuffer); + write_args(protocol); + + int length = memoryBuffer.length(); + frameBuffer = ByteBuffer.wrap(memoryBuffer.getArray(), 0, length); + + TFramedTransport.encodeFrameSize(length, sizeBufferArray); + sizeBuffer = ByteBuffer.wrap(sizeBufferArray); + } + + /** + * Register with selector and start first state, which could be either connecting or writing. + * @throws IOException if register or starting fails + */ + void start(Selector sel) throws IOException { + SelectionKey key; + if (transport.isOpen()) { + state = State.WRITING_REQUEST_SIZE; + key = transport.registerSelector(sel, SelectionKey.OP_WRITE); + } else { + state = State.CONNECTING; + key = transport.registerSelector(sel, SelectionKey.OP_CONNECT); + + // non-blocking connect can complete immediately, + // in which case we should not expect the OP_CONNECT + if (transport.startConnect()) { + registerForFirstWrite(key); + } + } + + key.attach(this); + } + + void registerForFirstWrite(SelectionKey key) throws IOException { + state = State.WRITING_REQUEST_SIZE; + key.interestOps(SelectionKey.OP_WRITE); + } + + protected ByteBuffer getFrameBuffer() { + return frameBuffer; + } + + /** + * Transition to next state, doing whatever work is required. Since this + * method is only called by the selector thread, we can make changes to our + * select interests without worrying about concurrency. + * @param key + */ + void transition(SelectionKey key) { + // Ensure key is valid + if (!key.isValid()) { + key.cancel(); + Exception e = new TTransportException("Selection key not valid!"); + onError(e); + return; + } + + // Transition function + try { + switch (state) { + case CONNECTING: + doConnecting(key); + break; + case WRITING_REQUEST_SIZE: + doWritingRequestSize(); + break; + case WRITING_REQUEST_BODY: + doWritingRequestBody(key); + break; + case READING_RESPONSE_SIZE: + doReadingResponseSize(); + break; + case READING_RESPONSE_BODY: + doReadingResponseBody(key); + break; + default: // RESPONSE_READ, ERROR, or bug + throw new IllegalStateException("Method call in state " + state + + " but selector called transition method. Seems like a bug..."); + } + } catch (Exception e) { + key.cancel(); + key.attach(null); + onError(e); + } + } + + protected void onError(Exception e) { + client.onError(e); + callback.onError(e); + state = State.ERROR; + } + + private void doReadingResponseBody(SelectionKey key) throws IOException { + if (transport.read(frameBuffer) < 0) { + throw new IOException("Read call frame failed"); + } + if (frameBuffer.remaining() == 0) { + cleanUpAndFireCallback(key); + } + } + + private void cleanUpAndFireCallback(SelectionKey key) { + state = State.RESPONSE_READ; + key.interestOps(0); + // this ensures that the TAsyncMethod instance doesn't hang around + key.attach(null); + try { + T result = this.getResult(); + client.onComplete(); + callback.onComplete(result); + } catch (Exception e) { + key.cancel(); + onError(e); + } + } + + private void doReadingResponseSize() throws IOException { + if (transport.read(sizeBuffer) < 0) { + throw new IOException("Read call frame size failed"); + } + if (sizeBuffer.remaining() == 0) { + state = State.READING_RESPONSE_BODY; + frameBuffer = ByteBuffer.allocate(TFramedTransport.decodeFrameSize(sizeBufferArray)); + } + } + + private void doWritingRequestBody(SelectionKey key) throws IOException { + if (transport.write(frameBuffer) < 0) { + throw new IOException("Write call frame failed"); + } + if (frameBuffer.remaining() == 0) { + if (isOneway) { + cleanUpAndFireCallback(key); + } else { + state = State.READING_RESPONSE_SIZE; + sizeBuffer.rewind(); // Prepare to read incoming frame size + key.interestOps(SelectionKey.OP_READ); + } + } + } + + private void doWritingRequestSize() throws IOException { + if (transport.write(sizeBuffer) < 0) { + throw new IOException("Write call frame size failed"); + } + if (sizeBuffer.remaining() == 0) { + state = State.WRITING_REQUEST_BODY; + } + } + + private void doConnecting(SelectionKey key) throws IOException { + if (!key.isConnectable() || !transport.finishConnect()) { + throw new IOException("not connectable or finishConnect returned false after we got an OP_CONNECT"); + } + registerForFirstWrite(key); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/EnumMetaData.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/EnumMetaData.java new file mode 100644 index 000000000..be49cb949 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/EnumMetaData.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.meta_data; + +import org.apache.thrift.TEnum; + +public class EnumMetaData extends FieldValueMetaData { + public final Class<? extends TEnum> enumClass; + + public EnumMetaData(byte type, Class<? extends TEnum> sClass){ + super(type); + this.enumClass = sClass; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/FieldMetaData.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/FieldMetaData.java new file mode 100644 index 000000000..445f7e474 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/FieldMetaData.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.meta_data; + +import java.util.HashMap; +import java.util.Map; +import org.apache.thrift.TBase; +import org.apache.thrift.TFieldIdEnum; + +/** + * This class is used to store meta data about thrift fields. Every field in a + * a struct should have a corresponding instance of this class describing it. + * + */ +public class FieldMetaData implements java.io.Serializable { + public final String fieldName; + public final byte requirementType; + public final FieldValueMetaData valueMetaData; + private static Map<Class<? extends TBase>, Map<? extends TFieldIdEnum, FieldMetaData>> structMap; + + static { + structMap = new HashMap<Class<? extends TBase>, Map<? extends TFieldIdEnum, FieldMetaData>>(); + } + + public FieldMetaData(String name, byte req, FieldValueMetaData vMetaData){ + this.fieldName = name; + this.requirementType = req; + this.valueMetaData = vMetaData; + } + + public static synchronized void addStructMetaDataMap(Class<? extends TBase> sClass, Map<? extends TFieldIdEnum, FieldMetaData> map){ + structMap.put(sClass, map); + } + + /** + * Returns a map with metadata (i.e. instances of FieldMetaData) that + * describe the fields of the given class. + * + * @param sClass The TBase class for which the metadata map is requested + */ + public static synchronized Map<? extends TFieldIdEnum, FieldMetaData> getStructMetaDataMap(Class<? extends TBase> sClass){ + if (!structMap.containsKey(sClass)){ // Load class if it hasn't been loaded + try{ + sClass.newInstance(); + } catch (InstantiationException e){ + throw new RuntimeException("InstantiationException for TBase class: " + sClass.getName() + ", message: " + e.getMessage()); + } catch (IllegalAccessException e){ + throw new RuntimeException("IllegalAccessException for TBase class: " + sClass.getName() + ", message: " + e.getMessage()); + } + } + return structMap.get(sClass); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/FieldValueMetaData.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/FieldValueMetaData.java new file mode 100644 index 000000000..2180b089b --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/FieldValueMetaData.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.meta_data; + +import org.apache.thrift.protocol.TType; + +/** + * FieldValueMetaData and collection of subclasses to store metadata about + * the value(s) of a field + */ +public class FieldValueMetaData implements java.io.Serializable { + public final byte type; + + private final boolean isTypedefType; + private final String typedefName; + private final boolean isBinary; + + public FieldValueMetaData(byte type, boolean binary) { + this.type = type; + this.isTypedefType = false; + this.typedefName = null; + this.isBinary = binary; + } + + public FieldValueMetaData(byte type) { + this(type, false); + } + + public FieldValueMetaData(byte type, String typedefName) { + this.type = type; + this.isTypedefType = true; + this.typedefName = typedefName; + this.isBinary = false; + } + + public boolean isTypedef() { + return isTypedefType; + } + + public String getTypedefName() { + return typedefName; + } + + public boolean isStruct() { + return type == TType.STRUCT; + } + + public boolean isContainer() { + return type == TType.LIST || type == TType.MAP || type == TType.SET; + } + + public boolean isBinary() { + return isBinary; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/ListMetaData.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/ListMetaData.java new file mode 100644 index 000000000..8e7073bf5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/ListMetaData.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.meta_data; + +public class ListMetaData extends FieldValueMetaData { + public final FieldValueMetaData elemMetaData; + + public ListMetaData(byte type, FieldValueMetaData eMetaData){ + super(type); + this.elemMetaData = eMetaData; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/MapMetaData.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/MapMetaData.java new file mode 100644 index 000000000..e7c408c78 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/MapMetaData.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.meta_data; + +public class MapMetaData extends FieldValueMetaData { + public final FieldValueMetaData keyMetaData; + public final FieldValueMetaData valueMetaData; + + public MapMetaData(byte type, FieldValueMetaData kMetaData, FieldValueMetaData vMetaData){ + super(type); + this.keyMetaData = kMetaData; + this.valueMetaData = vMetaData; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/SetMetaData.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/SetMetaData.java new file mode 100644 index 000000000..cf4b96aab --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/SetMetaData.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.meta_data; + +public class SetMetaData extends FieldValueMetaData { + public final FieldValueMetaData elemMetaData; + + public SetMetaData(byte type, FieldValueMetaData eMetaData){ + super(type); + this.elemMetaData = eMetaData; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/StructMetaData.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/StructMetaData.java new file mode 100644 index 000000000..b37d21dab --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/meta_data/StructMetaData.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.meta_data; + +import org.apache.thrift.TBase; + +public class StructMetaData extends FieldValueMetaData { + public final Class<? extends TBase> structClass; + + public StructMetaData(byte type, Class<? extends TBase> sClass){ + super(type); + this.structClass = sClass; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/ShortStack.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/ShortStack.java new file mode 100644 index 000000000..9e6593074 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/ShortStack.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +import java.util.Arrays; + +/** + * ShortStack is a short-specific Stack implementation written for the express + * purpose of very fast operations on TCompactProtocol's field id stack. This + * implementation performs at least 10x faster than java.util.Stack. + */ +class ShortStack { + + private short[] vector; + + /** Always points to the next location */ + private int top = 0; + + public ShortStack(int initialCapacity) { + vector = new short[initialCapacity]; + } + + public short pop() { + return vector[--top]; + } + + public void push(short pushed) { + if (vector.length == top) { + grow(); + } + vector[top++] = pushed; + } + + private void grow() { + vector = Arrays.copyOf(vector, vector.length << 1); + } + + public void clear() { + top = 0; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("<ShortStack vector:["); + for (int i = 0; i < vector.length; i++) { + boolean isTop = (i == (top - 1)); + short value = vector[i]; + if (i != 0) { + sb.append(' '); + } + if (isTop) { + sb.append(">>").append(value).append("<<"); + } else { + sb.append(value); + } + } + sb.append("]>"); + return sb.toString(); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TBase64Utils.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TBase64Utils.java new file mode 100644 index 000000000..abfc965b7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TBase64Utils.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +/** + * Class for encoding and decoding Base64 data. + * + * This class is kept at package level because the interface does no input + * validation and is therefore too low-level for generalized reuse. + * + * Note also that the encoding does not pad with equal signs , as discussed in + * section 2.2 of the RFC (http://www.faqs.org/rfcs/rfc3548.html). Furthermore, + * bad data encountered when decoding is neither rejected or ignored but simply + * results in bad decoded data -- this is not in compliance with the RFC but is + * done in the interest of performance. + * + */ +class TBase64Utils { + + private static final String ENCODE_TABLE = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + /** + * Encode len bytes of data in src at offset srcOff, storing the result into + * dst at offset dstOff. len must be 1, 2, or 3. dst must have at least len+1 + * bytes of space at dstOff. src and dst should not be the same object. This + * method does no validation of the input values in the interest of + * performance. + * + * @param src the source of bytes to encode + * @param srcOff the offset into the source to read the unencoded bytes + * @param len the number of bytes to encode (must be 1, 2, or 3). + * @param dst the destination for the encoding + * @param dstOff the offset into the destination to place the encoded bytes + */ + static final void encode(byte[] src, int srcOff, int len, byte[] dst, + int dstOff) { + dst[dstOff] = (byte)ENCODE_TABLE.charAt((src[srcOff] >> 2) & 0x3F); + if (len == 3) { + dst[dstOff + 1] = + (byte)ENCODE_TABLE.charAt( + ((src[srcOff] << 4) & 0x30) | ((src[srcOff+1] >> 4) & 0x0F)); + dst[dstOff + 2] = + (byte)ENCODE_TABLE.charAt( + ((src[srcOff+1] << 2) & 0x3C) | ((src[srcOff+2] >> 6) & 0x03)); + dst[dstOff + 3] = + (byte)ENCODE_TABLE.charAt(src[srcOff+2] & 0x3F); + } + else if (len == 2) { + dst[dstOff+1] = + (byte)ENCODE_TABLE.charAt( + ((src[srcOff] << 4) & 0x30) | ((src[srcOff+1] >> 4) & 0x0F)); + dst[dstOff + 2] = + (byte)ENCODE_TABLE.charAt((src[srcOff+1] << 2) & 0x3C); + } + else { // len == 1) { + dst[dstOff + 1] = + (byte)ENCODE_TABLE.charAt((src[srcOff] << 4) & 0x30); + } + } + + private static final byte[] DECODE_TABLE = { + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, + 52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, + 15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, + -1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, + 41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + }; + + /** + * Decode len bytes of data in src at offset srcOff, storing the result into + * dst at offset dstOff. len must be 2, 3, or 4. dst must have at least len-1 + * bytes of space at dstOff. src and dst may be the same object as long as + * dstoff <= srcOff. This method does no validation of the input values in + * the interest of performance. + * + * @param src the source of bytes to decode + * @param srcOff the offset into the source to read the encoded bytes + * @param len the number of bytes to decode (must be 2, 3, or 4) + * @param dst the destination for the decoding + * @param dstOff the offset into the destination to place the decoded bytes + */ + static final void decode(byte[] src, int srcOff, int len, byte[] dst, + int dstOff) { + dst[dstOff] = (byte) + ((DECODE_TABLE[src[srcOff] & 0x0FF] << 2) | + (DECODE_TABLE[src[srcOff+1] & 0x0FF] >> 4)); + if (len > 2) { + dst[dstOff+1] = (byte) + (((DECODE_TABLE[src[srcOff+1] & 0x0FF] << 4) & 0xF0) | + (DECODE_TABLE[src[srcOff+2] & 0x0FF] >> 2)); + if (len > 3) { + dst[dstOff+2] = (byte) + (((DECODE_TABLE[src[srcOff+2] & 0x0FF] << 6) & 0xC0) | + DECODE_TABLE[src[srcOff+3] & 0x0FF]); + } + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TBinaryProtocol.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TBinaryProtocol.java new file mode 100644 index 000000000..7924e2fe6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TBinaryProtocol.java @@ -0,0 +1,457 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +import org.apache.thrift.TException; +import org.apache.thrift.transport.TTransport; + +/** + * Binary protocol implementation for thrift. + * + */ +public class TBinaryProtocol extends TProtocol { + private static final TStruct ANONYMOUS_STRUCT = new TStruct(); + private static final long NO_LENGTH_LIMIT = -1; + + protected static final int VERSION_MASK = 0xffff0000; + protected static final int VERSION_1 = 0x80010000; + + /** + * The maximum number of bytes to read from the transport for + * variable-length fields (such as strings or binary) or {@link #NO_LENGTH_LIMIT} for + * unlimited. + */ + private final long stringLengthLimit_; + + /** + * The maximum number of elements to read from the network for + * containers (maps, sets, lists), or {@link #NO_LENGTH_LIMIT} for unlimited. + */ + private final long containerLengthLimit_; + + protected boolean strictRead_; + protected boolean strictWrite_; + + private final byte[] inoutTemp = new byte[8]; + + /** + * Factory + */ + public static class Factory implements TProtocolFactory { + protected long stringLengthLimit_; + protected long containerLengthLimit_; + protected boolean strictRead_; + protected boolean strictWrite_; + + public Factory() { + this(false, true); + } + + public Factory(boolean strictRead, boolean strictWrite) { + this(strictRead, strictWrite, NO_LENGTH_LIMIT, NO_LENGTH_LIMIT); + } + + public Factory(long stringLengthLimit, long containerLengthLimit) { + this(false, true, stringLengthLimit, containerLengthLimit); + } + + public Factory(boolean strictRead, boolean strictWrite, long stringLengthLimit, long containerLengthLimit) { + stringLengthLimit_ = stringLengthLimit; + containerLengthLimit_ = containerLengthLimit; + strictRead_ = strictRead; + strictWrite_ = strictWrite; + } + + public TProtocol getProtocol(TTransport trans) { + return new TBinaryProtocol(trans, stringLengthLimit_, containerLengthLimit_, strictRead_, strictWrite_); + } + } + + /** + * Constructor + */ + public TBinaryProtocol(TTransport trans) { + this(trans, false, true); + } + + public TBinaryProtocol(TTransport trans, boolean strictRead, boolean strictWrite) { + this(trans, NO_LENGTH_LIMIT, NO_LENGTH_LIMIT, strictRead, strictWrite); + } + + public TBinaryProtocol(TTransport trans, long stringLengthLimit, long containerLengthLimit) { + this(trans, stringLengthLimit, containerLengthLimit, false, true); + } + + public TBinaryProtocol(TTransport trans, long stringLengthLimit, long containerLengthLimit, boolean strictRead, boolean strictWrite) { + super(trans); + stringLengthLimit_ = stringLengthLimit; + containerLengthLimit_ = containerLengthLimit; + strictRead_ = strictRead; + strictWrite_ = strictWrite; + } + + @Override + public void writeMessageBegin(TMessage message) throws TException { + if (strictWrite_) { + int version = VERSION_1 | message.type; + writeI32(version); + writeString(message.name); + writeI32(message.seqid); + } else { + writeString(message.name); + writeByte(message.type); + writeI32(message.seqid); + } + } + + @Override + public void writeMessageEnd() throws TException {} + + @Override + public void writeStructBegin(TStruct struct) throws TException {} + + @Override + public void writeStructEnd() throws TException {} + + @Override + public void writeFieldBegin(TField field) throws TException { + writeByte(field.type); + writeI16(field.id); + } + + @Override + public void writeFieldEnd() throws TException {} + + @Override + public void writeFieldStop() throws TException { + writeByte(TType.STOP); + } + + @Override + public void writeMapBegin(TMap map) throws TException { + writeByte(map.keyType); + writeByte(map.valueType); + writeI32(map.size); + } + + @Override + public void writeMapEnd() throws TException {} + + @Override + public void writeListBegin(TList list) throws TException { + writeByte(list.elemType); + writeI32(list.size); + } + + @Override + public void writeListEnd() throws TException {} + + @Override + public void writeSetBegin(TSet set) throws TException { + writeByte(set.elemType); + writeI32(set.size); + } + + @Override + public void writeSetEnd() throws TException {} + + @Override + public void writeBool(boolean b) throws TException { + writeByte(b ? (byte)1 : (byte)0); + } + + @Override + public void writeByte(byte b) throws TException { + inoutTemp[0] = b; + trans_.write(inoutTemp, 0, 1); + } + + @Override + public void writeI16(short i16) throws TException { + inoutTemp[0] = (byte)(0xff & (i16 >> 8)); + inoutTemp[1] = (byte)(0xff & (i16)); + trans_.write(inoutTemp, 0, 2); + } + + @Override + public void writeI32(int i32) throws TException { + inoutTemp[0] = (byte)(0xff & (i32 >> 24)); + inoutTemp[1] = (byte)(0xff & (i32 >> 16)); + inoutTemp[2] = (byte)(0xff & (i32 >> 8)); + inoutTemp[3] = (byte)(0xff & (i32)); + trans_.write(inoutTemp, 0, 4); + } + + @Override + public void writeI64(long i64) throws TException { + inoutTemp[0] = (byte)(0xff & (i64 >> 56)); + inoutTemp[1] = (byte)(0xff & (i64 >> 48)); + inoutTemp[2] = (byte)(0xff & (i64 >> 40)); + inoutTemp[3] = (byte)(0xff & (i64 >> 32)); + inoutTemp[4] = (byte)(0xff & (i64 >> 24)); + inoutTemp[5] = (byte)(0xff & (i64 >> 16)); + inoutTemp[6] = (byte)(0xff & (i64 >> 8)); + inoutTemp[7] = (byte)(0xff & (i64)); + trans_.write(inoutTemp, 0, 8); + } + + @Override + public void writeDouble(double dub) throws TException { + writeI64(Double.doubleToLongBits(dub)); + } + + @Override + public void writeString(String str) throws TException { + byte[] dat = str.getBytes(StandardCharsets.UTF_8); + writeI32(dat.length); + trans_.write(dat, 0, dat.length); + } + + @Override + public void writeBinary(ByteBuffer bin) throws TException { + int length = bin.limit() - bin.position(); + writeI32(length); + trans_.write(bin.array(), bin.position() + bin.arrayOffset(), length); + } + + /** + * Reading methods. + */ + + @Override + public TMessage readMessageBegin() throws TException { + int size = readI32(); + if (size < 0) { + int version = size & VERSION_MASK; + if (version != VERSION_1) { + throw new TProtocolException(TProtocolException.BAD_VERSION, "Bad version in readMessageBegin"); + } + return new TMessage(readString(), (byte)(size & 0x000000ff), readI32()); + } else { + if (strictRead_) { + throw new TProtocolException(TProtocolException.BAD_VERSION, "Missing version in readMessageBegin, old client?"); + } + return new TMessage(readStringBody(size), readByte(), readI32()); + } + } + + @Override + public void readMessageEnd() throws TException {} + + @Override + public TStruct readStructBegin() throws TException { + return ANONYMOUS_STRUCT; + } + + @Override + public void readStructEnd() throws TException {} + + @Override + public TField readFieldBegin() throws TException { + byte type = readByte(); + short id = type == TType.STOP ? 0 : readI16(); + return new TField("", type, id); + } + + @Override + public void readFieldEnd() throws TException {} + + @Override + public TMap readMapBegin() throws TException { + TMap map = new TMap(readByte(), readByte(), readI32()); + checkContainerReadLength(map.size); + return map; + } + + @Override + public void readMapEnd() throws TException {} + + @Override + public TList readListBegin() throws TException { + TList list = new TList(readByte(), readI32()); + checkContainerReadLength(list.size); + return list; + } + + @Override + public void readListEnd() throws TException {} + + @Override + public TSet readSetBegin() throws TException { + TSet set = new TSet(readByte(), readI32()); + checkContainerReadLength(set.size); + return set; + } + + @Override + public void readSetEnd() throws TException {} + + @Override + public boolean readBool() throws TException { + return (readByte() == 1); + } + + @Override + public byte readByte() throws TException { + if (trans_.getBytesRemainingInBuffer() >= 1) { + byte b = trans_.getBuffer()[trans_.getBufferPosition()]; + trans_.consumeBuffer(1); + return b; + } + readAll(inoutTemp, 0, 1); + return inoutTemp[0]; + } + + @Override + public short readI16() throws TException { + byte[] buf = inoutTemp; + int off = 0; + + if (trans_.getBytesRemainingInBuffer() >= 2) { + buf = trans_.getBuffer(); + off = trans_.getBufferPosition(); + trans_.consumeBuffer(2); + } else { + readAll(inoutTemp, 0, 2); + } + + return + (short) + (((buf[off] & 0xff) << 8) | + ((buf[off+1] & 0xff))); + } + + @Override + public int readI32() throws TException { + byte[] buf = inoutTemp; + int off = 0; + + if (trans_.getBytesRemainingInBuffer() >= 4) { + buf = trans_.getBuffer(); + off = trans_.getBufferPosition(); + trans_.consumeBuffer(4); + } else { + readAll(inoutTemp, 0, 4); + } + return + ((buf[off] & 0xff) << 24) | + ((buf[off+1] & 0xff) << 16) | + ((buf[off+2] & 0xff) << 8) | + ((buf[off+3] & 0xff)); + } + + @Override + public long readI64() throws TException { + byte[] buf = inoutTemp; + int off = 0; + + if (trans_.getBytesRemainingInBuffer() >= 8) { + buf = trans_.getBuffer(); + off = trans_.getBufferPosition(); + trans_.consumeBuffer(8); + } else { + readAll(inoutTemp, 0, 8); + } + + return + ((long)(buf[off] & 0xff) << 56) | + ((long)(buf[off+1] & 0xff) << 48) | + ((long)(buf[off+2] & 0xff) << 40) | + ((long)(buf[off+3] & 0xff) << 32) | + ((long)(buf[off+4] & 0xff) << 24) | + ((long)(buf[off+5] & 0xff) << 16) | + ((long)(buf[off+6] & 0xff) << 8) | + ((long)(buf[off+7] & 0xff)); + } + + @Override + public double readDouble() throws TException { + return Double.longBitsToDouble(readI64()); + } + + @Override + public String readString() throws TException { + int size = readI32(); + + checkStringReadLength(size); + + if (trans_.getBytesRemainingInBuffer() >= size) { + String s = new String(trans_.getBuffer(), trans_.getBufferPosition(), + size, StandardCharsets.UTF_8); + trans_.consumeBuffer(size); + return s; + } + + return readStringBody(size); + } + + public String readStringBody(int size) throws TException { + checkStringReadLength(size); + byte[] buf = new byte[size]; + trans_.readAll(buf, 0, size); + return new String(buf, StandardCharsets.UTF_8); + } + + @Override + public ByteBuffer readBinary() throws TException { + int size = readI32(); + + checkStringReadLength(size); + + if (trans_.getBytesRemainingInBuffer() >= size) { + ByteBuffer bb = ByteBuffer.wrap(trans_.getBuffer(), trans_.getBufferPosition(), size); + trans_.consumeBuffer(size); + return bb; + } + + byte[] buf = new byte[size]; + trans_.readAll(buf, 0, size); + return ByteBuffer.wrap(buf); + } + + private void checkStringReadLength(int length) throws TProtocolException { + if (length < 0) { + throw new TProtocolException(TProtocolException.NEGATIVE_SIZE, + "Negative length: " + length); + } + if (stringLengthLimit_ != NO_LENGTH_LIMIT && length > stringLengthLimit_) { + throw new TProtocolException(TProtocolException.SIZE_LIMIT, + "Length exceeded max allowed: " + length); + } + } + + private void checkContainerReadLength(int length) throws TProtocolException { + if (length < 0) { + throw new TProtocolException(TProtocolException.NEGATIVE_SIZE, + "Negative length: " + length); + } + if (containerLengthLimit_ != NO_LENGTH_LIMIT && length > containerLengthLimit_) { + throw new TProtocolException(TProtocolException.SIZE_LIMIT, + "Length exceeded max allowed: " + length); + } + } + + private int readAll(byte[] buf, int off, int len) throws TException { + return trans_.readAll(buf, off, len); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TCompactProtocol.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TCompactProtocol.java new file mode 100644 index 000000000..ee0586945 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TCompactProtocol.java @@ -0,0 +1,904 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +import org.apache.thrift.TException; +import org.apache.thrift.transport.TTransport; + +/** + * TCompactProtocol2 is the Java implementation of the compact protocol specified + * in THRIFT-110. The fundamental approach to reducing the overhead of + * structures is a) use variable-length integers all over the place and b) make + * use of unused bits wherever possible. Your savings will obviously vary + * based on the specific makeup of your structs, but in general, the more + * fields, nested structures, short strings and collections, and low-value i32 + * and i64 fields you have, the more benefit you'll see. + */ +public class TCompactProtocol extends TProtocol { + private final static byte[] EMPTY_BYTES = new byte[0]; + private final static ByteBuffer EMPTY_BUFFER = ByteBuffer.wrap(EMPTY_BYTES); + + private final static long NO_LENGTH_LIMIT = -1; + + private final static TStruct ANONYMOUS_STRUCT = new TStruct(""); + private final static TField TSTOP = new TField("", TType.STOP, (short)0); + + private final static byte[] ttypeToCompactType = new byte[16]; + + static { + ttypeToCompactType[TType.STOP] = TType.STOP; + ttypeToCompactType[TType.BOOL] = Types.BOOLEAN_TRUE; + ttypeToCompactType[TType.BYTE] = Types.BYTE; + ttypeToCompactType[TType.I16] = Types.I16; + ttypeToCompactType[TType.I32] = Types.I32; + ttypeToCompactType[TType.I64] = Types.I64; + ttypeToCompactType[TType.DOUBLE] = Types.DOUBLE; + ttypeToCompactType[TType.STRING] = Types.BINARY; + ttypeToCompactType[TType.LIST] = Types.LIST; + ttypeToCompactType[TType.SET] = Types.SET; + ttypeToCompactType[TType.MAP] = Types.MAP; + ttypeToCompactType[TType.STRUCT] = Types.STRUCT; + } + + /** + * TProtocolFactory that produces TCompactProtocols. + */ + public static class Factory implements TProtocolFactory { + private final long stringLengthLimit_; + private final long containerLengthLimit_; + + public Factory() { + this(NO_LENGTH_LIMIT, NO_LENGTH_LIMIT); + } + + public Factory(long stringLengthLimit) { + this(stringLengthLimit, NO_LENGTH_LIMIT); + } + + public Factory(long stringLengthLimit, long containerLengthLimit) { + this.containerLengthLimit_ = containerLengthLimit; + this.stringLengthLimit_ = stringLengthLimit; + } + + public TProtocol getProtocol(TTransport trans) { + return new TCompactProtocol(trans, stringLengthLimit_, containerLengthLimit_); + } + } + + private static final byte PROTOCOL_ID = (byte)0x82; + private static final byte VERSION = 1; + private static final byte VERSION_MASK = 0x1f; // 0001 1111 + private static final byte TYPE_MASK = (byte)0xE0; // 1110 0000 + private static final byte TYPE_BITS = 0x07; // 0000 0111 + private static final int TYPE_SHIFT_AMOUNT = 5; + + /** + * All of the on-wire type codes. + */ + private static class Types { + public static final byte BOOLEAN_TRUE = 0x01; + public static final byte BOOLEAN_FALSE = 0x02; + public static final byte BYTE = 0x03; + public static final byte I16 = 0x04; + public static final byte I32 = 0x05; + public static final byte I64 = 0x06; + public static final byte DOUBLE = 0x07; + public static final byte BINARY = 0x08; + public static final byte LIST = 0x09; + public static final byte SET = 0x0A; + public static final byte MAP = 0x0B; + public static final byte STRUCT = 0x0C; + } + + /** + * Used to keep track of the last field for the current and previous structs, + * so we can do the delta stuff. + */ + private ShortStack lastField_ = new ShortStack(15); + + private short lastFieldId_ = 0; + + /** + * If we encounter a boolean field begin, save the TField here so it can + * have the value incorporated. + */ + private TField booleanField_ = null; + + /** + * If we read a field header, and it's a boolean field, save the boolean + * value here so that readBool can use it. + */ + private Boolean boolValue_ = null; + + /** + * The maximum number of bytes to read from the transport for + * variable-length fields (such as strings or binary) or {@link #NO_LENGTH_LIMIT} for + * unlimited. + */ + private final long stringLengthLimit_; + + /** + * The maximum number of elements to read from the network for + * containers (maps, sets, lists), or {@link #NO_LENGTH_LIMIT} for unlimited. + */ + private final long containerLengthLimit_; + + /** + * Temporary buffer used for various operations that would otherwise require a + * small allocation. + */ + private final byte[] temp = new byte[10]; + + /** + * Create a TCompactProtocol. + * + * @param transport the TTransport object to read from or write to. + * @param stringLengthLimit the maximum number of bytes to read for + * variable-length fields. + * @param containerLengthLimit the maximum number of elements to read + * for containers. + */ + public TCompactProtocol(TTransport transport, long stringLengthLimit, long containerLengthLimit) { + super(transport); + this.stringLengthLimit_ = stringLengthLimit; + this.containerLengthLimit_ = containerLengthLimit; + } + + /** + * Create a TCompactProtocol. + * + * @param transport the TTransport object to read from or write to. + * @param stringLengthLimit the maximum number of bytes to read for + * variable-length fields. + * @deprecated Use constructor specifying both string limit and container limit instead + */ + @Deprecated + public TCompactProtocol(TTransport transport, long stringLengthLimit) { + this(transport, stringLengthLimit, NO_LENGTH_LIMIT); + } + + /** + * Create a TCompactProtocol. + * + * @param transport the TTransport object to read from or write to. + */ + public TCompactProtocol(TTransport transport) { + this(transport, NO_LENGTH_LIMIT, NO_LENGTH_LIMIT); + } + + @Override + public void reset() { + lastField_.clear(); + lastFieldId_ = 0; + } + + // + // Public Writing methods. + // + + /** + * Write a message header to the wire. Compact Protocol messages contain the + * protocol version so we can migrate forwards in the future if need be. + */ + @Override + public void writeMessageBegin(TMessage message) throws TException { + writeByteDirect(PROTOCOL_ID); + writeByteDirect((VERSION & VERSION_MASK) | ((message.type << TYPE_SHIFT_AMOUNT) & TYPE_MASK)); + writeVarint32(message.seqid); + writeString(message.name); + } + + /** + * Write a struct begin. This doesn't actually put anything on the wire. We + * use it as an opportunity to put special placeholder markers on the field + * stack so we can get the field id deltas correct. + */ + @Override + public void writeStructBegin(TStruct struct) throws TException { + lastField_.push(lastFieldId_); + lastFieldId_ = 0; + } + + /** + * Write a struct end. This doesn't actually put anything on the wire. We use + * this as an opportunity to pop the last field from the current struct off + * of the field stack. + */ + public void writeStructEnd() throws TException { + lastFieldId_ = lastField_.pop(); + } + + /** + * Write a field header containing the field id and field type. If the + * difference between the current field id and the last one is small (< 15), + * then the field id will be encoded in the 4 MSB as a delta. Otherwise, the + * field id will follow the type header as a zigzag varint. + */ + public void writeFieldBegin(TField field) throws TException { + if (field.type == TType.BOOL) { + // we want to possibly include the value, so we'll wait. + booleanField_ = field; + } else { + writeFieldBeginInternal(field, (byte)-1); + } + } + + /** + * The workhorse of writeFieldBegin. It has the option of doing a + * 'type override' of the type header. This is used specifically in the + * boolean field case. + */ + private void writeFieldBeginInternal(TField field, byte typeOverride) throws TException { + // short lastField = lastField_.pop(); + + // if there's a type override, use that. + byte typeToWrite = typeOverride == -1 ? getCompactType(field.type) : typeOverride; + + // check if we can use delta encoding for the field id + if (field.id > lastFieldId_ && field.id - lastFieldId_ <= 15) { + // write them together + writeByteDirect((field.id - lastFieldId_) << 4 | typeToWrite); + } else { + // write them separate + writeByteDirect(typeToWrite); + writeI16(field.id); + } + + lastFieldId_ = field.id; + // lastField_.push(field.id); + } + + /** + * Write the STOP symbol so we know there are no more fields in this struct. + */ + public void writeFieldStop() throws TException { + writeByteDirect(TType.STOP); + } + + /** + * Write a map header. If the map is empty, omit the key and value type + * headers, as we don't need any additional information to skip it. + */ + public void writeMapBegin(TMap map) throws TException { + if (map.size == 0) { + writeByteDirect(0); + } else { + writeVarint32(map.size); + writeByteDirect(getCompactType(map.keyType) << 4 | getCompactType(map.valueType)); + } + } + + /** + * Write a list header. + */ + public void writeListBegin(TList list) throws TException { + writeCollectionBegin(list.elemType, list.size); + } + + /** + * Write a set header. + */ + public void writeSetBegin(TSet set) throws TException { + writeCollectionBegin(set.elemType, set.size); + } + + /** + * Write a boolean value. Potentially, this could be a boolean field, in + * which case the field header info isn't written yet. If so, decide what the + * right type header is for the value and then write the field header. + * Otherwise, write a single byte. + */ + public void writeBool(boolean b) throws TException { + if (booleanField_ != null) { + // we haven't written the field header yet + writeFieldBeginInternal(booleanField_, b ? Types.BOOLEAN_TRUE : Types.BOOLEAN_FALSE); + booleanField_ = null; + } else { + // we're not part of a field, so just write the value. + writeByteDirect(b ? Types.BOOLEAN_TRUE : Types.BOOLEAN_FALSE); + } + } + + /** + * Write a byte. Nothing to see here! + */ + public void writeByte(byte b) throws TException { + writeByteDirect(b); + } + + /** + * Write an I16 as a zigzag varint. + */ + public void writeI16(short i16) throws TException { + writeVarint32(intToZigZag(i16)); + } + + /** + * Write an i32 as a zigzag varint. + */ + public void writeI32(int i32) throws TException { + writeVarint32(intToZigZag(i32)); + } + + /** + * Write an i64 as a zigzag varint. + */ + public void writeI64(long i64) throws TException { + writeVarint64(longToZigzag(i64)); + } + + /** + * Write a double to the wire as 8 bytes. + */ + public void writeDouble(double dub) throws TException { + fixedLongToBytes(Double.doubleToLongBits(dub), temp, 0); + trans_.write(temp, 0, 8); + } + + /** + * Write a string to the wire with a varint size preceding. + */ + public void writeString(String str) throws TException { + byte[] bytes = str.getBytes(StandardCharsets.UTF_8); + writeBinary(bytes, 0, bytes.length); + } + + /** + * Write a byte array, using a varint for the size. + */ + public void writeBinary(ByteBuffer bin) throws TException { + int length = bin.limit() - bin.position(); + writeBinary(bin.array(), bin.position() + bin.arrayOffset(), length); + } + + private void writeBinary(byte[] buf, int offset, int length) throws TException { + writeVarint32(length); + trans_.write(buf, offset, length); + } + + // + // These methods are called by structs, but don't actually have any wire + // output or purpose. + // + + public void writeMessageEnd() throws TException {} + public void writeMapEnd() throws TException {} + public void writeListEnd() throws TException {} + public void writeSetEnd() throws TException {} + public void writeFieldEnd() throws TException {} + + // + // Internal writing methods + // + + /** + * Abstract method for writing the start of lists and sets. List and sets on + * the wire differ only by the type indicator. + */ + protected void writeCollectionBegin(byte elemType, int size) throws TException { + if (size <= 14) { + writeByteDirect(size << 4 | getCompactType(elemType)); + } else { + writeByteDirect(0xf0 | getCompactType(elemType)); + writeVarint32(size); + } + } + + /** + * Write an i32 as a varint. Results in 1-5 bytes on the wire. + * TODO: make a permanent buffer like writeVarint64? + */ + private void writeVarint32(int n) throws TException { + int idx = 0; + while (true) { + if ((n & ~0x7F) == 0) { + temp[idx++] = (byte)n; + // writeByteDirect((byte)n); + break; + // return; + } else { + temp[idx++] = (byte)((n & 0x7F) | 0x80); + // writeByteDirect((byte)((n & 0x7F) | 0x80)); + n >>>= 7; + } + } + trans_.write(temp, 0, idx); + } + + /** + * Write an i64 as a varint. Results in 1-10 bytes on the wire. + */ + private void writeVarint64(long n) throws TException { + int idx = 0; + while (true) { + if ((n & ~0x7FL) == 0) { + temp[idx++] = (byte)n; + break; + } else { + temp[idx++] = ((byte)((n & 0x7F) | 0x80)); + n >>>= 7; + } + } + trans_.write(temp, 0, idx); + } + + /** + * Convert l into a zigzag long. This allows negative numbers to be + * represented compactly as a varint. + */ + private long longToZigzag(long l) { + return (l << 1) ^ (l >> 63); + } + + /** + * Convert n into a zigzag int. This allows negative numbers to be + * represented compactly as a varint. + */ + private int intToZigZag(int n) { + return (n << 1) ^ (n >> 31); + } + + /** + * Convert a long into little-endian bytes in buf starting at off and going + * until off+7. + */ + private void fixedLongToBytes(long n, byte[] buf, int off) { + buf[off+0] = (byte)( n & 0xff); + buf[off+1] = (byte)((n >> 8 ) & 0xff); + buf[off+2] = (byte)((n >> 16) & 0xff); + buf[off+3] = (byte)((n >> 24) & 0xff); + buf[off+4] = (byte)((n >> 32) & 0xff); + buf[off+5] = (byte)((n >> 40) & 0xff); + buf[off+6] = (byte)((n >> 48) & 0xff); + buf[off+7] = (byte)((n >> 56) & 0xff); + } + + /** + * Writes a byte without any possibility of all that field header nonsense. + * Used internally by other writing methods that know they need to write a byte. + */ + private void writeByteDirect(byte b) throws TException { + temp[0] = b; + trans_.write(temp, 0, 1); + } + + /** + * Writes a byte without any possibility of all that field header nonsense. + */ + private void writeByteDirect(int n) throws TException { + writeByteDirect((byte)n); + } + + + // + // Reading methods. + // + + /** + * Read a message header. + */ + public TMessage readMessageBegin() throws TException { + byte protocolId = readByte(); + if (protocolId != PROTOCOL_ID) { + throw new TProtocolException("Expected protocol id " + Integer.toHexString(PROTOCOL_ID) + " but got " + Integer.toHexString(protocolId)); + } + byte versionAndType = readByte(); + byte version = (byte)(versionAndType & VERSION_MASK); + if (version != VERSION) { + throw new TProtocolException("Expected version " + VERSION + " but got " + version); + } + byte type = (byte)((versionAndType >> TYPE_SHIFT_AMOUNT) & TYPE_BITS); + int seqid = readVarint32(); + String messageName = readString(); + return new TMessage(messageName, type, seqid); + } + + /** + * Read a struct begin. There's nothing on the wire for this, but it is our + * opportunity to push a new struct begin marker onto the field stack. + */ + public TStruct readStructBegin() throws TException { + lastField_.push(lastFieldId_); + lastFieldId_ = 0; + return ANONYMOUS_STRUCT; + } + + /** + * Doesn't actually consume any wire data, just removes the last field for + * this struct from the field stack. + */ + public void readStructEnd() throws TException { + // consume the last field we read off the wire. + lastFieldId_ = lastField_.pop(); + } + + /** + * Read a field header off the wire. + */ + public TField readFieldBegin() throws TException { + byte type = readByte(); + + // if it's a stop, then we can return immediately, as the struct is over. + if (type == TType.STOP) { + return TSTOP; + } + + short fieldId; + + // mask off the 4 MSB of the type header. it could contain a field id delta. + short modifier = (short)((type & 0xf0) >> 4); + if (modifier == 0) { + // not a delta. look ahead for the zigzag varint field id. + fieldId = readI16(); + } else { + // has a delta. add the delta to the last read field id. + fieldId = (short)(lastFieldId_ + modifier); + } + + TField field = new TField("", getTType((byte)(type & 0x0f)), fieldId); + + // if this happens to be a boolean field, the value is encoded in the type + if (isBoolType(type)) { + // save the boolean value in a special instance variable. + boolValue_ = (byte)(type & 0x0f) == Types.BOOLEAN_TRUE ? Boolean.TRUE : Boolean.FALSE; + } + + // push the new field onto the field stack so we can keep the deltas going. + lastFieldId_ = field.id; + return field; + } + + /** + * Read a map header off the wire. If the size is zero, skip reading the key + * and value type. This means that 0-length maps will yield TMaps without the + * "correct" types. + */ + public TMap readMapBegin() throws TException { + int size = readVarint32(); + checkContainerReadLength(size); + byte keyAndValueType = size == 0 ? 0 : readByte(); + return new TMap(getTType((byte)(keyAndValueType >> 4)), getTType((byte)(keyAndValueType & 0xf)), size); + } + + /** + * Read a list header off the wire. If the list size is 0-14, the size will + * be packed into the element type header. If it's a longer list, the 4 MSB + * of the element type header will be 0xF, and a varint will follow with the + * true size. + */ + public TList readListBegin() throws TException { + byte size_and_type = readByte(); + int size = (size_and_type >> 4) & 0x0f; + if (size == 15) { + size = readVarint32(); + } + checkContainerReadLength(size); + byte type = getTType(size_and_type); + return new TList(type, size); + } + + /** + * Read a set header off the wire. If the set size is 0-14, the size will + * be packed into the element type header. If it's a longer set, the 4 MSB + * of the element type header will be 0xF, and a varint will follow with the + * true size. + */ + public TSet readSetBegin() throws TException { + return new TSet(readListBegin()); + } + + /** + * Read a boolean off the wire. If this is a boolean field, the value should + * already have been read during readFieldBegin, so we'll just consume the + * pre-stored value. Otherwise, read a byte. + */ + public boolean readBool() throws TException { + if (boolValue_ != null) { + boolean result = boolValue_.booleanValue(); + boolValue_ = null; + return result; + } + return readByte() == Types.BOOLEAN_TRUE; + } + + /** + * Read a single byte off the wire. Nothing interesting here. + */ + public byte readByte() throws TException { + byte b; + if (trans_.getBytesRemainingInBuffer() > 0) { + b = trans_.getBuffer()[trans_.getBufferPosition()]; + trans_.consumeBuffer(1); + } else { + trans_.readAll(temp, 0, 1); + b = temp[0]; + } + return b; + } + + /** + * Read an i16 from the wire as a zigzag varint. + */ + public short readI16() throws TException { + return (short)zigzagToInt(readVarint32()); + } + + /** + * Read an i32 from the wire as a zigzag varint. + */ + public int readI32() throws TException { + return zigzagToInt(readVarint32()); + } + + /** + * Read an i64 from the wire as a zigzag varint. + */ + public long readI64() throws TException { + return zigzagToLong(readVarint64()); + } + + /** + * No magic here - just read a double off the wire. + */ + public double readDouble() throws TException { + trans_.readAll(temp, 0, 8); + return Double.longBitsToDouble(bytesToLong(temp)); + } + + /** + * Reads a byte[] (via readBinary), and then UTF-8 decodes it. + */ + public String readString() throws TException { + int length = readVarint32(); + checkStringReadLength(length); + + if (length == 0) { + return ""; + } + + final String str; + if (trans_.getBytesRemainingInBuffer() >= length) { + str = new String(trans_.getBuffer(), trans_.getBufferPosition(), + length, StandardCharsets.UTF_8); + trans_.consumeBuffer(length); + } else { + str = new String(readBinary(length), StandardCharsets.UTF_8); + } + return str; + } + + /** + * Read a byte[] from the wire. + */ + public ByteBuffer readBinary() throws TException { + int length = readVarint32(); + checkStringReadLength(length); + if (length == 0) return EMPTY_BUFFER; + + if (trans_.getBytesRemainingInBuffer() >= length) { + ByteBuffer bb = ByteBuffer.wrap(trans_.getBuffer(), trans_.getBufferPosition(), length); + trans_.consumeBuffer(length); + return bb; + } + + byte[] buf = new byte[length]; + trans_.readAll(buf, 0, length); + return ByteBuffer.wrap(buf); + } + + /** + * Read a byte[] of a known length from the wire. + */ + private byte[] readBinary(int length) throws TException { + if (length == 0) return EMPTY_BYTES; + + byte[] buf = new byte[length]; + trans_.readAll(buf, 0, length); + return buf; + } + + private void checkStringReadLength(int length) throws TProtocolException { + if (length < 0) { + throw new TProtocolException(TProtocolException.NEGATIVE_SIZE, + "Negative length: " + length); + } + if (stringLengthLimit_ != NO_LENGTH_LIMIT && length > stringLengthLimit_) { + throw new TProtocolException(TProtocolException.SIZE_LIMIT, + "Length exceeded max allowed: " + length); + } + } + + private void checkContainerReadLength(int length) throws TProtocolException { + if (length < 0) { + throw new TProtocolException(TProtocolException.NEGATIVE_SIZE, + "Negative length: " + length); + } + if (containerLengthLimit_ != NO_LENGTH_LIMIT && length > containerLengthLimit_) { + throw new TProtocolException(TProtocolException.SIZE_LIMIT, + "Length exceeded max allowed: " + length); + } + } + + // + // These methods are here for the struct to call, but don't have any wire + // encoding. + // + public void readMessageEnd() throws TException {} + public void readFieldEnd() throws TException {} + public void readMapEnd() throws TException {} + public void readListEnd() throws TException {} + public void readSetEnd() throws TException {} + + // + // Internal reading methods + // + + /** + * Read an i32 from the wire as a varint. The MSB of each byte is set + * if there is another byte to follow. This can read up to 5 bytes. + */ + private int readVarint32() throws TException { + int result = 0; + int shift = 0; + if (trans_.getBytesRemainingInBuffer() >= 5) { + byte[] buf = trans_.getBuffer(); + int pos = trans_.getBufferPosition(); + int off = 0; + while (true) { + byte b = buf[pos+off]; + result |= (int) (b & 0x7f) << shift; + if ((b & 0x80) != 0x80) break; + shift += 7; + off++; + } + trans_.consumeBuffer(off+1); + } else { + while (true) { + byte b = readByte(); + result |= (int) (b & 0x7f) << shift; + if ((b & 0x80) != 0x80) break; + shift += 7; + } + } + return result; + } + + /** + * Read an i64 from the wire as a proper varint. The MSB of each byte is set + * if there is another byte to follow. This can read up to 10 bytes. + */ + private long readVarint64() throws TException { + int shift = 0; + long result = 0; + if (trans_.getBytesRemainingInBuffer() >= 10) { + byte[] buf = trans_.getBuffer(); + int pos = trans_.getBufferPosition(); + int off = 0; + while (true) { + byte b = buf[pos+off]; + result |= (long) (b & 0x7f) << shift; + if ((b & 0x80) != 0x80) break; + shift += 7; + off++; + } + trans_.consumeBuffer(off+1); + } else { + while (true) { + byte b = readByte(); + result |= (long) (b & 0x7f) << shift; + if ((b & 0x80) != 0x80) break; + shift +=7; + } + } + return result; + } + + // + // encoding helpers + // + + /** + * Convert from zigzag int to int. + */ + private int zigzagToInt(int n) { + return (n >>> 1) ^ -(n & 1); + } + + /** + * Convert from zigzag long to long. + */ + private long zigzagToLong(long n) { + return (n >>> 1) ^ -(n & 1); + } + + /** + * Note that it's important that the mask bytes are long literals, + * otherwise they'll default to ints, and when you shift an int left 56 bits, + * you just get a messed up int. + */ + private long bytesToLong(byte[] bytes) { + return + ((bytes[7] & 0xffL) << 56) | + ((bytes[6] & 0xffL) << 48) | + ((bytes[5] & 0xffL) << 40) | + ((bytes[4] & 0xffL) << 32) | + ((bytes[3] & 0xffL) << 24) | + ((bytes[2] & 0xffL) << 16) | + ((bytes[1] & 0xffL) << 8) | + ((bytes[0] & 0xffL)); + } + + // + // type testing and converting + // + + private boolean isBoolType(byte b) { + int lowerNibble = b & 0x0f; + return lowerNibble == Types.BOOLEAN_TRUE || lowerNibble == Types.BOOLEAN_FALSE; + } + + /** + * Given a TCompactProtocol.Types constant, convert it to its corresponding + * TType value. + */ + private byte getTType(byte type) throws TProtocolException { + switch ((byte)(type & 0x0f)) { + case TType.STOP: + return TType.STOP; + case Types.BOOLEAN_FALSE: + case Types.BOOLEAN_TRUE: + return TType.BOOL; + case Types.BYTE: + return TType.BYTE; + case Types.I16: + return TType.I16; + case Types.I32: + return TType.I32; + case Types.I64: + return TType.I64; + case Types.DOUBLE: + return TType.DOUBLE; + case Types.BINARY: + return TType.STRING; + case Types.LIST: + return TType.LIST; + case Types.SET: + return TType.SET; + case Types.MAP: + return TType.MAP; + case Types.STRUCT: + return TType.STRUCT; + default: + throw new TProtocolException("don't know what type: " + (byte)(type & 0x0f)); + } + } + + /** + * Given a TType value, find the appropriate TCompactProtocol.Types constant. + */ + private byte getCompactType(byte ttype) { + return ttypeToCompactType[ttype]; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TField.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TField.java new file mode 100644 index 000000000..3872b008f --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TField.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +/** + * Helper class that encapsulates field metadata. + * <p>Two fields are considered equal if they have the same type and id.</p> + */ +public class TField { + public TField() { + this("", TType.STOP, (short)0); + } + + public TField(String n, byte t, short i) { + name = n; + type = t; + id = i; + } + + public final String name; + public final byte type; + public final short id; + + public String toString() { + return "<TField name:'" + name + "' type:" + type + " field-id:" + id + ">"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + id; + result = prime * result + type; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TField otherField = (TField) obj; + return type == otherField.type && id == otherField.id; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TJSONProtocol.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TJSONProtocol.java new file mode 100644 index 000000000..d37c4937f --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TJSONProtocol.java @@ -0,0 +1,973 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Stack; + +import org.apache.thrift.TByteArrayOutputStream; +import org.apache.thrift.TException; +import org.apache.thrift.transport.TTransport; + +/** + * JSON protocol implementation for thrift. + * + * This is a full-featured protocol supporting write and read. + * + * Please see the C++ class header for a detailed description of the + * protocol's wire format. + * + */ +public class TJSONProtocol extends TProtocol { + + /** + * Factory for JSON protocol objects + */ + public static class Factory implements TProtocolFactory { + protected boolean fieldNamesAsString_ = false; + + public Factory() {} + + public Factory(boolean fieldNamesAsString) { + fieldNamesAsString_ = fieldNamesAsString; + } + + public TProtocol getProtocol(TTransport trans) { + return new TJSONProtocol(trans, fieldNamesAsString_); + } + + } + + private static final byte[] COMMA = new byte[] {','}; + private static final byte[] COLON = new byte[] {':'}; + private static final byte[] LBRACE = new byte[] {'{'}; + private static final byte[] RBRACE = new byte[] {'}'}; + private static final byte[] LBRACKET = new byte[] {'['}; + private static final byte[] RBRACKET = new byte[] {']'}; + private static final byte[] QUOTE = new byte[] {'"'}; + private static final byte[] BACKSLASH = new byte[] {'\\'}; + private static final byte[] ZERO = new byte[] {'0'}; + + private static final byte[] ESCSEQ = new byte[] {'\\','u','0','0'}; + + private static final long VERSION = 1; + + private static final byte[] JSON_CHAR_TABLE = { + /* 0 1 2 3 4 5 6 7 8 9 A B C D E F */ + 0, 0, 0, 0, 0, 0, 0, 0,'b','t','n', 0,'f','r', 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 + 1, 1,'"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2 + }; + + private static final String ESCAPE_CHARS = "\"\\/bfnrt"; + + private static final byte[] ESCAPE_CHAR_VALS = { + '"', '\\', '/', '\b', '\f', '\n', '\r', '\t', + }; + + private static final int DEF_STRING_SIZE = 16; + + private static final byte[] NAME_BOOL = new byte[] {'t', 'f'}; + private static final byte[] NAME_BYTE = new byte[] {'i','8'}; + private static final byte[] NAME_I16 = new byte[] {'i','1','6'}; + private static final byte[] NAME_I32 = new byte[] {'i','3','2'}; + private static final byte[] NAME_I64 = new byte[] {'i','6','4'}; + private static final byte[] NAME_DOUBLE = new byte[] {'d','b','l'}; + private static final byte[] NAME_STRUCT = new byte[] {'r','e','c'}; + private static final byte[] NAME_STRING = new byte[] {'s','t','r'}; + private static final byte[] NAME_MAP = new byte[] {'m','a','p'}; + private static final byte[] NAME_LIST = new byte[] {'l','s','t'}; + private static final byte[] NAME_SET = new byte[] {'s','e','t'}; + + private static final TStruct ANONYMOUS_STRUCT = new TStruct(); + + private static final byte[] getTypeNameForTypeID(byte typeID) + throws TException { + switch (typeID) { + case TType.BOOL: + return NAME_BOOL; + case TType.BYTE: + return NAME_BYTE; + case TType.I16: + return NAME_I16; + case TType.I32: + return NAME_I32; + case TType.I64: + return NAME_I64; + case TType.DOUBLE: + return NAME_DOUBLE; + case TType.STRING: + return NAME_STRING; + case TType.STRUCT: + return NAME_STRUCT; + case TType.MAP: + return NAME_MAP; + case TType.SET: + return NAME_SET; + case TType.LIST: + return NAME_LIST; + default: + throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, + "Unrecognized type"); + } + } + + private static final byte getTypeIDForTypeName(byte[] name) + throws TException { + byte result = TType.STOP; + if (name.length > 1) { + switch (name[0]) { + case 'd': + result = TType.DOUBLE; + break; + case 'i': + switch (name[1]) { + case '8': + result = TType.BYTE; + break; + case '1': + result = TType.I16; + break; + case '3': + result = TType.I32; + break; + case '6': + result = TType.I64; + break; + } + break; + case 'l': + result = TType.LIST; + break; + case 'm': + result = TType.MAP; + break; + case 'r': + result = TType.STRUCT; + break; + case 's': + if (name[1] == 't') { + result = TType.STRING; + } + else if (name[1] == 'e') { + result = TType.SET; + } + break; + case 't': + result = TType.BOOL; + break; + } + } + if (result == TType.STOP) { + throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, + "Unrecognized type"); + } + return result; + } + + // Base class for tracking JSON contexts that may require inserting/reading + // additional JSON syntax characters + // This base context does nothing. + protected class JSONBaseContext { + protected void write() throws TException {} + + protected void read() throws TException {} + + protected boolean escapeNum() { return false; } + } + + // Context for JSON lists. Will insert/read commas before each item except + // for the first one + protected class JSONListContext extends JSONBaseContext { + private boolean first_ = true; + + @Override + protected void write() throws TException { + if (first_) { + first_ = false; + } else { + trans_.write(COMMA); + } + } + + @Override + protected void read() throws TException { + if (first_) { + first_ = false; + } else { + readJSONSyntaxChar(COMMA); + } + } + } + + // Context for JSON records. Will insert/read colons before the value portion + // of each record pair, and commas before each key except the first. In + // addition, will indicate that numbers in the key position need to be + // escaped in quotes (since JSON keys must be strings). + protected class JSONPairContext extends JSONBaseContext { + private boolean first_ = true; + private boolean colon_ = true; + + @Override + protected void write() throws TException { + if (first_) { + first_ = false; + colon_ = true; + } else { + trans_.write(colon_ ? COLON : COMMA); + colon_ = !colon_; + } + } + + @Override + protected void read() throws TException { + if (first_) { + first_ = false; + colon_ = true; + } else { + readJSONSyntaxChar(colon_ ? COLON : COMMA); + colon_ = !colon_; + } + } + + @Override + protected boolean escapeNum() { + return colon_; + } + } + + // Holds up to one byte from the transport + protected class LookaheadReader { + + private boolean hasData_; + private byte[] data_ = new byte[1]; + + // Return and consume the next byte to be read, either taking it from the + // data buffer if present or getting it from the transport otherwise. + protected byte read() throws TException { + if (hasData_) { + hasData_ = false; + } + else { + trans_.readAll(data_, 0, 1); + } + return data_[0]; + } + + // Return the next byte to be read without consuming, filling the data + // buffer if it has not been filled already. + protected byte peek() throws TException { + if (!hasData_) { + trans_.readAll(data_, 0, 1); + } + hasData_ = true; + return data_[0]; + } + } + + // Stack of nested contexts that we may be in + private Stack<JSONBaseContext> contextStack_ = new Stack<JSONBaseContext>(); + + // Current context that we are in + private JSONBaseContext context_ = new JSONBaseContext(); + + // Reader that manages a 1-byte buffer + private LookaheadReader reader_ = new LookaheadReader(); + + // Write out the TField names as a string instead of the default integer value + private boolean fieldNamesAsString_ = false; + + // Push a new JSON context onto the stack. + private void pushContext(JSONBaseContext c) { + contextStack_.push(context_); + context_ = c; + } + + // Pop the last JSON context off the stack + private void popContext() { + context_ = contextStack_.pop(); + } + + // Reset the context stack to its initial state + private void resetContext() { + while (!contextStack_.isEmpty()) { + popContext(); + } + } + + /** + * Constructor + */ + public TJSONProtocol(TTransport trans) { + super(trans); + } + + public TJSONProtocol(TTransport trans, boolean fieldNamesAsString) { + super(trans); + fieldNamesAsString_ = fieldNamesAsString; + } + + @Override + public void reset() { + contextStack_.clear(); + context_ = new JSONBaseContext(); + reader_ = new LookaheadReader(); + } + + // Temporary buffer used by several methods + private byte[] tmpbuf_ = new byte[4]; + + // Read a byte that must match b[0]; otherwise an exception is thrown. + // Marked protected to avoid synthetic accessor in JSONListContext.read + // and JSONPairContext.read + protected void readJSONSyntaxChar(byte[] b) throws TException { + byte ch = reader_.read(); + if (ch != b[0]) { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Unexpected character:" + (char)ch); + } + } + + // Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its + // corresponding hex value + private static final byte hexVal(byte ch) throws TException { + if ((ch >= '0') && (ch <= '9')) { + return (byte)((char)ch - '0'); + } + else if ((ch >= 'a') && (ch <= 'f')) { + return (byte)((char)ch - 'a' + 10); + } + else { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Expected hex character"); + } + } + + // Convert a byte containing a hex value to its corresponding hex character + private static final byte hexChar(byte val) { + val &= 0x0F; + if (val < 10) { + return (byte)((char)val + '0'); + } + else { + return (byte)((char)(val - 10) + 'a'); + } + } + + // Write the bytes in array buf as a JSON characters, escaping as needed + private void writeJSONString(byte[] b) throws TException { + context_.write(); + trans_.write(QUOTE); + int len = b.length; + for (int i = 0; i < len; i++) { + if ((b[i] & 0x00FF) >= 0x30) { + if (b[i] == BACKSLASH[0]) { + trans_.write(BACKSLASH); + trans_.write(BACKSLASH); + } + else { + trans_.write(b, i, 1); + } + } + else { + tmpbuf_[0] = JSON_CHAR_TABLE[b[i]]; + if (tmpbuf_[0] == 1) { + trans_.write(b, i, 1); + } + else if (tmpbuf_[0] > 1) { + trans_.write(BACKSLASH); + trans_.write(tmpbuf_, 0, 1); + } + else { + trans_.write(ESCSEQ); + tmpbuf_[0] = hexChar((byte)(b[i] >> 4)); + tmpbuf_[1] = hexChar(b[i]); + trans_.write(tmpbuf_, 0, 2); + } + } + } + trans_.write(QUOTE); + } + + // Write out number as a JSON value. If the context dictates so, it will be + // wrapped in quotes to output as a JSON string. + private void writeJSONInteger(long num) throws TException { + context_.write(); + String str = Long.toString(num); + boolean escapeNum = context_.escapeNum(); + if (escapeNum) { + trans_.write(QUOTE); + } + byte[] buf = str.getBytes(StandardCharsets.UTF_8); + trans_.write(buf); + if (escapeNum) { + trans_.write(QUOTE); + } + } + + // Write out a double as a JSON value. If it is NaN or infinity or if the + // context dictates escaping, write out as JSON string. + private void writeJSONDouble(double num) throws TException { + context_.write(); + String str = Double.toString(num); + boolean special = false; + switch (str.charAt(0)) { + case 'N': // NaN + case 'I': // Infinity + special = true; + break; + case '-': + if (str.charAt(1) == 'I') { // -Infinity + special = true; + } + break; + default: + break; + } + + boolean escapeNum = special || context_.escapeNum(); + if (escapeNum) { + trans_.write(QUOTE); + } + byte[] b = str.getBytes(StandardCharsets.UTF_8); + trans_.write(b, 0, b.length); + if (escapeNum) { + trans_.write(QUOTE); + } + } + + // Write out contents of byte array b as a JSON string with base-64 encoded + // data + private void writeJSONBase64(byte[] b, int offset, int length) throws TException { + context_.write(); + trans_.write(QUOTE); + int len = length; + int off = offset; + while (len >= 3) { + // Encode 3 bytes at a time + TBase64Utils.encode(b, off, 3, tmpbuf_, 0); + trans_.write(tmpbuf_, 0, 4); + off += 3; + len -= 3; + } + if (len > 0) { + // Encode remainder + TBase64Utils.encode(b, off, len, tmpbuf_, 0); + trans_.write(tmpbuf_, 0, len + 1); + } + trans_.write(QUOTE); + } + + private void writeJSONObjectStart() throws TException { + context_.write(); + trans_.write(LBRACE); + pushContext(new JSONPairContext()); + } + + private void writeJSONObjectEnd() throws TException { + popContext(); + trans_.write(RBRACE); + } + + private void writeJSONArrayStart() throws TException { + context_.write(); + trans_.write(LBRACKET); + pushContext(new JSONListContext()); + } + + private void writeJSONArrayEnd() throws TException { + popContext(); + trans_.write(RBRACKET); + } + + @Override + public void writeMessageBegin(TMessage message) throws TException { + resetContext(); // THRIFT-3743 + writeJSONArrayStart(); + writeJSONInteger(VERSION); + byte[] b = message.name.getBytes(StandardCharsets.UTF_8); + writeJSONString(b); + writeJSONInteger(message.type); + writeJSONInteger(message.seqid); + } + + @Override + public void writeMessageEnd() throws TException { + writeJSONArrayEnd(); + } + + @Override + public void writeStructBegin(TStruct struct) throws TException { + writeJSONObjectStart(); + } + + @Override + public void writeStructEnd() throws TException { + writeJSONObjectEnd(); + } + + @Override + public void writeFieldBegin(TField field) throws TException { + if (fieldNamesAsString_) { + writeString(field.name); + } else { + writeJSONInteger(field.id); + } + writeJSONObjectStart(); + writeJSONString(getTypeNameForTypeID(field.type)); + } + + @Override + public void writeFieldEnd() throws TException { + writeJSONObjectEnd(); + } + + @Override + public void writeFieldStop() {} + + @Override + public void writeMapBegin(TMap map) throws TException { + writeJSONArrayStart(); + writeJSONString(getTypeNameForTypeID(map.keyType)); + writeJSONString(getTypeNameForTypeID(map.valueType)); + writeJSONInteger(map.size); + writeJSONObjectStart(); + } + + @Override + public void writeMapEnd() throws TException { + writeJSONObjectEnd(); + writeJSONArrayEnd(); + } + + @Override + public void writeListBegin(TList list) throws TException { + writeJSONArrayStart(); + writeJSONString(getTypeNameForTypeID(list.elemType)); + writeJSONInteger(list.size); + } + + @Override + public void writeListEnd() throws TException { + writeJSONArrayEnd(); + } + + @Override + public void writeSetBegin(TSet set) throws TException { + writeJSONArrayStart(); + writeJSONString(getTypeNameForTypeID(set.elemType)); + writeJSONInteger(set.size); + } + + @Override + public void writeSetEnd() throws TException { + writeJSONArrayEnd(); + } + + @Override + public void writeBool(boolean b) throws TException { + writeJSONInteger(b ? (long)1 : (long)0); + } + + @Override + public void writeByte(byte b) throws TException { + writeJSONInteger((long)b); + } + + @Override + public void writeI16(short i16) throws TException { + writeJSONInteger((long)i16); + } + + @Override + public void writeI32(int i32) throws TException { + writeJSONInteger((long)i32); + } + + @Override + public void writeI64(long i64) throws TException { + writeJSONInteger(i64); + } + + @Override + public void writeDouble(double dub) throws TException { + writeJSONDouble(dub); + } + + @Override + public void writeString(String str) throws TException { + byte[] b = str.getBytes(StandardCharsets.UTF_8); + writeJSONString(b); + } + + @Override + public void writeBinary(ByteBuffer bin) throws TException { + writeJSONBase64(bin.array(), bin.position() + bin.arrayOffset(), bin.limit() - bin.position() - bin.arrayOffset()); + } + + /** + * Reading methods. + */ + + // Read in a JSON string, unescaping as appropriate.. Skip reading from the + // context if skipContext is true. + private TByteArrayOutputStream readJSONString(boolean skipContext) + throws TException { + TByteArrayOutputStream arr = new TByteArrayOutputStream(DEF_STRING_SIZE); + ArrayList<Character> codeunits = new ArrayList<Character>(); + if (!skipContext) { + context_.read(); + } + readJSONSyntaxChar(QUOTE); + while (true) { + byte ch = reader_.read(); + if (ch == QUOTE[0]) { + break; + } + if (ch == ESCSEQ[0]) { + ch = reader_.read(); + if (ch == ESCSEQ[1]) { + trans_.readAll(tmpbuf_, 0, 4); + short cu = (short)( + ((short)hexVal(tmpbuf_[0]) << 12) + + ((short)hexVal(tmpbuf_[1]) << 8) + + ((short)hexVal(tmpbuf_[2]) << 4) + + (short)hexVal(tmpbuf_[3])); + try { + if (Character.isHighSurrogate((char)cu)) { + if (codeunits.size() > 0) { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Expected low surrogate char"); + } + codeunits.add((char)cu); + } + else if (Character.isLowSurrogate((char)cu)) { + if (codeunits.size() == 0) { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Expected high surrogate char"); + } + + codeunits.add((char)cu); + arr.write( + (new String(new int[] { codeunits.get(0), codeunits.get(1) }, + 0, 2)).getBytes(StandardCharsets.UTF_8)); + codeunits.clear(); + } + else { + arr.write((new String(new int[] { cu }, 0, 1)) + .getBytes(StandardCharsets.UTF_8)); + } + continue; + } catch (IOException ex) { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Invalid unicode sequence"); + } + } + else { + int off = ESCAPE_CHARS.indexOf(ch); + if (off == -1) { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Expected control char"); + } + ch = ESCAPE_CHAR_VALS[off]; + } + } + arr.write(ch); + } + return arr; + } + + // Return true if the given byte could be a valid part of a JSON number. + private boolean isJSONNumeric(byte b) { + switch (b) { + case '+': + case '-': + case '.': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'E': + case 'e': + return true; + } + return false; + } + + // Read in a sequence of characters that are all valid in JSON numbers. Does + // not do a complete regex check to validate that this is actually a number. + private String readJSONNumericChars() throws TException { + StringBuilder strbld = new StringBuilder(); + while (true) { + byte ch = reader_.peek(); + if (!isJSONNumeric(ch)) { + break; + } + strbld.append((char)reader_.read()); + } + return strbld.toString(); + } + + // Read in a JSON number. If the context dictates, read in enclosing quotes. + private long readJSONInteger() throws TException { + context_.read(); + if (context_.escapeNum()) { + readJSONSyntaxChar(QUOTE); + } + String str = readJSONNumericChars(); + if (context_.escapeNum()) { + readJSONSyntaxChar(QUOTE); + } + try { + return Long.valueOf(str); + } + catch (NumberFormatException ex) { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Bad data encounted in numeric data"); + } + } + + // Read in a JSON double value. Throw if the value is not wrapped in quotes + // when expected or if wrapped in quotes when not expected. + private double readJSONDouble() throws TException { + context_.read(); + if (reader_.peek() == QUOTE[0]) { + TByteArrayOutputStream arr = readJSONString(true); + double dub = Double.valueOf(arr.toString(StandardCharsets.UTF_8)); + if (!context_.escapeNum() && !Double.isNaN(dub) + && !Double.isInfinite(dub)) { + // Throw exception -- we should not be in a string in this case + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Numeric data unexpectedly quoted"); + } + return dub; + } + else { + if (context_.escapeNum()) { + // This will throw - we should have had a quote if escapeNum == true + readJSONSyntaxChar(QUOTE); + } + try { + return Double.valueOf(readJSONNumericChars()); + } + catch (NumberFormatException ex) { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Bad data encounted in numeric data"); + } + } + } + + // Read in a JSON string containing base-64 encoded data and decode it. + private byte[] readJSONBase64() throws TException { + TByteArrayOutputStream arr = readJSONString(false); + byte[] b = arr.get(); + int len = arr.len(); + int off = 0; + int size = 0; + // Ignore padding + int bound = len >= 2 ? len - 2 : 0; + for (int i = len - 1; i >= bound && b[i] == '='; --i) { + --len; + } + while (len >= 4) { + // Decode 4 bytes at a time + TBase64Utils.decode(b, off, 4, b, size); // NB: decoded in place + off += 4; + len -= 4; + size += 3; + } + // Don't decode if we hit the end or got a single leftover byte (invalid + // base64 but legal for skip of regular string type) + if (len > 1) { + // Decode remainder + TBase64Utils.decode(b, off, len, b, size); // NB: decoded in place + size += len - 1; + } + // Sadly we must copy the byte[] (any way around this?) + byte [] result = new byte[size]; + System.arraycopy(b, 0, result, 0, size); + return result; + } + + private void readJSONObjectStart() throws TException { + context_.read(); + readJSONSyntaxChar(LBRACE); + pushContext(new JSONPairContext()); + } + + private void readJSONObjectEnd() throws TException { + readJSONSyntaxChar(RBRACE); + popContext(); + } + + private void readJSONArrayStart() throws TException { + context_.read(); + readJSONSyntaxChar(LBRACKET); + pushContext(new JSONListContext()); + } + + private void readJSONArrayEnd() throws TException { + readJSONSyntaxChar(RBRACKET); + popContext(); + } + + @Override + public TMessage readMessageBegin() throws TException { + resetContext(); // THRIFT-3743 + readJSONArrayStart(); + if (readJSONInteger() != VERSION) { + throw new TProtocolException(TProtocolException.BAD_VERSION, + "Message contained bad version."); + } + String name = readJSONString(false).toString(StandardCharsets.UTF_8); + byte type = (byte) readJSONInteger(); + int seqid = (int) readJSONInteger(); + return new TMessage(name, type, seqid); + } + + @Override + public void readMessageEnd() throws TException { + readJSONArrayEnd(); + } + + @Override + public TStruct readStructBegin() throws TException { + readJSONObjectStart(); + return ANONYMOUS_STRUCT; + } + + @Override + public void readStructEnd() throws TException { + readJSONObjectEnd(); + } + + @Override + public TField readFieldBegin() throws TException { + byte ch = reader_.peek(); + byte type; + short id = 0; + if (ch == RBRACE[0]) { + type = TType.STOP; + } + else { + id = (short) readJSONInteger(); + readJSONObjectStart(); + type = getTypeIDForTypeName(readJSONString(false).get()); + } + return new TField("", type, id); + } + + @Override + public void readFieldEnd() throws TException { + readJSONObjectEnd(); + } + + @Override + public TMap readMapBegin() throws TException { + readJSONArrayStart(); + byte keyType = getTypeIDForTypeName(readJSONString(false).get()); + byte valueType = getTypeIDForTypeName(readJSONString(false).get()); + int size = (int)readJSONInteger(); + readJSONObjectStart(); + return new TMap(keyType, valueType, size); + } + + @Override + public void readMapEnd() throws TException { + readJSONObjectEnd(); + readJSONArrayEnd(); + } + + @Override + public TList readListBegin() throws TException { + readJSONArrayStart(); + byte elemType = getTypeIDForTypeName(readJSONString(false).get()); + int size = (int)readJSONInteger(); + return new TList(elemType, size); + } + + @Override + public void readListEnd() throws TException { + readJSONArrayEnd(); + } + + @Override + public TSet readSetBegin() throws TException { + readJSONArrayStart(); + byte elemType = getTypeIDForTypeName(readJSONString(false).get()); + int size = (int)readJSONInteger(); + return new TSet(elemType, size); + } + + @Override + public void readSetEnd() throws TException { + readJSONArrayEnd(); + } + + @Override + public boolean readBool() throws TException { + return (readJSONInteger() == 0 ? false : true); + } + + @Override + public byte readByte() throws TException { + return (byte) readJSONInteger(); + } + + @Override + public short readI16() throws TException { + return (short) readJSONInteger(); + } + + @Override + public int readI32() throws TException { + return (int) readJSONInteger(); + } + + @Override + public long readI64() throws TException { + return (long) readJSONInteger(); + } + + @Override + public double readDouble() throws TException { + return readJSONDouble(); + } + + @Override + public String readString() throws TException { + return readJSONString(false).toString(StandardCharsets.UTF_8); + } + + @Override + public ByteBuffer readBinary() throws TException { + return ByteBuffer.wrap(readJSONBase64()); + } + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TList.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TList.java new file mode 100644 index 000000000..0d36e83d9 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TList.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +/** + * Helper class that encapsulates list metadata. + * + */ +public final class TList { + public TList() { + this(TType.STOP, 0); + } + + public TList(byte t, int s) { + elemType = t; + size = s; + } + + public final byte elemType; + public final int size; +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TMap.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TMap.java new file mode 100644 index 000000000..20881f7ac --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TMap.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +/** + * Helper class that encapsulates map metadata. + * + */ +public final class TMap { + public TMap() { + this(TType.STOP, TType.STOP, 0); + } + + public TMap(byte k, byte v, int s) { + keyType = k; + valueType = v; + size = s; + } + + public final byte keyType; + public final byte valueType; + public final int size; +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TMessage.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TMessage.java new file mode 100644 index 000000000..f13b8ca50 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TMessage.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +/** + * Helper class that encapsulates struct metadata. + * + */ +public final class TMessage { + public TMessage() { + this("", TType.STOP, 0); + } + + public TMessage(String n, byte t, int s) { + name = n; + type = t; + seqid = s; + } + + public final String name; + public final byte type; + public final int seqid; + + @Override + public String toString() { + return "<TMessage name:'" + name + "' type: " + type + " seqid:" + seqid + ">"; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + seqid; + result = prime * result + type; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TMessage other = (TMessage) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (seqid != other.seqid) + return false; + if (type != other.type) + return false; + return true; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TMessageType.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TMessageType.java new file mode 100644 index 000000000..aa3f93177 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TMessageType.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +/** + * Message type constants in the Thrift protocol. + * + */ +public final class TMessageType { + public static final byte CALL = 1; + public static final byte REPLY = 2; + public static final byte EXCEPTION = 3; + public static final byte ONEWAY = 4; +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TMultiplexedProtocol.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TMultiplexedProtocol.java new file mode 100644 index 000000000..0ea566ba6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TMultiplexedProtocol.java @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +import org.apache.thrift.TException; + +/** + * <code>TMultiplexedProtocol</code> is a protocol-independent concrete decorator + * that allows a Thrift client to communicate with a multiplexing Thrift server, + * by prepending the service name to the function name during function calls. + * + * <p>NOTE: THIS IS NOT USED BY SERVERS. On the server, use {@link org.apache.thrift.TMultiplexedProcessor TMultiplexedProcessor} to handle requests + * from a multiplexing client. + * + * <p>This example uses a single socket transport to invoke two services: + * + * <pre> + * {@code + * TSocket transport = new TSocket("localhost", 9090); + * transport.open(); + * + * TBinaryProtocol protocol = new TBinaryProtocol(transport); + * + * TMultiplexedProtocol mp = new TMultiplexedProtocol(protocol, "Calculator"); + * Calculator.Client service = new Calculator.Client(mp); + * + * TMultiplexedProtocol mp2 = new TMultiplexedProtocol(protocol, "WeatherReport"); + * WeatherReport.Client service2 = new WeatherReport.Client(mp2); + * + * System.out.println(service.add(2,2)); + * System.out.println(service2.getTemperature()); + * } + * </pre> + * + * @see org.apache.thrift.protocol.TProtocolDecorator + */ +public class TMultiplexedProtocol extends TProtocolDecorator { + + /** Used to delimit the service name from the function name */ + public static final String SEPARATOR = ":"; + + private final String SERVICE_NAME; + + /** + * Wrap the specified protocol, allowing it to be used to communicate with a + * multiplexing server. The <code>serviceName</code> is required as it is + * prepended to the message header so that the multiplexing server can broker + * the function call to the proper service. + * + * @param protocol Your communication protocol of choice, e.g. <code>TBinaryProtocol</code>. + * @param serviceName The service name of the service communicating via this protocol. + */ + public TMultiplexedProtocol(TProtocol protocol, String serviceName) { + super(protocol); + SERVICE_NAME = serviceName; + } + + /** + * Prepends the service name to the function name, separated by TMultiplexedProtocol.SEPARATOR. + * + * @param tMessage The original message. + * @throws TException Passed through from wrapped <code>TProtocol</code> instance. + */ + @Override + public void writeMessageBegin(TMessage tMessage) throws TException { + if (tMessage.type == TMessageType.CALL || tMessage.type == TMessageType.ONEWAY) { + super.writeMessageBegin(new TMessage( + SERVICE_NAME + SEPARATOR + tMessage.name, + tMessage.type, + tMessage.seqid + )); + } else { + super.writeMessageBegin(tMessage); + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocol.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocol.java new file mode 100644 index 000000000..0e96368d4 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocol.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +import java.nio.ByteBuffer; + +import org.apache.thrift.TException; +import org.apache.thrift.scheme.IScheme; +import org.apache.thrift.scheme.StandardScheme; +import org.apache.thrift.transport.TTransport; + +/** + * Protocol interface definition. + * + */ +public abstract class TProtocol { + + /** + * Prevent direct instantiation + */ + @SuppressWarnings("unused") + private TProtocol() {} + + /** + * Transport + */ + protected TTransport trans_; + + /** + * Constructor + */ + protected TProtocol(TTransport trans) { + trans_ = trans; + } + + /** + * Transport accessor + */ + public TTransport getTransport() { + return trans_; + } + + /** + * Writing methods. + */ + + public abstract void writeMessageBegin(TMessage message) throws TException; + + public abstract void writeMessageEnd() throws TException; + + public abstract void writeStructBegin(TStruct struct) throws TException; + + public abstract void writeStructEnd() throws TException; + + public abstract void writeFieldBegin(TField field) throws TException; + + public abstract void writeFieldEnd() throws TException; + + public abstract void writeFieldStop() throws TException; + + public abstract void writeMapBegin(TMap map) throws TException; + + public abstract void writeMapEnd() throws TException; + + public abstract void writeListBegin(TList list) throws TException; + + public abstract void writeListEnd() throws TException; + + public abstract void writeSetBegin(TSet set) throws TException; + + public abstract void writeSetEnd() throws TException; + + public abstract void writeBool(boolean b) throws TException; + + public abstract void writeByte(byte b) throws TException; + + public abstract void writeI16(short i16) throws TException; + + public abstract void writeI32(int i32) throws TException; + + public abstract void writeI64(long i64) throws TException; + + public abstract void writeDouble(double dub) throws TException; + + public abstract void writeString(String str) throws TException; + + public abstract void writeBinary(ByteBuffer buf) throws TException; + + /** + * Reading methods. + */ + + public abstract TMessage readMessageBegin() throws TException; + + public abstract void readMessageEnd() throws TException; + + public abstract TStruct readStructBegin() throws TException; + + public abstract void readStructEnd() throws TException; + + public abstract TField readFieldBegin() throws TException; + + public abstract void readFieldEnd() throws TException; + + public abstract TMap readMapBegin() throws TException; + + public abstract void readMapEnd() throws TException; + + public abstract TList readListBegin() throws TException; + + public abstract void readListEnd() throws TException; + + public abstract TSet readSetBegin() throws TException; + + public abstract void readSetEnd() throws TException; + + public abstract boolean readBool() throws TException; + + public abstract byte readByte() throws TException; + + public abstract short readI16() throws TException; + + public abstract int readI32() throws TException; + + public abstract long readI64() throws TException; + + public abstract double readDouble() throws TException; + + public abstract String readString() throws TException; + + public abstract ByteBuffer readBinary() throws TException; + + /** + * Reset any internal state back to a blank slate. This method only needs to + * be implemented for stateful protocols. + */ + public void reset() {} + + /** + * Scheme accessor + */ + public Class<? extends IScheme> getScheme() { + return StandardScheme.class; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocolDecorator.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocolDecorator.java new file mode 100644 index 000000000..2d29cd231 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocolDecorator.java @@ -0,0 +1,213 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +import org.apache.thrift.TException; + +import java.nio.ByteBuffer; + +/** + * <code>TProtocolDecorator</code> forwards all requests to an enclosed + * <code>TProtocol</code> instance, providing a way to author concise + * concrete decorator subclasses. While it has no abstract methods, it + * is marked abstract as a reminder that by itself, it does not modify + * the behaviour of the enclosed <code>TProtocol</code>. + * + * <p>See p.175 of Design Patterns (by Gamma et al.)</p> + * + * @see org.apache.thrift.protocol.TMultiplexedProtocol + */ +public abstract class TProtocolDecorator extends TProtocol { + + private final TProtocol concreteProtocol; + + /** + * Encloses the specified protocol. + * @param protocol All operations will be forward to this protocol. Must be non-null. + */ + public TProtocolDecorator(TProtocol protocol) { + super(protocol.getTransport()); + concreteProtocol = protocol; + } + + public void writeMessageBegin(TMessage tMessage) throws TException { + concreteProtocol.writeMessageBegin(tMessage); + } + + public void writeMessageEnd() throws TException { + concreteProtocol.writeMessageEnd(); + } + + public void writeStructBegin(TStruct tStruct) throws TException { + concreteProtocol.writeStructBegin(tStruct); + } + + public void writeStructEnd() throws TException { + concreteProtocol.writeStructEnd(); + } + + public void writeFieldBegin(TField tField) throws TException { + concreteProtocol.writeFieldBegin(tField); + } + + public void writeFieldEnd() throws TException { + concreteProtocol.writeFieldEnd(); + } + + public void writeFieldStop() throws TException { + concreteProtocol.writeFieldStop(); + } + + public void writeMapBegin(TMap tMap) throws TException { + concreteProtocol.writeMapBegin(tMap); + } + + public void writeMapEnd() throws TException { + concreteProtocol.writeMapEnd(); + } + + public void writeListBegin(TList tList) throws TException { + concreteProtocol.writeListBegin(tList); + } + + public void writeListEnd() throws TException { + concreteProtocol.writeListEnd(); + } + + public void writeSetBegin(TSet tSet) throws TException { + concreteProtocol.writeSetBegin(tSet); + } + + public void writeSetEnd() throws TException { + concreteProtocol.writeSetEnd(); + } + + public void writeBool(boolean b) throws TException { + concreteProtocol.writeBool(b); + } + + public void writeByte(byte b) throws TException { + concreteProtocol.writeByte(b); + } + + public void writeI16(short i) throws TException { + concreteProtocol.writeI16(i); + } + + public void writeI32(int i) throws TException { + concreteProtocol.writeI32(i); + } + + public void writeI64(long l) throws TException { + concreteProtocol.writeI64(l); + } + + public void writeDouble(double v) throws TException { + concreteProtocol.writeDouble(v); + } + + public void writeString(String s) throws TException { + concreteProtocol.writeString(s); + } + + public void writeBinary(ByteBuffer buf) throws TException { + concreteProtocol.writeBinary(buf); + } + + public TMessage readMessageBegin() throws TException { + return concreteProtocol.readMessageBegin(); + } + + public void readMessageEnd() throws TException { + concreteProtocol.readMessageEnd(); + } + + public TStruct readStructBegin() throws TException { + return concreteProtocol.readStructBegin(); + } + + public void readStructEnd() throws TException { + concreteProtocol.readStructEnd(); + } + + public TField readFieldBegin() throws TException { + return concreteProtocol.readFieldBegin(); + } + + public void readFieldEnd() throws TException { + concreteProtocol.readFieldEnd(); + } + + public TMap readMapBegin() throws TException { + return concreteProtocol.readMapBegin(); + } + + public void readMapEnd() throws TException { + concreteProtocol.readMapEnd(); + } + + public TList readListBegin() throws TException { + return concreteProtocol.readListBegin(); + } + + public void readListEnd() throws TException { + concreteProtocol.readListEnd(); + } + + public TSet readSetBegin() throws TException { + return concreteProtocol.readSetBegin(); + } + + public void readSetEnd() throws TException { + concreteProtocol.readSetEnd(); + } + + public boolean readBool() throws TException { + return concreteProtocol.readBool(); + } + + public byte readByte() throws TException { + return concreteProtocol.readByte(); + } + + public short readI16() throws TException { + return concreteProtocol.readI16(); + } + + public int readI32() throws TException { + return concreteProtocol.readI32(); + } + + public long readI64() throws TException { + return concreteProtocol.readI64(); + } + + public double readDouble() throws TException { + return concreteProtocol.readDouble(); + } + + public String readString() throws TException { + return concreteProtocol.readString(); + } + + public ByteBuffer readBinary() throws TException { + return concreteProtocol.readBinary(); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocolException.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocolException.java new file mode 100644 index 000000000..870f1b939 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocolException.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +import org.apache.thrift.TException; + +/** + * Protocol exceptions. + * + */ +public class TProtocolException extends TException { + + + private static final long serialVersionUID = 1L; + public static final int UNKNOWN = 0; + public static final int INVALID_DATA = 1; + public static final int NEGATIVE_SIZE = 2; + public static final int SIZE_LIMIT = 3; + public static final int BAD_VERSION = 4; + public static final int NOT_IMPLEMENTED = 5; + public static final int DEPTH_LIMIT = 6; + + protected int type_ = UNKNOWN; + + public TProtocolException() { + super(); + } + + public TProtocolException(int type) { + super(); + type_ = type; + } + + public TProtocolException(int type, String message) { + super(message); + type_ = type; + } + + public TProtocolException(String message) { + super(message); + } + + public TProtocolException(int type, Throwable cause) { + super(cause); + type_ = type; + } + + public TProtocolException(Throwable cause) { + super(cause); + } + + public TProtocolException(String message, Throwable cause) { + super(message, cause); + } + + public TProtocolException(int type, String message, Throwable cause) { + super(message, cause); + type_ = type; + } + + public int getType() { + return type_; + } + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocolFactory.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocolFactory.java new file mode 100644 index 000000000..b72e87b38 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocolFactory.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +import java.io.Serializable; + +import org.apache.thrift.transport.TTransport; + +/** + * Factory interface for constructing protocol instances. + */ +public interface TProtocolFactory extends Serializable { + public TProtocol getProtocol(TTransport trans); +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocolUtil.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocolUtil.java new file mode 100644 index 000000000..cdaa30b87 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TProtocolUtil.java @@ -0,0 +1,221 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +import org.apache.thrift.TException; + +/** + * Utility class with static methods for interacting with protocol data + * streams. + * + */ +public class TProtocolUtil { + + /** + * The maximum recursive depth the skip() function will traverse before + * throwing a TException. + */ + private static int maxSkipDepth = Integer.MAX_VALUE; + + /** + * Specifies the maximum recursive depth that the skip function will + * traverse before throwing a TException. This is a global setting, so + * any call to skip in this JVM will enforce this value. + * + * @param depth the maximum recursive depth. A value of 2 would allow + * the skip function to skip a structure or collection with basic children, + * but it would not permit skipping a struct that had a field containing + * a child struct. A value of 1 would only allow skipping of simple + * types and empty structs/collections. + */ + public static void setMaxSkipDepth(int depth) { + maxSkipDepth = depth; + } + + /** + * Skips over the next data element from the provided input TProtocol object. + * + * @param prot the protocol object to read from + * @param type the next value will be interpreted as this TType value. + */ + public static void skip(TProtocol prot, byte type) + throws TException { + skip(prot, type, maxSkipDepth); + } + + /** + * Skips over the next data element from the provided input TProtocol object. + * + * @param prot the protocol object to read from + * @param type the next value will be interpreted as this TType value. + * @param maxDepth this function will only skip complex objects to this + * recursive depth, to prevent Java stack overflow. + */ + public static void skip(TProtocol prot, byte type, int maxDepth) + throws TException { + if (maxDepth <= 0) { + throw new TException("Maximum skip depth exceeded"); + } + switch (type) { + case TType.BOOL: + prot.readBool(); + break; + + case TType.BYTE: + prot.readByte(); + break; + + case TType.I16: + prot.readI16(); + break; + + case TType.I32: + prot.readI32(); + break; + + case TType.I64: + prot.readI64(); + break; + + case TType.DOUBLE: + prot.readDouble(); + break; + + case TType.STRING: + prot.readBinary(); + break; + + case TType.STRUCT: + prot.readStructBegin(); + while (true) { + TField field = prot.readFieldBegin(); + if (field.type == TType.STOP) { + break; + } + skip(prot, field.type, maxDepth - 1); + prot.readFieldEnd(); + } + prot.readStructEnd(); + break; + + case TType.MAP: + TMap map = prot.readMapBegin(); + for (int i = 0; i < map.size; i++) { + skip(prot, map.keyType, maxDepth - 1); + skip(prot, map.valueType, maxDepth - 1); + } + prot.readMapEnd(); + break; + + case TType.SET: + TSet set = prot.readSetBegin(); + for (int i = 0; i < set.size; i++) { + skip(prot, set.elemType, maxDepth - 1); + } + prot.readSetEnd(); + break; + + case TType.LIST: + TList list = prot.readListBegin(); + for (int i = 0; i < list.size; i++) { + skip(prot, list.elemType, maxDepth - 1); + } + prot.readListEnd(); + break; + + default: + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Unrecognized type " + type); + } + } + + /** + * Attempt to determine the protocol used to serialize some data. + * + * The guess is based on known specificities of supported protocols. + * In some cases, no guess can be done, in that case we return the + * fallback TProtocolFactory. + * To be certain to correctly detect the protocol, the first encoded + * field should have a field id < 256 + * + * @param data The serialized data to guess the protocol for. + * @param fallback The TProtocol to return if no guess can be made. + * @return a Class implementing TProtocolFactory which can be used to create a deserializer. + */ + public static TProtocolFactory guessProtocolFactory(byte[] data, TProtocolFactory fallback) { + // + // If the first and last bytes are opening/closing curly braces we guess the protocol as + // being TJSONProtocol. + // It could not be a TCompactBinary encoding for a field of type 0xb (Map) + // with delta id 7 as the last byte for TCompactBinary is always 0. + // + + if ('{' == data[0] && '}' == data[data.length - 1]) { + return new TJSONProtocol.Factory(); + } + + // + // If the last byte is not 0, then it cannot be TCompactProtocol, it must be + // TBinaryProtocol. + // + + if (data[data.length - 1] != 0) { + return new TBinaryProtocol.Factory(); + } + + // + // A first byte of value > 16 indicates TCompactProtocol was used, and the first byte + // encodes a delta field id (id <= 15) and a field type. + // + + if (data[0] > 0x10) { + return new TCompactProtocol.Factory(); + } + + // + // If the second byte is 0 then it is a field id < 256 encoded by TBinaryProtocol. + // It cannot possibly be TCompactProtocol since a value of 0 would imply a field id + // of 0 as the zig zag varint encoding would end. + // + + if (data.length > 1 && 0 == data[1]) { + return new TBinaryProtocol.Factory(); + } + + // + // If bit 7 of the first byte of the field id is set then we have two choices: + // 1. A field id > 63 was encoded with TCompactProtocol. + // 2. A field id > 0x7fff (32767) was encoded with TBinaryProtocol and the last byte of the + // serialized data is 0. + // Option 2 is impossible since field ids are short and thus limited to 32767. + // + + if (data.length > 1 && (data[1] & 0x80) != 0) { + return new TCompactProtocol.Factory(); + } + + // + // The remaining case is either a field id <= 63 encoded as TCompactProtocol, + // one >= 256 encoded with TBinaryProtocol with a last byte at 0, or an empty structure. + // As we cannot really decide, we return the fallback protocol. + // + return fallback; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TSet.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TSet.java new file mode 100644 index 000000000..38be9a991 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TSet.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +/** + * Helper class that encapsulates set metadata. + * + */ +public final class TSet { + public TSet() { + this(TType.STOP, 0); + } + + public TSet(byte t, int s) { + elemType = t; + size = s; + } + + public TSet(TList list) { + this(list.elemType, list.size); + } + + public final byte elemType; + public final int size; +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TSimpleJSONProtocol.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TSimpleJSONProtocol.java new file mode 100644 index 000000000..eb7e23bf9 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TSimpleJSONProtocol.java @@ -0,0 +1,483 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Stack; + +import org.apache.thrift.TException; +import org.apache.thrift.transport.TTransport; + +/** + * JSON protocol implementation for thrift. + * + * This protocol is write-only and produces a simple output format + * suitable for parsing by scripting languages. It should not be + * confused with the full-featured TJSONProtocol. + * + */ +public class TSimpleJSONProtocol extends TProtocol { + + /** + * Factory + */ + public static class Factory implements TProtocolFactory { + public TProtocol getProtocol(TTransport trans) { + return new TSimpleJSONProtocol(trans); + } + } + + private static final byte[] COMMA = new byte[] {','}; + private static final byte[] COLON = new byte[] {':'}; + private static final byte[] LBRACE = new byte[] {'{'}; + private static final byte[] RBRACE = new byte[] {'}'}; + private static final byte[] LBRACKET = new byte[] {'['}; + private static final byte[] RBRACKET = new byte[] {']'}; + private static final char QUOTE = '"'; + + private static final TStruct ANONYMOUS_STRUCT = new TStruct(); + private static final TField ANONYMOUS_FIELD = new TField(); + private static final TMessage EMPTY_MESSAGE = new TMessage(); + private static final TSet EMPTY_SET = new TSet(); + private static final TList EMPTY_LIST = new TList(); + private static final TMap EMPTY_MAP = new TMap(); + private static final String LIST = "list"; + private static final String SET = "set"; + private static final String MAP = "map"; + + protected class Context { + protected void write() throws TException {} + + /** + * Returns whether the current value is a key in a map + */ + protected boolean isMapKey() { return false; } + } + + protected class ListContext extends Context { + protected boolean first_ = true; + + protected void write() throws TException { + if (first_) { + first_ = false; + } else { + trans_.write(COMMA); + } + } + } + + protected class StructContext extends Context { + protected boolean first_ = true; + protected boolean colon_ = true; + + protected void write() throws TException { + if (first_) { + first_ = false; + colon_ = true; + } else { + trans_.write(colon_ ? COLON : COMMA); + colon_ = !colon_; + } + } + } + + protected class MapContext extends StructContext { + protected boolean isKey = true; + + @Override + protected void write() throws TException { + super.write(); + isKey = !isKey; + } + + protected boolean isMapKey() { + // we want to coerce map keys to json strings regardless + // of their type + return isKey; + } + } + + protected final Context BASE_CONTEXT = new Context(); + + /** + * Stack of nested contexts that we may be in. + */ + protected Stack<Context> writeContextStack_ = new Stack<Context>(); + + /** + * Current context that we are in + */ + protected Context writeContext_ = BASE_CONTEXT; + + /** + * Push a new write context onto the stack. + */ + protected void pushWriteContext(Context c) { + writeContextStack_.push(writeContext_); + writeContext_ = c; + } + + /** + * Pop the last write context off the stack + */ + protected void popWriteContext() { + writeContext_ = writeContextStack_.pop(); + } + + /** + * Reset the write context stack to its initial state. + */ + protected void resetWriteContext() { + while (!writeContextStack_.isEmpty()) { + popWriteContext(); + } + } + + /** + * Used to make sure that we are not encountering a map whose keys are containers + */ + protected void assertContextIsNotMapKey(String invalidKeyType) throws CollectionMapKeyException { + if (writeContext_.isMapKey()) { + throw new CollectionMapKeyException("Cannot serialize a map with keys that are of type " + invalidKeyType); + } + } + + /** + * Constructor + */ + public TSimpleJSONProtocol(TTransport trans) { + super(trans); + } + + @Override + public void writeMessageBegin(TMessage message) throws TException { + resetWriteContext(); // THRIFT-3743 + trans_.write(LBRACKET); + pushWriteContext(new ListContext()); + writeString(message.name); + writeByte(message.type); + writeI32(message.seqid); + } + + @Override + public void writeMessageEnd() throws TException { + popWriteContext(); + trans_.write(RBRACKET); + } + + @Override + public void writeStructBegin(TStruct struct) throws TException { + writeContext_.write(); + trans_.write(LBRACE); + pushWriteContext(new StructContext()); + } + + @Override + public void writeStructEnd() throws TException { + popWriteContext(); + trans_.write(RBRACE); + } + + @Override + public void writeFieldBegin(TField field) throws TException { + // Note that extra type information is omitted in JSON! + writeString(field.name); + } + + @Override + public void writeFieldEnd() throws TException {} + + @Override + public void writeFieldStop() throws TException {} + + @Override + public void writeMapBegin(TMap map) throws TException { + assertContextIsNotMapKey(MAP); + writeContext_.write(); + trans_.write(LBRACE); + pushWriteContext(new MapContext()); + // No metadata! + } + + @Override + public void writeMapEnd() throws TException { + popWriteContext(); + trans_.write(RBRACE); + } + + @Override + public void writeListBegin(TList list) throws TException { + assertContextIsNotMapKey(LIST); + writeContext_.write(); + trans_.write(LBRACKET); + pushWriteContext(new ListContext()); + // No metadata! + } + + @Override + public void writeListEnd() throws TException { + popWriteContext(); + trans_.write(RBRACKET); + } + + @Override + public void writeSetBegin(TSet set) throws TException { + assertContextIsNotMapKey(SET); + writeContext_.write(); + trans_.write(LBRACKET); + pushWriteContext(new ListContext()); + // No metadata! + } + + @Override + public void writeSetEnd() throws TException { + popWriteContext(); + trans_.write(RBRACKET); + } + + @Override + public void writeBool(boolean b) throws TException { + writeByte(b ? (byte)1 : (byte)0); + } + + @Override + public void writeByte(byte b) throws TException { + writeI32(b); + } + + @Override + public void writeI16(short i16) throws TException { + writeI32(i16); + } + + @Override + public void writeI32(int i32) throws TException { + if(writeContext_.isMapKey()) { + writeString(Integer.toString(i32)); + } else { + writeContext_.write(); + _writeStringData(Integer.toString(i32)); + } + } + + public void _writeStringData(String s) throws TException { + byte[] b = s.getBytes(StandardCharsets.UTF_8); + trans_.write(b); + } + + @Override + public void writeI64(long i64) throws TException { + if(writeContext_.isMapKey()) { + writeString(Long.toString(i64)); + } else { + writeContext_.write(); + _writeStringData(Long.toString(i64)); + } + } + + @Override + public void writeDouble(double dub) throws TException { + if(writeContext_.isMapKey()) { + writeString(Double.toString(dub)); + } else { + writeContext_.write(); + _writeStringData(Double.toString(dub)); + } + } + + @Override + public void writeString(String str) throws TException { + writeContext_.write(); + int length = str.length(); + StringBuffer escape = new StringBuffer(length + 16); + escape.append(QUOTE); + for (int i = 0; i < length; ++i) { + char c = str.charAt(i); + switch (c) { + case '"': + case '\\': + escape.append('\\'); + escape.append(c); + break; + case '\b': + escape.append('\\'); + escape.append('b'); + break; + case '\f': + escape.append('\\'); + escape.append('f'); + break; + case '\n': + escape.append('\\'); + escape.append('n'); + break; + case '\r': + escape.append('\\'); + escape.append('r'); + break; + case '\t': + escape.append('\\'); + escape.append('t'); + break; + default: + // Control characters! According to JSON RFC u0020 (space) + if (c < ' ') { + String hex = Integer.toHexString(c); + escape.append('\\'); + escape.append('u'); + for (int j = 4; j > hex.length(); --j) { + escape.append('0'); + } + escape.append(hex); + } else { + escape.append(c); + } + break; + } + } + escape.append(QUOTE); + _writeStringData(escape.toString()); + } + + @Override + public void writeBinary(ByteBuffer bin) throws TException { + // TODO(mcslee): Fix this + writeString(new String(bin.array(), bin.position() + bin.arrayOffset(), + bin.limit() - bin.position() - bin.arrayOffset(), + StandardCharsets.UTF_8)); + } + + /** + * Reading methods. + */ + + @Override + public TMessage readMessageBegin() throws TException { + // TODO(mcslee): implement + return EMPTY_MESSAGE; + } + + @Override + public void readMessageEnd() throws TException {} + + @Override + public TStruct readStructBegin() throws TException { + // TODO(mcslee): implement + return ANONYMOUS_STRUCT; + } + + @Override + public void readStructEnd() throws TException {} + + @Override + public TField readFieldBegin() throws TException { + // TODO(mcslee): implement + return ANONYMOUS_FIELD; + } + + @Override + public void readFieldEnd() throws TException {} + + @Override + public TMap readMapBegin() throws TException { + // TODO(mcslee): implement + return EMPTY_MAP; + } + + @Override + public void readMapEnd() throws TException {} + + @Override + public TList readListBegin() throws TException { + // TODO(mcslee): implement + return EMPTY_LIST; + } + + @Override + public void readListEnd() throws TException {} + + @Override + public TSet readSetBegin() throws TException { + // TODO(mcslee): implement + return EMPTY_SET; + } + + @Override + public void readSetEnd() throws TException {} + + @Override + public boolean readBool() throws TException { + return (readByte() == 1); + } + + @Override + public byte readByte() throws TException { + // TODO(mcslee): implement + return 0; + } + + @Override + public short readI16() throws TException { + // TODO(mcslee): implement + return 0; + } + + @Override + public int readI32() throws TException { + // TODO(mcslee): implement + return 0; + } + + @Override + public long readI64() throws TException { + // TODO(mcslee): implement + return 0; + } + + @Override + public double readDouble() throws TException { + // TODO(mcslee): implement + return 0; + } + + @Override + public String readString() throws TException { + // TODO(mcslee): implement + return ""; + } + + public String readStringBody(int size) throws TException { + // TODO(mcslee): implement + return ""; + } + + @Override + public ByteBuffer readBinary() throws TException { + // TODO(mcslee): implement + return ByteBuffer.wrap(new byte[0]); + } + + public static class CollectionMapKeyException extends TException { + public CollectionMapKeyException(String message) { + super(message); + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TStruct.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TStruct.java new file mode 100644 index 000000000..a0f79012a --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TStruct.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +/** + * Helper class that encapsulates struct metadata. + * + */ +public final class TStruct { + public TStruct() { + this(""); + } + + public TStruct(String n) { + name = n; + } + + public final String name; +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TTupleProtocol.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TTupleProtocol.java new file mode 100644 index 000000000..74f5226c8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TTupleProtocol.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +import java.util.BitSet; + +import org.apache.thrift.TException; +import org.apache.thrift.scheme.IScheme; +import org.apache.thrift.scheme.TupleScheme; +import org.apache.thrift.transport.TTransport; + +public final class TTupleProtocol extends TCompactProtocol { + public static class Factory implements TProtocolFactory { + public Factory() {} + + public TProtocol getProtocol(TTransport trans) { + return new TTupleProtocol(trans); + } + } + + public TTupleProtocol(TTransport transport) { + super(transport); + } + + @Override + public Class<? extends IScheme> getScheme() { + return TupleScheme.class; + } + + public void writeBitSet(BitSet bs, int vectorWidth) throws TException { + byte[] bytes = toByteArray(bs, vectorWidth); + for (byte b : bytes) { + writeByte(b); + } + } + + public BitSet readBitSet(int i) throws TException { + int length = (int) Math.ceil(i/8.0); + byte[] bytes = new byte[length]; + for (int j = 0; j < length; j++) { + bytes[j] = readByte(); + } + BitSet bs = fromByteArray(bytes); + return bs; + } + + /** + * Returns a bitset containing the values in bytes. The byte-ordering must be + * big-endian. + */ + public static BitSet fromByteArray(byte[] bytes) { + BitSet bits = new BitSet(); + for (int i = 0; i < bytes.length * 8; i++) { + if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) { + bits.set(i); + } + } + return bits; + } + + /** + * Returns a byte array of at least length 1. The most significant bit in the + * result is guaranteed not to be a 1 (since BitSet does not support sign + * extension). The byte-ordering of the result is big-endian which means the + * most significant bit is in element 0. The bit at index 0 of the bit set is + * assumed to be the least significant bit. + * + * @param bits + * @param vectorWidth + * @return a byte array of at least length 1 + */ + public static byte[] toByteArray(BitSet bits, int vectorWidth) { + byte[] bytes = new byte[(int) Math.ceil(vectorWidth/8.0)]; + for (int i = 0; i < bits.length(); i++) { + if (bits.get(i)) { + bytes[bytes.length - i / 8 - 1] |= 1 << (i % 8); + } + } + return bytes; + } + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TType.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TType.java new file mode 100644 index 000000000..c3c1a0abd --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/protocol/TType.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.protocol; + +/** + * Type constants in the Thrift protocol. + */ +public final class TType { + public static final byte STOP = 0; + public static final byte VOID = 1; + public static final byte BOOL = 2; + public static final byte BYTE = 3; + public static final byte DOUBLE = 4; + public static final byte I16 = 6; + public static final byte I32 = 8; + public static final byte I64 = 10; + public static final byte STRING = 11; + public static final byte STRUCT = 12; + public static final byte MAP = 13; + public static final byte SET = 14; + public static final byte LIST = 15; + public static final byte ENUM = 16; +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/scheme/IScheme.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/scheme/IScheme.java new file mode 100644 index 000000000..aa3550705 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/scheme/IScheme.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.scheme; + +import org.apache.thrift.TBase; + +public interface IScheme<T extends TBase> { + + public void read(org.apache.thrift.protocol.TProtocol iproto, T struct) throws org.apache.thrift.TException; + + public void write(org.apache.thrift.protocol.TProtocol oproto, T struct) throws org.apache.thrift.TException; + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/scheme/SchemeFactory.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/scheme/SchemeFactory.java new file mode 100644 index 000000000..006a66805 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/scheme/SchemeFactory.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.scheme; + +public interface SchemeFactory { + + public <S extends IScheme> S getScheme(); + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/scheme/StandardScheme.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/scheme/StandardScheme.java new file mode 100644 index 000000000..ffab04db6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/scheme/StandardScheme.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.scheme; + +import org.apache.thrift.TBase; + +public abstract class StandardScheme<T extends TBase> implements IScheme<T> { + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/scheme/TupleScheme.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/scheme/TupleScheme.java new file mode 100644 index 000000000..365242b11 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/scheme/TupleScheme.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.scheme; + +import org.apache.thrift.TBase; + +public abstract class TupleScheme<T extends TBase> implements IScheme<T> { + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/AbstractNonblockingServer.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/AbstractNonblockingServer.java new file mode 100644 index 000000000..8c206e427 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/AbstractNonblockingServer.java @@ -0,0 +1,618 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.server; + +import org.apache.thrift.TAsyncProcessor; +import org.apache.thrift.TByteArrayOutputStream; +import org.apache.thrift.TException; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.transport.TFramedTransport; +import org.apache.thrift.transport.TIOStreamTransport; +import org.apache.thrift.transport.TMemoryInputTransport; +import org.apache.thrift.transport.TNonblockingServerTransport; +import org.apache.thrift.transport.TNonblockingTransport; +import org.apache.thrift.transport.TTransport; +import org.apache.thrift.transport.TTransportException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.spi.SelectorProvider; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Provides common methods and classes used by nonblocking TServer + * implementations. + */ +public abstract class AbstractNonblockingServer extends TServer { + protected final Logger LOGGER = LoggerFactory.getLogger(getClass().getName()); + + public static abstract class AbstractNonblockingServerArgs<T extends AbstractNonblockingServerArgs<T>> extends AbstractServerArgs<T> { + public long maxReadBufferBytes = 256 * 1024 * 1024; + + public AbstractNonblockingServerArgs(TNonblockingServerTransport transport) { + super(transport); + transportFactory(new TFramedTransport.Factory()); + } + } + + /** + * The maximum amount of memory we will allocate to client IO buffers at a + * time. Without this limit, the server will gladly allocate client buffers + * right into an out of memory exception, rather than waiting. + */ + final long MAX_READ_BUFFER_BYTES; + + /** + * How many bytes are currently allocated to read buffers. + */ + final AtomicLong readBufferBytesAllocated = new AtomicLong(0); + + public AbstractNonblockingServer(AbstractNonblockingServerArgs args) { + super(args); + MAX_READ_BUFFER_BYTES = args.maxReadBufferBytes; + } + + /** + * Begin accepting connections and processing invocations. + */ + public void serve() { + // start any IO threads + if (!startThreads()) { + return; + } + + // start listening, or exit + if (!startListening()) { + return; + } + + setServing(true); + + // this will block while we serve + waitForShutdown(); + + setServing(false); + + // do a little cleanup + stopListening(); + } + + /** + * Starts any threads required for serving. + * + * @return true if everything went ok, false if threads could not be started. + */ + protected abstract boolean startThreads(); + + /** + * A method that will block until when threads handling the serving have been + * shut down. + */ + protected abstract void waitForShutdown(); + + /** + * Have the server transport start accepting connections. + * + * @return true if we started listening successfully, false if something went + * wrong. + */ + protected boolean startListening() { + try { + serverTransport_.listen(); + return true; + } catch (TTransportException ttx) { + LOGGER.error("Failed to start listening on server socket!", ttx); + return false; + } + } + + /** + * Stop listening for connections. + */ + protected void stopListening() { + serverTransport_.close(); + } + + /** + * Perform an invocation. This method could behave several different ways - + * invoke immediately inline, queue for separate execution, etc. + * + * @return true if invocation was successfully requested, which is not a + * guarantee that invocation has completed. False if the request + * failed. + */ + protected abstract boolean requestInvoke(FrameBuffer frameBuffer); + + /** + * An abstract thread that handles selecting on a set of transports and + * {@link FrameBuffer FrameBuffers} associated with selected keys + * corresponding to requests. + */ + protected abstract class AbstractSelectThread extends Thread { + protected Selector selector; + + // List of FrameBuffers that want to change their selection interests. + protected final Set<FrameBuffer> selectInterestChanges = new HashSet<FrameBuffer>(); + + public AbstractSelectThread() throws IOException { + this.selector = SelectorProvider.provider().openSelector(); + } + + /** + * If the selector is blocked, wake it up. + */ + public void wakeupSelector() { + selector.wakeup(); + } + + /** + * Add FrameBuffer to the list of select interest changes and wake up the + * selector if it's blocked. When the select() call exits, it'll give the + * FrameBuffer a chance to change its interests. + */ + public void requestSelectInterestChange(FrameBuffer frameBuffer) { + synchronized (selectInterestChanges) { + selectInterestChanges.add(frameBuffer); + } + // wakeup the selector, if it's currently blocked. + selector.wakeup(); + } + + /** + * Check to see if there are any FrameBuffers that have switched their + * interest type from read to write or vice versa. + */ + protected void processInterestChanges() { + synchronized (selectInterestChanges) { + for (FrameBuffer fb : selectInterestChanges) { + fb.changeSelectInterests(); + } + selectInterestChanges.clear(); + } + } + + /** + * Do the work required to read from a readable client. If the frame is + * fully read, then invoke the method call. + */ + protected void handleRead(SelectionKey key) { + FrameBuffer buffer = (FrameBuffer) key.attachment(); + if (!buffer.read()) { + cleanupSelectionKey(key); + return; + } + + // if the buffer's frame read is complete, invoke the method. + if (buffer.isFrameFullyRead()) { + if (!requestInvoke(buffer)) { + cleanupSelectionKey(key); + } + } + } + + /** + * Let a writable client get written, if there's data to be written. + */ + protected void handleWrite(SelectionKey key) { + FrameBuffer buffer = (FrameBuffer) key.attachment(); + if (!buffer.write()) { + cleanupSelectionKey(key); + } + } + + /** + * Do connection-close cleanup on a given SelectionKey. + */ + protected void cleanupSelectionKey(SelectionKey key) { + // remove the records from the two maps + FrameBuffer buffer = (FrameBuffer) key.attachment(); + if (buffer != null) { + // close the buffer + buffer.close(); + } + // cancel the selection key + key.cancel(); + } + } // SelectThread + + /** + * Possible states for the FrameBuffer state machine. + */ + private enum FrameBufferState { + // in the midst of reading the frame size off the wire + READING_FRAME_SIZE, + // reading the actual frame data now, but not all the way done yet + READING_FRAME, + // completely read the frame, so an invocation can now happen + READ_FRAME_COMPLETE, + // waiting to get switched to listening for write events + AWAITING_REGISTER_WRITE, + // started writing response data, not fully complete yet + WRITING, + // another thread wants this framebuffer to go back to reading + AWAITING_REGISTER_READ, + // we want our transport and selection key invalidated in the selector + // thread + AWAITING_CLOSE + } + + /** + * Class that implements a sort of state machine around the interaction with a + * client and an invoker. It manages reading the frame size and frame data, + * getting it handed off as wrapped transports, and then the writing of + * response data back to the client. In the process it manages flipping the + * read and write bits on the selection key for its client. + */ + public class FrameBuffer { + private final Logger LOGGER = LoggerFactory.getLogger(getClass().getName()); + + // the actual transport hooked up to the client. + protected final TNonblockingTransport trans_; + + // the SelectionKey that corresponds to our transport + protected final SelectionKey selectionKey_; + + // the SelectThread that owns the registration of our transport + protected final AbstractSelectThread selectThread_; + + // where in the process of reading/writing are we? + protected FrameBufferState state_ = FrameBufferState.READING_FRAME_SIZE; + + // the ByteBuffer we'll be using to write and read, depending on the state + protected ByteBuffer buffer_; + + protected final TByteArrayOutputStream response_; + + // the frame that the TTransport should wrap. + protected final TMemoryInputTransport frameTrans_; + + // the transport that should be used to connect to clients + protected final TTransport inTrans_; + + protected final TTransport outTrans_; + + // the input protocol to use on frames + protected final TProtocol inProt_; + + // the output protocol to use on frames + protected final TProtocol outProt_; + + // context associated with this connection + protected final ServerContext context_; + + public FrameBuffer(final TNonblockingTransport trans, + final SelectionKey selectionKey, + final AbstractSelectThread selectThread) { + trans_ = trans; + selectionKey_ = selectionKey; + selectThread_ = selectThread; + buffer_ = ByteBuffer.allocate(4); + + frameTrans_ = new TMemoryInputTransport(); + response_ = new TByteArrayOutputStream(); + inTrans_ = inputTransportFactory_.getTransport(frameTrans_); + outTrans_ = outputTransportFactory_.getTransport(new TIOStreamTransport(response_)); + inProt_ = inputProtocolFactory_.getProtocol(inTrans_); + outProt_ = outputProtocolFactory_.getProtocol(outTrans_); + + if (eventHandler_ != null) { + context_ = eventHandler_.createContext(inProt_, outProt_); + } else { + context_ = null; + } + } + + /** + * Give this FrameBuffer a chance to read. The selector loop should have + * received a read event for this FrameBuffer. + * + * @return true if the connection should live on, false if it should be + * closed + */ + public boolean read() { + if (state_ == FrameBufferState.READING_FRAME_SIZE) { + // try to read the frame size completely + if (!internalRead()) { + return false; + } + + // if the frame size has been read completely, then prepare to read the + // actual frame. + if (buffer_.remaining() == 0) { + // pull out the frame size as an integer. + int frameSize = buffer_.getInt(0); + if (frameSize <= 0) { + LOGGER.error("Read an invalid frame size of " + frameSize + + ". Are you using TFramedTransport on the client side?"); + return false; + } + + // if this frame will always be too large for this server, log the + // error and close the connection. + if (frameSize > MAX_READ_BUFFER_BYTES) { + LOGGER.error("Read a frame size of " + frameSize + + ", which is bigger than the maximum allowable buffer size for ALL connections."); + return false; + } + + // if this frame will push us over the memory limit, then return. + // with luck, more memory will free up the next time around. + if (readBufferBytesAllocated.get() + frameSize > MAX_READ_BUFFER_BYTES) { + return true; + } + + // increment the amount of memory allocated to read buffers + readBufferBytesAllocated.addAndGet(frameSize + 4); + + // reallocate the readbuffer as a frame-sized buffer + buffer_ = ByteBuffer.allocate(frameSize + 4); + buffer_.putInt(frameSize); + + state_ = FrameBufferState.READING_FRAME; + } else { + // this skips the check of READING_FRAME state below, since we can't + // possibly go on to that state if there's data left to be read at + // this one. + return true; + } + } + + // it is possible to fall through from the READING_FRAME_SIZE section + // to READING_FRAME if there's already some frame data available once + // READING_FRAME_SIZE is complete. + + if (state_ == FrameBufferState.READING_FRAME) { + if (!internalRead()) { + return false; + } + + // since we're already in the select loop here for sure, we can just + // modify our selection key directly. + if (buffer_.remaining() == 0) { + // get rid of the read select interests + selectionKey_.interestOps(0); + state_ = FrameBufferState.READ_FRAME_COMPLETE; + } + + return true; + } + + // if we fall through to this point, then the state must be invalid. + LOGGER.error("Read was called but state is invalid (" + state_ + ")"); + return false; + } + + /** + * Give this FrameBuffer a chance to write its output to the final client. + */ + public boolean write() { + if (state_ == FrameBufferState.WRITING) { + try { + if (trans_.write(buffer_) < 0) { + return false; + } + } catch (IOException e) { + LOGGER.warn("Got an IOException during write!", e); + return false; + } + + // we're done writing. now we need to switch back to reading. + if (buffer_.remaining() == 0) { + prepareRead(); + } + return true; + } + + LOGGER.error("Write was called, but state is invalid (" + state_ + ")"); + return false; + } + + /** + * Give this FrameBuffer a chance to set its interest to write, once data + * has come in. + */ + public void changeSelectInterests() { + switch (state_) { + case AWAITING_REGISTER_WRITE: + // set the OP_WRITE interest + selectionKey_.interestOps(SelectionKey.OP_WRITE); + state_ = FrameBufferState.WRITING; + break; + case AWAITING_REGISTER_READ: + prepareRead(); + break; + case AWAITING_CLOSE: + close(); + selectionKey_.cancel(); + break; + default: + LOGGER.error( + "changeSelectInterest was called, but state is invalid ({})", + state_); + } + } + + /** + * Shut the connection down. + */ + public void close() { + // if we're being closed due to an error, we might have allocated a + // buffer that we need to subtract for our memory accounting. + if (state_ == FrameBufferState.READING_FRAME || + state_ == FrameBufferState.READ_FRAME_COMPLETE || + state_ == FrameBufferState.AWAITING_CLOSE) { + readBufferBytesAllocated.addAndGet(-buffer_.array().length); + } + trans_.close(); + if (eventHandler_ != null) { + eventHandler_.deleteContext(context_, inProt_, outProt_); + } + } + + /** + * Check if this FrameBuffer has a full frame read. + */ + public boolean isFrameFullyRead() { + return state_ == FrameBufferState.READ_FRAME_COMPLETE; + } + + /** + * After the processor has processed the invocation, whatever thread is + * managing invocations should call this method on this FrameBuffer so we + * know it's time to start trying to write again. Also, if it turns out that + * there actually isn't any data in the response buffer, we'll skip trying + * to write and instead go back to reading. + */ + public void responseReady() { + // the read buffer is definitely no longer in use, so we will decrement + // our read buffer count. we do this here as well as in close because + // we'd like to free this read memory up as quickly as possible for other + // clients. + readBufferBytesAllocated.addAndGet(-buffer_.array().length); + + if (response_.len() == 0) { + // go straight to reading again. this was probably an oneway method + state_ = FrameBufferState.AWAITING_REGISTER_READ; + buffer_ = null; + } else { + buffer_ = ByteBuffer.wrap(response_.get(), 0, response_.len()); + + // set state that we're waiting to be switched to write. we do this + // asynchronously through requestSelectInterestChange() because there is + // a possibility that we're not in the main thread, and thus currently + // blocked in select(). (this functionality is in place for the sake of + // the HsHa server.) + state_ = FrameBufferState.AWAITING_REGISTER_WRITE; + } + requestSelectInterestChange(); + } + + /** + * Actually invoke the method signified by this FrameBuffer. + */ + public void invoke() { + frameTrans_.reset(buffer_.array()); + response_.reset(); + + try { + if (eventHandler_ != null) { + eventHandler_.processContext(context_, inTrans_, outTrans_); + } + processorFactory_.getProcessor(inTrans_).process(inProt_, outProt_); + responseReady(); + return; + } catch (TException te) { + LOGGER.warn("Exception while invoking!", te); + } catch (Throwable t) { + LOGGER.error("Unexpected throwable while invoking!", t); + } + // This will only be reached when there is a throwable. + state_ = FrameBufferState.AWAITING_CLOSE; + requestSelectInterestChange(); + } + + /** + * Perform a read into buffer. + * + * @return true if the read succeeded, false if there was an error or the + * connection closed. + */ + private boolean internalRead() { + try { + if (trans_.read(buffer_) < 0) { + return false; + } + return true; + } catch (IOException e) { + LOGGER.warn("Got an IOException in internalRead!", e); + return false; + } + } + + /** + * We're done writing, so reset our interest ops and change state + * accordingly. + */ + private void prepareRead() { + // we can set our interest directly without using the queue because + // we're in the select thread. + selectionKey_.interestOps(SelectionKey.OP_READ); + // get ready for another go-around + buffer_ = ByteBuffer.allocate(4); + state_ = FrameBufferState.READING_FRAME_SIZE; + } + + /** + * When this FrameBuffer needs to change its select interests and execution + * might not be in its select thread, then this method will make sure the + * interest change gets done when the select thread wakes back up. When the + * current thread is this FrameBuffer's select thread, then it just does the + * interest change immediately. + */ + protected void requestSelectInterestChange() { + if (Thread.currentThread() == this.selectThread_) { + changeSelectInterests(); + } else { + this.selectThread_.requestSelectInterestChange(this); + } + } + } // FrameBuffer + + public class AsyncFrameBuffer extends FrameBuffer { + public AsyncFrameBuffer(TNonblockingTransport trans, SelectionKey selectionKey, AbstractSelectThread selectThread) { + super(trans, selectionKey, selectThread); + } + + public TProtocol getInputProtocol() { + return inProt_; + } + + public TProtocol getOutputProtocol() { + return outProt_; + } + + + public void invoke() { + frameTrans_.reset(buffer_.array()); + response_.reset(); + + try { + if (eventHandler_ != null) { + eventHandler_.processContext(context_, inTrans_, outTrans_); + } + ((TAsyncProcessor)processorFactory_.getProcessor(inTrans_)).process(this); + return; + } catch (TException te) { + LOGGER.warn("Exception while invoking!", te); + } catch (Throwable t) { + LOGGER.error("Unexpected throwable while invoking!", t); + } + // This will only be reached when there is a throwable. + state_ = FrameBufferState.AWAITING_CLOSE; + requestSelectInterestChange(); + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/Invocation.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/Invocation.java new file mode 100644 index 000000000..e8210f419 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/Invocation.java @@ -0,0 +1,20 @@ +package org.apache.thrift.server; + +import org.apache.thrift.server.AbstractNonblockingServer.FrameBuffer; + +/** + * An Invocation represents a method call that is prepared to execute, given + * an idle worker thread. It contains the input and output protocols the + * thread's processor should use to perform the usual Thrift invocation. + */ +class Invocation implements Runnable { + private final FrameBuffer frameBuffer; + + public Invocation(final FrameBuffer frameBuffer) { + this.frameBuffer = frameBuffer; + } + + public void run() { + frameBuffer.invoke(); + } +}
\ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/ServerContext.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/ServerContext.java new file mode 100644 index 000000000..9b0b99eea --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/ServerContext.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +/** + * Interface for storing server's connection context + */ + +package org.apache.thrift.server; + +public interface ServerContext {} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TExtensibleServlet.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TExtensibleServlet.java new file mode 100644 index 000000000..75082c0f7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TExtensibleServlet.java @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.server; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.thrift.TException; +import org.apache.thrift.TProcessor; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.protocol.TProtocolFactory; +import org.apache.thrift.transport.TIOStreamTransport; +import org.apache.thrift.transport.TTransport; + +/** + * Servlet implementation class ThriftServer, that allows {@link TProcessor} and + * {@link TProtocolFactory} to be supplied after the {@link #init()} method has + * finished. <br> + * Subclasses must implement the abstract methods that return the TProcessor and + * two TProtocolFactory. Those methods are guaranteed to be called exactly once, + * and that {@link ServletContext} is available. + */ +public abstract class TExtensibleServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + private TProcessor processor; + + private TProtocolFactory inFactory; + + private TProtocolFactory outFactory; + + private Collection<Map.Entry<String, String>> customHeaders; + + /** + * Returns the appropriate {@link TProcessor}. This will be called <b>once</b> just + * after the {@link #init()} method + * + * @return the appropriate {@link TProcessor} + */ + protected abstract TProcessor getProcessor(); + + /** + * Returns the appropriate in {@link TProtocolFactory}. This will be called + * <b>once</b> just after the {@link #init()} method + * + * @return the appropriate in {@link TProtocolFactory} + */ + protected abstract TProtocolFactory getInProtocolFactory(); + + /** + * Returns the appropriate out {@link TProtocolFactory}. This will be called + * <b>once</b> just after the {@link #init()} method + * + * @return the appropriate out {@link TProtocolFactory} + */ + protected abstract TProtocolFactory getOutProtocolFactory(); + + @Override + public final void init(ServletConfig config) throws ServletException { + super.init(config); //no-args init() happens here + this.processor = getProcessor(); + this.inFactory = getInProtocolFactory(); + this.outFactory = getOutProtocolFactory(); + this.customHeaders = new ArrayList<Map.Entry<String, String>>(); + + if (processor == null) { + throw new ServletException("processor must be set"); + } + if (inFactory == null) { + throw new ServletException("inFactory must be set"); + } + if (outFactory == null) { + throw new ServletException("outFactory must be set"); + } + } + + /** + * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse + * response) + */ + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + TTransport inTransport = null; + TTransport outTransport = null; + + try { + response.setContentType("application/x-thrift"); + + if (null != this.customHeaders) { + for (Map.Entry<String, String> header : this.customHeaders) { + response.addHeader(header.getKey(), header.getValue()); + } + } + + InputStream in = request.getInputStream(); + OutputStream out = response.getOutputStream(); + + TTransport transport = new TIOStreamTransport(in, out); + inTransport = transport; + outTransport = transport; + + TProtocol inProtocol = inFactory.getProtocol(inTransport); + TProtocol outProtocol = inFactory.getProtocol(outTransport); + + processor.process(inProtocol, outProtocol); + out.flush(); + } catch (TException te) { + throw new ServletException(te); + } + } + + /** + * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse + * response) + */ + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + doPost(req, resp); + } + + public void addCustomHeader(final String key, final String value) { + this.customHeaders.add(new Map.Entry<String, String>() { + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + public String setValue(String value) { + return null; + } + }); + } + + public void setCustomHeaders(Collection<Map.Entry<String, String>> headers) { + this.customHeaders.clear(); + this.customHeaders.addAll(headers); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/THsHaServer.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/THsHaServer.java new file mode 100644 index 000000000..4c5d7b5b5 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/THsHaServer.java @@ -0,0 +1,204 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.server; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.apache.thrift.transport.TNonblockingServerTransport; + +/** + * An extension of the TNonblockingServer to a Half-Sync/Half-Async server. + * Like TNonblockingServer, it relies on the use of TFramedTransport. + */ +public class THsHaServer extends TNonblockingServer { + + public static class Args extends AbstractNonblockingServerArgs<Args> { + public int minWorkerThreads = 5; + public int maxWorkerThreads = Integer.MAX_VALUE; + private int stopTimeoutVal = 60; + private TimeUnit stopTimeoutUnit = TimeUnit.SECONDS; + private ExecutorService executorService = null; + + public Args(TNonblockingServerTransport transport) { + super(transport); + } + + + /** + * Sets the min and max threads. + * + * @deprecated use {@link #minWorkerThreads(int)} and {@link #maxWorkerThreads(int)} instead. + */ + @Deprecated + public Args workerThreads(int n) { + minWorkerThreads = n; + maxWorkerThreads = n; + return this; + } + + /** + * @return what the min threads was set to. + * @deprecated use {@link #getMinWorkerThreads()} and {@link #getMaxWorkerThreads()} instead. + */ + @Deprecated + public int getWorkerThreads() { + return minWorkerThreads; + } + + public Args minWorkerThreads(int n) { + minWorkerThreads = n; + return this; + } + + public Args maxWorkerThreads(int n) { + maxWorkerThreads = n; + return this; + } + + public int getMinWorkerThreads() { + return minWorkerThreads; + } + + public int getMaxWorkerThreads() { + return maxWorkerThreads; + } + + public int getStopTimeoutVal() { + return stopTimeoutVal; + } + + public Args stopTimeoutVal(int stopTimeoutVal) { + this.stopTimeoutVal = stopTimeoutVal; + return this; + } + + public TimeUnit getStopTimeoutUnit() { + return stopTimeoutUnit; + } + + public Args stopTimeoutUnit(TimeUnit stopTimeoutUnit) { + this.stopTimeoutUnit = stopTimeoutUnit; + return this; + } + + public ExecutorService getExecutorService() { + return executorService; + } + + public Args executorService(ExecutorService executorService) { + this.executorService = executorService; + return this; + } + } + + + // This wraps all the functionality of queueing and thread pool management + // for the passing of Invocations from the Selector to workers. + private final ExecutorService invoker; + + private final Args args; + + /** + * Create the server with the specified Args configuration + */ + public THsHaServer(Args args) { + super(args); + + invoker = args.executorService == null ? createInvokerPool(args) : args.executorService; + this.args = args; + } + + /** + * {@inheritDoc} + */ + @Override + protected void waitForShutdown() { + joinSelector(); + gracefullyShutdownInvokerPool(); + } + + /** + * Helper to create an invoker pool + */ + protected static ExecutorService createInvokerPool(Args options) { + int minWorkerThreads = options.minWorkerThreads; + int maxWorkerThreads = options.maxWorkerThreads; + int stopTimeoutVal = options.stopTimeoutVal; + TimeUnit stopTimeoutUnit = options.stopTimeoutUnit; + + LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(); + ExecutorService invoker = new ThreadPoolExecutor(minWorkerThreads, + maxWorkerThreads, stopTimeoutVal, stopTimeoutUnit, queue); + + return invoker; + } + + protected ExecutorService getInvoker() { + return invoker; + } + + protected void gracefullyShutdownInvokerPool() { + // try to gracefully shut down the executor service + invoker.shutdown(); + + // Loop until awaitTermination finally does return without a interrupted + // exception. If we don't do this, then we'll shut down prematurely. We want + // to let the executorService clear it's task queue, closing client sockets + // appropriately. + long timeoutMS = args.stopTimeoutUnit.toMillis(args.stopTimeoutVal); + long now = System.currentTimeMillis(); + while (timeoutMS >= 0) { + try { + invoker.awaitTermination(timeoutMS, TimeUnit.MILLISECONDS); + break; + } catch (InterruptedException ix) { + long newnow = System.currentTimeMillis(); + timeoutMS -= (newnow - now); + now = newnow; + } + } + } + + /** + * We override the standard invoke method here to queue the invocation for + * invoker service instead of immediately invoking. The thread pool takes care + * of the rest. + */ + @Override + protected boolean requestInvoke(FrameBuffer frameBuffer) { + try { + Runnable invocation = getRunnable(frameBuffer); + invoker.execute(invocation); + return true; + } catch (RejectedExecutionException rx) { + LOGGER.warn("ExecutorService rejected execution!", rx); + return false; + } + } + + protected Runnable getRunnable(FrameBuffer frameBuffer){ + return new Invocation(frameBuffer); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TNonblockingServer.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TNonblockingServer.java new file mode 100644 index 000000000..79610b0f7 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TNonblockingServer.java @@ -0,0 +1,247 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.server; + +import org.apache.thrift.transport.TNonblockingServerTransport; +import org.apache.thrift.transport.TNonblockingTransport; +import org.apache.thrift.transport.TTransportException; + +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.util.Iterator; + +/** + * A nonblocking TServer implementation. This allows for fairness amongst all + * connected clients in terms of invocations. + * + * This server is inherently single-threaded. If you want a limited thread pool + * coupled with invocation-fairness, see THsHaServer. + * + * To use this server, you MUST use a TFramedTransport at the outermost + * transport, otherwise this server will be unable to determine when a whole + * method call has been read off the wire. Clients must also use TFramedTransport. + */ +public class TNonblockingServer extends AbstractNonblockingServer { + + public static class Args extends AbstractNonblockingServerArgs<Args> { + public Args(TNonblockingServerTransport transport) { + super(transport); + } + } + + private SelectAcceptThread selectAcceptThread_; + + public TNonblockingServer(AbstractNonblockingServerArgs args) { + super(args); + } + + + /** + * Start the selector thread to deal with accepts and client messages. + * + * @return true if everything went ok, false if we couldn't start for some + * reason. + */ + @Override + protected boolean startThreads() { + // start the selector + try { + selectAcceptThread_ = new SelectAcceptThread((TNonblockingServerTransport)serverTransport_); + selectAcceptThread_.start(); + return true; + } catch (IOException e) { + LOGGER.error("Failed to start selector thread!", e); + return false; + } + } + + @Override + protected void waitForShutdown() { + joinSelector(); + } + + /** + * Block until the selector thread exits. + */ + protected void joinSelector() { + // wait until the selector thread exits + try { + selectAcceptThread_.join(); + } catch (InterruptedException e) { + LOGGER.debug("Interrupted while waiting for accept thread", e); + Thread.currentThread().interrupt(); + } + } + + /** + * Stop serving and shut everything down. + */ + @Override + public void stop() { + stopped_ = true; + if (selectAcceptThread_ != null) { + selectAcceptThread_.wakeupSelector(); + } + } + + /** + * Perform an invocation. This method could behave several different ways + * - invoke immediately inline, queue for separate execution, etc. + */ + @Override + protected boolean requestInvoke(FrameBuffer frameBuffer) { + frameBuffer.invoke(); + return true; + } + + + public boolean isStopped() { + return selectAcceptThread_.isStopped(); + } + + /** + * The thread that will be doing all the selecting, managing new connections + * and those that still need to be read. + */ + protected class SelectAcceptThread extends AbstractSelectThread { + + // The server transport on which new client transports will be accepted + private final TNonblockingServerTransport serverTransport; + + /** + * Set up the thread that will handle the non-blocking accepts, reads, and + * writes. + */ + public SelectAcceptThread(final TNonblockingServerTransport serverTransport) + throws IOException { + this.serverTransport = serverTransport; + serverTransport.registerSelector(selector); + } + + public boolean isStopped() { + return stopped_; + } + + /** + * The work loop. Handles both selecting (all IO operations) and managing + * the selection preferences of all existing connections. + */ + public void run() { + try { + if (eventHandler_ != null) { + eventHandler_.preServe(); + } + + while (!stopped_) { + select(); + processInterestChanges(); + } + for (SelectionKey selectionKey : selector.keys()) { + cleanupSelectionKey(selectionKey); + } + } catch (Throwable t) { + LOGGER.error("run() exiting due to uncaught error", t); + } finally { + try { + selector.close(); + } catch (IOException e) { + LOGGER.error("Got an IOException while closing selector!", e); + } + stopped_ = true; + } + } + + /** + * Select and process IO events appropriately: + * If there are connections to be accepted, accept them. + * If there are existing connections with data waiting to be read, read it, + * buffering until a whole frame has been read. + * If there are any pending responses, buffer them until their target client + * is available, and then send the data. + */ + private void select() { + try { + // wait for io events. + selector.select(); + + // process the io events we received + Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator(); + while (!stopped_ && selectedKeys.hasNext()) { + SelectionKey key = selectedKeys.next(); + selectedKeys.remove(); + + // skip if not valid + if (!key.isValid()) { + cleanupSelectionKey(key); + continue; + } + + // if the key is marked Accept, then it has to be the server + // transport. + if (key.isAcceptable()) { + handleAccept(); + } else if (key.isReadable()) { + // deal with reads + handleRead(key); + } else if (key.isWritable()) { + // deal with writes + handleWrite(key); + } else { + LOGGER.warn("Unexpected state in select! " + key.interestOps()); + } + } + } catch (IOException e) { + LOGGER.warn("Got an IOException while selecting!", e); + } + } + + protected FrameBuffer createFrameBuffer(final TNonblockingTransport trans, + final SelectionKey selectionKey, + final AbstractSelectThread selectThread) { + return processorFactory_.isAsyncProcessor() ? + new AsyncFrameBuffer(trans, selectionKey, selectThread) : + new FrameBuffer(trans, selectionKey, selectThread); + } + + /** + * Accept a new connection. + */ + private void handleAccept() throws IOException { + SelectionKey clientKey = null; + TNonblockingTransport client = null; + try { + // accept the connection + client = (TNonblockingTransport)serverTransport.accept(); + clientKey = client.registerSelector(selector, SelectionKey.OP_READ); + + // add this key to the map + FrameBuffer frameBuffer = createFrameBuffer(client, clientKey, SelectAcceptThread.this); + + clientKey.attach(frameBuffer); + } catch (TTransportException tte) { + // something went wrong accepting. + LOGGER.warn("Exception trying to accept!", tte); + if (clientKey != null) cleanupSelectionKey(clientKey); + if (client != null) client.close(); + } + } + } // SelectAcceptThread +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TServer.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TServer.java new file mode 100644 index 000000000..bac06b26b --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TServer.java @@ -0,0 +1,177 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.server; + +import org.apache.thrift.TProcessor; +import org.apache.thrift.TProcessorFactory; +import org.apache.thrift.protocol.TBinaryProtocol; +import org.apache.thrift.protocol.TProtocolFactory; +import org.apache.thrift.transport.TServerTransport; +import org.apache.thrift.transport.TTransportFactory; + +/** + * Generic interface for a Thrift server. + * + */ +public abstract class TServer { + + public static class Args extends AbstractServerArgs<Args> { + public Args(TServerTransport transport) { + super(transport); + } + } + + public static abstract class AbstractServerArgs<T extends AbstractServerArgs<T>> { + final TServerTransport serverTransport; + TProcessorFactory processorFactory; + TTransportFactory inputTransportFactory = new TTransportFactory(); + TTransportFactory outputTransportFactory = new TTransportFactory(); + TProtocolFactory inputProtocolFactory = new TBinaryProtocol.Factory(); + TProtocolFactory outputProtocolFactory = new TBinaryProtocol.Factory(); + + public AbstractServerArgs(TServerTransport transport) { + serverTransport = transport; + } + + public T processorFactory(TProcessorFactory factory) { + this.processorFactory = factory; + return (T) this; + } + + public T processor(TProcessor processor) { + this.processorFactory = new TProcessorFactory(processor); + return (T) this; + } + + public T transportFactory(TTransportFactory factory) { + this.inputTransportFactory = factory; + this.outputTransportFactory = factory; + return (T) this; + } + + public T inputTransportFactory(TTransportFactory factory) { + this.inputTransportFactory = factory; + return (T) this; + } + + public T outputTransportFactory(TTransportFactory factory) { + this.outputTransportFactory = factory; + return (T) this; + } + + public T protocolFactory(TProtocolFactory factory) { + this.inputProtocolFactory = factory; + this.outputProtocolFactory = factory; + return (T) this; + } + + public T inputProtocolFactory(TProtocolFactory factory) { + this.inputProtocolFactory = factory; + return (T) this; + } + + public T outputProtocolFactory(TProtocolFactory factory) { + this.outputProtocolFactory = factory; + return (T) this; + } + } + + /** + * Core processor + */ + protected TProcessorFactory processorFactory_; + + /** + * Server transport + */ + protected TServerTransport serverTransport_; + + /** + * Input Transport Factory + */ + protected TTransportFactory inputTransportFactory_; + + /** + * Output Transport Factory + */ + protected TTransportFactory outputTransportFactory_; + + /** + * Input Protocol Factory + */ + protected TProtocolFactory inputProtocolFactory_; + + /** + * Output Protocol Factory + */ + protected TProtocolFactory outputProtocolFactory_; + + private volatile boolean isServing; + + protected TServerEventHandler eventHandler_; + + // Flag for stopping the server + // Please see THRIFT-1795 for the usage of this flag + protected volatile boolean stopped_ = false; + + protected TServer(AbstractServerArgs args) { + processorFactory_ = args.processorFactory; + serverTransport_ = args.serverTransport; + inputTransportFactory_ = args.inputTransportFactory; + outputTransportFactory_ = args.outputTransportFactory; + inputProtocolFactory_ = args.inputProtocolFactory; + outputProtocolFactory_ = args.outputProtocolFactory; + } + + /** + * The run method fires up the server and gets things going. + */ + public abstract void serve(); + + /** + * Stop the server. This is optional on a per-implementation basis. Not + * all servers are required to be cleanly stoppable. + */ + public void stop() {} + + public boolean isServing() { + return isServing; + } + + protected void setServing(boolean serving) { + isServing = serving; + } + + public void setServerEventHandler(TServerEventHandler eventHandler) { + eventHandler_ = eventHandler; + } + + public TServerEventHandler getEventHandler() { + return eventHandler_; + } + + public boolean getShouldStop() { + return this.stopped_; + } + + public void setShouldStop(boolean shouldStop) { + this.stopped_ = shouldStop; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TServerEventHandler.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TServerEventHandler.java new file mode 100644 index 000000000..f069b9bfb --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TServerEventHandler.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.server; + +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.transport.TTransport; + +/** + * Interface that can handle events from the server core. To + * use this you should subclass it and implement the methods that you care + * about. Your subclass can also store local data that you may care about, + * such as additional "arguments" to these methods (stored in the object + * instance's state). + */ +public interface TServerEventHandler { + + /** + * Called before the server begins. + */ + void preServe(); + + /** + * Called when a new client has connected and is about to being processing. + */ + ServerContext createContext(TProtocol input, + TProtocol output); + + /** + * Called when a client has finished request-handling to delete server + * context. + */ + void deleteContext(ServerContext serverContext, + TProtocol input, + TProtocol output); + + /** + * Called when a client is about to call the processor. + */ + void processContext(ServerContext serverContext, + TTransport inputTransport, TTransport outputTransport); + +}
\ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TServlet.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TServlet.java new file mode 100644 index 000000000..c1ab9df55 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TServlet.java @@ -0,0 +1,119 @@ +package org.apache.thrift.server; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.thrift.TException; +import org.apache.thrift.TProcessor; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.protocol.TProtocolFactory; +import org.apache.thrift.transport.TIOStreamTransport; +import org.apache.thrift.transport.TTransport; + +/** + * Servlet implementation class ThriftServer + */ +public class TServlet extends HttpServlet { + + private final TProcessor processor; + + private final TProtocolFactory inProtocolFactory; + + private final TProtocolFactory outProtocolFactory; + + private final Collection<Map.Entry<String, String>> customHeaders; + + /** + * @see HttpServlet#HttpServlet() + */ + public TServlet(TProcessor processor, TProtocolFactory inProtocolFactory, + TProtocolFactory outProtocolFactory) { + super(); + this.processor = processor; + this.inProtocolFactory = inProtocolFactory; + this.outProtocolFactory = outProtocolFactory; + this.customHeaders = new ArrayList<Map.Entry<String, String>>(); + } + + /** + * @see HttpServlet#HttpServlet() + */ + public TServlet(TProcessor processor, TProtocolFactory protocolFactory) { + this(processor, protocolFactory, protocolFactory); + } + + /** + * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse + * response) + */ + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + TTransport inTransport = null; + TTransport outTransport = null; + + try { + response.setContentType("application/x-thrift"); + + if (null != this.customHeaders) { + for (Map.Entry<String, String> header : this.customHeaders) { + response.addHeader(header.getKey(), header.getValue()); + } + } + InputStream in = request.getInputStream(); + OutputStream out = response.getOutputStream(); + + TTransport transport = new TIOStreamTransport(in, out); + inTransport = transport; + outTransport = transport; + + TProtocol inProtocol = inProtocolFactory.getProtocol(inTransport); + TProtocol outProtocol = outProtocolFactory.getProtocol(outTransport); + + processor.process(inProtocol, outProtocol); + out.flush(); + } catch (TException te) { + throw new ServletException(te); + } + } + + /** + * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse + * response) + */ + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + doPost(request, response); + } + + public void addCustomHeader(final String key, final String value) { + this.customHeaders.add(new Map.Entry<String, String>() { + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + public String setValue(String value) { + return null; + } + }); + } + + public void setCustomHeaders(Collection<Map.Entry<String, String>> headers) { + this.customHeaders.clear(); + this.customHeaders.addAll(headers); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TSimpleServer.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TSimpleServer.java new file mode 100644 index 000000000..13501efc6 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TSimpleServer.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.server; + +import org.apache.thrift.TException; +import org.apache.thrift.TProcessor; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.transport.TTransport; +import org.apache.thrift.transport.TTransportException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Simple singlethreaded server for testing. + * + */ +public class TSimpleServer extends TServer { + + private static final Logger LOGGER = LoggerFactory.getLogger(TSimpleServer.class.getName()); + + public TSimpleServer(AbstractServerArgs args) { + super(args); + } + + public void serve() { + try { + serverTransport_.listen(); + } catch (TTransportException ttx) { + LOGGER.error("Error occurred during listening.", ttx); + return; + } + + // Run the preServe event + if (eventHandler_ != null) { + eventHandler_.preServe(); + } + + setServing(true); + + while (!stopped_) { + TTransport client = null; + TProcessor processor = null; + TTransport inputTransport = null; + TTransport outputTransport = null; + TProtocol inputProtocol = null; + TProtocol outputProtocol = null; + ServerContext connectionContext = null; + try { + client = serverTransport_.accept(); + if (client != null) { + processor = processorFactory_.getProcessor(client); + inputTransport = inputTransportFactory_.getTransport(client); + outputTransport = outputTransportFactory_.getTransport(client); + inputProtocol = inputProtocolFactory_.getProtocol(inputTransport); + outputProtocol = outputProtocolFactory_.getProtocol(outputTransport); + if (eventHandler_ != null) { + connectionContext = eventHandler_.createContext(inputProtocol, outputProtocol); + } + while (true) { + if (eventHandler_ != null) { + eventHandler_.processContext(connectionContext, inputTransport, outputTransport); + } + processor.process(inputProtocol, outputProtocol); + } + } + } catch (TTransportException ttx) { + // Client died, just move on + } catch (TException tx) { + if (!stopped_) { + LOGGER.error("Thrift error occurred during processing of message.", tx); + } + } catch (Exception x) { + if (!stopped_) { + LOGGER.error("Error occurred during processing of message.", x); + } + } + + if (eventHandler_ != null) { + eventHandler_.deleteContext(connectionContext, inputProtocol, outputProtocol); + } + + if (inputTransport != null) { + inputTransport.close(); + } + + if (outputTransport != null) { + outputTransport.close(); + } + + } + setServing(false); + } + + public void stop() { + stopped_ = true; + serverTransport_.interrupt(); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TThreadPoolServer.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TThreadPoolServer.java new file mode 100644 index 000000000..87e873381 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TThreadPoolServer.java @@ -0,0 +1,359 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.server; + +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.apache.thrift.TException; +import org.apache.thrift.TProcessor; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.transport.TServerTransport; +import org.apache.thrift.transport.TTransport; +import org.apache.thrift.transport.TTransportException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Server which uses Java's built in ThreadPool management to spawn off + * a worker pool that + * + */ +public class TThreadPoolServer extends TServer { + private static final Logger LOGGER = LoggerFactory.getLogger(TThreadPoolServer.class.getName()); + + public static class Args extends AbstractServerArgs<Args> { + public int minWorkerThreads = 5; + public int maxWorkerThreads = Integer.MAX_VALUE; + public ExecutorService executorService; + public int stopTimeoutVal = 60; + public TimeUnit stopTimeoutUnit = TimeUnit.SECONDS; + public int requestTimeout = 20; + public TimeUnit requestTimeoutUnit = TimeUnit.SECONDS; + public int beBackoffSlotLength = 100; + public TimeUnit beBackoffSlotLengthUnit = TimeUnit.MILLISECONDS; + + public Args(TServerTransport transport) { + super(transport); + } + + public Args minWorkerThreads(int n) { + minWorkerThreads = n; + return this; + } + + public Args maxWorkerThreads(int n) { + maxWorkerThreads = n; + return this; + } + + public Args stopTimeoutVal(int n) { + stopTimeoutVal = n; + return this; + } + + public Args stopTimeoutUnit(TimeUnit tu) { + stopTimeoutUnit = tu; + return this; + } + + public Args requestTimeout(int n) { + requestTimeout = n; + return this; + } + + public Args requestTimeoutUnit(TimeUnit tu) { + requestTimeoutUnit = tu; + return this; + } + //Binary exponential backoff slot length + public Args beBackoffSlotLength(int n) { + beBackoffSlotLength = n; + return this; + } + + //Binary exponential backoff slot time unit + public Args beBackoffSlotLengthUnit(TimeUnit tu) { + beBackoffSlotLengthUnit = tu; + return this; + } + + public Args executorService(ExecutorService executorService) { + this.executorService = executorService; + return this; + } + } + + // Executor service for handling client connections + private ExecutorService executorService_; + + private final TimeUnit stopTimeoutUnit; + + private final long stopTimeoutVal; + + private final TimeUnit requestTimeoutUnit; + + private final long requestTimeout; + + private final long beBackoffSlotInMillis; + + private Random random = new Random(System.currentTimeMillis()); + + public TThreadPoolServer(Args args) { + super(args); + + stopTimeoutUnit = args.stopTimeoutUnit; + stopTimeoutVal = args.stopTimeoutVal; + requestTimeoutUnit = args.requestTimeoutUnit; + requestTimeout = args.requestTimeout; + beBackoffSlotInMillis = args.beBackoffSlotLengthUnit.toMillis(args.beBackoffSlotLength); + + executorService_ = args.executorService != null ? + args.executorService : createDefaultExecutorService(args); + } + + private static ExecutorService createDefaultExecutorService(Args args) { + SynchronousQueue<Runnable> executorQueue = + new SynchronousQueue<Runnable>(); + return new ThreadPoolExecutor(args.minWorkerThreads, + args.maxWorkerThreads, + args.stopTimeoutVal, + args.stopTimeoutUnit, + executorQueue); + } + + protected ExecutorService getExecutorService() { + return executorService_; + } + + protected boolean preServe() { + try { + serverTransport_.listen(); + } catch (TTransportException ttx) { + LOGGER.error("Error occurred during listening.", ttx); + return false; + } + + // Run the preServe event + if (eventHandler_ != null) { + eventHandler_.preServe(); + } + stopped_ = false; + setServing(true); + + return true; + } + + public void serve() { + if (!preServe()) { + return; + } + + execute(); + waitForShutdown(); + + setServing(false); + } + + protected void execute() { + int failureCount = 0; + while (!stopped_) { + try { + TTransport client = serverTransport_.accept(); + WorkerProcess wp = new WorkerProcess(client); + + int retryCount = 0; + long remainTimeInMillis = requestTimeoutUnit.toMillis(requestTimeout); + while(true) { + try { + executorService_.execute(wp); + break; + } catch(Throwable t) { + if (t instanceof RejectedExecutionException) { + retryCount++; + try { + if (remainTimeInMillis > 0) { + //do a truncated 20 binary exponential backoff sleep + long sleepTimeInMillis = ((long) (random.nextDouble() * + (1L << Math.min(retryCount, 20)))) * beBackoffSlotInMillis; + sleepTimeInMillis = Math.min(sleepTimeInMillis, remainTimeInMillis); + TimeUnit.MILLISECONDS.sleep(sleepTimeInMillis); + remainTimeInMillis = remainTimeInMillis - sleepTimeInMillis; + } else { + client.close(); + wp = null; + LOGGER.warn("Task has been rejected by ExecutorService " + retryCount + + " times till timedout, reason: " + t); + break; + } + } catch (InterruptedException e) { + LOGGER.warn("Interrupted while waiting to place client on executor queue."); + Thread.currentThread().interrupt(); + break; + } + } else if (t instanceof Error) { + LOGGER.error("ExecutorService threw error: " + t, t); + throw (Error)t; + } else { + //for other possible runtime errors from ExecutorService, should also not kill serve + LOGGER.warn("ExecutorService threw error: " + t, t); + break; + } + } + } + } catch (TTransportException ttx) { + if (!stopped_) { + ++failureCount; + LOGGER.warn("Transport error occurred during acceptance of message.", ttx); + } + } + } + } + + protected void waitForShutdown() { + executorService_.shutdown(); + + // Loop until awaitTermination finally does return without a interrupted + // exception. If we don't do this, then we'll shut down prematurely. We want + // to let the executorService clear it's task queue, closing client sockets + // appropriately. + long timeoutMS = stopTimeoutUnit.toMillis(stopTimeoutVal); + long now = System.currentTimeMillis(); + while (timeoutMS >= 0) { + try { + executorService_.awaitTermination(timeoutMS, TimeUnit.MILLISECONDS); + break; + } catch (InterruptedException ix) { + long newnow = System.currentTimeMillis(); + timeoutMS -= (newnow - now); + now = newnow; + } + } + } + + public void stop() { + stopped_ = true; + serverTransport_.interrupt(); + } + + private class WorkerProcess implements Runnable { + + /** + * Client that this services. + */ + private TTransport client_; + + /** + * Default constructor. + * + * @param client Transport to process + */ + private WorkerProcess(TTransport client) { + client_ = client; + } + + /** + * Loops on processing a client forever + */ + public void run() { + TProcessor processor = null; + TTransport inputTransport = null; + TTransport outputTransport = null; + TProtocol inputProtocol = null; + TProtocol outputProtocol = null; + + TServerEventHandler eventHandler = null; + ServerContext connectionContext = null; + + try { + processor = processorFactory_.getProcessor(client_); + inputTransport = inputTransportFactory_.getTransport(client_); + outputTransport = outputTransportFactory_.getTransport(client_); + inputProtocol = inputProtocolFactory_.getProtocol(inputTransport); + outputProtocol = outputProtocolFactory_.getProtocol(outputTransport); + + eventHandler = getEventHandler(); + if (eventHandler != null) { + connectionContext = eventHandler.createContext(inputProtocol, outputProtocol); + } + // we check stopped_ first to make sure we're not supposed to be shutting + // down. this is necessary for graceful shutdown. + while (true) { + + if (eventHandler != null) { + eventHandler.processContext(connectionContext, inputTransport, outputTransport); + } + + if (stopped_) { + break; + } + processor.process(inputProtocol, outputProtocol); + } + } catch (Exception x) { + // We'll usually receive RuntimeException types here + // Need to unwrap to ascertain real causing exception before we choose to ignore + // Ignore err-logging all transport-level/type exceptions + if (!isIgnorableException(x)) { + // Log the exception at error level and continue + LOGGER.error((x instanceof TException? "Thrift " : "") + "Error occurred during processing of message.", x); + } + } finally { + if (eventHandler != null) { + eventHandler.deleteContext(connectionContext, inputProtocol, outputProtocol); + } + if (inputTransport != null) { + inputTransport.close(); + } + if (outputTransport != null) { + outputTransport.close(); + } + if (client_.isOpen()) { + client_.close(); + } + } + } + + private boolean isIgnorableException(Exception x) { + TTransportException tTransportException = null; + + if (x instanceof TTransportException) { + tTransportException = (TTransportException)x; + } + else if (x.getCause() instanceof TTransportException) { + tTransportException = (TTransportException)x.getCause(); + } + + if (tTransportException != null) { + switch(tTransportException.getType()) { + case TTransportException.END_OF_FILE: + case TTransportException.TIMED_OUT: + return true; + } + } + return false; + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TThreadedSelectorServer.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TThreadedSelectorServer.java new file mode 100644 index 000000000..038507e9c --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/server/TThreadedSelectorServer.java @@ -0,0 +1,744 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.server; + +import org.apache.thrift.transport.TNonblockingServerTransport; +import org.apache.thrift.transport.TNonblockingTransport; +import org.apache.thrift.transport.TTransportException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.spi.SelectorProvider; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; + +/** + * A Half-Sync/Half-Async server with a separate pool of threads to handle + * non-blocking I/O. Accepts are handled on a single thread, and a configurable + * number of nonblocking selector threads manage reading and writing of client + * connections. A synchronous worker thread pool handles processing of requests. + * + * Performs better than TNonblockingServer/THsHaServer in multi-core + * environments when the the bottleneck is CPU on the single selector thread + * handling I/O. In addition, because the accept handling is decoupled from + * reads/writes and invocation, the server has better ability to handle back- + * pressure from new connections (e.g. stop accepting when busy). + * + * Like TNonblockingServer, it relies on the use of TFramedTransport. + */ +public class TThreadedSelectorServer extends AbstractNonblockingServer { + private static final Logger LOGGER = LoggerFactory.getLogger(TThreadedSelectorServer.class.getName()); + + public static class Args extends AbstractNonblockingServerArgs<Args> { + + /** The number of threads for selecting on already-accepted connections */ + public int selectorThreads = 2; + /** + * The size of the executor service (if none is specified) that will handle + * invocations. This may be set to 0, in which case invocations will be + * handled directly on the selector threads (as is in TNonblockingServer) + */ + private int workerThreads = 5; + /** Time to wait for server to stop gracefully */ + private int stopTimeoutVal = 60; + private TimeUnit stopTimeoutUnit = TimeUnit.SECONDS; + /** The ExecutorService for handling dispatched requests */ + private ExecutorService executorService = null; + /** + * The size of the blocking queue per selector thread for passing accepted + * connections to the selector thread + */ + private int acceptQueueSizePerThread = 4; + + /** + * Determines the strategy for handling new accepted connections. + */ + public static enum AcceptPolicy { + /** + * Require accepted connection registration to be handled by the executor. + * If the worker pool is saturated, further accepts will be closed + * immediately. Slightly increases latency due to an extra scheduling. + */ + FAIR_ACCEPT, + /** + * Handle the accepts as fast as possible, disregarding the status of the + * executor service. + */ + FAST_ACCEPT + } + + private AcceptPolicy acceptPolicy = AcceptPolicy.FAST_ACCEPT; + + public Args(TNonblockingServerTransport transport) { + super(transport); + } + + public Args selectorThreads(int i) { + selectorThreads = i; + return this; + } + + public int getSelectorThreads() { + return selectorThreads; + } + + public Args workerThreads(int i) { + workerThreads = i; + return this; + } + + public int getWorkerThreads() { + return workerThreads; + } + + public int getStopTimeoutVal() { + return stopTimeoutVal; + } + + public Args stopTimeoutVal(int stopTimeoutVal) { + this.stopTimeoutVal = stopTimeoutVal; + return this; + } + + public TimeUnit getStopTimeoutUnit() { + return stopTimeoutUnit; + } + + public Args stopTimeoutUnit(TimeUnit stopTimeoutUnit) { + this.stopTimeoutUnit = stopTimeoutUnit; + return this; + } + + public ExecutorService getExecutorService() { + return executorService; + } + + public Args executorService(ExecutorService executorService) { + this.executorService = executorService; + return this; + } + + public int getAcceptQueueSizePerThread() { + return acceptQueueSizePerThread; + } + + public Args acceptQueueSizePerThread(int acceptQueueSizePerThread) { + this.acceptQueueSizePerThread = acceptQueueSizePerThread; + return this; + } + + public AcceptPolicy getAcceptPolicy() { + return acceptPolicy; + } + + public Args acceptPolicy(AcceptPolicy acceptPolicy) { + this.acceptPolicy = acceptPolicy; + return this; + } + + public void validate() { + if (selectorThreads <= 0) { + throw new IllegalArgumentException("selectorThreads must be positive."); + } + if (workerThreads < 0) { + throw new IllegalArgumentException("workerThreads must be non-negative."); + } + if (acceptQueueSizePerThread <= 0) { + throw new IllegalArgumentException("acceptQueueSizePerThread must be positive."); + } + } + } + + // The thread handling all accepts + private AcceptThread acceptThread; + + // Threads handling events on client transports + private final Set<SelectorThread> selectorThreads = new HashSet<SelectorThread>(); + + // This wraps all the functionality of queueing and thread pool management + // for the passing of Invocations from the selector thread(s) to the workers + // (if any). + private final ExecutorService invoker; + + private final Args args; + + /** + * Create the server with the specified Args configuration + */ + public TThreadedSelectorServer(Args args) { + super(args); + args.validate(); + invoker = args.executorService == null ? createDefaultExecutor(args) : args.executorService; + this.args = args; + } + + /** + * Start the accept and selector threads running to deal with clients. + * + * @return true if everything went ok, false if we couldn't start for some + * reason. + */ + @Override + protected boolean startThreads() { + try { + for (int i = 0; i < args.selectorThreads; ++i) { + selectorThreads.add(new SelectorThread(args.acceptQueueSizePerThread)); + } + acceptThread = new AcceptThread((TNonblockingServerTransport) serverTransport_, + createSelectorThreadLoadBalancer(selectorThreads)); + for (SelectorThread thread : selectorThreads) { + thread.start(); + } + acceptThread.start(); + return true; + } catch (IOException e) { + LOGGER.error("Failed to start threads!", e); + return false; + } + } + + /** + * Joins the accept and selector threads and shuts down the executor service. + */ + @Override + protected void waitForShutdown() { + try { + joinThreads(); + } catch (InterruptedException e) { + // Non-graceful shutdown occurred + LOGGER.error("Interrupted while joining threads!", e); + } + gracefullyShutdownInvokerPool(); + } + + protected void joinThreads() throws InterruptedException { + // wait until the io threads exit + acceptThread.join(); + for (SelectorThread thread : selectorThreads) { + thread.join(); + } + } + + /** + * Stop serving and shut everything down. + */ + @Override + public void stop() { + stopped_ = true; + + // Stop queuing connect attempts asap + stopListening(); + + if (acceptThread != null) { + acceptThread.wakeupSelector(); + } + if (selectorThreads != null) { + for (SelectorThread thread : selectorThreads) { + if (thread != null) + thread.wakeupSelector(); + } + } + } + + protected void gracefullyShutdownInvokerPool() { + // try to gracefully shut down the executor service + invoker.shutdown(); + + // Loop until awaitTermination finally does return without a interrupted + // exception. If we don't do this, then we'll shut down prematurely. We want + // to let the executorService clear it's task queue, closing client sockets + // appropriately. + long timeoutMS = args.stopTimeoutUnit.toMillis(args.stopTimeoutVal); + long now = System.currentTimeMillis(); + while (timeoutMS >= 0) { + try { + invoker.awaitTermination(timeoutMS, TimeUnit.MILLISECONDS); + break; + } catch (InterruptedException ix) { + long newnow = System.currentTimeMillis(); + timeoutMS -= (newnow - now); + now = newnow; + } + } + } + + /** + * We override the standard invoke method here to queue the invocation for + * invoker service instead of immediately invoking. If there is no thread + * pool, handle the invocation inline on this thread + */ + @Override + protected boolean requestInvoke(FrameBuffer frameBuffer) { + Runnable invocation = getRunnable(frameBuffer); + if (invoker != null) { + try { + invoker.execute(invocation); + return true; + } catch (RejectedExecutionException rx) { + LOGGER.warn("ExecutorService rejected execution!", rx); + return false; + } + } else { + // Invoke on the caller's thread + invocation.run(); + return true; + } + } + + protected Runnable getRunnable(FrameBuffer frameBuffer) { + return new Invocation(frameBuffer); + } + + /** + * Helper to create the invoker if one is not specified + */ + protected static ExecutorService createDefaultExecutor(Args options) { + return (options.workerThreads > 0) ? Executors.newFixedThreadPool(options.workerThreads) : null; + } + + private static BlockingQueue<TNonblockingTransport> createDefaultAcceptQueue(int queueSize) { + if (queueSize == 0) { + // Unbounded queue + return new LinkedBlockingQueue<TNonblockingTransport>(); + } + return new ArrayBlockingQueue<TNonblockingTransport>(queueSize); + } + + /** + * The thread that selects on the server transport (listen socket) and accepts + * new connections to hand off to the IO selector threads + */ + protected class AcceptThread extends Thread { + + // The listen socket to accept on + private final TNonblockingServerTransport serverTransport; + private final Selector acceptSelector; + + private final SelectorThreadLoadBalancer threadChooser; + + /** + * Set up the AcceptThead + * + * @throws IOException + */ + public AcceptThread(TNonblockingServerTransport serverTransport, + SelectorThreadLoadBalancer threadChooser) throws IOException { + this.serverTransport = serverTransport; + this.threadChooser = threadChooser; + this.acceptSelector = SelectorProvider.provider().openSelector(); + this.serverTransport.registerSelector(acceptSelector); + } + + /** + * The work loop. Selects on the server transport and accepts. If there was + * a server transport that had blocking accepts, and returned on blocking + * client transports, that should be used instead + */ + public void run() { + try { + if (eventHandler_ != null) { + eventHandler_.preServe(); + } + + while (!stopped_) { + select(); + } + } catch (Throwable t) { + LOGGER.error("run() on AcceptThread exiting due to uncaught error", t); + } finally { + try { + acceptSelector.close(); + } catch (IOException e) { + LOGGER.error("Got an IOException while closing accept selector!", e); + } + // This will wake up the selector threads + TThreadedSelectorServer.this.stop(); + } + } + + /** + * If the selector is blocked, wake it up. + */ + public void wakeupSelector() { + acceptSelector.wakeup(); + } + + /** + * Select and process IO events appropriately: If there are connections to + * be accepted, accept them. + */ + private void select() { + try { + // wait for connect events. + acceptSelector.select(); + + // process the io events we received + Iterator<SelectionKey> selectedKeys = acceptSelector.selectedKeys().iterator(); + while (!stopped_ && selectedKeys.hasNext()) { + SelectionKey key = selectedKeys.next(); + selectedKeys.remove(); + + // skip if not valid + if (!key.isValid()) { + continue; + } + + if (key.isAcceptable()) { + handleAccept(); + } else { + LOGGER.warn("Unexpected state in select! " + key.interestOps()); + } + } + } catch (IOException e) { + LOGGER.warn("Got an IOException while selecting!", e); + } + } + + /** + * Accept a new connection. + */ + private void handleAccept() { + final TNonblockingTransport client = doAccept(); + if (client != null) { + // Pass this connection to a selector thread + final SelectorThread targetThread = threadChooser.nextThread(); + + if (args.acceptPolicy == Args.AcceptPolicy.FAST_ACCEPT || invoker == null) { + doAddAccept(targetThread, client); + } else { + // FAIR_ACCEPT + try { + invoker.submit(new Runnable() { + public void run() { + doAddAccept(targetThread, client); + } + }); + } catch (RejectedExecutionException rx) { + LOGGER.warn("ExecutorService rejected accept registration!", rx); + // close immediately + client.close(); + } + } + } + } + + private TNonblockingTransport doAccept() { + try { + return (TNonblockingTransport) serverTransport.accept(); + } catch (TTransportException tte) { + // something went wrong accepting. + LOGGER.warn("Exception trying to accept!", tte); + return null; + } + } + + private void doAddAccept(SelectorThread thread, TNonblockingTransport client) { + if (!thread.addAcceptedConnection(client)) { + client.close(); + } + } + } // AcceptThread + + /** + * The SelectorThread(s) will be doing all the selecting on accepted active + * connections. + */ + protected class SelectorThread extends AbstractSelectThread { + + // Accepted connections added by the accept thread. + private final BlockingQueue<TNonblockingTransport> acceptedQueue; + private int SELECTOR_AUTO_REBUILD_THRESHOLD = 512; + private long MONITOR_PERIOD = 1000L; + private int jvmBug = 0; + + /** + * Set up the SelectorThread with an unbounded queue for incoming accepts. + * + * @throws IOException + * if a selector cannot be created + */ + public SelectorThread() throws IOException { + this(new LinkedBlockingQueue<TNonblockingTransport>()); + } + + /** + * Set up the SelectorThread with an bounded queue for incoming accepts. + * + * @throws IOException + * if a selector cannot be created + */ + public SelectorThread(int maxPendingAccepts) throws IOException { + this(createDefaultAcceptQueue(maxPendingAccepts)); + } + + /** + * Set up the SelectorThread with a specified queue for connections. + * + * @param acceptedQueue + * The BlockingQueue implementation for holding incoming accepted + * connections. + * @throws IOException + * if a selector cannot be created. + */ + public SelectorThread(BlockingQueue<TNonblockingTransport> acceptedQueue) throws IOException { + this.acceptedQueue = acceptedQueue; + } + + /** + * Hands off an accepted connection to be handled by this thread. This + * method will block if the queue for new connections is at capacity. + * + * @param accepted + * The connection that has been accepted. + * @return true if the connection has been successfully added. + */ + public boolean addAcceptedConnection(TNonblockingTransport accepted) { + try { + acceptedQueue.put(accepted); + } catch (InterruptedException e) { + LOGGER.warn("Interrupted while adding accepted connection!", e); + return false; + } + selector.wakeup(); + return true; + } + + /** + * The work loop. Handles selecting (read/write IO), dispatching, and + * managing the selection preferences of all existing connections. + */ + public void run() { + try { + while (!stopped_) { + select(); + processAcceptedConnections(); + processInterestChanges(); + } + for (SelectionKey selectionKey : selector.keys()) { + cleanupSelectionKey(selectionKey); + } + } catch (Throwable t) { + LOGGER.error("run() on SelectorThread exiting due to uncaught error", t); + } finally { + try { + selector.close(); + } catch (IOException e) { + LOGGER.error("Got an IOException while closing selector!", e); + } + // This will wake up the accept thread and the other selector threads + TThreadedSelectorServer.this.stop(); + } + } + + /** + * Select and process IO events appropriately: If there are existing + * connections with data waiting to be read, read it, buffering until a + * whole frame has been read. If there are any pending responses, buffer + * them until their target client is available, and then send the data. + */ + private void select() { + try { + + doSelect(); + + // process the io events we received + Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator(); + while (!stopped_ && selectedKeys.hasNext()) { + SelectionKey key = selectedKeys.next(); + selectedKeys.remove(); + + // skip if not valid + if (!key.isValid()) { + cleanupSelectionKey(key); + continue; + } + + if (key.isReadable()) { + // deal with reads + handleRead(key); + } else if (key.isWritable()) { + // deal with writes + handleWrite(key); + } else { + LOGGER.warn("Unexpected state in select! " + key.interestOps()); + } + } + } catch (IOException e) { + LOGGER.warn("Got an IOException while selecting!", e); + } + } + + /** + * Do select and judge epoll bug happen. + * See : https://issues.apache.org/jira/browse/THRIFT-4251 + */ + private void doSelect() throws IOException { + long beforeSelect = System.currentTimeMillis(); + int selectedNums = selector.select(); + long afterSelect = System.currentTimeMillis(); + + if (selectedNums == 0) { + jvmBug++; + } else { + jvmBug = 0; + } + + long selectedTime = afterSelect - beforeSelect; + if (selectedTime >= MONITOR_PERIOD) { + jvmBug = 0; + } else if (jvmBug > SELECTOR_AUTO_REBUILD_THRESHOLD) { + LOGGER.warn("In {} ms happen {} times jvm bug; rebuilding selector.", MONITOR_PERIOD, jvmBug); + rebuildSelector(); + selector.selectNow(); + jvmBug = 0; + } + + } + + /** + * Replaces the current Selector of this SelectorThread with newly created Selector to work + * around the infamous epoll 100% CPU bug. + */ + private synchronized void rebuildSelector() { + final Selector oldSelector = selector; + if (oldSelector == null) { + return; + } + Selector newSelector = null; + try { + newSelector = Selector.open(); + LOGGER.warn("Created new Selector."); + } catch (IOException e) { + LOGGER.error("Create new Selector error.", e); + } + + for (SelectionKey key : oldSelector.selectedKeys()) { + if (!key.isValid() && key.readyOps() == 0) + continue; + SelectableChannel channel = key.channel(); + Object attachment = key.attachment(); + + try { + if (attachment == null) { + channel.register(newSelector, key.readyOps()); + } else { + channel.register(newSelector, key.readyOps(), attachment); + } + } catch (ClosedChannelException e) { + LOGGER.error("Register new selector key error.", e); + } + + } + + selector = newSelector; + try { + oldSelector.close(); + } catch (IOException e) { + LOGGER.error("Close old selector error.", e); + } + LOGGER.warn("Replace new selector success."); + } + + private void processAcceptedConnections() { + // Register accepted connections + while (!stopped_) { + TNonblockingTransport accepted = acceptedQueue.poll(); + if (accepted == null) { + break; + } + registerAccepted(accepted); + } + } + + protected FrameBuffer createFrameBuffer(final TNonblockingTransport trans, + final SelectionKey selectionKey, + final AbstractSelectThread selectThread) { + return processorFactory_.isAsyncProcessor() ? + new AsyncFrameBuffer(trans, selectionKey, selectThread) : + new FrameBuffer(trans, selectionKey, selectThread); + } + + private void registerAccepted(TNonblockingTransport accepted) { + SelectionKey clientKey = null; + try { + clientKey = accepted.registerSelector(selector, SelectionKey.OP_READ); + + FrameBuffer frameBuffer = createFrameBuffer(accepted, clientKey, SelectorThread.this); + + clientKey.attach(frameBuffer); + } catch (IOException e) { + LOGGER.warn("Failed to register accepted connection to selector!", e); + if (clientKey != null) { + cleanupSelectionKey(clientKey); + } + accepted.close(); + } + } + } // SelectorThread + + /** + * Creates a SelectorThreadLoadBalancer to be used by the accept thread for + * assigning newly accepted connections across the threads. + */ + protected SelectorThreadLoadBalancer createSelectorThreadLoadBalancer(Collection<? extends SelectorThread> threads) { + return new SelectorThreadLoadBalancer(threads); + } + + /** + * A round robin load balancer for choosing selector threads for new + * connections. + */ + protected static class SelectorThreadLoadBalancer { + private final Collection<? extends SelectorThread> threads; + private Iterator<? extends SelectorThread> nextThreadIterator; + + public <T extends SelectorThread> SelectorThreadLoadBalancer(Collection<T> threads) { + if (threads.isEmpty()) { + throw new IllegalArgumentException("At least one selector thread is required"); + } + this.threads = Collections.unmodifiableList(new ArrayList<T>(threads)); + nextThreadIterator = this.threads.iterator(); + } + + public SelectorThread nextThread() { + // Choose a selector thread (round robin) + if (!nextThreadIterator.hasNext()) { + nextThreadIterator = threads.iterator(); + } + return nextThreadIterator.next(); + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/AutoExpandingBuffer.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/AutoExpandingBuffer.java new file mode 100644 index 000000000..fc3aa92df --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/AutoExpandingBuffer.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.util.Arrays; + +/** + * Helper class that wraps a byte[] so that it can expand and be reused. Users + * should call resizeIfNecessary to make sure the buffer has suitable capacity, + * and then use the array as needed. Note that the internal array will grow at a + * rate slightly faster than the requested capacity with the (untested) + * objective of avoiding expensive buffer allocations and copies. + */ +class AutoExpandingBuffer { + private byte[] array; + + public AutoExpandingBuffer(int initialCapacity) { + this.array = new byte[initialCapacity]; + } + + public void resizeIfNecessary(int size) { + final int currentCapacity = this.array.length; + if (currentCapacity < size) { + // Increase by a factor of 1.5x + int growCapacity = currentCapacity + (currentCapacity >> 1); + int newCapacity = Math.max(growCapacity, size); + this.array = Arrays.copyOf(array, newCapacity); + } + } + + public byte[] array() { + return this.array; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/AutoExpandingBufferReadTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/AutoExpandingBufferReadTransport.java new file mode 100644 index 000000000..a28d25485 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/AutoExpandingBufferReadTransport.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +/** + * TTransport for reading from an AutoExpandingBuffer. + */ +public class AutoExpandingBufferReadTransport extends TTransport { + + private final AutoExpandingBuffer buf; + + private int pos = 0; + private int limit = 0; + + public AutoExpandingBufferReadTransport(int initialCapacity) { + this.buf = new AutoExpandingBuffer(initialCapacity); + } + + public void fill(TTransport inTrans, int length) throws TTransportException { + buf.resizeIfNecessary(length); + inTrans.readAll(buf.array(), 0, length); + pos = 0; + limit = length; + } + + @Override + public void close() {} + + @Override + public boolean isOpen() { return true; } + + @Override + public void open() throws TTransportException {} + + @Override + public final int read(byte[] target, int off, int len) throws TTransportException { + int amtToRead = Math.min(len, getBytesRemainingInBuffer()); + System.arraycopy(buf.array(), pos, target, off, amtToRead); + consumeBuffer(amtToRead); + return amtToRead; + } + + @Override + public void write(byte[] buf, int off, int len) throws TTransportException { + throw new UnsupportedOperationException(); + } + + @Override + public final void consumeBuffer(int len) { + pos += len; + } + + @Override + public final byte[] getBuffer() { + return buf.array(); + } + + @Override + public final int getBufferPosition() { + return pos; + } + + @Override + public final int getBytesRemainingInBuffer() { + return limit - pos; + } +} +
\ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/AutoExpandingBufferWriteTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/AutoExpandingBufferWriteTransport.java new file mode 100644 index 000000000..ec7e7d45a --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/AutoExpandingBufferWriteTransport.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +/** + * TTransport for writing to an AutoExpandingBuffer. + */ +public final class AutoExpandingBufferWriteTransport extends TTransport { + + private final AutoExpandingBuffer buf; + private int pos; + private int res; + + /** + * Constructor. + * @param initialCapacity the initial capacity of the buffer + * @param frontReserve space, if any, to reserve at the beginning such + * that the first write is after this reserve. + * This allows framed transport to reserve space + * for the frame buffer length. + * @throws IllegalArgumentException if initialCapacity is less than one + * @throws IllegalArgumentException if frontReserve is less than zero + * @throws IllegalArgumentException if frontReserve is greater than initialCapacity + */ + public AutoExpandingBufferWriteTransport(int initialCapacity, int frontReserve) { + if (initialCapacity < 1) { + throw new IllegalArgumentException("initialCapacity"); + } + if (frontReserve < 0 || initialCapacity < frontReserve) { + throw new IllegalArgumentException("frontReserve"); + } + this.buf = new AutoExpandingBuffer(initialCapacity); + this.pos = frontReserve; + this.res = frontReserve; + } + + @Override + public void close() {} + + @Override + public boolean isOpen() {return true;} + + @Override + public void open() throws TTransportException {} + + @Override + public int read(byte[] buf, int off, int len) throws TTransportException { + throw new UnsupportedOperationException(); + } + + @Override + public void write(byte[] toWrite, int off, int len) throws TTransportException { + buf.resizeIfNecessary(pos + len); + System.arraycopy(toWrite, off, buf.array(), pos, len); + pos += len; + } + + public AutoExpandingBuffer getBuf() { + return buf; + } + + /** + * @return length of the buffer, including any front reserve + */ + public int getLength() { + return pos; + } + + public void reset() { + pos = res; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TByteBuffer.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TByteBuffer.java new file mode 100644 index 000000000..b6b065748 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TByteBuffer.java @@ -0,0 +1,87 @@ +package org.apache.thrift.transport; + +import java.nio.BufferOverflowException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; + +/** + * ByteBuffer-backed implementation of TTransport. + */ +public final class TByteBuffer extends TTransport { + private final ByteBuffer byteBuffer; + + /** + * Creates a new TByteBuffer wrapping a given NIO ByteBuffer. + */ + public TByteBuffer(ByteBuffer byteBuffer) { + this.byteBuffer = byteBuffer; + } + + @Override + public boolean isOpen() { + return true; + } + + @Override + public void open() { + } + + @Override + public void close() { + } + + @Override + public int read(byte[] buf, int off, int len) throws TTransportException { + final int n = Math.min(byteBuffer.remaining(), len); + if (n > 0) { + try { + byteBuffer.get(buf, off, n); + } catch (BufferUnderflowException e) { + throw new TTransportException("Unexpected end of input buffer", e); + } + } + return n; + } + + @Override + public void write(byte[] buf, int off, int len) throws TTransportException { + try { + byteBuffer.put(buf, off, len); + } catch (BufferOverflowException e) { + throw new TTransportException("Not enough room in output buffer", e); + } + } + + /** + * Get the underlying NIO ByteBuffer. + */ + public ByteBuffer getByteBuffer() { + return byteBuffer; + } + + /** + * Convenience method to call clear() on the underlying NIO ByteBuffer. + */ + public TByteBuffer clear() { + byteBuffer.clear(); + return this; + } + + /** + * Convenience method to call flip() on the underlying NIO ByteBuffer. + */ + public TByteBuffer flip() { + byteBuffer.flip(); + return this; + } + + /** + * Convenience method to convert the underlying NIO ByteBuffer to a + * plain old byte array. + */ + public byte[] toByteArray() { + final byte[] data = new byte[byteBuffer.remaining()]; + byteBuffer.slice().get(data); + return data; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TFastFramedTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TFastFramedTransport.java new file mode 100644 index 000000000..a1fd2490a --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TFastFramedTransport.java @@ -0,0 +1,200 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +/** + * This transport is wire compatible with {@link TFramedTransport}, but makes + * use of reusable, expanding read and write buffers in order to avoid + * allocating new byte[]s all the time. Since the buffers only expand, you + * should probably only use this transport if your messages are not too variably + * large, unless the persistent memory cost is not an issue. + * + * This implementation is NOT threadsafe. + */ +public class TFastFramedTransport extends TTransport { + + public static class Factory extends TTransportFactory { + private final int initialCapacity; + private final int maxLength; + + public Factory() { + this(DEFAULT_BUF_CAPACITY, DEFAULT_MAX_LENGTH); + } + + public Factory(int initialCapacity) { + this(initialCapacity, DEFAULT_MAX_LENGTH); + } + + public Factory(int initialCapacity, int maxLength) { + this.initialCapacity = initialCapacity; + this.maxLength = maxLength; + } + + @Override + public TTransport getTransport(TTransport trans) { + return new TFastFramedTransport(trans, + initialCapacity, + maxLength); + } + } + + /** + * How big should the default read and write buffers be? + */ + public static final int DEFAULT_BUF_CAPACITY = 1024; + /** + * How big is the largest allowable frame? Defaults to 16MB. + */ + public static final int DEFAULT_MAX_LENGTH = 16384000; + + private final TTransport underlying; + private final AutoExpandingBufferWriteTransport writeBuffer; + private AutoExpandingBufferReadTransport readBuffer; + private final int initialBufferCapacity; + private final byte[] i32buf = new byte[4]; + private final int maxLength; + + /** + * Create a new {@link TFastFramedTransport}. Use the defaults + * for initial buffer size and max frame length. + * @param underlying Transport that real reads and writes will go through to. + */ + public TFastFramedTransport(TTransport underlying) { + this(underlying, DEFAULT_BUF_CAPACITY, DEFAULT_MAX_LENGTH); + } + + /** + * Create a new {@link TFastFramedTransport}. Use the specified + * initial buffer capacity and the default max frame length. + * @param underlying Transport that real reads and writes will go through to. + * @param initialBufferCapacity The initial size of the read and write buffers. + * In practice, it's not critical to set this unless you know in advance that + * your messages are going to be very large. + */ + public TFastFramedTransport(TTransport underlying, int initialBufferCapacity) { + this(underlying, initialBufferCapacity, DEFAULT_MAX_LENGTH); + } + + /** + * + * @param underlying Transport that real reads and writes will go through to. + * @param initialBufferCapacity The initial size of the read and write buffers. + * In practice, it's not critical to set this unless you know in advance that + * your messages are going to be very large. (You can pass + * TFramedTransportWithReusableBuffer.DEFAULT_BUF_CAPACITY if you're only + * using this constructor because you want to set the maxLength.) + * @param maxLength The max frame size you are willing to read. You can use + * this parameter to limit how much memory can be allocated. + */ + public TFastFramedTransport(TTransport underlying, int initialBufferCapacity, int maxLength) { + this.underlying = underlying; + this.maxLength = maxLength; + this.initialBufferCapacity = initialBufferCapacity; + readBuffer = new AutoExpandingBufferReadTransport(initialBufferCapacity); + writeBuffer = new AutoExpandingBufferWriteTransport(initialBufferCapacity, 4); + } + + @Override + public void close() { + underlying.close(); + } + + @Override + public boolean isOpen() { + return underlying.isOpen(); + } + + @Override + public void open() throws TTransportException { + underlying.open(); + } + + @Override + public int read(byte[] buf, int off, int len) throws TTransportException { + int got = readBuffer.read(buf, off, len); + if (got > 0) { + return got; + } + + // Read another frame of data + readFrame(); + + return readBuffer.read(buf, off, len); + } + + private void readFrame() throws TTransportException { + underlying.readAll(i32buf , 0, 4); + int size = TFramedTransport.decodeFrameSize(i32buf); + + if (size < 0) { + close(); + throw new TTransportException(TTransportException.CORRUPTED_DATA, "Read a negative frame size (" + size + ")!"); + } + + if (size > maxLength) { + close(); + throw new TTransportException(TTransportException.CORRUPTED_DATA, + "Frame size (" + size + ") larger than max length (" + maxLength + ")!"); + } + + readBuffer.fill(underlying, size); + } + + @Override + public void write(byte[] buf, int off, int len) throws TTransportException { + writeBuffer.write(buf, off, len); + } + + @Override + public void consumeBuffer(int len) { + readBuffer.consumeBuffer(len); + } + + /** + * Only clears the read buffer! + */ + public void clear() { + readBuffer = new AutoExpandingBufferReadTransport(initialBufferCapacity); + } + + @Override + public void flush() throws TTransportException { + int payloadLength = writeBuffer.getLength() - 4; + byte[] data = writeBuffer.getBuf().array(); + TFramedTransport.encodeFrameSize(payloadLength, data); + underlying.write(data, 0, payloadLength + 4); + writeBuffer.reset(); + underlying.flush(); + } + + @Override + public byte[] getBuffer() { + return readBuffer.getBuffer(); + } + + @Override + public int getBufferPosition() { + return readBuffer.getBufferPosition(); + } + + @Override + public int getBytesRemainingInBuffer() { + return readBuffer.getBytesRemainingInBuffer(); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TFileProcessor.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TFileProcessor.java new file mode 100644 index 000000000..96087d1a3 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TFileProcessor.java @@ -0,0 +1,118 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import org.apache.thrift.TProcessor; +import org.apache.thrift.TException; +import org.apache.thrift.protocol.TProtocol; +import org.apache.thrift.protocol.TProtocolFactory; + +/** + * FileProcessor: helps in processing files generated by TFileTransport. + * Port of original cpp implementation + */ +public class TFileProcessor { + + private TProcessor processor_; + private TProtocolFactory inputProtocolFactory_; + private TProtocolFactory outputProtocolFactory_; + private TFileTransport inputTransport_; + private TTransport outputTransport_; + + public TFileProcessor(TProcessor processor, TProtocolFactory protocolFactory, + TFileTransport inputTransport, + TTransport outputTransport) { + processor_ = processor; + inputProtocolFactory_ = outputProtocolFactory_ = protocolFactory; + inputTransport_ = inputTransport; + outputTransport_ = outputTransport; + } + + public TFileProcessor(TProcessor processor, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + TFileTransport inputTransport, + TTransport outputTransport) { + processor_ = processor; + inputProtocolFactory_ = inputProtocolFactory; + outputProtocolFactory_ = outputProtocolFactory; + inputTransport_ = inputTransport; + outputTransport_ = outputTransport; + } + + private void processUntil(int lastChunk) throws TException { + TProtocol ip = inputProtocolFactory_.getProtocol(inputTransport_); + TProtocol op = outputProtocolFactory_.getProtocol(outputTransport_); + int curChunk = inputTransport_.getCurChunk(); + + try { + while (lastChunk >= curChunk) { + processor_.process(ip, op); + int newChunk = inputTransport_.getCurChunk(); + curChunk = newChunk; + } + } catch (TTransportException e) { + // if we are processing the last chunk - we could have just hit EOF + // on EOF - trap the error and stop processing. + if(e.getType() != TTransportException.END_OF_FILE) + throw e; + else { + return; + } + } + } + + /** + * Process from start to last chunk both inclusive where chunks begin from 0 + + * @param startChunkNum first chunk to be processed + * @param endChunkNum last chunk to be processed + */ + public void processChunk(int startChunkNum, int endChunkNum) throws TException { + int numChunks = inputTransport_.getNumChunks(); + if(endChunkNum < 0) + endChunkNum += numChunks; + + if(startChunkNum < 0) + startChunkNum += numChunks; + + if(endChunkNum < startChunkNum) + throw new TException("endChunkNum " + endChunkNum + " is less than " + startChunkNum); + + inputTransport_.seekToChunk(startChunkNum); + processUntil(endChunkNum); + } + + /** + * Process a single chunk + * + * @param chunkNum chunk to be processed + */ + public void processChunk(int chunkNum) throws TException { + processChunk(chunkNum, chunkNum); + } + + /** + * Process a current chunk + */ + public void processChunk() throws TException { + processChunk(inputTransport_.getCurChunk()); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TFileTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TFileTransport.java new file mode 100644 index 000000000..88b73e54d --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TFileTransport.java @@ -0,0 +1,624 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; +import java.util.Random; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * FileTransport implementation of the TTransport interface. + * Currently this is a straightforward port of the cpp implementation + * + * It may make better sense to provide a basic stream access on top of the framed file format + * The FileTransport can then be a user of this framed file format with some additional logic + * for chunking. + */ +public class TFileTransport extends TTransport { + + private static final Logger LOGGER = LoggerFactory.getLogger(TFileTransport.class.getName()); + + public static class TruncableBufferedInputStream extends BufferedInputStream { + public void trunc() { + pos = count = 0; + } + public TruncableBufferedInputStream(InputStream in) { + super(in); + } + public TruncableBufferedInputStream(InputStream in, int size) { + super(in, size); + } + } + + + public static class Event { + private byte[] buf_; + private int nread_; + private int navailable_; + + /** + * Initialize an event. Initially, it has no valid contents + * + * @param buf byte array buffer to store event + */ + public Event(byte[] buf) { + buf_ = buf; + nread_ = navailable_ = 0; + } + + public byte[] getBuf() { return buf_;} + public int getSize() { return buf_.length; } + + + public void setAvailable(int sz) { nread_ = 0; navailable_=sz;} + public int getRemaining() { return (navailable_ - nread_); } + + public int emit(byte[] buf, int offset, int ndesired) { + if((ndesired == 0) || (ndesired > getRemaining())) + ndesired = getRemaining(); + + if(ndesired <= 0) + return (ndesired); + + System.arraycopy(buf_, nread_, buf, offset, ndesired); + nread_ += ndesired; + + return(ndesired); + } + }; + + public static class ChunkState { + /** + * Chunk Size. Must be same across all implementations + */ + public static final int DEFAULT_CHUNK_SIZE = 16 * 1024 * 1024; + + private int chunk_size_ = DEFAULT_CHUNK_SIZE; + private long offset_ = 0; + + public ChunkState() {} + public ChunkState(int chunk_size) { chunk_size_ = chunk_size; } + + public void skip(int size) {offset_ += size; } + public void seek(long offset) {offset_ = offset;} + + public int getChunkSize() { return chunk_size_;} + public int getChunkNum() { return ((int)(offset_/chunk_size_));} + public int getRemaining() { return (chunk_size_ - ((int)(offset_ % chunk_size_)));} + public long getOffset() { return (offset_);} + } + + public static enum TailPolicy { + + NOWAIT(0, 0), + WAIT_FOREVER(500, -1); + + /** + * Time in milliseconds to sleep before next read + * If 0, no sleep + */ + public final int timeout_; + + /** + * Number of retries before giving up + * if 0, no retries + * if -1, retry forever + */ + public final int retries_; + + /** + * ctor for policy + * + * @param timeout sleep time for this particular policy + * @param retries number of retries + */ + + TailPolicy(int timeout, int retries) { + timeout_ = timeout; + retries_ = retries; + } + } + + /** + * Current tailing policy + */ + TailPolicy currentPolicy_ = TailPolicy.NOWAIT; + + + /** + * Underlying file being read + */ + protected TSeekableFile inputFile_ = null; + + /** + * Underlying outputStream + */ + protected OutputStream outputStream_ = null; + + + /** + * Event currently read in + */ + Event currentEvent_ = null; + + /** + * InputStream currently being used for reading + */ + InputStream inputStream_ = null; + + /** + * current Chunk state + */ + ChunkState cs = null; + + /** + * is read only? + */ + private boolean readOnly_ = false; + + /** + * Get File Tailing Policy + * + * @return current read policy + */ + public TailPolicy getTailPolicy() { + return (currentPolicy_); + } + + /** + * Set file Tailing Policy + * + * @param policy New policy to set + * @return Old policy + */ + public TailPolicy setTailPolicy(TailPolicy policy) { + TailPolicy old = currentPolicy_; + currentPolicy_ = policy; + return (old); + } + + + /** + * Initialize read input stream + * + * @return input stream to read from file + */ + private InputStream createInputStream() throws TTransportException { + InputStream is; + try { + if(inputStream_ != null) { + ((TruncableBufferedInputStream)inputStream_).trunc(); + is = inputStream_; + } else { + is = new TruncableBufferedInputStream(inputFile_.getInputStream()); + } + } catch (IOException iox) { + throw new TTransportException(iox.getMessage(), iox); + } + return(is); + } + + /** + * Read (potentially tailing) an input stream + * + * @param is InputStream to read from + * @param buf Buffer to read into + * @param off Offset in buffer to read into + * @param len Number of bytes to read + * @param tp policy to use if we hit EOF + * + * @return number of bytes read + */ + private int tailRead(InputStream is, byte[] buf, + int off, int len, TailPolicy tp) throws TTransportException { + int orig_len = len; + try { + int retries = 0; + while(len > 0) { + int cnt = is.read(buf, off, len); + if(cnt > 0) { + off += cnt; + len -= cnt; + retries = 0; + cs.skip(cnt); // remember that we read so many bytes + } else if (cnt == -1) { + // EOF + retries++; + + if((tp.retries_ != -1) && tp.retries_ < retries) + return (orig_len - len); + + if(tp.timeout_ > 0) { + try {Thread.sleep(tp.timeout_);} catch(InterruptedException e) {} + } + } else { + // either non-zero or -1 is what the contract says! + throw new + TTransportException("Unexpected return from InputStream.read = " + + cnt); + } + } + } catch (IOException iox) { + throw new TTransportException(iox.getMessage(), iox); + } + + return(orig_len - len); + } + + /** + * Event is corrupted. Do recovery + * + * @return true if recovery could be performed and we can read more data + * false is returned only when nothing more can be read + */ + private boolean performRecovery() throws TTransportException { + int numChunks = getNumChunks(); + int curChunk = cs.getChunkNum(); + + if(curChunk >= (numChunks-1)) { + return false; + } + seekToChunk(curChunk+1); + return true; + } + + /** + * Read event from underlying file + * + * @return true if event could be read, false otherwise (on EOF) + */ + private boolean readEvent() throws TTransportException { + byte[] ebytes = new byte[4]; + int esize; + int nread; + int nrequested; + + retry: + do { + // corner case. read to end of chunk + nrequested = cs.getRemaining(); + if(nrequested < 4) { + nread = tailRead(inputStream_, ebytes, 0, nrequested, currentPolicy_); + if(nread != nrequested) { + return(false); + } + } + + // assuming serialized on little endian machine + nread = tailRead(inputStream_, ebytes, 0, 4, currentPolicy_); + if(nread != 4) { + return(false); + } + + esize=0; + for(int i=3; i>=0; i--) { + int val = (0x000000ff & (int)ebytes[i]); + esize |= (val << (i*8)); + } + + // check if event is corrupted and do recovery as required + if(esize > cs.getRemaining()) { + throw new TTransportException("FileTransport error: bad event size"); + /* + if(performRecovery()) { + esize=0; + } else { + return false; + } + */ + } + } while (esize == 0); + + // reset existing event or get a larger one + if(currentEvent_.getSize() < esize) + currentEvent_ = new Event(new byte [esize]); + + // populate the event + byte[] buf = currentEvent_.getBuf(); + nread = tailRead(inputStream_, buf, 0, esize, currentPolicy_); + if(nread != esize) { + return(false); + } + currentEvent_.setAvailable(esize); + return(true); + } + + /** + * open if both input/output open unless readonly + * + * @return true + */ + public boolean isOpen() { + return ((inputStream_ != null) && (readOnly_ || (outputStream_ != null))); + } + + + /** + * Diverging from the cpp model and sticking to the TSocket model + * Files are not opened in ctor - but in explicit open call + */ + public void open() throws TTransportException { + if (isOpen()) + throw new TTransportException(TTransportException.ALREADY_OPEN); + + try { + inputStream_ = createInputStream(); + cs = new ChunkState(); + currentEvent_ = new Event(new byte [256]); + + if(!readOnly_) + outputStream_ = new BufferedOutputStream(inputFile_.getOutputStream()); + } catch (IOException iox) { + throw new TTransportException(TTransportException.NOT_OPEN, iox); + } + } + + /** + * Closes the transport. + */ + public void close() { + if (inputFile_ != null) { + try { + inputFile_.close(); + } catch (IOException iox) { + LOGGER.warn("WARNING: Error closing input file: " + + iox.getMessage()); + } + inputFile_ = null; + } + if (outputStream_ != null) { + try { + outputStream_.close(); + } catch (IOException iox) { + LOGGER.warn("WARNING: Error closing output stream: " + + iox.getMessage()); + } + outputStream_ = null; + } + } + + + /** + * File Transport ctor + * + * @param path File path to read and write from + * @param readOnly Whether this is a read-only transport + */ + public TFileTransport(final String path, boolean readOnly) throws IOException { + inputFile_ = new TStandardFile(path); + readOnly_ = readOnly; + } + + /** + * File Transport ctor + * + * @param inputFile open TSeekableFile to read/write from + * @param readOnly Whether this is a read-only transport + */ + public TFileTransport(TSeekableFile inputFile, boolean readOnly) { + inputFile_ = inputFile; + readOnly_ = readOnly; + } + + + /** + * Cloned from TTransport.java:readAll(). Only difference is throwing an EOF exception + * where one is detected. + */ + public int readAll(byte[] buf, int off, int len) + throws TTransportException { + int got = 0; + int ret = 0; + while (got < len) { + ret = read(buf, off+got, len-got); + if (ret < 0) { + throw new TTransportException("Error in reading from file"); + } + if(ret == 0) { + throw new TTransportException(TTransportException.END_OF_FILE, + "End of File reached"); + } + got += ret; + } + return got; + } + + + /** + * Reads up to len bytes into buffer buf, starting at offset off. + * + * @param buf Array to read into + * @param off Index to start reading at + * @param len Maximum number of bytes to read + * @return The number of bytes actually read + * @throws TTransportException if there was an error reading data + */ + public int read(byte[] buf, int off, int len) throws TTransportException { + if(!isOpen()) + throw new TTransportException(TTransportException.NOT_OPEN, + "Must open before reading"); + + if(currentEvent_.getRemaining() == 0) { + if(!readEvent()) + return(0); + } + + int nread = currentEvent_.emit(buf, off, len); + return nread; + } + + public int getNumChunks() throws TTransportException { + if(!isOpen()) + throw new TTransportException(TTransportException.NOT_OPEN, + "Must open before getNumChunks"); + try { + long len = inputFile_.length(); + if(len == 0) + return 0; + else + return (((int)(len/cs.getChunkSize())) + 1); + + } catch (IOException iox) { + throw new TTransportException(iox.getMessage(), iox); + } + } + + public int getCurChunk() throws TTransportException { + if(!isOpen()) + throw new TTransportException(TTransportException.NOT_OPEN, + "Must open before getCurChunk"); + return (cs.getChunkNum()); + + } + + + public void seekToChunk(int chunk) throws TTransportException { + if(!isOpen()) + throw new TTransportException(TTransportException.NOT_OPEN, + "Must open before seeking"); + + int numChunks = getNumChunks(); + + // file is empty, seeking to chunk is pointless + if (numChunks == 0) { + return; + } + + // negative indicates reverse seek (from the end) + if (chunk < 0) { + chunk += numChunks; + } + + // too large a value for reverse seek, just seek to beginning + if (chunk < 0) { + chunk = 0; + } + + long eofOffset=0; + boolean seekToEnd = (chunk >= numChunks); + if(seekToEnd) { + chunk = chunk - 1; + try { eofOffset = inputFile_.length(); } + catch (IOException iox) {throw new TTransportException(iox.getMessage(), + iox);} + } + + if(chunk*cs.getChunkSize() != cs.getOffset()) { + try { inputFile_.seek((long)chunk*cs.getChunkSize()); } + catch (IOException iox) { + throw new TTransportException("Seek to chunk " + + chunk + " " +iox.getMessage(), iox); + } + + cs.seek((long)chunk*cs.getChunkSize()); + currentEvent_.setAvailable(0); + inputStream_ = createInputStream(); + } + + if(seekToEnd) { + // waiting forever here - otherwise we can hit EOF and end up + // having consumed partial data from the data stream. + TailPolicy old = setTailPolicy(TailPolicy.WAIT_FOREVER); + while(cs.getOffset() < eofOffset) { readEvent(); } + currentEvent_.setAvailable(0); + setTailPolicy(old); + } + } + + public void seekToEnd() throws TTransportException { + if(!isOpen()) + throw new TTransportException(TTransportException.NOT_OPEN, + "Must open before seeking"); + seekToChunk(getNumChunks()); + } + + + /** + * Writes up to len bytes from the buffer. + * + * @param buf The output data buffer + * @param off The offset to start writing from + * @param len The number of bytes to write + * @throws TTransportException if there was an error writing data + */ + public void write(byte[] buf, int off, int len) throws TTransportException { + throw new TTransportException("Not Supported"); + } + + /** + * Flush any pending data out of a transport buffer. + * + * @throws TTransportException if there was an error writing out data. + */ + public void flush() throws TTransportException { + throw new TTransportException("Not Supported"); + } + + /** + * test program + * + */ + public static void main(String[] args) throws Exception { + + int num_chunks = 10; + + if((args.length < 1) || args[0].equals("--help") + || args[0].equals("-h") || args[0].equals("-?")) { + printUsage(); + } + + if(args.length > 1) { + try { + num_chunks = Integer.parseInt(args[1]); + } catch (Exception e) { + LOGGER.error("Cannot parse " + args[1]); + printUsage(); + } + } + + TFileTransport t = new TFileTransport(args[0], true); + t.open(); + LOGGER.info("NumChunks="+t.getNumChunks()); + + Random r = new Random(); + for(int j=0; j<num_chunks; j++) { + byte[] buf = new byte[4096]; + int cnum = r.nextInt(t.getNumChunks()-1); + LOGGER.info("Reading chunk "+cnum); + t.seekToChunk(cnum); + for(int i=0; i<4096; i++) { + t.read(buf, 0, 4096); + } + } + } + + private static void printUsage() { + LOGGER.error("Usage: TFileTransport <filename> [num_chunks]"); + LOGGER.error(" (Opens and reads num_chunks chunks from file randomly)"); + System.exit(1); + } + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TFramedTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TFramedTransport.java new file mode 100644 index 000000000..a006c3a6a --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TFramedTransport.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import org.apache.thrift.TByteArrayOutputStream; + +/** + * TFramedTransport is a buffered TTransport that ensures a fully read message + * every time by preceding messages with a 4-byte frame size. + */ +public class TFramedTransport extends TTransport { + + protected static final int DEFAULT_MAX_LENGTH = 16384000; + + private int maxLength_; + + /** + * Underlying transport + */ + private TTransport transport_ = null; + + /** + * Buffer for output + */ + private final TByteArrayOutputStream writeBuffer_ = + new TByteArrayOutputStream(1024); + + /** + * Buffer for input + */ + private final TMemoryInputTransport readBuffer_ = + new TMemoryInputTransport(new byte[0]); + + public static class Factory extends TTransportFactory { + private int maxLength_; + + public Factory() { + maxLength_ = TFramedTransport.DEFAULT_MAX_LENGTH; + } + + public Factory(int maxLength) { + maxLength_ = maxLength; + } + + @Override + public TTransport getTransport(TTransport base) { + return new TFramedTransport(base, maxLength_); + } + } + + /** + * Something to fill in the first four bytes of the buffer + * to make room for the frame size. This allows the + * implementation to write once instead of twice. + */ + private static final byte[] sizeFiller_ = new byte[] { 0x00, 0x00, 0x00, 0x00 }; + + /** + * Constructor wraps around another transport + */ + public TFramedTransport(TTransport transport, int maxLength) { + transport_ = transport; + maxLength_ = maxLength; + writeBuffer_.write(sizeFiller_, 0, 4); + } + + public TFramedTransport(TTransport transport) { + transport_ = transport; + maxLength_ = TFramedTransport.DEFAULT_MAX_LENGTH; + writeBuffer_.write(sizeFiller_, 0, 4); + } + + public void open() throws TTransportException { + transport_.open(); + } + + public boolean isOpen() { + return transport_.isOpen(); + } + + public void close() { + transport_.close(); + } + + public int read(byte[] buf, int off, int len) throws TTransportException { + int got = readBuffer_.read(buf, off, len); + if (got > 0) { + return got; + } + + // Read another frame of data + readFrame(); + + return readBuffer_.read(buf, off, len); + } + + @Override + public byte[] getBuffer() { + return readBuffer_.getBuffer(); + } + + @Override + public int getBufferPosition() { + return readBuffer_.getBufferPosition(); + } + + @Override + public int getBytesRemainingInBuffer() { + return readBuffer_.getBytesRemainingInBuffer(); + } + + @Override + public void consumeBuffer(int len) { + readBuffer_.consumeBuffer(len); + } + + public void clear() { + readBuffer_.clear(); + } + + private final byte[] i32buf = new byte[4]; + + private void readFrame() throws TTransportException { + transport_.readAll(i32buf, 0, 4); + int size = decodeFrameSize(i32buf); + + if (size < 0) { + close(); + throw new TTransportException(TTransportException.CORRUPTED_DATA, "Read a negative frame size (" + size + ")!"); + } + + if (size > maxLength_) { + close(); + throw new TTransportException(TTransportException.CORRUPTED_DATA, + "Frame size (" + size + ") larger than max length (" + maxLength_ + ")!"); + } + + byte[] buff = new byte[size]; + transport_.readAll(buff, 0, size); + readBuffer_.reset(buff); + } + + public void write(byte[] buf, int off, int len) throws TTransportException { + writeBuffer_.write(buf, off, len); + } + + @Override + public void flush() throws TTransportException { + byte[] buf = writeBuffer_.get(); + int len = writeBuffer_.len() - 4; // account for the prepended frame size + writeBuffer_.reset(); + writeBuffer_.write(sizeFiller_, 0, 4); // make room for the next frame's size data + + encodeFrameSize(len, buf); // this is the frame length without the filler + transport_.write(buf, 0, len + 4); // we have to write the frame size and frame data + transport_.flush(); + } + + public static final void encodeFrameSize(final int frameSize, final byte[] buf) { + buf[0] = (byte)(0xff & (frameSize >> 24)); + buf[1] = (byte)(0xff & (frameSize >> 16)); + buf[2] = (byte)(0xff & (frameSize >> 8)); + buf[3] = (byte)(0xff & (frameSize)); + } + + public static final int decodeFrameSize(final byte[] buf) { + return + ((buf[0] & 0xff) << 24) | + ((buf[1] & 0xff) << 16) | + ((buf[2] & 0xff) << 8) | + ((buf[3] & 0xff)); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/THttpClient.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/THttpClient.java new file mode 100644 index 000000000..c3063fe43 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/THttpClient.java @@ -0,0 +1,362 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.IOException; + +import java.net.URL; +import java.net.HttpURLConnection; +import java.util.HashMap; +import java.util.Map; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.params.CoreConnectionPNames; + +/** + * HTTP implementation of the TTransport interface. Used for working with a + * Thrift web services implementation (using for example TServlet). + * + * This class offers two implementations of the HTTP transport. + * One uses HttpURLConnection instances, the other HttpClient from Apache + * Http Components. + * The chosen implementation depends on the constructor used to + * create the THttpClient instance. + * Using the THttpClient(String url) constructor or passing null as the + * HttpClient to THttpClient(String url, HttpClient client) will create an + * instance which will use HttpURLConnection. + * + * When using HttpClient, the following configuration leads to 5-15% + * better performance than the HttpURLConnection implementation: + * + * http.protocol.version=HttpVersion.HTTP_1_1 + * http.protocol.content-charset=UTF-8 + * http.protocol.expect-continue=false + * http.connection.stalecheck=false + * + * Also note that under high load, the HttpURLConnection implementation + * may exhaust the open file descriptor limit. + * + * @see <a href="https://issues.apache.org/jira/browse/THRIFT-970">THRIFT-970</a> + */ + +public class THttpClient extends TTransport { + + private URL url_ = null; + + private final ByteArrayOutputStream requestBuffer_ = new ByteArrayOutputStream(); + + private InputStream inputStream_ = null; + + private int connectTimeout_ = 0; + + private int readTimeout_ = 0; + + private Map<String,String> customHeaders_ = null; + + private final HttpHost host; + + private final HttpClient client; + + public static class Factory extends TTransportFactory { + + private final String url; + private final HttpClient client; + + public Factory(String url) { + this.url = url; + this.client = null; + } + + public Factory(String url, HttpClient client) { + this.url = url; + this.client = client; + } + + @Override + public TTransport getTransport(TTransport trans) { + try { + if (null != client) { + return new THttpClient(url, client); + } else { + return new THttpClient(url); + } + } catch (TTransportException tte) { + return null; + } + } + } + + public THttpClient(String url) throws TTransportException { + try { + url_ = new URL(url); + this.client = null; + this.host = null; + } catch (IOException iox) { + throw new TTransportException(iox); + } + } + + public THttpClient(String url, HttpClient client) throws TTransportException { + try { + url_ = new URL(url); + this.client = client; + this.host = new HttpHost(url_.getHost(), -1 == url_.getPort() ? url_.getDefaultPort() : url_.getPort(), url_.getProtocol()); + } catch (IOException iox) { + throw new TTransportException(iox); + } + } + + public void setConnectTimeout(int timeout) { + connectTimeout_ = timeout; + if (null != this.client) { + // WARNING, this modifies the HttpClient params, this might have an impact elsewhere if the + // same HttpClient is used for something else. + client.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, connectTimeout_); + } + } + + public void setReadTimeout(int timeout) { + readTimeout_ = timeout; + if (null != this.client) { + // WARNING, this modifies the HttpClient params, this might have an impact elsewhere if the + // same HttpClient is used for something else. + client.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, readTimeout_); + } + } + + public void setCustomHeaders(Map<String,String> headers) { + customHeaders_ = headers; + } + + public void setCustomHeader(String key, String value) { + if (customHeaders_ == null) { + customHeaders_ = new HashMap<String, String>(); + } + customHeaders_.put(key, value); + } + + public void open() {} + + public void close() { + if (null != inputStream_) { + try { + inputStream_.close(); + } catch (IOException ioe) { + ; + } + inputStream_ = null; + } + } + + public boolean isOpen() { + return true; + } + + public int read(byte[] buf, int off, int len) throws TTransportException { + if (inputStream_ == null) { + throw new TTransportException("Response buffer is empty, no request."); + } + try { + int ret = inputStream_.read(buf, off, len); + if (ret == -1) { + throw new TTransportException("No more data available."); + } + return ret; + } catch (IOException iox) { + throw new TTransportException(iox); + } + } + + public void write(byte[] buf, int off, int len) { + requestBuffer_.write(buf, off, len); + } + + /** + * copy from org.apache.http.util.EntityUtils#consume. Android has it's own httpcore + * that doesn't have a consume. + */ + private static void consume(final HttpEntity entity) throws IOException { + if (entity == null) { + return; + } + if (entity.isStreaming()) { + InputStream instream = entity.getContent(); + if (instream != null) { + instream.close(); + } + } + } + + private void flushUsingHttpClient() throws TTransportException { + + if (null == this.client) { + throw new TTransportException("Null HttpClient, aborting."); + } + + // Extract request and reset buffer + byte[] data = requestBuffer_.toByteArray(); + requestBuffer_.reset(); + + HttpPost post = null; + + InputStream is = null; + + try { + // Set request to path + query string + post = new HttpPost(this.url_.getFile()); + + // + // Headers are added to the HttpPost instance, not + // to HttpClient. + // + + post.setHeader("Content-Type", "application/x-thrift"); + post.setHeader("Accept", "application/x-thrift"); + post.setHeader("User-Agent", "Java/THttpClient/HC"); + + if (null != customHeaders_) { + for (Map.Entry<String, String> header : customHeaders_.entrySet()) { + post.setHeader(header.getKey(), header.getValue()); + } + } + + post.setEntity(new ByteArrayEntity(data)); + + HttpResponse response = this.client.execute(this.host, post); + int responseCode = response.getStatusLine().getStatusCode(); + + // + // Retrieve the inputstream BEFORE checking the status code so + // resources get freed in the finally clause. + // + + is = response.getEntity().getContent(); + + if (responseCode != HttpStatus.SC_OK) { + throw new TTransportException("HTTP Response code: " + responseCode); + } + + // Read the responses into a byte array so we can release the connection + // early. This implies that the whole content will have to be read in + // memory, and that momentarily we might use up twice the memory (while the + // thrift struct is being read up the chain). + // Proceeding differently might lead to exhaustion of connections and thus + // to app failure. + + byte[] buf = new byte[1024]; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + int len = 0; + do { + len = is.read(buf); + if (len > 0) { + baos.write(buf, 0, len); + } + } while (-1 != len); + + try { + // Indicate we're done with the content. + consume(response.getEntity()); + } catch (IOException ioe) { + // We ignore this exception, it might only mean the server has no + // keep-alive capability. + } + + inputStream_ = new ByteArrayInputStream(baos.toByteArray()); + } catch (IOException ioe) { + // Abort method so the connection gets released back to the connection manager + if (null != post) { + post.abort(); + } + throw new TTransportException(ioe); + } finally { + if (null != is) { + // Close the entity's input stream, this will release the underlying connection + try { + is.close(); + } catch (IOException ioe) { + throw new TTransportException(ioe); + } + } + if (post != null) { + post.releaseConnection(); + } + } + } + + public void flush() throws TTransportException { + + if (null != this.client) { + flushUsingHttpClient(); + return; + } + + // Extract request and reset buffer + byte[] data = requestBuffer_.toByteArray(); + requestBuffer_.reset(); + + try { + // Create connection object + HttpURLConnection connection = (HttpURLConnection)url_.openConnection(); + + // Timeouts, only if explicitly set + if (connectTimeout_ > 0) { + connection.setConnectTimeout(connectTimeout_); + } + if (readTimeout_ > 0) { + connection.setReadTimeout(readTimeout_); + } + + // Make the request + connection.setRequestMethod("POST"); + connection.setRequestProperty("Content-Type", "application/x-thrift"); + connection.setRequestProperty("Accept", "application/x-thrift"); + connection.setRequestProperty("User-Agent", "Java/THttpClient"); + if (customHeaders_ != null) { + for (Map.Entry<String, String> header : customHeaders_.entrySet()) { + connection.setRequestProperty(header.getKey(), header.getValue()); + } + } + connection.setDoOutput(true); + connection.connect(); + connection.getOutputStream().write(data); + + int responseCode = connection.getResponseCode(); + if (responseCode != HttpURLConnection.HTTP_OK) { + throw new TTransportException("HTTP Response code: " + responseCode); + } + + // Read the responses + inputStream_ = connection.getInputStream(); + + } catch (IOException iox) { + throw new TTransportException(iox); + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TIOStreamTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TIOStreamTransport.java new file mode 100644 index 000000000..2d31f392f --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TIOStreamTransport.java @@ -0,0 +1,162 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * This is the most commonly used base transport. It takes an InputStream + * and an OutputStream and uses those to perform all transport operations. + * This allows for compatibility with all the nice constructs Java already + * has to provide a variety of types of streams. + * + */ +public class TIOStreamTransport extends TTransport { + + private static final Logger LOGGER = LoggerFactory.getLogger(TIOStreamTransport.class.getName()); + + /** Underlying inputStream */ + protected InputStream inputStream_ = null; + + /** Underlying outputStream */ + protected OutputStream outputStream_ = null; + + /** + * Subclasses can invoke the default constructor and then assign the input + * streams in the open method. + */ + protected TIOStreamTransport() {} + + /** + * Input stream constructor. + * + * @param is Input stream to read from + */ + public TIOStreamTransport(InputStream is) { + inputStream_ = is; + } + + /** + * Output stream constructor. + * + * @param os Output stream to read from + */ + public TIOStreamTransport(OutputStream os) { + outputStream_ = os; + } + + /** + * Two-way stream constructor. + * + * @param is Input stream to read from + * @param os Output stream to read from + */ + public TIOStreamTransport(InputStream is, OutputStream os) { + inputStream_ = is; + outputStream_ = os; + } + + /** + * + * @return false after close is called. + */ + public boolean isOpen() { + return inputStream_ != null && outputStream_ != null; + } + + /** + * The streams must already be open. This method does nothing. + */ + public void open() throws TTransportException {} + + /** + * Closes both the input and output streams. + */ + public void close() { + if (inputStream_ != null) { + try { + inputStream_.close(); + } catch (IOException iox) { + LOGGER.warn("Error closing input stream.", iox); + } + inputStream_ = null; + } + if (outputStream_ != null) { + try { + outputStream_.close(); + } catch (IOException iox) { + LOGGER.warn("Error closing output stream.", iox); + } + outputStream_ = null; + } + } + + /** + * Reads from the underlying input stream if not null. + */ + public int read(byte[] buf, int off, int len) throws TTransportException { + if (inputStream_ == null) { + throw new TTransportException(TTransportException.NOT_OPEN, "Cannot read from null inputStream"); + } + int bytesRead; + try { + bytesRead = inputStream_.read(buf, off, len); + } catch (IOException iox) { + throw new TTransportException(TTransportException.UNKNOWN, iox); + } + if (bytesRead < 0) { + throw new TTransportException(TTransportException.END_OF_FILE, "Socket is closed by peer."); + } + return bytesRead; + } + + /** + * Writes to the underlying output stream if not null. + */ + public void write(byte[] buf, int off, int len) throws TTransportException { + if (outputStream_ == null) { + throw new TTransportException(TTransportException.NOT_OPEN, "Cannot write to null outputStream"); + } + try { + outputStream_.write(buf, off, len); + } catch (IOException iox) { + throw new TTransportException(TTransportException.UNKNOWN, iox); + } + } + + /** + * Flushes the underlying output stream if not null. + */ + public void flush() throws TTransportException { + if (outputStream_ == null) { + throw new TTransportException(TTransportException.NOT_OPEN, "Cannot flush null outputStream"); + } + try { + outputStream_.flush(); + } catch (IOException iox) { + throw new TTransportException(TTransportException.UNKNOWN, iox); + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TMemoryBuffer.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TMemoryBuffer.java new file mode 100644 index 000000000..b19ac86d2 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TMemoryBuffer.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import org.apache.thrift.TByteArrayOutputStream; +import java.nio.charset.Charset; + +/** + * Memory buffer-based implementation of the TTransport interface. + */ +public class TMemoryBuffer extends TTransport { + /** + * Create a TMemoryBuffer with an initial buffer size of <i>size</i>. The + * internal buffer will grow as necessary to accommodate the size of the data + * being written to it. + * + * @param size the initial size of the buffer + */ + public TMemoryBuffer(int size) { + arr_ = new TByteArrayOutputStream(size); + } + + @Override + public boolean isOpen() { + return true; + } + + @Override + public void open() { + /* Do nothing */ + } + + @Override + public void close() { + /* Do nothing */ + } + + @Override + public int read(byte[] buf, int off, int len) { + byte[] src = arr_.get(); + int amtToRead = (len > arr_.len() - pos_ ? arr_.len() - pos_ : len); + if (amtToRead > 0) { + System.arraycopy(src, pos_, buf, off, amtToRead); + pos_ += amtToRead; + } + return amtToRead; + } + + @Override + public void write(byte[] buf, int off, int len) { + arr_.write(buf, off, len); + } + + /** + * Output the contents of the memory buffer as a String, using the supplied + * encoding + * @param charset the encoding to use + * @return the contents of the memory buffer as a String + */ + public String toString(Charset charset) { + return arr_.toString(charset); + } + + public String inspect() { + StringBuilder buf = new StringBuilder(); + byte[] bytes = arr_.toByteArray(); + for (int i = 0; i < bytes.length; i++) { + buf.append(pos_ == i ? "==>" : "" ).append(Integer.toHexString(bytes[i] & 0xff)).append(" "); + } + return buf.toString(); + } + + // The contents of the buffer + private TByteArrayOutputStream arr_; + + // Position to read next byte from + private int pos_; + + public int length() { + return arr_.size(); + } + + public byte[] getArray() { + return arr_.get(); + } +} + diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TMemoryInputTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TMemoryInputTransport.java new file mode 100644 index 000000000..2530dcc36 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TMemoryInputTransport.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +public final class TMemoryInputTransport extends TTransport { + + private byte[] buf_; + private int pos_; + private int endPos_; + + public TMemoryInputTransport() { + } + + public TMemoryInputTransport(byte[] buf) { + reset(buf); + } + + public TMemoryInputTransport(byte[] buf, int offset, int length) { + reset(buf, offset, length); + } + + public void reset(byte[] buf) { + reset(buf, 0, buf.length); + } + + public void reset(byte[] buf, int offset, int length) { + buf_ = buf; + pos_ = offset; + endPos_ = offset + length; + } + + public void clear() { + buf_ = null; + } + + @Override + public void close() {} + + @Override + public boolean isOpen() { + return true; + } + + @Override + public void open() throws TTransportException {} + + @Override + public int read(byte[] buf, int off, int len) throws TTransportException { + int bytesRemaining = getBytesRemainingInBuffer(); + int amtToRead = (len > bytesRemaining ? bytesRemaining : len); + if (amtToRead > 0) { + System.arraycopy(buf_, pos_, buf, off, amtToRead); + consumeBuffer(amtToRead); + } + return amtToRead; + } + + @Override + public void write(byte[] buf, int off, int len) throws TTransportException { + throw new UnsupportedOperationException("No writing allowed!"); + } + + @Override + public byte[] getBuffer() { + return buf_; + } + + public int getBufferPosition() { + return pos_; + } + + public int getBytesRemainingInBuffer() { + return endPos_ - pos_; + } + + public void consumeBuffer(int len) { + pos_ += len; + } + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TNonblockingServerSocket.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TNonblockingServerSocket.java new file mode 100644 index 000000000..df37cb06e --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TNonblockingServerSocket.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.SocketException; +import java.nio.channels.ClosedChannelException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Wrapper around ServerSocketChannel + */ +public class TNonblockingServerSocket extends TNonblockingServerTransport { + private static final Logger LOGGER = LoggerFactory.getLogger(TNonblockingServerSocket.class.getName()); + + /** + * This channel is where all the nonblocking magic happens. + */ + private ServerSocketChannel serverSocketChannel = null; + + /** + * Underlying ServerSocket object + */ + private ServerSocket serverSocket_ = null; + + /** + * Timeout for client sockets from accept + */ + private int clientTimeout_ = 0; + + public static class NonblockingAbstractServerSocketArgs extends + AbstractServerTransportArgs<NonblockingAbstractServerSocketArgs> {} + + /** + * Creates just a port listening server socket + */ + public TNonblockingServerSocket(int port) throws TTransportException { + this(port, 0); + } + + /** + * Creates just a port listening server socket + */ + public TNonblockingServerSocket(int port, int clientTimeout) throws TTransportException { + this(new NonblockingAbstractServerSocketArgs().port(port).clientTimeout(clientTimeout)); + } + + public TNonblockingServerSocket(InetSocketAddress bindAddr) throws TTransportException { + this(bindAddr, 0); + } + + public TNonblockingServerSocket(InetSocketAddress bindAddr, int clientTimeout) throws TTransportException { + this(new NonblockingAbstractServerSocketArgs().bindAddr(bindAddr).clientTimeout(clientTimeout)); + } + + public TNonblockingServerSocket(NonblockingAbstractServerSocketArgs args) throws TTransportException { + clientTimeout_ = args.clientTimeout; + try { + serverSocketChannel = ServerSocketChannel.open(); + serverSocketChannel.configureBlocking(false); + + // Make server socket + serverSocket_ = serverSocketChannel.socket(); + // Prevent 2MSL delay problem on server restarts + serverSocket_.setReuseAddress(true); + // Bind to listening port + serverSocket_.bind(args.bindAddr, args.backlog); + } catch (IOException ioe) { + serverSocket_ = null; + throw new TTransportException("Could not create ServerSocket on address " + args.bindAddr.toString() + ".", ioe); + } + } + + public void listen() throws TTransportException { + // Make sure not to block on accept + if (serverSocket_ != null) { + try { + serverSocket_.setSoTimeout(0); + } catch (SocketException sx) { + LOGGER.error("Socket exception while setting socket timeout", sx); + } + } + } + + protected TNonblockingSocket acceptImpl() throws TTransportException { + if (serverSocket_ == null) { + throw new TTransportException(TTransportException.NOT_OPEN, "No underlying server socket."); + } + try { + SocketChannel socketChannel = serverSocketChannel.accept(); + if (socketChannel == null) { + return null; + } + + TNonblockingSocket tsocket = new TNonblockingSocket(socketChannel); + tsocket.setTimeout(clientTimeout_); + return tsocket; + } catch (IOException iox) { + throw new TTransportException(iox); + } + } + + public void registerSelector(Selector selector) { + try { + // Register the server socket channel, indicating an interest in + // accepting new connections + serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); + } catch (ClosedChannelException e) { + // this shouldn't happen, ideally... + // TODO: decide what to do with this. + } + } + + public void close() { + if (serverSocket_ != null) { + try { + serverSocket_.close(); + } catch (IOException iox) { + LOGGER.warn("WARNING: Could not close server socket: " + iox.getMessage()); + } + serverSocket_ = null; + } + } + + public void interrupt() { + // The thread-safeness of this is dubious, but Java documentation suggests + // that it is safe to do this from a different thread context + close(); + } + + public int getPort() { + if (serverSocket_ == null) + return -1; + return serverSocket_.getLocalPort(); + } + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TNonblockingServerTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TNonblockingServerTransport.java new file mode 100644 index 000000000..ba45b09dc --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TNonblockingServerTransport.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.nio.channels.Selector; + +/** + * Server transport that can be operated in a nonblocking fashion. + */ +public abstract class TNonblockingServerTransport extends TServerTransport { + + public abstract void registerSelector(Selector selector); +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TNonblockingSocket.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TNonblockingSocket.java new file mode 100644 index 000000000..f86a48b42 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TNonblockingSocket.java @@ -0,0 +1,210 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Transport for use with async client. + */ +public class TNonblockingSocket extends TNonblockingTransport { + + private static final Logger LOGGER = LoggerFactory.getLogger(TNonblockingSocket.class.getName()); + + /** + * Host and port if passed in, used for lazy non-blocking connect. + */ + private final SocketAddress socketAddress_; + + private final SocketChannel socketChannel_; + + public TNonblockingSocket(String host, int port) throws IOException { + this(host, port, 0); + } + + /** + * Create a new nonblocking socket transport that will be connected to host:port. + * @param host + * @param port + * @throws IOException + */ + public TNonblockingSocket(String host, int port, int timeout) throws IOException { + this(SocketChannel.open(), timeout, new InetSocketAddress(host, port)); + } + + /** + * Constructor that takes an already created socket. + * + * @param socketChannel Already created SocketChannel object + * @throws IOException if there is an error setting up the streams + */ + public TNonblockingSocket(SocketChannel socketChannel) throws IOException { + this(socketChannel, 0, null); + if (!socketChannel.isConnected()) throw new IOException("Socket must already be connected"); + } + + private TNonblockingSocket(SocketChannel socketChannel, int timeout, SocketAddress socketAddress) + throws IOException { + socketChannel_ = socketChannel; + socketAddress_ = socketAddress; + + // make it a nonblocking channel + socketChannel.configureBlocking(false); + + // set options + Socket socket = socketChannel.socket(); + socket.setSoLinger(false, 0); + socket.setTcpNoDelay(true); + socket.setKeepAlive(true); + setTimeout(timeout); + } + + /** + * Register the new SocketChannel with our Selector, indicating + * we'd like to be notified when it's ready for I/O. + * + * @param selector + * @return the selection key for this socket. + */ + public SelectionKey registerSelector(Selector selector, int interests) throws IOException { + return socketChannel_.register(selector, interests); + } + + /** + * Sets the socket timeout, although this implementation never uses blocking operations so it is unused. + * + * @param timeout Milliseconds timeout + */ + public void setTimeout(int timeout) { + try { + socketChannel_.socket().setSoTimeout(timeout); + } catch (SocketException sx) { + LOGGER.warn("Could not set socket timeout.", sx); + } + } + + /** + * Returns a reference to the underlying SocketChannel. + */ + public SocketChannel getSocketChannel() { + return socketChannel_; + } + + /** + * Checks whether the socket is connected. + */ + public boolean isOpen() { + // isConnected() does not return false after close(), but isOpen() does + return socketChannel_.isOpen() && socketChannel_.isConnected(); + } + + /** + * Do not call, the implementation provides its own lazy non-blocking connect. + */ + public void open() throws TTransportException { + throw new RuntimeException("open() is not implemented for TNonblockingSocket"); + } + + /** + * Perform a nonblocking read into buffer. + */ + public int read(ByteBuffer buffer) throws IOException { + return socketChannel_.read(buffer); + } + + + /** + * Reads from the underlying input stream if not null. + */ + public int read(byte[] buf, int off, int len) throws TTransportException { + if ((socketChannel_.validOps() & SelectionKey.OP_READ) != SelectionKey.OP_READ) { + throw new TTransportException(TTransportException.NOT_OPEN, + "Cannot read from write-only socket channel"); + } + try { + return socketChannel_.read(ByteBuffer.wrap(buf, off, len)); + } catch (IOException iox) { + throw new TTransportException(TTransportException.UNKNOWN, iox); + } + } + + /** + * Perform a nonblocking write of the data in buffer; + */ + public int write(ByteBuffer buffer) throws IOException { + return socketChannel_.write(buffer); + } + + /** + * Writes to the underlying output stream if not null. + */ + public void write(byte[] buf, int off, int len) throws TTransportException { + if ((socketChannel_.validOps() & SelectionKey.OP_WRITE) != SelectionKey.OP_WRITE) { + throw new TTransportException(TTransportException.NOT_OPEN, + "Cannot write to write-only socket channel"); + } + try { + socketChannel_.write(ByteBuffer.wrap(buf, off, len)); + } catch (IOException iox) { + throw new TTransportException(TTransportException.UNKNOWN, iox); + } + } + + /** + * Noop. + */ + public void flush() throws TTransportException { + // Not supported by SocketChannel. + } + + /** + * Closes the socket. + */ + public void close() { + try { + socketChannel_.close(); + } catch (IOException iox) { + LOGGER.warn("Could not close socket.", iox); + } + } + + /** {@inheritDoc} */ + public boolean startConnect() throws IOException { + return socketChannel_.connect(socketAddress_); + } + + /** {@inheritDoc} */ + public boolean finishConnect() throws IOException { + return socketChannel_.finishConnect(); + } + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TNonblockingTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TNonblockingTransport.java new file mode 100644 index 000000000..43c130688 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TNonblockingTransport.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.io.IOException; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; + +public abstract class TNonblockingTransport extends TTransport { + + /** + * Non-blocking connection initialization. + * @see java.nio.channels.SocketChannel#connect(SocketAddress remote) + */ + public abstract boolean startConnect() throws IOException; + + /** + * Non-blocking connection completion. + * @see java.nio.channels.SocketChannel#finishConnect() + */ + public abstract boolean finishConnect() throws IOException; + + public abstract SelectionKey registerSelector(Selector selector, int interests) throws IOException; + + public abstract int read(ByteBuffer buffer) throws IOException; + + public abstract int write(ByteBuffer buffer) throws IOException; +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSSLTransportFactory.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSSLTransportFactory.java new file mode 100644 index 000000000..73dfaaf18 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSSLTransportFactory.java @@ -0,0 +1,447 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.URL; +import java.net.MalformedURLException; +import java.security.KeyStore; +import java.util.Arrays; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A Factory for providing and setting up Client and Server SSL wrapped + * TSocket and TServerSocket + */ +public class TSSLTransportFactory { + + private static final Logger LOGGER = + LoggerFactory.getLogger(TSSLTransportFactory.class); + + /** + * Get a SSL wrapped TServerSocket bound to the specified port. In this + * configuration the default settings are used. Default settings are retrieved + * from System properties that are set. + * + * Example system properties: + * -Djavax.net.ssl.trustStore=<truststore location> + * -Djavax.net.ssl.trustStorePassword=password + * -Djavax.net.ssl.keyStore=<keystore location> + * -Djavax.net.ssl.keyStorePassword=password + * + * @param port + * @return A SSL wrapped TServerSocket + * @throws TTransportException + */ + public static TServerSocket getServerSocket(int port) throws TTransportException { + return getServerSocket(port, 0); + } + + /** + * Get a default SSL wrapped TServerSocket bound to the specified port + * + * @param port + * @param clientTimeout + * @return A SSL wrapped TServerSocket + * @throws TTransportException + */ + public static TServerSocket getServerSocket(int port, int clientTimeout) throws TTransportException { + return getServerSocket(port, clientTimeout, false, null); + } + + /** + * Get a default SSL wrapped TServerSocket bound to the specified port and interface + * + * @param port + * @param clientTimeout + * @param ifAddress + * @return A SSL wrapped TServerSocket + * @throws TTransportException + */ + public static TServerSocket getServerSocket(int port, int clientTimeout, boolean clientAuth, InetAddress ifAddress) throws TTransportException { + SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); + return createServer(factory, port, clientTimeout, clientAuth, ifAddress, null); + } + + /** + * Get a configured SSL wrapped TServerSocket bound to the specified port and interface. + * Here the TSSLTransportParameters are used to set the values for the algorithms, keystore, + * truststore and other settings + * + * @param port + * @param clientTimeout + * @param ifAddress + * @param params + * @return A SSL wrapped TServerSocket + * @throws TTransportException + */ + public static TServerSocket getServerSocket(int port, int clientTimeout, InetAddress ifAddress, TSSLTransportParameters params) throws TTransportException { + if (params == null || !(params.isKeyStoreSet || params.isTrustStoreSet)) { + throw new TTransportException("Either one of the KeyStore or TrustStore must be set for SSLTransportParameters"); + } + + SSLContext ctx = createSSLContext(params); + return createServer(ctx.getServerSocketFactory(), port, clientTimeout, params.clientAuth, ifAddress, params); + } + + private static TServerSocket createServer(SSLServerSocketFactory factory, int port, int timeout, boolean clientAuth, + InetAddress ifAddress, TSSLTransportParameters params) throws TTransportException { + try { + SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(port, 100, ifAddress); + serverSocket.setSoTimeout(timeout); + serverSocket.setNeedClientAuth(clientAuth); + if (params != null && params.cipherSuites != null) { + serverSocket.setEnabledCipherSuites(params.cipherSuites); + } + return new TServerSocket(new TServerSocket.ServerSocketTransportArgs(). + serverSocket(serverSocket).clientTimeout(timeout)); + } catch (Exception e) { + throw new TTransportException("Could not bind to port " + port, e); + } + } + + /** + * Get a default SSL wrapped TSocket connected to the specified host and port. All + * the client methods return a bound connection. So there is no need to call open() on the + * TTransport. + * + * @param host + * @param port + * @param timeout + * @return A SSL wrapped TSocket + * @throws TTransportException + */ + public static TSocket getClientSocket(String host, int port, int timeout) throws TTransportException { + SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault(); + return createClient(factory, host, port, timeout); + } + + /** + * Get a default SSL wrapped TSocket connected to the specified host and port. + * + * @param host + * @param port + * @return A SSL wrapped TSocket + * @throws TTransportException + */ + public static TSocket getClientSocket(String host, int port) throws TTransportException { + return getClientSocket(host, port, 0); + } + + /** + * Get a custom configured SSL wrapped TSocket. The SSL settings are obtained from the + * passed in TSSLTransportParameters. + * + * @param host + * @param port + * @param timeout + * @param params + * @return A SSL wrapped TSocket + * @throws TTransportException + */ + public static TSocket getClientSocket(String host, int port, int timeout, TSSLTransportParameters params) throws TTransportException { + if (params == null || !(params.isKeyStoreSet || params.isTrustStoreSet)) { + throw new TTransportException("Either one of the KeyStore or TrustStore must be set for SSLTransportParameters"); + } + + SSLContext ctx = createSSLContext(params); + return createClient(ctx.getSocketFactory(), host, port, timeout); + } + + private static SSLContext createSSLContext(TSSLTransportParameters params) throws TTransportException { + SSLContext ctx; + InputStream in = null; + InputStream is = null; + + try { + ctx = SSLContext.getInstance(params.protocol); + TrustManagerFactory tmf = null; + KeyManagerFactory kmf = null; + + if (params.isTrustStoreSet) { + tmf = TrustManagerFactory.getInstance(params.trustManagerType); + KeyStore ts = KeyStore.getInstance(params.trustStoreType); + if (params.trustStoreStream != null) { + in = params.trustStoreStream; + } else { + in = getStoreAsStream(params.trustStore); + } + ts.load(in, + (params.trustPass != null ? params.trustPass.toCharArray() : null)); + tmf.init(ts); + } + + if (params.isKeyStoreSet) { + kmf = KeyManagerFactory.getInstance(params.keyManagerType); + KeyStore ks = KeyStore.getInstance(params.keyStoreType); + if (params.keyStoreStream != null) { + is = params.keyStoreStream; + } else { + is = getStoreAsStream(params.keyStore); + } + ks.load(is, params.keyPass.toCharArray()); + kmf.init(ks, params.keyPass.toCharArray()); + } + + if (params.isKeyStoreSet && params.isTrustStoreSet) { + ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + } + else if (params.isKeyStoreSet) { + ctx.init(kmf.getKeyManagers(), null, null); + } + else { + ctx.init(null, tmf.getTrustManagers(), null); + } + + } catch (Exception e) { + throw new TTransportException("Error creating the transport", e); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + LOGGER.warn("Unable to close stream", e); + } + } + if (is != null) { + try { + is.close(); + } catch (IOException e) { + LOGGER.warn("Unable to close stream", e); + } + } + } + + return ctx; + } + + private static InputStream getStoreAsStream(String store) throws IOException { + try { + return new FileInputStream(store); + } catch(FileNotFoundException e) { + } + + InputStream storeStream = null; + try { + storeStream = new URL(store).openStream(); + if (storeStream != null) { + return storeStream; + } + } catch(MalformedURLException e) { + } + + storeStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(store); + + if (storeStream != null) { + return storeStream; + } else { + throw new IOException("Could not load file: " + store); + } + } + + private static TSocket createClient(SSLSocketFactory factory, String host, int port, int timeout) throws TTransportException { + try { + SSLSocket socket = (SSLSocket) factory.createSocket(host, port); + socket.setSoTimeout(timeout); + return new TSocket(socket); + } catch (Exception e) { + throw new TTransportException("Could not connect to " + host + " on port " + port, e); + } + } + + + /** + * A Class to hold all the SSL parameters + */ + public static class TSSLTransportParameters { + protected String protocol = "TLS"; + protected String keyStore; + protected InputStream keyStoreStream; + protected String keyPass; + protected String keyManagerType = KeyManagerFactory.getDefaultAlgorithm(); + protected String keyStoreType = "JKS"; + protected String trustStore; + protected InputStream trustStoreStream; + protected String trustPass; + protected String trustManagerType = TrustManagerFactory.getDefaultAlgorithm(); + protected String trustStoreType = "JKS"; + protected String[] cipherSuites; + protected boolean clientAuth = false; + protected boolean isKeyStoreSet = false; + protected boolean isTrustStoreSet = false; + + public TSSLTransportParameters() {} + + /** + * Create parameters specifying the protocol and cipher suites + * + * @param protocol The specific protocol (TLS/SSL) can be specified with versions + * @param cipherSuites + */ + public TSSLTransportParameters(String protocol, String[] cipherSuites) { + this(protocol, cipherSuites, false); + } + + /** + * Create parameters specifying the protocol, cipher suites and if client authentication + * is required + * + * @param protocol The specific protocol (TLS/SSL) can be specified with versions + * @param cipherSuites + * @param clientAuth + */ + public TSSLTransportParameters(String protocol, String[] cipherSuites, boolean clientAuth) { + if (protocol != null) { + this.protocol = protocol; + } + this.cipherSuites = cipherSuites != null ? Arrays.copyOf(cipherSuites, cipherSuites.length) : null; + this.clientAuth = clientAuth; + } + + /** + * Set the keystore, password, certificate type and the store type + * + * @param keyStore Location of the Keystore on disk + * @param keyPass Keystore password + * @param keyManagerType The default is X509 + * @param keyStoreType The default is JKS + */ + public void setKeyStore(String keyStore, String keyPass, String keyManagerType, String keyStoreType) { + this.keyStore = keyStore; + this.keyPass = keyPass; + if (keyManagerType != null) { + this.keyManagerType = keyManagerType; + } + if (keyStoreType != null) { + this.keyStoreType = keyStoreType; + } + isKeyStoreSet = true; + } + + /** + * Set the keystore, password, certificate type and the store type + * + * @param keyStoreStream Keystore content input stream + * @param keyPass Keystore password + * @param keyManagerType The default is X509 + * @param keyStoreType The default is JKS + */ + public void setKeyStore(InputStream keyStoreStream, String keyPass, String keyManagerType, String keyStoreType) { + this.keyStoreStream = keyStoreStream; + setKeyStore("", keyPass, keyManagerType, keyStoreType); + } + + /** + * Set the keystore and password + * + * @param keyStore Location of the Keystore on disk + * @param keyPass Keystore password + */ + public void setKeyStore(String keyStore, String keyPass) { + setKeyStore(keyStore, keyPass, null, null); + } + + /** + * Set the keystore and password + * + * @param keyStoreStream Keystore content input stream + * @param keyPass Keystore password + */ + public void setKeyStore(InputStream keyStoreStream, String keyPass) { + setKeyStore(keyStoreStream, keyPass, null, null); + } + + /** + * Set the truststore, password, certificate type and the store type + * + * @param trustStore Location of the Truststore on disk + * @param trustPass Truststore password + * @param trustManagerType The default is X509 + * @param trustStoreType The default is JKS + */ + public void setTrustStore(String trustStore, String trustPass, String trustManagerType, String trustStoreType) { + this.trustStore = trustStore; + this.trustPass = trustPass; + if (trustManagerType != null) { + this.trustManagerType = trustManagerType; + } + if (trustStoreType != null) { + this.trustStoreType = trustStoreType; + } + isTrustStoreSet = true; + } + + /** + * Set the truststore, password, certificate type and the store type + * + * @param trustStoreStream Truststore content input stream + * @param trustPass Truststore password + * @param trustManagerType The default is X509 + * @param trustStoreType The default is JKS + */ + public void setTrustStore(InputStream trustStoreStream, String trustPass, String trustManagerType, String trustStoreType) { + this.trustStoreStream = trustStoreStream; + setTrustStore("", trustPass, trustManagerType, trustStoreType); + } + + /** + * Set the truststore and password + * + * @param trustStore Location of the Truststore on disk + * @param trustPass Truststore password + */ + public void setTrustStore(String trustStore, String trustPass) { + setTrustStore(trustStore, trustPass, null, null); + } + + /** + * Set the truststore and password + * + * @param trustStoreStream Truststore content input stream + * @param trustPass Truststore password + */ + public void setTrustStore(InputStream trustStoreStream, String trustPass) { + setTrustStore(trustStoreStream, trustPass, null, null); + } + + /** + * Set if client authentication is required + * + * @param clientAuth + */ + public void requireClientAuth(boolean clientAuth) { + this.clientAuth = clientAuth; + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSaslClientTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSaslClientTransport.java new file mode 100644 index 000000000..4b1ca0a94 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSaslClientTransport.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Wraps another Thrift <code>TTransport</code>, but performs SASL client + * negotiation on the call to <code>open()</code>. This class will wrap ensuing + * communication over it, if a SASL QOP is negotiated with the other party. + */ +public class TSaslClientTransport extends TSaslTransport { + + private static final Logger LOGGER = LoggerFactory.getLogger(TSaslClientTransport.class); + + /** + * The name of the mechanism this client supports. + */ + private final String mechanism; + + /** + * Uses the given <code>SaslClient</code>. + * + * @param saslClient + * The <code>SaslClient</code> to use for the subsequent SASL + * negotiation. + * @param transport + * Transport underlying this one. + */ + public TSaslClientTransport(SaslClient saslClient, TTransport transport) { + super(saslClient, transport); + mechanism = saslClient.getMechanismName(); + } + + /** + * Creates a <code>SaslClient</code> using the given SASL-specific parameters. + * See the Java documentation for <code>Sasl.createSaslClient</code> for the + * details of the parameters. + * + * @param transport + * The underlying Thrift transport. + * @throws SaslException + */ + public TSaslClientTransport(String mechanism, String authorizationId, String protocol, + String serverName, Map<String, String> props, CallbackHandler cbh, TTransport transport) + throws SaslException { + super(Sasl.createSaslClient(new String[] { mechanism }, authorizationId, protocol, serverName, + props, cbh), transport); + this.mechanism = mechanism; + } + + + @Override + protected SaslRole getRole() { + return SaslRole.CLIENT; + } + + /** + * Performs the client side of the initial portion of the Thrift SASL + * protocol. Generates and sends the initial response to the server, including + * which mechanism this client wants to use. + */ + @Override + protected void handleSaslStartMessage() throws TTransportException, SaslException { + SaslClient saslClient = getSaslClient(); + + byte[] initialResponse = new byte[0]; + if (saslClient.hasInitialResponse()) + initialResponse = saslClient.evaluateChallenge(initialResponse); + + LOGGER.debug("Sending mechanism name {} and initial response of length {}", mechanism, + initialResponse.length); + + byte[] mechanismBytes = mechanism.getBytes(StandardCharsets.UTF_8); + sendSaslMessage(NegotiationStatus.START, + mechanismBytes); + // Send initial response + sendSaslMessage(saslClient.isComplete() ? NegotiationStatus.COMPLETE : NegotiationStatus.OK, + initialResponse); + underlyingTransport.flush(); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSaslServerTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSaslServerTransport.java new file mode 100644 index 000000000..39b81ca43 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSaslServerTransport.java @@ -0,0 +1,230 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.lang.ref.WeakReference; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; + +import javax.security.auth.callback.CallbackHandler; +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Wraps another Thrift <code>TTransport</code>, but performs SASL server + * negotiation on the call to <code>open()</code>. This class will wrap ensuing + * communication over it, if a SASL QOP is negotiated with the other party. + */ +public class TSaslServerTransport extends TSaslTransport { + + private static final Logger LOGGER = LoggerFactory.getLogger(TSaslServerTransport.class); + + /** + * Mapping from SASL mechanism name -> all the parameters required to + * instantiate a SASL server. + */ + private Map<String, TSaslServerDefinition> serverDefinitionMap = new HashMap<String, TSaslServerDefinition>(); + + /** + * Contains all the parameters used to define a SASL server implementation. + */ + private static class TSaslServerDefinition { + public String mechanism; + public String protocol; + public String serverName; + public Map<String, String> props; + public CallbackHandler cbh; + + public TSaslServerDefinition(String mechanism, String protocol, String serverName, + Map<String, String> props, CallbackHandler cbh) { + this.mechanism = mechanism; + this.protocol = protocol; + this.serverName = serverName; + this.props = props; + this.cbh = cbh; + } + } + + /** + * Uses the given underlying transport. Assumes that addServerDefinition is + * called later. + * + * @param transport + * Transport underlying this one. + */ + public TSaslServerTransport(TTransport transport) { + super(transport); + } + + /** + * Creates a <code>SaslServer</code> using the given SASL-specific parameters. + * See the Java documentation for <code>Sasl.createSaslServer</code> for the + * details of the parameters. + * + * @param transport + * The underlying Thrift transport. + */ + public TSaslServerTransport(String mechanism, String protocol, String serverName, + Map<String, String> props, CallbackHandler cbh, TTransport transport) { + super(transport); + addServerDefinition(mechanism, protocol, serverName, props, cbh); + } + + private TSaslServerTransport(Map<String, TSaslServerDefinition> serverDefinitionMap, TTransport transport) { + super(transport); + this.serverDefinitionMap.putAll(serverDefinitionMap); + } + + /** + * Add a supported server definition to this transport. See the Java + * documentation for <code>Sasl.createSaslServer</code> for the details of the + * parameters. + */ + public void addServerDefinition(String mechanism, String protocol, String serverName, + Map<String, String> props, CallbackHandler cbh) { + serverDefinitionMap.put(mechanism, new TSaslServerDefinition(mechanism, protocol, serverName, + props, cbh)); + } + + @Override + protected SaslRole getRole() { + return SaslRole.SERVER; + } + + /** + * Performs the server side of the initial portion of the Thrift SASL protocol. + * Receives the initial response from the client, creates a SASL server using + * the mechanism requested by the client (if this server supports it), and + * sends the first challenge back to the client. + */ + @Override + protected void handleSaslStartMessage() throws TTransportException, SaslException { + SaslResponse message = receiveSaslMessage(); + + LOGGER.debug("Received start message with status {}", message.status); + if (message.status != NegotiationStatus.START) { + throw sendAndThrowMessage(NegotiationStatus.ERROR, "Expecting START status, received " + message.status); + } + + // Get the mechanism name. + String mechanismName = new String(message.payload, StandardCharsets.UTF_8); + TSaslServerDefinition serverDefinition = serverDefinitionMap.get(mechanismName); + LOGGER.debug("Received mechanism name '{}'", mechanismName); + + if (serverDefinition == null) { + throw sendAndThrowMessage(NegotiationStatus.BAD, "Unsupported mechanism type " + mechanismName); + } + SaslServer saslServer = Sasl.createSaslServer(serverDefinition.mechanism, + serverDefinition.protocol, serverDefinition.serverName, serverDefinition.props, + serverDefinition.cbh); + setSaslServer(saslServer); + } + + /** + * <code>TTransportFactory</code> to create + * <code>TSaslServerTransports</code>. Ensures that a given + * underlying <code>TTransport</code> instance receives the same + * <code>TSaslServerTransport</code>. This is kind of an awful hack to work + * around the fact that Thrift is designed assuming that + * <code>TTransport</code> instances are stateless, and thus the existing + * <code>TServers</code> use different <code>TTransport</code> instances for + * input and output. + */ + public static class Factory extends TTransportFactory { + + /** + * This is the implementation of the awful hack described above. + * <code>WeakHashMap</code> is used to ensure that we don't leak memory. + */ + private static Map<TTransport, WeakReference<TSaslServerTransport>> transportMap = + Collections.synchronizedMap(new WeakHashMap<TTransport, WeakReference<TSaslServerTransport>>()); + + /** + * Mapping from SASL mechanism name -> all the parameters required to + * instantiate a SASL server. + */ + private Map<String, TSaslServerDefinition> serverDefinitionMap = new HashMap<String, TSaslServerDefinition>(); + + /** + * Create a new Factory. Assumes that <code>addServerDefinition</code> will + * be called later. + */ + public Factory() { + super(); + } + + /** + * Create a new <code>Factory</code>, initially with the single server + * definition given. You may still call <code>addServerDefinition</code> + * later. See the Java documentation for <code>Sasl.createSaslServer</code> + * for the details of the parameters. + */ + public Factory(String mechanism, String protocol, String serverName, + Map<String, String> props, CallbackHandler cbh) { + super(); + addServerDefinition(mechanism, protocol, serverName, props, cbh); + } + + /** + * Add a supported server definition to the transports created by this + * factory. See the Java documentation for + * <code>Sasl.createSaslServer</code> for the details of the parameters. + */ + public void addServerDefinition(String mechanism, String protocol, String serverName, + Map<String, String> props, CallbackHandler cbh) { + serverDefinitionMap.put(mechanism, new TSaslServerDefinition(mechanism, protocol, serverName, + props, cbh)); + } + + /** + * Get a new <code>TSaslServerTransport</code> instance, or reuse the + * existing one if a <code>TSaslServerTransport</code> has already been + * created before using the given <code>TTransport</code> as an underlying + * transport. This ensures that a given underlying transport instance + * receives the same <code>TSaslServerTransport</code>. + */ + @Override + public TTransport getTransport(TTransport base) { + WeakReference<TSaslServerTransport> ret = transportMap.get(base); + if (ret == null || ret.get() == null) { + LOGGER.debug("transport map does not contain key", base); + ret = new WeakReference<TSaslServerTransport>(new TSaslServerTransport(serverDefinitionMap, base)); + try { + ret.get().open(); + } catch (TTransportException e) { + LOGGER.debug("failed to open server transport", e); + throw new RuntimeException(e); + } + transportMap.put(base, ret); // No need for putIfAbsent(). + // Concurrent calls to getTransport() will pass in different TTransports. + } else { + LOGGER.debug("transport map does contain key {}", base); + } + return ret.get(); + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSaslTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSaslTransport.java new file mode 100644 index 000000000..4a453b68f --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSaslTransport.java @@ -0,0 +1,575 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import javax.security.sasl.Sasl; +import javax.security.sasl.SaslClient; +import javax.security.sasl.SaslException; +import javax.security.sasl.SaslServer; + +import org.apache.thrift.EncodingUtils; +import org.apache.thrift.TByteArrayOutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A superclass for SASL client/server thrift transports. A subclass need only + * implement the <code>open</code> method. + */ +abstract class TSaslTransport extends TTransport { + + private static final Logger LOGGER = LoggerFactory.getLogger(TSaslTransport.class); + + protected static final int DEFAULT_MAX_LENGTH = 0x7FFFFFFF; + + protected static final int MECHANISM_NAME_BYTES = 1; + protected static final int STATUS_BYTES = 1; + protected static final int PAYLOAD_LENGTH_BYTES = 4; + + protected static enum SaslRole { + SERVER, CLIENT; + } + + /** + * Status bytes used during the initial Thrift SASL handshake. + */ + protected static enum NegotiationStatus { + START((byte)0x01), + OK((byte)0x02), + BAD((byte)0x03), + ERROR((byte)0x04), + COMPLETE((byte)0x05); + + private final byte value; + + private static final Map<Byte, NegotiationStatus> reverseMap = + new HashMap<Byte, NegotiationStatus>(); + static { + for (NegotiationStatus s : NegotiationStatus.class.getEnumConstants()) { + reverseMap.put(s.getValue(), s); + } + } + + private NegotiationStatus(byte val) { + this.value = val; + } + + public byte getValue() { + return value; + } + + public static NegotiationStatus byValue(byte val) { + return reverseMap.get(val); + } + } + + /** + * Transport underlying this one. + */ + protected TTransport underlyingTransport; + + /** + * Either a SASL client or a SASL server. + */ + private SaslParticipant sasl; + + /** + * Whether or not we should wrap/unwrap reads/writes. Determined by whether or + * not a QOP is negotiated during the SASL handshake. + */ + private boolean shouldWrap = false; + + /** + * Buffer for input. + */ + private TMemoryInputTransport readBuffer = new TMemoryInputTransport(); + + /** + * Buffer for output. + */ + private final TByteArrayOutputStream writeBuffer = new TByteArrayOutputStream(1024); + + /** + * Create a TSaslTransport. It's assumed that setSaslServer will be called + * later to initialize the SASL endpoint underlying this transport. + * + * @param underlyingTransport + * The thrift transport which this transport is wrapping. + */ + protected TSaslTransport(TTransport underlyingTransport) { + this.underlyingTransport = underlyingTransport; + } + + /** + * Create a TSaslTransport which acts as a client. + * + * @param saslClient + * The <code>SaslClient</code> which this transport will use for SASL + * negotiation. + * @param underlyingTransport + * The thrift transport which this transport is wrapping. + */ + protected TSaslTransport(SaslClient saslClient, TTransport underlyingTransport) { + sasl = new SaslParticipant(saslClient); + this.underlyingTransport = underlyingTransport; + } + + protected void setSaslServer(SaslServer saslServer) { + sasl = new SaslParticipant(saslServer); + } + + // Used to read the status byte and payload length. + private final byte[] messageHeader = new byte[STATUS_BYTES + PAYLOAD_LENGTH_BYTES]; + + /** + * Send a complete Thrift SASL message. + * + * @param status + * The status to send. + * @param payload + * The data to send as the payload of this message. + * @throws TTransportException + */ + protected void sendSaslMessage(NegotiationStatus status, byte[] payload) throws TTransportException { + if (payload == null) + payload = new byte[0]; + + messageHeader[0] = status.getValue(); + EncodingUtils.encodeBigEndian(payload.length, messageHeader, STATUS_BYTES); + + LOGGER.debug("{}: Writing message with status {} and payload length {}", + getRole(), status, payload.length); + + underlyingTransport.write(messageHeader); + underlyingTransport.write(payload); + underlyingTransport.flush(); + } + + /** + * Read a complete Thrift SASL message. + * + * @return The SASL status and payload from this message. + * @throws TTransportException + * Thrown if there is a failure reading from the underlying + * transport, or if a status code of BAD or ERROR is encountered. + */ + protected SaslResponse receiveSaslMessage() throws TTransportException { + underlyingTransport.readAll(messageHeader, 0, messageHeader.length); + + byte statusByte = messageHeader[0]; + + NegotiationStatus status = NegotiationStatus.byValue(statusByte); + if (status == null) { + throw sendAndThrowMessage(NegotiationStatus.ERROR, "Invalid status " + statusByte); + } + + int payloadBytes = EncodingUtils.decodeBigEndian(messageHeader, STATUS_BYTES); + if (payloadBytes < 0 || payloadBytes > 104857600 /* 100 MB */) { + throw sendAndThrowMessage( + NegotiationStatus.ERROR, "Invalid payload header length: " + payloadBytes); + } + + byte[] payload = new byte[payloadBytes]; + underlyingTransport.readAll(payload, 0, payload.length); + + if (status == NegotiationStatus.BAD || status == NegotiationStatus.ERROR) { + String remoteMessage = new String(payload, StandardCharsets.UTF_8); + throw new TTransportException("Peer indicated failure: " + remoteMessage); + } + LOGGER.debug("{}: Received message with status {} and payload length {}", + getRole(), status, payload.length); + return new SaslResponse(status, payload); + } + + /** + * Send a Thrift SASL message with the given status (usually BAD or ERROR) and + * string message, and then throw a TTransportException with the given + * message. + * + * @param status + * The Thrift SASL status code to send. Usually BAD or ERROR. + * @param message + * The optional message to send to the other side. + * @throws TTransportException + * Always thrown with the message provided. + * @return always throws TTransportException but declares return type to allow + * throw sendAndThrowMessage(...) to inform compiler control flow + */ + protected TTransportException sendAndThrowMessage(NegotiationStatus status, String message) throws TTransportException { + try { + sendSaslMessage(status, message.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + LOGGER.warn("Could not send failure response", e); + message += "\nAlso, could not send response: " + e.toString(); + } + throw new TTransportException(message); + } + + /** + * Implemented by subclasses to start the Thrift SASL handshake process. When + * this method completes, the <code>SaslParticipant</code> in this class is + * assumed to be initialized. + * + * @throws TTransportException + * @throws SaslException + */ + abstract protected void handleSaslStartMessage() throws TTransportException, SaslException; + + protected abstract SaslRole getRole(); + + /** + * Opens the underlying transport if it's not already open and then performs + * SASL negotiation. If a QOP is negotiated during this SASL handshake, it used + * for all communication on this transport after this call is complete. + */ + @Override + public void open() throws TTransportException { + /* + * readSaslHeader is used to tag whether the SASL header has been read properly. + * If there is a problem in reading the header, there might not be any + * data in the stream, possibly a TCP health check from load balancer. + */ + boolean readSaslHeader = false; + + LOGGER.debug("opening transport {}", this); + if (sasl != null && sasl.isComplete()) + throw new TTransportException("SASL transport already open"); + + if (!underlyingTransport.isOpen()) + underlyingTransport.open(); + + try { + // Negotiate a SASL mechanism. The client also sends its + // initial response, or an empty one. + handleSaslStartMessage(); + readSaslHeader = true; + LOGGER.debug("{}: Start message handled", getRole()); + + SaslResponse message = null; + while (!sasl.isComplete()) { + message = receiveSaslMessage(); + if (message.status != NegotiationStatus.COMPLETE && + message.status != NegotiationStatus.OK) { + throw new TTransportException("Expected COMPLETE or OK, got " + message.status); + } + + byte[] challenge = sasl.evaluateChallengeOrResponse(message.payload); + + // If we are the client, and the server indicates COMPLETE, we don't need to + // send back any further response. + if (message.status == NegotiationStatus.COMPLETE && + getRole() == SaslRole.CLIENT) { + LOGGER.debug("{}: All done!", getRole()); + continue; + } + + sendSaslMessage(sasl.isComplete() ? NegotiationStatus.COMPLETE : NegotiationStatus.OK, + challenge); + } + LOGGER.debug("{}: Main negotiation loop complete", getRole()); + + // If we're the client, and we're complete, but the server isn't + // complete yet, we need to wait for its response. This will occur + // with ANONYMOUS auth, for example, where we send an initial response + // and are immediately complete. + if (getRole() == SaslRole.CLIENT && + (message == null || message.status == NegotiationStatus.OK)) { + LOGGER.debug("{}: SASL Client receiving last message", getRole()); + message = receiveSaslMessage(); + if (message.status != NegotiationStatus.COMPLETE) { + throw new TTransportException( + "Expected SASL COMPLETE, but got " + message.status); + } + } + } catch (SaslException e) { + try { + LOGGER.error("SASL negotiation failure", e); + throw sendAndThrowMessage(NegotiationStatus.BAD, e.getMessage()); + } finally { + underlyingTransport.close(); + } + } catch (TTransportException e) { + // If there is no-data or no-sasl header in the stream, + // log the failure, and clean up the underlying transport. + if (!readSaslHeader && e.getType() == TTransportException.END_OF_FILE) { + underlyingTransport.close(); + LOGGER.debug("No data or no sasl data in the stream during negotiation"); + } + throw e; + } + + String qop = (String) sasl.getNegotiatedProperty(Sasl.QOP); + if (qop != null && !qop.equalsIgnoreCase("auth")) + shouldWrap = true; + } + + /** + * Get the underlying <code>SaslClient</code>. + * + * @return The <code>SaslClient</code>, or <code>null</code> if this transport + * is backed by a <code>SaslServer</code>. + */ + public SaslClient getSaslClient() { + return sasl.saslClient; + } + + /** + * Get the underlying transport that Sasl is using. + * @return The <code>TTransport</code> transport + */ + public TTransport getUnderlyingTransport() { + return underlyingTransport; + } + + /** + * Get the underlying <code>SaslServer</code>. + * + * @return The <code>SaslServer</code>, or <code>null</code> if this transport + * is backed by a <code>SaslClient</code>. + */ + public SaslServer getSaslServer() { + return sasl.saslServer; + } + + /** + * Read a 4-byte word from the underlying transport and interpret it as an + * integer. + * + * @return The length prefix of the next SASL message to read. + * @throws TTransportException + * Thrown if reading from the underlying transport fails. + */ + protected int readLength() throws TTransportException { + byte[] lenBuf = new byte[4]; + underlyingTransport.readAll(lenBuf, 0, lenBuf.length); + return EncodingUtils.decodeBigEndian(lenBuf); + } + + /** + * Write the given integer as 4 bytes to the underlying transport. + * + * @param length + * The length prefix of the next SASL message to write. + * @throws TTransportException + * Thrown if writing to the underlying transport fails. + */ + protected void writeLength(int length) throws TTransportException { + byte[] lenBuf = new byte[4]; + TFramedTransport.encodeFrameSize(length, lenBuf); + underlyingTransport.write(lenBuf); + } + + // Below is the SASL implementation of the TTransport interface. + + /** + * Closes the underlying transport and disposes of the SASL implementation + * underlying this transport. + */ + @Override + public void close() { + underlyingTransport.close(); + try { + sasl.dispose(); + } catch (SaslException e) { + // Not much we can do here. + } + } + + /** + * True if the underlying transport is open and the SASL handshake is + * complete. + */ + @Override + public boolean isOpen() { + return underlyingTransport.isOpen() && sasl != null && sasl.isComplete(); + } + + /** + * Read from the underlying transport. Unwraps the contents if a QOP was + * negotiated during the SASL handshake. + */ + @Override + public int read(byte[] buf, int off, int len) throws TTransportException { + if (!isOpen()) + throw new TTransportException("SASL authentication not complete"); + + int got = readBuffer.read(buf, off, len); + if (got > 0) { + return got; + } + + // Read another frame of data + try { + readFrame(); + } catch (SaslException e) { + throw new TTransportException(e); + } catch (TTransportException transportException) { + // If there is no-data or no-sasl header in the stream, log the failure, and rethrow. + if (transportException.getType() == TTransportException.END_OF_FILE) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("No data or no sasl data in the stream during negotiation"); + } + } + throw transportException; + } + + return readBuffer.read(buf, off, len); + } + + /** + * Read a single frame of data from the underlying transport, unwrapping if + * necessary. + * + * @throws TTransportException + * Thrown if there's an error reading from the underlying transport. + * @throws SaslException + * Thrown if there's an error unwrapping the data. + */ + private void readFrame() throws TTransportException, SaslException { + int dataLength = readLength(); + + if (dataLength < 0) + throw new TTransportException("Read a negative frame size (" + dataLength + ")!"); + + byte[] buff = new byte[dataLength]; + LOGGER.debug("{}: reading data length: {}", getRole(), dataLength); + underlyingTransport.readAll(buff, 0, dataLength); + if (shouldWrap) { + buff = sasl.unwrap(buff, 0, buff.length); + LOGGER.debug("data length after unwrap: {}", buff.length); + } + readBuffer.reset(buff); + } + + /** + * Write to the underlying transport. + */ + @Override + public void write(byte[] buf, int off, int len) throws TTransportException { + if (!isOpen()) + throw new TTransportException("SASL authentication not complete"); + + writeBuffer.write(buf, off, len); + } + + /** + * Flushes to the underlying transport. Wraps the contents if a QOP was + * negotiated during the SASL handshake. + */ + @Override + public void flush() throws TTransportException { + byte[] buf = writeBuffer.get(); + int dataLength = writeBuffer.len(); + writeBuffer.reset(); + + if (shouldWrap) { + LOGGER.debug("data length before wrap: {}", dataLength); + try { + buf = sasl.wrap(buf, 0, dataLength); + } catch (SaslException e) { + throw new TTransportException(e); + } + dataLength = buf.length; + } + LOGGER.debug("writing data length: {}", dataLength); + writeLength(dataLength); + underlyingTransport.write(buf, 0, dataLength); + underlyingTransport.flush(); + } + + /** + * Used exclusively by readSaslMessage to return both a status and data. + */ + protected static class SaslResponse { + public NegotiationStatus status; + public byte[] payload; + + public SaslResponse(NegotiationStatus status, byte[] payload) { + this.status = status; + this.payload = payload; + } + } + + /** + * Used to abstract over the <code>SaslServer</code> and + * <code>SaslClient</code> classes, which share a lot of their interface, but + * unfortunately don't share a common superclass. + */ + private static class SaslParticipant { + // One of these will always be null. + public SaslServer saslServer; + public SaslClient saslClient; + + public SaslParticipant(SaslServer saslServer) { + this.saslServer = saslServer; + } + + public SaslParticipant(SaslClient saslClient) { + this.saslClient = saslClient; + } + + public byte[] evaluateChallengeOrResponse(byte[] challengeOrResponse) throws SaslException { + if (saslClient != null) { + return saslClient.evaluateChallenge(challengeOrResponse); + } else { + return saslServer.evaluateResponse(challengeOrResponse); + } + } + + public boolean isComplete() { + if (saslClient != null) + return saslClient.isComplete(); + else + return saslServer.isComplete(); + } + + public void dispose() throws SaslException { + if (saslClient != null) + saslClient.dispose(); + else + saslServer.dispose(); + } + + public byte[] unwrap(byte[] buf, int off, int len) throws SaslException { + if (saslClient != null) + return saslClient.unwrap(buf, off, len); + else + return saslServer.unwrap(buf, off, len); + } + + public byte[] wrap(byte[] buf, int off, int len) throws SaslException { + if (saslClient != null) + return saslClient.wrap(buf, off, len); + else + return saslServer.wrap(buf, off, len); + } + + public Object getNegotiatedProperty(String propName) { + if (saslClient != null) + return saslClient.getNegotiatedProperty(propName); + else + return saslServer.getNegotiatedProperty(propName); + } + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSeekableFile.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSeekableFile.java new file mode 100644 index 000000000..e02d36f6c --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSeekableFile.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; + +public interface TSeekableFile { + + public InputStream getInputStream() throws IOException; + public OutputStream getOutputStream() throws IOException; + public void close() throws IOException; + public long length() throws IOException; + public void seek(long pos) throws IOException; +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TServerSocket.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TServerSocket.java new file mode 100644 index 000000000..79f7b7f49 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TServerSocket.java @@ -0,0 +1,158 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; + +/** + * Wrapper around ServerSocket for Thrift. + * + */ +public class TServerSocket extends TServerTransport { + + private static final Logger LOGGER = LoggerFactory.getLogger(TServerSocket.class.getName()); + + /** + * Underlying ServerSocket object + */ + private ServerSocket serverSocket_ = null; + + /** + * Timeout for client sockets from accept + */ + private int clientTimeout_ = 0; + + public static class ServerSocketTransportArgs extends AbstractServerTransportArgs<ServerSocketTransportArgs> { + ServerSocket serverSocket; + + public ServerSocketTransportArgs serverSocket(ServerSocket serverSocket) { + this.serverSocket = serverSocket; + return this; + } + } + + /** + * Creates a server socket from underlying socket object + */ + public TServerSocket(ServerSocket serverSocket) throws TTransportException { + this(serverSocket, 0); + } + + /** + * Creates a server socket from underlying socket object + */ + public TServerSocket(ServerSocket serverSocket, int clientTimeout) throws TTransportException { + this(new ServerSocketTransportArgs().serverSocket(serverSocket).clientTimeout(clientTimeout)); + } + + /** + * Creates just a port listening server socket + */ + public TServerSocket(int port) throws TTransportException { + this(port, 0); + } + + /** + * Creates just a port listening server socket + */ + public TServerSocket(int port, int clientTimeout) throws TTransportException { + this(new InetSocketAddress(port), clientTimeout); + } + + public TServerSocket(InetSocketAddress bindAddr) throws TTransportException { + this(bindAddr, 0); + } + + public TServerSocket(InetSocketAddress bindAddr, int clientTimeout) throws TTransportException { + this(new ServerSocketTransportArgs().bindAddr(bindAddr).clientTimeout(clientTimeout)); + } + + public TServerSocket(ServerSocketTransportArgs args) throws TTransportException { + clientTimeout_ = args.clientTimeout; + if (args.serverSocket != null) { + this.serverSocket_ = args.serverSocket; + return; + } + try { + // Make server socket + serverSocket_ = new ServerSocket(); + // Prevent 2MSL delay problem on server restarts + serverSocket_.setReuseAddress(true); + // Bind to listening port + serverSocket_.bind(args.bindAddr, args.backlog); + } catch (IOException ioe) { + close(); + throw new TTransportException("Could not create ServerSocket on address " + args.bindAddr.toString() + ".", ioe); + } + } + + public void listen() throws TTransportException { + // Make sure to block on accept + if (serverSocket_ != null) { + try { + serverSocket_.setSoTimeout(0); + } catch (SocketException sx) { + LOGGER.error("Could not set socket timeout.", sx); + } + } + } + + protected TSocket acceptImpl() throws TTransportException { + if (serverSocket_ == null) { + throw new TTransportException(TTransportException.NOT_OPEN, "No underlying server socket."); + } + try { + Socket result = serverSocket_.accept(); + TSocket result2 = new TSocket(result); + result2.setTimeout(clientTimeout_); + return result2; + } catch (IOException iox) { + throw new TTransportException(iox); + } + } + + public void close() { + if (serverSocket_ != null) { + try { + serverSocket_.close(); + } catch (IOException iox) { + LOGGER.warn("Could not close server socket.", iox); + } + serverSocket_ = null; + } + } + + public void interrupt() { + // The thread-safeness of this is dubious, but Java documentation suggests + // that it is safe to do this from a different thread context + close(); + } + + public ServerSocket getServerSocket() { + return serverSocket_; + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TServerTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TServerTransport.java new file mode 100644 index 000000000..424e4faaa --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TServerTransport.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.io.Closeable; +import java.net.InetSocketAddress; + +/** + * Server transport. Object which provides client transports. + * + */ +public abstract class TServerTransport implements Closeable { + + public static abstract class AbstractServerTransportArgs<T extends AbstractServerTransportArgs<T>> { + int backlog = 0; // A value of 0 means the default value will be used (currently set at 50) + int clientTimeout = 0; + InetSocketAddress bindAddr; + + public T backlog(int backlog) { + this.backlog = backlog; + return (T) this; + } + + public T clientTimeout(int clientTimeout) { + this.clientTimeout = clientTimeout; + return (T) this; + } + + public T port(int port) { + this.bindAddr = new InetSocketAddress(port); + return (T) this; + } + + public T bindAddr(InetSocketAddress bindAddr) { + this.bindAddr = bindAddr; + return (T) this; + } + } + + public abstract void listen() throws TTransportException; + + public final TTransport accept() throws TTransportException { + TTransport transport = acceptImpl(); + if (transport == null) { + throw new TTransportException("accept() may not return NULL"); + } + return transport; + } + + public abstract void close(); + + protected abstract TTransport acceptImpl() throws TTransportException; + + /** + * Optional method implementation. This signals to the server transport + * that it should break out of any accept() or listen() that it is currently + * blocked on. This method, if implemented, MUST be thread safe, as it may + * be called from a different thread context than the other TServerTransport + * methods. + */ + public void interrupt() {} + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSimpleFileTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSimpleFileTransport.java new file mode 100644 index 000000000..42102d9e8 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSimpleFileTransport.java @@ -0,0 +1,216 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.io.IOException; +import java.io.RandomAccessFile; + + +/** + * Basic file support for the TTransport interface + */ +public final class TSimpleFileTransport extends TTransport { + + private RandomAccessFile file = null; + private boolean readable; + private boolean writable; + private String path_; + + + /** + * Create a transport backed by a simple file + * + * @param path the path to the file to open/create + * @param read true to support read operations + * @param write true to support write operations + * @param openFile true to open the file on construction + * @throws TTransportException if file open fails + */ + public TSimpleFileTransport(String path, boolean read, + boolean write, boolean openFile) + throws TTransportException { + if (path.length() <= 0) { + throw new TTransportException("No path specified"); + } + if (!read && !write) { + throw new TTransportException("Neither READ nor WRITE specified"); + } + readable = read; + writable = write; + path_ = path; + if (openFile) { + open(); + } + } + + /** + * Create a transport backed by a simple file + * Implicitly opens file to conform to C++ behavior. + * + * @param path the path to the file to open/create + * @param read true to support read operations + * @param write true to support write operations + * @throws TTransportException if file open fails + */ + public TSimpleFileTransport(String path, boolean read, boolean write) + throws TTransportException { + this(path, read, write, true); + } + + /** + * Create a transport backed by a simple read only disk file (implicitly opens + * file) + * + * @param path the path to the file to open/create + * @throws TTransportException if file open fails + */ + public TSimpleFileTransport(String path) throws TTransportException { + this(path, true, false, true); + } + + /** + * Test file status + * + * @return true if open, otherwise false + */ + @Override + public boolean isOpen() { + return (file != null); + } + + /** + * Open file if not previously opened. + * + * @throws TTransportException if open fails + */ + @Override + public void open() throws TTransportException { + if (file == null){ + try { + String access = "r"; //RandomAccessFile objects must be readable + if (writable) { + access += "w"; + } + file = new RandomAccessFile(path_, access); + } catch (IOException ioe) { + file = null; + throw new TTransportException(ioe.getMessage()); + } + } + } + + /** + * Close file, subsequent read/write activity will throw exceptions + */ + @Override + public void close() { + if (file != null) { + try { + file.close(); + } catch (Exception e) { + //Nothing to do + } + file = null; + } + } + + /** + * Read up to len many bytes into buf at offset + * + * @param buf houses bytes read + * @param off offset into buff to begin writing to + * @param len maximum number of bytes to read + * @return number of bytes actually read + * @throws TTransportException on read failure + */ + @Override + public int read(byte[] buf, int off, int len) throws TTransportException { + if (!readable) { + throw new TTransportException("Read operation on write only file"); + } + int iBytesRead = 0; + try { + iBytesRead = file.read(buf, off, len); + } catch (IOException ioe) { + file = null; + throw new TTransportException(ioe.getMessage()); + } + return iBytesRead; + } + + /** + * Write len many bytes from buff starting at offset + * + * @param buf buffer containing bytes to write + * @param off offset into buffer to begin writing from + * @param len number of bytes to write + * @throws TTransportException on write failure + */ + @Override + public void write(byte[] buf, int off, int len) throws TTransportException { + try { + file.write(buf, off, len); + } catch (IOException ioe) { + file = null; + throw new TTransportException(ioe.getMessage()); + } + } + + /** + * Move file pointer to specified offset, new read/write calls will act here + * + * @param offset bytes from beginning of file to move pointer to + * @throws TTransportException is seek fails + */ + public void seek(long offset) throws TTransportException { + try { + file.seek(offset); + } catch (IOException ex) { + throw new TTransportException(ex.getMessage()); + } + } + + /** + * Return the length of the file in bytes + * + * @return length of the file in bytes + * @throws TTransportException if file access fails + */ + public long length() throws TTransportException { + try { + return file.length(); + } catch (IOException ex) { + throw new TTransportException(ex.getMessage()); + } + } + + /** + * Return current file pointer position in bytes from beginning of file + * + * @return file pointer position + * @throws TTransportException if file access fails + */ + public long getFilePointer() throws TTransportException { + try { + return file.getFilePointer(); + } catch (IOException ex) { + throw new TTransportException(ex.getMessage()); + } + } +}
\ No newline at end of file diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSocket.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSocket.java new file mode 100644 index 000000000..b20b32b78 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TSocket.java @@ -0,0 +1,248 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketException; + +/** + * Socket implementation of the TTransport interface. To be commented soon! + * + */ +public class TSocket extends TIOStreamTransport { + + private static final Logger LOGGER = LoggerFactory.getLogger(TSocket.class.getName()); + + /** + * Wrapped Socket object + */ + private Socket socket_; + + /** + * Remote host + */ + private String host_; + + /** + * Remote port + */ + private int port_; + + /** + * Socket timeout - read timeout on the socket + */ + private int socketTimeout_; + + /** + * Connection timeout + */ + private int connectTimeout_; + + /** + * Constructor that takes an already created socket. + * + * @param socket Already created socket object + * @throws TTransportException if there is an error setting up the streams + */ + public TSocket(Socket socket) throws TTransportException { + socket_ = socket; + try { + socket_.setSoLinger(false, 0); + socket_.setTcpNoDelay(true); + socket_.setKeepAlive(true); + } catch (SocketException sx) { + LOGGER.warn("Could not configure socket.", sx); + } + + if (isOpen()) { + try { + inputStream_ = new BufferedInputStream(socket_.getInputStream()); + outputStream_ = new BufferedOutputStream(socket_.getOutputStream()); + } catch (IOException iox) { + close(); + throw new TTransportException(TTransportException.NOT_OPEN, iox); + } + } + } + + /** + * Creates a new unconnected socket that will connect to the given host + * on the given port. + * + * @param host Remote host + * @param port Remote port + */ + public TSocket(String host, int port) { + this(host, port, 0); + } + + /** + * Creates a new unconnected socket that will connect to the given host + * on the given port. + * + * @param host Remote host + * @param port Remote port + * @param timeout Socket timeout and connection timeout + */ + public TSocket(String host, int port, int timeout) { + this(host, port, timeout, timeout); + } + + /** + * Creates a new unconnected socket that will connect to the given host + * on the given port, with a specific connection timeout and a + * specific socket timeout. + * + * @param host Remote host + * @param port Remote port + * @param socketTimeout Socket timeout + * @param connectTimeout Connection timeout + */ + public TSocket(String host, int port, int socketTimeout, int connectTimeout) { + host_ = host; + port_ = port; + socketTimeout_ = socketTimeout; + connectTimeout_ = connectTimeout; + initSocket(); + } + + /** + * Initializes the socket object + */ + private void initSocket() { + socket_ = new Socket(); + try { + socket_.setSoLinger(false, 0); + socket_.setTcpNoDelay(true); + socket_.setKeepAlive(true); + socket_.setSoTimeout(socketTimeout_); + } catch (SocketException sx) { + LOGGER.error("Could not configure socket.", sx); + } + } + + /** + * Sets the socket timeout and connection timeout. + * + * @param timeout Milliseconds timeout + */ + public void setTimeout(int timeout) { + this.setConnectTimeout(timeout); + this.setSocketTimeout(timeout); + } + + /** + * Sets the time after which the connection attempt will time out + * + * @param timeout Milliseconds timeout + */ + public void setConnectTimeout(int timeout) { + connectTimeout_ = timeout; + } + + /** + * Sets the socket timeout + * + * @param timeout Milliseconds timeout + */ + public void setSocketTimeout(int timeout) { + socketTimeout_ = timeout; + try { + socket_.setSoTimeout(timeout); + } catch (SocketException sx) { + LOGGER.warn("Could not set socket timeout.", sx); + } + } + + /** + * Returns a reference to the underlying socket. + */ + public Socket getSocket() { + if (socket_ == null) { + initSocket(); + } + return socket_; + } + + /** + * Checks whether the socket is connected. + */ + public boolean isOpen() { + if (socket_ == null) { + return false; + } + return socket_.isConnected(); + } + + /** + * Connects the socket, creating a new socket object if necessary. + */ + public void open() throws TTransportException { + if (isOpen()) { + throw new TTransportException(TTransportException.ALREADY_OPEN, "Socket already connected."); + } + + if (host_ == null || host_.length() == 0) { + throw new TTransportException(TTransportException.NOT_OPEN, "Cannot open null host."); + } + if (port_ <= 0 || port_ > 65535) { + throw new TTransportException(TTransportException.NOT_OPEN, "Invalid port " + port_); + } + + if (socket_ == null) { + initSocket(); + } + + try { + socket_.connect(new InetSocketAddress(host_, port_), connectTimeout_); + inputStream_ = new BufferedInputStream(socket_.getInputStream()); + outputStream_ = new BufferedOutputStream(socket_.getOutputStream()); + } catch (IOException iox) { + close(); + throw new TTransportException(TTransportException.NOT_OPEN, iox); + } + } + + /** + * Closes the socket. + */ + public void close() { + // Close the underlying streams + super.close(); + + // Close the socket + if (socket_ != null) { + try { + socket_.close(); + } catch (IOException iox) { + LOGGER.warn("Could not close socket.", iox); + } + socket_ = null; + } + } + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TStandardFile.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TStandardFile.java new file mode 100644 index 000000000..7a33af8ee --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TStandardFile.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.io.FileInputStream; +import java.io.FileOutputStream; + +public class TStandardFile implements TSeekableFile { + + protected String path_ = null; + protected RandomAccessFile inputFile_ = null; + + public TStandardFile(String path) throws IOException { + path_ = path; + inputFile_ = new RandomAccessFile(path_, "r"); + } + + public InputStream getInputStream() throws IOException { + return new FileInputStream(inputFile_.getFD()); + } + + public OutputStream getOutputStream() throws IOException { + return new FileOutputStream(path_); + } + + public void close() throws IOException { + if(inputFile_ != null) { + inputFile_.close(); + } + } + + public long length() throws IOException { + return inputFile_.length(); + } + + public void seek(long pos) throws IOException { + inputFile_.seek(pos); + } +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TTransport.java new file mode 100644 index 000000000..73ad730ce --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TTransport.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.io.Closeable; + +/** + * Generic class that encapsulates the I/O layer. This is basically a thin + * wrapper around the combined functionality of Java input/output streams. + * + */ +public abstract class TTransport implements Closeable { + + /** + * Queries whether the transport is open. + * + * @return True if the transport is open. + */ + public abstract boolean isOpen(); + + /** + * Is there more data to be read? + * + * @return True if the remote side is still alive and feeding us + */ + public boolean peek() { + return isOpen(); + } + + /** + * Opens the transport for reading/writing. + * + * @throws TTransportException if the transport could not be opened + */ + public abstract void open() + throws TTransportException; + + /** + * Closes the transport. + */ + public abstract void close(); + + /** + * Reads up to len bytes into buffer buf, starting at offset off. + * + * @param buf Array to read into + * @param off Index to start reading at + * @param len Maximum number of bytes to read + * @return The number of bytes actually read + * @throws TTransportException if there was an error reading data + */ + public abstract int read(byte[] buf, int off, int len) + throws TTransportException; + + /** + * Guarantees that all of len bytes are actually read off the transport. + * + * @param buf Array to read into + * @param off Index to start reading at + * @param len Maximum number of bytes to read + * @return The number of bytes actually read, which must be equal to len + * @throws TTransportException if there was an error reading data + */ + public int readAll(byte[] buf, int off, int len) + throws TTransportException { + int got = 0; + int ret = 0; + while (got < len) { + ret = read(buf, off+got, len-got); + if (ret <= 0) { + throw new TTransportException( + "Cannot read. Remote side has closed. Tried to read " + + len + + " bytes, but only got " + + got + + " bytes. (This is often indicative of an internal error on the server side. Please check your server logs.)"); + } + got += ret; + } + return got; + } + + /** + * Writes the buffer to the output + * + * @param buf The output data buffer + * @throws TTransportException if an error occurs writing data + */ + public void write(byte[] buf) throws TTransportException { + write(buf, 0, buf.length); + } + + /** + * Writes up to len bytes from the buffer. + * + * @param buf The output data buffer + * @param off The offset to start writing from + * @param len The number of bytes to write + * @throws TTransportException if there was an error writing data + */ + public abstract void write(byte[] buf, int off, int len) + throws TTransportException; + + /** + * Flush any pending data out of a transport buffer. + * + * @throws TTransportException if there was an error writing out data. + */ + public void flush() + throws TTransportException {} + + /** + * Access the protocol's underlying buffer directly. If this is not a + * buffered transport, return null. + * @return protocol's Underlying buffer + */ + public byte[] getBuffer() { + return null; + } + + /** + * Return the index within the underlying buffer that specifies the next spot + * that should be read from. + * @return index within the underlying buffer that specifies the next spot + * that should be read from + */ + public int getBufferPosition() { + return 0; + } + + /** + * Get the number of bytes remaining in the underlying buffer. Returns -1 if + * this is a non-buffered transport. + * @return the number of bytes remaining in the underlying buffer. <br> Returns -1 if + * this is a non-buffered transport. + */ + public int getBytesRemainingInBuffer() { + return -1; + } + + /** + * Consume len bytes from the underlying buffer. + * @param len + */ + public void consumeBuffer(int len) {} +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TTransportException.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TTransportException.java new file mode 100644 index 000000000..b886bc269 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TTransportException.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import org.apache.thrift.TException; + +/** + * Transport exceptions. + * + */ +public class TTransportException extends TException { + + private static final long serialVersionUID = 1L; + + public static final int UNKNOWN = 0; + public static final int NOT_OPEN = 1; + public static final int ALREADY_OPEN = 2; + public static final int TIMED_OUT = 3; + public static final int END_OF_FILE = 4; + public static final int CORRUPTED_DATA = 5; + + protected int type_ = UNKNOWN; + + public TTransportException() { + super(); + } + + public TTransportException(int type) { + super(); + type_ = type; + } + + public TTransportException(int type, String message) { + super(message); + type_ = type; + } + + public TTransportException(String message) { + super(message); + } + + public TTransportException(int type, Throwable cause) { + super(cause); + type_ = type; + } + + public TTransportException(Throwable cause) { + super(cause); + } + + public TTransportException(String message, Throwable cause) { + super(message, cause); + } + + public TTransportException(int type, String message, Throwable cause) { + super(message, cause); + type_ = type; + } + + public int getType() { + return type_; + } + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TTransportFactory.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TTransportFactory.java new file mode 100644 index 000000000..3e71630ae --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TTransportFactory.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +/** + * Factory class used to create wrapped instance of Transports. + * This is used primarily in servers, which get Transports from + * a ServerTransport and then may want to mutate them (i.e. create + * a BufferedTransport from the underlying base transport) + * + */ +public class TTransportFactory { + + /** + * Return a wrapped instance of the base Transport. + * + * @param trans The base transport + * @return Wrapped Transport + */ + public TTransport getTransport(TTransport trans) { + return trans; + } + +} diff --git a/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TZlibTransport.java b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TZlibTransport.java new file mode 100644 index 000000000..e755aa532 --- /dev/null +++ b/src/jaegertracing/thrift/lib/java/src/org/apache/thrift/transport/TZlibTransport.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.thrift.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +/** + * TZlibTransport deflates on write and inflates on read. + */ +public class TZlibTransport extends TIOStreamTransport { + + private TTransport transport_ = null; + + public static class Factory extends TTransportFactory { + public Factory() { + } + + @Override + public TTransport getTransport(TTransport base) { + return new TZlibTransport(base); + } + } + + /** + * Constructs a new TZlibTransport instance. + * @param transport the underlying transport to read from and write to + */ + public TZlibTransport(TTransport transport) { + this(transport, Deflater.BEST_COMPRESSION); + } + + /** + * Constructs a new TZlibTransport instance. + * @param transport the underlying transport to read from and write to + * @param compressionLevel 0 for no compression, 9 for maximum compression + */ + public TZlibTransport(TTransport transport, int compressionLevel) { + transport_ = transport; + inputStream_ = new InflaterInputStream(new TTransportInputStream(transport_), new Inflater()); + outputStream_ = new DeflaterOutputStream(new TTransportOutputStream(transport_), new Deflater(compressionLevel, false), true); + } + + @Override + public boolean isOpen() { + return transport_.isOpen(); + } + + @Override + public void open() throws TTransportException { + transport_.open(); + } + + @Override + public void close() { + super.close(); + if (transport_.isOpen()) { + transport_.close(); + } + } +} + +class TTransportInputStream extends InputStream { + + private TTransport transport = null; + + public TTransportInputStream(TTransport transport) { + this.transport = transport; + } + + @Override + public int read() throws IOException { + try { + byte[] buf = new byte[1]; + transport.read(buf, 0, 1); + return buf[0]; + } catch (TTransportException e) { + throw new IOException(e); + } + } + + @Override + public int read(byte b[], int off, int len) throws IOException { + try { + return transport.read(b, off, len); + } catch (TTransportException e) { + throw new IOException(e); + } + } +} + +class TTransportOutputStream extends OutputStream { + + private TTransport transport = null; + + public TTransportOutputStream(TTransport transport) { + this.transport = transport; + } + + @Override + public void write(final int b) throws IOException { + try { + transport.write(new byte[]{(byte) b}); + } catch (TTransportException e) { + throw new IOException(e); + } + } + + @Override + public void write(byte b[], int off, int len) throws IOException { + try { + transport.write(b, off, len); + } catch (TTransportException e) { + throw new IOException(e); + } + } + + @Override + public void flush() throws IOException { + try { + transport.flush(); + } catch (TTransportException e) { + throw new IOException(e); + } + } +} + |