THRIFT-1348 C++ Qt bindings
Patch: Doug Rosvick and Vitali Lovich
git-svn-id: https://svn.apache.org/repos/asf/thrift/trunk@1242900 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/cpp/Makefile.am b/lib/cpp/Makefile.am
index f8093ba..188b371 100644
--- a/lib/cpp/Makefile.am
+++ b/lib/cpp/Makefile.am
@@ -17,6 +17,9 @@
# under the License.
#
+moc_%.cpp: %.h
+ $(QT_MOC) $(QT_CFLAGS) $< -o $@
+
SUBDIRS = .
if WITH_TESTS
@@ -39,6 +42,10 @@
lib_LTLIBRARIES += libthriftz.la
pkgconfig_DATA += thrift-z.pc
endif
+if AMX_HAVE_QT
+lib_LTLIBRARIES += libthriftqt.la
+pkgconfig_DATA += thrift-qt.pc
+endif
AM_CXXFLAGS = -Wall
AM_CPPFLAGS = $(BOOST_CPPFLAGS) -I$(srcdir)/src
@@ -94,18 +101,27 @@
libthriftz_la_SOURCES = src/transport/TZlibTransport.cpp
+libthriftqt_la_MOC = src/qt/moc_TQTcpServer.cpp
+libthriftqt_la_SOURCES = $(libthriftqt_la_MOC) \
+ src/qt/TQIODeviceTransport.cpp \
+ src/qt/TQTcpServer.cpp
+CLEANFILES = $(libthriftqt_la_MOC)
+
# Flags for the various libraries
libthriftnb_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBEVENT_CPPFLAGS)
libthriftz_la_CPPFLAGS = $(AM_CPPFLAGS) $(ZLIB_CPPFLAGS)
+libthriftqt_la_CPPFLAGS = $(AM_CPPFLAGS) $(QT_CFLAGS)
libthriftnb_la_CXXFLAGS = $(AM_CXXFLAGS)
libthriftz_la_CXXFLAGS = $(AM_CXXFLAGS)
+libthriftqt_la_CXXFLAGS = $(AM_CXXFLAGS)
libthriftnb_la_LDFLAGS = -release $(VERSION) $(BOOST_LDFLAGS)
libthriftz_la_LDFLAGS = -release $(VERSION) $(BOOST_LDFLAGS)
+libthriftqt_la_LDFLAGS = -release $(VERSION) $(BOOST_LDFLAGS) $(QT_LIBS)
include_thriftdir = $(includedir)/thrift
include_thrift_HEADERS = \
$(top_builddir)/config.h \
- src/TDispatchProcessor.h \
+ src/TDispatchProcessor.h \
src/Thrift.h \
src/TReflectionLocal.h \
src/TProcessor.h \
@@ -186,6 +202,11 @@
src/async/TEvhttpClientChannel.h \
src/async/TEvhttpServer.h
+include_qtdir = $(include_thriftdir)/qt
+include_qt_HEADERS = \
+ src/qt/TQIODeviceTransport.h \
+ src/qt/TQTcpServer.h
+
noinst_PROGRAMS = concurrency_test
@@ -212,4 +233,5 @@
thrift-nb.pc.in \
thrift.pc.in \
thrift-z.pc.in \
+ thrift-qt.pc.in \
$(WINDOWS_DIST)
diff --git a/lib/cpp/src/qt/TQIODeviceTransport.cpp b/lib/cpp/src/qt/TQIODeviceTransport.cpp
new file mode 100644
index 0000000..c5b53a8
--- /dev/null
+++ b/lib/cpp/src/qt/TQIODeviceTransport.cpp
@@ -0,0 +1,155 @@
+#include "TQIODeviceTransport.h"
+
+#include <transport/TBufferTransports.h>
+
+#include <QAbstractSocket>
+
+#include <iostream>
+
+namespace apache { namespace thrift { namespace transport {
+ using boost::shared_ptr;
+
+ TQIODeviceTransport::TQIODeviceTransport(shared_ptr<QIODevice> dev)
+ : dev_(dev)
+ {
+ }
+
+ TQIODeviceTransport::~TQIODeviceTransport()
+ {
+ dev_->close();
+ }
+
+ void TQIODeviceTransport::open()
+ {
+ if (!isOpen()) {
+ throw TTransportException(TTransportException::NOT_OPEN,
+ "open(): underlying QIODevice isn't open");
+ }
+ }
+
+ bool TQIODeviceTransport::isOpen()
+ {
+ return dev_->isOpen();
+ }
+
+ bool TQIODeviceTransport::peek()
+ {
+ return dev_->bytesAvailable() > 0;
+ }
+
+ void TQIODeviceTransport::close()
+ {
+ dev_->close();
+ }
+
+ uint32_t TQIODeviceTransport::readAll(uint8_t* buf, uint32_t len) {
+ uint32_t requestLen = len;
+ while (len) {
+ uint32_t readSize;
+ try {
+ readSize = read(buf, len);
+ } catch (...) {
+ if (len != requestLen) {
+ // something read already
+ return requestLen - len;
+ }
+ // error but nothing read yet
+ throw;
+ }
+ if (readSize == 0) {
+ // dev_->waitForReadyRead(50);
+ } else {
+ buf += readSize;
+ len -= readSize;
+ }
+ }
+ return requestLen;
+ }
+
+ uint32_t TQIODeviceTransport::read(uint8_t* buf, uint32_t len)
+ {
+ uint32_t actualSize;
+ qint64 readSize;
+
+ if (!dev_->isOpen()) {
+ throw TTransportException(TTransportException::NOT_OPEN,
+ "read(): underlying QIODevice is not open");
+ }
+
+ actualSize = (uint32_t)std::min((qint64)len, dev_->bytesAvailable());
+ readSize = dev_->read(reinterpret_cast<char *>(buf), len);
+
+ if (readSize < 0) {
+ QAbstractSocket* socket;
+ if ((socket = qobject_cast<QAbstractSocket* >(dev_.get()))) {
+ throw TTransportException(TTransportException::UNKNOWN,
+ "Failed to read() from QAbstractSocket",
+ socket->error());
+ }
+ throw TTransportException(TTransportException::UNKNOWN,
+ "Failed to read from from QIODevice");
+ }
+
+ return (uint32_t)readSize;
+ }
+
+ void TQIODeviceTransport::write(const uint8_t* buf, uint32_t len)
+ {
+ while (len) {
+ uint32_t written = write_partial(buf, len);
+ len -= written;
+ // dev_->waitForBytesWritten(50);
+ }
+ }
+
+ uint32_t TQIODeviceTransport::write_partial(const uint8_t* buf, uint32_t len)
+ {
+ qint64 written;
+
+ if (!dev_->isOpen()) {
+ throw TTransportException(TTransportException::NOT_OPEN,
+ "write_partial(): underlying QIODevice is not open");
+ }
+
+ written = dev_->write(reinterpret_cast<const char*>(buf), len);
+ if (written < 0) {
+ QAbstractSocket* socket;
+ if ((socket = qobject_cast<QAbstractSocket*>(dev_.get()))) {
+ throw TTransportException(TTransportException::UNKNOWN,
+ "write_partial(): failed to write to QAbstractSocket", socket->error());
+ }
+
+ throw TTransportException(TTransportException::UNKNOWN,
+ "write_partial(): failed to write to underlying QIODevice");
+ }
+
+ return (uint32_t)written;
+ }
+
+ void TQIODeviceTransport::flush()
+ {
+ if (!dev_->isOpen()) {
+ throw TTransportException(TTransportException::NOT_OPEN,
+ "flush(): underlying QIODevice is not open");
+ }
+
+ QAbstractSocket* socket;
+
+ if ((socket = qobject_cast<QAbstractSocket*>(dev_.get()))) {
+ socket->flush();
+ } else {
+ dev_->waitForBytesWritten(1);
+ }
+ }
+
+ uint8_t* TQIODeviceTransport::borrow(uint8_t* buf, uint32_t* len)
+ {
+ return NULL;
+ }
+
+ void TQIODeviceTransport::consume(uint32_t len)
+ {
+ throw TTransportException(TTransportException::UNKNOWN);
+ }
+}}} // apache::thrift::transport
+
diff --git a/lib/cpp/src/qt/TQIODeviceTransport.h b/lib/cpp/src/qt/TQIODeviceTransport.h
new file mode 100644
index 0000000..8ce7c78
--- /dev/null
+++ b/lib/cpp/src/qt/TQIODeviceTransport.h
@@ -0,0 +1,48 @@
+#ifndef _THRIFT_ASYNC_TQIODEVICE_TRANSPORT_H_
+#define _THRIFT_ASYNC_TQIODEVICE_TRANSPORT_H_ 1
+
+#include <QIODevice>
+
+#include <boost/shared_ptr.hpp>
+
+#include <transport/TVirtualTransport.h>
+
+namespace apache { namespace thrift { namespace protocol {
+class TProtocol;
+}}} // apache::thrift::protocol
+
+namespace apache { namespace thrift { namespace transport {
+
+ class TQIODeviceTransport : public apache::thrift::transport::TVirtualTransport<TQIODeviceTransport> {
+ public:
+ explicit TQIODeviceTransport(boost::shared_ptr<QIODevice> dev);
+ ~TQIODeviceTransport();
+
+ void open();
+
+ bool isOpen();
+
+ bool peek();
+
+ void close();
+
+ uint32_t readAll(uint8_t *buf, uint32_t len);
+
+ uint32_t read(uint8_t* buf, uint32_t len);
+
+ void write(const uint8_t* buf, uint32_t len);
+
+ uint32_t write_partial(const uint8_t* buf, uint32_t len);
+
+ void flush();
+
+ uint8_t* borrow(uint8_t* buf, uint32_t* len);
+ void consume(uint32_t len);
+
+ private:
+ boost::shared_ptr<QIODevice> dev_;
+ };
+}}} // apache::thrift::transport
+
+#endif // #ifndef _THRIFT_ASYNC_TQIODEVICE_TRANSPORT_H_
+
diff --git a/lib/cpp/src/qt/TQTcpServer.cpp b/lib/cpp/src/qt/TQTcpServer.cpp
new file mode 100644
index 0000000..65ad68d
--- /dev/null
+++ b/lib/cpp/src/qt/TQTcpServer.cpp
@@ -0,0 +1,140 @@
+#include "TQTcpServer.h"
+
+#include <protocol/TProtocol.h>
+#include <async/TAsyncProcessor.h>
+
+#include <QTcpSocket>
+
+#include "TQIODeviceTransport.h"
+
+using boost::shared_ptr;
+using apache::thrift::protocol::TProtocol;
+using apache::thrift::protocol::TProtocolFactory;
+using apache::thrift::transport::TTransport;
+using apache::thrift::transport::TTransportException;
+using apache::thrift::transport::TQIODeviceTransport;
+using std::tr1::function;
+using std::tr1::bind;
+
+QT_USE_NAMESPACE
+
+namespace apache { namespace thrift { namespace async {
+
+struct TQTcpServer::ConnectionContext {
+ shared_ptr<QTcpSocket> connection_;
+ shared_ptr<TTransport> transport_;
+ shared_ptr<TProtocol> iprot_;
+ shared_ptr<TProtocol> oprot_;
+
+ explicit ConnectionContext(shared_ptr<QTcpSocket> connection,
+ shared_ptr<TTransport> transport,
+ shared_ptr<TProtocol> iprot,
+ shared_ptr<TProtocol> oprot)
+ : connection_(connection)
+ , transport_(transport)
+ , iprot_(iprot)
+ , oprot_(oprot)
+ {}
+};
+
+TQTcpServer::TQTcpServer(shared_ptr<QTcpServer> server,
+ shared_ptr<TAsyncProcessor> processor,
+ shared_ptr<TProtocolFactory> pfact,
+ QObject* parent)
+ : QObject(parent)
+ , server_(server)
+ , processor_(processor)
+ , pfact_(pfact)
+{
+ connect(server.get(), SIGNAL(newConnection()), SLOT(processIncoming()));
+}
+
+TQTcpServer::~TQTcpServer()
+{
+}
+
+void TQTcpServer::processIncoming()
+{
+ while (server_->hasPendingConnections()) {
+ // take ownership of the QTcpSocket; technically it could be deleted
+ // when the QTcpServer is destroyed, but any real app should delete this
+ // class before deleting the QTcpServer that we are using
+ shared_ptr<QTcpSocket> connection(server_->nextPendingConnection());
+
+ shared_ptr<TTransport> transport;
+ shared_ptr<TProtocol> iprot;
+ shared_ptr<TProtocol> oprot;
+
+ try {
+ transport = shared_ptr<TTransport>(new TQIODeviceTransport(connection));
+ iprot = shared_ptr<TProtocol>(pfact_->getProtocol(transport));
+ oprot = shared_ptr<TProtocol>(pfact_->getProtocol(transport));
+ } catch(...) {
+ qWarning("[TQTcpServer] Failed to initialize transports/protocols");
+ continue;
+ }
+
+ ctxMap_[connection.get()] =
+ shared_ptr<ConnectionContext>(
+ new ConnectionContext(connection, transport, iprot, oprot));
+
+ connect(connection.get(), SIGNAL(readyRead()), SLOT(beginDecode()));
+
+ // need to use QueuedConnection since we will be deleting the socket in the slot
+ connect(connection.get(), SIGNAL(disconnected()), SLOT(socketClosed()),
+ Qt::QueuedConnection);
+ }
+}
+
+void TQTcpServer::beginDecode()
+{
+ QTcpSocket* connection(qobject_cast<QTcpSocket*>(sender()));
+ Q_ASSERT(connection);
+
+ if (ctxMap_.find(connection) == ctxMap_.end())
+ {
+ qWarning("[TQTcpServer] Got data on an unknown QTcpSocket");
+ return;
+ }
+
+ shared_ptr<ConnectionContext> ctx = ctxMap_[connection];
+
+ try {
+ processor_->process(
+ bind(&TQTcpServer::finish, this,
+ ctx, std::tr1::placeholders::_1),
+ ctx->iprot_, ctx->oprot_);
+ } catch(const TTransportException& ex) {
+ qWarning("[TQTcpServer] TTransportException during processing: '%s'",
+ ex.what());
+ ctxMap_.erase(connection);
+ } catch(...) {
+ qWarning("[TQTcpServer] Unknown processor exception");
+ ctxMap_.erase(connection);
+ }
+}
+
+void TQTcpServer::socketClosed()
+{
+ QTcpSocket* connection(qobject_cast<QTcpSocket*>(sender()));
+ Q_ASSERT(connection);
+
+ if (ctxMap_.find(connection) == ctxMap_.end())
+ {
+ qWarning("[TQTcpServer] Unknown QTcpSocket closed");
+ return;
+ }
+
+ ctxMap_.erase(connection);
+}
+
+void TQTcpServer::finish(shared_ptr<ConnectionContext> ctx, bool healthy)
+{
+ if (!healthy)
+ {
+ qWarning("[TQTcpServer] Processor failed to process data successfully");
+ ctxMap_.erase(ctx->connection_.get());
+ }
+}
+
+}}} // apache::thrift::async
diff --git a/lib/cpp/src/qt/TQTcpServer.h b/lib/cpp/src/qt/TQTcpServer.h
new file mode 100644
index 0000000..82bec39
--- /dev/null
+++ b/lib/cpp/src/qt/TQTcpServer.h
@@ -0,0 +1,48 @@
+#ifndef _THRIFT_TASYNC_QTCP_SERVER_H_
+#define _THRIFT_TASYNC_QTCP_SERVER_H_
+
+#include <QObject>
+#include <QTcpServer>
+
+#include <boost/shared_ptr.hpp>
+
+#include <tr1/functional>
+
+namespace apache { namespace thrift { namespace protocol {
+class TProtocol;
+class TProtocolFactory;
+}}} // apache::thrift::protocol
+
+namespace apache { namespace thrift { namespace async {
+
+class TAsyncProcessor;
+
+class TQTcpServer : public QObject {
+ Q_OBJECT
+ public:
+ TQTcpServer(boost::shared_ptr<QTcpServer> server,
+ boost::shared_ptr<TAsyncProcessor> processor,
+ boost::shared_ptr<apache::thrift::protocol::TProtocolFactory> protocolFactory,
+ QT_PREPEND_NAMESPACE(QObject)* parent = NULL);
+ virtual ~TQTcpServer();
+
+ private Q_SLOTS:
+ void processIncoming();
+ void beginDecode();
+ void socketClosed();
+
+ private:
+ class ConnectionContext;
+
+ void finish(boost::shared_ptr<ConnectionContext> ctx, bool healthy);
+
+ boost::shared_ptr<QTcpServer> server_;
+ boost::shared_ptr<TAsyncProcessor> processor_;
+ boost::shared_ptr<apache::thrift::protocol::TProtocolFactory> pfact_;
+
+ std::map<QT_PREPEND_NAMESPACE(QTcpSocket)*, boost::shared_ptr<ConnectionContext> > ctxMap_;
+};
+
+}}} // apache::thrift::async
+
+#endif // #ifndef _THRIFT_TASYNC_QTCP_SERVER_H_
diff --git a/lib/cpp/src/qt/moc_TQTcpServer.cpp b/lib/cpp/src/qt/moc_TQTcpServer.cpp
new file mode 100644
index 0000000..0e94da9
--- /dev/null
+++ b/lib/cpp/src/qt/moc_TQTcpServer.cpp
@@ -0,0 +1,85 @@
+/****************************************************************************
+** Meta object code from reading C++ file 'TQTcpServer.h'
+**
+** Created: Thu Feb 9 20:47:09 2012
+** by: The Qt Meta Object Compiler version 62 (Qt 4.6.3)
+**
+** WARNING! All changes made in this file will be lost!
+*****************************************************************************/
+
+#include "TQTcpServer.h"
+#if !defined(Q_MOC_OUTPUT_REVISION)
+#error "The header file 'TQTcpServer.h' doesn't include <QObject>."
+#elif Q_MOC_OUTPUT_REVISION != 62
+#error "This file was generated using the moc from 4.6.3. It"
+#error "cannot be used with the include files from this version of Qt."
+#error "(The moc has changed too much.)"
+#endif
+
+QT_BEGIN_MOC_NAMESPACE
+static const uint qt_meta_data_apache__thrift__async__TQTcpServer[] = {
+
+ // content:
+ 4, // revision
+ 0, // classname
+ 0, 0, // classinfo
+ 3, 14, // methods
+ 0, 0, // properties
+ 0, 0, // enums/sets
+ 0, 0, // constructors
+ 0, // flags
+ 0, // signalCount
+
+ // slots: signature, parameters, type, tag, flags
+ 36, 35, 35, 35, 0x08,
+ 54, 35, 35, 35, 0x08,
+ 68, 35, 35, 35, 0x08,
+
+ 0 // eod
+};
+
+static const char qt_meta_stringdata_apache__thrift__async__TQTcpServer[] = {
+ "apache::thrift::async::TQTcpServer\0\0"
+ "processIncoming()\0beginDecode()\0"
+ "socketClosed()\0"
+};
+
+const QMetaObject apache::thrift::async::TQTcpServer::staticMetaObject = {
+ { &QObject::staticMetaObject, qt_meta_stringdata_apache__thrift__async__TQTcpServer,
+ qt_meta_data_apache__thrift__async__TQTcpServer, 0 }
+};
+
+#ifdef Q_NO_DATA_RELOCATION
+const QMetaObject &apache::thrift::async::TQTcpServer::getStaticMetaObject() { return staticMetaObject; }
+#endif //Q_NO_DATA_RELOCATION
+
+const QMetaObject *apache::thrift::async::TQTcpServer::metaObject() const
+{
+ return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject;
+}
+
+void *apache::thrift::async::TQTcpServer::qt_metacast(const char *_clname)
+{
+ if (!_clname) return 0;
+ if (!strcmp(_clname, qt_meta_stringdata_apache__thrift__async__TQTcpServer))
+ return static_cast<void*>(const_cast< TQTcpServer*>(this));
+ return QObject::qt_metacast(_clname);
+}
+
+int apache::thrift::async::TQTcpServer::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
+{
+ _id = QObject::qt_metacall(_c, _id, _a);
+ if (_id < 0)
+ return _id;
+ if (_c == QMetaObject::InvokeMetaMethod) {
+ switch (_id) {
+ case 0: processIncoming(); break;
+ case 1: beginDecode(); break;
+ case 2: socketClosed(); break;
+ default: ;
+ }
+ _id -= 3;
+ }
+ return _id;
+}
+QT_END_MOC_NAMESPACE
diff --git a/lib/cpp/thrift-qt.pc.in b/lib/cpp/thrift-qt.pc.in
new file mode 100644
index 0000000..846e362
--- /dev/null
+++ b/lib/cpp/thrift-qt.pc.in
@@ -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.
+#
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Thrift
+Description: Thrift Qt API
+Version: @VERSION@
+Requires: thrift = @VERSION@
+Libs: -L${libdir} -lthriftqt
+Cflags: -I${includedir}/thrift