mirror of
https://github.com/valitydev/woody_java.git
synced 2024-11-06 08:15:21 +00:00
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:
parent
f4c5ed0125
commit
41effb76f0
57
libthrift/pom.xml
Normal file
57
libthrift/pom.xml
Normal 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>
|
@ -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();
|
||||
}
|
||||
}
|
148
libthrift/src/main/java/org/apache/thrift/EncodingUtils.java
Normal file
148
libthrift/src/main/java/org/apache/thrift/EncodingUtils.java
Normal 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);
|
||||
}
|
||||
}
|
121
libthrift/src/main/java/org/apache/thrift/Option.java
Normal file
121
libthrift/src/main/java/org/apache/thrift/Option.java
Normal 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>();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
82
libthrift/src/main/java/org/apache/thrift/ShortStack.java
Normal file
82
libthrift/src/main/java/org/apache/thrift/ShortStack.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
82
libthrift/src/main/java/org/apache/thrift/TBase.java
Normal file
82
libthrift/src/main/java/org/apache/thrift/TBase.java
Normal 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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
337
libthrift/src/main/java/org/apache/thrift/TBaseHelper.java
Normal file
337
libthrift/src/main/java/org/apache/thrift/TBaseHelper.java
Normal 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));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
358
libthrift/src/main/java/org/apache/thrift/TDeserializer.java
Normal file
358
libthrift/src/main/java/org/apache/thrift/TDeserializer.java
Normal 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());
|
||||
}
|
||||
}
|
24
libthrift/src/main/java/org/apache/thrift/TEnum.java
Normal file
24
libthrift/src/main/java/org/apache/thrift/TEnum.java
Normal 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();
|
||||
}
|
57
libthrift/src/main/java/org/apache/thrift/TEnumHelper.java
Normal file
57
libthrift/src/main/java/org/apache/thrift/TEnumHelper.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
45
libthrift/src/main/java/org/apache/thrift/TException.java
Normal file
45
libthrift/src/main/java/org/apache/thrift/TException.java
Normal 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);
|
||||
}
|
||||
}
|
34
libthrift/src/main/java/org/apache/thrift/TFieldIdEnum.java
Normal file
34
libthrift/src/main/java/org/apache/thrift/TFieldIdEnum.java
Normal 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();
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
}
|
32
libthrift/src/main/java/org/apache/thrift/TProcessor.java
Normal file
32
libthrift/src/main/java/org/apache/thrift/TProcessor.java
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
44
libthrift/src/main/java/org/apache/thrift/TSerializable.java
Normal file
44
libthrift/src/main/java/org/apache/thrift/TSerializable.java
Normal 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;
|
||||
|
||||
}
|
110
libthrift/src/main/java/org/apache/thrift/TSerializer.java
Normal file
110
libthrift/src/main/java/org/apache/thrift/TSerializer.java
Normal 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));
|
||||
}
|
||||
}
|
||||
|
108
libthrift/src/main/java/org/apache/thrift/TServiceClient.java
Normal file
108
libthrift/src/main/java/org/apache/thrift/TServiceClient.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
279
libthrift/src/main/java/org/apache/thrift/TUnion.java
Normal file
279
libthrift/src/main/java/org/apache/thrift/TUnion.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,284 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.thrift.async;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.SelectionKey;
|
||||
import java.nio.channels.Selector;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.apache.thrift.TException;
|
||||
import org.apache.thrift.protocol.TProtocol;
|
||||
import org.apache.thrift.protocol.TProtocolFactory;
|
||||
import org.apache.thrift.transport.TFramedTransport;
|
||||
import org.apache.thrift.transport.TMemoryBuffer;
|
||||
import org.apache.thrift.transport.TNonblockingTransport;
|
||||
import org.apache.thrift.transport.TTransportException;
|
||||
|
||||
/**
|
||||
* Encapsulates an async method call.
|
||||
* <p>
|
||||
* Need to generate:
|
||||
* <ul>
|
||||
* <li>protected abstract void write_args(TProtocol protocol)</li>
|
||||
* <li>protected abstract T getResult() throws <Exception_1>, <Exception_2>, ...</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param <T> The return type of the encapsulated method call.
|
||||
*/
|
||||
public abstract class TAsyncMethodCall<T> {
|
||||
|
||||
private static final int INITIAL_MEMORY_BUFFER_SIZE = 128;
|
||||
private static AtomicLong sequenceIdCounter = new AtomicLong(0);
|
||||
|
||||
public static enum State {
|
||||
CONNECTING,
|
||||
WRITING_REQUEST_SIZE,
|
||||
WRITING_REQUEST_BODY,
|
||||
READING_RESPONSE_SIZE,
|
||||
READING_RESPONSE_BODY,
|
||||
RESPONSE_READ,
|
||||
ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Next step in the call, initialized by start()
|
||||
*/
|
||||
private State state = null;
|
||||
|
||||
protected final TNonblockingTransport transport;
|
||||
private final TProtocolFactory protocolFactory;
|
||||
protected final TAsyncClient client;
|
||||
private final AsyncMethodCallback<T> callback;
|
||||
private final boolean isOneway;
|
||||
private long sequenceId;
|
||||
private final long timeout;
|
||||
|
||||
private ByteBuffer sizeBuffer;
|
||||
private final byte[] sizeBufferArray = new byte[4];
|
||||
private ByteBuffer frameBuffer;
|
||||
|
||||
private long startTime = System.currentTimeMillis();
|
||||
|
||||
protected TAsyncMethodCall(TAsyncClient client, TProtocolFactory protocolFactory, TNonblockingTransport transport, AsyncMethodCallback<T> callback, boolean isOneway) {
|
||||
this.transport = transport;
|
||||
this.callback = callback;
|
||||
this.protocolFactory = protocolFactory;
|
||||
this.client = client;
|
||||
this.isOneway = isOneway;
|
||||
this.sequenceId = TAsyncMethodCall.sequenceIdCounter.getAndIncrement();
|
||||
this.timeout = client.getTimeout();
|
||||
}
|
||||
|
||||
protected State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
protected boolean isFinished() {
|
||||
return state == State.RESPONSE_READ;
|
||||
}
|
||||
|
||||
protected long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
protected long getSequenceId() {
|
||||
return sequenceId;
|
||||
}
|
||||
|
||||
public TAsyncClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public boolean hasTimeout() {
|
||||
return timeout > 0;
|
||||
}
|
||||
|
||||
public long getTimeoutTimestamp() {
|
||||
return timeout + startTime;
|
||||
}
|
||||
|
||||
protected abstract void write_args(TProtocol protocol) throws TException;
|
||||
|
||||
protected abstract T getResult() throws Exception;
|
||||
|
||||
/**
|
||||
* Initialize buffers.
|
||||
* @throws TException if buffer initialization fails
|
||||
*/
|
||||
protected void prepareMethodCall() throws TException {
|
||||
TMemoryBuffer memoryBuffer = new TMemoryBuffer(INITIAL_MEMORY_BUFFER_SIZE);
|
||||
TProtocol protocol = protocolFactory.getProtocol(memoryBuffer);
|
||||
write_args(protocol);
|
||||
|
||||
int length = memoryBuffer.length();
|
||||
frameBuffer = ByteBuffer.wrap(memoryBuffer.getArray(), 0, length);
|
||||
|
||||
TFramedTransport.encodeFrameSize(length, sizeBufferArray);
|
||||
sizeBuffer = ByteBuffer.wrap(sizeBufferArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register with selector and start first state, which could be either connecting or writing.
|
||||
* @throws IOException if register or starting fails
|
||||
*/
|
||||
void start(Selector sel) throws IOException {
|
||||
SelectionKey key;
|
||||
if (transport.isOpen()) {
|
||||
state = State.WRITING_REQUEST_SIZE;
|
||||
key = transport.registerSelector(sel, SelectionKey.OP_WRITE);
|
||||
} else {
|
||||
state = State.CONNECTING;
|
||||
key = transport.registerSelector(sel, SelectionKey.OP_CONNECT);
|
||||
|
||||
// non-blocking connect can complete immediately,
|
||||
// in which case we should not expect the OP_CONNECT
|
||||
if (transport.startConnect()) {
|
||||
registerForFirstWrite(key);
|
||||
}
|
||||
}
|
||||
|
||||
key.attach(this);
|
||||
}
|
||||
|
||||
void registerForFirstWrite(SelectionKey key) throws IOException {
|
||||
state = State.WRITING_REQUEST_SIZE;
|
||||
key.interestOps(SelectionKey.OP_WRITE);
|
||||
}
|
||||
|
||||
protected ByteBuffer getFrameBuffer() {
|
||||
return frameBuffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to next state, doing whatever work is required. Since this
|
||||
* method is only called by the selector thread, we can make changes to our
|
||||
* select interests without worrying about concurrency.
|
||||
* @param key
|
||||
*/
|
||||
void transition(SelectionKey key) {
|
||||
// Ensure key is valid
|
||||
if (!key.isValid()) {
|
||||
key.cancel();
|
||||
Exception e = new TTransportException("Selection key not valid!");
|
||||
onError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Transition function
|
||||
try {
|
||||
switch (state) {
|
||||
case CONNECTING:
|
||||
doConnecting(key);
|
||||
break;
|
||||
case WRITING_REQUEST_SIZE:
|
||||
doWritingRequestSize();
|
||||
break;
|
||||
case WRITING_REQUEST_BODY:
|
||||
doWritingRequestBody(key);
|
||||
break;
|
||||
case READING_RESPONSE_SIZE:
|
||||
doReadingResponseSize();
|
||||
break;
|
||||
case READING_RESPONSE_BODY:
|
||||
doReadingResponseBody(key);
|
||||
break;
|
||||
default: // RESPONSE_READ, ERROR, or bug
|
||||
throw new IllegalStateException("Method call in state " + state
|
||||
+ " but selector called transition method. Seems like a bug...");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
key.cancel();
|
||||
key.attach(null);
|
||||
onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onError(Exception e) {
|
||||
client.onError(e);
|
||||
callback.onError(e);
|
||||
state = State.ERROR;
|
||||
}
|
||||
|
||||
private void doReadingResponseBody(SelectionKey key) throws IOException {
|
||||
if (transport.read(frameBuffer) < 0) {
|
||||
throw new IOException("Read call frame failed");
|
||||
}
|
||||
if (frameBuffer.remaining() == 0) {
|
||||
cleanUpAndFireCallback(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanUpAndFireCallback(SelectionKey key) {
|
||||
state = State.RESPONSE_READ;
|
||||
key.interestOps(0);
|
||||
// this ensures that the TAsyncMethod instance doesn't hang around
|
||||
key.attach(null);
|
||||
try {
|
||||
T result = this.getResult();
|
||||
client.onComplete();
|
||||
callback.onComplete(result);
|
||||
} catch (Exception e) {
|
||||
key.cancel();
|
||||
onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void doReadingResponseSize() throws IOException {
|
||||
if (transport.read(sizeBuffer) < 0) {
|
||||
throw new IOException("Read call frame size failed");
|
||||
}
|
||||
if (sizeBuffer.remaining() == 0) {
|
||||
state = State.READING_RESPONSE_BODY;
|
||||
frameBuffer = ByteBuffer.allocate(TFramedTransport.decodeFrameSize(sizeBufferArray));
|
||||
}
|
||||
}
|
||||
|
||||
private void doWritingRequestBody(SelectionKey key) throws IOException {
|
||||
if (transport.write(frameBuffer) < 0) {
|
||||
throw new IOException("Write call frame failed");
|
||||
}
|
||||
if (frameBuffer.remaining() == 0) {
|
||||
if (isOneway) {
|
||||
cleanUpAndFireCallback(key);
|
||||
} else {
|
||||
state = State.READING_RESPONSE_SIZE;
|
||||
sizeBuffer.rewind(); // Prepare to read incoming frame size
|
||||
key.interestOps(SelectionKey.OP_READ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doWritingRequestSize() throws IOException {
|
||||
if (transport.write(sizeBuffer) < 0) {
|
||||
throw new IOException("Write call frame size failed");
|
||||
}
|
||||
if (sizeBuffer.remaining() == 0) {
|
||||
state = State.WRITING_REQUEST_BODY;
|
||||
}
|
||||
}
|
||||
|
||||
private void doConnecting(SelectionKey key) throws IOException {
|
||||
if (!key.isConnectable() || !transport.finishConnect()) {
|
||||
throw new IOException("not connectable or finishConnect returned false after we got an OP_CONNECT");
|
||||
}
|
||||
registerForFirstWrite(key);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 (< 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];
|
||||
}
|
||||
}
|
@ -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
@ -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;
|
||||
}
|
40
libthrift/src/main/java/org/apache/thrift/protocol/TMap.java
Normal file
40
libthrift/src/main/java/org/apache/thrift/protocol/TMap.java
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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_;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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 < 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;
|
||||
}
|
||||
}
|
42
libthrift/src/main/java/org/apache/thrift/protocol/TSet.java
Normal file
42
libthrift/src/main/java/org/apache/thrift/protocol/TSet.java
Normal 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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
@ -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> {
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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 {}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
177
libthrift/src/main/java/org/apache/thrift/server/TServer.java
Normal file
177
libthrift/src/main/java/org/apache/thrift/server/TServer.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
149
libthrift/src/main/java/org/apache/thrift/server/TServlet.java
Normal file
149
libthrift/src/main/java/org/apache/thrift/server/TServlet.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -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=<truststore location>
|
||||
* -Djavax.net.ssl.trustStorePassword=password
|
||||
* -Djavax.net.ssl.keyStore=<keystore location>
|
||||
* -Djavax.net.ssl.keyStorePassword=password
|
||||
*
|
||||
* @param port
|
||||
* @return A SSL wrapped TServerSocket
|
||||
* @throws TTransportException
|
||||
*/
|
||||
public static TServerSocket getServerSocket(int port) throws TTransportException {
|
||||
return getServerSocket(port, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a default SSL wrapped TServerSocket bound to the specified port
|
||||
*
|
||||
* @param port
|
||||
* @param clientTimeout
|
||||
* @return A SSL wrapped TServerSocket
|
||||
* @throws TTransportException
|
||||
*/
|
||||
public static TServerSocket getServerSocket(int port, int clientTimeout) throws TTransportException {
|
||||
return getServerSocket(port, clientTimeout, false, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a default SSL wrapped TServerSocket bound to the specified port and interface
|
||||
*
|
||||
* @param port
|
||||
* @param clientTimeout
|
||||
* @param ifAddress
|
||||
* @return A SSL wrapped TServerSocket
|
||||
* @throws TTransportException
|
||||
*/
|
||||
public static TServerSocket getServerSocket(int port, int clientTimeout, boolean clientAuth, InetAddress ifAddress) throws TTransportException {
|
||||
SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
|
||||
return createServer(factory, port, clientTimeout, clientAuth, ifAddress, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a configured SSL wrapped TServerSocket bound to the specified port and interface.
|
||||
* Here the TSSLTransportParameters are used to set the values for the algorithms, keystore,
|
||||
* truststore and other settings
|
||||
*
|
||||
* @param port
|
||||
* @param clientTimeout
|
||||
* @param ifAddress
|
||||
* @param params
|
||||
* @return A SSL wrapped TServerSocket
|
||||
* @throws TTransportException
|
||||
*/
|
||||
public static TServerSocket getServerSocket(int port, int clientTimeout, InetAddress ifAddress, TSSLTransportParameters params) throws TTransportException {
|
||||
if (params == null || !(params.isKeyStoreSet || params.isTrustStoreSet)) {
|
||||
throw new TTransportException("Either one of the KeyStore or TrustStore must be set for SSLTransportParameters");
|
||||
}
|
||||
|
||||
SSLContext ctx = createSSLContext(params);
|
||||
return createServer(ctx.getServerSocketFactory(), port, clientTimeout, params.clientAuth, ifAddress, params);
|
||||
}
|
||||
|
||||
private static TServerSocket createServer(SSLServerSocketFactory factory, int port, int timeout, boolean clientAuth,
|
||||
InetAddress ifAddress, TSSLTransportParameters params) throws TTransportException {
|
||||
try {
|
||||
SSLServerSocket serverSocket = (SSLServerSocket) factory.createServerSocket(port, 100, ifAddress);
|
||||
serverSocket.setSoTimeout(timeout);
|
||||
serverSocket.setNeedClientAuth(clientAuth);
|
||||
if (params != null && params.cipherSuites != null) {
|
||||
serverSocket.setEnabledCipherSuites(params.cipherSuites);
|
||||
}
|
||||
return new TServerSocket(new TServerSocket.ServerSocketTransportArgs().
|
||||
serverSocket(serverSocket).clientTimeout(timeout));
|
||||
} catch (Exception e) {
|
||||
throw new TTransportException("Could not bind to port " + port, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a default SSL wrapped TSocket connected to the specified host and port. All
|
||||
* the client methods return a bound connection. So there is no need to call open() on the
|
||||
* TTransport.
|
||||
*
|
||||
* @param host
|
||||
* @param port
|
||||
* @param timeout
|
||||
* @return A SSL wrapped TSocket
|
||||
* @throws TTransportException
|
||||
*/
|
||||
public static TSocket getClientSocket(String host, int port, int timeout) throws TTransportException {
|
||||
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
|
||||
return createClient(factory, host, port, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a default SSL wrapped TSocket connected to the specified host and port.
|
||||
*
|
||||
* @param host
|
||||
* @param port
|
||||
* @return A SSL wrapped TSocket
|
||||
* @throws TTransportException
|
||||
*/
|
||||
public static TSocket getClientSocket(String host, int port) throws TTransportException {
|
||||
return getClientSocket(host, port, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a custom configured SSL wrapped TSocket. The SSL settings are obtained from the
|
||||
* passed in TSSLTransportParameters.
|
||||
*
|
||||
* @param host
|
||||
* @param port
|
||||
* @param timeout
|
||||
* @param params
|
||||
* @return A SSL wrapped TSocket
|
||||
* @throws TTransportException
|
||||
*/
|
||||
public static TSocket getClientSocket(String host, int port, int timeout, TSSLTransportParameters params) throws TTransportException {
|
||||
if (params == null || !(params.isKeyStoreSet || params.isTrustStoreSet)) {
|
||||
throw new TTransportException("Either one of the KeyStore or TrustStore must be set for SSLTransportParameters");
|
||||
}
|
||||
|
||||
SSLContext ctx = createSSLContext(params);
|
||||
return createClient(ctx.getSocketFactory(), host, port, timeout);
|
||||
}
|
||||
|
||||
private static SSLContext createSSLContext(TSSLTransportParameters params) throws TTransportException {
|
||||
SSLContext ctx;
|
||||
InputStream in = null;
|
||||
InputStream is = null;
|
||||
|
||||
try {
|
||||
ctx = SSLContext.getInstance(params.protocol);
|
||||
TrustManagerFactory tmf = null;
|
||||
KeyManagerFactory kmf = null;
|
||||
|
||||
if (params.isTrustStoreSet) {
|
||||
tmf = TrustManagerFactory.getInstance(params.trustManagerType);
|
||||
KeyStore ts = KeyStore.getInstance(params.trustStoreType);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user