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