Ft/mspf 278/add network error mapper (#25)

* MSPF-278: Add network error mapper

* MSPF-198: Add libthrift as module

* MSPF-278: Fix tests: eliminating PROVIDER_ERROR

* MSPF-278: Disable SocketTest
This commit is contained in:
Vladimir Pankrashkin 2017-08-09 15:59:30 +03:00 committed by GitHub
parent f4c5ed0125
commit 41effb76f0
125 changed files with 16640 additions and 32 deletions

57
libthrift/pom.xml Normal file
View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>woody</artifactId>
<groupId>com.rbkmoney.woody</groupId>
<version>1.1.6</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>libthrift</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.6</version>
</dependency>
<dependency>
<groupId>com.rbkmoney.woody</groupId>
<artifactId>woody-api</artifactId>
<scope>provided</scope>
</dependency>
<!--Thirdparty libs-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!--Test libs-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -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 org.apache.thrift.async.AsyncMethodCallback;
import org.apache.thrift.protocol.TMessage;
import org.apache.thrift.protocol.TMessageType;
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();
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,121 @@
/*
* 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> {
/**
* 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.toString()+")";
}
}
/**
* 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 new Some<T>(value);
} else {
return new None<T>();
}
}
/**
* 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);
}
public static <T> None<T> none() {
return new None<T>();
}
}

View File

@ -0,0 +1,63 @@
/**
*
*/
package org.apache.thrift;
import com.rbkmoney.woody.api.event.CallType;
import com.rbkmoney.woody.api.trace.ContextUtils;
import com.rbkmoney.woody.api.trace.Metadata;
import com.rbkmoney.woody.api.trace.MetadataProperties;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.api.trace.context.TraceContext;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.rbkmoney.woody.api.trace.context.TraceContext.getCurrentTraceData;
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 {
getCurrentTraceData().getServiceSpan().getMetadata().putValue(MetadataProperties.CALL_TYPE, isOneway() ? CallType.CAST : CallType.CALL);
T args = getEmptyArgsInstance();
args.read(iprot);
iprot.readMessageEnd();
TBase result = null;
try {
result = getResult(iface, args);
} catch(TException tex) {
LOGGER.error("Internal error processing " + getMethodName(), tex);
throw tex;
}
if(!isOneway()) {
oprot.writeMessageBegin(new TMessage(getMethodName(), TMessageType.REPLY, seqid));
result.write(oprot);
oprot.writeMessageEnd();
oprot.getTransport().flush();
}
}
protected abstract boolean isOneway();
public abstract TBase getResult(I iface, T args) throws TException;
public abstract T getEmptyArgsInstance();
public String getMethodName() {
return methodName;
}
}

View File

@ -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;
/**
* 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.
*/
public class ShortStack {
private short[] vector;
private int top = -1;
public ShortStack(int initialCapacity) {
vector = new short[initialCapacity];
}
public short pop() {
return vector[top--];
}
public void push(short pushed) {
if (vector.length == top + 1) {
grow();
}
vector[++top] = pushed;
}
private void grow() {
short[] newVector = new short[vector.length * 2];
System.arraycopy(vector, 0, newVector, 0, vector.length);
vector = newVector;
}
public short peek() {
return vector[top];
}
public void clear() {
top = -1;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("<ShortStack vector:[");
for (int i = 0; i < vector.length; i++) {
if (i != 0) {
sb.append(" ");
}
if (i == top) {
sb.append(">>");
}
sb.append(vector[i]);
if (i == top) {
sb.append("<<");
}
}
sb.append("]>");
return sb.toString();
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,35 @@
/*
* 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.server.AbstractNonblockingServer.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Map;
public interface TAsyncProcessor {
/**
* Implementations must call fb.responseReady() once processing is complete
*/
public boolean process(final AsyncFrameBuffer fb) throws TException;
}

View File

@ -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;
import java.io.Serializable;
import org.apache.thrift.protocol.TProtocol;
import java.util.Map;
/**
* 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);
/**
* Get all fields.
*
* @return fields
*/
public F[] getFields();
/**
* Get Fields meta data
*
* @return field meta data
*/
public Map<F, org.apache.thrift.meta_data.FieldMetaData> getFieldMetaData();
/**
* 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();
}

View File

@ -0,0 +1,105 @@
/*
* 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 boolean 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();
if (!fn.isOneway()) {
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();
}
fb.responseReady();
return true;
}
//Get Args
TBase args = fn.getEmptyArgsInstance();
try {
args.read(in);
} catch (TProtocolException e) {
in.readMessageEnd();
if (!fn.isOneway()) {
TApplicationException x = new TApplicationException(TApplicationException.PROTOCOL_ERROR, e.getMessage());
out.writeMessageBegin(new TMessage(msg.name, TMessageType.EXCEPTION, msg.seqid));
x.write(out);
out.writeMessageEnd();
out.getTransport().flush();
}
fb.responseReady();
return true;
}
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) {
resultHandler.onError(e);
}
return true;
}
@Override
public boolean process(TProtocol in, TProtocol out) throws TException {
return false;
}
}

View File

@ -0,0 +1,337 @@
/**
* 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.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Collection;
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.valueOf(a).compareTo(b);
}
public static int compareTo(byte a, byte b) {
if (a < b) {
return -1;
} else if (b < a) {
return 1;
} else {
return 0;
}
}
public static int compareTo(short a, short b) {
if (a < b) {
return -1;
} else if (b < a) {
return 1;
} else {
return 0;
}
}
public static int compareTo(int a, int b) {
if (a < b) {
return -1;
} else if (b < a) {
return 1;
} else {
return 0;
}
}
public static int compareTo(long a, long b) {
if (a < b) {
return -1;
} else if (b < a) {
return 1;
} else {
return 0;
}
}
public static int compareTo(double a, double b) {
if (a < b) {
return -1;
} else if (b < a) {
return 1;
} else {
return 0;
}
}
public static int compareTo(String a, String b) {
return a.compareTo(b);
}
public static int compareTo(byte[] a, byte[] b) {
int sizeCompare = compareTo(a.length, b.length);
if (sizeCompare != 0) {
return sizeCompare;
}
for (int i = 0; i < a.length; i++) {
int byteCompare = compareTo(a[i], b[i]);
if (byteCompare != 0) {
return byteCompare;
}
}
return 0;
}
public static int compareTo(Comparable a, Comparable b) {
return a.compareTo(b);
}
public static int compareTo(List a, List b) {
int lastComparison = compareTo(a.size(), b.size());
if (lastComparison != 0) {
return lastComparison;
}
for (int i = 0; i < a.size(); i++) {
lastComparison = comparator.compare(a.get(i), b.get(i));
if (lastComparison != 0) {
return lastComparison;
}
}
return 0;
}
public static int compareTo(Set a, Set b) {
int lastComparison = compareTo(a.size(), b.size());
if (lastComparison != 0) {
return lastComparison;
}
SortedSet sortedA = new TreeSet(comparator);
sortedA.addAll(a);
SortedSet sortedB = new TreeSet(comparator);
sortedB.addAll(b);
Iterator iterA = sortedA.iterator();
Iterator iterB = sortedB.iterator();
// Compare each item.
while (iterA.hasNext() && iterB.hasNext()) {
lastComparison = comparator.compare(iterA.next(), iterB.next());
if (lastComparison != 0) {
return lastComparison;
}
}
return 0;
}
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) {
if (orig == null) {
return null;
}
byte[] copy = new byte[orig.length];
System.arraycopy(orig, 0, copy, 0, orig.length);
return copy;
}
public static int hashCode(long value) {
int low = (int) value;
int high = (int) (value >>> 32);
return high * 127 + low;
}
public static int hashCode(double value) {
return hashCode(Double.doubleToRawLongBits(value));
}
}

View File

@ -0,0 +1,44 @@
package org.apache.thrift;
import java.util.Collections;
import java.util.Map;
import com.rbkmoney.woody.api.trace.ContextUtils;
import com.rbkmoney.woody.api.trace.MetadataProperties;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.api.trace.context.TraceContext;
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;
import static com.rbkmoney.woody.api.trace.context.TraceContext.getCurrentTraceData;
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 boolean process(TProtocol in, TProtocol out) throws TException {
TMessage msg = in.readMessageBegin();
getCurrentTraceData().getServiceSpan().getMetadata().putValue(MetadataProperties.CALL_NAME, msg.name);
ProcessFunction fn = processMap.get(msg.name);
if (fn == null) {
TProtocolUtil.skip(in, TType.STRUCT);
in.readMessageEnd();
throw new TApplicationException(TApplicationException.UNKNOWN_METHOD, "Invalid method name: '"+msg.name+"'");
}
fn.process(msg.seqid, in, out, iface);
return true;
}
}

View File

@ -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.io.ByteArrayOutputStream;
/**
* 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;
}
}

View File

@ -0,0 +1,358 @@
/*
* 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 this point is reached, iprot will be positioned at the start of the field.
switch(ttype){
case TType.BOOL:
if (field.type == TType.BOOL){
return protocol_.readBool();
}
break;
case TType.BYTE:
if (field.type == TType.BYTE) {
return protocol_.readByte();
}
break;
case TType.DOUBLE:
if (field.type == TType.DOUBLE) {
return protocol_.readDouble();
}
break;
case TType.I16:
if (field.type == TType.I16) {
return protocol_.readI16();
}
break;
case TType.I32:
if (field.type == TType.I32) {
return protocol_.readI32();
}
break;
case TType.I64:
if (field.type == TType.I64) {
return protocol_.readI64();
}
break;
case TType.STRING:
if (field.type == TType.STRING) {
return protocol_.readString();
}
break;
case 100: // hack to differentiate between string and binary
if (field.type == TType.STRING) {
return protocol_.readBinary();
}
break;
}
}
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;
for (int i = 0; i < fieldIdPathRest.length; i++){
fieldIdPath[i + 1] = fieldIdPathRest[i];
}
// 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());
}
}

View File

@ -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();
}

View File

@ -0,0 +1,57 @@
/*
* 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.InstantiationException;
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;
}
}
}

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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;
}

View File

@ -0,0 +1,143 @@
/*
* 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>();
/**
* '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);
}
/**
* 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 TException 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 boolean 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) {
// TODO Apache Guys - Can the server ever get an EXCEPTION or REPLY?
// TODO Should we check for this here?
throw new TException("This should not have happened!?");
}
// Extract the service name
int index = message.name.indexOf(TMultiplexedProtocol.SEPARATOR);
if (index < 0) {
throw new TException("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 TException("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
return 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;
}
}
}

View File

@ -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());
}
}
}
}

View File

@ -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; }
}

View File

@ -0,0 +1,32 @@
/*
* 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 boolean process(TProtocol in, TProtocol out)
throws TException;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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));
}
}

View File

@ -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;
import com.rbkmoney.woody.api.interceptor.CommonInterceptor;
import com.rbkmoney.woody.api.trace.MetadataProperties;
import com.rbkmoney.woody.api.trace.context.TraceContext;
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 CommonInterceptor interceptor;
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_;
}
public CommonInterceptor getInterceptor() {
return interceptor;
}
public void setInterceptor(CommonInterceptor interceptor) {
this.interceptor = interceptor;
}
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 {
TMessage msg = new TMessage(methodName, type, ++seqid_);
oprot_.writeMessageBegin(msg);
args.write(oprot_);
oprot_.writeMessageEnd();
oprot_.getTransport().flush();
}
protected void receiveBase(TBase<?,?> result, String methodName) throws TException {
if (TraceContext.getCurrentTraceData().getActiveSpan().getMetadata().containsKey(MetadataProperties.RESPONSE_SKIP_READING_FLAG)){
return;
}
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();
}
}

View File

@ -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);
}

View File

@ -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();
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();
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);
}
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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());
}
}
}
}

View File

@ -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 &lt;Exception_1&gt;, &lt;Exception_2&gt;, ...</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);
}
}

View File

@ -0,0 +1,14 @@
package org.apache.thrift.meta_data;
public class CollectionMetaData extends FieldValueMetaData {
public final FieldValueMetaData elemMetaData;
public CollectionMetaData(byte type, FieldValueMetaData eMetaData){
super(type);
this.elemMetaData = eMetaData;
}
public FieldValueMetaData getElementMetaData() {
return elemMetaData;
}
}

View File

@ -0,0 +1,35 @@
/*
* 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;
}
public Class<? extends TEnum> getEnumClass() {
return enumClass;
}
}

View File

@ -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);
}
}

View File

@ -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.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 int getType() {
return type;
}
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;
}
}

View File

@ -0,0 +1,28 @@
/*
* 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 CollectionMetaData {
public ListMetaData(byte type, FieldValueMetaData eMetaData){
super(type, eMetaData);
}
}

View File

@ -0,0 +1,39 @@
/*
* 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;
}
public FieldValueMetaData getKeyMetaData() {
return keyMetaData;
}
public FieldValueMetaData getValueMetaData() {
return valueMetaData;
}
}

View File

@ -0,0 +1,28 @@
/*
* 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 CollectionMetaData {
public SetMetaData(byte type, FieldValueMetaData eMetaData){
super(type, eMetaData);
}
}

View File

@ -0,0 +1,35 @@
/*
* 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;
}
public Class<? extends TBase> getStructClass() {
return structClass;
}
}

View File

@ -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]);
}
}
}
}

View File

@ -0,0 +1,426 @@
/*
* 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 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;
}
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);
}
}
public void writeMessageEnd() {}
public void writeStructBegin(TStruct struct) {}
public void writeStructEnd() {}
public void writeFieldBegin(TField field) throws TException {
writeByte(field.type);
writeI16(field.id);
}
public void writeFieldEnd() {}
public void writeFieldStop() throws TException {
writeByte(TType.STOP);
}
public void writeMapBegin(TMap map) throws TException {
writeByte(map.keyType);
writeByte(map.valueType);
writeI32(map.size);
}
public void writeMapEnd() {}
public void writeListBegin(TList list) throws TException {
writeByte(list.elemType);
writeI32(list.size);
}
public void writeListEnd() {}
public void writeSetBegin(TSet set) throws TException {
writeByte(set.elemType);
writeI32(set.size);
}
public void writeSetEnd() {}
public void writeBool(boolean b) throws TException {
writeByte(b ? (byte)1 : (byte)0);
}
public void writeByte(byte b) throws TException {
inoutTemp[0] = b;
trans_.write(inoutTemp, 0, 1);
}
public void writeI16(short i16) throws TException {
inoutTemp[0] = (byte)(0xff & (i16 >> 8));
inoutTemp[1] = (byte)(0xff & (i16));
trans_.write(inoutTemp, 0, 2);
}
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);
}
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);
}
public void writeDouble(double dub) throws TException {
writeI64(Double.doubleToLongBits(dub));
}
public void writeString(String str) throws TException {
try {
byte[] dat = str.getBytes("UTF-8");
writeI32(dat.length);
trans_.write(dat, 0, dat.length);
} catch (UnsupportedEncodingException uex) {
throw new TException("JVM DOES NOT SUPPORT UTF-8");
}
}
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.
*/
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());
}
}
public void readMessageEnd() {}
public TStruct readStructBegin() {
return ANONYMOUS_STRUCT;
}
public void readStructEnd() {}
public TField readFieldBegin() throws TException {
byte type = readByte();
short id = type == TType.STOP ? 0 : readI16();
return new TField("", type, id);
}
public void readFieldEnd() {}
public TMap readMapBegin() throws TException {
TMap map = new TMap(readByte(), readByte(), readI32());
checkContainerReadLength(map.size);
return map;
}
public void readMapEnd() {}
public TList readListBegin() throws TException {
TList list = new TList(readByte(), readI32());
checkContainerReadLength(list.size);
return list;
}
public void readListEnd() {}
public TSet readSetBegin() throws TException {
TSet set = new TSet(readByte(), readI32());
checkContainerReadLength(set.size);
return set;
}
public void readSetEnd() {}
public boolean readBool() throws TException {
return (readByte() == 1);
}
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];
}
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)));
}
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));
}
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));
}
public double readDouble() throws TException {
return Double.longBitsToDouble(readI64());
}
public String readString() throws TException {
int size = readI32();
checkStringReadLength(size);
if (trans_.getBytesRemainingInBuffer() >= size) {
try {
String s = new String(trans_.getBuffer(), trans_.getBufferPosition(), size, "UTF-8");
trans_.consumeBuffer(size);
return s;
} catch (UnsupportedEncodingException e) {
throw new TException("JVM DOES NOT SUPPORT UTF-8");
}
}
return readStringBody(size);
}
public String readStringBody(int size) throws TException {
try {
byte[] buf = new byte[size];
trans_.readAll(buf, 0, size);
return new String(buf, "UTF-8");
} catch (UnsupportedEncodingException uex) {
throw new TException("JVM DOES NOT SUPPORT UTF-8");
}
}
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);
}
}

View File

@ -0,0 +1,908 @@
/*
* 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 org.apache.thrift.ShortStack;
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.
*/
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.
*/
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 (&lt; 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 {
try {
byte[] bytes = str.getBytes("UTF-8");
writeBinary(bytes, 0, bytes.length);
} catch (UnsupportedEncodingException e) {
throw new TException("UTF-8 not supported!");
}
}
/**
* 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 "";
}
try {
if (trans_.getBytesRemainingInBuffer() >= length) {
String str = new String(trans_.getBuffer(), trans_.getBufferPosition(), length, "UTF-8");
trans_.consumeBuffer(length);
return str;
} else {
return new String(readBinary(length), "UTF-8");
}
} catch (UnsupportedEncodingException e) {
throw new TException("UTF-8 not supported!");
}
}
/**
* 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];
}
}

View File

@ -0,0 +1,66 @@
/*
* 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.
*
*/
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 + ((name == null) ? 0 : name.hashCode());
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;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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_;
}
}

View File

@ -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);
}

View File

@ -0,0 +1,220 @@
/*
* 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:
break;
}
}
/**
* 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 &lt; 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;
}
}

View File

@ -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;
}

View File

@ -0,0 +1,448 @@
/*
* 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.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);
}
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);
}
public void writeMessageEnd() throws TException {
popWriteContext();
trans_.write(RBRACKET);
}
public void writeStructBegin(TStruct struct) throws TException {
writeContext_.write();
trans_.write(LBRACE);
pushWriteContext(new StructContext());
}
public void writeStructEnd() throws TException {
popWriteContext();
trans_.write(RBRACE);
}
public void writeFieldBegin(TField field) throws TException {
// Note that extra type information is omitted in JSON!
writeString(field.name);
}
public void writeFieldEnd() {}
public void writeFieldStop() {}
public void writeMapBegin(TMap map) throws TException {
assertContextIsNotMapKey(MAP);
writeContext_.write();
trans_.write(LBRACE);
pushWriteContext(new MapContext());
// No metadata!
}
public void writeMapEnd() throws TException {
popWriteContext();
trans_.write(RBRACE);
}
public void writeListBegin(TList list) throws TException {
assertContextIsNotMapKey(LIST);
writeContext_.write();
trans_.write(LBRACKET);
pushWriteContext(new ListContext());
// No metadata!
}
public void writeListEnd() throws TException {
popWriteContext();
trans_.write(RBRACKET);
}
public void writeSetBegin(TSet set) throws TException {
assertContextIsNotMapKey(SET);
writeContext_.write();
trans_.write(LBRACKET);
pushWriteContext(new ListContext());
// No metadata!
}
public void writeSetEnd() throws TException {
popWriteContext();
trans_.write(RBRACKET);
}
public void writeBool(boolean b) throws TException {
writeByte(b ? (byte)1 : (byte)0);
}
public void writeByte(byte b) throws TException {
writeI32(b);
}
public void writeI16(short i16) throws TException {
writeI32(i16);
}
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 {
try {
byte[] b = s.getBytes("UTF-8");
trans_.write(b);
} catch (UnsupportedEncodingException uex) {
throw new TException("JVM DOES NOT SUPPORT UTF-8");
}
}
public void writeI64(long i64) throws TException {
if(writeContext_.isMapKey()) {
writeString(Long.toString(i64));
} else {
writeContext_.write();
_writeStringData(Long.toString(i64));
}
}
public void writeDouble(double dub) throws TException {
if(writeContext_.isMapKey()) {
writeString(Double.toString(dub));
} else {
writeContext_.write();
_writeStringData(Double.toString(dub));
}
}
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());
}
public void writeBinary(ByteBuffer bin) throws TException {
try {
// TODO(mcslee): Fix this
writeString(new String(bin.array(), bin.position() + bin.arrayOffset(), bin.limit() - bin.position() - bin.arrayOffset(), "UTF-8"));
} catch (UnsupportedEncodingException uex) {
throw new TException("JVM DOES NOT SUPPORT UTF-8");
}
}
/**
* Reading methods.
*/
public TMessage readMessageBegin() throws TException {
// TODO(mcslee): implement
return EMPTY_MESSAGE;
}
public void readMessageEnd() {}
public TStruct readStructBegin() {
// TODO(mcslee): implement
return ANONYMOUS_STRUCT;
}
public void readStructEnd() {}
public TField readFieldBegin() throws TException {
// TODO(mcslee): implement
return ANONYMOUS_FIELD;
}
public void readFieldEnd() {}
public TMap readMapBegin() throws TException {
// TODO(mcslee): implement
return EMPTY_MAP;
}
public void readMapEnd() {}
public TList readListBegin() throws TException {
// TODO(mcslee): implement
return EMPTY_LIST;
}
public void readListEnd() {}
public TSet readSetBegin() throws TException {
// TODO(mcslee): implement
return EMPTY_SET;
}
public void readSetEnd() {}
public boolean readBool() throws TException {
return (readByte() == 1);
}
public byte readByte() throws TException {
// TODO(mcslee): implement
return 0;
}
public short readI16() throws TException {
// TODO(mcslee): implement
return 0;
}
public int readI32() throws TException {
// TODO(mcslee): implement
return 0;
}
public long readI64() throws TException {
// TODO(mcslee): implement
return 0;
}
public double readDouble() throws TException {
// TODO(mcslee): implement
return 0;
}
public String readString() throws TException {
// TODO(mcslee): implement
return "";
}
public String readStringBody(int size) throws TException {
// TODO(mcslee): implement
return "";
}
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);
}
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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> {
}

View File

@ -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> {
}

View File

@ -0,0 +1,612 @@
/*
* 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.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;
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;
/**
* 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 = Long.MAX_VALUE;
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 final 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() {
if (state_ == FrameBufferState.AWAITING_REGISTER_WRITE) {
// set the OP_WRITE interest
selectionKey_.interestOps(SelectionKey.OP_WRITE);
state_ = FrameBufferState.WRITING;
} else if (state_ == FrameBufferState.AWAITING_REGISTER_READ) {
prepareRead();
} else if (state_ == FrameBufferState.AWAITING_CLOSE) {
close();
selectionKey_.cancel();
} else {
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();
}
}
}

View File

@ -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();
}
}

View File

@ -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 {}

View File

@ -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
*/
protected abstract TProcessor getProcessor();
/**
* Returns the appropriate in {@link TProtocolFactory}. This will be called
* <b>once</b> just after the {@link #init()} method
*
* @return
*/
protected abstract TProtocolFactory getInProtocolFactory();
/**
* Returns the appropriate out {@link TProtocolFactory}. This will be called
* <b>once</b> just after the {@link #init()} method
*
* @return
*/
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);
}
}

View File

@ -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.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 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);
}
}

View File

@ -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.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) {
// for now, just silently ignore. technically this means we'll have less of
// a graceful shutdown as a result.
}
}
/**
* 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);
tte.printStackTrace();
if (clientKey != null) cleanupSelectionKey(clientKey);
if (client != null) client.close();
}
}
} // SelectAcceptThread
}

View File

@ -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 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;
}
}

View File

@ -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);
}

View File

@ -0,0 +1,149 @@
package org.apache.thrift.server;
import com.rbkmoney.woody.api.interceptor.CommonInterceptor;
import com.rbkmoney.woody.api.interceptor.EmptyCommonInterceptor;
import com.rbkmoney.woody.api.trace.ContextUtils;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.api.trace.context.TraceContext;
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;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
/**
* 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;
private final CommonInterceptor defaultInterceptor = new EmptyCommonInterceptor() {
@Override
public boolean interceptResponse(TraceData traceData, Object providerContext, Object... contextParams) {
Throwable t = ContextUtils.getCallError(traceData.getServiceSpan());
if (t != null) {
ContextUtils.setInterceptionError(traceData.getServiceSpan(), t);
return false;
}
return true;
}
};
private CommonInterceptor interceptor;
public TServlet(TProcessor processor, TProtocolFactory inProtocolFactory,
TProtocolFactory outProtocolFactory, CommonInterceptor interceptor) {
super();
this.processor = processor;
this.inProtocolFactory = inProtocolFactory;
this.outProtocolFactory = outProtocolFactory;
this.customHeaders = new ArrayList<>();
this.interceptor = interceptor == null ? defaultInterceptor : interceptor;
}
/**
* @see HttpServlet#HttpServlet()
*/
public TServlet(TProcessor processor, TProtocolFactory inProtocolFactory,
TProtocolFactory outProtocolFactory) {
this(processor, inProtocolFactory, outProtocolFactory, null);
}
/**
* @see HttpServlet#HttpServlet()
*/
public TServlet(TProcessor processor, TProtocolFactory protocolFactory) {
this(processor, protocolFactory, protocolFactory);
}
public TServlet(TProcessor processor, TProtocolFactory protocolFactory, CommonInterceptor interceptor) {
this(processor, protocolFactory, protocolFactory, interceptor);
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
* response)
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
TraceData traceData = TraceContext.getCurrentTraceData();
OutputStream out = null;
try {
InputStream in = request.getInputStream();
out = response.getOutputStream();
response.setContentType("application/x-thrift");
if (!interceptor.interceptRequest(traceData, request, response)) {
ContextUtils.tryThrowInterceptionError(traceData.getServiceSpan());
}
if (null != this.customHeaders) {
for (Map.Entry<String, String> header : this.customHeaders) {
response.addHeader(header.getKey(), header.getValue());
}
}
TTransport transport = new TIOStreamTransport(in, out);
TProtocol inProtocol = inProtocolFactory.getProtocol(transport);
TProtocol outProtocol = outProtocolFactory.getProtocol(transport);
processor.process(inProtocol, outProtocol);
} catch (Throwable te) {
ContextUtils.setCallError(traceData.getServiceSpan(), te);
} finally {
if (!interceptor.interceptResponse(traceData, response)) {
Throwable t = ContextUtils.getInterceptionError(traceData.getServiceSpan());
if (t != null) {
throw new ServletException(t);
}
}
if (out != null) {
out.flush();
}
}
}
/**
* @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 AbstractMap.SimpleImmutableEntry(key, value));
}
public void setCustomHeaders(Collection<Map.Entry<String, String>> headers) {
this.customHeaders.clear();
this.customHeaders.addAll(headers);
}
}

View File

@ -0,0 +1,117 @@
/*
* 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);
}
if(!processor.process(inputProtocol, outputProtocol)) {
break;
}
}
}
} 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();
}
}

View File

@ -0,0 +1,314 @@
/*
* 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.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.TSaslTransportException;
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 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,
TimeUnit.SECONDS,
executorQueue);
}
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();
}
stopped_ = false;
setServing(true);
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);
}
}
}
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;
}
}
setServing(false);
}
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_ || !processor.process(inputProtocol, outputProtocol)) {
break;
}
}
} catch (TSaslTransportException ttx) {
// Something thats not SASL was in the stream, continue silently
} catch (TTransportException ttx) {
// Assume the client died and continue silently
} catch (TException tx) {
LOGGER.error("Thrift error occurred during processing of message.", tx);
} catch (Exception x) {
LOGGER.error("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();
}
}
}
}
}

View File

@ -0,0 +1,668 @@
/*
* 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.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;
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;
/**
* 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;
/**
* 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 {
// 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 (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);
}
}
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();
}
}
}

View File

@ -0,0 +1,52 @@
/*
* 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;
/**
* 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.
*/
public class AutoExpandingBuffer {
private byte[] array;
private final double growthCoefficient;
public AutoExpandingBuffer(int initialCapacity, double growthCoefficient) {
if (growthCoefficient < 1.0) {
throw new IllegalArgumentException("Growth coefficient must be >= 1.0");
}
array = new byte[initialCapacity];
this.growthCoefficient = growthCoefficient;
}
public void resizeIfNecessary(int size) {
if (array.length < size) {
byte[] newBuf = new byte[(int)(size * growthCoefficient)];
System.arraycopy(array, 0, newBuf, 0, array.length);
array = newBuf;
}
}
public byte[] array() {
return array;
}
}

View File

@ -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, double overgrowthCoefficient) {
this.buf = new AutoExpandingBuffer(initialCapacity, overgrowthCoefficient);
}
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;
}
}

View File

@ -0,0 +1,66 @@
/*
* 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;
public AutoExpandingBufferWriteTransport(int initialCapacity, double growthCoefficient) {
this.buf = new AutoExpandingBuffer(initialCapacity, growthCoefficient);
this.pos = 0;
}
@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;
}
public int getPos() {
return pos;
}
public void reset() {
pos = 0;
}
}

View File

@ -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, len);
} 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;
}
}

View File

@ -0,0 +1,191 @@
/*
* 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 final AutoExpandingBufferReadTransport readBuffer;
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;
writeBuffer = new AutoExpandingBufferWriteTransport(initialBufferCapacity, 1.5);
readBuffer = new AutoExpandingBufferReadTransport(initialBufferCapacity, 1.5);
}
@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);
}
@Override
public void flush() throws TTransportException {
int length = writeBuffer.getPos();
TFramedTransport.encodeFrameSize(length, i32buf);
underlying.write(i32buf, 0, 4);
underlying.write(writeBuffer.getBuf().array(), 0, length);
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();
}
}

View File

@ -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());
}
}

View File

@ -0,0 +1,621 @@
/*
* 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;
/**
* 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 {
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) {
System.err.println("createInputStream: "+iox.getMessage());
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(), 8192);
} 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) {
System.err.println("WARNING: Error closing input file: " +
iox.getMessage());
}
inputFile_ = null;
}
if (outputStream_ != null) {
try {
outputStream_.close();
} catch (IOException iox) {
System.err.println("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) {
System.err.println("createInputStream: "+iox.getMessage());
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) {
System.err.println("Cannot parse " + args[1]);
printUsage();
}
}
TFileTransport t = new TFileTransport(args[0], true);
t.open();
System.out.println("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);
System.out.println("Reading chunk "+cnum);
t.seekToChunk(cnum);
for(int i=0; i<4096; i++) {
t.read(buf, 0, 4096);
}
}
}
private static void printUsage() {
System.err.println("Usage: TFileTransport <filename> [num_chunks]");
System.err.println(" (Opens and reads num_chunks chunks from file randomly)");
System.exit(1);
}
}

View File

@ -0,0 +1,178 @@
/*
* 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 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_);
}
}
/**
* Constructor wraps around another transport
*/
public TFramedTransport(TTransport transport, int maxLength) {
transport_ = transport;
maxLength_ = maxLength;
}
public TFramedTransport(TTransport transport) {
transport_ = transport;
maxLength_ = TFramedTransport.DEFAULT_MAX_LENGTH;
}
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 {
if (readBuffer_ != null) {
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);
}
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();
writeBuffer_.reset();
encodeFrameSize(len, i32buf);
transport_.write(i32buf, 0, 4);
transport_.write(buf, 0, len);
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));
}
}

View File

@ -0,0 +1,396 @@
/*
* 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 com.rbkmoney.woody.api.interceptor.CommonInterceptor;
import com.rbkmoney.woody.api.interceptor.EmptyCommonInterceptor;
import com.rbkmoney.woody.api.trace.ContextUtils;
import com.rbkmoney.woody.api.trace.TraceData;
import com.rbkmoney.woody.api.trace.context.TraceContext;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.BooleanSupplier;
/**
* HTTP implementation of the TTransport interface. Used for working with a
* Thrift web services implementation (using for example TServlet).
* <p>
* 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.
* <p>
* When using HttpClient, the following configuration leads to 5-15%
* better performance than the HttpURLConnection implementation:
* <p>
* http.protocol.version=HttpVersion.HTTP_1_1
* http.protocol.content-charset=UTF-8
* http.protocol.expect-continue=false
* http.connection.stalecheck=false
* <p>
* 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 RequestConfig requestConfig = null;
private final HttpHost host;
private final HttpClient client;
private final CommonInterceptor interceptor;
public static class Factory extends TTransportFactory {
private final String url;
private final HttpClient client;
private final CommonInterceptor interceptor;
public Factory(String url) {
this(url, (HttpClient) null);
}
public Factory(String url, CommonInterceptor interceptor) {
this(url, null, interceptor);
}
public Factory(String url, HttpClient client) {
this(url, client, null);
}
public Factory(String url, HttpClient client, CommonInterceptor interceptor) {
this.url = url;
this.client = client;
this.interceptor = interceptor;
}
@Override
public TTransport getTransport(TTransport trans) {
try {
if (null != client) {
return new THttpClient(url, client, interceptor);
} else {
return new THttpClient(url, interceptor);
}
} catch (TTransportException tte) {
return null;
}
}
}
public THttpClient(String url) throws TTransportException {
this(url, (CommonInterceptor) null);
}
public THttpClient(String url, CommonInterceptor interceptor) throws TTransportException {
try {
url_ = new URL(url);
this.client = null;
this.host = null;
this.interceptor = interceptor == null ? new EmptyCommonInterceptor() : interceptor;
} catch (IOException iox) {
throw new TTransportException(iox);
}
}
public THttpClient(String url, HttpClient client) throws TTransportException {
this(url, client, null);
}
public THttpClient(String url, HttpClient client, CommonInterceptor interceptor) 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());
this.interceptor = interceptor == null ? new EmptyCommonInterceptor() : interceptor;
} catch (IOException iox) {
throw new TTransportException(iox);
}
}
public void setConnectTimeout(int timeout) {
connectTimeout_ = timeout;
requestConfig = null;
}
public void setReadTimeout(int timeout) {
readTimeout_ = timeout;
requestConfig = null;
}
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 HttpClient getHttpClient() {
return client;
}
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 setMainHeaders(BiConsumer<String, String> hSetter) {
hSetter.accept("Content-Type", "application/x-thrift");
hSetter.accept("Accept", "application/x-thrift");
hSetter.accept("User-Agent", "Java/THttpClient/HC");
}
private void setCustomHeaders(BiConsumer<String, String> hSetter) {
if (null != customHeaders_) {
for (Map.Entry<String, String> header : customHeaders_.entrySet()) {
hSetter.accept(header.getKey(), header.getValue());
}
}
}
private void intercept(BooleanSupplier interception, String errMsg) throws TTransportException {
if (!interception.getAsBoolean()) {
Throwable reqErr = ContextUtils.getInterceptionError(TraceContext.getCurrentTraceData().getClientSpan());
if (reqErr != null) {
if (reqErr instanceof RuntimeException) {
throw (RuntimeException) reqErr;
} else {
throw new TTransportException(errMsg, reqErr);
}
}
}
}
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;
try {
// Set request to path + query string
post = new HttpPost(this.url_.getFile());
RequestConfig activeRequestConfig = requestConfig == null ? RequestConfig.custom()
.setConnectTimeout(connectTimeout_)
.setSocketTimeout(readTimeout_)
.build() : requestConfig;
post.setConfig(activeRequestConfig);
//
// Headers are added to the HttpPost instance, not
// to HttpClient.
//
HttpPost newPost = post;
setMainHeaders((key, val) -> newPost.setHeader(key, val));
setCustomHeaders((key, val) -> newPost.setHeader(key, val));
TraceData traceData = TraceContext.getCurrentTraceData();
intercept(() -> interceptor.interceptRequest(traceData, newPost, this.url_), "Request interception error");
post.setEntity(new ByteArrayEntity(data));
HttpResponse response = this.client.execute(this.host, post);
intercept(() -> interceptor.interceptResponse(traceData, response), "Response interception error");
//
// Retrieve the inputstream BEFORE checking the status code so
// resources get freed in the finally clause.
//
try (InputStream is = response.getEntity().getContent()) {
// 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];
int len;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((len = is.read(buf)) != -1) {
baos.write(buf, 0, 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);
}
}
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");
setMainHeaders((key, val) -> connection.setRequestProperty(key, val));
setCustomHeaders((key, val) -> connection.setRequestProperty(key, val));
TraceData traceData = TraceContext.getCurrentTraceData();
intercept(() -> interceptor.interceptRequest(traceData, connection, url_), "Request interception error");
connection.setDoOutput(true);
connection.connect();
connection.getOutputStream().write(data);
intercept(() -> interceptor.interceptResponse(traceData, connection), "Response interception error");
// Read the responses
inputStream_ = connection.getInputStream();
} catch (IOException iox) {
throw new TTransportException(iox);
}
}
}

View File

@ -0,0 +1,164 @@
/*
* 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;
}
/**
* The streams must already be open at construction time, so this should
* always return true.
*
* @return true
*/
public boolean isOpen() {
return true;
}
/**
* 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);
}
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);
}
}
}

View File

@ -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.transport;
import org.apache.thrift.TByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
/**
* 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.
*/
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 enc the encoding to use
* @return the contents of the memory buffer as a String
*/
public String toString(String enc) throws UnsupportedEncodingException {
return arr_.toString(enc);
}
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();
}
}

View File

@ -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;
}
}

View File

@ -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() + ".");
}
}
public void listen() throws TTransportException {
// Make sure not to block on accept
if (serverSocket_ != null) {
try {
serverSocket_.setSoTimeout(0);
} catch (SocketException sx) {
sx.printStackTrace();
}
}
}
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();
}
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -0,0 +1,385 @@
/*
* 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;
/**
* A Factory for providing and setting up Client and Server SSL wrapped
* TSocket and TServerSocket
*/
public class TSSLTransportFactory {
/**
* 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=&lt;truststore location&gt;
* -Djavax.net.ssl.trustStorePassword=password
* -Djavax.net.ssl.keyStore=&lt;keystore location&gt;
* -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);
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);
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) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
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 String keyPass;
protected String keyManagerType = KeyManagerFactory.getDefaultAlgorithm();
protected String keyStoreType = "JKS";
protected String trustStore;
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 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 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 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 if client authentication is required
*
* @param clientAuth
*/
public void requireClientAuth(boolean clientAuth) {
this.clientAuth = clientAuth;
}
}
}

View File

@ -0,0 +1,107 @@
/*
* 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.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();
sendSaslMessage(NegotiationStatus.START,
mechanismBytes);
// Send initial response
sendSaslMessage(saslClient.isComplete() ? NegotiationStatus.COMPLETE : NegotiationStatus.OK,
initialResponse);
underlyingTransport.flush();
}
}

View File

@ -0,0 +1,229 @@
/*
* 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.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);
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();
}
}
}

View File

@ -0,0 +1,578 @@
/*
* 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.UnsupportedEncodingException;
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);
if (LOGGER.isDebugEnabled())
LOGGER.debug(getRole() + ": Writing message with status {} and payload length {}",
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) {
try {
String remoteMessage = new String(payload, "UTF-8");
throw new TTransportException("Peer indicated failure: " + remoteMessage);
} catch (UnsupportedEncodingException e) {
throw new TTransportException(e);
}
}
if (LOGGER.isDebugEnabled())
LOGGER.debug(getRole() + ": Received message with status {} and payload length {}",
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());
} 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());
break;
}
sendSaslMessage(sasl.isComplete() ? NegotiationStatus.COMPLETE : NegotiationStatus.OK,
challenge);
}
LOGGER.debug("{}: Main negotiation loop complete", getRole());
assert sasl.isComplete();
// 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, throw a different
* type of exception so we can handle this scenario differently.
*/
if (!readSaslHeader && e.getType() == TTransportException.END_OF_FILE) {
underlyingTransport.close();
LOGGER.debug("No data or no sasl data in the stream");
throw new TSaslTransportException("No data or no sasl data in the stream");
}
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);
}
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);
}
}
}

View File

@ -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.transport;
/*
* This exception is used to track exceptions in TSaslTransport
* that does not have Sasl signature in their stream.
*/
public class TSaslTransportException extends TTransportException {
public TSaslTransportException() {
super();
}
public TSaslTransportException(String message) {
super(message);
}
public TSaslTransportException(Throwable cause) {
super(cause);
}
public TSaslTransportException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -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;
}

Some files were not shown because too many files have changed in this diff Show More