From c270d9ef5c56c0edfa6f5450caec1ea19e9a8f71 Mon Sep 17 00:00:00 2001 From: Jens Geyer Date: Fri, 28 Feb 2020 19:22:34 +0100 Subject: [PATCH] Revert "THRIFT-4982 Remove deprecated C# bindings from the code base" - nullable fix - Only compiler, test, lib and tutorial code. --- .gitignore | 4 + compiler/cpp/CMakeLists.txt | 59 +- compiler/cpp/Makefile.am | 1 + compiler/cpp/compiler.vcxproj | 1 + compiler/cpp/compiler.vcxproj.filters | 3 + .../src/thrift/generate/t_csharp_generator.cc | 3288 +++++++++++++++++ .../cpp/src/thrift/generate/t_generator.cc | 5 +- compiler/cpp/tests/CMakeLists.txt | 1 + lib/csharp/Makefile.am | 114 + lib/csharp/README.md | 32 + .../Properties/AssemblyInfo.cs | 60 + lib/csharp/ThriftMSBuildTask/ThriftBuild.cs | 246 ++ .../ThriftMSBuildTask.csproj | 118 + lib/csharp/coding_standards.md | 6 + lib/csharp/src/Collections/TCollections.cs | 94 + lib/csharp/src/Collections/THashSet.cs | 160 + lib/csharp/src/Net35/ExtensionsNet35.cs | 31 + lib/csharp/src/Properties/AssemblyInfo.cs | 55 + lib/csharp/src/Protocol/TAbstractBase.cs | 29 + lib/csharp/src/Protocol/TBase.cs | 29 + lib/csharp/src/Protocol/TBase64Utils.cs | 100 + lib/csharp/src/Protocol/TBinaryProtocol.cs | 410 ++ lib/csharp/src/Protocol/TCompactProtocol.cs | 867 +++++ lib/csharp/src/Protocol/TField.cs | 62 + lib/csharp/src/Protocol/TGuidExtensions.cs | 69 + lib/csharp/src/Protocol/TJSONProtocol.cs | 1134 ++++++ lib/csharp/src/Protocol/TList.cs | 54 + lib/csharp/src/Protocol/TMap.cs | 62 + lib/csharp/src/Protocol/TMessage.cs | 62 + lib/csharp/src/Protocol/TMessageType.cs | 31 + .../src/Protocol/TMultiplexedProcessor.cs | 183 + .../src/Protocol/TMultiplexedProtocol.cs | 103 + lib/csharp/src/Protocol/TProtocol.cs | 144 + lib/csharp/src/Protocol/TProtocolDecorator.cs | 271 ++ lib/csharp/src/Protocol/TProtocolException.cs | 67 + lib/csharp/src/Protocol/TProtocolFactory.cs | 33 + lib/csharp/src/Protocol/TProtocolUtil.cs | 108 + lib/csharp/src/Protocol/TSet.cs | 59 + lib/csharp/src/Protocol/TStruct.cs | 46 + lib/csharp/src/Protocol/TType.cs | 45 + lib/csharp/src/Server/TServer.cs | 155 + lib/csharp/src/Server/TServerEventHandler.cs | 53 + lib/csharp/src/Server/TSimpleServer.cs | 180 + lib/csharp/src/Server/TThreadPoolServer.cs | 295 ++ lib/csharp/src/Server/TThreadedServer.cs | 282 ++ lib/csharp/src/TApplicationException.cs | 146 + lib/csharp/src/TAsyncProcessor.cs | 38 + lib/csharp/src/TControllingHandler.cs | 29 + lib/csharp/src/TException.cs | 40 + lib/csharp/src/TProcessor.cs | 33 + lib/csharp/src/TProcessorFactory.cs | 30 + lib/csharp/src/TPrototypeProcessorFactory.cs | 55 + lib/csharp/src/TSingletonProcessorFactory.cs | 43 + lib/csharp/src/Thrift.45.csproj | 139 + lib/csharp/src/Thrift.csproj | 167 + lib/csharp/src/Thrift.sln | 47 + .../src/Transport/TBufferedTransport.cs | 194 + lib/csharp/src/Transport/TFramedTransport.cs | 205 + lib/csharp/src/Transport/THttpClient.cs | 486 +++ lib/csharp/src/Transport/THttpHandler.cs | 102 + .../src/Transport/THttpTaskAsyncHandler.cs | 97 + lib/csharp/src/Transport/TMemoryBuffer.cs | 117 + .../Transport/TNamedPipeClientTransport.cs | 111 + .../Transport/TNamedPipeServerTransport.cs | 296 ++ lib/csharp/src/Transport/TServerSocket.cs | 176 + lib/csharp/src/Transport/TServerTransport.cs | 44 + .../src/Transport/TSilverlightSocket.cs | 393 ++ lib/csharp/src/Transport/TSocket.cs | 245 ++ .../src/Transport/TSocketVersionizer.cs | 78 + lib/csharp/src/Transport/TStreamTransport.cs | 128 + lib/csharp/src/Transport/TTLSServerSocket.cs | 223 ++ lib/csharp/src/Transport/TTLSSocket.cs | 445 +++ lib/csharp/src/Transport/TTransport.cs | 146 + .../src/Transport/TTransportException.cs | 69 + lib/csharp/src/Transport/TTransportFactory.cs | 42 + lib/csharp/src/thrift.snk | Bin 0 -> 596 bytes lib/csharp/test/JSON/JSONTest.csproj | 85 + lib/csharp/test/JSON/Program.cs | 95 + .../test/JSON/Properties/AssemblyInfo.cs | 55 + lib/csharp/test/JSON/app.config | 21 + .../Multiplex/Client/Multiplex.Test.Client.cs | 82 + .../Multiplex/Client/MultiplexClient.csproj | 148 + .../Client/Properties/AssemblyInfo.cs | 55 + lib/csharp/test/Multiplex/Makefile.am | 63 + .../test/Multiplex/Multiplex.Test.Common.cs | 40 + .../Multiplex/Server/Multiplex.Test.Server.cs | 107 + .../Multiplex/Server/MultiplexServer.csproj | 148 + .../Server/Properties/AssemblyInfo.cs | 55 + .../ThriftMVCTest/App_Start/FilterConfig.cs | 31 + .../ThriftMVCTest/App_Start/RouteConfig.cs | 39 + .../test/ThriftMVCTest/AsyncHttpHandler.cs | 32 + .../Controllers/HomeController.cs | 70 + lib/csharp/test/ThriftMVCTest/Global.asax | 19 + lib/csharp/test/ThriftMVCTest/Global.asax.cs | 34 + .../ThriftMVCTest/Properties/AssemblyInfo.cs | 53 + .../test/ThriftMVCTest/SecondServiceImpl.cs | 37 + .../test/ThriftMVCTest/SyncHttpHandler.cs | 32 + .../test/ThriftMVCTest/ThriftMVCTest.csproj | 200 + .../ThriftMVCTest/Views/Home/Index.cshtml | 25 + .../ThriftMVCTest/Views/Shared/_Layout.cshtml | 30 + .../test/ThriftMVCTest/Views/Web.config | 60 + .../ThriftMVCTest/Views/_ViewStart.cshtml | 22 + .../test/ThriftMVCTest/Web.Debug.config | 45 + .../test/ThriftMVCTest/Web.Release.config | 46 + lib/csharp/test/ThriftMVCTest/Web.config | 92 + lib/csharp/test/ThriftMVCTest/favicon.ico | Bin 0 -> 32038 bytes lib/csharp/test/ThriftMVCTest/packages.config | 25 + test/ThriftTest.thrift | 1 + test/csharp/Makefile.am | 95 + test/csharp/Program.cs | 71 + test/csharp/Properties/AssemblyInfo.cs | 55 + test/csharp/TestClient.cs | 890 +++++ test/csharp/TestServer.cs | 541 +++ test/csharp/ThriftTest.csproj | 138 + test/csharp/ThriftTest.sln | 31 + test/csharp/app.config | 3 + tutorial/csharp/CsharpClient/CsharpClient.cs | 92 + .../csharp/CsharpClient/CsharpClient.csproj | 110 + .../CsharpClient/Properties/AssemblyInfo.cs | 55 + tutorial/csharp/CsharpServer/CsharpServer.cs | 129 + .../csharp/CsharpServer/CsharpServer.csproj | 111 + .../CsharpServer/Properties/AssemblyInfo.cs | 55 + tutorial/csharp/tutorial.sln | 39 + 123 files changed, 17807 insertions(+), 30 deletions(-) create mode 100644 compiler/cpp/src/thrift/generate/t_csharp_generator.cc create mode 100644 lib/csharp/Makefile.am create mode 100644 lib/csharp/README.md create mode 100644 lib/csharp/ThriftMSBuildTask/Properties/AssemblyInfo.cs create mode 100644 lib/csharp/ThriftMSBuildTask/ThriftBuild.cs create mode 100644 lib/csharp/ThriftMSBuildTask/ThriftMSBuildTask.csproj create mode 100644 lib/csharp/coding_standards.md create mode 100644 lib/csharp/src/Collections/TCollections.cs create mode 100644 lib/csharp/src/Collections/THashSet.cs create mode 100644 lib/csharp/src/Net35/ExtensionsNet35.cs create mode 100644 lib/csharp/src/Properties/AssemblyInfo.cs create mode 100644 lib/csharp/src/Protocol/TAbstractBase.cs create mode 100644 lib/csharp/src/Protocol/TBase.cs create mode 100644 lib/csharp/src/Protocol/TBase64Utils.cs create mode 100644 lib/csharp/src/Protocol/TBinaryProtocol.cs create mode 100644 lib/csharp/src/Protocol/TCompactProtocol.cs create mode 100644 lib/csharp/src/Protocol/TField.cs create mode 100644 lib/csharp/src/Protocol/TGuidExtensions.cs create mode 100644 lib/csharp/src/Protocol/TJSONProtocol.cs create mode 100644 lib/csharp/src/Protocol/TList.cs create mode 100644 lib/csharp/src/Protocol/TMap.cs create mode 100644 lib/csharp/src/Protocol/TMessage.cs create mode 100644 lib/csharp/src/Protocol/TMessageType.cs create mode 100644 lib/csharp/src/Protocol/TMultiplexedProcessor.cs create mode 100644 lib/csharp/src/Protocol/TMultiplexedProtocol.cs create mode 100644 lib/csharp/src/Protocol/TProtocol.cs create mode 100644 lib/csharp/src/Protocol/TProtocolDecorator.cs create mode 100644 lib/csharp/src/Protocol/TProtocolException.cs create mode 100644 lib/csharp/src/Protocol/TProtocolFactory.cs create mode 100644 lib/csharp/src/Protocol/TProtocolUtil.cs create mode 100644 lib/csharp/src/Protocol/TSet.cs create mode 100644 lib/csharp/src/Protocol/TStruct.cs create mode 100644 lib/csharp/src/Protocol/TType.cs create mode 100644 lib/csharp/src/Server/TServer.cs create mode 100644 lib/csharp/src/Server/TServerEventHandler.cs create mode 100644 lib/csharp/src/Server/TSimpleServer.cs create mode 100644 lib/csharp/src/Server/TThreadPoolServer.cs create mode 100644 lib/csharp/src/Server/TThreadedServer.cs create mode 100644 lib/csharp/src/TApplicationException.cs create mode 100644 lib/csharp/src/TAsyncProcessor.cs create mode 100644 lib/csharp/src/TControllingHandler.cs create mode 100644 lib/csharp/src/TException.cs create mode 100644 lib/csharp/src/TProcessor.cs create mode 100644 lib/csharp/src/TProcessorFactory.cs create mode 100644 lib/csharp/src/TPrototypeProcessorFactory.cs create mode 100644 lib/csharp/src/TSingletonProcessorFactory.cs create mode 100644 lib/csharp/src/Thrift.45.csproj create mode 100644 lib/csharp/src/Thrift.csproj create mode 100644 lib/csharp/src/Thrift.sln create mode 100644 lib/csharp/src/Transport/TBufferedTransport.cs create mode 100644 lib/csharp/src/Transport/TFramedTransport.cs create mode 100644 lib/csharp/src/Transport/THttpClient.cs create mode 100644 lib/csharp/src/Transport/THttpHandler.cs create mode 100644 lib/csharp/src/Transport/THttpTaskAsyncHandler.cs create mode 100644 lib/csharp/src/Transport/TMemoryBuffer.cs create mode 100644 lib/csharp/src/Transport/TNamedPipeClientTransport.cs create mode 100644 lib/csharp/src/Transport/TNamedPipeServerTransport.cs create mode 100644 lib/csharp/src/Transport/TServerSocket.cs create mode 100644 lib/csharp/src/Transport/TServerTransport.cs create mode 100644 lib/csharp/src/Transport/TSilverlightSocket.cs create mode 100644 lib/csharp/src/Transport/TSocket.cs create mode 100644 lib/csharp/src/Transport/TSocketVersionizer.cs create mode 100644 lib/csharp/src/Transport/TStreamTransport.cs create mode 100644 lib/csharp/src/Transport/TTLSServerSocket.cs create mode 100644 lib/csharp/src/Transport/TTLSSocket.cs create mode 100644 lib/csharp/src/Transport/TTransport.cs create mode 100644 lib/csharp/src/Transport/TTransportException.cs create mode 100644 lib/csharp/src/Transport/TTransportFactory.cs create mode 100644 lib/csharp/src/thrift.snk create mode 100644 lib/csharp/test/JSON/JSONTest.csproj create mode 100644 lib/csharp/test/JSON/Program.cs create mode 100644 lib/csharp/test/JSON/Properties/AssemblyInfo.cs create mode 100644 lib/csharp/test/JSON/app.config create mode 100644 lib/csharp/test/Multiplex/Client/Multiplex.Test.Client.cs create mode 100644 lib/csharp/test/Multiplex/Client/MultiplexClient.csproj create mode 100644 lib/csharp/test/Multiplex/Client/Properties/AssemblyInfo.cs create mode 100644 lib/csharp/test/Multiplex/Makefile.am create mode 100644 lib/csharp/test/Multiplex/Multiplex.Test.Common.cs create mode 100644 lib/csharp/test/Multiplex/Server/Multiplex.Test.Server.cs create mode 100644 lib/csharp/test/Multiplex/Server/MultiplexServer.csproj create mode 100644 lib/csharp/test/Multiplex/Server/Properties/AssemblyInfo.cs create mode 100644 lib/csharp/test/ThriftMVCTest/App_Start/FilterConfig.cs create mode 100644 lib/csharp/test/ThriftMVCTest/App_Start/RouteConfig.cs create mode 100644 lib/csharp/test/ThriftMVCTest/AsyncHttpHandler.cs create mode 100644 lib/csharp/test/ThriftMVCTest/Controllers/HomeController.cs create mode 100644 lib/csharp/test/ThriftMVCTest/Global.asax create mode 100644 lib/csharp/test/ThriftMVCTest/Global.asax.cs create mode 100644 lib/csharp/test/ThriftMVCTest/Properties/AssemblyInfo.cs create mode 100644 lib/csharp/test/ThriftMVCTest/SecondServiceImpl.cs create mode 100644 lib/csharp/test/ThriftMVCTest/SyncHttpHandler.cs create mode 100644 lib/csharp/test/ThriftMVCTest/ThriftMVCTest.csproj create mode 100644 lib/csharp/test/ThriftMVCTest/Views/Home/Index.cshtml create mode 100644 lib/csharp/test/ThriftMVCTest/Views/Shared/_Layout.cshtml create mode 100644 lib/csharp/test/ThriftMVCTest/Views/Web.config create mode 100644 lib/csharp/test/ThriftMVCTest/Views/_ViewStart.cshtml create mode 100644 lib/csharp/test/ThriftMVCTest/Web.Debug.config create mode 100644 lib/csharp/test/ThriftMVCTest/Web.Release.config create mode 100644 lib/csharp/test/ThriftMVCTest/Web.config create mode 100644 lib/csharp/test/ThriftMVCTest/favicon.ico create mode 100644 lib/csharp/test/ThriftMVCTest/packages.config create mode 100644 test/csharp/Makefile.am create mode 100644 test/csharp/Program.cs create mode 100644 test/csharp/Properties/AssemblyInfo.cs create mode 100644 test/csharp/TestClient.cs create mode 100644 test/csharp/TestServer.cs create mode 100644 test/csharp/ThriftTest.csproj create mode 100644 test/csharp/ThriftTest.sln create mode 100644 test/csharp/app.config create mode 100644 tutorial/csharp/CsharpClient/CsharpClient.cs create mode 100644 tutorial/csharp/CsharpClient/CsharpClient.csproj create mode 100644 tutorial/csharp/CsharpClient/Properties/AssemblyInfo.cs create mode 100644 tutorial/csharp/CsharpServer/CsharpServer.cs create mode 100644 tutorial/csharp/CsharpServer/CsharpServer.csproj create mode 100644 tutorial/csharp/CsharpServer/Properties/AssemblyInfo.cs create mode 100644 tutorial/csharp/tutorial.sln diff --git a/.gitignore b/.gitignore index e7f06a77214..cfa07ff566e 100644 --- a/.gitignore +++ b/.gitignore @@ -158,6 +158,8 @@ project.lock.json /lib/cpp/x64/Debug-mt/ /lib/cpp/x64/Release /lib/cpp/x64/Release-mt +/lib/csharp/src/bin +/lib/csharp/src/obj /lib/c_glib/*.gcda /lib/c_glib/*.gcno /lib/c_glib/*.loT @@ -346,6 +348,8 @@ project.lock.json /test/cpp/StressTestNonBlocking /test/cpp/TestClient /test/cpp/TestServer +/test/csharp/bin +/test/csharp/obj /test/dart/**/.dart_tool /test/dart/**/.packages /test/dart/**/packages diff --git a/compiler/cpp/CMakeLists.txt b/compiler/cpp/CMakeLists.txt index b0f123555d8..9fc7ec0ec62 100644 --- a/compiler/cpp/CMakeLists.txt +++ b/compiler/cpp/CMakeLists.txt @@ -86,35 +86,36 @@ macro(THRIFT_ADD_VALIDATOR_COMPILER name description initial) endmacro() # The following compiler can be enabled or disabled -THRIFT_ADD_COMPILER(c_glib "Enable compiler for C with Glib" ON) -THRIFT_ADD_COMPILER(cl "Enable compiler for Common LISP" ON) -THRIFT_ADD_COMPILER(cpp "Enable compiler for C++" ON) -THRIFT_ADD_COMPILER(d "Enable compiler for D" ON) -THRIFT_ADD_COMPILER(dart "Enable compiler for Dart" ON) -THRIFT_ADD_COMPILER(delphi "Enable compiler for Delphi" ON) -THRIFT_ADD_COMPILER(erl "Enable compiler for Erlang" ON) -THRIFT_ADD_COMPILER(go "Enable compiler for Go" ON) -THRIFT_ADD_COMPILER(gv "Enable compiler for GraphViz" ON) -THRIFT_ADD_COMPILER(haxe "Enable compiler for Haxe" ON) -THRIFT_ADD_COMPILER(html "Enable compiler for HTML Documentation" ON) -THRIFT_ADD_COMPILER(markdown "Enable compiler for Markdown Documentation" ON) -THRIFT_ADD_COMPILER(java "Enable compiler for Java" ON) -THRIFT_ADD_COMPILER(javame "Enable compiler for Java ME" ON) -THRIFT_ADD_COMPILER(js "Enable compiler for JavaScript" ON) -THRIFT_ADD_COMPILER(json "Enable compiler for JSON" ON) -THRIFT_ADD_COMPILER(kotlin "Enable compiler for Kotlin" ON) -THRIFT_ADD_COMPILER(lua "Enable compiler for Lua" ON) -THRIFT_ADD_COMPILER(netstd "Enable compiler for .NET Standard" ON) -THRIFT_ADD_COMPILER(ocaml "Enable compiler for OCaml" ON) -THRIFT_ADD_COMPILER(perl "Enable compiler for Perl" ON) -THRIFT_ADD_COMPILER(php "Enable compiler for PHP" ON) -THRIFT_ADD_COMPILER(py "Enable compiler for Python 2.0" ON) -THRIFT_ADD_COMPILER(rb "Enable compiler for Ruby" ON) -THRIFT_ADD_COMPILER(rs "Enable compiler for Rust" ON) -THRIFT_ADD_COMPILER(st "Enable compiler for Smalltalk" ON) -THRIFT_ADD_COMPILER(swift "Enable compiler for Cocoa Swift" ON) -THRIFT_ADD_COMPILER(xml "Enable compiler for XML" ON) -THRIFT_ADD_COMPILER(xsd "Enable compiler for XSD" ON) +THRIFT_ADD_COMPILER(c_glib "Enable compiler for C with Glib" ON) +THRIFT_ADD_COMPILER(cl "Enable compiler for Common LISP" ON) +THRIFT_ADD_COMPILER(cpp "Enable compiler for C++" ON) +THRIFT_ADD_COMPILER(csharp "Enable compiler for C#" ON) +THRIFT_ADD_COMPILER(d "Enable compiler for D" ON) +THRIFT_ADD_COMPILER(dart "Enable compiler for Dart" ON) +THRIFT_ADD_COMPILER(delphi "Enable compiler for Delphi" ON) +THRIFT_ADD_COMPILER(erl "Enable compiler for Erlang" ON) +THRIFT_ADD_COMPILER(go "Enable compiler for Go" ON) +THRIFT_ADD_COMPILER(gv "Enable compiler for GraphViz" ON) +THRIFT_ADD_COMPILER(haxe "Enable compiler for Haxe" ON) +THRIFT_ADD_COMPILER(html "Enable compiler for HTML Documentation" ON) +THRIFT_ADD_COMPILER(markdown "Enable compiler for Markdown Documentation" ON) +THRIFT_ADD_COMPILER(java "Enable compiler for Java" ON) +THRIFT_ADD_COMPILER(javame "Enable compiler for Java ME" ON) +THRIFT_ADD_COMPILER(js "Enable compiler for JavaScript" ON) +THRIFT_ADD_COMPILER(json "Enable compiler for JSON" ON) +THRIFT_ADD_COMPILER(kotlin "Enable compiler for Kotlin" ON) +THRIFT_ADD_COMPILER(lua "Enable compiler for Lua" ON) +THRIFT_ADD_COMPILER(netstd "Enable compiler for .NET Standard" ON) +THRIFT_ADD_COMPILER(ocaml "Enable compiler for OCaml" ON) +THRIFT_ADD_COMPILER(perl "Enable compiler for Perl" ON) +THRIFT_ADD_COMPILER(php "Enable compiler for PHP" ON) +THRIFT_ADD_COMPILER(py "Enable compiler for Python 2.0" ON) +THRIFT_ADD_COMPILER(rb "Enable compiler for Ruby" ON) +THRIFT_ADD_COMPILER(rs "Enable compiler for Rust" ON) +THRIFT_ADD_COMPILER(st "Enable compiler for Smalltalk" ON) +THRIFT_ADD_COMPILER(swift "Enable compiler for Cocoa Swift" ON) +THRIFT_ADD_COMPILER(xml "Enable compiler for XML" ON) +THRIFT_ADD_COMPILER(xsd "Enable compiler for XSD" ON) # The following compiler can be enabled or disabled by enabling or disabling certain languages THRIFT_ADD_VALIDATOR_COMPILER(go "Enable validator compiler for Go" ON) diff --git a/compiler/cpp/Makefile.am b/compiler/cpp/Makefile.am index 29ef1bd94ca..0fd08910c2d 100644 --- a/compiler/cpp/Makefile.am +++ b/compiler/cpp/Makefile.am @@ -72,6 +72,7 @@ thrift_SOURCES = src/thrift/audit/t_audit.cpp \ thrift_SOURCES += src/thrift/generate/t_c_glib_generator.cc \ src/thrift/generate/t_cl_generator.cc \ src/thrift/generate/t_cpp_generator.cc \ + src/thrift/generate/t_csharp_generator.cc \ src/thrift/generate/t_d_generator.cc \ src/thrift/generate/t_dart_generator.cc \ src/thrift/generate/t_delphi_generator.cc \ diff --git a/compiler/cpp/compiler.vcxproj b/compiler/cpp/compiler.vcxproj index 9cc2ccbb786..9b6c924078d 100644 --- a/compiler/cpp/compiler.vcxproj +++ b/compiler/cpp/compiler.vcxproj @@ -56,6 +56,7 @@ + diff --git a/compiler/cpp/compiler.vcxproj.filters b/compiler/cpp/compiler.vcxproj.filters index 1c97e28bda7..54fd991f4e7 100644 --- a/compiler/cpp/compiler.vcxproj.filters +++ b/compiler/cpp/compiler.vcxproj.filters @@ -98,6 +98,9 @@ generate + + generate + generate diff --git a/compiler/cpp/src/thrift/generate/t_csharp_generator.cc b/compiler/cpp/src/thrift/generate/t_csharp_generator.cc new file mode 100644 index 00000000000..b8523043514 --- /dev/null +++ b/compiler/cpp/src/thrift/generate/t_csharp_generator.cc @@ -0,0 +1,3288 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "thrift/platform.h" +#include "thrift/generate/t_oop_generator.h" + +using std::map; +using std::ostream; +using std::ostringstream; +using std::string; +using std::stringstream; +using std::vector; + +static const string endl = "\n"; // avoid ostream << std::endl flushes + +struct member_mapping_scope { + void* scope_member; + std::map mapping_table; +}; + +class t_csharp_generator : public t_oop_generator { +public: + t_csharp_generator(t_program* program, + const std::map& parsed_options, + const std::string& option_string) + : t_oop_generator(program) { + (void)option_string; + + std::map::const_iterator iter; + + async_ = false; + nullable_ = false; + hashcode_ = false; + union_ = false; + serialize_ = false; + wcf_ = false; + wcf_namespace_.clear(); + for( iter = parsed_options.begin(); iter != parsed_options.end(); ++iter) { + if( iter->first.compare("async") == 0) { + async_ = true; + } else if( iter->first.compare("nullable") == 0) { + nullable_ = true; + } else if( iter->first.compare("hashcode") == 0) { + hashcode_ = true; + } else if( iter->first.compare("union") == 0) { + union_ = true; + } else if( iter->first.compare("serial") == 0) { + serialize_ = true; + wcf_namespace_ = iter->second; // since there can be only one namespace + } else if( iter->first.compare("wcf") == 0) { + wcf_ = true; + wcf_namespace_ = iter->second; + } else { + throw "unknown option csharp:" + iter->first; + } + } + + out_dir_base_ = "gen-csharp"; + } + + void init_generator() override; + void close_generator() override; + std::string display_name() const override; + + void generate_consts(std::vector consts) override; + + void generate_typedef(t_typedef* ttypedef) override; + void generate_enum(t_enum* tenum) override; + void generate_struct(t_struct* tstruct) override; + void generate_union(t_struct* tunion); + void generate_xception(t_struct* txception) override; + void generate_service(t_service* tservice) override; + void generate_property(ostream& out, t_field* tfield, bool isPublic, bool generateIsset); + void generate_csharp_property(ostream& out, + t_field* tfield, + bool isPublic, + bool includeIsset = true, + std::string fieldPrefix = ""); + bool print_const_value(std::ostream& out, + std::string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval = false, + bool needtype = false); + std::string render_const_value(std::ostream& out, + std::string name, + t_type* type, + t_const_value* value); + void print_const_constructor(std::ostream& out, std::vector consts); + void print_const_def_value(std::ostream& out, + std::string name, + t_type* type, + t_const_value* value); + + void generate_csharp_struct(t_struct* tstruct, bool is_exception); + void generate_csharp_union(t_struct* tunion); + void generate_csharp_struct_definition(std::ostream& out, + t_struct* tstruct, + bool is_xception = false, + bool in_class = false, + bool is_result = false); + void generate_csharp_union_definition(std::ostream& out, t_struct* tunion); + void generate_csharp_union_class(std::ostream& out, t_struct* tunion, t_field* tfield); + void generate_csharp_wcffault(std::ostream& out, t_struct* tstruct); + void generate_csharp_struct_reader(std::ostream& out, t_struct* tstruct); + void generate_csharp_struct_result_writer(std::ostream& out, t_struct* tstruct); + void generate_csharp_struct_writer(std::ostream& out, t_struct* tstruct); + void generate_csharp_struct_tostring(std::ostream& out, t_struct* tstruct); + void generate_csharp_struct_equals(std::ostream& out, t_struct* tstruct); + void generate_csharp_struct_hashcode(std::ostream& out, t_struct* tstruct); + void generate_csharp_union_reader(std::ostream& out, t_struct* tunion); + + void generate_function_helpers(t_function* tfunction); + void generate_service_interface(t_service* tservice); + void generate_separate_service_interfaces(t_service* tservice); + void generate_sync_service_interface(t_service* tservice); + void generate_async_service_interface(t_service* tservice); + void generate_combined_service_interface(t_service* tservice); + void generate_silverlight_async_methods(t_service* tservice); + void generate_service_helpers(t_service* tservice); + void generate_service_client(t_service* tservice); + void generate_service_server(t_service* tservice); + void generate_service_server_sync(t_service* tservice); + void generate_service_server_async(t_service* tservice); + void generate_process_function(t_service* tservice, t_function* function); + void generate_process_function_async(t_service* tservice, t_function* function); + + void generate_deserialize_field(std::ostream& out, + t_field* tfield, + std::string prefix = "", + bool is_propertyless = false); + void generate_deserialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + void generate_deserialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + void generate_deserialize_set_element(std::ostream& out, t_set* tset, std::string prefix = ""); + void generate_deserialize_map_element(std::ostream& out, t_map* tmap, std::string prefix = ""); + void generate_deserialize_list_element(std::ostream& out, t_list* list, std::string prefix = ""); + void generate_serialize_field(std::ostream& out, + t_field* tfield, + std::string prefix = "", + bool is_element = false, + bool is_propertyless = false); + void generate_serialize_struct(std::ostream& out, t_struct* tstruct, std::string prefix = ""); + void generate_serialize_container(std::ostream& out, t_type* ttype, std::string prefix = ""); + void generate_serialize_map_element(std::ostream& out, + t_map* tmap, + std::string iter, + std::string map); + void generate_serialize_set_element(std::ostream& out, t_set* tmap, std::string iter); + void generate_serialize_list_element(std::ostream& out, t_list* tlist, std::string iter); + + void generate_csharp_doc(std::ostream& out, t_field* field); + void generate_csharp_doc(std::ostream& out, t_doc* tdoc); + void generate_csharp_doc(std::ostream& out, t_function* tdoc); + void generate_csharp_docstring_comment(std::ostream& out, string contents); + + void start_csharp_namespace(std::ostream& out); + void end_csharp_namespace(std::ostream& out); + + std::string csharp_type_usings(); + std::string csharp_thrift_usings(); + + std::string type_name(t_type* ttype, + bool in_countainer = false, + bool in_init = false, + bool in_param = false, + bool is_required = false); + std::string base_type_name(t_base_type* tbase, + bool in_container = false, + bool in_param = false, + bool is_required = false); + std::string declare_field(t_field* tfield, bool init = false, std::string prefix = ""); + std::string function_signature_async_begin(t_function* tfunction, std::string prefix = ""); + std::string function_signature_async_end(t_function* tfunction, std::string prefix = ""); + std::string function_signature_async(t_function* tfunction, std::string prefix = ""); + std::string function_signature(t_function* tfunction, std::string prefix = ""); + std::string argument_list(t_struct* tstruct); + std::string type_to_enum(t_type* ttype); + std::string prop_name(t_field* tfield, bool suppress_mapping = false); + std::string get_enum_class_name(t_type* type) override; + + bool field_has_default(t_field* tfield) { return tfield->get_value() != NULL; } + + bool field_is_required(t_field* tfield) { return tfield->get_req() == t_field::T_REQUIRED; } + + bool type_can_be_null(t_type* ttype) { + while (ttype->is_typedef()) { + ttype = ((t_typedef*)ttype)->get_type(); + } + + return ttype->is_container() || ttype->is_struct() || ttype->is_xception() + || ttype->is_string(); + } + +private: + std::string namespace_name_; + ofstream_with_content_based_conditional_update f_service_; + std::string namespace_dir_; + bool async_; + bool nullable_; + bool union_; + bool hashcode_; + bool serialize_; + bool wcf_; + std::string wcf_namespace_; + + std::map csharp_keywords; + std::vector member_mapping_scopes; + + void init_keywords(); + std::string normalize_name(std::string name); + std::string make_valid_csharp_identifier(std::string const& fromName); + void prepare_member_name_mapping(t_struct* tstruct); + void prepare_member_name_mapping(void* scope, + const vector& members, + const string& structname); + void cleanup_member_name_mapping(void* scope); + string get_mapped_member_name(string oldname); +}; + +void t_csharp_generator::init_generator() { + MKDIR(get_out_dir().c_str()); + namespace_name_ = program_->get_namespace("csharp"); + + string dir = namespace_name_; + string subdir = get_out_dir().c_str(); + string::size_type loc; + + while ((loc = dir.find(".")) != string::npos) { + subdir = subdir + "/" + dir.substr(0, loc); + MKDIR(subdir.c_str()); + dir = dir.substr(loc + 1); + } + if (dir.size() > 0) { + subdir = subdir + "/" + dir; + MKDIR(subdir.c_str()); + } + + namespace_dir_ = subdir; + init_keywords(); + + while( ! member_mapping_scopes.empty()) { + cleanup_member_name_mapping( member_mapping_scopes.back().scope_member); + } + + pverbose("C# options:\n"); + pverbose("- async ...... %s\n", (async_ ? "ON" : "off")); + pverbose("- nullable ... %s\n", (nullable_ ? "ON" : "off")); + pverbose("- union ...... %s\n", (union_ ? "ON" : "off")); + pverbose("- hashcode ... %s\n", (hashcode_ ? "ON" : "off")); + pverbose("- serialize .. %s\n", (serialize_ ? "ON" : "off")); + pverbose("- wcf ........ %s\n", (wcf_ ? "ON" : "off")); +} + +std::string t_csharp_generator::normalize_name(std::string name) { + string tmp(name); + std::transform(tmp.begin(), tmp.end(), tmp.begin(), static_cast(std::tolower)); + + // un-conflict keywords by prefixing with "@" + if (csharp_keywords.find(tmp) != csharp_keywords.end()) { + return "@" + name; + } + + // no changes necessary + return name; +} + +void t_csharp_generator::init_keywords() { + csharp_keywords.clear(); + + // C# keywords + csharp_keywords["abstract"] = 1; + csharp_keywords["as"] = 1; + csharp_keywords["base"] = 1; + csharp_keywords["bool"] = 1; + csharp_keywords["break"] = 1; + csharp_keywords["byte"] = 1; + csharp_keywords["case"] = 1; + csharp_keywords["catch"] = 1; + csharp_keywords["char"] = 1; + csharp_keywords["checked"] = 1; + csharp_keywords["class"] = 1; + csharp_keywords["const"] = 1; + csharp_keywords["continue"] = 1; + csharp_keywords["decimal"] = 1; + csharp_keywords["default"] = 1; + csharp_keywords["delegate"] = 1; + csharp_keywords["do"] = 1; + csharp_keywords["double"] = 1; + csharp_keywords["else"] = 1; + csharp_keywords["enum"] = 1; + csharp_keywords["event"] = 1; + csharp_keywords["explicit"] = 1; + csharp_keywords["extern"] = 1; + csharp_keywords["false"] = 1; + csharp_keywords["finally"] = 1; + csharp_keywords["fixed"] = 1; + csharp_keywords["float"] = 1; + csharp_keywords["for"] = 1; + csharp_keywords["foreach"] = 1; + csharp_keywords["goto"] = 1; + csharp_keywords["if"] = 1; + csharp_keywords["implicit"] = 1; + csharp_keywords["in"] = 1; + csharp_keywords["int"] = 1; + csharp_keywords["interface"] = 1; + csharp_keywords["internal"] = 1; + csharp_keywords["is"] = 1; + csharp_keywords["lock"] = 1; + csharp_keywords["long"] = 1; + csharp_keywords["namespace"] = 1; + csharp_keywords["new"] = 1; + csharp_keywords["null"] = 1; + csharp_keywords["object"] = 1; + csharp_keywords["operator"] = 1; + csharp_keywords["out"] = 1; + csharp_keywords["override"] = 1; + csharp_keywords["params"] = 1; + csharp_keywords["private"] = 1; + csharp_keywords["protected"] = 1; + csharp_keywords["public"] = 1; + csharp_keywords["readonly"] = 1; + csharp_keywords["ref"] = 1; + csharp_keywords["return"] = 1; + csharp_keywords["sbyte"] = 1; + csharp_keywords["sealed"] = 1; + csharp_keywords["short"] = 1; + csharp_keywords["sizeof"] = 1; + csharp_keywords["stackalloc"] = 1; + csharp_keywords["static"] = 1; + csharp_keywords["string"] = 1; + csharp_keywords["struct"] = 1; + csharp_keywords["switch"] = 1; + csharp_keywords["this"] = 1; + csharp_keywords["throw"] = 1; + csharp_keywords["true"] = 1; + csharp_keywords["try"] = 1; + csharp_keywords["typeof"] = 1; + csharp_keywords["uint"] = 1; + csharp_keywords["ulong"] = 1; + csharp_keywords["unchecked"] = 1; + csharp_keywords["unsafe"] = 1; + csharp_keywords["ushort"] = 1; + csharp_keywords["using"] = 1; + csharp_keywords["virtual"] = 1; + csharp_keywords["void"] = 1; + csharp_keywords["volatile"] = 1; + csharp_keywords["while"] = 1; + + // C# contextual keywords + csharp_keywords["add"] = 1; + csharp_keywords["alias"] = 1; + csharp_keywords["ascending"] = 1; + csharp_keywords["async"] = 1; + csharp_keywords["await"] = 1; + csharp_keywords["descending"] = 1; + csharp_keywords["dynamic"] = 1; + csharp_keywords["from"] = 1; + csharp_keywords["get"] = 1; + csharp_keywords["global"] = 1; + csharp_keywords["group"] = 1; + csharp_keywords["into"] = 1; + csharp_keywords["join"] = 1; + csharp_keywords["let"] = 1; + csharp_keywords["orderby"] = 1; + csharp_keywords["partial"] = 1; + csharp_keywords["remove"] = 1; + csharp_keywords["select"] = 1; + csharp_keywords["set"] = 1; + csharp_keywords["value"] = 1; + csharp_keywords["var"] = 1; + csharp_keywords["where"] = 1; + csharp_keywords["yield"] = 1; +} + +void t_csharp_generator::start_csharp_namespace(ostream& out) { + if (!namespace_name_.empty()) { + out << "namespace " << namespace_name_ << "\n"; + scope_up(out); + } +} + +void t_csharp_generator::end_csharp_namespace(ostream& out) { + if (!namespace_name_.empty()) { + scope_down(out); + } +} + +string t_csharp_generator::csharp_type_usings() { + return string() + "using System;\n" + "using System.Collections;\n" + + "using System.Collections.Generic;\n" + "using System.Text;\n" + "using System.IO;\n" + + ((async_) ? "using System.Threading.Tasks;\n" : "") + "using Thrift;\n" + + "using Thrift.Collections;\n" + ((serialize_ || wcf_) ? "#if !SILVERLIGHT\n" : "") + + ((serialize_ || wcf_) ? "using System.Xml.Serialization;\n" : "") + + ((serialize_ || wcf_) ? "#endif\n" : "") + + "using System.Runtime.Serialization;\n"; +} + +string t_csharp_generator::csharp_thrift_usings() { + return string() + "using Thrift.Protocol;\n" + "using Thrift.Transport;\n"; +} + +void t_csharp_generator::close_generator() { +} +void t_csharp_generator::generate_typedef(t_typedef* ttypedef) { + (void)ttypedef; +} + +void t_csharp_generator::generate_enum(t_enum* tenum) { + string f_enum_name = namespace_dir_ + "/" + (tenum->get_name()) + ".cs"; + ofstream_with_content_based_conditional_update f_enum; + f_enum.open(f_enum_name.c_str()); + + f_enum << autogen_comment() << endl; + + start_csharp_namespace(f_enum); + + generate_csharp_doc(f_enum, tenum); + + indent(f_enum) << "public enum " << tenum->get_name() << "\n"; + scope_up(f_enum); + + vector constants = tenum->get_constants(); + vector::iterator c_iter; + for (c_iter = constants.begin(); c_iter != constants.end(); ++c_iter) { + generate_csharp_doc(f_enum, *c_iter); + + int value = (*c_iter)->get_value(); + indent(f_enum) << (*c_iter)->get_name() << " = " << value << "," << endl; + } + + scope_down(f_enum); + + end_csharp_namespace(f_enum); + + f_enum.close(); +} + +void t_csharp_generator::generate_consts(std::vector consts) { + if (consts.empty()) { + return; + } + string f_consts_name = namespace_dir_ + '/' + program_name_ + ".Constants.cs"; + ofstream_with_content_based_conditional_update f_consts; + f_consts.open(f_consts_name.c_str()); + + f_consts << autogen_comment() << csharp_type_usings() << endl; + + start_csharp_namespace(f_consts); + + indent(f_consts) << "public static class " << make_valid_csharp_identifier(program_name_) + << "Constants" << endl; + scope_up(f_consts); + + vector::iterator c_iter; + bool need_static_constructor = false; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + generate_csharp_doc(f_consts, (*c_iter)); + if (print_const_value(f_consts, + (*c_iter)->get_name(), + (*c_iter)->get_type(), + (*c_iter)->get_value(), + false)) { + need_static_constructor = true; + } + } + + if (need_static_constructor) { + print_const_constructor(f_consts, consts); + } + + scope_down(f_consts); + end_csharp_namespace(f_consts); + f_consts.close(); +} + +void t_csharp_generator::print_const_def_value(std::ostream& out, + string name, + t_type* type, + t_const_value* value) { + if (type->is_struct() || type->is_xception()) { + const vector& fields = ((t_struct*)type)->get_members(); + vector::const_iterator f_iter; + const map& val = value->get_map(); + map::const_iterator v_iter; + prepare_member_name_mapping((t_struct*)type); + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + t_field* field = NULL; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if ((*f_iter)->get_name() == v_iter->first->get_string()) { + field = (*f_iter); + } + } + if (field == NULL) { + throw "type error: " + type->get_name() + " has no field " + v_iter->first->get_string(); + } + t_type* field_type = field->get_type(); + string val = render_const_value(out, name, field_type, v_iter->second); + indent(out) << name << "." << prop_name(field) << " = " << val << ";" << endl; + } + cleanup_member_name_mapping((t_struct*)type); + } else if (type->is_map()) { + t_type* ktype = ((t_map*)type)->get_key_type(); + t_type* vtype = ((t_map*)type)->get_val_type(); + const map& val = value->get_map(); + map::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string key = render_const_value(out, name, ktype, v_iter->first); + string val = render_const_value(out, name, vtype, v_iter->second); + indent(out) << name << "[" << key << "]" + << " = " << val << ";" << endl; + } + } else if (type->is_list() || type->is_set()) { + t_type* etype; + if (type->is_list()) { + etype = ((t_list*)type)->get_elem_type(); + } else { + etype = ((t_set*)type)->get_elem_type(); + } + + const vector& val = value->get_list(); + vector::const_iterator v_iter; + for (v_iter = val.begin(); v_iter != val.end(); ++v_iter) { + string val = render_const_value(out, name, etype, *v_iter); + indent(out) << name << ".Add(" << val << ");" << endl; + } + } +} + +void t_csharp_generator::print_const_constructor(std::ostream& out, std::vector consts) { + indent(out) << "static " << make_valid_csharp_identifier(program_name_).c_str() << "Constants()" + << endl; + scope_up(out); + vector::iterator c_iter; + for (c_iter = consts.begin(); c_iter != consts.end(); ++c_iter) { + string name = (*c_iter)->get_name(); + t_type* type = (*c_iter)->get_type(); + t_const_value* value = (*c_iter)->get_value(); + + print_const_def_value(out, name, type, value); + } + scope_down(out); +} + +// it seems like all that methods that call this are using in_static to be the opposite of what it +// would imply +bool t_csharp_generator::print_const_value(std::ostream& out, + string name, + t_type* type, + t_const_value* value, + bool in_static, + bool defval, + bool needtype) { + indent(out); + bool need_static_construction = !in_static; + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + if (!defval || needtype) { + out << (in_static ? "" : type->is_base_type() ? "public const " : "public static ") + << type_name(type) << " "; + } + if (type->is_base_type()) { + string v2 = render_const_value(out, name, type, value); + out << name << " = " << v2 << ";" << endl; + need_static_construction = false; + } else if (type->is_enum()) { + out << name << " = " << type_name(type, false, true) << "." << value->get_identifier_name() + << ";" << endl; + need_static_construction = false; + } else if (type->is_struct() || type->is_xception()) { + out << name << " = new " << type_name(type) << "();" << endl; + } else if (type->is_map()) { + out << name << " = new " << type_name(type, true, true) << "();" << endl; + } else if (type->is_list() || type->is_set()) { + out << name << " = new " << type_name(type) << "();" << endl; + } + + if (defval && !type->is_base_type() && !type->is_enum()) { + print_const_def_value(out, name, type, value); + } + + return need_static_construction; +} + +std::string t_csharp_generator::render_const_value(ostream& out, + string name, + t_type* type, + t_const_value* value) { + (void)name; + std::ostringstream render; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_STRING: + render << '"' << get_escaped_string(value) << '"'; + break; + case t_base_type::TYPE_UUID: + render << "new System.Guid(\"" << get_escaped_string(value) << "\")"; + break; + case t_base_type::TYPE_BOOL: + render << ((value->get_integer() > 0) ? "true" : "false"); + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + render << value->get_integer(); + break; + case t_base_type::TYPE_DOUBLE: + if (value->get_type() == t_const_value::CV_INTEGER) { + render << value->get_integer(); + } else { + render << value->get_double(); + } + break; + default: + throw "compiler error: no const of base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + render << type->get_name() << "." << value->get_identifier_name(); + } else { + string t = tmp("tmp"); + print_const_value(out, t, type, value, true, true, true); + render << t; + } + + return render.str(); +} + +void t_csharp_generator::generate_struct(t_struct* tstruct) { + if (union_ && tstruct->is_union()) { + generate_csharp_union(tstruct); + } else { + generate_csharp_struct(tstruct, false); + } +} + +void t_csharp_generator::generate_xception(t_struct* txception) { + generate_csharp_struct(txception, true); +} + +void t_csharp_generator::generate_csharp_struct(t_struct* tstruct, bool is_exception) { + string f_struct_name = namespace_dir_ + "/" + (tstruct->get_name()) + ".cs"; + ofstream_with_content_based_conditional_update f_struct; + + f_struct.open(f_struct_name.c_str()); + + f_struct << autogen_comment() << csharp_type_usings() << csharp_thrift_usings() << endl; + + generate_csharp_struct_definition(f_struct, tstruct, is_exception); + + f_struct.close(); +} + +void t_csharp_generator::generate_csharp_struct_definition(ostream& out, + t_struct* tstruct, + bool is_exception, + bool in_class, + bool is_result) { + + if (!in_class) { + start_csharp_namespace(out); + } + + out << endl; + + generate_csharp_doc(out, tstruct); + prepare_member_name_mapping(tstruct); + + indent(out) << "#if !SILVERLIGHT" << endl; + indent(out) << "[Serializable]" << endl; + indent(out) << "#endif" << endl; + if ((serialize_ || wcf_) && !is_exception) { + indent(out) << "[DataContract(Namespace=\"" << wcf_namespace_ << "\")]" + << endl; // do not make exception classes directly WCF serializable, we provide a + // separate "fault" for that + } + bool is_final = (tstruct->annotations_.find("final") != tstruct->annotations_.end()); + + indent(out) << "public " << (is_final ? "sealed " : "") << "partial class " + << normalize_name(tstruct->get_name()) << " : "; + + if (is_exception) { + out << "TException, "; + } + out << "TBase"; + + out << endl; + + scope_up(out); + + const vector& members = tstruct->get_members(); + vector::const_iterator m_iter; + + // make private members with public Properties + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + // if the field is requied, then we use auto-properties + if (!field_is_required((*m_iter)) && (!nullable_ || field_has_default((*m_iter)))) { + indent(out) << "private " << declare_field(*m_iter, false, "_") << endl; + } + } + out << endl; + + bool has_non_required_fields = false; + bool has_non_required_default_value_fields = false; + bool has_required_fields = false; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + generate_csharp_doc(out, *m_iter); + generate_property(out, *m_iter, true, true); + bool is_required = field_is_required((*m_iter)); + bool has_default = field_has_default((*m_iter)); + if (is_required) { + has_required_fields = true; + } else { + if (has_default) { + has_non_required_default_value_fields = true; + } + has_non_required_fields = true; + } + } + + bool generate_isset = (nullable_ && has_non_required_default_value_fields) + || (!nullable_ && has_non_required_fields); + if (generate_isset) { + out << endl; + if (serialize_ || wcf_) { + out << indent() << "[XmlIgnore] // XmlSerializer" << endl << indent() + << "[DataMember(Order = 1)] // XmlObjectSerializer, DataContractJsonSerializer, etc." + << endl; + } + out << indent() << "public Isset __isset;" << endl << indent() << "#if !SILVERLIGHT" << endl + << indent() << "[Serializable]" << endl << indent() << "#endif" << endl; + if (serialize_ || wcf_) { + indent(out) << "[DataContract]" << endl; + } + indent(out) << "public struct Isset {" << endl; + indent_up(); + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + bool is_required = field_is_required((*m_iter)); + bool has_default = field_has_default((*m_iter)); + // if it is required, don't need Isset for that variable + // if it is not required, if it has a default value, we need to generate Isset + // if we are not nullable, then we generate Isset + if (!is_required && (!nullable_ || has_default)) { + if (serialize_ || wcf_) { + indent(out) << "[DataMember]" << endl; + } + indent(out) << "public bool " << normalize_name((*m_iter)->get_name()) << ";" << endl; + } + } + + indent_down(); + indent(out) << "}" << endl << endl; + + if (generate_isset && (serialize_ || wcf_)) { + indent(out) << "#region XmlSerializer support" << endl << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + bool is_required = field_is_required((*m_iter)); + bool has_default = field_has_default((*m_iter)); + // if it is required, don't need Isset for that variable + // if it is not required, if it has a default value, we need to generate Isset + // if we are not nullable, then we generate Isset + if (!is_required && (!nullable_ || has_default)) { + indent(out) << "public bool ShouldSerialize" << prop_name((*m_iter)) << "()" << endl; + indent(out) << "{" << endl; + indent_up(); + indent(out) << "return __isset." << normalize_name((*m_iter)->get_name()) << ";" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + } + } + + indent(out) << "#endregion XmlSerializer support" << endl << endl; + } + } + + // We always want a default, no argument constructor for Reading + indent(out) << "public " << normalize_name(tstruct->get_name()) << "() {" << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + t_type* t = (*m_iter)->get_type(); + while (t->is_typedef()) { + t = ((t_typedef*)t)->get_type(); + } + if ((*m_iter)->get_value() != NULL) { + if (field_is_required((*m_iter))) { + print_const_value(out, "this." + prop_name(*m_iter), t, (*m_iter)->get_value(), true, true); + } else { + print_const_value(out, + "this._" + (*m_iter)->get_name(), + t, + (*m_iter)->get_value(), + true, + true); + // Optionals with defaults are marked set + indent(out) << "this.__isset." << normalize_name((*m_iter)->get_name()) << " = true;" + << endl; + } + } + } + indent_down(); + indent(out) << "}" << endl << endl; + + if (has_required_fields) { + indent(out) << "public " << tstruct->get_name() << "("; + bool first = true; + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (field_is_required((*m_iter))) { + if (first) { + first = false; + } else { + out << ", "; + } + out << type_name((*m_iter)->get_type()) << " " << normalize_name((*m_iter)->get_name()); + } + } + out << ") : this() {" << endl; + indent_up(); + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + if (field_is_required((*m_iter))) { + indent(out) << "this." << prop_name((*m_iter)) << " = " << normalize_name((*m_iter)->get_name()) << ";" + << endl; + } + } + + indent_down(); + indent(out) << "}" << endl << endl; + } + + generate_csharp_struct_reader(out, tstruct); + if (is_result) { + generate_csharp_struct_result_writer(out, tstruct); + } else { + generate_csharp_struct_writer(out, tstruct); + } + if (hashcode_) { + generate_csharp_struct_equals(out, tstruct); + generate_csharp_struct_hashcode(out, tstruct); + } + generate_csharp_struct_tostring(out, tstruct); + scope_down(out); + out << endl; + + // generate a corresponding WCF fault to wrap the exception + if ((serialize_ || wcf_) && is_exception) { + generate_csharp_wcffault(out, tstruct); + } + + cleanup_member_name_mapping(tstruct); + if (!in_class) { + end_csharp_namespace(out); + } +} + +void t_csharp_generator::generate_csharp_wcffault(ostream& out, t_struct* tstruct) { + out << endl; + indent(out) << "#if !SILVERLIGHT" << endl; + indent(out) << "[Serializable]" << endl; + indent(out) << "#endif" << endl; + indent(out) << "[DataContract]" << endl; + bool is_final = (tstruct->annotations_.find("final") != tstruct->annotations_.end()); + + indent(out) << "public " << (is_final ? "sealed " : "") << "partial class " << tstruct->get_name() + << "Fault" << endl; + + scope_up(out); + + const vector& members = tstruct->get_members(); + vector::const_iterator m_iter; + + // make private members with public Properties + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + // if the field is requied, then we use auto-properties + if (!field_is_required((*m_iter)) && (!nullable_ || field_has_default((*m_iter)))) { + indent(out) << "private " << declare_field(*m_iter, false, "_") << endl; + } + } + out << endl; + + for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { + generate_property(out, *m_iter, true, false); + } + + scope_down(out); + out << endl; +} + +void t_csharp_generator::generate_csharp_struct_reader(ostream& out, t_struct* tstruct) { + indent(out) << "public void Read (TProtocol iprot)" << endl; + scope_up(out); + + out << indent() << "iprot.IncrementRecursionDepth();" << endl; + out << indent() << "try" << endl; + scope_up(out); + + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + + // Required variables aren't in __isset, so we need tmp vars to check them + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (field_is_required((*f_iter))) { + indent(out) << "bool isset_" << (*f_iter)->get_name() << " = false;" << endl; + } + } + + indent(out) << "TField field;" << endl << indent() << "iprot.ReadStructBegin();" << endl; + + indent(out) << "while (true)" << endl; + scope_up(out); + + indent(out) << "field = iprot.ReadFieldBegin();" << endl; + + indent(out) << "if (field.Type == TType.Stop) { " << endl; + indent_up(); + indent(out) << "break;" << endl; + indent_down(); + indent(out) << "}" << endl; + + indent(out) << "switch (field.ID)" << endl; + + scope_up(out); + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool is_required = field_is_required((*f_iter)); + indent(out) << "case " << (*f_iter)->get_key() << ":" << endl; + indent_up(); + indent(out) << "if (field.Type == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl; + indent_up(); + + generate_deserialize_field(out, *f_iter); + if (is_required) { + indent(out) << "isset_" << (*f_iter)->get_name() << " = true;" << endl; + } + + indent_down(); + out << indent() << "} else { " << endl << indent() << " TProtocolUtil.Skip(iprot, field.Type);" + << endl << indent() << "}" << endl << indent() << "break;" << endl; + indent_down(); + } + + indent(out) << "default: " << endl; + indent_up(); + indent(out) << "TProtocolUtil.Skip(iprot, field.Type);" << endl; + indent(out) << "break;" << endl; + indent_down(); + + scope_down(out); + + indent(out) << "iprot.ReadFieldEnd();" << endl; + + scope_down(out); + + indent(out) << "iprot.ReadStructEnd();" << endl; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (field_is_required((*f_iter))) { + indent(out) << "if (!isset_" << (*f_iter)->get_name() << ")" << endl; + indent_up(); + out << indent() + << "throw new TProtocolException(TProtocolException.INVALID_DATA, " + << "\"required field " << prop_name((*f_iter)) << " not set\");" + << endl; + indent_down(); + } + } + + scope_down(out); + out << indent() << "finally" << endl; + scope_up(out); + out << indent() << "iprot.DecrementRecursionDepth();" << endl; + scope_down(out); + + indent_down(); + + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_csharp_struct_writer(ostream& out, t_struct* tstruct) { + out << indent() << "public void Write(TProtocol oprot) {" << endl; + indent_up(); + + out << indent() << "oprot.IncrementRecursionDepth();" << endl; + out << indent() << "try" << endl; + scope_up(out); + + string name = tstruct->get_name(); + const vector& fields = tstruct->get_sorted_members(); + vector::const_iterator f_iter; + + indent(out) << "TStruct struc = new TStruct(\"" << name << "\");" << endl; + indent(out) << "oprot.WriteStructBegin(struc);" << endl; + + if (fields.size() > 0) { + indent(out) << "TField field = new TField();" << endl; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool is_required = field_is_required((*f_iter)); + bool has_default = field_has_default((*f_iter)); + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + + if (is_required) + { + if (null_allowed) { + indent(out) << "if (" << prop_name((*f_iter)) << " == null)" << endl; + indent_up(); + out << indent() + << "throw new TProtocolException(TProtocolException.INVALID_DATA, " + << "\"required field " << prop_name((*f_iter)) << " not set\");" + << endl; + indent_down(); + } + } + else + { + if (nullable_ && !has_default) { + indent(out) << "if (" << prop_name((*f_iter)) << " != null) {" << endl; + } + else if (null_allowed) { + out << indent() + << "if (" << prop_name((*f_iter)) << " != null && __isset." + << normalize_name((*f_iter)->get_name()) << ") {" + << endl; + } + else { + indent(out) << "if (__isset." << normalize_name((*f_iter)->get_name()) << ") {" << endl; + } + indent_up(); + } + indent(out) << "field.Name = \"" << (*f_iter)->get_name() << "\";" << endl; + indent(out) << "field.Type = " << type_to_enum((*f_iter)->get_type()) << ";" << endl; + indent(out) << "field.ID = " << (*f_iter)->get_key() << ";" << endl; + indent(out) << "oprot.WriteFieldBegin(field);" << endl; + + generate_serialize_field(out, *f_iter); + + indent(out) << "oprot.WriteFieldEnd();" << endl; + if (!is_required) { + indent_down(); + indent(out) << "}" << endl; + } + } + } + + indent(out) << "oprot.WriteFieldStop();" << endl; + indent(out) << "oprot.WriteStructEnd();" << endl; + + scope_down(out); + out << indent() << "finally" << endl; + scope_up(out); + out << indent() << "oprot.DecrementRecursionDepth();" << endl; + scope_down(out); + + indent_down(); + + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_csharp_struct_result_writer(ostream& out, t_struct* tstruct) { + indent(out) << "public void Write(TProtocol oprot) {" << endl; + indent_up(); + + out << indent() << "oprot.IncrementRecursionDepth();" << endl; + out << indent() << "try" << endl; + scope_up(out); + + string name = tstruct->get_name(); + const vector& fields = tstruct->get_sorted_members(); + vector::const_iterator f_iter; + + indent(out) << "TStruct struc = new TStruct(\"" << name << "\");" << endl; + indent(out) << "oprot.WriteStructBegin(struc);" << endl; + + if (fields.size() > 0) { + indent(out) << "TField field = new TField();" << endl; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + out << endl << indent() << "if "; + } else { + out << " else if "; + } + + if (nullable_) { + out << "(this." << prop_name((*f_iter)) << " != null) {" << endl; + } else { + out << "(this.__isset." << normalize_name((*f_iter)->get_name()) << ") {" << endl; + } + indent_up(); + + bool null_allowed = !nullable_ && type_can_be_null((*f_iter)->get_type()); + if (null_allowed) { + indent(out) << "if (" << prop_name(*f_iter) << " != null) {" << endl; + indent_up(); + } + + indent(out) << "field.Name = \"" << prop_name(*f_iter) << "\";" << endl; + indent(out) << "field.Type = " << type_to_enum((*f_iter)->get_type()) << ";" << endl; + indent(out) << "field.ID = " << (*f_iter)->get_key() << ";" << endl; + indent(out) << "oprot.WriteFieldBegin(field);" << endl; + + generate_serialize_field(out, *f_iter); + + indent(out) << "oprot.WriteFieldEnd();" << endl; + + if (null_allowed) { + indent_down(); + indent(out) << "}" << endl; + } + + indent_down(); + indent(out) << "}"; + } + } + + out << endl << indent() << "oprot.WriteFieldStop();" << endl << indent() + << "oprot.WriteStructEnd();" << endl; + + scope_down(out); + out << indent() << "finally" << endl; + scope_up(out); + out << indent() << "oprot.DecrementRecursionDepth();" << endl; + scope_down(out); + + indent_down(); + + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_csharp_struct_tostring(ostream& out, t_struct* tstruct) { + indent(out) << "public override string ToString() {" << endl; + indent_up(); + + indent(out) << "StringBuilder __sb = new StringBuilder(\"" << tstruct->get_name() << "(\");" + << endl; + + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + + bool useFirstFlag = false; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (!field_is_required((*f_iter))) { + indent(out) << "bool __first = true;" << endl; + useFirstFlag = true; + } + break; + } + + bool had_required = false; // set to true after first required field has been processed + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + bool is_required = field_is_required((*f_iter)); + bool has_default = field_has_default((*f_iter)); + if (nullable_ && !has_default && !is_required) { + indent(out) << "if (" << prop_name((*f_iter)) << " != null) {" << endl; + indent_up(); + } else if (!is_required) { + bool null_allowed = type_can_be_null((*f_iter)->get_type()); + if (null_allowed) { + indent(out) << "if (" << prop_name((*f_iter)) << " != null && __isset." + << normalize_name((*f_iter)->get_name()) << ") {" << endl; + indent_up(); + } else { + indent(out) << "if (__isset." << normalize_name((*f_iter)->get_name()) << ") {" << endl; + indent_up(); + } + } + + if (useFirstFlag && (!had_required)) { + indent(out) << "if(!__first) { __sb.Append(\", \"); }" << endl; + if (!is_required) { + indent(out) << "__first = false;" << endl; + } + indent(out) << "__sb.Append(\"" << prop_name((*f_iter)) << ": \");" << endl; + } else { + indent(out) << "__sb.Append(\", " << prop_name((*f_iter)) << ": \");" << endl; + } + + t_type* ttype = (*f_iter)->get_type(); + if (ttype->is_xception() || ttype->is_struct()) { + indent(out) << "__sb.Append(" << prop_name((*f_iter)) + << "== null ? \"\" : " << prop_name((*f_iter)) << ".ToString());" << endl; + } else { + indent(out) << "__sb.Append(" << prop_name((*f_iter)) << ");" << endl; + } + + if (!is_required) { + indent_down(); + indent(out) << "}" << endl; + } else { + had_required = true; // now __first must be false, so we don't need to check it anymore + } + } + + indent(out) << "__sb.Append(\")\");" << endl; + indent(out) << "return __sb.ToString();" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_csharp_union(t_struct* tunion) { + string f_union_name = namespace_dir_ + "/" + (tunion->get_name()) + ".cs"; + ofstream_with_content_based_conditional_update f_union; + + f_union.open(f_union_name.c_str()); + + f_union << autogen_comment() << csharp_type_usings() << csharp_thrift_usings() << endl; + + generate_csharp_union_definition(f_union, tunion); + + f_union.close(); +} + +void t_csharp_generator::generate_csharp_union_definition(std::ostream& out, t_struct* tunion) { + // Let's define the class first + start_csharp_namespace(out); + + indent(out) << "public abstract partial class " << tunion->get_name() << " : TAbstractBase {" + << endl; + + indent_up(); + + indent(out) << "public abstract void Write(TProtocol protocol);" << endl; + indent(out) << "public readonly int Isset;" << endl; + indent(out) << "public abstract object Data { get; }" << endl; + + indent(out) << "protected " << tunion->get_name() << "(int isset) {" << endl; + indent_up(); + indent(out) << "Isset = isset;" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + + indent(out) << "public class ___undefined : " << tunion->get_name() << " {" << endl; + indent_up(); + + indent(out) << "public override object Data { get { return null; } }" << endl; + + indent(out) << "public ___undefined() : base(0) {}" << endl << endl; + + indent(out) << "public override void Write(TProtocol protocol) {" << endl; + indent_up(); + indent(out) << "throw new TProtocolException( TProtocolException.INVALID_DATA, \"Cannot persist " + "an union type which is not set.\");" << endl; + indent_down(); + indent(out) << "}" << endl << endl; + + indent_down(); + indent(out) << "}" << endl << endl; + + const vector& fields = tunion->get_members(); + vector::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + generate_csharp_union_class(out, tunion, (*f_iter)); + } + + generate_csharp_union_reader(out, tunion); + + indent_down(); + indent(out) << "}" << endl << endl; + + end_csharp_namespace(out); +} + +void t_csharp_generator::generate_csharp_union_class(std::ostream& out, + t_struct* tunion, + t_field* tfield) { + indent(out) << "public " << type_name(tfield->get_type()) << " As_" << tfield->get_name() << endl; + indent(out) << "{" << endl; + indent_up(); + indent(out) << "get" << endl; + indent(out) << "{" << endl; + indent_up(); + indent(out) << "return (" << tfield->get_key() << " == Isset) ? (" << type_name(tfield->get_type()) << ")Data : default(" << type_name(tfield->get_type()) << ");" << endl; + indent_down(); + indent(out) << "}" << endl; + indent_down(); + indent(out) << "}" << endl + << endl; + + + indent(out) << "public class " << tfield->get_name() << " : " << tunion->get_name() << " {" + << endl; + indent_up(); + indent(out) << "private " << type_name(tfield->get_type()) << " _data;" << endl; + indent(out) << "public override object Data { get { return _data; } }" << endl; + indent(out) << "public " << tfield->get_name() << "(" << type_name(tfield->get_type()) + << " data) : base("<< tfield->get_key() <<") {" << endl; + indent_up(); + indent(out) << "this._data = data;" << endl; + indent_down(); + indent(out) << "}" << endl; + indent(out) << "public override void Write(TProtocol oprot) {" << endl; + indent_up(); + + out << indent() << "oprot.IncrementRecursionDepth();" << endl; + out << indent() << "try" << endl; + scope_up(out); + + indent(out) << "TStruct struc = new TStruct(\"" << tunion->get_name() << "\");" << endl; + indent(out) << "oprot.WriteStructBegin(struc);" << endl; + + indent(out) << "TField field = new TField();" << endl; + indent(out) << "field.Name = \"" << tfield->get_name() << "\";" << endl; + indent(out) << "field.Type = " << type_to_enum(tfield->get_type()) << ";" << endl; + indent(out) << "field.ID = " << tfield->get_key() << ";" << endl; + indent(out) << "oprot.WriteFieldBegin(field);" << endl; + + generate_serialize_field(out, tfield, "_data", true, true); + + indent(out) << "oprot.WriteFieldEnd();" << endl; + indent(out) << "oprot.WriteFieldStop();" << endl; + indent(out) << "oprot.WriteStructEnd();" << endl; + indent_down(); + + scope_down(out); + out << indent() << "finally" << endl; + scope_up(out); + out << indent() << "oprot.DecrementRecursionDepth();" << endl; + scope_down(out); + + indent(out) << "}" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_csharp_struct_equals(ostream& out, t_struct* tstruct) { + indent(out) << "public override bool Equals(object that) {" << endl; + indent_up(); + + indent(out) << "var other = that as " << type_name(tstruct) << ";" << endl; + indent(out) << "if (other == null) return false;" << endl; + indent(out) << "if (ReferenceEquals(this, other)) return true;" << endl; + + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + + bool first = true; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + indent(out) << "return "; + indent_up(); + } else { + out << endl; + indent(out) << "&& "; + } + if (!field_is_required((*f_iter)) && !(nullable_ && !field_has_default((*f_iter)))) { + out << "((__isset." << normalize_name((*f_iter)->get_name()) << " == other.__isset." + << normalize_name((*f_iter)->get_name()) << ") && ((!__isset." + << normalize_name((*f_iter)->get_name()) << ") || ("; + } + t_type* ttype = (*f_iter)->get_type(); + if (ttype->is_container() || ttype->is_binary()) { + out << "TCollections.Equals("; + } else { + out << "System.Object.Equals("; + } + out << prop_name((*f_iter)) << ", other." << prop_name((*f_iter)) << ")"; + if (!field_is_required((*f_iter)) && !(nullable_ && !field_has_default((*f_iter)))) { + out << ")))"; + } + } + if (first) { + indent(out) << "return true;" << endl; + } else { + out << ";" << endl; + indent_down(); + } + + indent_down(); + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_csharp_struct_hashcode(ostream& out, t_struct* tstruct) { + indent(out) << "public override int GetHashCode() {" << endl; + indent_up(); + + indent(out) << "int hashcode = 0;" << endl; + indent(out) << "unchecked {" << endl; + indent_up(); + + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + t_type* ttype = (*f_iter)->get_type(); + indent(out) << "hashcode = (hashcode * 397) ^ "; + if (type_can_be_null((*f_iter)->get_type())) { + out << "(" << prop_name((*f_iter)) << " == null ? 0 : "; + } else if (field_is_required((*f_iter))) { + out << "("; + } else if (nullable_) { + out << "(" << prop_name((*f_iter)) << " == null ? 0 : "; + } else { + out << "(!__isset." << normalize_name((*f_iter)->get_name()) << " ? 0 : "; + } + if (ttype->is_container()) { + out << "(TCollections.GetHashCode(" << prop_name((*f_iter)) << "))"; + } else { + out << "(" << prop_name((*f_iter)) << ".GetHashCode())"; + } + out << ");" << endl; + } + + indent_down(); + indent(out) << "}" << endl; + indent(out) << "return hashcode;" << endl; + + indent_down(); + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_service(t_service* tservice) { + string f_service_name = namespace_dir_ + "/" + service_name_ + ".cs"; + f_service_.open(f_service_name.c_str()); + + f_service_ << autogen_comment() << csharp_type_usings() << csharp_thrift_usings() << endl; + + start_csharp_namespace(f_service_); + + indent(f_service_) << "public partial class " << normalize_name(service_name_) << " {" << endl; + indent_up(); + + generate_service_interface(tservice); + generate_service_client(tservice); + generate_service_server(tservice); + generate_service_helpers(tservice); + + indent_down(); + + indent(f_service_) << "}" << endl; + end_csharp_namespace(f_service_); + f_service_.close(); +} + +void t_csharp_generator::generate_service_interface(t_service* tservice) { + generate_separate_service_interfaces(tservice); +} + +void t_csharp_generator::generate_separate_service_interfaces(t_service* tservice) { + generate_sync_service_interface(tservice); + + if (async_) { + generate_async_service_interface(tservice); + } + + generate_combined_service_interface(tservice); +} + +void t_csharp_generator::generate_sync_service_interface(t_service* tservice) { + string extends = ""; + string extends_iface = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_iface = " : " + extends + ".ISync"; + } + + generate_csharp_doc(f_service_, tservice); + + if (wcf_) { + indent(f_service_) << "[System.ServiceModel.ServiceContract(Namespace=\"" << wcf_namespace_ << "\")]" << endl; + } + indent(f_service_) << "public interface ISync" << extends_iface << " {" << endl; + + indent_up(); + vector functions = tservice->get_functions(); + vector::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_csharp_doc(f_service_, *f_iter); + + // if we're using WCF, add the corresponding attributes + if (wcf_) { + indent(f_service_) << "[System.ServiceModel.OperationContract]" << endl; + + const std::vector& xceptions = (*f_iter)->get_xceptions()->get_members(); + vector::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + indent(f_service_) << "[System.ServiceModel.FaultContract(typeof(" + + type_name((*x_iter)->get_type(), false, false) + "Fault))]" << endl; + } + } + + indent(f_service_) << function_signature(*f_iter) << ";" << endl; + } + indent_down(); + f_service_ << indent() << "}" << endl << endl; +} + +void t_csharp_generator::generate_async_service_interface(t_service* tservice) { + string extends = ""; + string extends_iface = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_iface = " : " + extends + ".IAsync"; + } + + generate_csharp_doc(f_service_, tservice); + + if (wcf_) { + indent(f_service_) << "[System.ServiceModel.ServiceContract(Namespace=\"" << wcf_namespace_ << "\")]" << endl; + } + indent(f_service_) << "public interface IAsync" << extends_iface << " {" << endl; + + indent_up(); + vector functions = tservice->get_functions(); + vector::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_csharp_doc(f_service_, *f_iter); + + // if we're using WCF, add the corresponding attributes + if (wcf_) { + indent(f_service_) << "[System.ServiceModel.OperationContract]" << endl; + + const std::vector& xceptions = (*f_iter)->get_xceptions()->get_members(); + vector::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + indent(f_service_) << "[System.ServiceModel.FaultContract(typeof(" + + type_name((*x_iter)->get_type(), false, false) + "Fault))]" << endl; + } + } + + indent(f_service_) << function_signature_async(*f_iter) << ";" << endl; + } + indent_down(); + f_service_ << indent() << "}" << endl << endl; +} + +void t_csharp_generator::generate_combined_service_interface(t_service* tservice) { + string extends_iface = " : ISync"; + + if (async_) { + extends_iface += ", IAsync"; + } + + generate_csharp_doc(f_service_, tservice); + + if (wcf_) { + indent(f_service_) << "[System.ServiceModel.ServiceContract(Namespace=\"" << wcf_namespace_ << "\")]" << endl; + } + + indent(f_service_) << "public interface Iface" << extends_iface << " {" << endl; + + indent_up(); + + // We need to generate extra old style async methods for silverlight. Since + // this isn't something you'd want to implement server-side, just put them into + // the main Iface interface. + generate_silverlight_async_methods(tservice); + + indent_down(); + + f_service_ << indent() << "}" << endl << endl; +} + +void t_csharp_generator::generate_silverlight_async_methods(t_service* tservice) { + vector functions = tservice->get_functions(); + vector::iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_csharp_doc(f_service_, *f_iter); + + // For backwards compatibility, include the Begin_, End_ methods if we're generating + // with the async flag. I'm not sure this is necessary, so someone with more knowledge + // can maybe remove these checks if they know it's safe. + if (!async_) { + indent(f_service_) << "#if SILVERLIGHT" << endl; + } + + indent(f_service_) << function_signature_async_begin(*f_iter, "Begin_") << ";" << endl; + indent(f_service_) << function_signature_async_end(*f_iter, "End_") << ";" << endl; + + if (!async_) { + indent(f_service_) << "#endif" << endl; + } + } +} + +void t_csharp_generator::generate_service_helpers(t_service* tservice) { + vector functions = tservice->get_functions(); + vector::iterator f_iter; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + t_struct* ts = (*f_iter)->get_arglist(); + generate_csharp_struct_definition(f_service_, ts, false, true); + generate_function_helpers(*f_iter); + } +} + +void t_csharp_generator::generate_service_client(t_service* tservice) { + string extends = ""; + string extends_client = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_client = extends + ".Client, "; + } else { + extends_client = "IDisposable, "; + } + + generate_csharp_doc(f_service_, tservice); + + indent(f_service_) << "public class Client : " << extends_client << "Iface {" << endl; + indent_up(); + indent(f_service_) << "public Client(TProtocol prot) : this(prot, prot)" << endl; + scope_up(f_service_); + scope_down(f_service_); + f_service_ << endl; + + indent(f_service_) << "public Client(TProtocol iprot, TProtocol oprot)"; + if (!extends.empty()) { + f_service_ << " : base(iprot, oprot)"; + } + f_service_ << endl; + + scope_up(f_service_); + if (extends.empty()) { + f_service_ << indent() << "iprot_ = iprot;" << endl << indent() << "oprot_ = oprot;" << endl; + } + scope_down(f_service_); + + f_service_ << endl; + + if (extends.empty()) { + f_service_ << indent() << "protected TProtocol iprot_;" << endl << indent() + << "protected TProtocol oprot_;" << endl << indent() << "protected int seqid_;" + << endl << endl; + + f_service_ << indent() << "public TProtocol InputProtocol" << endl; + scope_up(f_service_); + indent(f_service_) << "get { return iprot_; }" << endl; + scope_down(f_service_); + + f_service_ << indent() << "public TProtocol OutputProtocol" << endl; + scope_up(f_service_); + indent(f_service_) << "get { return oprot_; }" << endl; + scope_down(f_service_); + f_service_ << endl << endl; + + indent(f_service_) << "#region \" IDisposable Support \"" << endl; + indent(f_service_) << "private bool _IsDisposed;" << endl << endl; + indent(f_service_) << "// IDisposable" << endl; + indent(f_service_) << "public void Dispose()" << endl; + scope_up(f_service_); + indent(f_service_) << "Dispose(true);" << endl; + scope_down(f_service_); + indent(f_service_) << endl << endl; + indent(f_service_) << "protected virtual void Dispose(bool disposing)" << endl; + scope_up(f_service_); + indent(f_service_) << "if (!_IsDisposed)" << endl; + scope_up(f_service_); + indent(f_service_) << "if (disposing)" << endl; + scope_up(f_service_); + indent(f_service_) << "if (iprot_ != null)" << endl; + scope_up(f_service_); + indent(f_service_) << "((IDisposable)iprot_).Dispose();" << endl; + scope_down(f_service_); + indent(f_service_) << "if (oprot_ != null)" << endl; + scope_up(f_service_); + indent(f_service_) << "((IDisposable)oprot_).Dispose();" << endl; + scope_down(f_service_); + scope_down(f_service_); + scope_down(f_service_); + indent(f_service_) << "_IsDisposed = true;" << endl; + scope_down(f_service_); + indent(f_service_) << "#endregion" << endl; + f_service_ << endl << endl; + } + + vector functions = tservice->get_functions(); + vector::const_iterator f_iter; + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + string funname = (*f_iter)->get_name(); + + indent(f_service_) << endl; + + if (!async_) { + indent(f_service_) << "#if SILVERLIGHT" << endl; + indent(f_service_) << endl; + } + // Begin_ + indent(f_service_) << "public " << function_signature_async_begin(*f_iter, "Begin_") << endl; + scope_up(f_service_); + indent(f_service_) << "return " + << "send_" << funname << "(callback, state"; + + t_struct* arg_struct = (*f_iter)->get_arglist(); + prepare_member_name_mapping(arg_struct); + + const vector& fields = arg_struct->get_members(); + vector::const_iterator fld_iter; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << ", "; + f_service_ << normalize_name((*fld_iter)->get_name()); + } + f_service_ << ");" << endl; + scope_down(f_service_); + f_service_ << endl; + + // End + indent(f_service_) << "public " << function_signature_async_end(*f_iter, "End_") << endl; + scope_up(f_service_); + indent(f_service_) << "oprot_.Transport.EndFlush(asyncResult);" << endl; + if (!(*f_iter)->is_oneway()) { + f_service_ << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << "return "; + } + f_service_ << "recv_" << funname << "();" << endl; + } + scope_down(f_service_); + f_service_ << endl; + + // async + bool first; + if (async_) { + indent(f_service_) << "public async " << function_signature_async(*f_iter, "") << endl; + scope_up(f_service_); + + if (!(*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << type_name((*f_iter)->get_returntype()) << " retval;" << endl; + indent(f_service_) << "retval = "; + } else { + indent(f_service_); + } + f_service_ << "await Task.Run(() =>" << endl; + scope_up(f_service_); + indent(f_service_); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << "return "; + } + f_service_ << funname << "("; + first = true; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << (*fld_iter)->get_name(); + } + f_service_ << ");" << endl; + indent_down(); + indent(f_service_) << "});" << endl; + if (!(*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "return retval;" << endl; + } + scope_down(f_service_); + f_service_ << endl; + } + + if (!async_) { + indent(f_service_) << "#endif" << endl << endl; + } + + generate_csharp_doc(f_service_, *f_iter); + indent(f_service_) << "public " << function_signature(*f_iter) << endl; + scope_up(f_service_); + + // silverlight invoke + if (!async_) { + indent(f_service_) << "#if SILVERLIGHT" << endl; + + indent(f_service_) << "var asyncResult = Begin_" << funname << "(null, null"; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << ", " << normalize_name((*fld_iter)->get_name()); + } + f_service_ << ");" << endl; + + if (!(*f_iter)->is_oneway()) { + f_service_ << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << "return "; + } + f_service_ << "End_" << funname << "(asyncResult);" << endl; + } + f_service_ << endl; + + indent(f_service_) << "#else" << endl; + } + + // synchronous invoke + indent(f_service_) << "send_" << funname << "("; + + first = true; + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << normalize_name((*fld_iter)->get_name()); + } + f_service_ << ");" << endl; + + if (!(*f_iter)->is_oneway()) { + f_service_ << indent(); + if (!(*f_iter)->get_returntype()->is_void()) { + f_service_ << "return "; + } + f_service_ << "recv_" << funname << "();" << endl; + } + f_service_ << endl; + + if (!async_) { + indent(f_service_) << "#endif" << endl; + } + scope_down(f_service_); + + // Send + t_function send_function(g_type_void, + string("send_") + (*f_iter)->get_name(), + (*f_iter)->get_arglist()); + + string argsname = (*f_iter)->get_name() + "_args"; + + if (!async_) { + indent(f_service_) << "#if SILVERLIGHT" << endl; + } + + indent(f_service_) << "public " << function_signature_async_begin(&send_function) << endl; + scope_up(f_service_); + + f_service_ << indent() << "oprot_.WriteMessageBegin(new TMessage(\"" << funname << "\", " + << ((*f_iter)->is_oneway() ? "TMessageType.Oneway" : "TMessageType.Call") + << ", seqid_));" << endl << indent() << argsname << " args = new " << argsname + << "();" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << indent() << "args." << prop_name(*fld_iter) << " = " + << normalize_name((*fld_iter)->get_name()) << ";" << endl; + } + + f_service_ << indent() << "args.Write(oprot_);" << endl << indent() + << "oprot_.WriteMessageEnd();" << endl; + indent(f_service_) << "return oprot_.Transport.BeginFlush(callback, state);" << endl; + + scope_down(f_service_); + f_service_ << endl; + + if (!async_) { + indent(f_service_) << "#else" << endl; + f_service_ << endl; + } + + indent(f_service_) << "public " << function_signature(&send_function) << endl; + scope_up(f_service_); + + f_service_ << indent() << "oprot_.WriteMessageBegin(new TMessage(\"" << funname << "\", " + << ((*f_iter)->is_oneway() ? "TMessageType.Oneway" : "TMessageType.Call") + << ", seqid_));" << endl << indent() << argsname << " args = new " << argsname + << "();" << endl; + + for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) { + f_service_ << indent() << "args." << prop_name(*fld_iter) << " = " + << normalize_name((*fld_iter)->get_name()) << ";" << endl; + } + + f_service_ << indent() << "args.Write(oprot_);" << endl << indent() + << "oprot_.WriteMessageEnd();" << endl; + + indent(f_service_) << "oprot_.Transport.Flush();" << endl; + cleanup_member_name_mapping(arg_struct); + scope_down(f_service_); + + if (!async_) { + indent(f_service_) << "#endif" << endl; + } + + f_service_ << endl; + + if (!(*f_iter)->is_oneway()) { + string resultname = (*f_iter)->get_name() + "_result"; + + t_struct noargs(program_); + t_function recv_function((*f_iter)->get_returntype(), + string("recv_") + (*f_iter)->get_name(), + &noargs, + (*f_iter)->get_xceptions()); + indent(f_service_) << "public " << function_signature(&recv_function) << endl; + scope_up(f_service_); + + t_struct* xs = (*f_iter)->get_xceptions(); + prepare_member_name_mapping(xs, xs->get_members(), resultname); + + f_service_ << indent() << "TMessage msg = iprot_.ReadMessageBegin();" << endl << indent() + << "if (msg.Type == TMessageType.Exception) {" << endl; + indent_up(); + f_service_ << indent() << "TApplicationException x = TApplicationException.Read(iprot_);" + << endl << indent() << "iprot_.ReadMessageEnd();" << endl << indent() << "throw x;" + << endl; + indent_down(); + f_service_ << indent() << "}" << endl << indent() << resultname << " result = new " + << resultname << "();" << endl << indent() << "result.Read(iprot_);" << endl + << indent() << "iprot_.ReadMessageEnd();" << endl; + + if (!(*f_iter)->get_returntype()->is_void()) { + if (nullable_) { + if (type_can_be_null((*f_iter)->get_returntype())) { + f_service_ << indent() << "if (result.Success != null) {" << endl << indent() + << " return result.Success;" << endl << indent() << "}" << endl; + } else { + f_service_ << indent() << "if (result.Success.HasValue) {" << endl << indent() + << " return result.Success.Value;" << endl << indent() << "}" << endl; + } + } else { + f_service_ << indent() << "if (result.__isset.success) {" << endl << indent() + << " return result.Success;" << endl << indent() << "}" << endl; + } + } + + const std::vector& xceptions = xs->get_members(); + vector::const_iterator x_iter; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + if (nullable_) { + f_service_ << indent() << "if (result." << prop_name(*x_iter) << " != null) {" << endl + << indent() << " throw result." << prop_name(*x_iter) << ";" << endl + << indent() << "}" << endl; + } else { + f_service_ << indent() << "if (result.__isset." << normalize_name((*x_iter)->get_name()) + << ") {" << endl << indent() << " throw result." << prop_name(*x_iter) << ";" + << endl << indent() << "}" << endl; + } + } + + if ((*f_iter)->get_returntype()->is_void()) { + indent(f_service_) << "return;" << endl; + } else { + f_service_ << indent() + << "throw new " + "TApplicationException(TApplicationException.ExceptionType.MissingResult, \"" + << (*f_iter)->get_name() << " failed: unknown result\");" << endl; + } + + cleanup_member_name_mapping((*f_iter)->get_xceptions()); + scope_down(f_service_); + f_service_ << endl; + } + } + + indent_down(); + indent(f_service_) << "}" << endl; +} + +void t_csharp_generator::generate_service_server(t_service* tservice) { + if (async_) { + generate_service_server_async(tservice); + generate_service_server_sync(tservice); + } + else { + generate_service_server_sync(tservice); + } +} + +void t_csharp_generator::generate_service_server_sync(t_service* tservice) { + vector functions = tservice->get_functions(); + vector::iterator f_iter; + + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_processor = extends + ".Processor, "; + } + + indent(f_service_) << "public class Processor : " << extends_processor << "TProcessor {" << endl; + indent_up(); + + indent(f_service_) << "public Processor(ISync iface)"; + + if (!extends.empty()) { + f_service_ << " : base(iface)"; + } + f_service_ << endl; + scope_up(f_service_); + f_service_ << indent() << "iface_ = iface;" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << indent() << "processMap_[\"" << (*f_iter)->get_name() + << "\"] = " << (*f_iter)->get_name() << "_Process;" << endl; + } + + scope_down(f_service_); + f_service_ << endl; + + if (extends.empty()) { + f_service_ + << indent() + << "protected delegate void ProcessFunction(int seqid, TProtocol iprot, TProtocol oprot);" + << endl; + } + + f_service_ << indent() << "private ISync iface_;" << endl; + + if (extends.empty()) { + f_service_ << indent() << "protected Dictionary processMap_ = new " + "Dictionary();" << endl; + } + + f_service_ << endl; + + if (extends.empty()) { + indent(f_service_) << "public bool Process(TProtocol iprot, TProtocol oprot)" << endl; + } + else { + indent(f_service_) << "public new bool Process(TProtocol iprot, TProtocol oprot)" << endl; + } + scope_up(f_service_); + + f_service_ << indent() << "try" << endl; + scope_up(f_service_); + + f_service_ << indent() << "TMessage msg = iprot.ReadMessageBegin();" << endl; + + f_service_ + << indent() << "ProcessFunction fn;" << endl << indent() + << "processMap_.TryGetValue(msg.Name, out fn);" << endl << indent() << "if (fn == null) {" + << endl << indent() << " TProtocolUtil.Skip(iprot, TType.Struct);" << endl << indent() + << " iprot.ReadMessageEnd();" << endl << indent() + << " TApplicationException x = new TApplicationException " + "(TApplicationException.ExceptionType.UnknownMethod, \"Invalid method name: '\" + " + "msg.Name + \"'\");" << endl << indent() + << " oprot.WriteMessageBegin(new TMessage(msg.Name, TMessageType.Exception, msg.SeqID));" + << endl << indent() << " x.Write(oprot);" << endl << indent() << " oprot.WriteMessageEnd();" + << endl << indent() << " oprot.Transport.Flush();" << endl << indent() << " return true;" + << endl << indent() << "}" << endl << indent() << "fn(msg.SeqID, iprot, oprot);" << endl; + + scope_down(f_service_); + + f_service_ << indent() << "catch (IOException)" << endl; + scope_up(f_service_); + f_service_ << indent() << "return false;" << endl; + scope_down(f_service_); + + f_service_ << indent() << "return true;" << endl; + + scope_down(f_service_); + f_service_ << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function(tservice, *f_iter); + } + + indent_down(); + indent(f_service_) << "}" << endl << endl; +} + +void t_csharp_generator::generate_service_server_async(t_service* tservice) { + vector functions = tservice->get_functions(); + vector::iterator f_iter; + + string extends = ""; + string extends_processor = ""; + if (tservice->get_extends() != NULL) { + extends = type_name(tservice->get_extends()); + extends_processor = extends + ".AsyncProcessor, "; + } + + indent(f_service_) << "public class AsyncProcessor : " << extends_processor << "TAsyncProcessor {" << endl; + indent_up(); + + indent(f_service_) << "public AsyncProcessor(IAsync iface)"; + if (!extends.empty()) { + f_service_ << " : base(iface)"; + } + f_service_ << endl; + scope_up(f_service_); + f_service_ << indent() << "iface_ = iface;" << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + f_service_ << indent() << "processMap_[\"" << (*f_iter)->get_name() + << "\"] = " << (*f_iter)->get_name() << "_ProcessAsync;" << endl; + } + + scope_down(f_service_); + f_service_ << endl; + + if (extends.empty()) { + f_service_ + << indent() + << "protected delegate Task ProcessFunction(int seqid, TProtocol iprot, TProtocol oprot);" + << endl; + } + + f_service_ << indent() << "private IAsync iface_;" << endl; + + if (extends.empty()) { + f_service_ << indent() << "protected Dictionary processMap_ = new " + "Dictionary();" << endl; + } + + f_service_ << endl; + + if (extends.empty()) { + indent(f_service_) << "public async Task ProcessAsync(TProtocol iprot, TProtocol oprot)" << endl; + } + else { + indent(f_service_) << "public new async Task ProcessAsync(TProtocol iprot, TProtocol oprot)" << endl; + } + scope_up(f_service_); + + f_service_ << indent() << "try" << endl; + scope_up(f_service_); + + f_service_ << indent() << "TMessage msg = iprot.ReadMessageBegin();" << endl; + + f_service_ + << indent() << "ProcessFunction fn;" << endl << indent() + << "processMap_.TryGetValue(msg.Name, out fn);" << endl << indent() << "if (fn == null) {" + << endl << indent() << " TProtocolUtil.Skip(iprot, TType.Struct);" << endl << indent() + << " iprot.ReadMessageEnd();" << endl << indent() + << " TApplicationException x = new TApplicationException " + "(TApplicationException.ExceptionType.UnknownMethod, \"Invalid method name: '\" + " + "msg.Name + \"'\");" << endl << indent() + << " oprot.WriteMessageBegin(new TMessage(msg.Name, TMessageType.Exception, msg.SeqID));" + << endl << indent() << " x.Write(oprot);" << endl << indent() << " oprot.WriteMessageEnd();" + << endl << indent() << " oprot.Transport.Flush();" << endl << indent() << " return true;" + << endl << indent() << "}" << endl << indent() << "await fn(msg.SeqID, iprot, oprot);" << endl; + + scope_down(f_service_); + + f_service_ << indent() << "catch (IOException)" << endl; + scope_up(f_service_); + f_service_ << indent() << "return false;" << endl; + scope_down(f_service_); + + f_service_ << indent() << "return true;" << endl; + + scope_down(f_service_); + f_service_ << endl; + + for (f_iter = functions.begin(); f_iter != functions.end(); ++f_iter) { + generate_process_function_async(tservice, *f_iter); + } + + indent_down(); + indent(f_service_) << "}" << endl << endl; +} + +void t_csharp_generator::generate_function_helpers(t_function* tfunction) { + if (tfunction->is_oneway()) { + return; + } + + t_struct result(program_, tfunction->get_name() + "_result"); + t_field success(tfunction->get_returntype(), "success", 0); + if (!tfunction->get_returntype()->is_void()) { + result.append(&success); + } + + t_struct* xs = tfunction->get_xceptions(); + const vector& fields = xs->get_members(); + vector::const_iterator f_iter; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + result.append(*f_iter); + } + + generate_csharp_struct_definition(f_service_, &result, false, true, true); +} + +void t_csharp_generator::generate_process_function(t_service* tservice, t_function* tfunction) { + (void)tservice; + indent(f_service_) << "public void " << tfunction->get_name() + << "_Process(int seqid, TProtocol iprot, TProtocol oprot)" << endl; + scope_up(f_service_); + + string argsname = tfunction->get_name() + "_args"; + string resultname = tfunction->get_name() + "_result"; + + f_service_ << indent() << argsname << " args = new " << argsname << "();" << endl + << indent() << "args.Read(iprot);" << endl + << indent() << "iprot.ReadMessageEnd();" << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector& xceptions = xs->get_members(); + vector::const_iterator x_iter; + + if (!tfunction->is_oneway()) { + f_service_ << indent() << resultname << " result = new " << resultname << "();" << endl; + } + + f_service_ << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + + if (xceptions.size() > 0) { + f_service_ << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + } + + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector& fields = arg_struct->get_members(); + vector::const_iterator f_iter; + + f_service_ << indent(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + f_service_ << "result.Success = "; + } + f_service_ << "iface_." << normalize_name(tfunction->get_name()) << "("; + bool first = true; + prepare_member_name_mapping(arg_struct); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + f_service_ << ", "; + } + f_service_ << "args." << prop_name(*f_iter); + if (nullable_ && !type_can_be_null((*f_iter)->get_type())) { + f_service_ << ".Value"; + } + } + cleanup_member_name_mapping(arg_struct); + f_service_ << ");" << endl; + + prepare_member_name_mapping(xs, xs->get_members(), resultname); + if (xceptions.size() > 0) { + indent_down(); + f_service_ << indent() << "}" << endl; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << indent() << "catch (" << type_name((*x_iter)->get_type(), false, false) << " " + << (*x_iter)->get_name() << ")" << endl + << indent() << "{" << endl; + if (!tfunction->is_oneway()) { + indent_up(); + f_service_ << indent() << "result." << prop_name(*x_iter) << " = " << (*x_iter)->get_name() + << ";" << endl; + indent_down(); + } + f_service_ << indent() << "}" << endl; + } + } + if (!tfunction->is_oneway()) { + f_service_ << indent() << "oprot.WriteMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.Reply, seqid)); " << endl; + f_service_ << indent() << "result.Write(oprot);" << endl; + } + indent_down(); + + cleanup_member_name_mapping(xs); + + f_service_ << indent() << "}" << endl + << indent() << "catch (TTransportException)" << endl + << indent() << "{" << endl + << indent() << " throw;" << endl + << indent() << "}" << endl + << indent() << "catch (Exception ex)" << endl + << indent() << "{" << endl + << indent() << " Console.Error.WriteLine(\"Error occurred in processor:\");" << endl + << indent() << " Console.Error.WriteLine(ex.ToString());" << endl; + + if (tfunction->is_oneway()) { + f_service_ << indent() << "}" << endl; + } else { + f_service_ << indent() << " TApplicationException x = new TApplicationException" << indent() + << "(TApplicationException.ExceptionType.InternalError,\" Internal error.\");" + << endl + << indent() << " oprot.WriteMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.Exception, seqid));" << endl + << indent() << " x.Write(oprot);" << endl + << indent() << "}" << endl; + f_service_ << indent() << "oprot.WriteMessageEnd();" << endl + << indent() << "oprot.Transport.Flush();" << endl; + } + + scope_down(f_service_); + + f_service_ << endl; +} + +void t_csharp_generator::generate_process_function_async(t_service* tservice, t_function* tfunction) { + (void)tservice; + indent(f_service_) << "public async Task " << tfunction->get_name() + << "_ProcessAsync(int seqid, TProtocol iprot, TProtocol oprot)" << endl; + scope_up(f_service_); + + string argsname = tfunction->get_name() + "_args"; + string resultname = tfunction->get_name() + "_result"; + + f_service_ << indent() << argsname << " args = new " << argsname << "();" << endl + << indent() << "args.Read(iprot);" << endl + << indent() << "iprot.ReadMessageEnd();" << endl; + + t_struct* xs = tfunction->get_xceptions(); + const std::vector& xceptions = xs->get_members(); + vector::const_iterator x_iter; + + if (!tfunction->is_oneway()) { + f_service_ << indent() << resultname << " result = new " << resultname << "();" << endl; + } + + f_service_ << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + + if (xceptions.size() > 0) { + f_service_ << indent() << "try" << endl + << indent() << "{" << endl; + indent_up(); + } + + t_struct* arg_struct = tfunction->get_arglist(); + const std::vector& fields = arg_struct->get_members(); + vector::const_iterator f_iter; + + f_service_ << indent(); + if (!tfunction->is_oneway() && !tfunction->get_returntype()->is_void()) { + f_service_ << "result.Success = "; + } + f_service_ << "await iface_." << normalize_name(tfunction->get_name()) << "Async("; + bool first = true; + prepare_member_name_mapping(arg_struct); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } + else { + f_service_ << ", "; + } + f_service_ << "args." << prop_name(*f_iter); + if (nullable_ && !type_can_be_null((*f_iter)->get_type())) { + f_service_ << ".Value"; + } + } + cleanup_member_name_mapping(arg_struct); + f_service_ << ");" << endl; + + prepare_member_name_mapping(xs, xs->get_members(), resultname); + if (xceptions.size() > 0) { + indent_down(); + f_service_ << indent() << "}" << endl; + for (x_iter = xceptions.begin(); x_iter != xceptions.end(); ++x_iter) { + f_service_ << indent() << "catch (" << type_name((*x_iter)->get_type(), false, false) << " " + << (*x_iter)->get_name() << ")" << endl + << indent() << "{" << endl; + if (!tfunction->is_oneway()) { + indent_up(); + f_service_ << indent() << "result." << prop_name(*x_iter) << " = " << (*x_iter)->get_name() + << ";" << endl; + indent_down(); + } + f_service_ << indent() << "}" << endl; + } + } + if (!tfunction->is_oneway()) { + f_service_ << indent() << "oprot.WriteMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.Reply, seqid)); " << endl; + f_service_ << indent() << "result.Write(oprot);" << endl; + } + indent_down(); + + cleanup_member_name_mapping(xs); + + f_service_ << indent() << "}" << endl + << indent() << "catch (TTransportException)" << endl + << indent() << "{" << endl + << indent() << " throw;" << endl + << indent() << "}" << endl + << indent() << "catch (Exception ex)" << endl + << indent() << "{" << endl + << indent() << " Console.Error.WriteLine(\"Error occurred in processor:\");" << endl + << indent() << " Console.Error.WriteLine(ex.ToString());" << endl; + + if (tfunction->is_oneway()) { + f_service_ << indent() << "}" << endl; + } + else { + f_service_ << indent() << " TApplicationException x = new TApplicationException" << indent() + << "(TApplicationException.ExceptionType.InternalError,\" Internal error.\");" + << endl + << indent() << " oprot.WriteMessageBegin(new TMessage(\"" << tfunction->get_name() + << "\", TMessageType.Exception, seqid));" << endl + << indent() << " x.Write(oprot);" << endl + << indent() << "}" << endl; + f_service_ << indent() << "oprot.WriteMessageEnd();" << endl + << indent() << "oprot.Transport.Flush();" << endl; + } + + scope_down(f_service_); + + f_service_ << endl; +} + +void t_csharp_generator::generate_csharp_union_reader(std::ostream& out, t_struct* tunion) { + // Thanks to THRIFT-1768, we don't need to check for required fields in the union + const vector& fields = tunion->get_members(); + vector::const_iterator f_iter; + + indent(out) << "public static " << tunion->get_name() << " Read(TProtocol iprot)" << endl; + scope_up(out); + + out << indent() << "iprot.IncrementRecursionDepth();" << endl; + out << indent() << "try" << endl; + scope_up(out); + + indent(out) << tunion->get_name() << " retval;" << endl; + indent(out) << "iprot.ReadStructBegin();" << endl; + indent(out) << "TField field = iprot.ReadFieldBegin();" << endl; + // we cannot have the first field be a stop -- we must have a single field defined + indent(out) << "if (field.Type == TType.Stop)" << endl; + scope_up(out); + indent(out) << "iprot.ReadFieldEnd();" << endl; + indent(out) << "retval = new ___undefined();" << endl; + scope_down(out); + indent(out) << "else" << endl; + scope_up(out); + indent(out) << "switch (field.ID)" << endl; + scope_up(out); + + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + indent(out) << "case " << (*f_iter)->get_key() << ":" << endl; + indent_up(); + indent(out) << "if (field.Type == " << type_to_enum((*f_iter)->get_type()) << ") {" << endl; + indent_up(); + + indent(out) << type_name((*f_iter)->get_type()) << " temp;" << endl; + generate_deserialize_field(out, (*f_iter), "temp", true); + indent(out) << "retval = new " << (*f_iter)->get_name() << "(temp);" << endl; + + indent_down(); + out << indent() << "} else { " << endl << indent() << " TProtocolUtil.Skip(iprot, field.Type);" + << endl << indent() << " retval = new ___undefined();" << endl << indent() << "}" << endl + << indent() << "break;" << endl; + indent_down(); + } + + indent(out) << "default: " << endl; + indent_up(); + indent(out) << "TProtocolUtil.Skip(iprot, field.Type);" << endl << indent() + << "retval = new ___undefined();" << endl; + indent(out) << "break;" << endl; + indent_down(); + + scope_down(out); + + indent(out) << "iprot.ReadFieldEnd();" << endl; + + indent(out) << "if (iprot.ReadFieldBegin().Type != TType.Stop)" << endl; + scope_up(out); + indent(out) << "throw new TProtocolException(TProtocolException.INVALID_DATA);" << endl; + scope_down(out); + + // end of else for TStop + scope_down(out); + indent(out) << "iprot.ReadStructEnd();" << endl; + indent(out) << "return retval;" << endl; + indent_down(); + + scope_down(out); + out << indent() << "finally" << endl; + scope_up(out); + out << indent() << "iprot.DecrementRecursionDepth();" << endl; + scope_down(out); + + indent(out) << "}" << endl << endl; +} + +void t_csharp_generator::generate_deserialize_field(ostream& out, + t_field* tfield, + string prefix, + bool is_propertyless) { + t_type* type = tfield->get_type(); + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + if (type->is_void()) { + throw "CANNOT GENERATE DESERIALIZE CODE FOR void TYPE: " + prefix + tfield->get_name(); + } + + string name = prefix + (is_propertyless ? "" : prop_name(tfield)); + + if (type->is_struct() || type->is_xception()) { + generate_deserialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_deserialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + indent(out) << name << " = "; + + if (type->is_enum()) { + out << "(" << type_name(type, false, true) << ")"; + } + + out << "iprot."; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "ReadBinary();"; + } else { + out << "ReadString();"; + } + break; + case t_base_type::TYPE_UUID: + out << "ReadUuid();"; + break; + case t_base_type::TYPE_BOOL: + out << "ReadBool();"; + break; + case t_base_type::TYPE_I8: + out << "ReadByte();"; + break; + case t_base_type::TYPE_I16: + out << "ReadI16();"; + break; + case t_base_type::TYPE_I32: + out << "ReadI32();"; + break; + case t_base_type::TYPE_I64: + out << "ReadI64();"; + break; + case t_base_type::TYPE_DOUBLE: + out << "ReadDouble();"; + break; + default: + throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "ReadI32();"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO DESERIALIZE FIELD '%s' TYPE '%s'\n", + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +void t_csharp_generator::generate_deserialize_struct(ostream& out, + t_struct* tstruct, + string prefix) { + if (union_ && tstruct->is_union()) { + out << indent() << prefix << " = " << type_name(tstruct) << ".Read(iprot);" << endl; + } else { + out << indent() << prefix << " = new " << type_name(tstruct) << "();" << endl << indent() + << prefix << ".Read(iprot);" << endl; + } +} + +void t_csharp_generator::generate_deserialize_container(ostream& out, + t_type* ttype, + string prefix) { + scope_up(out); + + string obj; + + if (ttype->is_map()) { + obj = tmp("_map"); + } else if (ttype->is_set()) { + obj = tmp("_set"); + } else if (ttype->is_list()) { + obj = tmp("_list"); + } + + indent(out) << prefix << " = new " << type_name(ttype, false, true) << "();" << endl; + if (ttype->is_map()) { + out << indent() << "TMap " << obj << " = iprot.ReadMapBegin();" << endl; + } else if (ttype->is_set()) { + out << indent() << "TSet " << obj << " = iprot.ReadSetBegin();" << endl; + } else if (ttype->is_list()) { + out << indent() << "TList " << obj << " = iprot.ReadListBegin();" << endl; + } + + string i = tmp("_i"); + indent(out) << "for( int " << i << " = 0; " << i << " < " << obj << ".Count" + << "; " + << "++" << i << ")" << endl; + scope_up(out); + + if (ttype->is_map()) { + generate_deserialize_map_element(out, (t_map*)ttype, prefix); + } else if (ttype->is_set()) { + generate_deserialize_set_element(out, (t_set*)ttype, prefix); + } else if (ttype->is_list()) { + generate_deserialize_list_element(out, (t_list*)ttype, prefix); + } + + scope_down(out); + + if (ttype->is_map()) { + indent(out) << "iprot.ReadMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "iprot.ReadSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "iprot.ReadListEnd();" << endl; + } + + scope_down(out); +} + +void t_csharp_generator::generate_deserialize_map_element(ostream& out, + t_map* tmap, + string prefix) { + string key = tmp("_key"); + string val = tmp("_val"); + + t_field fkey(tmap->get_key_type(), key); + t_field fval(tmap->get_val_type(), val); + + indent(out) << declare_field(&fkey) << endl; + indent(out) << declare_field(&fval) << endl; + + generate_deserialize_field(out, &fkey); + generate_deserialize_field(out, &fval); + + indent(out) << prefix << "[" << key << "] = " << val << ";" << endl; +} + +void t_csharp_generator::generate_deserialize_set_element(ostream& out, + t_set* tset, + string prefix) { + string elem = tmp("_elem"); + t_field felem(tset->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".Add(" << elem << ");" << endl; +} + +void t_csharp_generator::generate_deserialize_list_element(ostream& out, + t_list* tlist, + string prefix) { + string elem = tmp("_elem"); + t_field felem(tlist->get_elem_type(), elem); + + indent(out) << declare_field(&felem) << endl; + + generate_deserialize_field(out, &felem); + + indent(out) << prefix << ".Add(" << elem << ");" << endl; +} + +void t_csharp_generator::generate_serialize_field(ostream& out, + t_field* tfield, + string prefix, + bool is_element, + bool is_propertyless) { + t_type* type = tfield->get_type(); + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + string name = prefix + (is_propertyless ? "" : prop_name(tfield)); + + if (type->is_void()) { + throw "CANNOT GENERATE SERIALIZE CODE FOR void TYPE: " + name; + } + + if (type->is_struct() || type->is_xception()) { + generate_serialize_struct(out, (t_struct*)type, name); + } else if (type->is_container()) { + generate_serialize_container(out, type, name); + } else if (type->is_base_type() || type->is_enum()) { + indent(out) << "oprot."; + + string nullable_name = nullable_ && !is_element && !field_is_required(tfield) ? name + ".Value" + : name; + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "compiler error: cannot serialize void field in a struct: " + name; + break; + case t_base_type::TYPE_STRING: + if (type->is_binary()) { + out << "WriteBinary("; + } else { + out << "WriteString("; + } + out << name << ");"; + break; + case t_base_type::TYPE_UUID: + out << "WriteUuid(" << nullable_name << ");"; + break; + case t_base_type::TYPE_BOOL: + out << "WriteBool(" << nullable_name << ");"; + break; + case t_base_type::TYPE_I8: + out << "WriteByte(" << nullable_name << ");"; + break; + case t_base_type::TYPE_I16: + out << "WriteI16(" << nullable_name << ");"; + break; + case t_base_type::TYPE_I32: + out << "WriteI32(" << nullable_name << ");"; + break; + case t_base_type::TYPE_I64: + out << "WriteI64(" << nullable_name << ");"; + break; + case t_base_type::TYPE_DOUBLE: + out << "WriteDouble(" << nullable_name << ");"; + break; + default: + throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase); + } + } else if (type->is_enum()) { + out << "WriteI32((int)" << nullable_name << ");"; + } + out << endl; + } else { + printf("DO NOT KNOW HOW TO SERIALIZE '%s%s' TYPE '%s'\n", + prefix.c_str(), + tfield->get_name().c_str(), + type_name(type).c_str()); + } +} + +void t_csharp_generator::generate_serialize_struct(ostream& out, + t_struct* tstruct, + string prefix) { + (void)tstruct; + out << indent() << prefix << ".Write(oprot);" << endl; +} + +void t_csharp_generator::generate_serialize_container(ostream& out, t_type* ttype, string prefix) { + scope_up(out); + + if (ttype->is_map()) { + indent(out) << "oprot.WriteMapBegin(new TMap(" << type_to_enum(((t_map*)ttype)->get_key_type()) + << ", " << type_to_enum(((t_map*)ttype)->get_val_type()) << ", " << prefix + << ".Count));" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.WriteSetBegin(new TSet(" << type_to_enum(((t_set*)ttype)->get_elem_type()) + << ", " << prefix << ".Count));" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.WriteListBegin(new TList(" + << type_to_enum(((t_list*)ttype)->get_elem_type()) << ", " << prefix << ".Count));" + << endl; + } + + string iter = tmp("_iter"); + if (ttype->is_map()) { + indent(out) << "foreach (" << type_name(((t_map*)ttype)->get_key_type()) << " " << iter + << " in " << prefix << ".Keys)"; + } else if (ttype->is_set()) { + indent(out) << "foreach (" << type_name(((t_set*)ttype)->get_elem_type()) << " " << iter + << " in " << prefix << ")"; + } else if (ttype->is_list()) { + indent(out) << "foreach (" << type_name(((t_list*)ttype)->get_elem_type()) << " " << iter + << " in " << prefix << ")"; + } + + out << endl; + scope_up(out); + + if (ttype->is_map()) { + generate_serialize_map_element(out, (t_map*)ttype, iter, prefix); + } else if (ttype->is_set()) { + generate_serialize_set_element(out, (t_set*)ttype, iter); + } else if (ttype->is_list()) { + generate_serialize_list_element(out, (t_list*)ttype, iter); + } + + scope_down(out); + + if (ttype->is_map()) { + indent(out) << "oprot.WriteMapEnd();" << endl; + } else if (ttype->is_set()) { + indent(out) << "oprot.WriteSetEnd();" << endl; + } else if (ttype->is_list()) { + indent(out) << "oprot.WriteListEnd();" << endl; + } + + scope_down(out); +} + +void t_csharp_generator::generate_serialize_map_element(ostream& out, + t_map* tmap, + string iter, + string map) { + t_field kfield(tmap->get_key_type(), iter); + generate_serialize_field(out, &kfield, "", true); + t_field vfield(tmap->get_val_type(), map + "[" + iter + "]"); + generate_serialize_field(out, &vfield, "", true); +} + +void t_csharp_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { + t_field efield(tset->get_elem_type(), iter); + generate_serialize_field(out, &efield, "", true); +} + +void t_csharp_generator::generate_serialize_list_element(ostream& out, + t_list* tlist, + string iter) { + t_field efield(tlist->get_elem_type(), iter); + generate_serialize_field(out, &efield, "", true); +} + +void t_csharp_generator::generate_property(ostream& out, + t_field* tfield, + bool isPublic, + bool generateIsset) { + generate_csharp_property(out, tfield, isPublic, generateIsset, "_"); +} +void t_csharp_generator::generate_csharp_property(ostream& out, + t_field* tfield, + bool isPublic, + bool generateIsset, + std::string fieldPrefix) { + if ((serialize_ || wcf_) && isPublic) { + indent(out) << "[DataMember(Order = 0)]" << endl; + } + bool has_default = field_has_default(tfield); + bool is_required = field_is_required(tfield); + if ((nullable_ && !has_default) || (is_required)) { + indent(out) << (isPublic ? "public " : "private ") + << type_name(tfield->get_type(), false, false, true, is_required) << " " + << prop_name(tfield) << " { get; set; }" << endl; + } else { + indent(out) << (isPublic ? "public " : "private ") + << type_name(tfield->get_type(), false, false, true) << " " << prop_name(tfield) + << endl; + scope_up(out); + indent(out) << "get" << endl; + scope_up(out); + bool use_nullable = false; + if (nullable_) { + t_type* ttype = tfield->get_type(); + while (ttype->is_typedef()) { + ttype = ((t_typedef*)ttype)->get_type(); + } + if (ttype->is_base_type()) { + use_nullable = ((t_base_type*)ttype)->get_base() != t_base_type::TYPE_STRING; + } else if (ttype->is_enum()) { + use_nullable = true; + } + } + indent(out) << "return " << fieldPrefix + tfield->get_name() << ";" << endl; + scope_down(out); + indent(out) << "set" << endl; + scope_up(out); + if (use_nullable) { + if (generateIsset) { + indent(out) << "__isset." << normalize_name(tfield->get_name()) << " = value.HasValue;" + << endl; + } + indent(out) << "if (value.HasValue) this." << fieldPrefix + tfield->get_name() + << " = value.Value;" << endl; + } else { + if (generateIsset) { + indent(out) << "__isset." << normalize_name(tfield->get_name()) << " = true;" << endl; + } + indent(out) << "this." << fieldPrefix + tfield->get_name() << " = value;" << endl; + } + scope_down(out); + scope_down(out); + } + out << endl; +} + +std::string t_csharp_generator::make_valid_csharp_identifier(std::string const& fromName) { + std::string str = fromName; + if (str.empty()) { + return str; + } + + // tests rely on this + assert(('A' < 'Z') && ('a' < 'z') && ('0' < '9')); + + // if the first letter is a number, we add an additional underscore in front of it + char c = str.at(0); + if (('0' <= c) && (c <= '9')) { + str = "_" + str; + } + + // following chars: letter, number or underscore + for (size_t i = 0; i < str.size(); ++i) { + c = str.at(i); + if ((('A' > c) || (c > 'Z')) && (('a' > c) || (c > 'z')) && (('0' > c) || (c > '9')) + && ('_' != c)) { + str.replace(i, 1, "_"); + } + } + + return str; +} + +void t_csharp_generator::cleanup_member_name_mapping(void* scope) { + if( member_mapping_scopes.empty()) { + throw "internal error: cleanup_member_name_mapping() no scope active"; + } + + member_mapping_scope& active = member_mapping_scopes.back(); + if (active.scope_member != scope) { + throw "internal error: cleanup_member_name_mapping() called for wrong struct"; + } + + member_mapping_scopes.pop_back(); +} + +string t_csharp_generator::get_mapped_member_name(string name) { + if( ! member_mapping_scopes.empty()) { + member_mapping_scope& active = member_mapping_scopes.back(); + map::iterator iter = active.mapping_table.find(name); + if (active.mapping_table.end() != iter) { + return iter->second; + } + } + + pverbose("no mapping for member %s\n", name.c_str()); + return name; +} + +void t_csharp_generator::prepare_member_name_mapping(t_struct* tstruct) { + prepare_member_name_mapping(tstruct, tstruct->get_members(), tstruct->get_name()); +} + +void t_csharp_generator::prepare_member_name_mapping(void* scope, + const vector& members, + const string& structname) { + // begin new scope + member_mapping_scope dummy; + dummy.scope_member = 0; + member_mapping_scopes.push_back(dummy); + member_mapping_scope& active = member_mapping_scopes.back(); + active.scope_member = scope; + + // current C# generator policy: + // - prop names are always rendered with an Uppercase first letter + // - struct names are used as given + std::set used_member_names; + vector::const_iterator iter; + + // prevent name conflicts with struct (CS0542 error) + used_member_names.insert(structname); + + // prevent name conflicts with known methods (THRIFT-2942) + used_member_names.insert("Read"); + used_member_names.insert("Write"); + + for (iter = members.begin(); iter != members.end(); ++iter) { + string oldname = (*iter)->get_name(); + string newname = prop_name(*iter, true); + while (true) { + + // new name conflicts with another member + if (used_member_names.find(newname) != used_member_names.end()) { + pverbose("struct %s: member %s conflicts with another member\n", + structname.c_str(), + newname.c_str()); + newname += '_'; + continue; + } + + // add always, this helps us to detect edge cases like + // different spellings ("foo" and "Foo") within the same struct + pverbose("struct %s: member mapping %s => %s\n", + structname.c_str(), + oldname.c_str(), + newname.c_str()); + active.mapping_table[oldname] = newname; + used_member_names.insert(newname); + break; + } + } +} + +std::string t_csharp_generator::prop_name(t_field* tfield, bool suppress_mapping) { + string name(tfield->get_name()); + if (suppress_mapping) { + name[0] = toupper(name[0]); + } else { + name = get_mapped_member_name(name); + } + return name; +} + +string t_csharp_generator::type_name(t_type* ttype, + bool in_container, + bool in_init, + bool in_param, + bool is_required) { + (void)in_init; + while (ttype->is_typedef()) { + ttype = ((t_typedef*)ttype)->get_type(); + } + + if (ttype->is_base_type()) { + return base_type_name((t_base_type*)ttype, in_container, in_param, is_required); + } else if (ttype->is_map()) { + t_map* tmap = (t_map*)ttype; + return "Dictionary<" + type_name(tmap->get_key_type(), true) + ", " + + type_name(tmap->get_val_type(), true) + ">"; + } else if (ttype->is_set()) { + t_set* tset = (t_set*)ttype; + return "THashSet<" + type_name(tset->get_elem_type(), true) + ">"; + } else if (ttype->is_list()) { + t_list* tlist = (t_list*)ttype; + return "List<" + type_name(tlist->get_elem_type(), true) + ">"; + } + + t_program* program = ttype->get_program(); + string postfix = (!is_required && nullable_ && in_param && ttype->is_enum()) ? "?" : ""; + if (program != NULL && program != program_) { + string ns = program->get_namespace("csharp"); + if (!ns.empty()) { + return ns + "." + normalize_name(ttype->get_name()) + postfix; + } + } + + return normalize_name(ttype->get_name()) + postfix; +} + +string t_csharp_generator::base_type_name(t_base_type* tbase, + bool in_container, + bool in_param, + bool is_required) { + (void)in_container; + string postfix = (!is_required && nullable_ && in_param) ? "?" : ""; + switch (tbase->get_base()) { + case t_base_type::TYPE_VOID: + return "void"; + case t_base_type::TYPE_STRING: + if (tbase->is_binary()) { + return "byte[]"; + } else { + return "string"; + } + case t_base_type::TYPE_UUID: + return "global::System.Guid"; + case t_base_type::TYPE_BOOL: + return "bool" + postfix; + case t_base_type::TYPE_I8: + return "sbyte" + postfix; + case t_base_type::TYPE_I16: + return "short" + postfix; + case t_base_type::TYPE_I32: + return "int" + postfix; + case t_base_type::TYPE_I64: + return "long" + postfix; + case t_base_type::TYPE_DOUBLE: + return "double" + postfix; + default: + throw "compiler error: no C# name for base type " + t_base_type::t_base_name(tbase->get_base()); + } +} + +string t_csharp_generator::declare_field(t_field* tfield, bool init, std::string prefix) { + string result = type_name(tfield->get_type()) + " " + prefix + tfield->get_name(); + if (init) { + t_type* ttype = tfield->get_type(); + while (ttype->is_typedef()) { + ttype = ((t_typedef*)ttype)->get_type(); + } + if (ttype->is_base_type() && field_has_default(tfield)) { + std::ofstream dummy; + result += " = " + render_const_value(dummy, tfield->get_name(), ttype, tfield->get_value()); + } else if (ttype->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)ttype)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + result += " = null"; + break; + case t_base_type::TYPE_UUID: + result += " = System.Guid.Empty"; + break; + case t_base_type::TYPE_BOOL: + result += " = false"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + result += " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + result += " = (double)0"; + break; + default: + throw "compiler error: unhandled type"; + } + } else if (ttype->is_enum()) { + result += " = (" + type_name(ttype, false, true) + ")0"; + } else if (ttype->is_container()) { + result += " = new " + type_name(ttype, false, true) + "()"; + } else { + result += " = new " + type_name(ttype, false, true) + "()"; + } + } + return result + ";"; +} + +string t_csharp_generator::function_signature(t_function* tfunction, string prefix) { + t_type* ttype = tfunction->get_returntype(); + return type_name(ttype) + " " + normalize_name(prefix + tfunction->get_name()) + "(" + + argument_list(tfunction->get_arglist()) + ")"; +} + +string t_csharp_generator::function_signature_async_begin(t_function* tfunction, string prefix) { + string comma = (tfunction->get_arglist()->get_members().size() > 0 ? ", " : ""); + return "IAsyncResult " + normalize_name(prefix + tfunction->get_name()) + + "(AsyncCallback callback, object state" + comma + argument_list(tfunction->get_arglist()) + + ")"; +} + +string t_csharp_generator::function_signature_async_end(t_function* tfunction, string prefix) { + t_type* ttype = tfunction->get_returntype(); + return type_name(ttype) + " " + normalize_name(prefix + tfunction->get_name()) + + "(IAsyncResult asyncResult)"; +} + +string t_csharp_generator::function_signature_async(t_function* tfunction, string prefix) { + t_type* ttype = tfunction->get_returntype(); + string task = "Task"; + if (!ttype->is_void()) + task += "<" + type_name(ttype) + ">"; + return task + " " + normalize_name(prefix + tfunction->get_name()) + "Async(" + + argument_list(tfunction->get_arglist()) + ")"; +} + +string t_csharp_generator::argument_list(t_struct* tstruct) { + string result = ""; + const vector& fields = tstruct->get_members(); + vector::const_iterator f_iter; + bool first = true; + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { + if (first) { + first = false; + } else { + result += ", "; + } + result += type_name((*f_iter)->get_type()) + " " + normalize_name((*f_iter)->get_name()); + } + return result; +} + +string t_csharp_generator::type_to_enum(t_type* type) { + while (type->is_typedef()) { + type = ((t_typedef*)type)->get_type(); + } + + if (type->is_base_type()) { + t_base_type::t_base tbase = ((t_base_type*)type)->get_base(); + switch (tbase) { + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + return "TType.String"; + case t_base_type::TYPE_UUID: + return "TType.Uuid"; + case t_base_type::TYPE_BOOL: + return "TType.Bool"; + case t_base_type::TYPE_I8: + return "TType.Byte"; + case t_base_type::TYPE_I16: + return "TType.I16"; + case t_base_type::TYPE_I32: + return "TType.I32"; + case t_base_type::TYPE_I64: + return "TType.I64"; + case t_base_type::TYPE_DOUBLE: + return "TType.Double"; + default: + break; + } + } else if (type->is_enum()) { + return "TType.I32"; + } else if (type->is_struct() || type->is_xception()) { + return "TType.Struct"; + } else if (type->is_map()) { + return "TType.Map"; + } else if (type->is_set()) { + return "TType.Set"; + } else if (type->is_list()) { + return "TType.List"; + } + + throw "INVALID TYPE IN type_to_enum: " + type->get_name(); +} + +void t_csharp_generator::generate_csharp_docstring_comment(ostream& out, string contents) { + generate_docstring_comment(out, "/// \n", "/// ", contents, "/// \n"); +} + +void t_csharp_generator::generate_csharp_doc(ostream& out, t_field* field) { + if (field->get_type()->is_enum()) { + string combined_message = field->get_doc() + "\nget_type()) + "\"/>"; + generate_csharp_docstring_comment(out, combined_message); + } else { + generate_csharp_doc(out, (t_doc*)field); + } +} + +void t_csharp_generator::generate_csharp_doc(ostream& out, t_doc* tdoc) { + if (tdoc->has_doc()) { + generate_csharp_docstring_comment(out, tdoc->get_doc()); + } +} + +void t_csharp_generator::generate_csharp_doc(ostream& out, t_function* tfunction) { + if (tfunction->has_doc()) { + stringstream ps; + const vector& fields = tfunction->get_arglist()->get_members(); + vector::const_iterator p_iter; + for (p_iter = fields.begin(); p_iter != fields.end(); ++p_iter) { + t_field* p = *p_iter; + ps << "\nget_name() << "\">"; + if (p->has_doc()) { + std::string str = p->get_doc(); + str.erase(std::remove(str.begin(), str.end(), '\n'), + str.end()); // remove the newlines that appear from the parser + ps << str; + } + ps << ""; + } + generate_docstring_comment(out, + "", + "/// ", + "\n" + tfunction->get_doc() + "" + ps.str(), + ""); + } +} + +std::string t_csharp_generator::get_enum_class_name(t_type* type) { + string package = ""; + t_program* program = type->get_program(); + if (program != NULL && program != program_) { + package = program->get_namespace("csharp") + "."; + } + return package + type->get_name(); +} + + +std::string t_csharp_generator::display_name() const { + return "C#"; +} + + +THRIFT_REGISTER_GENERATOR( + csharp, + "C#", + " async: Adds Async support using Task.Run.\n" + " wcf: Adds bindings for WCF to generated classes.\n" + " serial: Add serialization support to generated classes.\n" + " nullable: Use nullable types for properties.\n" + " hashcode: Generate a hashcode and equals implementation for classes.\n" + " union: Use new union typing, which includes a static read function for union " + "types.\n") diff --git a/compiler/cpp/src/thrift/generate/t_generator.cc b/compiler/cpp/src/thrift/generate/t_generator.cc index 68602377ed5..de14cd3b0b4 100644 --- a/compiler/cpp/src/thrift/generate/t_generator.cc +++ b/compiler/cpp/src/thrift/generate/t_generator.cc @@ -226,7 +226,10 @@ t_generator* t_generator_registry::get_generator(t_program* program, gen_map_t& the_map = get_generator_map(); gen_map_t::iterator iter = the_map.find(language); - if ((language == "csharp") || (language == "netcore")) { + if (language == "csharp") { + pwarning(1, "The '%s' target is deprecated. Consider moving to 'netstd' instead.", language.c_str()); + } + else if (language == "netcore") { failure("The '%s' target is no longer available. Use 'netstd' instead.", language.c_str()); } diff --git a/compiler/cpp/tests/CMakeLists.txt b/compiler/cpp/tests/CMakeLists.txt index d9c5209134c..ef922f44f1e 100644 --- a/compiler/cpp/tests/CMakeLists.txt +++ b/compiler/cpp/tests/CMakeLists.txt @@ -114,6 +114,7 @@ endmacro() THRIFT_ADD_COMPILER(c_glib "Enable compiler for C with Glib" OFF) THRIFT_ADD_COMPILER(cl "Enable compiler for Common LISP" OFF) THRIFT_ADD_COMPILER(cpp "Enable compiler for C++" OFF) +THRIFT_ADD_COMPILER(csharp "Enable compiler for C#" OFF) THRIFT_ADD_COMPILER(d "Enable compiler for D" OFF) THRIFT_ADD_COMPILER(dart "Enable compiler for Dart" OFF) THRIFT_ADD_COMPILER(delphi "Enable compiler for Delphi" OFF) diff --git a/lib/csharp/Makefile.am b/lib/csharp/Makefile.am new file mode 100644 index 00000000000..cc2bbc96cfe --- /dev/null +++ b/lib/csharp/Makefile.am @@ -0,0 +1,114 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +SUBDIRS = . test/Multiplex + +THRIFTCODE = \ + src/Collections/THashSet.cs \ + src/Collections/TCollections.cs \ + src/Properties/AssemblyInfo.cs \ + src/Protocol/TAbstractBase.cs \ + src/Protocol/TBase.cs \ + src/Protocol/TBase64Utils.cs \ + src/Protocol/TJSONProtocol.cs \ + src/Protocol/TProtocolException.cs \ + src/Protocol/TProtocolFactory.cs \ + src/Protocol/TList.cs \ + src/Protocol/TSet.cs \ + src/Protocol/TMap.cs \ + src/Protocol/TProtocolUtil.cs \ + src/Protocol/TMessageType.cs \ + src/Protocol/TProtocol.cs \ + src/Protocol/TProtocolDecorator.cs \ + src/Protocol/TMultiplexedProtocol.cs \ + src/Protocol/TMultiplexedProcessor.cs \ + src/Protocol/TType.cs \ + src/Protocol/TField.cs \ + src/Protocol/TMessage.cs \ + src/Protocol/TStruct.cs \ + src/Protocol/TBinaryProtocol.cs \ + src/Protocol/TCompactProtocol.cs \ + src/Server/TThreadedServer.cs \ + src/Server/TThreadPoolServer.cs \ + src/Server/TSimpleServer.cs \ + src/Server/TServer.cs \ + src/Server/TServerEventHandler.cs \ + src/Transport/TBufferedTransport.cs \ + src/Transport/TTransport.cs \ + src/Transport/TSocket.cs \ + src/Transport/TSocketVersionizer.cs \ + src/Transport/TTransportException.cs \ + src/Transport/TStreamTransport.cs \ + src/Transport/TFramedTransport.cs \ + src/Transport/TServerTransport.cs \ + src/Transport/TServerSocket.cs \ + src/Transport/TTransportFactory.cs \ + src/Transport/THttpClient.cs \ + src/Transport/THttpHandler.cs \ + src/Transport/TMemoryBuffer.cs \ + src/Transport/TNamedPipeClientTransport.cs \ + src/Transport/TNamedPipeServerTransport.cs \ + src/Transport/TTLSSocket.cs \ + src/Transport/TTLSServerSocket.cs \ + src/TProcessor.cs \ + src/TProcessorFactory.cs \ + src/TSingletonProcessorFactory.cs \ + src/TPrototypeProcessorFactory.cs \ + src/TControllingHandler.cs \ + src/TException.cs \ + src/TApplicationException.cs + +if MONO_MCS +export CSC = mcs +else +export CSC = gmcs +endif + +if NET_2_0 +export CSC_DEFINES = -d:NET_2_0 +endif + +all-local: Thrift.dll Thrift.45.dll + +Thrift.dll: $(THRIFTCODE) + $(CSC) $(CSC_DEFINES) -out:$@ -target:library -reference:System.Web $(THRIFTCODE) + +Thrift.45.dll: $(THRIFTCODE) + $(CSC) $(CSC_DEFINES) -out:$@ -target:library -reference:System.Web $(THRIFTCODE) + +CLEANFILES = \ + Thrift.dll \ + Thrift.45.dll + +DISTCLEANFILES = \ + Makefile.in + +EXTRA_DIST = \ + $(THRIFTCODE) \ + ThriftMSBuildTask \ + src/Thrift.csproj \ + src/Thrift.45.csproj \ + src/Thrift.sln \ + src/Net35/ExtensionsNet35.cs \ + src/Transport/TSilverlightSocket.cs \ + src/Transport/THttpTaskAsyncHandler.cs \ + src/TAsyncProcessor.cs \ + test \ + coding_standards.md \ + README.md diff --git a/lib/csharp/README.md b/lib/csharp/README.md new file mode 100644 index 00000000000..5fc14cb3cdd --- /dev/null +++ b/lib/csharp/README.md @@ -0,0 +1,32 @@ +Thrift C# Software Library + +Deprecation notice +======= + +Per [THRIFT-4723](https://issues.apache.org/jira/browse/THRIFT-4723), both CSharp and Netcore targets are deprecated +and will be removed with the next release. Migrate to the [NetStd language target](../netstd/README.md) instead. + +License +======= + +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +Using Thrift with C# +==================== + +Thrift requires Mono >= 1.2.6 or .NET framework >= 3.5 diff --git a/lib/csharp/ThriftMSBuildTask/Properties/AssemblyInfo.cs b/lib/csharp/ThriftMSBuildTask/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..f5f00005976 --- /dev/null +++ b/lib/csharp/ThriftMSBuildTask/Properties/AssemblyInfo.cs @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ThriftMSBuildTask")] +[assembly: AssemblyDescription("MSBuild Task to generate csharp from .thrift files, and compile the code into a library: ThriftImpl.dll")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("The Apache Software Foundation")] +[assembly: AssemblyProduct("ThriftMSBuildTask")] +[assembly: AssemblyCopyright("The Apache Software Foundation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +//@TODO where to put License information? + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5095e09d-7b95-4be1-b250-e1c1db1c485e")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.14.0.*")] +[assembly: AssemblyFileVersion("0.14.0.*")] diff --git a/lib/csharp/ThriftMSBuildTask/ThriftBuild.cs b/lib/csharp/ThriftMSBuildTask/ThriftBuild.cs new file mode 100644 index 00000000000..4e6d3011256 --- /dev/null +++ b/lib/csharp/ThriftMSBuildTask/ThriftBuild.cs @@ -0,0 +1,246 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Microsoft.Build.Tasks; +using System.IO; +using System.Diagnostics; + +namespace ThriftMSBuildTask +{ + /// + /// MSBuild Task to generate csharp from .thrift files, and compile the code into a library: ThriftImpl.dll + /// + public class ThriftBuild : Task + { + /// + /// The full path to the thrift.exe compiler + /// + [Required] + public ITaskItem ThriftExecutable + { + get; + set; + } + + /// + /// The full path to a thrift.dll C# library + /// + [Required] + public ITaskItem ThriftLibrary + { + get; + set; + } + + /// + /// A direcotry containing .thrift files + /// + [Required] + public ITaskItem ThriftDefinitionDir + { + get; + set; + } + + /// + /// The name of the auto-gen and compiled thrift library. It will placed in + /// the same directory as ThriftLibrary + /// + [Required] + public ITaskItem OutputName + { + get; + set; + } + + /// + /// The full path to the compiled ThriftLibrary. This allows msbuild tasks to use this + /// output as a variable for use elsewhere. + /// + [Output] + public ITaskItem ThriftImplementation + { + get { return thriftImpl; } + } + + private ITaskItem thriftImpl; + private const string lastCompilationName = "LAST_COMP_TIMESTAMP"; + + //use the Message Build Task to write something to build log + private void LogMessage(string text, MessageImportance importance) + { + Message m = new Message(); + m.Text = text; + m.Importance = importance.ToString(); + m.BuildEngine = this.BuildEngine; + m.Execute(); + } + + //recursively find .cs files in srcDir, paths should initially be non-null and empty + private void FindSourcesHelper(string srcDir, List paths) + { + string[] files = Directory.GetFiles(srcDir, "*.cs"); + foreach (string f in files) + { + paths.Add(f); + } + string[] dirs = Directory.GetDirectories(srcDir); + foreach (string dir in dirs) + { + FindSourcesHelper(dir, paths); + } + } + + /// + /// Quote paths with spaces + /// + private string SafePath(string path) + { + if (path.Contains(' ') && !path.StartsWith("\"")) + { + return "\"" + path + "\""; + } + return path; + } + + private ITaskItem[] FindSources(string srcDir) + { + List files = new List(); + FindSourcesHelper(srcDir, files); + ITaskItem[] items = new ITaskItem[files.Count]; + for (int i = 0; i < items.Length; i++) + { + items[i] = new TaskItem(files[i]); + } + return items; + } + + private string LastWriteTime(string defDir) + { + string[] files = Directory.GetFiles(defDir, "*.thrift"); + DateTime d = (new DirectoryInfo(defDir)).LastWriteTime; + foreach(string file in files) + { + FileInfo f = new FileInfo(file); + DateTime curr = f.LastWriteTime; + if (DateTime.Compare(curr, d) > 0) + { + d = curr; + } + } + return d.ToFileTimeUtc().ToString(); + } + + public override bool Execute() + { + string defDir = SafePath(ThriftDefinitionDir.ItemSpec); + //look for last compilation timestamp + string lastBuildPath = Path.Combine(defDir, lastCompilationName); + DirectoryInfo defDirInfo = new DirectoryInfo(defDir); + string lastWrite = LastWriteTime(defDir); + if (File.Exists(lastBuildPath)) + { + string lastComp = File.ReadAllText(lastBuildPath); + //don't recompile if the thrift library has been updated since lastComp + FileInfo f = new FileInfo(ThriftLibrary.ItemSpec); + string thriftLibTime = f.LastWriteTimeUtc.ToFileTimeUtc().ToString(); + if (lastComp.CompareTo(thriftLibTime) < 0) + { + //new thrift library, do a compile + lastWrite = thriftLibTime; + } + else if (lastComp == lastWrite || (lastComp == thriftLibTime && lastComp.CompareTo(lastWrite) > 0)) + { + //the .thrift dir hasn't been written to since last compilation, don't need to do anything + LogMessage("ThriftImpl up-to-date", MessageImportance.High); + return true; + } + } + + //find the directory of the thriftlibrary (that's where output will go) + FileInfo thriftLibInfo = new FileInfo(SafePath(ThriftLibrary.ItemSpec)); + string thriftDir = thriftLibInfo.Directory.FullName; + + string genDir = Path.Combine(thriftDir, "gen-csharp"); + if (Directory.Exists(genDir)) + { + try + { + Directory.Delete(genDir, true); + } + catch { /*eh i tried, just over-write now*/} + } + + //run the thrift executable to generate C# + foreach (string thriftFile in Directory.GetFiles(defDir, "*.thrift")) + { + LogMessage("Generating code for: " + thriftFile, MessageImportance.Normal); + Process p = new Process(); + p.StartInfo.FileName = SafePath(ThriftExecutable.ItemSpec); + p.StartInfo.Arguments = "--gen csharp -o " + SafePath(thriftDir) + " -r " + thriftFile; + p.StartInfo.UseShellExecute = false; + p.StartInfo.CreateNoWindow = true; + p.StartInfo.RedirectStandardOutput = false; + p.Start(); + p.WaitForExit(); + if (p.ExitCode != 0) + { + LogMessage("thrift.exe failed to compile " + thriftFile, MessageImportance.High); + return false; + } + if (p.ExitCode != 0) + { + LogMessage("thrift.exe failed to compile " + thriftFile, MessageImportance.High); + return false; + } + } + + Csc csc = new Csc(); + csc.TargetType = "library"; + csc.References = new ITaskItem[] { new TaskItem(ThriftLibrary.ItemSpec) }; + csc.EmitDebugInformation = true; + string outputPath = Path.Combine(thriftDir, OutputName.ItemSpec); + csc.OutputAssembly = new TaskItem(outputPath); + csc.Sources = FindSources(Path.Combine(thriftDir, "gen-csharp")); + csc.BuildEngine = this.BuildEngine; + LogMessage("Compiling generated cs...", MessageImportance.Normal); + if (!csc.Execute()) + { + return false; + } + + //write file to defDir to indicate a build was successfully completed + File.WriteAllText(lastBuildPath, lastWrite); + + thriftImpl = new TaskItem(outputPath); + + return true; + } + } +} diff --git a/lib/csharp/ThriftMSBuildTask/ThriftMSBuildTask.csproj b/lib/csharp/ThriftMSBuildTask/ThriftMSBuildTask.csproj new file mode 100644 index 00000000000..25899705c13 --- /dev/null +++ b/lib/csharp/ThriftMSBuildTask/ThriftMSBuildTask.csproj @@ -0,0 +1,118 @@ + + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {EC0A0231-66EA-4593-A792-C6CA3BB8668E} + Library + Properties + ThriftMSBuildTask + ThriftMSBuildTask + v3.5 + 512 + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 0.14.0.0 + false + false + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + + + + + + + + 3.5 + + + 3.5 + + + 3.5 + + + + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + diff --git a/lib/csharp/coding_standards.md b/lib/csharp/coding_standards.md new file mode 100644 index 00000000000..dc87190bfb1 --- /dev/null +++ b/lib/csharp/coding_standards.md @@ -0,0 +1,6 @@ +## C# Coding Standards + +Please follow: + * [Thrift General Coding Standards](/doc/coding_standards.md) + * [MSDN C# Coding Conventions](http://msdn.microsoft.com/en-us/library/ff926074.aspx) + * [C# Coding Guidelines](http://csharpguidelines.codeplex.com/) diff --git a/lib/csharp/src/Collections/TCollections.cs b/lib/csharp/src/Collections/TCollections.cs new file mode 100644 index 00000000000..84afb6a62a7 --- /dev/null +++ b/lib/csharp/src/Collections/TCollections.cs @@ -0,0 +1,94 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +using System; +using System.Collections; + +namespace Thrift.Collections +{ + public class TCollections + { + /// + /// This will return true if the two collections are value-wise the same. + /// If the collection contains a collection, the collections will be compared using this method. + /// + public static bool Equals (IEnumerable first, IEnumerable second) + { + if (first == null && second == null) + { + return true; + } + if (first == null || second == null) + { + return false; + } + IEnumerator fiter = first.GetEnumerator (); + IEnumerator siter = second.GetEnumerator (); + + bool fnext = fiter.MoveNext (); + bool snext = siter.MoveNext (); + while (fnext && snext) + { + IEnumerable fenum = fiter.Current as IEnumerable; + IEnumerable senum = siter.Current as IEnumerable; + if (fenum != null && senum != null) + { + if (!Equals(fenum, senum)) + { + return false; + } + } + else if (fenum == null ^ senum == null) + { + return false; + } + else if (!Equals(fiter.Current, siter.Current)) + { + return false; + } + fnext = fiter.MoveNext(); + snext = siter.MoveNext(); + } + + return fnext == snext; + } + + /// + /// This returns a hashcode based on the value of the enumerable. + /// + public static int GetHashCode (IEnumerable enumerable) + { + if (enumerable == null) + { + return 0; + } + + int hashcode = 0; + foreach (object obj in enumerable) + { + IEnumerable enum2 = obj as IEnumerable; + int objHash = enum2 == null ? obj.GetHashCode () : GetHashCode (enum2); + unchecked + { + hashcode = (hashcode * 397) ^ (objHash); + } + } + return hashcode; + } + } +} diff --git a/lib/csharp/src/Collections/THashSet.cs b/lib/csharp/src/Collections/THashSet.cs new file mode 100644 index 00000000000..e29271a935d --- /dev/null +++ b/lib/csharp/src/Collections/THashSet.cs @@ -0,0 +1,160 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System; +using System.Collections; +using System.Collections.Generic; + +#if SILVERLIGHT +using System.Runtime.Serialization; +#endif + +namespace Thrift.Collections +{ +#if SILVERLIGHT + [DataContract] +#else + [Serializable] +#endif + public class THashSet : ICollection + { +#if NET_2_0 || SILVERLIGHT +#if SILVERLIGHT + [DataMember] +#endif + TDictSet set = new TDictSet(); +#else + HashSet set = new HashSet(); +#endif + public int Count + { + get { return set.Count; } + } + + public bool IsReadOnly + { + get { return false; } + } + + public void Add(T item) + { + set.Add(item); + } + + public void Clear() + { + set.Clear(); + } + + public bool Contains(T item) + { + return set.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + set.CopyTo(array, arrayIndex); + } + + public IEnumerator GetEnumerator() + { + return set.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)set).GetEnumerator(); + } + + public bool Remove(T item) + { + return set.Remove(item); + } + +#if NET_2_0 || SILVERLIGHT +#if SILVERLIGHT + [DataContract] +#endif + private class TDictSet : ICollection + { +#if SILVERLIGHT + [DataMember] +#endif + Dictionary> dict = new Dictionary>(); + + public int Count + { + get { return dict.Count; } + } + + public bool IsReadOnly + { + get { return false; } + } + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)dict.Keys).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return dict.Keys.GetEnumerator(); + } + + public bool Add(V item) + { + if (!dict.ContainsKey(item)) + { + dict[item] = this; + return true; + } + + return false; + } + + void ICollection.Add(V item) + { + Add(item); + } + + public void Clear() + { + dict.Clear(); + } + + public bool Contains(V item) + { + return dict.ContainsKey(item); + } + + public void CopyTo(V[] array, int arrayIndex) + { + dict.Keys.CopyTo(array, arrayIndex); + } + + public bool Remove(V item) + { + return dict.Remove(item); + } + } +#endif + } + +} diff --git a/lib/csharp/src/Net35/ExtensionsNet35.cs b/lib/csharp/src/Net35/ExtensionsNet35.cs new file mode 100644 index 00000000000..73a42328847 --- /dev/null +++ b/lib/csharp/src/Net35/ExtensionsNet35.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +#if (!NET45) +namespace Thrift +{ + static class StreamExtensionsNet35 + { + // CopyTo() has been added in 4.0 + public static long CopyTo(this Stream source, Stream target) + { + byte[] buffer = new byte[8192]; // multiple of 4096 + long nTotal = 0; + while (true) + { + int nRead = source.Read(buffer, 0, buffer.Length); + if (nRead <= 0) // done? + return nTotal; + + target.Write(buffer, 0, nRead); + nTotal += nRead; + } + } + } + +} +#endif + diff --git a/lib/csharp/src/Properties/AssemblyInfo.cs b/lib/csharp/src/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..c8f51b5c076 --- /dev/null +++ b/lib/csharp/src/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Thrift")] +[assembly: AssemblyDescription("C# bindings for the Apache Thrift RPC system")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("The Apache Software Foundation")] +[assembly: AssemblyProduct("Thrift")] +[assembly: AssemblyCopyright("The Apache Software Foundation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +//@TODO where to put License information? + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("df3f8ef0-e0a3-4c86-a65b-8ec84e016b1d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("0.14.0.0")] +[assembly: AssemblyFileVersion("0.14.0.0")] diff --git a/lib/csharp/src/Protocol/TAbstractBase.cs b/lib/csharp/src/Protocol/TAbstractBase.cs new file mode 100644 index 00000000000..f5a61cd44ed --- /dev/null +++ b/lib/csharp/src/Protocol/TAbstractBase.cs @@ -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. + */ + +namespace Thrift.Protocol +{ + public interface TAbstractBase + { + /// + /// Writes the objects out to the protocol. + /// + void Write(TProtocol tProtocol); + } +} diff --git a/lib/csharp/src/Protocol/TBase.cs b/lib/csharp/src/Protocol/TBase.cs new file mode 100644 index 00000000000..411e4d95f6d --- /dev/null +++ b/lib/csharp/src/Protocol/TBase.cs @@ -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. + */ + +namespace Thrift.Protocol +{ + public interface TBase : TAbstractBase + { + /// + /// Reads the TObject from the given input protocol. + /// + void Read(TProtocol tProtocol); + } +} diff --git a/lib/csharp/src/Protocol/TBase64Utils.cs b/lib/csharp/src/Protocol/TBase64Utils.cs new file mode 100644 index 00000000000..9eaaebddaa8 --- /dev/null +++ b/lib/csharp/src/Protocol/TBase64Utils.cs @@ -0,0 +1,100 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System; + +namespace Thrift.Protocol +{ + internal static class TBase64Utils + { + internal const string ENCODE_TABLE = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + internal static void encode(byte[] src, int srcOff, int len, byte[] dst, + int dstOff) + { + dst[dstOff] = (byte)ENCODE_TABLE[(src[srcOff] >> 2) & 0x3F]; + if (len == 3) + { + dst[dstOff + 1] = + (byte)ENCODE_TABLE[ + ((src[srcOff] << 4) & 0x30) | ((src[srcOff + 1] >> 4) & 0x0F)]; + dst[dstOff + 2] = + (byte)ENCODE_TABLE[ + ((src[srcOff + 1] << 2) & 0x3C) | ((src[srcOff + 2] >> 6) & 0x03)]; + dst[dstOff + 3] = + (byte)ENCODE_TABLE[src[srcOff + 2] & 0x3F]; + } + else if (len == 2) + { + dst[dstOff + 1] = + (byte)ENCODE_TABLE[ + ((src[srcOff] << 4) & 0x30) | ((src[srcOff + 1] >> 4) & 0x0F)]; + dst[dstOff + 2] = + (byte)ENCODE_TABLE[(src[srcOff + 1] << 2) & 0x3C]; + + } + else + { // len == 1) { + dst[dstOff + 1] = + (byte)ENCODE_TABLE[(src[srcOff] << 4) & 0x30]; + } + } + + private static int[] 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, + }; + + internal static void decode(byte[] src, int srcOff, int len, byte[] dst, + int dstOff) + { + dst[dstOff] = (byte) + ((DECODE_TABLE[src[srcOff] & 0x0FF] << 2) | + (DECODE_TABLE[src[srcOff + 1] & 0x0FF] >> 4)); + if (len > 2) + { + dst[dstOff + 1] = (byte) + (((DECODE_TABLE[src[srcOff + 1] & 0x0FF] << 4) & 0xF0) | + (DECODE_TABLE[src[srcOff + 2] & 0x0FF] >> 2)); + if (len > 3) + { + dst[dstOff + 2] = (byte) + (((DECODE_TABLE[src[srcOff + 2] & 0x0FF] << 6) & 0xC0) | + DECODE_TABLE[src[srcOff + 3] & 0x0FF]); + } + } + } + + } +} diff --git a/lib/csharp/src/Protocol/TBinaryProtocol.cs b/lib/csharp/src/Protocol/TBinaryProtocol.cs new file mode 100644 index 00000000000..04db99ce27d --- /dev/null +++ b/lib/csharp/src/Protocol/TBinaryProtocol.cs @@ -0,0 +1,410 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Text; +using Thrift.Transport; +using Thrift.Protocol.Utilities; + +namespace Thrift.Protocol +{ + public class TBinaryProtocol : TProtocol + { + protected const uint VERSION_MASK = 0xffff0000; + protected const uint VERSION_1 = 0x80010000; + + protected bool strictRead_ = false; + protected bool strictWrite_ = true; + + #region BinaryProtocol Factory + + public class Factory : TProtocolFactory + { + protected bool strictRead_ = false; + protected bool strictWrite_ = true; + + public Factory() + : this(false, true) + { + } + + public Factory(bool strictRead, bool strictWrite) + { + strictRead_ = strictRead; + strictWrite_ = strictWrite; + } + + public TProtocol GetProtocol(TTransport trans) + { + return new TBinaryProtocol(trans, strictRead_, strictWrite_); + } + } + + #endregion + + public TBinaryProtocol(TTransport trans) + : this(trans, false, true) + { + } + + public TBinaryProtocol(TTransport trans, bool strictRead, bool strictWrite) + : base(trans) + { + strictRead_ = strictRead; + strictWrite_ = strictWrite; + } + + #region Write Methods + + public override void WriteMessageBegin(TMessage message) + { + if (strictWrite_) + { + uint version = VERSION_1 | (uint)(message.Type); + WriteI32((int)version); + WriteString(message.Name); + WriteI32(message.SeqID); + } + else + { + WriteString(message.Name); + WriteByte((sbyte)message.Type); + WriteI32(message.SeqID); + } + } + + public override void WriteMessageEnd() + { + } + + public override void WriteStructBegin(TStruct struc) + { + } + + public override void WriteStructEnd() + { + } + + public override void WriteFieldBegin(TField field) + { + WriteByte((sbyte)field.Type); + WriteI16(field.ID); + } + + public override void WriteFieldEnd() + { + } + + public override void WriteFieldStop() + { + WriteByte((sbyte)TType.Stop); + } + + public override void WriteMapBegin(TMap map) + { + WriteByte((sbyte)map.KeyType); + WriteByte((sbyte)map.ValueType); + WriteI32(map.Count); + } + + public override void WriteMapEnd() + { + } + + public override void WriteListBegin(TList list) + { + WriteByte((sbyte)list.ElementType); + WriteI32(list.Count); + } + + public override void WriteListEnd() + { + } + + public override void WriteSetBegin(TSet set) + { + WriteByte((sbyte)set.ElementType); + WriteI32(set.Count); + } + + public override void WriteSetEnd() + { + } + + public override void WriteBool(bool b) + { + WriteByte(b ? (sbyte)1 : (sbyte)0); + } + + private byte[] bout = new byte[1]; + public override void WriteByte(sbyte b) + { + bout[0] = (byte)b; + trans.Write(bout, 0, 1); + } + + private byte[] i16out = new byte[2]; + public override void WriteI16(short s) + { + i16out[0] = (byte)(0xff & (s >> 8)); + i16out[1] = (byte)(0xff & s); + trans.Write(i16out, 0, 2); + } + + private byte[] i32out = new byte[4]; + public override void WriteI32(int i32) + { + i32out[0] = (byte)(0xff & (i32 >> 24)); + i32out[1] = (byte)(0xff & (i32 >> 16)); + i32out[2] = (byte)(0xff & (i32 >> 8)); + i32out[3] = (byte)(0xff & i32); + trans.Write(i32out, 0, 4); + } + + private byte[] i64out = new byte[8]; + public override void WriteI64(long i64) + { + i64out[0] = (byte)(0xff & (i64 >> 56)); + i64out[1] = (byte)(0xff & (i64 >> 48)); + i64out[2] = (byte)(0xff & (i64 >> 40)); + i64out[3] = (byte)(0xff & (i64 >> 32)); + i64out[4] = (byte)(0xff & (i64 >> 24)); + i64out[5] = (byte)(0xff & (i64 >> 16)); + i64out[6] = (byte)(0xff & (i64 >> 8)); + i64out[7] = (byte)(0xff & i64); + trans.Write(i64out, 0, 8); + } + + public override void WriteDouble(double d) + { +#if !SILVERLIGHT + WriteI64(BitConverter.DoubleToInt64Bits(d)); +#else + var bytes = BitConverter.GetBytes(d); + WriteI64(BitConverter.ToInt64(bytes, 0)); +#endif + } + + public override void WriteBinary(byte[] b) + { + WriteI32(b.Length); + trans.Write(b, 0, b.Length); + } + + public override void WriteUuid(Guid uuid) + { + var bytes = uuid.SwapByteOrder().ToByteArray(); + trans.Write(bytes, 0, bytes.Length); + } + + + #endregion + + #region ReadMethods + + public override TMessage ReadMessageBegin() + { + TMessage message = new TMessage(); + int size = ReadI32(); + if (size < 0) + { + uint version = (uint)size & VERSION_MASK; + if (version != VERSION_1) + { + throw new TProtocolException(TProtocolException.BAD_VERSION, "Bad version in ReadMessageBegin: " + version); + } + message.Type = (TMessageType)(size & 0x000000ff); + message.Name = ReadString(); + message.SeqID = ReadI32(); + } + else + { + if (strictRead_) + { + throw new TProtocolException(TProtocolException.BAD_VERSION, "Missing version in readMessageBegin, old client?"); + } + message.Name = ReadStringBody(size); + message.Type = (TMessageType)ReadByte(); + message.SeqID = ReadI32(); + } + return message; + } + + public override void ReadMessageEnd() + { + } + + public override TStruct ReadStructBegin() + { + return new TStruct(); + } + + public override void ReadStructEnd() + { + } + + public override TField ReadFieldBegin() + { + TField field = new TField(); + field.Type = (TType)ReadByte(); + + if (field.Type != TType.Stop) + { + field.ID = ReadI16(); + } + + return field; + } + + public override void ReadFieldEnd() + { + } + + public override TMap ReadMapBegin() + { + TMap map = new TMap(); + map.KeyType = (TType)ReadByte(); + map.ValueType = (TType)ReadByte(); + map.Count = ReadI32(); + + return map; + } + + public override void ReadMapEnd() + { + } + + public override TList ReadListBegin() + { + TList list = new TList(); + list.ElementType = (TType)ReadByte(); + list.Count = ReadI32(); + + return list; + } + + public override void ReadListEnd() + { + } + + public override TSet ReadSetBegin() + { + TSet set = new TSet(); + set.ElementType = (TType)ReadByte(); + set.Count = ReadI32(); + + return set; + } + + public override void ReadSetEnd() + { + } + + public override bool ReadBool() + { + return ReadByte() == 1; + } + + private byte[] bin = new byte[1]; + public override sbyte ReadByte() + { + ReadAll(bin, 0, 1); + return (sbyte)bin[0]; + } + + private byte[] i16in = new byte[2]; + public override short ReadI16() + { + ReadAll(i16in, 0, 2); + return (short)(((i16in[0] & 0xff) << 8) | ((i16in[1] & 0xff))); + } + + private byte[] i32in = new byte[4]; + public override int ReadI32() + { + ReadAll(i32in, 0, 4); + return (int)(((i32in[0] & 0xff) << 24) | ((i32in[1] & 0xff) << 16) | ((i32in[2] & 0xff) << 8) | ((i32in[3] & 0xff))); + } + +#pragma warning disable 675 + + private byte[] i64in = new byte[8]; + public override long ReadI64() + { + ReadAll(i64in, 0, 8); + unchecked + { + return (long)( + ((long)(i64in[0] & 0xff) << 56) | + ((long)(i64in[1] & 0xff) << 48) | + ((long)(i64in[2] & 0xff) << 40) | + ((long)(i64in[3] & 0xff) << 32) | + ((long)(i64in[4] & 0xff) << 24) | + ((long)(i64in[5] & 0xff) << 16) | + ((long)(i64in[6] & 0xff) << 8) | + ((long)(i64in[7] & 0xff))); + } + } + +#pragma warning restore 675 + + public override double ReadDouble() + { +#if !SILVERLIGHT + return BitConverter.Int64BitsToDouble(ReadI64()); +#else + var value = ReadI64(); + var bytes = BitConverter.GetBytes(value); + return BitConverter.ToDouble(bytes, 0); +#endif + } + + public override Guid ReadUuid() + { + var buf = new byte[16]; + trans.ReadAll(buf, 0, 16); + return new Guid(buf).SwapByteOrder(); + } + + public override byte[] ReadBinary() + { + int size = ReadI32(); + byte[] buf = new byte[size]; + trans.ReadAll(buf, 0, size); + return buf; + } + private string ReadStringBody(int size) + { + byte[] buf = new byte[size]; + trans.ReadAll(buf, 0, size); + return Encoding.UTF8.GetString(buf, 0, buf.Length); + } + + private int ReadAll(byte[] buf, int off, int len) + { + return trans.ReadAll(buf, off, len); + } + + #endregion + } +} diff --git a/lib/csharp/src/Protocol/TCompactProtocol.cs b/lib/csharp/src/Protocol/TCompactProtocol.cs new file mode 100644 index 00000000000..a97f668d49a --- /dev/null +++ b/lib/csharp/src/Protocol/TCompactProtocol.cs @@ -0,0 +1,867 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Text; +using Thrift.Transport; +using Thrift.Protocol.Utilities; +using System.Collections; +using System.IO; +using System.Collections.Generic; + +namespace Thrift.Protocol +{ + public class TCompactProtocol : TProtocol + { + private static TStruct ANONYMOUS_STRUCT = new TStruct(""); + private static TField TSTOP = new TField("", TType.Stop, (short)0); + + private static byte[] ttypeToCompactType = new byte[17]; + + private const byte PROTOCOL_ID = 0x82; + private const byte VERSION = 1; + private const byte VERSION_MASK = 0x1f; // 0001 1111 + private const byte TYPE_MASK = 0xE0; // 1110 0000 + private const byte TYPE_BITS = 0x07; // 0000 0111 + private const int TYPE_SHIFT_AMOUNT = 5; + + /// + /// All of the on-wire type codes. + /// + private static class Types + { + public const byte STOP = 0x00; + public const byte BOOLEAN_TRUE = 0x01; + public const byte BOOLEAN_FALSE = 0x02; + public const byte BYTE = 0x03; + public const byte I16 = 0x04; + public const byte I32 = 0x05; + public const byte I64 = 0x06; + public const byte DOUBLE = 0x07; + public const byte BINARY = 0x08; + public const byte LIST = 0x09; + public const byte SET = 0x0A; + public const byte MAP = 0x0B; + public const byte STRUCT = 0x0C; + public const byte UUID = 0x0D; + } + + /// + /// Used to keep track of the last field for the current and previous structs, + /// so we can do the delta stuff. + /// + private Stack lastField_ = new Stack(15); + + private short lastFieldId_ = 0; + + /// + /// If we encounter a boolean field begin, save the TField here so it can + /// have the value incorporated. + /// + private Nullable booleanField_; + + /// + /// If we Read a field header, and it's a boolean field, save the boolean + /// value here so that ReadBool can use it. + /// + private Nullable boolValue_; + + + #region CompactProtocol Factory + + public class Factory : TProtocolFactory + { + public Factory() { } + + public TProtocol GetProtocol(TTransport trans) + { + return new TCompactProtocol(trans); + } + } + + #endregion + + public TCompactProtocol(TTransport trans) + : base(trans) + { + ttypeToCompactType[(int)TType.Stop] = Types.STOP; + ttypeToCompactType[(int)TType.Bool] = Types.BOOLEAN_TRUE; + ttypeToCompactType[(int)TType.Byte] = Types.BYTE; + ttypeToCompactType[(int)TType.I16] = Types.I16; + ttypeToCompactType[(int)TType.I32] = Types.I32; + ttypeToCompactType[(int)TType.I64] = Types.I64; + ttypeToCompactType[(int)TType.Double] = Types.DOUBLE; + ttypeToCompactType[(int)TType.String] = Types.BINARY; + ttypeToCompactType[(int)TType.List] = Types.LIST; + ttypeToCompactType[(int)TType.Set] = Types.SET; + ttypeToCompactType[(int)TType.Map] = Types.MAP; + ttypeToCompactType[(int)TType.Struct] = Types.STRUCT; + ttypeToCompactType[(int)TType.Uuid] = Types.UUID; + } + + public void reset() + { + lastField_.Clear(); + lastFieldId_ = 0; + } + + #region Write Methods + + /// + /// 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 byte[] byteDirectBuffer = new byte[1]; + + private void WriteByteDirect(byte b) + { + byteDirectBuffer[0] = b; + trans.Write(byteDirectBuffer); + } + + /// + /// Writes a byte without any possibility of all that field header nonsense. + /// + private void WriteByteDirect(int n) + { + WriteByteDirect((byte)n); + } + + /// + /// Write an i32 as a varint. Results in 1-5 bytes on the wire. + /// TODO: make a permanent buffer like WriteVarint64? + /// + byte[] i32buf = new byte[5]; + + private void WriteVarint32(uint n) + { + int idx = 0; + while (true) + { + if ((n & ~0x7F) == 0) + { + i32buf[idx++] = (byte)n; + // WriteByteDirect((byte)n); + break; + // return; + } + else + { + i32buf[idx++] = (byte)((n & 0x7F) | 0x80); + // WriteByteDirect((byte)((n & 0x7F) | 0x80)); + n >>= 7; + } + } + trans.Write(i32buf, 0, idx); + } + + /// + /// 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 override void WriteMessageBegin(TMessage message) + { + WriteByteDirect(PROTOCOL_ID); + WriteByteDirect((byte)((VERSION & VERSION_MASK) | ((((uint)message.Type) << TYPE_SHIFT_AMOUNT) & TYPE_MASK))); + WriteVarint32((uint)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 override void WriteStructBegin(TStruct strct) + { + 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 override void WriteStructEnd() + { + 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 override void WriteFieldBegin(TField field) + { + if (field.Type == TType.Bool) + { + // we want to possibly include the value, so we'll wait. + booleanField_ = field; + } + else + { + WriteFieldBeginInternal(field, 0xFF); + } + } + + /// + /// 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) + { + // short lastField = lastField_.Pop(); + + // if there's a type override, use that. + byte typeToWrite = typeOverride == 0xFF ? 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 override void WriteFieldStop() + { + WriteByteDirect(Types.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 override void WriteMapBegin(TMap map) + { + if (map.Count == 0) + { + WriteByteDirect(0); + } + else + { + WriteVarint32((uint)map.Count); + WriteByteDirect(getCompactType(map.KeyType) << 4 | getCompactType(map.ValueType)); + } + } + + /// + /// Write a list header. + /// + public override void WriteListBegin(TList list) + { + WriteCollectionBegin(list.ElementType, list.Count); + } + + /// + /// Write a set header. + /// + public override void WriteSetBegin(TSet set) + { + WriteCollectionBegin(set.ElementType, set.Count); + } + + /// + /// 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 override void WriteBool(Boolean b) + { + if (booleanField_ != null) + { + // we haven't written the field header yet + WriteFieldBeginInternal(booleanField_.Value, 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 override void WriteByte(sbyte b) + { + WriteByteDirect((byte)b); + } + + /// + /// Write an I16 as a zigzag varint. + /// + public override void WriteI16(short i16) + { + WriteVarint32(intToZigZag(i16)); + } + + /// + /// Write an i32 as a zigzag varint. + /// + public override void WriteI32(int i32) + { + WriteVarint32(intToZigZag(i32)); + } + + /// + /// Write an i64 as a zigzag varint. + /// + public override void WriteI64(long i64) + { + WriteVarint64(longToZigzag(i64)); + } + + /// + /// Write a double to the wire as 8 bytes. + /// + public override void WriteDouble(double dub) + { + byte[] data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; + fixedLongToBytes(BitConverter.DoubleToInt64Bits(dub), data, 0); + trans.Write(data); + } + + /// + /// Write a string to the wire with a varint size preceding. + /// + public override void WriteString(string str) + { + byte[] bytes = UTF8Encoding.UTF8.GetBytes(str); + WriteBinary(bytes, 0, bytes.Length); + } + + /// + /// Write a byte array, using a varint for the size. + /// + public override void WriteBinary(byte[] bin) + { + WriteBinary(bin, 0, bin.Length); + } + + private void WriteBinary(byte[] buf, int offset, int length) + { + WriteVarint32((uint)length); + trans.Write(buf, offset, length); + } + + public override void WriteUuid(Guid uuid) + { + var bytes = uuid.SwapByteOrder().ToByteArray(); + trans.Write(bytes, 0, bytes.Length); + } + + // + // These methods are called by structs, but don't actually have any wire + // output or purpose. + // + + public override void WriteMessageEnd() { } + public override void WriteMapEnd() { } + public override void WriteListEnd() { } + public override void WriteSetEnd() { } + public override void WriteFieldEnd() { } + + // + // 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(TType elemType, int size) + { + if (size <= 14) + { + WriteByteDirect(size << 4 | getCompactType(elemType)); + } + else + { + WriteByteDirect(0xf0 | getCompactType(elemType)); + WriteVarint32((uint)size); + } + } + + /// + /// Write an i64 as a varint. Results in 1-10 bytes on the wire. + /// + byte[] varint64out = new byte[10]; + private void WriteVarint64(ulong n) + { + int idx = 0; + while (true) + { + if ((n & ~(ulong)0x7FL) == 0) + { + varint64out[idx++] = (byte)n; + break; + } + else + { + varint64out[idx++] = ((byte)((n & 0x7F) | 0x80)); + n >>= 7; + } + } + trans.Write(varint64out, 0, idx); + } + + /// + /// Convert l into a zigzag long. This allows negative numbers to be + /// represented compactly as a varint. + /// + private ulong longToZigzag(long n) + { + return (ulong)(n << 1) ^ (ulong)(n >> 63); + } + + /// + /// Convert n into a zigzag int. This allows negative numbers to be + /// represented compactly as a varint. + /// + private uint intToZigZag(int n) + { + return (uint)(n << 1) ^ (uint)(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); + } + + #endregion + + #region ReadMethods + + /// + /// Read a message header. + /// + public override TMessage ReadMessageBegin() + { + byte protocolId = (byte)ReadByte(); + if (protocolId != PROTOCOL_ID) + { + throw new TProtocolException("Expected protocol id " + PROTOCOL_ID.ToString("X") + " but got " + protocolId.ToString("X")); + } + byte versionAndType = (byte)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 = (int)ReadVarint32(); + string messageName = ReadString(); + return new TMessage(messageName, (TMessageType)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 override TStruct ReadStructBegin() + { + 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 override void ReadStructEnd() + { + // consume the last field we Read off the wire. + lastFieldId_ = lastField_.Pop(); + } + + /// + /// Read a field header off the wire. + /// + public override TField ReadFieldBegin() + { + byte type = (byte)ReadByte(); + + // if it's a stop, then we can return immediately, as the struct is over. + if (type == Types.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 ? true : 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 override TMap ReadMapBegin() + { + int size = (int)ReadVarint32(); + byte keyAndValueType = size == 0 ? (byte)0 : (byte)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 override TList ReadListBegin() + { + byte size_and_type = (byte)ReadByte(); + int size = (size_and_type >> 4) & 0x0f; + if (size == 15) + { + size = (int)ReadVarint32(); + } + TType 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 override TSet ReadSetBegin() + { + 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 override Boolean ReadBool() + { + if (boolValue_ != null) + { + bool result = boolValue_.Value; + boolValue_ = null; + return result; + } + return ReadByte() == Types.BOOLEAN_TRUE; + } + + byte[] byteRawBuf = new byte[1]; + /// + /// Read a single byte off the wire. Nothing interesting here. + /// + public override sbyte ReadByte() + { + trans.ReadAll(byteRawBuf, 0, 1); + return (sbyte)byteRawBuf[0]; + } + + /// + /// Read an i16 from the wire as a zigzag varint. + /// + public override short ReadI16() + { + return (short)zigzagToInt(ReadVarint32()); + } + + /// + /// Read an i32 from the wire as a zigzag varint. + /// + public override int ReadI32() + { + return zigzagToInt(ReadVarint32()); + } + + /// + /// Read an i64 from the wire as a zigzag varint. + /// + public override long ReadI64() + { + return zigzagToLong(ReadVarint64()); + } + + /// + /// No magic here - just Read a double off the wire. + /// + public override double ReadDouble() + { + byte[] longBits = new byte[8]; + trans.ReadAll(longBits, 0, 8); + return BitConverter.Int64BitsToDouble(bytesToLong(longBits)); + } + + public override Guid ReadUuid() + { + var buf = new byte[16]; + trans.ReadAll(buf, 0, 16); + return new Guid(buf).SwapByteOrder(); + } + + /// + /// Reads a byte[] (via ReadBinary), and then UTF-8 decodes it. + /// + public override string ReadString() + { + int length = (int)ReadVarint32(); + + if (length == 0) + { + return ""; + } + + return Encoding.UTF8.GetString(ReadBinary(length)); + } + + /// + /// Read a byte[] from the wire. + /// + public override byte[] ReadBinary() + { + int length = (int)ReadVarint32(); + if (length == 0) return new byte[0]; + + byte[] buf = new byte[length]; + trans.ReadAll(buf, 0, length); + return buf; + } + + /// + /// Read a byte[] of a known length from the wire. + /// + private byte[] ReadBinary(int length) + { + if (length == 0) return new byte[0]; + + byte[] buf = new byte[length]; + trans.ReadAll(buf, 0, length); + return buf; + } + + // + // These methods are here for the struct to call, but don't have any wire + // encoding. + // + public override void ReadMessageEnd() { } + public override void ReadFieldEnd() { } + public override void ReadMapEnd() { } + public override void ReadListEnd() { } + public override void ReadSetEnd() { } + + // + // 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 uint ReadVarint32() + { + uint result = 0; + int shift = 0; + while (true) + { + byte b = (byte)ReadByte(); + result |= (uint)(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 ulong ReadVarint64() + { + int shift = 0; + ulong result = 0; + while (true) + { + byte b = (byte)ReadByte(); + result |= (ulong)(b & 0x7f) << shift; + if ((b & 0x80) != 0x80) break; + shift += 7; + } + + return result; + } + + #endregion + + // + // encoding helpers + // + + /// + /// Convert from zigzag int to int. + /// + private int zigzagToInt(uint n) + { + return (int)(n >> 1) ^ (-(int)(n & 1)); + } + + /// + /// Convert from zigzag long to long. + /// + private long zigzagToLong(ulong n) + { + return (long)(n >> 1) ^ (-(long)(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 TType getTType(byte type) + { + switch ((byte)(type & 0x0f)) + { + case Types.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; + case Types.UUID: + return TType.Uuid; + 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(TType ttype) + { + return ttypeToCompactType[(int)ttype]; + } + } +} diff --git a/lib/csharp/src/Protocol/TField.cs b/lib/csharp/src/Protocol/TField.cs new file mode 100644 index 00000000000..81795577ec8 --- /dev/null +++ b/lib/csharp/src/Protocol/TField.cs @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Thrift.Protocol +{ + public struct TField + { + private string name; + private TType type; + private short id; + + public TField(string name, TType type, short id) + :this() + { + this.name = name; + this.type = type; + this.id = id; + } + + public string Name + { + get { return name; } + set { name = value; } + } + + public TType Type + { + get { return type; } + set { type = value; } + } + + public short ID + { + get { return id; } + set { id = value; } + } + } +} diff --git a/lib/csharp/src/Protocol/TGuidExtensions.cs b/lib/csharp/src/Protocol/TGuidExtensions.cs new file mode 100644 index 00000000000..da8f49c42b5 --- /dev/null +++ b/lib/csharp/src/Protocol/TGuidExtensions.cs @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation(ASF) under one +// or more contributor license agreements.See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership.The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Thrift.Protocol.Utilities +{ + public static class TGuidExtensions + { + public static Guid SwapByteOrder(this Guid self) + { + var bytes = self.ToByteArray(); + + SwapBytes(ref bytes[0], ref bytes[3]); + SwapBytes(ref bytes[1], ref bytes[2]); + SwapBytes(ref bytes[4], ref bytes[5]); + SwapBytes(ref bytes[6], ref bytes[7]); + + return new Guid(bytes); + } + + private static void SwapBytes(ref byte one, ref byte two) + { + var tmp = one; + one = two; + two = tmp; + } + + #region SelfTest +#if DEBUG + static private readonly Guid INTERNAL_HOST_ORDER = new Guid("{67452301-ab89-efcd-0123-456789abcdef}"); + static private readonly Guid NETWORK_BYTE_ORDER = new Guid("{01234567-89ab-cdef-0123-456789abcdef}"); + + static TGuidExtensions() + { + SelfTest(); + } + + private static void SelfTest() + { + var guid = INTERNAL_HOST_ORDER; + guid = guid.SwapByteOrder(); + Debug.Assert(guid.Equals(NETWORK_BYTE_ORDER)); + guid = guid.SwapByteOrder(); + Debug.Assert(guid.Equals(INTERNAL_HOST_ORDER)); + } + +#endif + #endregion + + } +} diff --git a/lib/csharp/src/Protocol/TJSONProtocol.cs b/lib/csharp/src/Protocol/TJSONProtocol.cs new file mode 100644 index 00000000000..ed78c58aac9 --- /dev/null +++ b/lib/csharp/src/Protocol/TJSONProtocol.cs @@ -0,0 +1,1134 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System; +using System.IO; +using System.Text; +using System.Collections.Generic; + +using Thrift.Transport; +using System.Globalization; + +namespace Thrift.Protocol +{ + /// + /// JSON protocol implementation for thrift. + /// + /// This is a full-featured protocol supporting Write and Read. + /// + /// Please see the C++ class header for a detailed description of the + /// protocol's wire format. + /// + /// Adapted from the Java version. + /// + public class TJSONProtocol : TProtocol + { + /// + /// Factory for JSON protocol objects. + /// + public class Factory : TProtocolFactory + { + public TProtocol GetProtocol(TTransport trans) + { + return new TJSONProtocol(trans); + } + } + + private static byte[] COMMA = new byte[] { (byte)',' }; + private static byte[] COLON = new byte[] { (byte)':' }; + private static byte[] LBRACE = new byte[] { (byte)'{' }; + private static byte[] RBRACE = new byte[] { (byte)'}' }; + private static byte[] LBRACKET = new byte[] { (byte)'[' }; + private static byte[] RBRACKET = new byte[] { (byte)']' }; + private static byte[] QUOTE = new byte[] { (byte)'"' }; + private static byte[] BACKSLASH = new byte[] { (byte)'\\' }; + + private byte[] ESCSEQ = new byte[] { (byte)'\\', (byte)'u', (byte)'0', (byte)'0' }; + + private const long VERSION = 1; + private byte[] JSON_CHAR_TABLE = { + 0, 0, 0, 0, 0, 0, 0, 0,(byte)'b',(byte)'t',(byte)'n', 0,(byte)'f',(byte)'r', 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1,(byte)'"', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + }; + + private char[] ESCAPE_CHARS = "\"\\/bfnrt".ToCharArray(); + + private byte[] ESCAPE_CHAR_VALS = { + (byte)'"', (byte)'\\', (byte)'/', (byte)'\b', (byte)'\f', (byte)'\n', (byte)'\r', (byte)'\t', + }; + + private const int DEF_STRING_SIZE = 16; + + private static byte[] NAME_BOOL = new byte[] { (byte)'t', (byte)'f' }; + private static byte[] NAME_BYTE = new byte[] { (byte)'i', (byte)'8' }; + private static byte[] NAME_I16 = new byte[] { (byte)'i', (byte)'1', (byte)'6' }; + private static byte[] NAME_I32 = new byte[] { (byte)'i', (byte)'3', (byte)'2' }; + private static byte[] NAME_I64 = new byte[] { (byte)'i', (byte)'6', (byte)'4' }; + private static byte[] NAME_DOUBLE = new byte[] { (byte)'d', (byte)'b', (byte)'l' }; + private static byte[] NAME_STRUCT = new byte[] { (byte)'r', (byte)'e', (byte)'c' }; + private static byte[] NAME_STRING = new byte[] { (byte)'s', (byte)'t', (byte)'r' }; + private static byte[] NAME_MAP = new byte[] { (byte)'m', (byte)'a', (byte)'p' }; + private static byte[] NAME_LIST = new byte[] { (byte)'l', (byte)'s', (byte)'t' }; + private static byte[] NAME_SET = new byte[] { (byte)'s', (byte)'e', (byte)'t' }; + + private static byte[] GetTypeNameForTypeID(TType typeID) + { + switch (typeID) + { + case TType.Bool: + return NAME_BOOL; + case TType.Byte: + return NAME_BYTE; + case TType.I16: + return NAME_I16; + case TType.I32: + return NAME_I32; + case TType.I64: + return NAME_I64; + case TType.Double: + return NAME_DOUBLE; + case TType.String: + return NAME_STRING; + case TType.Struct: + return NAME_STRUCT; + case TType.Map: + return NAME_MAP; + case TType.Set: + return NAME_SET; + case TType.List: + return NAME_LIST; + default: + throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, + "Unrecognized type"); + } + } + + private static TType GetTypeIDForTypeName(byte[] name) + { + TType result = TType.Stop; + if (name.Length > 1) + { + switch (name[0]) + { + case (byte)'d': + result = TType.Double; + break; + case (byte)'i': + switch (name[1]) + { + case (byte)'8': + result = TType.Byte; + break; + case (byte)'1': + result = TType.I16; + break; + case (byte)'3': + result = TType.I32; + break; + case (byte)'6': + result = TType.I64; + break; + } + break; + case (byte)'l': + result = TType.List; + break; + case (byte)'m': + result = TType.Map; + break; + case (byte)'r': + result = TType.Struct; + break; + case (byte)'s': + if (name[1] == (byte)'t') + { + result = TType.String; + } + else if (name[1] == (byte)'e') + { + result = TType.Set; + } + break; + case (byte)'t': + result = TType.Bool; + break; + } + } + if (result == TType.Stop) + { + throw new TProtocolException(TProtocolException.NOT_IMPLEMENTED, + "Unrecognized type"); + } + return result; + } + + /// + /// Base class for tracking JSON contexts that may require + /// inserting/Reading additional JSON syntax characters + /// This base context does nothing. + /// + protected class JSONBaseContext + { + protected TJSONProtocol proto; + + public JSONBaseContext(TJSONProtocol proto) + { + this.proto = proto; + } + + public virtual void Write() { } + + public virtual void Read() { } + + public virtual bool EscapeNumbers() { return false; } + } + + /// + /// Context for JSON lists. Will insert/Read commas before each item except + /// for the first one + /// + protected class JSONListContext : JSONBaseContext + { + public JSONListContext(TJSONProtocol protocol) + : base(protocol) + { + + } + + private bool first = true; + + public override void Write() + { + if (first) + { + first = false; + } + else + { + proto.trans.Write(COMMA); + } + } + + public override void Read() + { + if (first) + { + first = false; + } + else + { + proto.ReadJSONSyntaxChar(COMMA); + } + } + } + + /// + /// Context for JSON records. Will insert/Read colons before the value portion + /// of each record pair, and commas before each key except the first. In + /// addition, will indicate that numbers in the key position need to be + /// escaped in quotes (since JSON keys must be strings). + /// + protected class JSONPairContext : JSONBaseContext + { + public JSONPairContext(TJSONProtocol proto) + : base(proto) + { + + } + + private bool first = true; + private bool colon = true; + + public override void Write() + { + if (first) + { + first = false; + colon = true; + } + else + { + proto.trans.Write(colon ? COLON : COMMA); + colon = !colon; + } + } + + public override void Read() + { + if (first) + { + first = false; + colon = true; + } + else + { + proto.ReadJSONSyntaxChar(colon ? COLON : COMMA); + colon = !colon; + } + } + + public override bool EscapeNumbers() + { + return colon; + } + } + + /// + /// Holds up to one byte from the transport + /// + protected class LookaheadReader + { + protected TJSONProtocol proto; + + public LookaheadReader(TJSONProtocol proto) + { + this.proto = proto; + } + + private bool hasData; + private byte[] data = new byte[1]; + + /// + /// Return and consume the next byte to be Read, either taking it from the + /// data buffer if present or getting it from the transport otherwise. + /// + public byte Read() + { + if (hasData) + { + hasData = false; + } + else + { + proto.trans.ReadAll(data, 0, 1); + } + return data[0]; + } + + /// + /// Return the next byte to be Read without consuming, filling the data + /// buffer if it has not been filled alReady. + /// + public byte Peek() + { + if (!hasData) + { + proto.trans.ReadAll(data, 0, 1); + } + hasData = true; + return data[0]; + } + } + + // Default encoding + protected Encoding utf8Encoding = UTF8Encoding.UTF8; + + // Stack of nested contexts that we may be in + protected Stack contextStack = new Stack(); + + // Current context that we are in + protected JSONBaseContext context; + + // Reader that manages a 1-byte buffer + protected LookaheadReader reader; + + /// + /// Push a new JSON context onto the stack. + /// + protected void PushContext(JSONBaseContext c) + { + contextStack.Push(context); + context = c; + } + + /// + /// Pop the last JSON context off the stack + /// + protected void PopContext() + { + context = contextStack.Pop(); + } + + /// + /// TJSONProtocol Constructor + /// + public TJSONProtocol(TTransport trans) + : base(trans) + { + context = new JSONBaseContext(this); + reader = new LookaheadReader(this); + } + + // Temporary buffer used by several methods + private byte[] tempBuffer = new byte[4]; + + /// + /// Read a byte that must match b[0]; otherwise an exception is thrown. + /// Marked protected to avoid synthetic accessor in JSONListContext.Read + /// and JSONPairContext.Read + /// + protected void ReadJSONSyntaxChar(byte[] b) + { + byte ch = reader.Read(); + if (ch != b[0]) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Unexpected character:" + (char)ch); + } + } + + /// + /// Convert a byte containing a hex char ('0'-'9' or 'a'-'f') into its + /// corresponding hex value + /// + private static byte HexVal(byte ch) + { + if ((ch >= '0') && (ch <= '9')) + { + return (byte)((char)ch - '0'); + } + else if ((ch >= 'a') && (ch <= 'f')) + { + ch += 10; + return (byte)((char)ch - 'a'); + } + else + { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Expected hex character"); + } + } + + /// + /// Convert a byte containing a hex value to its corresponding hex character + /// + private static byte HexChar(byte val) + { + val &= 0x0F; + if (val < 10) + { + return (byte)((char)val + '0'); + } + else + { + val -= 10; + return (byte)((char)val + 'a'); + } + } + + /// + /// Write the bytes in array buf as a JSON characters, escaping as needed + /// + private void WriteJSONString(byte[] b) + { + context.Write(); + trans.Write(QUOTE); + int len = b.Length; + for (int i = 0; i < len; i++) + { + if ((b[i] & 0x00FF) >= 0x30) + { + if (b[i] == BACKSLASH[0]) + { + trans.Write(BACKSLASH); + trans.Write(BACKSLASH); + } + else + { + trans.Write(b, i, 1); + } + } + else + { + tempBuffer[0] = JSON_CHAR_TABLE[b[i]]; + if (tempBuffer[0] == 1) + { + trans.Write(b, i, 1); + } + else if (tempBuffer[0] > 1) + { + trans.Write(BACKSLASH); + trans.Write(tempBuffer, 0, 1); + } + else + { + trans.Write(ESCSEQ); + tempBuffer[0] = HexChar((byte)(b[i] >> 4)); + tempBuffer[1] = HexChar(b[i]); + trans.Write(tempBuffer, 0, 2); + } + } + } + trans.Write(QUOTE); + } + + /// + /// Write out number as a JSON value. If the context dictates so, it will be + /// wrapped in quotes to output as a JSON string. + /// + private void WriteJSONInteger(long num) + { + context.Write(); + string str = num.ToString(); + + bool escapeNum = context.EscapeNumbers(); + if (escapeNum) + trans.Write(QUOTE); + + trans.Write(utf8Encoding.GetBytes(str)); + + if (escapeNum) + trans.Write(QUOTE); + } + + /// + /// Write out a double as a JSON value. If it is NaN or infinity or if the + /// context dictates escaping, Write out as JSON string. + /// + private void WriteJSONDouble(double num) + { + context.Write(); + string str = num.ToString("G17", CultureInfo.InvariantCulture); + bool special = false; + + switch (str[0]) + { + case 'N': // NaN + case 'I': // Infinity + special = true; + break; + case '-': + if (str[1] == 'I') + { // -Infinity + special = true; + } + break; + } + + bool escapeNum = special || context.EscapeNumbers(); + + if (escapeNum) + trans.Write(QUOTE); + + trans.Write(utf8Encoding.GetBytes(str)); + + if (escapeNum) + trans.Write(QUOTE); + } + /// + /// Write out contents of byte array b as a JSON string with base-64 encoded + /// data + /// + private void WriteJSONBase64(byte[] b) + { + context.Write(); + trans.Write(QUOTE); + + int len = b.Length; + int off = 0; + + while (len >= 3) + { + // Encode 3 bytes at a time + TBase64Utils.encode(b, off, 3, tempBuffer, 0); + trans.Write(tempBuffer, 0, 4); + off += 3; + len -= 3; + } + if (len > 0) + { + // Encode remainder + TBase64Utils.encode(b, off, len, tempBuffer, 0); + trans.Write(tempBuffer, 0, len + 1); + } + + trans.Write(QUOTE); + } + + private void WriteJSONObjectStart() + { + context.Write(); + trans.Write(LBRACE); + PushContext(new JSONPairContext(this)); + } + + private void WriteJSONObjectEnd() + { + PopContext(); + trans.Write(RBRACE); + } + + private void WriteJSONArrayStart() + { + context.Write(); + trans.Write(LBRACKET); + PushContext(new JSONListContext(this)); + } + + private void WriteJSONArrayEnd() + { + PopContext(); + trans.Write(RBRACKET); + } + + public override void WriteMessageBegin(TMessage message) + { + WriteJSONArrayStart(); + WriteJSONInteger(VERSION); + + byte[] b = utf8Encoding.GetBytes(message.Name); + WriteJSONString(b); + + WriteJSONInteger((long)message.Type); + WriteJSONInteger(message.SeqID); + } + + public override void WriteMessageEnd() + { + WriteJSONArrayEnd(); + } + + public override void WriteStructBegin(TStruct str) + { + WriteJSONObjectStart(); + } + + public override void WriteStructEnd() + { + WriteJSONObjectEnd(); + } + + public override void WriteFieldBegin(TField field) + { + WriteJSONInteger(field.ID); + WriteJSONObjectStart(); + WriteJSONString(GetTypeNameForTypeID(field.Type)); + } + + public override void WriteFieldEnd() + { + WriteJSONObjectEnd(); + } + + public override void WriteFieldStop() { } + + public override void WriteMapBegin(TMap map) + { + WriteJSONArrayStart(); + WriteJSONString(GetTypeNameForTypeID(map.KeyType)); + WriteJSONString(GetTypeNameForTypeID(map.ValueType)); + WriteJSONInteger(map.Count); + WriteJSONObjectStart(); + } + + public override void WriteMapEnd() + { + WriteJSONObjectEnd(); + WriteJSONArrayEnd(); + } + + public override void WriteListBegin(TList list) + { + WriteJSONArrayStart(); + WriteJSONString(GetTypeNameForTypeID(list.ElementType)); + WriteJSONInteger(list.Count); + } + + public override void WriteListEnd() + { + WriteJSONArrayEnd(); + } + + public override void WriteSetBegin(TSet set) + { + WriteJSONArrayStart(); + WriteJSONString(GetTypeNameForTypeID(set.ElementType)); + WriteJSONInteger(set.Count); + } + + public override void WriteSetEnd() + { + WriteJSONArrayEnd(); + } + + public override void WriteBool(bool b) + { + WriteJSONInteger(b ? (long)1 : (long)0); + } + + public override void WriteByte(sbyte b) + { + WriteJSONInteger((long)b); + } + + public override void WriteI16(short i16) + { + WriteJSONInteger((long)i16); + } + + public override void WriteI32(int i32) + { + WriteJSONInteger((long)i32); + } + + public override void WriteI64(long i64) + { + WriteJSONInteger(i64); + } + + public override void WriteDouble(double dub) + { + WriteJSONDouble(dub); + } + + public override void WriteString(string str) + { + byte[] b = utf8Encoding.GetBytes(str); + WriteJSONString(b); + } + + public override void WriteBinary(byte[] bin) + { + WriteJSONBase64(bin); + } + + public override void WriteUuid(Guid uuid) + { + WriteString(uuid.ToString("D")); // no curly braces + } + + /** + * Reading methods. + */ + + /// + /// Read in a JSON string, unescaping as appropriate.. Skip Reading from the + /// context if skipContext is true. + /// + private byte[] ReadJSONString(bool skipContext) + { + MemoryStream buffer = new MemoryStream(); + List codeunits = new List(); + + + if (!skipContext) + { + context.Read(); + } + ReadJSONSyntaxChar(QUOTE); + while (true) + { + byte ch = reader.Read(); + if (ch == QUOTE[0]) + { + break; + } + + // escaped? + if (ch != ESCSEQ[0]) + { + buffer.Write(new byte[] { (byte)ch }, 0, 1); + continue; + } + + // distinguish between \uXXXX and \? + ch = reader.Read(); + if (ch != ESCSEQ[1]) // control chars like \n + { + int off = Array.IndexOf(ESCAPE_CHARS, (char)ch); + if (off == -1) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Expected control char"); + } + ch = ESCAPE_CHAR_VALS[off]; + buffer.Write(new byte[] { (byte)ch }, 0, 1); + continue; + } + + + // it's \uXXXX + trans.ReadAll(tempBuffer, 0, 4); + var wch = (short)((HexVal((byte)tempBuffer[0]) << 12) + + (HexVal((byte)tempBuffer[1]) << 8) + + (HexVal((byte)tempBuffer[2]) << 4) + + HexVal(tempBuffer[3])); + if (Char.IsHighSurrogate((char)wch)) + { + if (codeunits.Count > 0) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Expected low surrogate char"); + } + codeunits.Add((char)wch); + } + else if (Char.IsLowSurrogate((char)wch)) + { + if (codeunits.Count == 0) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Expected high surrogate char"); + } + codeunits.Add((char)wch); + var tmp = utf8Encoding.GetBytes(codeunits.ToArray()); + buffer.Write(tmp, 0, tmp.Length); + codeunits.Clear(); + } + else + { + var tmp = utf8Encoding.GetBytes(new char[] { (char)wch }); + buffer.Write(tmp, 0, tmp.Length); + } + } + + + if (codeunits.Count > 0) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Expected low surrogate char"); + } + + return buffer.ToArray(); + } + + /// + /// Return true if the given byte could be a valid part of a JSON number. + /// + private bool IsJSONNumeric(byte b) + { + switch (b) + { + case (byte)'+': + case (byte)'-': + case (byte)'.': + case (byte)'0': + case (byte)'1': + case (byte)'2': + case (byte)'3': + case (byte)'4': + case (byte)'5': + case (byte)'6': + case (byte)'7': + case (byte)'8': + case (byte)'9': + case (byte)'E': + case (byte)'e': + return true; + } + return false; + } + + /// + /// Read in a sequence of characters that are all valid in JSON numbers. Does + /// not do a complete regex check to validate that this is actually a number. + /// + private string ReadJSONNumericChars() + { + StringBuilder strbld = new StringBuilder(); + while (true) + { + byte ch = reader.Peek(); + if (!IsJSONNumeric(ch)) + { + break; + } + strbld.Append((char)reader.Read()); + } + return strbld.ToString(); + } + + /// + /// Read in a JSON number. If the context dictates, Read in enclosing quotes. + /// + private long ReadJSONInteger() + { + context.Read(); + if (context.EscapeNumbers()) + { + ReadJSONSyntaxChar(QUOTE); + } + + string str = ReadJSONNumericChars(); + if (context.EscapeNumbers()) + { + ReadJSONSyntaxChar(QUOTE); + } + + try + { + return Int64.Parse(str); + } + catch (FormatException fex) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Bad data encounted in numeric data", fex); + } + } + + /// + /// Read in a JSON double value. Throw if the value is not wrapped in quotes + /// when expected or if wrapped in quotes when not expected. + /// + private double ReadJSONDouble() + { + context.Read(); + if (reader.Peek() == QUOTE[0]) + { + byte[] arr = ReadJSONString(true); + double dub = Double.Parse(utf8Encoding.GetString(arr, 0, arr.Length), CultureInfo.InvariantCulture); + + if (!context.EscapeNumbers() && !Double.IsNaN(dub) && !Double.IsInfinity(dub)) + { + // Throw exception -- we should not be in a string in this case + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Numeric data unexpectedly quoted"); + } + return dub; + } + else + { + if (context.EscapeNumbers()) + { + // This will throw - we should have had a quote if escapeNum == true + ReadJSONSyntaxChar(QUOTE); + } + try + { + return Double.Parse(ReadJSONNumericChars(), CultureInfo.InvariantCulture); + } + catch (FormatException fex) + { + throw new TProtocolException(TProtocolException.INVALID_DATA, + "Bad data encounted in numeric data", fex); + } + } + } + + /// + /// Read in a JSON string containing base-64 encoded data and decode it. + /// + private byte[] ReadJSONBase64() + { + byte[] b = ReadJSONString(false); + int len = b.Length; + int off = 0; + int size = 0; + // reduce len to ignore fill bytes + while ((len > 0) && (b[len - 1] == '=')) + { + --len; + } + // read & decode full byte triplets = 4 source bytes + while (len > 4) + { + // Decode 4 bytes at a time + TBase64Utils.decode(b, off, 4, b, size); // NB: decoded in place + off += 4; + len -= 4; + size += 3; + } + // Don't decode if we hit the end or got a single leftover byte (invalid + // base64 but legal for skip of regular string type) + if (len > 1) + { + // Decode remainder + TBase64Utils.decode(b, off, len, b, size); // NB: decoded in place + size += len - 1; + } + // Sadly we must copy the byte[] (any way around this?) + byte[] result = new byte[size]; + Array.Copy(b, 0, result, 0, size); + return result; + } + + private void ReadJSONObjectStart() + { + context.Read(); + ReadJSONSyntaxChar(LBRACE); + PushContext(new JSONPairContext(this)); + } + + private void ReadJSONObjectEnd() + { + ReadJSONSyntaxChar(RBRACE); + PopContext(); + } + + private void ReadJSONArrayStart() + { + context.Read(); + ReadJSONSyntaxChar(LBRACKET); + PushContext(new JSONListContext(this)); + } + + private void ReadJSONArrayEnd() + { + ReadJSONSyntaxChar(RBRACKET); + PopContext(); + } + + public override TMessage ReadMessageBegin() + { + TMessage message = new TMessage(); + ReadJSONArrayStart(); + if (ReadJSONInteger() != VERSION) + { + throw new TProtocolException(TProtocolException.BAD_VERSION, + "Message contained bad version."); + } + + var buf = ReadJSONString(false); + message.Name = utf8Encoding.GetString(buf, 0, buf.Length); + message.Type = (TMessageType)ReadJSONInteger(); + message.SeqID = (int)ReadJSONInteger(); + return message; + } + + public override void ReadMessageEnd() + { + ReadJSONArrayEnd(); + } + + public override TStruct ReadStructBegin() + { + ReadJSONObjectStart(); + return new TStruct(); + } + + public override void ReadStructEnd() + { + ReadJSONObjectEnd(); + } + + public override TField ReadFieldBegin() + { + TField field = new TField(); + byte ch = reader.Peek(); + if (ch == RBRACE[0]) + { + field.Type = TType.Stop; + } + else + { + field.ID = (short)ReadJSONInteger(); + ReadJSONObjectStart(); + field.Type = GetTypeIDForTypeName(ReadJSONString(false)); + } + return field; + } + + public override void ReadFieldEnd() + { + ReadJSONObjectEnd(); + } + + public override TMap ReadMapBegin() + { + TMap map = new TMap(); + ReadJSONArrayStart(); + map.KeyType = GetTypeIDForTypeName(ReadJSONString(false)); + map.ValueType = GetTypeIDForTypeName(ReadJSONString(false)); + map.Count = (int)ReadJSONInteger(); + ReadJSONObjectStart(); + return map; + } + + public override void ReadMapEnd() + { + ReadJSONObjectEnd(); + ReadJSONArrayEnd(); + } + + public override TList ReadListBegin() + { + TList list = new TList(); + ReadJSONArrayStart(); + list.ElementType = GetTypeIDForTypeName(ReadJSONString(false)); + list.Count = (int)ReadJSONInteger(); + return list; + } + + public override void ReadListEnd() + { + ReadJSONArrayEnd(); + } + + public override TSet ReadSetBegin() + { + TSet set = new TSet(); + ReadJSONArrayStart(); + set.ElementType = GetTypeIDForTypeName(ReadJSONString(false)); + set.Count = (int)ReadJSONInteger(); + return set; + } + + public override void ReadSetEnd() + { + ReadJSONArrayEnd(); + } + + public override bool ReadBool() + { + return (ReadJSONInteger() == 0 ? false : true); + } + + public override sbyte ReadByte() + { + return (sbyte)ReadJSONInteger(); + } + + public override short ReadI16() + { + return (short)ReadJSONInteger(); + } + + public override int ReadI32() + { + return (int)ReadJSONInteger(); + } + + public override long ReadI64() + { + return (long)ReadJSONInteger(); + } + + public override double ReadDouble() + { + return ReadJSONDouble(); + } + + public override string ReadString() + { + var buf = ReadJSONString(false); + return utf8Encoding.GetString(buf, 0, buf.Length); + } + + public override byte[] ReadBinary() + { + return ReadJSONBase64(); + } + + public override Guid ReadUuid() + { + return new Guid( ReadString()); + } + + } +} diff --git a/lib/csharp/src/Protocol/TList.cs b/lib/csharp/src/Protocol/TList.cs new file mode 100644 index 00000000000..0c8f2144a44 --- /dev/null +++ b/lib/csharp/src/Protocol/TList.cs @@ -0,0 +1,54 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Thrift.Protocol +{ + public struct TList + { + private TType elementType; + private int count; + + public TList(TType elementType, int count) + :this() + { + this.elementType = elementType; + this.count = count; + } + + public TType ElementType + { + get { return elementType; } + set { elementType = value; } + } + + public int Count + { + get { return count; } + set { count = value; } + } + } +} diff --git a/lib/csharp/src/Protocol/TMap.cs b/lib/csharp/src/Protocol/TMap.cs new file mode 100644 index 00000000000..aba9d3a95ec --- /dev/null +++ b/lib/csharp/src/Protocol/TMap.cs @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Thrift.Protocol +{ + public struct TMap + { + private TType keyType; + private TType valueType; + private int count; + + public TMap(TType keyType, TType valueType, int count) + :this() + { + this.keyType = keyType; + this.valueType = valueType; + this.count = count; + } + + public TType KeyType + { + get { return keyType; } + set { keyType = value; } + } + + public TType ValueType + { + get { return valueType; } + set { valueType = value; } + } + + public int Count + { + get { return count; } + set { count = value; } + } + } +} diff --git a/lib/csharp/src/Protocol/TMessage.cs b/lib/csharp/src/Protocol/TMessage.cs new file mode 100644 index 00000000000..348263c375d --- /dev/null +++ b/lib/csharp/src/Protocol/TMessage.cs @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Thrift.Protocol +{ + public struct TMessage + { + private string name; + private TMessageType type; + private int seqID; + + public TMessage(string name, TMessageType type, int seqid) + :this() + { + this.name = name; + this.type = type; + this.seqID = seqid; + } + + public string Name + { + get { return name; } + set { name = value; } + } + + public TMessageType Type + { + get { return type; } + set { type = value; } + } + + public int SeqID + { + get { return seqID; } + set { seqID = value; } + } + } +} diff --git a/lib/csharp/src/Protocol/TMessageType.cs b/lib/csharp/src/Protocol/TMessageType.cs new file mode 100644 index 00000000000..c7091fede83 --- /dev/null +++ b/lib/csharp/src/Protocol/TMessageType.cs @@ -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. + */ + +using System; + +namespace Thrift.Protocol +{ + public enum TMessageType + { + Call = 1, + Reply = 2, + Exception = 3, + Oneway = 4 + } +} diff --git a/lib/csharp/src/Protocol/TMultiplexedProcessor.cs b/lib/csharp/src/Protocol/TMultiplexedProcessor.cs new file mode 100644 index 00000000000..aa91c527fed --- /dev/null +++ b/lib/csharp/src/Protocol/TMultiplexedProcessor.cs @@ -0,0 +1,183 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Text; +using Thrift.Transport; +using System.Collections.Generic; +using System.IO; + +namespace Thrift.Protocol +{ + /// + /// is a allowing a single + /// to provide multiple services. + /// + /// To do so, you instantiate the processor and then register additional processors with it, + /// as shown in the following example: + /// + /// + /// 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(); + /// + /// + public class TMultiplexedProcessor : TProcessor + { + private Dictionary ServiceProcessorMap = new Dictionary(); + + /// + /// 'Register' a service with this TMultiplexedProcessor. This allows us to broker + /// requests to individual services by using the service name to select them at request time. + /// + /// Args: + /// - serviceName Name of a service, has to be identical to the name + /// declared in the Thrift IDL, e.g. "WeatherReport". + /// - processor Implementation of a service, usually referred to as "handlers", + /// e.g. WeatherReportHandler implementing WeatherReport.Iface. + /// + public void RegisterProcessor(string serviceName, TProcessor processor) + { + ServiceProcessorMap.Add(serviceName, processor); + } + + + private void Fail(TProtocol oprot, TMessage message, TApplicationException.ExceptionType extype, string etxt) + { + TApplicationException appex = new TApplicationException(extype, etxt); + + TMessage newMessage = new TMessage(message.Name, TMessageType.Exception, message.SeqID); + + oprot.WriteMessageBegin(newMessage); + appex.Write(oprot); + oprot.WriteMessageEnd(); + oprot.Transport.Flush(); + } + + + /// + /// This implementation of process performs the following steps: + /// + /// - Read the beginning of the message. + /// - Extract the service name from the message. + /// - Using the service name to locate the appropriate processor. + /// - Dispatch to the processor, with a decorated instance of TProtocol + /// that allows readMessageBegin() to return the original TMessage. + /// + /// Throws an exception if + /// - the message type is not CALL or ONEWAY, + /// - the service name was not found in the message, or + /// - the service name has not been RegisterProcessor()ed. + /// + public bool Process(TProtocol iprot, TProtocol oprot) + { + /* 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. */ + + try + { + TMessage message = iprot.ReadMessageBegin(); + + if ((message.Type != TMessageType.Call) && (message.Type != TMessageType.Oneway)) + { + Fail(oprot, message, + TApplicationException.ExceptionType.InvalidMessageType, + "Message type CALL or ONEWAY expected"); + return false; + } + + // Extract the service name + int index = message.Name.IndexOf(TMultiplexedProtocol.SEPARATOR); + if (index < 0) + { + Fail(oprot, message, + TApplicationException.ExceptionType.InvalidProtocol, + "Service name not found in message name: " + message.Name + ". " + + "Did you forget to use a TMultiplexProtocol in your client?"); + return false; + } + + // Create a new TMessage, something that can be consumed by any TProtocol + string serviceName = message.Name.Substring(0, index); + TProcessor actualProcessor; + if (!ServiceProcessorMap.TryGetValue(serviceName, out actualProcessor)) + { + Fail(oprot, message, + TApplicationException.ExceptionType.InternalError, + "Service name not found: " + serviceName + ". " + + "Did you forget to call RegisterProcessor()?"); + return false; + } + + // Create a new TMessage, removing the service name + TMessage newMessage = 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, newMessage), oprot); + + } + catch (IOException) + { + return false; // similar to all other processors + } + + } + + /// + /// 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 class StoredMessageProtocol : TProtocolDecorator + { + TMessage MsgBegin; + + public StoredMessageProtocol(TProtocol protocol, TMessage messageBegin) + : base(protocol) + { + this.MsgBegin = messageBegin; + } + + public override TMessage ReadMessageBegin() + { + return MsgBegin; + } + } + + } +} diff --git a/lib/csharp/src/Protocol/TMultiplexedProtocol.cs b/lib/csharp/src/Protocol/TMultiplexedProtocol.cs new file mode 100644 index 00000000000..1bd420fcc20 --- /dev/null +++ b/lib/csharp/src/Protocol/TMultiplexedProtocol.cs @@ -0,0 +1,103 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Text; +using Thrift.Transport; +using System.Collections.Generic; + +namespace Thrift.Protocol +{ + + /// + /// TMultiplexedProtocol 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. + /// + /// NOTE: THIS IS NOT TO BE USED BY SERVERS. + /// On the server, use TMultiplexedProcessor to handle requests from a multiplexing client. + /// + /// This example uses a single socket transport to invoke two services: + /// + /// 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()); + /// + /// + public class TMultiplexedProtocol : TProtocolDecorator + { + + /// + /// Used to delimit the service name from the function name. + /// + public static string SEPARATOR = ":"; + + private string ServiceName; + + /// + /// Wrap the specified protocol, allowing it to be used to communicate with a + /// multiplexing server. The is required as it is + /// prepended to the message header so that the multiplexing server can broker + /// the function call to the proper service. + /// + /// Your communication protocol of choice, e.g. . + /// The service name of the service communicating via this protocol. + public TMultiplexedProtocol(TProtocol protocol, string serviceName) + : base(protocol) + { + ServiceName = serviceName; + } + + /// + /// Prepends the service name to the function name, separated by TMultiplexedProtocol.SEPARATOR. + /// + /// The original message. + public override void WriteMessageBegin(TMessage tMessage) + { + switch (tMessage.Type) + { + case TMessageType.Call: + case TMessageType.Oneway: + base.WriteMessageBegin(new TMessage( + ServiceName + SEPARATOR + tMessage.Name, + tMessage.Type, + tMessage.SeqID)); + break; + + default: + base.WriteMessageBegin(tMessage); + break; + } + } + } +} diff --git a/lib/csharp/src/Protocol/TProtocol.cs b/lib/csharp/src/Protocol/TProtocol.cs new file mode 100644 index 00000000000..e90216e4bc0 --- /dev/null +++ b/lib/csharp/src/Protocol/TProtocol.cs @@ -0,0 +1,144 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Text; +using Thrift.Transport; + +namespace Thrift.Protocol +{ + public abstract class TProtocol : IDisposable + { + private const int DEFAULT_RECURSION_DEPTH = 64; + + protected TTransport trans; + protected int recursionLimit; + protected int recursionDepth; + + protected TProtocol(TTransport trans) + { + this.trans = trans; + this.recursionLimit = DEFAULT_RECURSION_DEPTH; + this.recursionDepth = 0; + } + + public TTransport Transport + { + get { return trans; } + } + + public int RecursionLimit + { + get { return recursionLimit; } + set { recursionLimit = value; } + } + + public void IncrementRecursionDepth() + { + if (recursionDepth < recursionLimit) + ++recursionDepth; + else + throw new TProtocolException(TProtocolException.DEPTH_LIMIT, "Depth limit exceeded"); + } + + public void DecrementRecursionDepth() + { + --recursionDepth; + } + + #region " IDisposable Support " + private bool _IsDisposed; + + // IDisposable + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (!_IsDisposed) + { + if (disposing) + { + if (trans is IDisposable) + (trans as IDisposable).Dispose(); + } + } + _IsDisposed = true; + } + #endregion + + public abstract void WriteMessageBegin(TMessage message); + public abstract void WriteMessageEnd(); + public abstract void WriteStructBegin(TStruct struc); + public abstract void WriteStructEnd(); + public abstract void WriteFieldBegin(TField field); + public abstract void WriteFieldEnd(); + public abstract void WriteFieldStop(); + public abstract void WriteMapBegin(TMap map); + public abstract void WriteMapEnd(); + public abstract void WriteListBegin(TList list); + public abstract void WriteListEnd(); + public abstract void WriteSetBegin(TSet set); + public abstract void WriteSetEnd(); + public abstract void WriteBool(bool b); + public abstract void WriteByte(sbyte b); + public abstract void WriteI16(short i16); + public abstract void WriteI32(int i32); + public abstract void WriteI64(long i64); + public abstract void WriteDouble(double d); + public virtual void WriteString(string s) + { + WriteBinary(Encoding.UTF8.GetBytes(s)); + } + public abstract void WriteBinary(byte[] b); + public abstract void WriteUuid(Guid uuid); + + public abstract TMessage ReadMessageBegin(); + public abstract void ReadMessageEnd(); + public abstract TStruct ReadStructBegin(); + public abstract void ReadStructEnd(); + public abstract TField ReadFieldBegin(); + public abstract void ReadFieldEnd(); + public abstract TMap ReadMapBegin(); + public abstract void ReadMapEnd(); + public abstract TList ReadListBegin(); + public abstract void ReadListEnd(); + public abstract TSet ReadSetBegin(); + public abstract void ReadSetEnd(); + public abstract bool ReadBool(); + public abstract sbyte ReadByte(); + public abstract short ReadI16(); + public abstract int ReadI32(); + public abstract long ReadI64(); + public abstract double ReadDouble(); + public virtual string ReadString() + { + var buf = ReadBinary(); + return Encoding.UTF8.GetString(buf, 0, buf.Length); + } + public abstract byte[] ReadBinary(); + public abstract Guid ReadUuid(); + } +} diff --git a/lib/csharp/src/Protocol/TProtocolDecorator.cs b/lib/csharp/src/Protocol/TProtocolDecorator.cs new file mode 100644 index 00000000000..09809eb79a9 --- /dev/null +++ b/lib/csharp/src/Protocol/TProtocolDecorator.cs @@ -0,0 +1,271 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Text; +using Thrift.Transport; +using System.Collections.Generic; + +namespace Thrift.Protocol +{ + /// + /// forwards all requests to an enclosed 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 . + /// + /// See p.175 of Design Patterns (by Gamma et al.) + /// + /// + public abstract class TProtocolDecorator : TProtocol + { + private TProtocol WrappedProtocol; + + /// + /// Encloses the specified protocol. + /// + /// All operations will be forward to this protocol. Must be non-null. + public TProtocolDecorator(TProtocol protocol) + : base(protocol.Transport) + { + + WrappedProtocol = protocol; + } + + public override void WriteMessageBegin(TMessage tMessage) + { + WrappedProtocol.WriteMessageBegin(tMessage); + } + + public override void WriteMessageEnd() + { + WrappedProtocol.WriteMessageEnd(); + } + + public override void WriteStructBegin(TStruct tStruct) + { + WrappedProtocol.WriteStructBegin(tStruct); + } + + public override void WriteStructEnd() + { + WrappedProtocol.WriteStructEnd(); + } + + public override void WriteFieldBegin(TField tField) + { + WrappedProtocol.WriteFieldBegin(tField); + } + + public override void WriteFieldEnd() + { + WrappedProtocol.WriteFieldEnd(); + } + + public override void WriteFieldStop() + { + WrappedProtocol.WriteFieldStop(); + } + + public override void WriteMapBegin(TMap tMap) + { + WrappedProtocol.WriteMapBegin(tMap); + } + + public override void WriteMapEnd() + { + WrappedProtocol.WriteMapEnd(); + } + + public override void WriteListBegin(TList tList) + { + WrappedProtocol.WriteListBegin(tList); + } + + public override void WriteListEnd() + { + WrappedProtocol.WriteListEnd(); + } + + public override void WriteSetBegin(TSet tSet) + { + WrappedProtocol.WriteSetBegin(tSet); + } + + public override void WriteSetEnd() + { + WrappedProtocol.WriteSetEnd(); + } + + public override void WriteBool(bool b) + { + WrappedProtocol.WriteBool(b); + } + + public override void WriteByte(sbyte b) + { + WrappedProtocol.WriteByte(b); + } + + public override void WriteI16(short i) + { + WrappedProtocol.WriteI16(i); + } + + public override void WriteI32(int i) + { + WrappedProtocol.WriteI32(i); + } + + public override void WriteI64(long l) + { + WrappedProtocol.WriteI64(l); + } + + public override void WriteDouble(double v) + { + WrappedProtocol.WriteDouble(v); + } + + public override void WriteString(string s) + { + WrappedProtocol.WriteString(s); + } + + public override void WriteBinary(byte[] bytes) + { + WrappedProtocol.WriteBinary(bytes); + } + + public override void WriteUuid(Guid uuid) + { + WrappedProtocol.WriteUuid(uuid); + } + + public override TMessage ReadMessageBegin() + { + return WrappedProtocol.ReadMessageBegin(); + } + + public override void ReadMessageEnd() + { + WrappedProtocol.ReadMessageEnd(); + } + + public override TStruct ReadStructBegin() + { + return WrappedProtocol.ReadStructBegin(); + } + + public override void ReadStructEnd() + { + WrappedProtocol.ReadStructEnd(); + } + + public override TField ReadFieldBegin() + { + return WrappedProtocol.ReadFieldBegin(); + } + + public override void ReadFieldEnd() + { + WrappedProtocol.ReadFieldEnd(); + } + + public override TMap ReadMapBegin() + { + return WrappedProtocol.ReadMapBegin(); + } + + public override void ReadMapEnd() + { + WrappedProtocol.ReadMapEnd(); + } + + public override TList ReadListBegin() + { + return WrappedProtocol.ReadListBegin(); + } + + public override void ReadListEnd() + { + WrappedProtocol.ReadListEnd(); + } + + public override TSet ReadSetBegin() + { + return WrappedProtocol.ReadSetBegin(); + } + + public override void ReadSetEnd() + { + WrappedProtocol.ReadSetEnd(); + } + + public override bool ReadBool() + { + return WrappedProtocol.ReadBool(); + } + + public override sbyte ReadByte() + { + return WrappedProtocol.ReadByte(); + } + + public override short ReadI16() + { + return WrappedProtocol.ReadI16(); + } + + public override int ReadI32() + { + return WrappedProtocol.ReadI32(); + } + + public override long ReadI64() + { + return WrappedProtocol.ReadI64(); + } + + public override double ReadDouble() + { + return WrappedProtocol.ReadDouble(); + } + + public override string ReadString() + { + return WrappedProtocol.ReadString(); + } + + public override byte[] ReadBinary() + { + return WrappedProtocol.ReadBinary(); + } + + public override Guid ReadUuid() + { + return WrappedProtocol.ReadUuid(); + } + } + +} diff --git a/lib/csharp/src/Protocol/TProtocolException.cs b/lib/csharp/src/Protocol/TProtocolException.cs new file mode 100644 index 00000000000..7bef236857b --- /dev/null +++ b/lib/csharp/src/Protocol/TProtocolException.cs @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; + +namespace Thrift.Protocol +{ + public class TProtocolException : TException + { + public const int UNKNOWN = 0; + public const int INVALID_DATA = 1; + public const int NEGATIVE_SIZE = 2; + public const int SIZE_LIMIT = 3; + public const int BAD_VERSION = 4; + public const int NOT_IMPLEMENTED = 5; + public const int DEPTH_LIMIT = 6; + + protected int type_ = UNKNOWN; + + public TProtocolException() + : base() + { + } + + public TProtocolException(int type, Exception inner = null) + : base(string.Empty, inner) + { + type_ = type; + } + + public TProtocolException(int type, string message, Exception inner = null) + : base(message, inner) + { + type_ = type; + } + + public TProtocolException(string message, Exception inner = null) + : base(message, inner) + { + } + + public int getType() + { + return type_; + } + } +} diff --git a/lib/csharp/src/Protocol/TProtocolFactory.cs b/lib/csharp/src/Protocol/TProtocolFactory.cs new file mode 100644 index 00000000000..71360a19ae9 --- /dev/null +++ b/lib/csharp/src/Protocol/TProtocolFactory.cs @@ -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. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using Thrift.Transport; + +namespace Thrift.Protocol +{ + public interface TProtocolFactory + { + TProtocol GetProtocol(TTransport trans); + } +} diff --git a/lib/csharp/src/Protocol/TProtocolUtil.cs b/lib/csharp/src/Protocol/TProtocolUtil.cs new file mode 100644 index 00000000000..d995c6ce76b --- /dev/null +++ b/lib/csharp/src/Protocol/TProtocolUtil.cs @@ -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. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; + +namespace Thrift.Protocol +{ + public static class TProtocolUtil + { + public static void Skip(TProtocol prot, TType type) + { + prot.IncrementRecursionDepth(); + try + { + 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: + // Don't try to decode the string, just skip it. + prot.ReadBinary(); + break; + case TType.Struct: + prot.ReadStructBegin(); + while (true) + { + TField field = prot.ReadFieldBegin(); + if (field.Type == TType.Stop) + { + break; + } + Skip(prot, field.Type); + prot.ReadFieldEnd(); + } + prot.ReadStructEnd(); + break; + case TType.Map: + TMap map = prot.ReadMapBegin(); + for (int i = 0; i < map.Count; i++) + { + Skip(prot, map.KeyType); + Skip(prot, map.ValueType); + } + prot.ReadMapEnd(); + break; + case TType.Set: + TSet set = prot.ReadSetBegin(); + for (int i = 0; i < set.Count; i++) + { + Skip(prot, set.ElementType); + } + prot.ReadSetEnd(); + break; + case TType.List: + TList list = prot.ReadListBegin(); + for (int i = 0; i < list.Count; i++) + { + Skip(prot, list.ElementType); + } + prot.ReadListEnd(); + break; + default: + throw new TProtocolException(TProtocolException.INVALID_DATA, "Unknown data type " + type.ToString("d")); + } + } + finally + { + prot.DecrementRecursionDepth(); + } + } + } +} diff --git a/lib/csharp/src/Protocol/TSet.cs b/lib/csharp/src/Protocol/TSet.cs new file mode 100644 index 00000000000..a918ab53729 --- /dev/null +++ b/lib/csharp/src/Protocol/TSet.cs @@ -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. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Thrift.Protocol +{ + public struct TSet + { + private TType elementType; + private int count; + + public TSet(TType elementType, int count) + :this() + { + this.elementType = elementType; + this.count = count; + } + + public TSet(TList list) + : this(list.ElementType, list.Count) + { + } + + public TType ElementType + { + get { return elementType; } + set { elementType = value; } + } + + public int Count + { + get { return count; } + set { count = value; } + } + } +} diff --git a/lib/csharp/src/Protocol/TStruct.cs b/lib/csharp/src/Protocol/TStruct.cs new file mode 100644 index 00000000000..f4844a4c9d0 --- /dev/null +++ b/lib/csharp/src/Protocol/TStruct.cs @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Thrift.Protocol +{ + public struct TStruct + { + private string name; + + public TStruct(string name) + :this() + { + this.name = name; + } + + public string Name + { + get { return name; } + set { name = value; } + } + } +} diff --git a/lib/csharp/src/Protocol/TType.cs b/lib/csharp/src/Protocol/TType.cs new file mode 100644 index 00000000000..27865ba5632 --- /dev/null +++ b/lib/csharp/src/Protocol/TType.cs @@ -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. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; + +namespace Thrift.Protocol +{ + public enum TType : byte + { + Stop = 0, + Void = 1, + Bool = 2, + Byte = 3, + Double = 4, + I16 = 6, + I32 = 8, + I64 = 10, + String = 11, + Struct = 12, + Map = 13, + Set = 14, + List = 15, + Uuid = 16 + } +} diff --git a/lib/csharp/src/Server/TServer.cs b/lib/csharp/src/Server/TServer.cs new file mode 100644 index 00000000000..2bc04f3a045 --- /dev/null +++ b/lib/csharp/src/Server/TServer.cs @@ -0,0 +1,155 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using Thrift.Protocol; +using Thrift.Transport; +using System.IO; + +namespace Thrift.Server +{ + public abstract class TServer + { + //Attributes + protected TProcessorFactory processorFactory; + protected TServerTransport serverTransport; + protected TTransportFactory inputTransportFactory; + protected TTransportFactory outputTransportFactory; + protected TProtocolFactory inputProtocolFactory; + protected TProtocolFactory outputProtocolFactory; + protected TServerEventHandler serverEventHandler = null; + + //Methods + public void setEventHandler(TServerEventHandler seh) + { + serverEventHandler = seh; + } + public TServerEventHandler getEventHandler() + { + return serverEventHandler; + } + + //Log delegation + public delegate void LogDelegate(string str); + private LogDelegate _logDelegate; + protected LogDelegate logDelegate + { + get { return _logDelegate; } + set { _logDelegate = (value != null) ? value : DefaultLogDelegate; } + } + protected static void DefaultLogDelegate(string s) + { + Console.Error.WriteLine(s); + } + + //Construction + public TServer(TProcessor processor, + TServerTransport serverTransport) + : this(processor, serverTransport, + new TTransportFactory(), + new TTransportFactory(), + new TBinaryProtocol.Factory(), + new TBinaryProtocol.Factory(), + DefaultLogDelegate) + { + } + + public TServer(TProcessor processor, + TServerTransport serverTransport, + LogDelegate logDelegate) + : this(processor, + serverTransport, + new TTransportFactory(), + new TTransportFactory(), + new TBinaryProtocol.Factory(), + new TBinaryProtocol.Factory(), + logDelegate) + { + } + + public TServer(TProcessor processor, + TServerTransport serverTransport, + TTransportFactory transportFactory) + : this(processor, + serverTransport, + transportFactory, + transportFactory, + new TBinaryProtocol.Factory(), + new TBinaryProtocol.Factory(), + DefaultLogDelegate) + { + } + + public TServer(TProcessor processor, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory) + : this(processor, + serverTransport, + transportFactory, + transportFactory, + protocolFactory, + protocolFactory, + DefaultLogDelegate) + { + } + + public TServer(TProcessor processor, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + LogDelegate logDelegate) + { + this.processorFactory = new TSingletonProcessorFactory(processor); + this.serverTransport = serverTransport; + this.inputTransportFactory = inputTransportFactory; + this.outputTransportFactory = outputTransportFactory; + this.inputProtocolFactory = inputProtocolFactory; + this.outputProtocolFactory = outputProtocolFactory; + this.logDelegate = (logDelegate != null) ? logDelegate : DefaultLogDelegate; + } + + public TServer(TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + LogDelegate logDelegate) + { + this.processorFactory = processorFactory; + this.serverTransport = serverTransport; + this.inputTransportFactory = inputTransportFactory; + this.outputTransportFactory = outputTransportFactory; + this.inputProtocolFactory = inputProtocolFactory; + this.outputProtocolFactory = outputProtocolFactory; + this.logDelegate = (logDelegate != null) ? logDelegate : DefaultLogDelegate; + } + + //Abstract Interface + public abstract void Serve(); + public abstract void Stop(); + } +} diff --git a/lib/csharp/src/Server/TServerEventHandler.cs b/lib/csharp/src/Server/TServerEventHandler.cs new file mode 100644 index 00000000000..e81efc6af9b --- /dev/null +++ b/lib/csharp/src/Server/TServerEventHandler.cs @@ -0,0 +1,53 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; + +namespace Thrift.Server +{ + /// + /// Interface implemented by server users to handle events from the server. + /// + public interface TServerEventHandler + { + /// + /// Called before the server begins. + /// + void preServe(); + + /// + /// Called when a new client has connected and is about to being processing. + /// + object createContext(Thrift.Protocol.TProtocol input, Thrift.Protocol.TProtocol output); + + /// + /// Called when a client has finished request-handling to delete server context. + /// + void deleteContext(object serverContext, Thrift.Protocol.TProtocol input, Thrift.Protocol.TProtocol output); + + /// + /// Called when a client is about to call the processor. + /// + void processContext(object serverContext, Thrift.Transport.TTransport transport); + }; +} diff --git a/lib/csharp/src/Server/TSimpleServer.cs b/lib/csharp/src/Server/TSimpleServer.cs new file mode 100644 index 00000000000..4e7ea96f4af --- /dev/null +++ b/lib/csharp/src/Server/TSimpleServer.cs @@ -0,0 +1,180 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using Thrift.Transport; +using Thrift.Protocol; + +namespace Thrift.Server +{ + /// + /// Simple single-threaded server for testing. + /// + public class TSimpleServer : TServer + { + private bool stop = false; + + public TSimpleServer(TProcessor processor, + TServerTransport serverTransport) + : base(processor, serverTransport, new TTransportFactory(), new TTransportFactory(), new TBinaryProtocol.Factory(), new TBinaryProtocol.Factory(), DefaultLogDelegate) + { + } + + public TSimpleServer(TProcessor processor, + TServerTransport serverTransport, + LogDelegate logDel) + : base(processor, serverTransport, new TTransportFactory(), new TTransportFactory(), new TBinaryProtocol.Factory(), new TBinaryProtocol.Factory(), logDel) + { + } + + public TSimpleServer(TProcessor processor, + TServerTransport serverTransport, + TTransportFactory transportFactory) + : base(processor, + serverTransport, + transportFactory, + transportFactory, + new TBinaryProtocol.Factory(), + new TBinaryProtocol.Factory(), + DefaultLogDelegate) + { + } + + public TSimpleServer(TProcessor processor, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory) + : base(processor, + serverTransport, + transportFactory, + transportFactory, + protocolFactory, + protocolFactory, + DefaultLogDelegate) + { + } + + public TSimpleServer(TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory) + : base(processorFactory, + serverTransport, + transportFactory, + transportFactory, + protocolFactory, + protocolFactory, + DefaultLogDelegate) + { + } + + public override void Serve() + { + try + { + serverTransport.Listen(); + } + catch (TTransportException ttx) + { + logDelegate(ttx.ToString()); + return; + } + + //Fire the preServe server event when server is up but before any client connections + if (serverEventHandler != null) + serverEventHandler.preServe(); + + while (!stop) + { + TProcessor processor = null; + TTransport client = null; + TTransport inputTransport = null; + TTransport outputTransport = null; + TProtocol inputProtocol = null; + TProtocol outputProtocol = null; + object connectionContext = null; + try + { + using (client = serverTransport.Accept()) + { + processor = processorFactory.GetProcessor(client); + if (client != null) + { + using (inputTransport = inputTransportFactory.GetTransport(client)) + { + using (outputTransport = outputTransportFactory.GetTransport(client)) + { + inputProtocol = inputProtocolFactory.GetProtocol(inputTransport); + outputProtocol = outputProtocolFactory.GetProtocol(outputTransport); + + //Recover event handler (if any) and fire createContext server event when a client connects + if (serverEventHandler != null) + connectionContext = serverEventHandler.createContext(inputProtocol, outputProtocol); + + //Process client requests until client disconnects + while (!stop) + { + if (!inputTransport.Peek()) + break; + + //Fire processContext server event + //N.B. This is the pattern implemented in C++ and the event fires provisionally. + //That is to say it may be many minutes between the event firing and the client request + //actually arriving or the client may hang up without ever makeing a request. + if (serverEventHandler != null) + serverEventHandler.processContext(connectionContext, inputTransport); + //Process client request (blocks until transport is readable) + if (!processor.Process(inputProtocol, outputProtocol)) + break; + } + } + } + } + } + } + catch (TTransportException ttx) + { + if (!stop || ttx.Type != TTransportException.ExceptionType.Interrupted) + { + logDelegate(ttx.ToString()); + } + } + catch (Exception x) + { + //Unexpected + logDelegate(x.ToString()); + } + + //Fire deleteContext server event after client disconnects + if (serverEventHandler != null) + serverEventHandler.deleteContext(connectionContext, inputProtocol, outputProtocol); + } + } + + public override void Stop() + { + stop = true; + serverTransport.Close(); + } + } +} diff --git a/lib/csharp/src/Server/TThreadPoolServer.cs b/lib/csharp/src/Server/TThreadPoolServer.cs new file mode 100644 index 00000000000..a494ce7bddd --- /dev/null +++ b/lib/csharp/src/Server/TThreadPoolServer.cs @@ -0,0 +1,295 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Threading; +using Thrift.Protocol; +using Thrift.Transport; + +namespace Thrift.Server +{ + /// + /// Server that uses C# built-in ThreadPool to spawn threads when handling requests. + /// + public class TThreadPoolServer : TServer + { + private const int DEFAULT_MIN_THREADS = -1; // use .NET ThreadPool defaults + private const int DEFAULT_MAX_THREADS = -1; // use .NET ThreadPool defaults + private volatile bool stop = false; + + public struct Configuration + { + public int MinWorkerThreads; + public int MaxWorkerThreads; + public int MinIOThreads; + public int MaxIOThreads; + + public Configuration(int min = DEFAULT_MIN_THREADS, int max = DEFAULT_MAX_THREADS) + { + MinWorkerThreads = min; + MaxWorkerThreads = max; + MinIOThreads = min; + MaxIOThreads = max; + } + + public Configuration(int minWork, int maxWork, int minIO, int maxIO) + { + MinWorkerThreads = minWork; + MaxWorkerThreads = maxWork; + MinIOThreads = minIO; + MaxIOThreads = maxIO; + } + } + + public TThreadPoolServer(TProcessor processor, TServerTransport serverTransport) + : this(new TSingletonProcessorFactory(processor), serverTransport, + new TTransportFactory(), new TTransportFactory(), + new TBinaryProtocol.Factory(), new TBinaryProtocol.Factory(), + new Configuration(), DefaultLogDelegate) + { + } + + public TThreadPoolServer(TProcessor processor, TServerTransport serverTransport, LogDelegate logDelegate) + : this(new TSingletonProcessorFactory(processor), serverTransport, + new TTransportFactory(), new TTransportFactory(), + new TBinaryProtocol.Factory(), new TBinaryProtocol.Factory(), + new Configuration(), logDelegate) + { + } + + public TThreadPoolServer(TProcessor processor, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory) + : this(new TSingletonProcessorFactory(processor), serverTransport, + transportFactory, transportFactory, + protocolFactory, protocolFactory, + new Configuration(), DefaultLogDelegate) + { + } + + public TThreadPoolServer(TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory) + : this(processorFactory, serverTransport, + transportFactory, transportFactory, + protocolFactory, protocolFactory, + new Configuration(), DefaultLogDelegate) + { + } + + public TThreadPoolServer(TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + int minThreadPoolThreads, int maxThreadPoolThreads, LogDelegate logDel) + : this(processorFactory, serverTransport, inputTransportFactory, outputTransportFactory, + inputProtocolFactory, outputProtocolFactory, + new Configuration(minThreadPoolThreads, maxThreadPoolThreads), + logDel) + { + } + + public TThreadPoolServer(TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + Configuration threadConfig, + LogDelegate logDel) + : base(processorFactory, serverTransport, inputTransportFactory, outputTransportFactory, + inputProtocolFactory, outputProtocolFactory, logDel) + { + lock (typeof(TThreadPoolServer)) + { + if ((threadConfig.MaxWorkerThreads > 0) || (threadConfig.MaxIOThreads > 0)) + { + int work, comm; + ThreadPool.GetMaxThreads(out work, out comm); + if (threadConfig.MaxWorkerThreads > 0) + work = threadConfig.MaxWorkerThreads; + if (threadConfig.MaxIOThreads > 0) + comm = threadConfig.MaxIOThreads; + if (!ThreadPool.SetMaxThreads(work, comm)) + throw new Exception("Error: could not SetMaxThreads in ThreadPool"); + } + + if ((threadConfig.MinWorkerThreads > 0) || (threadConfig.MinIOThreads > 0)) + { + int work, comm; + ThreadPool.GetMinThreads(out work, out comm); + if (threadConfig.MinWorkerThreads > 0) + work = threadConfig.MinWorkerThreads; + if (threadConfig.MinIOThreads > 0) + comm = threadConfig.MinIOThreads; + if (!ThreadPool.SetMinThreads(work, comm)) + throw new Exception("Error: could not SetMinThreads in ThreadPool"); + } + } + } + + + /// + /// Use new ThreadPool thread for each new client connection. + /// + public override void Serve() + { + try + { + serverTransport.Listen(); + } + catch (TTransportException ttx) + { + logDelegate("Error, could not listen on ServerTransport: " + ttx); + return; + } + + //Fire the preServe server event when server is up but before any client connections + if (serverEventHandler != null) + serverEventHandler.preServe(); + + while (!stop) + { + int failureCount = 0; + try + { + TTransport client = serverTransport.Accept(); + ThreadPool.QueueUserWorkItem(this.Execute, client); + } + catch (TTransportException ttx) + { + if (!stop || ttx.Type != TTransportException.ExceptionType.Interrupted) + { + ++failureCount; + logDelegate(ttx.ToString()); + } + + } + } + + if (stop) + { + try + { + serverTransport.Close(); + } + catch (TTransportException ttx) + { + logDelegate("TServerTransport failed on close: " + ttx.Message); + } + stop = false; + } + } + + /// + /// Loops on processing a client forever + /// threadContext will be a TTransport instance + /// + /// + private void Execute(object threadContext) + { + using (TTransport client = (TTransport)threadContext) + { + TProcessor processor = processorFactory.GetProcessor(client, this); + TTransport inputTransport = null; + TTransport outputTransport = null; + TProtocol inputProtocol = null; + TProtocol outputProtocol = null; + object connectionContext = null; + try + { + try + { + inputTransport = inputTransportFactory.GetTransport(client); + outputTransport = outputTransportFactory.GetTransport(client); + inputProtocol = inputProtocolFactory.GetProtocol(inputTransport); + outputProtocol = outputProtocolFactory.GetProtocol(outputTransport); + + //Recover event handler (if any) and fire createContext server event when a client connects + if (serverEventHandler != null) + connectionContext = serverEventHandler.createContext(inputProtocol, outputProtocol); + + //Process client requests until client disconnects + while (!stop) + { + if (!inputTransport.Peek()) + break; + + //Fire processContext server event + //N.B. This is the pattern implemented in C++ and the event fires provisionally. + //That is to say it may be many minutes between the event firing and the client request + //actually arriving or the client may hang up without ever makeing a request. + if (serverEventHandler != null) + serverEventHandler.processContext(connectionContext, inputTransport); + //Process client request (blocks until transport is readable) + if (!processor.Process(inputProtocol, outputProtocol)) + break; + } + } + catch (TTransportException) + { + //Usually a client disconnect, expected + } + catch (Exception x) + { + //Unexpected + logDelegate("Error: " + x); + } + + //Fire deleteContext server event after client disconnects + if (serverEventHandler != null) + serverEventHandler.deleteContext(connectionContext, inputProtocol, outputProtocol); + + } + finally + { + //Close transports + if (inputTransport != null) + inputTransport.Close(); + if (outputTransport != null) + outputTransport.Close(); + + // disposable stuff should be disposed + if (inputProtocol != null) + inputProtocol.Dispose(); + if (outputProtocol != null) + outputProtocol.Dispose(); + if (inputTransport != null) + inputTransport.Dispose(); + if (outputTransport != null) + outputTransport.Dispose(); + } + } + } + + public override void Stop() + { + stop = true; + serverTransport.Close(); + } + } +} diff --git a/lib/csharp/src/Server/TThreadedServer.cs b/lib/csharp/src/Server/TThreadedServer.cs new file mode 100644 index 00000000000..cc051a33bee --- /dev/null +++ b/lib/csharp/src/Server/TThreadedServer.cs @@ -0,0 +1,282 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System; +using System.Collections.Generic; +using System.Threading; +using Thrift.Collections; +using Thrift.Protocol; +using Thrift.Transport; + +namespace Thrift.Server +{ + /// + /// Server that uses C# threads (as opposed to the ThreadPool) when handling requests. + /// + public class TThreadedServer : TServer + { + private const int DEFAULT_MAX_THREADS = 100; + private volatile bool stop = false; + private readonly int maxThreads; + + private Queue clientQueue; + private THashSet clientThreads; + private object clientLock; + private Thread workerThread; + + public int ClientThreadsCount + { + get { return clientThreads.Count; } + } + + public TThreadedServer(TProcessor processor, TServerTransport serverTransport) + : this(new TSingletonProcessorFactory(processor), serverTransport, + new TTransportFactory(), new TTransportFactory(), + new TBinaryProtocol.Factory(), new TBinaryProtocol.Factory(), + DEFAULT_MAX_THREADS, DefaultLogDelegate) + { + } + + public TThreadedServer(TProcessor processor, TServerTransport serverTransport, LogDelegate logDelegate) + : this(new TSingletonProcessorFactory(processor), serverTransport, + new TTransportFactory(), new TTransportFactory(), + new TBinaryProtocol.Factory(), new TBinaryProtocol.Factory(), + DEFAULT_MAX_THREADS, logDelegate) + { + } + + + public TThreadedServer(TProcessor processor, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory) + : this(new TSingletonProcessorFactory(processor), serverTransport, + transportFactory, transportFactory, + protocolFactory, protocolFactory, + DEFAULT_MAX_THREADS, DefaultLogDelegate) + { + } + + public TThreadedServer(TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory transportFactory, + TProtocolFactory protocolFactory) + : this(processorFactory, serverTransport, + transportFactory, transportFactory, + protocolFactory, protocolFactory, + DEFAULT_MAX_THREADS, DefaultLogDelegate) + { + } + public TThreadedServer(TProcessorFactory processorFactory, + TServerTransport serverTransport, + TTransportFactory inputTransportFactory, + TTransportFactory outputTransportFactory, + TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory, + int maxThreads, LogDelegate logDel) + : base(processorFactory, serverTransport, inputTransportFactory, outputTransportFactory, + inputProtocolFactory, outputProtocolFactory, logDel) + { + this.maxThreads = maxThreads; + clientQueue = new Queue(); + clientLock = new object(); + clientThreads = new THashSet(); + } + + /// + /// Use new Thread for each new client connection. block until numConnections < maxThreads. + /// + public override void Serve() + { + try + { + //start worker thread + workerThread = new Thread(new ThreadStart(Execute)); + workerThread.Start(); + serverTransport.Listen(); + } + catch (TTransportException ttx) + { + logDelegate("Error, could not listen on ServerTransport: " + ttx); + return; + } + + //Fire the preServe server event when server is up but before any client connections + if (serverEventHandler != null) + serverEventHandler.preServe(); + + while (!stop) + { + int failureCount = 0; + try + { + TTransport client = serverTransport.Accept(); + lock (clientLock) + { + clientQueue.Enqueue(client); + Monitor.Pulse(clientLock); + } + } + catch (TTransportException ttx) + { + if (!stop || ttx.Type != TTransportException.ExceptionType.Interrupted) + { + ++failureCount; + logDelegate(ttx.ToString()); + } + + } + } + + if (stop) + { + try + { + serverTransport.Close(); + } + catch (TTransportException ttx) + { + logDelegate("TServeTransport failed on close: " + ttx.Message); + } + stop = false; + } + } + + /// + /// Loops on processing a client forever + /// + private void Execute() + { + while (!stop) + { + TTransport client; + Thread t; + lock (clientLock) + { + //don't dequeue if too many connections + while (clientThreads.Count >= maxThreads) + { + Monitor.Wait(clientLock); + } + + while (clientQueue.Count == 0) + { + Monitor.Wait(clientLock); + } + + client = clientQueue.Dequeue(); + t = new Thread(new ParameterizedThreadStart(ClientWorker)); + clientThreads.Add(t); + } + //start processing requests from client on new thread + t.Start(client); + } + } + + private void ClientWorker(object context) + { + using (TTransport client = (TTransport)context) + { + TProcessor processor = processorFactory.GetProcessor(client); + TTransport inputTransport = null; + TTransport outputTransport = null; + TProtocol inputProtocol = null; + TProtocol outputProtocol = null; + object connectionContext = null; + try + { + try + { + inputTransport = inputTransportFactory.GetTransport(client); + outputTransport = outputTransportFactory.GetTransport(client); + inputProtocol = inputProtocolFactory.GetProtocol(inputTransport); + outputProtocol = outputProtocolFactory.GetProtocol(outputTransport); + + //Recover event handler (if any) and fire createContext server event when a client connects + if (serverEventHandler != null) + connectionContext = serverEventHandler.createContext(inputProtocol, outputProtocol); + + //Process client requests until client disconnects + while (!stop) + { + if (!inputTransport.Peek()) + break; + + //Fire processContext server event + //N.B. This is the pattern implemented in C++ and the event fires provisionally. + //That is to say it may be many minutes between the event firing and the client request + //actually arriving or the client may hang up without ever makeing a request. + if (serverEventHandler != null) + serverEventHandler.processContext(connectionContext, inputTransport); + //Process client request (blocks until transport is readable) + if (!processor.Process(inputProtocol, outputProtocol)) + break; + } + } + catch (TTransportException) + { + //Usually a client disconnect, expected + } + catch (Exception x) + { + //Unexpected + logDelegate("Error: " + x); + } + + //Fire deleteContext server event after client disconnects + if (serverEventHandler != null) + serverEventHandler.deleteContext(connectionContext, inputProtocol, outputProtocol); + + lock (clientLock) + { + clientThreads.Remove(Thread.CurrentThread); + Monitor.Pulse(clientLock); + } + + } + finally + { + //Close transports + if (inputTransport != null) + inputTransport.Close(); + if (outputTransport != null) + outputTransport.Close(); + + // disposable stuff should be disposed + if (inputProtocol != null) + inputProtocol.Dispose(); + if (outputProtocol != null) + outputProtocol.Dispose(); + } + } + } + + public override void Stop() + { + stop = true; + serverTransport.Close(); + //clean up all the threads myself + workerThread.Abort(); + foreach (Thread t in clientThreads) + { + t.Abort(); + } + } + } +} diff --git a/lib/csharp/src/TApplicationException.cs b/lib/csharp/src/TApplicationException.cs new file mode 100644 index 00000000000..8dd7ae57822 --- /dev/null +++ b/lib/csharp/src/TApplicationException.cs @@ -0,0 +1,146 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using Thrift.Protocol; + +namespace Thrift +{ + public class TApplicationException : TException + { + protected ExceptionType type; + + public TApplicationException() + { + } + + public TApplicationException(ExceptionType type) + { + this.type = type; + } + + public TApplicationException(ExceptionType type, string message) + : base(message, null) // TApplicationException is serializable, but we never serialize InnerException + { + this.type = type; + } + + public static TApplicationException Read(TProtocol iprot) + { + TField field; + + string message = null; + ExceptionType type = ExceptionType.Unknown; + + iprot.ReadStructBegin(); + 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 = (ExceptionType)iprot.ReadI32(); + } + else + { + TProtocolUtil.Skip(iprot, field.Type); + } + break; + default: + TProtocolUtil.Skip(iprot, field.Type); + break; + } + + iprot.ReadFieldEnd(); + } + + iprot.ReadStructEnd(); + + return new TApplicationException(type, message); + } + + public void Write(TProtocol oprot) + { + TStruct struc = new TStruct("TApplicationException"); + TField field = new TField(); + + oprot.WriteStructBegin(struc); + + if (!string.IsNullOrEmpty(Message)) + { + field.Name = "message"; + field.Type = TType.String; + field.ID = 1; + oprot.WriteFieldBegin(field); + oprot.WriteString(Message); + oprot.WriteFieldEnd(); + } + + field.Name = "type"; + field.Type = TType.I32; + field.ID = 2; + oprot.WriteFieldBegin(field); + oprot.WriteI32((int)type); + oprot.WriteFieldEnd(); + oprot.WriteFieldStop(); + oprot.WriteStructEnd(); + } + + public enum ExceptionType + { + Unknown, + UnknownMethod, + InvalidMessageType, + WrongMethodName, + BadSequenceID, + MissingResult, + InternalError, + ProtocolError, + InvalidTransform, + InvalidProtocol, + UnsupportedClientType + } + + public ExceptionType Type + { + get { return type; } + } + } +} diff --git a/lib/csharp/src/TAsyncProcessor.cs b/lib/csharp/src/TAsyncProcessor.cs new file mode 100644 index 00000000000..ab432255ba5 --- /dev/null +++ b/lib/csharp/src/TAsyncProcessor.cs @@ -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. + */ + +using System.Threading.Tasks; +using Thrift.Protocol; + +namespace Thrift +{ + /// + /// Processes a message asynchronously. + /// + public interface TAsyncProcessor + { + /// + /// Processes the next part of the message. + /// + /// The input protocol. + /// The output protocol. + /// true if there's more to process, false otherwise. + Task ProcessAsync(TProtocol iprot, TProtocol oprot); + } +} diff --git a/lib/csharp/src/TControllingHandler.cs b/lib/csharp/src/TControllingHandler.cs new file mode 100644 index 00000000000..7b5203a5f2d --- /dev/null +++ b/lib/csharp/src/TControllingHandler.cs @@ -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. + */ + +using System; +using Thrift.Server; + +namespace Thrift +{ + public interface TControllingHandler + { + TServer server { get; set; } + } +} diff --git a/lib/csharp/src/TException.cs b/lib/csharp/src/TException.cs new file mode 100644 index 00000000000..b9fae6e9f19 --- /dev/null +++ b/lib/csharp/src/TException.cs @@ -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. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; + +namespace Thrift +{ + public class TException : Exception + { + public TException() + { + } + + public TException(string message, Exception inner = null) + : base(message, inner) + { + } + + } +} diff --git a/lib/csharp/src/TProcessor.cs b/lib/csharp/src/TProcessor.cs new file mode 100644 index 00000000000..71ce7550892 --- /dev/null +++ b/lib/csharp/src/TProcessor.cs @@ -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. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using Thrift.Protocol; + +namespace Thrift +{ + public interface TProcessor + { + bool Process(TProtocol iprot, TProtocol oprot); + } +} diff --git a/lib/csharp/src/TProcessorFactory.cs b/lib/csharp/src/TProcessorFactory.cs new file mode 100644 index 00000000000..fdf631bc77e --- /dev/null +++ b/lib/csharp/src/TProcessorFactory.cs @@ -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. + */ + +using System; +using Thrift.Server; +using Thrift.Transport; + +namespace Thrift +{ + public interface TProcessorFactory + { + TProcessor GetProcessor(TTransport trans, TServer server = null); + } +} diff --git a/lib/csharp/src/TPrototypeProcessorFactory.cs b/lib/csharp/src/TPrototypeProcessorFactory.cs new file mode 100644 index 00000000000..0b47261fbbe --- /dev/null +++ b/lib/csharp/src/TPrototypeProcessorFactory.cs @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Thrift.Server; +using Thrift.Transport; + +namespace Thrift +{ + public class TPrototypeProcessorFactory : TProcessorFactory where P : TProcessor + { + object[] handlerArgs = null; + + public TPrototypeProcessorFactory() + { + handlerArgs = new object[0]; + } + + public TPrototypeProcessorFactory(params object[] handlerArgs) + { + this.handlerArgs = handlerArgs; + } + + public TProcessor GetProcessor(TTransport trans, TServer server = null) + { + H handler = (H)Activator.CreateInstance(typeof(H), handlerArgs); + + TControllingHandler handlerServerRef = handler as TControllingHandler; + if (handlerServerRef != null) + { + handlerServerRef.server = server; + } + return Activator.CreateInstance(typeof(P), new object[] { handler }) as TProcessor; + } + } +} diff --git a/lib/csharp/src/TSingletonProcessorFactory.cs b/lib/csharp/src/TSingletonProcessorFactory.cs new file mode 100644 index 00000000000..ed2897ba362 --- /dev/null +++ b/lib/csharp/src/TSingletonProcessorFactory.cs @@ -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. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Thrift.Server; +using Thrift.Transport; + +namespace Thrift +{ + public class TSingletonProcessorFactory : TProcessorFactory + { + private readonly TProcessor processor_; + + public TSingletonProcessorFactory(TProcessor processor) + { + processor_ = processor; + } + + public TProcessor GetProcessor(TTransport trans, TServer server = null) + { + return processor_; + } + } +} diff --git a/lib/csharp/src/Thrift.45.csproj b/lib/csharp/src/Thrift.45.csproj new file mode 100644 index 00000000000..eeab4df4d8d --- /dev/null +++ b/lib/csharp/src/Thrift.45.csproj @@ -0,0 +1,139 @@ + + + + + Debug + AnyCPU + {EBCE35DA-CF6A-42BC-A357-A9C09B534299} + Library + Properties + Thrift + Thrift45 + v4.5 + 512 + + + true + portable + false + bin\Debug\ + TRACE;DEBUG;NET45 + prompt + 4 + + + portable + true + bin\Release\ + TRACE;NET45 + prompt + 4 + + + true + + + thrift.snk + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/csharp/src/Thrift.csproj b/lib/csharp/src/Thrift.csproj new file mode 100644 index 00000000000..309452de15d --- /dev/null +++ b/lib/csharp/src/Thrift.csproj @@ -0,0 +1,167 @@ + + + + + Debug + AnyCPU + {499EB63C-D74C-47E8-AE48-A2FC94538E9D} + 9.0.21022 + 2.0 + Library + false + Thrift + v3.5 + 512 + Thrift + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 0.14.0.0 + false + false + true + + + true + portable + false + bin\Debug\ + TRACE;DEBUG + prompt + 4 + AllRules.ruleset + + + portable + true + bin\Release\ + + + prompt + 4 + AllRules.ruleset + + + true + + + thrift.snk + + + + + 3.5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + + + + + + \ No newline at end of file diff --git a/lib/csharp/src/Thrift.sln b/lib/csharp/src/Thrift.sln new file mode 100644 index 00000000000..a29e46882ab --- /dev/null +++ b/lib/csharp/src/Thrift.sln @@ -0,0 +1,47 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thrift", "Thrift.csproj", "{499EB63C-D74C-47E8-AE48-A2FC94538E9D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThriftTest", "..\test\ThriftTest\ThriftTest.csproj", "{48DD757F-CA95-4DD7-BDA4-58DB6F108C2C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThriftMSBuildTask", "..\ThriftMSBuildTask\ThriftMSBuildTask.csproj", "{EC0A0231-66EA-4593-A792-C6CA3BB8668E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thrift.45", "Thrift.45.csproj", "{EBCE35DA-CF6A-42BC-A357-A9C09B534299}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThriftMVCTest", "..\test\ThriftMVCTest\ThriftMVCTest.csproj", "{891B4487-C7BA-427E-BBC8-4C596C229A10}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {499EB63C-D74C-47E8-AE48-A2FC94538E9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {499EB63C-D74C-47E8-AE48-A2FC94538E9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {499EB63C-D74C-47E8-AE48-A2FC94538E9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {499EB63C-D74C-47E8-AE48-A2FC94538E9D}.Release|Any CPU.Build.0 = Release|Any CPU + {48DD757F-CA95-4DD7-BDA4-58DB6F108C2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48DD757F-CA95-4DD7-BDA4-58DB6F108C2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48DD757F-CA95-4DD7-BDA4-58DB6F108C2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48DD757F-CA95-4DD7-BDA4-58DB6F108C2C}.Release|Any CPU.Build.0 = Release|Any CPU + {EC0A0231-66EA-4593-A792-C6CA3BB8668E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EC0A0231-66EA-4593-A792-C6CA3BB8668E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EC0A0231-66EA-4593-A792-C6CA3BB8668E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EC0A0231-66EA-4593-A792-C6CA3BB8668E}.Release|Any CPU.Build.0 = Release|Any CPU + {EBCE35DA-CF6A-42BC-A357-A9C09B534299}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBCE35DA-CF6A-42BC-A357-A9C09B534299}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBCE35DA-CF6A-42BC-A357-A9C09B534299}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBCE35DA-CF6A-42BC-A357-A9C09B534299}.Release|Any CPU.Build.0 = Release|Any CPU + {891B4487-C7BA-427E-BBC8-4C596C229A10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {891B4487-C7BA-427E-BBC8-4C596C229A10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {891B4487-C7BA-427E-BBC8-4C596C229A10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {891B4487-C7BA-427E-BBC8-4C596C229A10}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = Thrift.csproj + EndGlobalSection +EndGlobal diff --git a/lib/csharp/src/Transport/TBufferedTransport.cs b/lib/csharp/src/Transport/TBufferedTransport.cs new file mode 100644 index 00000000000..88709881086 --- /dev/null +++ b/lib/csharp/src/Transport/TBufferedTransport.cs @@ -0,0 +1,194 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System; +using System.IO; + +namespace Thrift.Transport +{ + public class TBufferedTransport : TTransport, IDisposable + { + private readonly int bufSize; + private readonly MemoryStream inputBuffer = new MemoryStream(0); + private readonly MemoryStream outputBuffer = new MemoryStream(0); + private readonly TTransport transport; + + public TBufferedTransport(TTransport transport, int bufSize = 1024) + { + if (transport == null) + throw new ArgumentNullException("transport"); + if (bufSize <= 0) + throw new ArgumentException("bufSize", "Buffer size must be a positive number."); + this.transport = transport; + this.bufSize = bufSize; + } + + public TTransport UnderlyingTransport + { + get + { + CheckNotDisposed(); + return transport; + } + } + + public override bool IsOpen + { + get + { + // We can legitimately throw here but be nice a bit. + // CheckNotDisposed(); + return !_IsDisposed && transport.IsOpen; + } + } + + public override void Open() + { + CheckNotDisposed(); + transport.Open(); + } + + public override void Close() + { + CheckNotDisposed(); + transport.Close(); + } + + public override int Read(byte[] buf, int off, int len) + { + CheckNotDisposed(); + ValidateBufferArgs(buf, off, len); + if (!IsOpen) + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + + if (inputBuffer.Capacity < bufSize) + inputBuffer.Capacity = bufSize; + + while (true) + { + int got = inputBuffer.Read(buf, off, len); + if (got > 0) + return got; + + inputBuffer.Seek(0, SeekOrigin.Begin); + inputBuffer.SetLength(inputBuffer.Capacity); + int filled = transport.Read(inputBuffer.GetBuffer(), 0, (int)inputBuffer.Length); + inputBuffer.SetLength(filled); + if (filled == 0) + return 0; + } + } + + public override void Write(byte[] buf, int off, int len) + { + CheckNotDisposed(); + ValidateBufferArgs(buf, off, len); + if (!IsOpen) + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + // Relative offset from "off" argument + int offset = 0; + if (outputBuffer.Length > 0) + { + int capa = (int)(outputBuffer.Capacity - outputBuffer.Length); + int writeSize = capa <= len ? capa : len; + outputBuffer.Write(buf, off, writeSize); + offset += writeSize; + if (writeSize == capa) + { + transport.Write(outputBuffer.GetBuffer(), 0, (int)outputBuffer.Length); + outputBuffer.SetLength(0); + } + } + while (len - offset >= bufSize) + { + transport.Write(buf, off + offset, bufSize); + offset += bufSize; + } + int remain = len - offset; + if (remain > 0) + { + if (outputBuffer.Capacity < bufSize) + outputBuffer.Capacity = bufSize; + outputBuffer.Write(buf, off + offset, remain); + } + } + + private void InternalFlush() + { + if (!IsOpen) + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + if (outputBuffer.Length > 0) + { + transport.Write(outputBuffer.GetBuffer(), 0, (int)outputBuffer.Length); + outputBuffer.SetLength(0); + } + } + + public override void Flush() + { + CheckNotDisposed(); + InternalFlush(); + + transport.Flush(); + } + + public override IAsyncResult BeginFlush(AsyncCallback callback, object state) + { + CheckNotDisposed(); + InternalFlush(); + + return transport.BeginFlush( callback, state); + } + + public override void EndFlush(IAsyncResult asyncResult) + { + transport.EndFlush( asyncResult); + } + + + + protected void CheckNotDisposed() + { + if (_IsDisposed) + throw new ObjectDisposedException("TBufferedTransport"); + } + + #region " IDisposable Support " + protected bool _IsDisposed { get; private set; } + + // IDisposable + protected override void Dispose(bool disposing) + { + if (!_IsDisposed) + { + if (disposing) + { + if (inputBuffer != null) + inputBuffer.Dispose(); + if (outputBuffer != null) + outputBuffer.Dispose(); + if (transport != null) + transport.Dispose(); + } + } + _IsDisposed = true; + } + #endregion + } +} diff --git a/lib/csharp/src/Transport/TFramedTransport.cs b/lib/csharp/src/Transport/TFramedTransport.cs new file mode 100644 index 00000000000..a746a3223a5 --- /dev/null +++ b/lib/csharp/src/Transport/TFramedTransport.cs @@ -0,0 +1,205 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +using System; +using System.IO; + +namespace Thrift.Transport +{ + public class TFramedTransport : TTransport, IDisposable + { + private readonly TTransport transport; + private readonly MemoryStream writeBuffer = new MemoryStream(1024); + private readonly MemoryStream readBuffer = new MemoryStream(1024); + + private const int HeaderSize = 4; + private readonly byte[] headerBuf = new byte[HeaderSize]; + + public class Factory : TTransportFactory + { + public override TTransport GetTransport(TTransport trans) + { + return new TFramedTransport(trans); + } + } + + public TFramedTransport(TTransport transport) + { + if (transport == null) + throw new ArgumentNullException("transport"); + this.transport = transport; + InitWriteBuffer(); + } + + public override void Open() + { + CheckNotDisposed(); + transport.Open(); + } + + public override bool IsOpen + { + get + { + // We can legitimately throw here but be nice a bit. + // CheckNotDisposed(); + return !_IsDisposed && transport.IsOpen; + } + } + + public override void Close() + { + CheckNotDisposed(); + transport.Close(); + } + + public override int Read(byte[] buf, int off, int len) + { + CheckNotDisposed(); + ValidateBufferArgs(buf, off, len); + if (!IsOpen) + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + 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() + { + transport.ReadAll(headerBuf, 0, HeaderSize); + int size = DecodeFrameSize(headerBuf); + + readBuffer.SetLength(size); + readBuffer.Seek(0, SeekOrigin.Begin); + byte[] buff = readBuffer.GetBuffer(); + transport.ReadAll(buff, 0, size); + } + + public override void Write(byte[] buf, int off, int len) + { + CheckNotDisposed(); + ValidateBufferArgs(buf, off, len); + if (!IsOpen) + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + if (writeBuffer.Length + (long)len > (long)int.MaxValue) + Flush(); + writeBuffer.Write(buf, off, len); + } + + private void InternalFlush() + { + CheckNotDisposed(); + if (!IsOpen) + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + byte[] buf = writeBuffer.GetBuffer(); + int len = (int)writeBuffer.Length; + int data_len = len - HeaderSize; + if (data_len < 0) + throw new System.InvalidOperationException(); // logic error actually + + // Inject message header into the reserved buffer space + EncodeFrameSize(data_len, buf); + + // Send the entire message at once + transport.Write(buf, 0, len); + + InitWriteBuffer(); + } + + public override void Flush() + { + CheckNotDisposed(); + InternalFlush(); + + transport.Flush(); + } + + public override IAsyncResult BeginFlush(AsyncCallback callback, object state) + { + CheckNotDisposed(); + InternalFlush(); + + return transport.BeginFlush( callback, state); + } + + public override void EndFlush(IAsyncResult asyncResult) + { + transport.EndFlush( asyncResult); + } + + private void InitWriteBuffer() + { + // Reserve space for message header to be put right before sending it out + writeBuffer.SetLength(HeaderSize); + writeBuffer.Seek(0, SeekOrigin.End); + } + + private static void EncodeFrameSize(int frameSize, 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)); + } + + private static int DecodeFrameSize(byte[] buf) + { + return + ((buf[0] & 0xff) << 24) | + ((buf[1] & 0xff) << 16) | + ((buf[2] & 0xff) << 8) | + ((buf[3] & 0xff)); + } + + + private void CheckNotDisposed() + { + if (_IsDisposed) + throw new ObjectDisposedException("TFramedTransport"); + } + + #region " IDisposable Support " + private bool _IsDisposed; + + // IDisposable + protected override void Dispose(bool disposing) + { + if (!_IsDisposed) + { + if (disposing) + { + if (readBuffer != null) + readBuffer.Dispose(); + if (writeBuffer != null) + writeBuffer.Dispose(); + if (transport != null) + transport.Dispose(); + } + } + _IsDisposed = true; + } + #endregion + } +} diff --git a/lib/csharp/src/Transport/THttpClient.cs b/lib/csharp/src/Transport/THttpClient.cs new file mode 100644 index 00000000000..986799cd271 --- /dev/null +++ b/lib/csharp/src/Transport/THttpClient.cs @@ -0,0 +1,486 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.IO.Compression; + +namespace Thrift.Transport +{ + public class THttpClient : TTransport, IDisposable + { + private readonly Uri uri; + private readonly X509Certificate[] certificates; + private Stream inputStream; + private MemoryStream outputStream = new MemoryStream(); + + // Timeouts in milliseconds + private int connectTimeout = 30000; + + private int readTimeout = 30000; + + private IDictionary customHeaders = new Dictionary(); + private string userAgent = "C#/THttpClient"; + +#if !SILVERLIGHT + private IWebProxy proxy = WebRequest.DefaultWebProxy; +#endif + + public THttpClient(Uri u) + : this(u, Enumerable.Empty()) + { + } + public THttpClient(Uri u, string userAgent) + : this(u, userAgent, Enumerable.Empty()) + { + } + + public THttpClient(Uri u, IEnumerable certificates) + { + uri = u; + this.certificates = (certificates ?? Enumerable.Empty()).ToArray(); + } + public THttpClient(Uri u, string userAgent, IEnumerable certificates) + { + uri = u; + this.userAgent = userAgent; + this.certificates = (certificates ?? Enumerable.Empty()).ToArray(); + } + + public int ConnectTimeout + { + set + { + connectTimeout = value; + } + } + + public int ReadTimeout + { + set + { + readTimeout = value; + } + } + + public IDictionary CustomHeaders + { + get + { + return customHeaders; + } + } + +#if !SILVERLIGHT + public IWebProxy Proxy + { + set + { + proxy = value; + } + } +#endif + + public override bool IsOpen + { + get + { + return true; + } + } + + public override void Open() + { + } + + public override void Close() + { + if (inputStream != null) + { + inputStream.Close(); + inputStream = null; + } + if (outputStream != null) + { + outputStream.Close(); + outputStream = null; + } + } + + public override int Read(byte[] buf, int off, int len) + { + if (inputStream == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "No request has been sent"); + } + + try + { + int ret = inputStream.Read(buf, off, len); + + if (ret == -1) + { + throw new TTransportException(TTransportException.ExceptionType.EndOfFile, "No more data available"); + } + + return ret; + } + catch (IOException iox) + { + throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString(), iox); + } + } + + public override void Write(byte[] buf, int off, int len) + { + outputStream.Write(buf, off, len); + } + +#if !SILVERLIGHT + public override void Flush() + { + try + { + SendRequest(); + } + finally + { + outputStream = new MemoryStream(); + } + } + + private void SendRequest() + { + try + { + HttpWebRequest connection = CreateRequest(); + connection.Headers.Add("Accept-Encoding", "gzip, deflate"); + + byte[] data = outputStream.ToArray(); + connection.ContentLength = data.Length; + + using (Stream requestStream = connection.GetRequestStream()) + { + requestStream.Write(data, 0, data.Length); + + // Resolve HTTP hang that can happens after successive calls by making sure + // that we release the response and response stream. To support this, we copy + // the response to a memory stream. + using (var response = connection.GetResponse()) + { + using (var responseStream = response.GetResponseStream()) + { + // Copy the response to a memory stream so that we can + // cleanly close the response and response stream. + inputStream = new MemoryStream(); + byte[] buffer = new byte[8192]; // multiple of 4096 + int bytesRead; + while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0) + { + inputStream.Write(buffer, 0, bytesRead); + } + inputStream.Seek(0, 0); + } + + var encodings = response.Headers.GetValues("Content-Encoding"); + if (encodings != null) + { + foreach (var encoding in encodings) + { + switch (encoding) + { + case "gzip": + DecompressGZipped(ref inputStream); + break; + case "deflate": + DecompressDeflated(ref inputStream); + break; + default: + break; + } + } + } + } + } + } + catch (IOException iox) + { + throw new TTransportException(TTransportException.ExceptionType.Unknown, iox.ToString(), iox); + } + catch (WebException wx) + { + throw new TTransportException(TTransportException.ExceptionType.Unknown, "Couldn't connect to server: " + wx, wx); + } + } + + private void DecompressDeflated(ref Stream inputStream) + { + var tmp = new MemoryStream(); + using (var decomp = new DeflateStream(inputStream, CompressionMode.Decompress)) + { + decomp.CopyTo(tmp); + } + inputStream.Dispose(); + inputStream = tmp; + inputStream.Seek(0, 0); + } + + private void DecompressGZipped(ref Stream inputStream) + { + var tmp = new MemoryStream(); + using (var decomp = new GZipStream(inputStream, CompressionMode.Decompress)) + { + decomp.CopyTo(tmp); + } + inputStream.Dispose(); + inputStream = tmp; + inputStream.Seek(0, 0); + } +#endif + private HttpWebRequest CreateRequest() + { + HttpWebRequest connection = (HttpWebRequest)WebRequest.Create(uri); + + +#if !SILVERLIGHT + // Adding certificates through code is not supported with WP7 Silverlight + // see "Windows Phone 7 and Certificates_FINAL_121610.pdf" + connection.ClientCertificates.AddRange(certificates); + + if (connectTimeout > 0) + { + connection.Timeout = connectTimeout; + } + if (readTimeout > 0) + { + connection.ReadWriteTimeout = readTimeout; + } +#endif + // Make the request + connection.ContentType = "application/x-thrift"; + connection.Accept = "application/x-thrift"; + connection.UserAgent = userAgent; + connection.Method = "POST"; +#if !SILVERLIGHT + connection.ProtocolVersion = HttpVersion.Version10; +#endif + + //add custom headers here + foreach (KeyValuePair item in customHeaders) + { +#if !SILVERLIGHT + connection.Headers.Add(item.Key, item.Value); +#else + connection.Headers[item.Key] = item.Value; +#endif + } + +#if !SILVERLIGHT + connection.Proxy = proxy; +#endif + + return connection; + } + + public override IAsyncResult BeginFlush(AsyncCallback callback, object state) + { + // Extract request and reset buffer + var data = outputStream.ToArray(); + + //requestBuffer_ = new MemoryStream(); + + try + { + // Create connection object + var flushAsyncResult = new FlushAsyncResult(callback, state); + flushAsyncResult.Connection = CreateRequest(); + + flushAsyncResult.Data = data; + + + flushAsyncResult.Connection.BeginGetRequestStream(GetRequestStreamCallback, flushAsyncResult); + return flushAsyncResult; + + } + catch (IOException iox) + { + throw new TTransportException(iox.ToString(), iox); + } + } + + public override void EndFlush(IAsyncResult asyncResult) + { + try + { + var flushAsyncResult = (FlushAsyncResult)asyncResult; + + if (!flushAsyncResult.IsCompleted) + { + var waitHandle = flushAsyncResult.AsyncWaitHandle; + waitHandle.WaitOne(); // blocking INFINITEly + waitHandle.Close(); + } + + if (flushAsyncResult.AsyncException != null) + { + throw flushAsyncResult.AsyncException; + } + } + finally + { + outputStream = new MemoryStream(); + } + + } + + private void GetRequestStreamCallback(IAsyncResult asynchronousResult) + { + var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState; + try + { + var reqStream = flushAsyncResult.Connection.EndGetRequestStream(asynchronousResult); + reqStream.Write(flushAsyncResult.Data, 0, flushAsyncResult.Data.Length); + reqStream.Flush(); + reqStream.Close(); + + // Start the asynchronous operation to get the response + flushAsyncResult.Connection.BeginGetResponse(GetResponseCallback, flushAsyncResult); + } + catch (Exception exception) + { + flushAsyncResult.AsyncException = new TTransportException(exception.ToString(), exception); + flushAsyncResult.UpdateStatusToComplete(); + flushAsyncResult.NotifyCallbackWhenAvailable(); + } + } + + private void GetResponseCallback(IAsyncResult asynchronousResult) + { + var flushAsyncResult = (FlushAsyncResult)asynchronousResult.AsyncState; + try + { + inputStream = flushAsyncResult.Connection.EndGetResponse(asynchronousResult).GetResponseStream(); + } + catch (Exception exception) + { + flushAsyncResult.AsyncException = new TTransportException(exception.ToString(), exception); + } + flushAsyncResult.UpdateStatusToComplete(); + flushAsyncResult.NotifyCallbackWhenAvailable(); + } + + // Based on http://msmvps.com/blogs/luisabreu/archive/2009/06/15/multithreading-implementing-the-iasyncresult-interface.aspx + class FlushAsyncResult : IAsyncResult + { + private volatile Boolean _isCompleted; + private ManualResetEvent _evt; + private readonly AsyncCallback _cbMethod; + private readonly object _state; + + public FlushAsyncResult(AsyncCallback cbMethod, object state) + { + _cbMethod = cbMethod; + _state = state; + } + + internal byte[] Data { get; set; } + internal HttpWebRequest Connection { get; set; } + internal TTransportException AsyncException { get; set; } + + public object AsyncState + { + get { return _state; } + } + public WaitHandle AsyncWaitHandle + { + get { return GetEvtHandle(); } + } + public bool CompletedSynchronously + { + get { return false; } + } + public bool IsCompleted + { + get { return _isCompleted; } + } + private readonly object _locker = new object(); + private ManualResetEvent GetEvtHandle() + { + lock (_locker) + { + if (_evt == null) + { + _evt = new ManualResetEvent(false); + } + if (_isCompleted) + { + _evt.Set(); + } + } + return _evt; + } + internal void UpdateStatusToComplete() + { + _isCompleted = true; //1. set _iscompleted to true + lock (_locker) + { + if (_evt != null) + { + _evt.Set(); //2. set the event, when it exists + } + } + } + + internal void NotifyCallbackWhenAvailable() + { + if (_cbMethod != null) + { + _cbMethod(this); + } + } + } + + #region " IDisposable Support " + private bool _IsDisposed; + + // IDisposable + protected override void Dispose(bool disposing) + { + if (!_IsDisposed) + { + if (disposing) + { + if (inputStream != null) + inputStream.Dispose(); + if (outputStream != null) + outputStream.Dispose(); + } + } + _IsDisposed = true; + } + #endregion + } +} diff --git a/lib/csharp/src/Transport/THttpHandler.cs b/lib/csharp/src/Transport/THttpHandler.cs new file mode 100644 index 00000000000..4115ef95aaa --- /dev/null +++ b/lib/csharp/src/Transport/THttpHandler.cs @@ -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. + * + * + */ + +using System; +using System.Web; +using System.Net; +using System.IO; + +using Thrift.Protocol; + +namespace Thrift.Transport +{ + public class THttpHandler : IHttpHandler + { + protected TProcessor processor; + + protected TProtocolFactory inputProtocolFactory; + protected TProtocolFactory outputProtocolFactory; + + protected const string contentType = "application/x-thrift"; + protected System.Text.Encoding encoding = System.Text.Encoding.UTF8; + + public THttpHandler(TProcessor processor) + : this(processor, new TBinaryProtocol.Factory()) + { + + } + + public THttpHandler(TProcessor processor, TProtocolFactory protocolFactory) + : this(processor, protocolFactory, protocolFactory) + { + + } + + public THttpHandler(TProcessor processor, TProtocolFactory inputProtocolFactory, TProtocolFactory outputProtocolFactory) + { + this.processor = processor; + this.inputProtocolFactory = inputProtocolFactory; + this.outputProtocolFactory = outputProtocolFactory; + } + + public void ProcessRequest(HttpListenerContext context) + { + context.Response.ContentType = contentType; + context.Response.ContentEncoding = encoding; + ProcessRequest(context.Request.InputStream, context.Response.OutputStream); + } + + public void ProcessRequest(HttpContext context) + { + context.Response.ContentType = contentType; + context.Response.ContentEncoding = encoding; + ProcessRequest(context.Request.InputStream, context.Response.OutputStream); + } + + public void ProcessRequest(Stream input, Stream output) + { + TTransport transport = new TStreamTransport(input,output); + + try + { + var inputProtocol = inputProtocolFactory.GetProtocol(transport); + var outputProtocol = outputProtocolFactory.GetProtocol(transport); + + while (processor.Process(inputProtocol, outputProtocol)) + { + } + } + catch (TTransportException) + { + // Client died, just move on + } + finally + { + transport.Close(); + } + } + + public bool IsReusable + { + get { return true; } + } + } +} diff --git a/lib/csharp/src/Transport/THttpTaskAsyncHandler.cs b/lib/csharp/src/Transport/THttpTaskAsyncHandler.cs new file mode 100644 index 00000000000..e491f32cb98 --- /dev/null +++ b/lib/csharp/src/Transport/THttpTaskAsyncHandler.cs @@ -0,0 +1,97 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System.Threading.Tasks; +using System.Web; +using Thrift.Protocol; + +namespace Thrift.Transport +{ + /// + /// An async task based HTTP handler for processing thrift services. + /// + public class THttpTaskAsyncHandler : HttpTaskAsyncHandler + { + private readonly TAsyncProcessor _processor; + private readonly TProtocolFactory _inputProtocolFactory; + private readonly TProtocolFactory _outputProtocolFactory; + + /// + /// Initializes a new instance of the class + /// using the for both input and output streams. + /// + /// The async processor implementation. + public THttpTaskAsyncHandler(TAsyncProcessor processor) + : this(processor, new TBinaryProtocol.Factory()) + { + } + + /// + /// Initializes a new instance of the class + /// using for both input and output streams. + /// + /// The async processor implementation. + /// The protocol factory. + public THttpTaskAsyncHandler(TAsyncProcessor processor, TProtocolFactory protocolFactory) + : this(processor, protocolFactory, protocolFactory) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The async processor implementation. + /// The input protocol factory. + /// The output protocol factory. + public THttpTaskAsyncHandler(TAsyncProcessor processor, TProtocolFactory inputProtocolFactory, + TProtocolFactory outputProtocolFactory) + { + _processor = processor; + _inputProtocolFactory = inputProtocolFactory; + _outputProtocolFactory = outputProtocolFactory; + } + + public override async Task ProcessRequestAsync(HttpContext context) + { + var transport = new TStreamTransport(context.Request.InputStream, context.Response.OutputStream); + + try + { + var input = _inputProtocolFactory.GetProtocol(transport); + var output = _outputProtocolFactory.GetProtocol(transport); + + while (await _processor.ProcessAsync(input, output)) + { + } + } + catch (TTransportException) + { + // Client died, just move on + } + finally + { + transport.Close(); + } + } + } +} diff --git a/lib/csharp/src/Transport/TMemoryBuffer.cs b/lib/csharp/src/Transport/TMemoryBuffer.cs new file mode 100644 index 00000000000..303d083299e --- /dev/null +++ b/lib/csharp/src/Transport/TMemoryBuffer.cs @@ -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. + */ + +using System; +using System.IO; +using System.Reflection; +using Thrift.Protocol; + +namespace Thrift.Transport +{ + public class TMemoryBuffer : TTransport + { + + private readonly MemoryStream byteStream; + + public TMemoryBuffer() + { + byteStream = new MemoryStream(); + } + + public TMemoryBuffer(byte[] buf) + { + byteStream = new MemoryStream(buf); + } + + public override void Open() + { + /** do nothing **/ + } + + public override void Close() + { + /** do nothing **/ + } + + public override int Read(byte[] buf, int off, int len) + { + return byteStream.Read(buf, off, len); + } + + public override void Write(byte[] buf, int off, int len) + { + byteStream.Write(buf, off, len); + } + + public byte[] GetBuffer() + { + return byteStream.ToArray(); + } + + + public override bool IsOpen + { + get { return true; } + } + + public static byte[] Serialize(TAbstractBase s) + { + var t = new TMemoryBuffer(); + var p = new TBinaryProtocol(t); + + s.Write(p); + + return t.GetBuffer(); + } + + public static T DeSerialize(byte[] buf) where T : TAbstractBase + { + var trans = new TMemoryBuffer(buf); + var p = new TBinaryProtocol(trans); + if (typeof(TBase).IsAssignableFrom(typeof(T))) + { + var method = typeof(T).GetMethod("Read", BindingFlags.Instance | BindingFlags.Public); + var t = Activator.CreateInstance(); + method.Invoke(t, new object[] { p }); + return t; + } + else + { + var method = typeof(T).GetMethod("Read", BindingFlags.Static | BindingFlags.Public); + return (T)method.Invoke(null, new object[] { p }); + } + } + + private bool _IsDisposed; + + // IDisposable + protected override void Dispose(bool disposing) + { + if (!_IsDisposed) + { + if (disposing) + { + if (byteStream != null) + byteStream.Dispose(); + } + } + _IsDisposed = true; + } + } +} diff --git a/lib/csharp/src/Transport/TNamedPipeClientTransport.cs b/lib/csharp/src/Transport/TNamedPipeClientTransport.cs new file mode 100644 index 00000000000..49a50aa5b02 --- /dev/null +++ b/lib/csharp/src/Transport/TNamedPipeClientTransport.cs @@ -0,0 +1,111 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.IO.Pipes; +using System.Threading; + +namespace Thrift.Transport +{ + public class TNamedPipeClientTransport : TTransport + { + private NamedPipeClientStream client; + private string ServerName; + private string PipeName; + private int ConnectTimeout; + + public TNamedPipeClientTransport(string pipe, int timeout = Timeout.Infinite) + { + ServerName = "."; + PipeName = pipe; + ConnectTimeout = timeout; + } + + public TNamedPipeClientTransport(string server, string pipe, int timeout = Timeout.Infinite) + { + ServerName = (server != "") ? server : "."; + PipeName = pipe; + ConnectTimeout = timeout; + } + + public override bool IsOpen + { + get { return client != null && client.IsConnected; } + } + + public override void Open() + { + if (IsOpen) + { + throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen); + } + client = new NamedPipeClientStream(ServerName, PipeName, PipeDirection.InOut, PipeOptions.None); + client.Connect(ConnectTimeout); + } + + public override void Close() + { + if (client != null) + { + client.Close(); + client = null; + } + } + + public override int Read(byte[] buf, int off, int len) + { + if (client == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + } + + return client.Read(buf, off, len); + } + + public override void Write(byte[] buf, int off, int len) + { + if (client == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + } + + // if necessary, send the data in chunks + // there's a system limit around 0x10000 bytes that we hit otherwise + // MSDN: "Pipe write operations across a network are limited to 65,535 bytes per write. For more information regarding pipes, see the Remarks section." + var nBytes = Math.Min(len, 15 * 4096); // 16 would exceed the limit + while (nBytes > 0) + { + client.Write(buf, off, nBytes); + + off += nBytes; + len -= nBytes; + nBytes = Math.Min(len, nBytes); + } + } + + protected override void Dispose(bool disposing) + { + client.Dispose(); + } + } +} diff --git a/lib/csharp/src/Transport/TNamedPipeServerTransport.cs b/lib/csharp/src/Transport/TNamedPipeServerTransport.cs new file mode 100644 index 00000000000..32215cfc10f --- /dev/null +++ b/lib/csharp/src/Transport/TNamedPipeServerTransport.cs @@ -0,0 +1,296 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.IO.Pipes; +using System.Threading; +using System.Security.Principal; + +namespace Thrift.Transport +{ + public class TNamedPipeServerTransport : TServerTransport + { + /// + /// This is the address of the Pipe on the localhost. + /// + private readonly string pipeAddress; + private NamedPipeServerStream stream = null; + private bool asyncMode = true; + + public TNamedPipeServerTransport(string pipeAddress) + { + this.pipeAddress = pipeAddress; + } + + public override void Listen() + { + // nothing to do here + } + + public override void Close() + { + if (stream != null) + { + try + { + stream.Close(); + stream.Dispose(); + } + finally + { + stream = null; + } + } + } + + private void EnsurePipeInstance() + { + if (stream == null) + { + var direction = PipeDirection.InOut; + var maxconn = NamedPipeServerStream.MaxAllowedServerInstances; + var mode = PipeTransmissionMode.Byte; + var options = asyncMode ? PipeOptions.Asynchronous : PipeOptions.None; + const int INBUF_SIZE = 4096; + const int OUTBUF_SIZE = 4096; + + // security + var security = new PipeSecurity(); + security.AddAccessRule( + new PipeAccessRule( + new SecurityIdentifier(WellKnownSidType.WorldSid, null), + PipeAccessRights.Read | PipeAccessRights.Write | PipeAccessRights.Synchronize | PipeAccessRights.CreateNewInstance, + System.Security.AccessControl.AccessControlType.Allow + ) + ); + + try + { + stream = new NamedPipeServerStream(pipeAddress, direction, maxconn, mode, options, INBUF_SIZE, OUTBUF_SIZE, security); + } + catch (NotImplementedException) // Mono still does not support async, fallback to sync + { + if (asyncMode) + { + options &= (~PipeOptions.Asynchronous); + stream = new NamedPipeServerStream(pipeAddress, direction, maxconn, mode, options, INBUF_SIZE, OUTBUF_SIZE, security); + asyncMode = false; + } + else + { + throw; + } + } + + } + } + + protected override TTransport AcceptImpl() + { + try + { + EnsurePipeInstance(); + + if (asyncMode) + { + var evt = new ManualResetEvent(false); + Exception eOuter = null; + + stream.BeginWaitForConnection(asyncResult => + { + try + { + if (stream != null) + stream.EndWaitForConnection(asyncResult); + else + eOuter = new TTransportException(TTransportException.ExceptionType.Interrupted); + } + catch (Exception e) + { + if (stream != null) + eOuter = e; + else + eOuter = new TTransportException(TTransportException.ExceptionType.Interrupted, e.Message, e); + } + evt.Set(); + }, null); + + evt.WaitOne(); + + if (eOuter != null) + throw eOuter; // rethrow exception + } + else + { + stream.WaitForConnection(); + } + + var trans = new ServerTransport(stream,asyncMode); + stream = null; // pass ownership to ServerTransport + return trans; + } + catch (TTransportException) + { + Close(); + throw; + } + catch (Exception e) + { + Close(); + throw new TTransportException(TTransportException.ExceptionType.NotOpen, e.Message, e); + } + } + + private class ServerTransport : TTransport + { + private NamedPipeServerStream stream; + private bool asyncMode; + + public ServerTransport(NamedPipeServerStream stream, bool asyncMode) + { + this.stream = stream; + this.asyncMode = asyncMode; + } + + public override bool IsOpen + { + get { return stream != null && stream.IsConnected; } + } + + public override void Open() + { + } + + public override void Close() + { + if (stream != null) + stream.Close(); + } + + public override int Read(byte[] buf, int off, int len) + { + if (stream == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + } + + if (asyncMode) + { + Exception eOuter = null; + var evt = new ManualResetEvent(false); + int retval = 0; + + stream.BeginRead(buf, off, len, asyncResult => + { + try + { + if (stream != null) + retval = stream.EndRead(asyncResult); + else + eOuter = new TTransportException(TTransportException.ExceptionType.Interrupted); + } + catch (Exception e) + { + if (stream != null) + eOuter = e; + else + eOuter = new TTransportException(TTransportException.ExceptionType.Interrupted, e.Message, e); + } + evt.Set(); + }, null); + + evt.WaitOne(); + + if (eOuter != null) + throw eOuter; // rethrow exception + else + return retval; + } + else + { + return stream.Read(buf, off, len); + } + } + + public override void Write(byte[] buf, int off, int len) + { + if (stream == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen); + } + + // if necessary, send the data in chunks + // there's a system limit around 0x10000 bytes that we hit otherwise + // MSDN: "Pipe write operations across a network are limited to 65,535 bytes per write. For more information regarding pipes, see the Remarks section." + var nBytes = Math.Min(len, 15 * 4096); // 16 would exceed the limit + while (nBytes > 0) + { + + if (asyncMode) + { + Exception eOuter = null; + var evt = new ManualResetEvent(false); + + stream.BeginWrite(buf, off, nBytes, asyncResult => + { + try + { + if (stream != null) + stream.EndWrite(asyncResult); + else + eOuter = new TTransportException(TTransportException.ExceptionType.Interrupted); + } + catch (Exception e) + { + if (stream != null) + eOuter = e; + else + eOuter = new TTransportException(TTransportException.ExceptionType.Interrupted, e.Message, e); + } + evt.Set(); + }, null); + + evt.WaitOne(); + + if (eOuter != null) + throw eOuter; // rethrow exception + } + else + { + stream.Write(buf, off, nBytes); + } + + off += nBytes; + len -= nBytes; + nBytes = Math.Min(len, nBytes); + } + } + + protected override void Dispose(bool disposing) + { + if (stream != null) + stream.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/lib/csharp/src/Transport/TServerSocket.cs b/lib/csharp/src/Transport/TServerSocket.cs new file mode 100644 index 00000000000..d8ec62ab3f4 --- /dev/null +++ b/lib/csharp/src/Transport/TServerSocket.cs @@ -0,0 +1,176 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Net.Sockets; + + +namespace Thrift.Transport +{ + public class TServerSocket : TServerTransport + { + /// + /// Underlying server with socket. + /// + private TcpListener server = null; + + /// + /// Port to listen on. + /// + private int port = 0; + + /// + /// Timeout for client sockets from accept. + /// + private int clientTimeout = 0; + + /// + /// Whether or not to wrap new TSocket connections in buffers. + /// + private bool useBufferedSockets = false; + + /// + /// Creates a server socket from underlying socket object. + /// + public TServerSocket(TcpListener listener) + : this(listener, 0) + { + } + + /// + /// Creates a server socket from underlying socket object. + /// + public TServerSocket(TcpListener listener, int clientTimeout) + { + this.server = listener; + this.clientTimeout = clientTimeout; + } + + /// + /// Creates just a port listening server socket. + /// + public TServerSocket(int port) + : this(port, 0) + { + } + + /// + /// Creates just a port listening server socket. + /// + public TServerSocket(int port, int clientTimeout) + : this(port, clientTimeout, false) + { + } + + public TServerSocket(int port, int clientTimeout, bool useBufferedSockets) + { + this.port = port; + this.clientTimeout = clientTimeout; + this.useBufferedSockets = useBufferedSockets; + try + { + // Make server socket + this.server = TSocketVersionizer.CreateTcpListener(this.port); + this.server.Server.NoDelay = true; + } + catch (Exception ex) + { + server = null; + throw new TTransportException("Could not create ServerSocket on port " + this.port + ".", ex); + } + } + + public override void Listen() + { + // Make sure not to block on accept + if (server != null) + { + try + { + server.Start(); + } + catch (SocketException sx) + { + throw new TTransportException("Could not accept on listening socket: " + sx.Message, sx); + } + } + } + + protected override TTransport AcceptImpl() + { + if (server == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "No underlying server socket."); + } + try + { + TSocket result2 = null; + TcpClient result = server.AcceptTcpClient(); + try + { + result2 = new TSocket(result); + result2.Timeout = clientTimeout; + if (useBufferedSockets) + { + TBufferedTransport result3 = new TBufferedTransport(result2); + return result3; + } + else + { + return result2; + } + } + catch (System.Exception) + { + // If a TSocket was successfully created, then let + // it do proper cleanup of the TcpClient object. + if (result2 != null) + result2.Dispose(); + else // Otherwise, clean it up ourselves. + ((IDisposable)result).Dispose(); + throw; + } + } + catch (Exception ex) + { + throw new TTransportException(ex.ToString(), ex); + } + } + + public override void Close() + { + if (server != null) + { + try + { + server.Stop(); + } + catch (Exception ex) + { + throw new TTransportException("WARNING: Could not close server socket: " + ex, ex); + } + server = null; + } + } + } +} diff --git a/lib/csharp/src/Transport/TServerTransport.cs b/lib/csharp/src/Transport/TServerTransport.cs new file mode 100644 index 00000000000..e63880be1a0 --- /dev/null +++ b/lib/csharp/src/Transport/TServerTransport.cs @@ -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. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; + +namespace Thrift.Transport +{ + public abstract class TServerTransport + { + public abstract void Listen(); + public abstract void Close(); + protected abstract TTransport AcceptImpl(); + + public TTransport Accept() + { + TTransport transport = AcceptImpl(); + if (transport == null) + { + throw new TTransportException("accept() may not return NULL"); + } + return transport; + } + } +} diff --git a/lib/csharp/src/Transport/TSilverlightSocket.cs b/lib/csharp/src/Transport/TSilverlightSocket.cs new file mode 100644 index 00000000000..40469ab40ce --- /dev/null +++ b/lib/csharp/src/Transport/TSilverlightSocket.cs @@ -0,0 +1,393 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +/* only for silverlight */ +#if SILVERLIGHT + +using System; +using System.Net.Sockets; +using System.IO; +using System.Net; +using System.Threading; + +namespace Thrift.Transport +{ + public class TSilverlightSocket : TTransport + { + Socket socket = null; + static ManualResetEvent readAsyncComplete = new ManualResetEvent(false); + public event EventHandler connectHandler = null; + + // memory stream for write cache. + private MemoryStream outputStream = new MemoryStream(); + + private string host = null; + private int port = 0; + private int timeout = 0; + + // constructor + public TSilverlightSocket(string host, int port) + : this(host, port, 0) + { + } + + // constructor + public TSilverlightSocket(string host, int port, int timeout) + { + this.host = host; + this.port = port; + this.timeout = timeout; + + InitSocket(); + } + + private void InitSocket() + { + // Create a stream-based, TCP socket using the InterNetwork Address Family. + socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + socket.NoDelay = true; + } + + public int Timeout + { + set + { + timeout = value; + } + } + + public string Host + { + get + { + return host; + } + } + + public int Port + { + get + { + return port; + } + } + + public override bool IsOpen + { + get + { + if (socket == null) + { + return false; + } + + return socket.Connected; + } + } + + public override void Open() + { + if (IsOpen) + { + throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen, "Socket already connected"); + } + + if (string.IsNullOrEmpty(host)) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open null host"); + } + + if (port <= 0) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open without port"); + } + + if (socket == null) + { + InitSocket(); + } + + if (timeout == 0) // no timeout -> infinite + { + timeout = 10000; // set a default timeout for WP. + } + + { + // Create DnsEndPoint. The hostName and port are passed in to this method. + DnsEndPoint hostEntry = new DnsEndPoint(this.host, this.port); + + // Create a SocketAsyncEventArgs object to be used in the connection request + SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs(); + socketEventArg.RemoteEndPoint = hostEntry; + + // Inline event handler for the Completed event. + // Note: This event handler was implemented inline in order to make this method self-contained. + socketEventArg.Completed += new EventHandler(delegate(object s, SocketAsyncEventArgs e) + { + if (connectHandler != null) + { + connectHandler(this, e); + } + }); + + // Make an asynchronous Connect request over the socket + socket.ConnectAsync(socketEventArg); + } + } + + public override int Read(byte[] buf, int off, int len) + { + bool _timeout = true; + string _error = null; + int _recvBytes = -1; + + if (socket == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Socket is not open"); + } + + // Create SocketAsyncEventArgs context object + SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs(); + socketEventArg.RemoteEndPoint = socket.RemoteEndPoint; + + // Setup the buffer to receive the data + socketEventArg.SetBuffer(buf, off, len); + + // Inline event handler for the Completed event. + // Note: This even handler was implemented inline in order to make + // this method self-contained. + socketEventArg.Completed += new EventHandler(delegate(object s, SocketAsyncEventArgs e) + { + _timeout = false; + + if (e.SocketError == SocketError.Success) + { + _recvBytes = e.BytesTransferred; + } + else + { + _error = e.SocketError.ToString(); + } + + readAsyncComplete.Set(); + }); + + // Sets the state of the event to nonsignaled, causing threads to block + readAsyncComplete.Reset(); + + // Make an asynchronous Receive request over the socket + socket.ReceiveAsync(socketEventArg); + + // Block the UI thread for a maximum of TIMEOUT_MILLISECONDS milliseconds. + // If no response comes back within this time then proceed + readAsyncComplete.WaitOne(this.timeout); + + if (_timeout) + { + throw new TTransportException(TTransportException.ExceptionType.TimedOut, "Socket recv timeout"); + } + + if (_error != null) + { + throw new TTransportException(TTransportException.ExceptionType.Unknown, _error); + } + + return _recvBytes; + } + + public override void Write(byte[] buf, int off, int len) + { + outputStream.Write(buf, off, len); + } + + private void beginFlush_Completed(object sender, SocketAsyncEventArgs e) + { + FlushAsyncResult flushAsyncResult = e.UserToken as FlushAsyncResult; + flushAsyncResult.UpdateStatusToComplete(); + flushAsyncResult.NotifyCallbackWhenAvailable(); + + if (e.SocketError != SocketError.Success) + { + throw new TTransportException(TTransportException.ExceptionType.Unknown, e.SocketError.ToString()); + } + } + + public override IAsyncResult BeginFlush(AsyncCallback callback, object state) + { + // Extract request and reset buffer + byte[] data = outputStream.ToArray(); + + FlushAsyncResult flushAsyncResult = new FlushAsyncResult(callback, state); + + SocketAsyncEventArgs socketEventArg = new SocketAsyncEventArgs(); + socketEventArg.RemoteEndPoint = socket.RemoteEndPoint; + socketEventArg.UserToken = flushAsyncResult; + + socketEventArg.Completed += beginFlush_Completed; + socketEventArg.SetBuffer(data, 0, data.Length); + + socket.SendAsync(socketEventArg); + + return flushAsyncResult; + } + + public override void EndFlush(IAsyncResult asyncResult) + { + try + { + var flushAsyncResult = (FlushAsyncResult)asyncResult; + + if (!flushAsyncResult.IsCompleted) + { + var waitHandle = flushAsyncResult.AsyncWaitHandle; + waitHandle.WaitOne(); + waitHandle.Close(); + } + + if (flushAsyncResult.AsyncException != null) + { + throw flushAsyncResult.AsyncException; + } + } + finally + { + outputStream = new MemoryStream(); + } + } + + // Copy from impl from THttpClient.cs + // Based on http://msmvps.com/blogs/luisabreu/archive/2009/06/15/multithreading-implementing-the-iasyncresult-interface.aspx + class FlushAsyncResult : IAsyncResult + { + private volatile Boolean _isCompleted; + private ManualResetEvent _evt; + private readonly AsyncCallback _cbMethod; + private readonly object _state; + + public FlushAsyncResult(AsyncCallback cbMethod, object state) + { + _cbMethod = cbMethod; + _state = state; + } + + internal byte[] Data { get; set; } + internal Socket Connection { get; set; } + internal TTransportException AsyncException { get; set; } + + public object AsyncState + { + get { return _state; } + } + + public WaitHandle AsyncWaitHandle + { + get { return GetEvtHandle(); } + } + + public bool CompletedSynchronously + { + get { return false; } + } + + public bool IsCompleted + { + get { return _isCompleted; } + } + + private readonly object _locker = new object(); + + private ManualResetEvent GetEvtHandle() + { + lock (_locker) + { + if (_evt == null) + { + _evt = new ManualResetEvent(false); + } + if (_isCompleted) + { + _evt.Set(); + } + } + return _evt; + } + + internal void UpdateStatusToComplete() + { + _isCompleted = true; //1. set _iscompleted to true + lock (_locker) + { + if (_evt != null) + { + _evt.Set(); //2. set the event, when it exists + } + } + } + + internal void NotifyCallbackWhenAvailable() + { + if (_cbMethod != null) + { + _cbMethod(this); + } + } + } + + public override void Close() + { + if (socket != null) + { + socket.Close(); + socket = null; + } + } + +#region " IDisposable Support " + private bool _IsDisposed; + + // IDisposable + protected override void Dispose(bool disposing) + { + if (!_IsDisposed) + { + if (disposing) + { + if (outputStream != null) + { + outputStream.Dispose(); + } + outputStream = null; + if (socket != null) + { + ((IDisposable)socket).Dispose(); + } + } + } + _IsDisposed = true; + } +#endregion + } +} + + +#endif diff --git a/lib/csharp/src/Transport/TSocket.cs b/lib/csharp/src/Transport/TSocket.cs new file mode 100644 index 00000000000..d8fa335ad07 --- /dev/null +++ b/lib/csharp/src/Transport/TSocket.cs @@ -0,0 +1,245 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Net.Sockets; + +namespace Thrift.Transport +{ + public class TSocket : TStreamTransport + { + private TcpClient client = null; + private string host = null; + private int port = 0; + private int timeout = 0; + + public TSocket(TcpClient client) + { + this.client = client; + + if (IsOpen) + { + inputStream = client.GetStream(); + outputStream = client.GetStream(); + } + } + + public TSocket(string host, int port) + : this(host, port, 0) + { + } + + public TSocket(string host, int port, int timeout) + { + this.host = host; + this.port = port; + this.timeout = timeout; + + InitSocket(); + } + + private void InitSocket() + { + this.client = TSocketVersionizer.CreateTcpClient(); + this.client.ReceiveTimeout = client.SendTimeout = timeout; + this.client.Client.NoDelay = true; + } + + public int Timeout + { + set + { + client.ReceiveTimeout = client.SendTimeout = timeout = value; + } + } + + public TcpClient TcpClient + { + get + { + return client; + } + } + + public string Host + { + get + { + return host; + } + } + + public int Port + { + get + { + return port; + } + } + + public override bool IsOpen + { + get + { + if (client == null) + { + return false; + } + + return client.Connected; + } + } + + public override void Open() + { + if (IsOpen) + { + throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen, "Socket already connected"); + } + + if (string.IsNullOrEmpty(host)) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open null host"); + } + + if (port <= 0) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open without port"); + } + + if (client == null) + { + InitSocket(); + } + + if (timeout == 0) // no timeout -> infinite + { + client.Connect(host, port); + } + else // we have a timeout -> use it + { + ConnectHelper hlp = new ConnectHelper(client); + IAsyncResult asyncres = client.BeginConnect(host, port, new AsyncCallback(ConnectCallback), hlp); + bool bConnected = asyncres.AsyncWaitHandle.WaitOne(timeout) && client.Connected; + if (!bConnected) + { + lock (hlp.Mutex) + { + if (hlp.CallbackDone) + { + asyncres.AsyncWaitHandle.Close(); + client.Close(); + } + else + { + hlp.DoCleanup = true; + client = null; + } + } + throw new TTransportException(TTransportException.ExceptionType.TimedOut, "Connect timed out"); + } + } + + inputStream = client.GetStream(); + outputStream = client.GetStream(); + } + + + static void ConnectCallback(IAsyncResult asyncres) + { + ConnectHelper hlp = asyncres.AsyncState as ConnectHelper; + lock (hlp.Mutex) + { + hlp.CallbackDone = true; + + try + { + if (hlp.Client.Client != null) + hlp.Client.EndConnect(asyncres); + } + catch (Exception) + { + // catch that away + } + + if (hlp.DoCleanup) + { + try + { + asyncres.AsyncWaitHandle.Close(); + } + catch (Exception) { } + + try + { + if (hlp.Client is IDisposable) + ((IDisposable)hlp.Client).Dispose(); + } + catch (Exception) { } + hlp.Client = null; + } + } + } + + private class ConnectHelper + { + public object Mutex = new object(); + public bool DoCleanup = false; + public bool CallbackDone = false; + public TcpClient Client; + public ConnectHelper(TcpClient client) + { + Client = client; + } + } + + public override void Close() + { + base.Close(); + if (client != null) + { + client.Close(); + client = null; + } + } + + #region " IDisposable Support " + private bool _IsDisposed; + + // IDisposable + protected override void Dispose(bool disposing) + { + if (!_IsDisposed) + { + if (disposing) + { + if (client != null) + ((IDisposable)client).Dispose(); + base.Dispose(disposing); + } + } + _IsDisposed = true; + } + #endregion + } +} diff --git a/lib/csharp/src/Transport/TSocketVersionizer.cs b/lib/csharp/src/Transport/TSocketVersionizer.cs new file mode 100644 index 00000000000..8c2f8e995e8 --- /dev/null +++ b/lib/csharp/src/Transport/TSocketVersionizer.cs @@ -0,0 +1,78 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Reflection; +using System.Text; +#if NET45 +using System.Threading.Tasks; +#endif + +namespace Thrift.Transport +{ + /// + /// PropertyInfo for the DualMode property of the System.Net.Sockets.Socket class. Used to determine if the sockets are capable of + /// automatic IPv4 and IPv6 handling. If DualMode is present the sockets automatically handle IPv4 and IPv6 connections. + /// If the DualMode is not available the system configuration determines whether IPv4 or IPv6 is used. + /// + internal static class TSocketVersionizer + { + /// + /// Creates a TcpClient according to the capabilities of the used framework. + /// + internal static TcpClient CreateTcpClient() + { + TcpClient client = null; + +#if NET45 + client = new TcpClient(AddressFamily.InterNetworkV6); + client.Client.DualMode = true; +#else + client = new TcpClient(AddressFamily.InterNetwork); +#endif + + return client; + } + + /// + /// Creates a TcpListener according to the capabilities of the used framework. + /// + internal static TcpListener CreateTcpListener(Int32 port) + { + TcpListener listener = null; + +#if NET45 + listener = new TcpListener(System.Net.IPAddress.IPv6Any, port); + listener.Server.DualMode = true; +#else + + listener = new TcpListener(System.Net.IPAddress.Any, port); +#endif + + return listener; + } + } +} diff --git a/lib/csharp/src/Transport/TStreamTransport.cs b/lib/csharp/src/Transport/TStreamTransport.cs new file mode 100644 index 00000000000..304599faa3e --- /dev/null +++ b/lib/csharp/src/Transport/TStreamTransport.cs @@ -0,0 +1,128 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.IO; + +namespace Thrift.Transport +{ + public class TStreamTransport : TTransport + { + protected Stream inputStream; + protected Stream outputStream; + + protected TStreamTransport() + { + } + + public TStreamTransport(Stream inputStream, Stream outputStream) + { + this.inputStream = inputStream; + this.outputStream = outputStream; + } + + public Stream OutputStream + { + get { return outputStream; } + } + + public Stream InputStream + { + get { return inputStream; } + } + + public override bool IsOpen + { + get { return true; } + } + + public override void Open() + { + } + + public override void Close() + { + if (inputStream != null) + { + inputStream.Close(); + inputStream = null; + } + if (outputStream != null) + { + outputStream.Close(); + outputStream = null; + } + } + + public override int Read(byte[] buf, int off, int len) + { + if (inputStream == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot read from null inputstream"); + } + + return inputStream.Read(buf, off, len); + } + + public override void Write(byte[] buf, int off, int len) + { + if (outputStream == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot write to null outputstream"); + } + + outputStream.Write(buf, off, len); + } + + public override void Flush() + { + if (outputStream == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot flush null outputstream"); + } + + outputStream.Flush(); + } + + + #region " IDisposable Support " + private bool _IsDisposed; + + // IDisposable + protected override void Dispose(bool disposing) + { + if (!_IsDisposed) + { + if (disposing) + { + if (InputStream != null) + InputStream.Dispose(); + if (OutputStream != null) + OutputStream.Dispose(); + } + } + _IsDisposed = true; + } + #endregion + } +} diff --git a/lib/csharp/src/Transport/TTLSServerSocket.cs b/lib/csharp/src/Transport/TTLSServerSocket.cs new file mode 100644 index 00000000000..716a97ca84a --- /dev/null +++ b/lib/csharp/src/Transport/TTLSServerSocket.cs @@ -0,0 +1,223 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; + +namespace Thrift.Transport +{ + /// + /// SSL Server Socket Wrapper Class + /// + public class TTLSServerSocket : TServerTransport + { + /// + /// Underlying tcp server + /// + private TcpListener server = null; + + /// + /// The port where the socket listen + /// + private int port = 0; + + /// + /// Timeout for the created server socket + /// + private readonly int clientTimeout; + + /// + /// Whether or not to wrap new TSocket connections in buffers + /// + private bool useBufferedSockets = false; + + /// + /// The servercertificate with the private- and public-key + /// + private X509Certificate serverCertificate; + + /// + /// The function to validate the client certificate. + /// + private RemoteCertificateValidationCallback clientCertValidator; + + /// + /// The function to determine which certificate to use. + /// + private LocalCertificateSelectionCallback localCertificateSelectionCallback; + + /// + /// The SslProtocols value that represents the protocol used for authentication. + /// + private readonly SslProtocols sslProtocols; + + /// + /// Initializes a new instance of the class. + /// + /// The port where the server runs. + /// The certificate object. + public TTLSServerSocket(int port, X509Certificate2 certificate) + : this(port, 0, certificate) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The port where the server runs. + /// Send/receive timeout. + /// The certificate object. + public TTLSServerSocket(int port, int clientTimeout, X509Certificate2 certificate) + : this(port, clientTimeout, false, certificate) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The port where the server runs. + /// Send/receive timeout. + /// If set to true [use buffered sockets]. + /// The certificate object. + /// The certificate validator. + /// The callback to select which certificate to use. + /// The SslProtocols value that represents the protocol used for authentication. + public TTLSServerSocket( + int port, + int clientTimeout, + bool useBufferedSockets, + X509Certificate2 certificate, + RemoteCertificateValidationCallback clientCertValidator = null, + LocalCertificateSelectionCallback localCertificateSelectionCallback = null, + // TODO: Enable Tls11 and Tls12 (TLS 1.1 and 1.2) by default once we start using .NET 4.5+. + SslProtocols sslProtocols = SslProtocols.Tls) + { + if (!certificate.HasPrivateKey) + { + throw new TTransportException(TTransportException.ExceptionType.Unknown, "Your server-certificate needs to have a private key"); + } + + this.port = port; + this.clientTimeout = clientTimeout; + this.serverCertificate = certificate; + this.useBufferedSockets = useBufferedSockets; + this.clientCertValidator = clientCertValidator; + this.localCertificateSelectionCallback = localCertificateSelectionCallback; + this.sslProtocols = sslProtocols; + try + { + // Create server socket + this.server = TSocketVersionizer.CreateTcpListener(this.port); + this.server.Server.NoDelay = true; + } + catch (Exception ex) + { + server = null; + throw new TTransportException("Could not create ServerSocket on port " + this.port + ".", ex); + } + } + + /// + /// Starts the server. + /// + public override void Listen() + { + // Make sure accept is not blocking + if (this.server != null) + { + try + { + this.server.Start(); + } + catch (SocketException sx) + { + throw new TTransportException("Could not accept on listening socket: " + sx.Message, sx); + } + } + } + + /// + /// Callback for Accept Implementation + /// + /// + /// TTransport-object. + /// + protected override TTransport AcceptImpl() + { + if (this.server == null) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "No underlying server socket."); + } + + try + { + TcpClient client = this.server.AcceptTcpClient(); + client.SendTimeout = client.ReceiveTimeout = this.clientTimeout; + + //wrap the client in an SSL Socket passing in the SSL cert + TTLSSocket socket = new TTLSSocket( + client, + this.serverCertificate, + true, + this.clientCertValidator, + this.localCertificateSelectionCallback, + this.sslProtocols); + + socket.setupTLS(); + + if (useBufferedSockets) + { + TBufferedTransport trans = new TBufferedTransport(socket); + return trans; + } + else + { + return socket; + } + + } + catch (Exception ex) + { + throw new TTransportException(ex.ToString(), ex); + } + } + + /// + /// Stops the Server + /// + public override void Close() + { + if (this.server != null) + { + try + { + this.server.Stop(); + } + catch (Exception ex) + { + throw new TTransportException("WARNING: Could not close server socket: " + ex, ex); + } + this.server = null; + } + } + } +} diff --git a/lib/csharp/src/Transport/TTLSSocket.cs b/lib/csharp/src/Transport/TTLSSocket.cs new file mode 100644 index 00000000000..06286dc8b50 --- /dev/null +++ b/lib/csharp/src/Transport/TTLSSocket.cs @@ -0,0 +1,445 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; + +namespace Thrift.Transport +{ + /// + /// SSL Socket Wrapper class + /// + public class TTLSSocket : TStreamTransport + { + /// + /// Internal TCP Client + /// + private TcpClient client; + + /// + /// The host + /// + private string host; + + /// + /// The port + /// + private int port; + + /// + /// The timeout for the connection + /// + private int timeout; + + /// + /// Internal SSL Stream for IO + /// + private SslStream secureStream; + + /// + /// Defines wheter or not this socket is a server socket
+ /// This is used for the TLS-authentication + ///
+ private bool isServer; + + /// + /// The certificate + /// + private X509Certificate certificate; + + /// + /// User defined certificate validator. + /// + private RemoteCertificateValidationCallback certValidator; + + /// + /// The function to determine which certificate to use. + /// + private LocalCertificateSelectionCallback localCertificateSelectionCallback; + + /// + /// The SslProtocols value that represents the protocol used for authentication.SSL protocols to be used. + /// + private readonly SslProtocols sslProtocols; + + /// + /// Initializes a new instance of the class. + /// + /// An already created TCP-client + /// The certificate. + /// if set to true [is server]. + /// User defined cert validator. + /// The callback to select which certificate to use. + /// The SslProtocols value that represents the protocol used for authentication. + public TTLSSocket( + TcpClient client, + X509Certificate certificate, + bool isServer = false, + RemoteCertificateValidationCallback certValidator = null, + LocalCertificateSelectionCallback localCertificateSelectionCallback = null, + // TODO: Enable Tls11 and Tls12 (TLS 1.1 and 1.2) by default once we start using .NET 4.5+. + SslProtocols sslProtocols = SslProtocols.Tls) + { + this.client = client; + this.certificate = certificate; + this.certValidator = certValidator; + this.localCertificateSelectionCallback = localCertificateSelectionCallback; + this.sslProtocols = sslProtocols; + this.isServer = isServer; + if (isServer && certificate == null) + { + throw new ArgumentException("TTLSSocket needs certificate to be used for server", "certificate"); + } + + if (IsOpen) + { + base.inputStream = client.GetStream(); + base.outputStream = client.GetStream(); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The host, where the socket should connect to. + /// The port. + /// The certificate path. + /// User defined cert validator. + /// The callback to select which certificate to use. + /// The SslProtocols value that represents the protocol used for authentication. + public TTLSSocket( + string host, + int port, + string certificatePath, + RemoteCertificateValidationCallback certValidator = null, + LocalCertificateSelectionCallback localCertificateSelectionCallback = null, + SslProtocols sslProtocols = SslProtocols.Tls) + : this(host, port, 0, X509Certificate.CreateFromCertFile(certificatePath), certValidator, localCertificateSelectionCallback, sslProtocols) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The host, where the socket should connect to. + /// The port. + /// The certificate. + /// User defined cert validator. + /// The callback to select which certificate to use. + /// The SslProtocols value that represents the protocol used for authentication. + public TTLSSocket( + string host, + int port, + X509Certificate certificate = null, + RemoteCertificateValidationCallback certValidator = null, + LocalCertificateSelectionCallback localCertificateSelectionCallback = null, + SslProtocols sslProtocols = SslProtocols.Tls) + : this(host, port, 0, certificate, certValidator, localCertificateSelectionCallback, sslProtocols) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The host, where the socket should connect to. + /// The port. + /// The timeout. + /// The certificate. + /// User defined cert validator. + /// The callback to select which certificate to use. + /// The SslProtocols value that represents the protocol used for authentication. + public TTLSSocket( + string host, + int port, + int timeout, + X509Certificate certificate, + RemoteCertificateValidationCallback certValidator = null, + LocalCertificateSelectionCallback localCertificateSelectionCallback = null, + SslProtocols sslProtocols = SslProtocols.Tls) + { + this.host = host; + this.port = port; + this.timeout = timeout; + this.certificate = certificate; + this.certValidator = certValidator; + this.localCertificateSelectionCallback = localCertificateSelectionCallback; + this.sslProtocols = sslProtocols; + + InitSocket(); + } + + /// + /// Creates the TcpClient and sets the timeouts + /// + private void InitSocket() + { + client = TSocketVersionizer.CreateTcpClient(); + client.ReceiveTimeout = client.SendTimeout = timeout; + client.Client.NoDelay = true; + } + + /// + /// Sets Send / Recv Timeout for IO + /// + public int Timeout + { + set + { + this.client.ReceiveTimeout = this.client.SendTimeout = this.timeout = value; + } + } + + /// + /// Gets the TCP client. + /// + public TcpClient TcpClient + { + get + { + return client; + } + } + + /// + /// Gets the host. + /// + public string Host + { + get + { + return host; + } + } + + /// + /// Gets the port. + /// + public int Port + { + get + { + return port; + } + } + + /// + /// Gets a value indicating whether TCP Client is Cpen + /// + public override bool IsOpen + { + get + { + if (this.client == null) + { + return false; + } + + return this.client.Connected; + } + } + + /// + /// Validates the certificates!
+ ///
+ /// The sender-object. + /// The used certificate. + /// The certificate chain. + /// An enum, which lists all the errors from the .NET certificate check. + /// + private bool DefaultCertificateValidator(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslValidationErrors) + { + return (sslValidationErrors == SslPolicyErrors.None); + } + + /// + /// Connects to the host and starts the routine, which sets up the TLS + /// + public override void Open() + { + if (IsOpen) + { + throw new TTransportException(TTransportException.ExceptionType.AlreadyOpen, "Socket already connected"); + } + + if (string.IsNullOrEmpty(host)) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open null host"); + } + + if (port <= 0) + { + throw new TTransportException(TTransportException.ExceptionType.NotOpen, "Cannot open without port"); + } + + if (client == null) + { + InitSocket(); + } + + if (timeout == 0) // no timeout -> infinite + { + client.Connect(host, port); + } + else // we have a timeout -> use it + { + ConnectHelper hlp = new ConnectHelper(client); + IAsyncResult asyncres = client.BeginConnect(host, port, new AsyncCallback(ConnectCallback), hlp); + bool bConnected = asyncres.AsyncWaitHandle.WaitOne(timeout) && client.Connected; + if (!bConnected) + { + lock (hlp.Mutex) + { + if (hlp.CallbackDone) + { + asyncres.AsyncWaitHandle.Close(); + client.Close(); + } + else + { + hlp.DoCleanup = true; + client = null; + } + } + throw new TTransportException(TTransportException.ExceptionType.TimedOut, "Connect timed out"); + } + } + + setupTLS(); + } + + /// + /// Creates a TLS-stream and lays it over the existing socket + /// + public void setupTLS() + { + RemoteCertificateValidationCallback validator = this.certValidator ?? DefaultCertificateValidator; + + if (this.localCertificateSelectionCallback != null) + { + this.secureStream = new SslStream( + this.client.GetStream(), + false, + validator, + this.localCertificateSelectionCallback + ); + } + else + { + this.secureStream = new SslStream( + this.client.GetStream(), + false, + validator + ); + } + + try + { + if (isServer) + { + // Server authentication + this.secureStream.AuthenticateAsServer(this.certificate, this.certValidator != null, sslProtocols, true); + } + else + { + // Client authentication + X509CertificateCollection certs = certificate != null ? new X509CertificateCollection { certificate } : new X509CertificateCollection(); + this.secureStream.AuthenticateAsClient(host, certs, sslProtocols, true); + } + } + catch (Exception) + { + this.Close(); + throw; + } + + inputStream = this.secureStream; + outputStream = this.secureStream; + } + + static void ConnectCallback(IAsyncResult asyncres) + { + ConnectHelper hlp = asyncres.AsyncState as ConnectHelper; + lock (hlp.Mutex) + { + hlp.CallbackDone = true; + + try + { + if (hlp.Client.Client != null) + hlp.Client.EndConnect(asyncres); + } + catch (Exception) + { + // catch that away + } + + if (hlp.DoCleanup) + { + try + { + asyncres.AsyncWaitHandle.Close(); + } + catch (Exception) { } + + try + { + if (hlp.Client is IDisposable) + ((IDisposable)hlp.Client).Dispose(); + } + catch (Exception) { } + hlp.Client = null; + } + } + } + + private class ConnectHelper + { + public object Mutex = new object(); + public bool DoCleanup = false; + public bool CallbackDone = false; + public TcpClient Client; + public ConnectHelper(TcpClient client) + { + Client = client; + } + } + + /// + /// Closes the SSL Socket + /// + public override void Close() + { + base.Close(); + if (this.client != null) + { + this.client.Close(); + this.client = null; + } + + if (this.secureStream != null) + { + this.secureStream.Close(); + this.secureStream = null; + } + } + } +} diff --git a/lib/csharp/src/Transport/TTransport.cs b/lib/csharp/src/Transport/TTransport.cs new file mode 100644 index 00000000000..5e4ac22ea90 --- /dev/null +++ b/lib/csharp/src/Transport/TTransport.cs @@ -0,0 +1,146 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; +using System.IO; + +namespace Thrift.Transport +{ + public abstract class TTransport : IDisposable + { + public abstract bool IsOpen + { + get; + } + + private byte[] _peekBuffer = new byte[1]; + private bool _hasPeekByte; + + public bool Peek() + { + //If we already have a byte read but not consumed, do nothing. + if (_hasPeekByte) + return true; + + //If transport closed we can't peek. + if (!IsOpen) + return false; + + //Try to read one byte. If succeeds we will need to store it for the next read. + try + { + int bytes = Read(_peekBuffer, 0, 1); + if (bytes == 0) + return false; + } + catch (IOException) + { + return false; + } + + _hasPeekByte = true; + return true; + } + + public abstract void Open(); + + public abstract void Close(); + + protected static void ValidateBufferArgs(byte[] buf, int off, int len) + { + if (buf == null) + throw new ArgumentNullException("buf"); + if (off < 0) + throw new ArgumentOutOfRangeException("Buffer offset is smaller than zero."); + if (len < 0) + throw new ArgumentOutOfRangeException("Buffer length is smaller than zero."); + if (off + len > buf.Length) + throw new ArgumentOutOfRangeException("Not enough data."); + } + + public abstract int Read(byte[] buf, int off, int len); + + public int ReadAll(byte[] buf, int off, int len) + { + ValidateBufferArgs(buf, off, len); + int got = 0; + + //If we previously peeked a byte, we need to use that first. + if (_hasPeekByte) + { + buf[off + got++] = _peekBuffer[0]; + _hasPeekByte = false; + } + + while (got < len) + { + int ret = Read(buf, off + got, len - got); + if (ret <= 0) + { + throw new TTransportException( + TTransportException.ExceptionType.EndOfFile, + "Cannot read, Remote side has closed"); + } + got += ret; + } + return got; + } + + public virtual void Write(byte[] buf) + { + Write(buf, 0, buf.Length); + } + + public abstract void Write(byte[] buf, int off, int len); + + public virtual void Flush() + { + } + + public virtual IAsyncResult BeginFlush(AsyncCallback callback, object state) + { + throw new TTransportException( + TTransportException.ExceptionType.Unknown, + "Asynchronous operations are not supported by this transport."); + } + + public virtual void EndFlush(IAsyncResult asyncResult) + { + throw new TTransportException( + TTransportException.ExceptionType.Unknown, + "Asynchronous operations are not supported by this transport."); + } + + #region " IDisposable Support " + // IDisposable + protected abstract void Dispose(bool disposing); + + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above. + Dispose(true); + GC.SuppressFinalize(this); + } + #endregion + } +} diff --git a/lib/csharp/src/Transport/TTransportException.cs b/lib/csharp/src/Transport/TTransportException.cs new file mode 100644 index 00000000000..7f6cc1889ea --- /dev/null +++ b/lib/csharp/src/Transport/TTransportException.cs @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; + +namespace Thrift.Transport +{ + public class TTransportException : TException + { + protected ExceptionType type; + + public TTransportException() + : base() + { + } + + public TTransportException(ExceptionType type) + : this() + { + this.type = type; + } + + public TTransportException(ExceptionType type, string message, Exception inner = null) + : base(message, inner) + { + this.type = type; + } + + public TTransportException(string message, Exception inner = null) + : base(message, inner) + { + } + + public ExceptionType Type + { + get { return type; } + } + + public enum ExceptionType + { + Unknown, + NotOpen, + AlreadyOpen, + TimedOut, + EndOfFile, + Interrupted + } + } +} diff --git a/lib/csharp/src/Transport/TTransportFactory.cs b/lib/csharp/src/Transport/TTransportFactory.cs new file mode 100644 index 00000000000..47a0c62651a --- /dev/null +++ b/lib/csharp/src/Transport/TTransportFactory.cs @@ -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. + * + * Contains some contributions under the Thrift Software License. + * Please see doc/old-thrift-license.txt in the Thrift distribution for + * details. + */ + +using System; + +namespace Thrift.Transport +{ + /// + /// From Mark Slee & Aditya Agarwal of Facebook: + /// Factory class used to create wrapped instance of Transports. + /// This is used primarily in servers, which get Transports from + /// a ServerTransport and then may want to mutate them (i.e. create + /// a BufferedTransport from the underlying base transport) + /// + public class TTransportFactory + { + public virtual TTransport GetTransport(TTransport trans) + { + return trans; + } + } +} diff --git a/lib/csharp/src/thrift.snk b/lib/csharp/src/thrift.snk new file mode 100644 index 0000000000000000000000000000000000000000..97bc5812b81ce10bd7a3e7fa85ffe4fca58837a4 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096M(P*E7L9A{Nz%0-6F4Ni!Qh(Kbg{5KA z2E1b23-+!Xd}C$U<2C<(&MT@p-O1qWT;=lE^6FoDjaj)hfK()avE=qA0UjD_dnyJKp4oD4IAF%k%(##bch;%0k!ErNuo!=V%U6um{k+ zxGJw2Q3!4o9q~6a_(HZlq8e8vm^F-F8s*Oq9GC*du(tuh3*S(r46R#&$m*dVSmw9< zkJro_Em0r8sB0u|@B$Ms&W(3}U!PWN$0Rb0UTs5hRh#5^;9Sd{Oc1;2rF@$Tv@h%{ iLR4J<2AKHW9M~)kpNz1bB-?e{-!;;q{yCP(dwHkK- literal 0 HcmV?d00001 diff --git a/lib/csharp/test/JSON/JSONTest.csproj b/lib/csharp/test/JSON/JSONTest.csproj new file mode 100644 index 00000000000..f07d43eece1 --- /dev/null +++ b/lib/csharp/test/JSON/JSONTest.csproj @@ -0,0 +1,85 @@ + + + + + Debug + x86 + 8.0.30703 + 2.0 + {E37A0034-DCBF-4886-A0DA-25A03D12D975} + Exe + Properties + JSONTest + JSONTest + v4.0 + + + 512 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + {499EB63C-D74C-47E8-AE48-A2FC94538E9D} + Thrift + + + + + \ No newline at end of file diff --git a/lib/csharp/test/JSON/Program.cs b/lib/csharp/test/JSON/Program.cs new file mode 100644 index 00000000000..f61388ae7c2 --- /dev/null +++ b/lib/csharp/test/JSON/Program.cs @@ -0,0 +1,95 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using Thrift.Protocol; +using Thrift.Transport; + +namespace JSONTest +{ + class Program + { + static void Main(string[] args) + { + TestThrift2365(); // JSON binary decodes too much data + TestThrift2336(); // hex encoding using \uXXXX where 0xXXXX > 0xFF + TestThrift3403(); // JSON escaped unicode surrogate pair support. + } + + + public static void TestThrift2365() + { + var rnd = new Random(); + for (var len = 0; len < 10; ++len) + { + byte[] dataWritten = new byte[len]; + rnd.NextBytes(dataWritten); + + Stream stm = new MemoryStream(); + TTransport trans = new TStreamTransport(null, stm); + TProtocol prot = new TJSONProtocol(trans); + prot.WriteBinary(dataWritten); + + stm.Position = 0; + trans = new TStreamTransport(stm, null); + prot = new TJSONProtocol(trans); + byte[] dataRead = prot.ReadBinary(); + + Debug.Assert(dataRead.Length == dataWritten.Length); + for (var i = 0; i < dataRead.Length; ++i) + Debug.Assert(dataRead[i] == dataWritten[i]); + } + } + + + public static void TestThrift2336() + { + const string RUSSIAN_TEXT = "\u0420\u0443\u0441\u0441\u043a\u043e\u0435 \u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435"; + const string RUSSIAN_JSON = "\"\\u0420\\u0443\\u0441\\u0441\\u043a\\u043e\\u0435 \\u041d\\u0430\\u0437\\u0432\\u0430\\u043d\\u0438\\u0435\""; + + // prepare buffer with JSON data + byte[] rawBytes = new byte[RUSSIAN_JSON.Length]; + for (var i = 0; i < RUSSIAN_JSON.Length; ++i) + rawBytes[i] = (byte)(RUSSIAN_JSON[i] & (char)0xFF); // only low bytes + + // parse and check + var stm = new MemoryStream(rawBytes); + var trans = new TStreamTransport(stm, null); + var prot = new TJSONProtocol(trans); + Debug.Assert(prot.ReadString() == RUSSIAN_TEXT, "reading JSON with hex-encoded chars > 8 bit"); + } + + public static void TestThrift3403() + { + string GCLEF_TEXT = "\ud834\udd1e"; + const string GCLEF_JSON = "\"\\ud834\\udd1e\""; + + // parse and check + var stm = new MemoryStream(Encoding.UTF8.GetBytes(GCLEF_JSON)); + var trans = new TStreamTransport(stm, null); + var prot = new TJSONProtocol(trans); + Debug.Assert(prot.ReadString() == GCLEF_TEXT, "reading JSON with surrogate pair hex-encoded chars"); + } + } +} diff --git a/lib/csharp/test/JSON/Properties/AssemblyInfo.cs b/lib/csharp/test/JSON/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..fdff4a1a5c4 --- /dev/null +++ b/lib/csharp/test/JSON/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die mit einer Assembly verknüpft sind. +[assembly: AssemblyTitle("JSONTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("The Apache Software Foundation")] +[assembly: AssemblyProduct("Thrift")] +[assembly: AssemblyCopyright("The Apache Software Foundation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar +// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von +// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("2b2e7d56-3e65-4368-92d7-e34d56b7105e")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/lib/csharp/test/JSON/app.config b/lib/csharp/test/JSON/app.config new file mode 100644 index 00000000000..9c1919d4fd3 --- /dev/null +++ b/lib/csharp/test/JSON/app.config @@ -0,0 +1,21 @@ + + + + diff --git a/lib/csharp/test/Multiplex/Client/Multiplex.Test.Client.cs b/lib/csharp/test/Multiplex/Client/Multiplex.Test.Client.cs new file mode 100644 index 00000000000..c810a08917f --- /dev/null +++ b/lib/csharp/test/Multiplex/Client/Multiplex.Test.Client.cs @@ -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. + */ + +using System; +using System.Collections.Generic; +using Thrift.Collections; +using Thrift.Transport; +using Thrift.Protocol; +using Thrift.Server; +using Thrift; +using Test.Multiplex; + +namespace Test.Multiplex.Client +{ + public class TestClient + { + static void Execute(int port) + { + try + { + TTransport trans; + trans = new TSocket("localhost", port); + trans = new TFramedTransport(trans); + trans.Open(); + + TProtocol Protocol = new TBinaryProtocol(trans, true, true); + + TMultiplexedProtocol multiplex; + + multiplex = new TMultiplexedProtocol(Protocol, Constants.NAME_BENCHMARKSERVICE); + BenchmarkService.Iface bench = new BenchmarkService.Client(multiplex); + + multiplex = new TMultiplexedProtocol(Protocol, Constants.NAME_AGGR); + Aggr.Iface aggr = new Aggr.Client(multiplex); + + for (sbyte i = 1; 10 >= i; ++i) + { + aggr.addValue(bench.fibonacci(i)); + } + + foreach (int k in aggr.getValues()) + { + Console.Write(k.ToString() + " "); + Console.WriteLine(""); + } + trans.Close(); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + } + + static void Main(string[] args) + { + int port = 9090; + if (args.Length > 0) + { + port = ushort.Parse(args[0]); + } + Execute(port); + Console.WriteLine("done."); + } + } +} + diff --git a/lib/csharp/test/Multiplex/Client/MultiplexClient.csproj b/lib/csharp/test/Multiplex/Client/MultiplexClient.csproj new file mode 100644 index 00000000000..551640ba6ba --- /dev/null +++ b/lib/csharp/test/Multiplex/Client/MultiplexClient.csproj @@ -0,0 +1,148 @@ + + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {5E91DA17-E548-415F-8C9F-9E84EDF8EE06} + Exe + Properties + MultiplexClient + MultiplexClient + v3.5 + 512 + false + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 0.14.0.0 + false + true + + + true + full + false + ..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + + + pdbonly + true + ..\bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + + + + + + + Multiplex.Test.Common.cs + + + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 2.0 %28x86%29 + false + + + False + .NET Framework 3.0 %28x86%29 + false + + + False + .NET Framework 3.5 + true + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + {499EB63C-D74C-47E8-AE48-A2FC94538E9D} + Thrift + + + + + + rmdir /s /q "$(ProjectDir)gen-csharp" +del /f /q "$(ProjectDir)ThriftImpl.dll" +SET OUTPUT_DIR=$(ProjectDir) + +SET THRIFT_FILE=$(ProjectDir)\..\..\..\..\..\contrib\async-test\aggr.thrift +for %25%25I in ("%25OUTPUT_DIR%25") do set SHORT_DIR=%25%25~fsI +for %25%25I in ("%25THRIFT_FILE%25") do set THRIFT_SHORT=%25%25~fsI +"$(ProjectDir)\..\..\..\..\..\compiler\cpp\thrift.exe" --gen csharp -o %25SHORT_DIR%25 %25THRIFT_SHORT%25 + +SET THRIFT_FILE=$(ProjectDir)\..\..\..\..\..\lib\rb\benchmark\Benchmark.thrift +for %25%25I in ("%25OUTPUT_DIR%25") do set SHORT_DIR=%25%25~fsI +for %25%25I in ("%25THRIFT_FILE%25") do set THRIFT_SHORT=%25%25~fsI +"$(ProjectDir)\..\..\..\..\..\compiler\cpp\thrift.exe" --gen csharp -o %25SHORT_DIR%25 %25THRIFT_SHORT%25 + + + + diff --git a/lib/csharp/test/Multiplex/Client/Properties/AssemblyInfo.cs b/lib/csharp/test/Multiplex/Client/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..ce6692fb0d3 --- /dev/null +++ b/lib/csharp/test/Multiplex/Client/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MultiplexClient")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("The Apache Software Foundation")] +[assembly: AssemblyProduct("Thrift")] +[assembly: AssemblyCopyright("The Apache Software Foundation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("66FC61E5-420B-4b56-8012-D6D6CE22537F")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.14.0.0")] +[assembly: AssemblyFileVersion("0.14.0.0")] diff --git a/lib/csharp/test/Multiplex/Makefile.am b/lib/csharp/test/Multiplex/Makefile.am new file mode 100644 index 00000000000..9c1f1b8b75c --- /dev/null +++ b/lib/csharp/test/Multiplex/Makefile.am @@ -0,0 +1,63 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +GENERATED = \ + gen-csharp/Aggr.cs \ + gen-csharp/BenchmarkService.cs \ + gen-csharp/Error.cs + +BUILT_SOURCES = $(GENERATED) + +THRIFT = $(top_builddir)/compiler/cpp/thrift + +gen-csharp/Aggr.cs: $(top_srcdir)/contrib/async-test/aggr.thrift + $(THRIFT) --gen csharp $< + +gen-csharp/BenchmarkService.cs gen-csharp/Error.cs: $(top_srcdir)/lib/rb/benchmark/Benchmark.thrift + $(THRIFT) --gen csharp $< + +ThriftImpl.dll: Multiplex.Test.Common.cs $(GENERATED) ../../Thrift.dll + $(CSC) $(CSC_DEFINES) -t:library -out:./ThriftImpl.dll -reference:../../Thrift.dll $(GENERATED) $< + +MultiplexClient.exe: Client/Multiplex.Test.Client.cs ThriftImpl.dll + $(CSC) $(CSC_DEFINES) -out:$@ -reference:../../Thrift.dll -reference:ThriftImpl.dll $< + +MultiplexServer.exe: Server/Multiplex.Test.Server.cs ThriftImpl.dll + $(CSC) $(CSC_DEFINES) -out:$@ -reference:../../Thrift.dll -reference:ThriftImpl.dll $< + +CLEANFILES = \ + MultiplexClient.exe \ + MultiplexServer.exe \ + ThriftImpl.dll + +DISTCLEANFILES = \ + Makefile.in + +clean-local: + $(RM) -rf gen-csharp + +dist-hook: + $(RM) -r $(distdir)/gen-csharp/ + +TESTPORT = 9501 +check-local: MultiplexServer.exe MultiplexClient.exe + echo $(TESTPORT) + MONO_PATH=../../ timeout 10 mono MultiplexServer.exe $(TESTPORT) & + sleep 1 + MONO_PATH=../../ mono MultiplexClient.exe $(TESTPORT) diff --git a/lib/csharp/test/Multiplex/Multiplex.Test.Common.cs b/lib/csharp/test/Multiplex/Multiplex.Test.Common.cs new file mode 100644 index 00000000000..a687852d91c --- /dev/null +++ b/lib/csharp/test/Multiplex/Multiplex.Test.Common.cs @@ -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. + */ + +// Distributed under the Thrift Software License +// +// See accompanying file LICENSE or visit the Thrift site at: +// http://developers.facebook.com/thrift/ +using System; +using System.Collections.Generic; +using Thrift.Collections; +using Thrift.Transport; +using Thrift.Protocol; +using Thrift.Server; + +namespace Test.Multiplex +{ + public class Constants + { + public const string NAME_BENCHMARKSERVICE = "BenchmarkService"; + public const string NAME_AGGR = "Aggr"; + } +} + + diff --git a/lib/csharp/test/Multiplex/Server/Multiplex.Test.Server.cs b/lib/csharp/test/Multiplex/Server/Multiplex.Test.Server.cs new file mode 100644 index 00000000000..9786189bb31 --- /dev/null +++ b/lib/csharp/test/Multiplex/Server/Multiplex.Test.Server.cs @@ -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. + */ + +using System; +using System.Collections.Generic; +using Thrift.Collections; +using Thrift.Transport; +using Thrift.Protocol; +using Thrift.Server; +using Thrift; +using Test.Multiplex; + +namespace Test.Multiplex.Server +{ + public class TestServer + { + class BenchmarkServiceImpl : BenchmarkService.Iface + { + public int fibonacci(sbyte n) + { + int prev, next, result; + prev = 0; + result = 1; + while (n > 0) + { + next = result + prev; + prev = result; + result = next; + --n; + } + return result; + } + } + + class AggrServiceImpl : Aggr.Iface + { + List values = new List(); + + public void addValue(int value) + { + values.Add(value); + } + + public List getValues() + { + return values; + } + } + + static void Execute(int port) + { + try + { + // create protocol factory, default to BinaryProtocol + TProtocolFactory ProtocolFactory = new TBinaryProtocol.Factory(true,true); + TServerTransport servertrans = new TServerSocket(port, 0, false); + TTransportFactory TransportFactory = new TFramedTransport.Factory(); + + BenchmarkService.Iface benchHandler = new BenchmarkServiceImpl(); + TProcessor benchProcessor = new BenchmarkService.Processor(benchHandler); + + Aggr.Iface aggrHandler = new AggrServiceImpl(); + TProcessor aggrProcessor = new Aggr.Processor(aggrHandler); + + TMultiplexedProcessor multiplex = new TMultiplexedProcessor(); + multiplex.RegisterProcessor(Constants.NAME_BENCHMARKSERVICE, benchProcessor); + multiplex.RegisterProcessor(Constants.NAME_AGGR, aggrProcessor); + + TServer ServerEngine = new TSimpleServer(multiplex, servertrans, TransportFactory, ProtocolFactory); + + Console.WriteLine("Starting the server ..."); + ServerEngine.Serve(); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + } + } + + static void Main(string[] args) + { + int port = 9090; + if (args.Length > 0) + { + port = ushort.Parse(args[0]); + } + Execute(port); + } + } +} + diff --git a/lib/csharp/test/Multiplex/Server/MultiplexServer.csproj b/lib/csharp/test/Multiplex/Server/MultiplexServer.csproj new file mode 100644 index 00000000000..771ee1266e0 --- /dev/null +++ b/lib/csharp/test/Multiplex/Server/MultiplexServer.csproj @@ -0,0 +1,148 @@ + + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {D592BDF3-0DCE-48FB-890F-E4AE1D9CE7CD} + Exe + Properties + MultiplexServer + MultiplexServer + v3.5 + 512 + false + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 0.14.0.0 + false + true + + + true + full + false + ..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + + + pdbonly + true + ..\bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + + + + + + + Multiplex.Test.Common.cs + + + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 2.0 %28x86%29 + false + + + False + .NET Framework 3.0 %28x86%29 + false + + + False + .NET Framework 3.5 + true + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + {499EB63C-D74C-47E8-AE48-A2FC94538E9D} + Thrift + + + + + + rmdir /s /q "$(ProjectDir)gen-csharp" +del /f /q "$(ProjectDir)ThriftImpl.dll" +SET OUTPUT_DIR=$(ProjectDir) + +SET THRIFT_FILE=$(ProjectDir)\..\..\..\..\..\contrib\async-test\aggr.thrift +for %25%25I in ("%25OUTPUT_DIR%25") do set SHORT_DIR=%25%25~fsI +for %25%25I in ("%25THRIFT_FILE%25") do set THRIFT_SHORT=%25%25~fsI +"$(ProjectDir)\..\..\..\..\..\compiler\cpp\thrift.exe" --gen csharp -o %25SHORT_DIR%25 %25THRIFT_SHORT%25 + +SET THRIFT_FILE=$(ProjectDir)\..\..\..\..\..\lib\rb\benchmark\Benchmark.thrift +for %25%25I in ("%25OUTPUT_DIR%25") do set SHORT_DIR=%25%25~fsI +for %25%25I in ("%25THRIFT_FILE%25") do set THRIFT_SHORT=%25%25~fsI +"$(ProjectDir)\..\..\..\..\..\compiler\cpp\thrift.exe" --gen csharp -o %25SHORT_DIR%25 %25THRIFT_SHORT%25 + + + + diff --git a/lib/csharp/test/Multiplex/Server/Properties/AssemblyInfo.cs b/lib/csharp/test/Multiplex/Server/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..50b11af2b23 --- /dev/null +++ b/lib/csharp/test/Multiplex/Server/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MultiplexServer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("The Apache Software Foundation")] +[assembly: AssemblyProduct("Thrift")] +[assembly: AssemblyCopyright("The Apache Software Foundation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("F2F436C1-3D4F-411a-ADC3-B98848476A8E")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.14.0.0")] +[assembly: AssemblyFileVersion("0.14.0.0")] diff --git a/lib/csharp/test/ThriftMVCTest/App_Start/FilterConfig.cs b/lib/csharp/test/ThriftMVCTest/App_Start/FilterConfig.cs new file mode 100644 index 00000000000..855184fd23f --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/App_Start/FilterConfig.cs @@ -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. + */ + +using System.Web.Mvc; + +namespace ThriftMVCTest +{ + public static class FilterConfig + { + public static void RegisterGlobalFilters(GlobalFilterCollection filters) + { + filters.Add(new HandleErrorAttribute()); + } + } +} diff --git a/lib/csharp/test/ThriftMVCTest/App_Start/RouteConfig.cs b/lib/csharp/test/ThriftMVCTest/App_Start/RouteConfig.cs new file mode 100644 index 00000000000..b4b6023d69a --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/App_Start/RouteConfig.cs @@ -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. + */ + +using System.Web.Mvc; +using System.Web.Routing; + +namespace ThriftMVCTest +{ + public static class RouteConfig + { + public static void RegisterRoutes(RouteCollection routes) + { + routes.IgnoreRoute("{resource}.thrift"); + routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); + + routes.MapRoute( + name: "Default", + url: "{controller}/{action}/{id}", + defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } + ); + } + } +} diff --git a/lib/csharp/test/ThriftMVCTest/AsyncHttpHandler.cs b/lib/csharp/test/ThriftMVCTest/AsyncHttpHandler.cs new file mode 100644 index 00000000000..7f26184fac0 --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/AsyncHttpHandler.cs @@ -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. + */ + +using Thrift.Transport; + +namespace ThriftMVCTest +{ + public class AsyncHttpHandler : THttpTaskAsyncHandler + { + public AsyncHttpHandler() + : base( + new Thrift.Test.SecondService.AsyncProcessor(new SecondServiceImpl())) + { + } + } +} \ No newline at end of file diff --git a/lib/csharp/test/ThriftMVCTest/Controllers/HomeController.cs b/lib/csharp/test/ThriftMVCTest/Controllers/HomeController.cs new file mode 100644 index 00000000000..c9a1ec43fdd --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/Controllers/HomeController.cs @@ -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. + */ + +using System; +using System.Threading.Tasks; +using System.Web.Mvc; +using Thrift.Protocol; +using Thrift.Test; +using Thrift.Transport; + +namespace ThriftMVCTest.Controllers +{ + public class HomeController : Controller + { + public ActionResult Index() + { + return View(); + } + + public async Task TestThriftAsync() + { + var baseUri = new Uri(string.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Authority, + Url.Content("~"))); + + SecondService.IAsync asyncService = + new SecondService.Client(new TBinaryProtocol(new THttpClient(new Uri(baseUri, "Async.thrift")))); + + var result = await asyncService.secondtestStringAsync("TestString"); + if (result != "testString(\"TestString\")") + { + throw new Exception("The wrong result was returned"); + } + + return RedirectToAction("Index"); + } + + public ActionResult TestThriftSync() + { + var baseUri = new Uri(string.Format("{0}://{1}{2}", Request.Url.Scheme, Request.Url.Authority, + Url.Content("~"))); + + SecondService.ISync service = + new SecondService.Client(new TBinaryProtocol(new THttpClient(new Uri(baseUri, "Sync.thrift")))); + + var result = service.secondtestString("TestString"); + if (result != "testString(\"TestString\")") + { + throw new Exception("The wrong result was returned"); + } + + return RedirectToAction("Index"); + } + } +} diff --git a/lib/csharp/test/ThriftMVCTest/Global.asax b/lib/csharp/test/ThriftMVCTest/Global.asax new file mode 100644 index 00000000000..7bb688c7ae7 --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/Global.asax @@ -0,0 +1,19 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="ThriftMVCTest.MvcApplication" Language="C#" %> + diff --git a/lib/csharp/test/ThriftMVCTest/Global.asax.cs b/lib/csharp/test/ThriftMVCTest/Global.asax.cs new file mode 100644 index 00000000000..59731efb3b5 --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/Global.asax.cs @@ -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. + */ + +using System.Web.Mvc; +using System.Web.Routing; + +namespace ThriftMVCTest +{ + public class MvcApplication : System.Web.HttpApplication + { + protected void Application_Start() + { + AreaRegistration.RegisterAllAreas(); + FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); + RouteConfig.RegisterRoutes(RouteTable.Routes); + } + } +} diff --git a/lib/csharp/test/ThriftMVCTest/Properties/AssemblyInfo.cs b/lib/csharp/test/ThriftMVCTest/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..a87107cb992 --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/Properties/AssemblyInfo.cs @@ -0,0 +1,53 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ThriftMVCTest")] +[assembly: AssemblyDescription("A web project for testing the thrift ASP.NET features.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("The Apache Software Foundation")] +[assembly: AssemblyProduct("Thrift")] +[assembly: AssemblyCopyright("The Apache Software Foundation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("366f9bd0-3c0e-48aa-b2ca-61fd4a93e427")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("0.14.0.0")] +[assembly: AssemblyFileVersion("0.14.0.0")] diff --git a/lib/csharp/test/ThriftMVCTest/SecondServiceImpl.cs b/lib/csharp/test/ThriftMVCTest/SecondServiceImpl.cs new file mode 100644 index 00000000000..fad301a351a --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/SecondServiceImpl.cs @@ -0,0 +1,37 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System.Threading.Tasks; +using Thrift.Test; + +namespace ThriftMVCTest +{ + public class SecondServiceImpl : SecondService.IAsync, SecondService.ISync + { + public Task secondtestStringAsync(string thing) + { + return Task.FromResult(thing); + } + + public string secondtestString(string thing) + { + return "testString(\"" + thing + "\")"; + } + } +} diff --git a/lib/csharp/test/ThriftMVCTest/SyncHttpHandler.cs b/lib/csharp/test/ThriftMVCTest/SyncHttpHandler.cs new file mode 100644 index 00000000000..4fe26624a2d --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/SyncHttpHandler.cs @@ -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. + */ + +using Thrift.Transport; + +namespace ThriftMVCTest +{ + public class SyncHttpHandler : THttpHandler + { + public SyncHttpHandler() + : base( + new Thrift.Test.SecondService.Processor(new SecondServiceImpl())) + { + } + } +} \ No newline at end of file diff --git a/lib/csharp/test/ThriftMVCTest/ThriftMVCTest.csproj b/lib/csharp/test/ThriftMVCTest/ThriftMVCTest.csproj new file mode 100644 index 00000000000..0eb969a04c2 --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/ThriftMVCTest.csproj @@ -0,0 +1,200 @@ + + + + + + Debug + AnyCPU + + + 2.0 + {891B4487-C7BA-427E-BBC8-4C596C229A10} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + ThriftMVCTest + ThriftMVCTest + v4.5 + false + true + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + True + ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + + + + + + True + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll + + + True + ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll + + + True + ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll + + + True + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll + + + True + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll + + + True + ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll + + + .\ThriftImpl.dll + + + + + + + + + + Global.asax + + + + + + + + + + Web.config + + + Web.config + + + + + + + + + + + + Designer + + + + + {ebce35da-cf6a-42bc-a357-a9c09b534299} + Thrift.45 + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + + + + True + True + 57482 + / + http://localhost:57482/ + False + False + + + False + + + + + + rmdir /s /q "$(ProjectDir)gen-csharp" +del /f /q "$(ProjectDir)ThriftImpl.dll" +SET OUTPUT_DIR=$(ProjectDir) +SET THRIFT_FILE=$(ProjectDir)\..\..\..\..\test\ThriftTest.thrift +for %25%25I in ("%25OUTPUT_DIR%25") do set SHORT_DIR=%25%25~fsI +for %25%25I in ("%25THRIFT_FILE%25") do set THRIFT_SHORT=%25%25~fsI +"$(ProjectDir)\..\..\..\..\compiler\cpp\Debug\thrift.exe" --gen csharp:async=true -o %25SHORT_DIR%25 %25THRIFT_SHORT%25 +"$(MSBuildToolsPath)\Csc.exe" /t:library /out:"$(ProjectDir)ThriftImpl.dll" /recurse:"$(ProjectDir)gen-csharp"\* /reference:"$(ProjectDir)..\..\src\bin\Debug\Thrift45.dll" + + + \ No newline at end of file diff --git a/lib/csharp/test/ThriftMVCTest/Views/Home/Index.cshtml b/lib/csharp/test/ThriftMVCTest/Views/Home/Index.cshtml new file mode 100644 index 00000000000..f0ca7da209e --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/Views/Home/Index.cshtml @@ -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. +*@ + +@{ + ViewBag.Title = "Home Page"; +} + +

@Html.ActionLink("Test Thrift Async Service", "TestThriftAsync")

+

@Html.ActionLink("Test Thrift Sync Service", "TestThriftSync")

diff --git a/lib/csharp/test/ThriftMVCTest/Views/Shared/_Layout.cshtml b/lib/csharp/test/ThriftMVCTest/Views/Shared/_Layout.cshtml new file mode 100644 index 00000000000..b41c99a100f --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/Views/Shared/_Layout.cshtml @@ -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. +*@ + + + + + + + Thrift ASP.NET Test + + + @RenderBody() + + diff --git a/lib/csharp/test/ThriftMVCTest/Views/Web.config b/lib/csharp/test/ThriftMVCTest/Views/Web.config new file mode 100644 index 00000000000..3c211387a95 --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/Views/Web.config @@ -0,0 +1,60 @@ + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/csharp/test/ThriftMVCTest/Views/_ViewStart.cshtml b/lib/csharp/test/ThriftMVCTest/Views/_ViewStart.cshtml new file mode 100644 index 00000000000..8cde2eeb95a --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/Views/_ViewStart.cshtml @@ -0,0 +1,22 @@ +@* + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*@ + +@{ + Layout = "~/Views/Shared/_Layout.cshtml"; +} diff --git a/lib/csharp/test/ThriftMVCTest/Web.Debug.config b/lib/csharp/test/ThriftMVCTest/Web.Debug.config new file mode 100644 index 00000000000..45d56d80993 --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/Web.Debug.config @@ -0,0 +1,45 @@ + + + + + + + + diff --git a/lib/csharp/test/ThriftMVCTest/Web.Release.config b/lib/csharp/test/ThriftMVCTest/Web.Release.config new file mode 100644 index 00000000000..157c340ca86 --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/Web.Release.config @@ -0,0 +1,46 @@ + + + + + + + + + diff --git a/lib/csharp/test/ThriftMVCTest/Web.config b/lib/csharp/test/ThriftMVCTest/Web.config new file mode 100644 index 00000000000..9c57d117cd7 --- /dev/null +++ b/lib/csharp/test/ThriftMVCTest/Web.config @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/csharp/test/ThriftMVCTest/favicon.ico b/lib/csharp/test/ThriftMVCTest/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a3a799985c43bc7309d701b2cad129023377dc71 GIT binary patch literal 32038 zcmeHwX>eTEbtY7aYbrGrkNjgie?1jXjZ#zP%3n{}GObKv$BxI7Sl;Bwl5E+Qtj&t8 z*p|m4DO#HoJC-FyvNnp8NP<{Na0LMnTtO21(rBP}?EAiNjWgeO?z`{3ZoURUQlV2d zY1Pqv{m|X_oO91|?^z!6@@~od!@OH>&BN;>c@O+yUfy5w>LccTKJJ&`-k<%M^Zvi( z<$dKp=jCnNX5Qa+M_%6g|IEv~4R84q9|7E=|Ho(Wz3f-0wPjaRL;W*N^>q%^KGRr7 zxbjSORb_c&eO;oV_DZ7ua!sPH=0c+W;`vzJ#j~-x3uj};50#vqo*0w4!LUqs*UCh9 zvy2S%$#8$K4EOa&e@~aBS65_hc~Mpu=454VT2^KzWqEpBA=ME|O;1cn?8p<+{MKJf zbK#@1wzL44m$k(?85=Obido7=C|xWKe%66$z)NrzRwR>?hK?_bbwT z@Da?lBrBL}Zemo1@!9pYRau&!ld17h{f+UV0sY(R{ET$PBB|-=Nr@l-nY6w8HEAw* zRMIQU`24Jl_IFEPcS=_HdrOP5yf81z_?@M>83Vv65$QFr9nPg(wr`Ke8 zaY4ogdnMA*F7a4Q1_uXadTLUpCk;$ZPRRJ^sMOch;rlbvUGc1R9=u;dr9YANbQ<4Z z#P|Cp9BP$FXNPolgyr1XGt$^lFPF}rmBF5rj1Kh5%dforrP8W}_qJL$2qMBS-#%-|s#BPZBSETsn_EBYcr(W5dq( z@f%}C|iN7)YN`^)h7R?Cg}Do*w-!zwZb9=BMp%Wsh@nb22hA zA{`wa8Q;yz6S)zfo%sl08^GF`9csI9BlGnEy#0^Y3b);M+n<(}6jziM7nhe57a1rj zC@(2ISYBL^UtWChKzVWgf%4LW2Tqg_^7jMw`C$KvU+mcakFjV(BGAW9g%CzSyM;Df z143=mq0oxaK-H;o>F3~zJ<(3-j&?|QBn)WJfP#JR zRuA;`N?L83wQt78QIA$(Z)lGQY9r^SFal;LB^qi`8%8@y+mwcGsf~nv)bBy2S7z~9 z=;X@Gglk)^jpbNz?1;`!J3QUfAOp4U$Uxm5>92iT`mek#$>s`)M>;e4{#%HAAcb^8_Ax%ersk|}# z0bd;ZPu|2}18KtvmIo8`1@H~@2ejwo(5rFS`Z4&O{$$+ch2hC0=06Jh`@p+p8LZzY z&2M~8T6X^*X?yQ$3N5EzRv$(FtSxhW>>ABUyp!{484f8(%C1_y)3D%Qgfl_!sz`LTXOjR&L!zPA0qH_iNS!tY{!^2WfD%uT}P zI<~&?@&))5&hPPHVRl9);TPO>@UI2d!^ksb!$9T96V(F){puTsn(}qt_WXNw4VvHj zf;6A_XCvE`Z@}E-IOaG0rs>K>^=Sr&OgT_p;F@v0VCN0Y$r|Lw1?Wjt`AKK~RT*kJ z2>QPuVgLNcF+XKno;WBv$yj@d_WFJbl*#*V_Cwzo@%3n5%z4g21G*PVZ)wM5$A{klYozmGlB zT@u2+s}=f}25%IA!yNcXUr!!1)z(Nqbhojg0lv@7@0UlvUMT)*r;M$d0-t)Z?B1@qQk()o!4fqvfr_I0r7 zy1(NdkHEj#Yu{K>T#We#b#FD=c1XhS{hdTh9+8gy-vkcdkk*QS@y(xxEMb1w6z<^~ zYcETGfB#ibR#ql0EiD;PR$L&Vrh2uRv5t_$;NxC;>7_S5_OXxsi8udY3BUUdi55Sk zcyKM+PQ9YMA%D1kH1q48OFG(Gbl=FmV;yk8o>k%0$rJ8%-IYsHclnYuTskkaiCGkUlkMY~mx&K}XRlKIW;odWIeuKjtbc^8bBOTqK zjj(ot`_j?A6y_h%vxE9o*ntx#PGrnK7AljD_r58ylE*oy@{IY%+mA^!|2vW_`>`aC{#3`#3;D_$^S^cM zRcF+uTO2sICledvFgNMU@A%M)%8JbSLq{dD|2|2Sg8vvh_uV6*Q?F&rKaV{v_qz&y z`f;stIb?Cb2!Cg7CG91Bhu@D@RaIrq-+o+T2fwFu#|j>lD6ZS9-t^5cx>p|?flqUA z;Cgs#V)O#`Aw4$Kr)L5?|7f4izl!;n0jux}tEW$&&YBXz9o{+~HhoiYDJ`w5BVTl&ARya=M7zdy$FEe}iGBur8XE>rhLj&_yDk5D4n2GJZ07u7%zyAfNtOLn;)M?h*Py-Xtql5aJOtL4U8e|!t? z((sc6&OJXrPdVef^wZV&x=Z&~uA7^ix8rly^rEj?#d&~pQ{HN8Yq|fZ#*bXn-26P^ z5!)xRzYO9{u6vx5@q_{FE4#7BipS#{&J7*>y}lTyV94}dfE%Yk>@@pDe&F7J09(-0|wuI|$of-MRfK51#t@t2+U|*s=W; z!Y&t{dS%!4VEEi$efA!#<<7&04?kB}Soprd8*jYv;-Qj~h~4v>{XX~kjF+@Z7<t?^|i z#>_ag2i-CRAM8Ret^rZt*^K?`G|o>1o(mLkewxyA)38k93`<~4VFI?5VB!kBh%NNU zxb8K(^-MU1ImWQxG~nFB-Un;6n{lQz_FfsW9^H$Xcn{;+W^ZcG$0qLM#eNV=vGE@# z1~k&!h4@T|IiI<47@pS|i?Qcl=XZJL#$JKve;booMqDUYY{(xcdj6STDE=n?;fsS1 ze`h~Q{CT$K{+{t+#*I1=&&-UU8M&}AwAxD-rMa=e!{0gQXP@6azBq9(ji11uJF%@5 zCvV`#*?;ZguQ7o|nH%bm*s&jLej#@B35gy32ZAE0`Pz@#j6R&kN5w{O4~1rhDoU zEBdU)%Nl?8zi|DR((u|gg~r$aLYmGMyK%FO*qLvwxK5+cn*`;O`16c!&&XT{$j~5k zXb^fbh1GT-CI*Nj{-?r7HNg=e3E{6rxuluPXY z5Nm8ktc$o4-^SO0|Es_sp!A$8GVwOX+%)cH<;=u#R#nz;7QsHl;J@a{5NUAmAHq4D zIU5@jT!h?kUp|g~iN*!>jM6K!W5ar0v~fWrSHK@})@6Lh#h)C6F6@)&-+C3(zO! z8+kV|B7LctM3DpI*~EYo>vCj>_?x&H;>y0*vKwE0?vi$CLt zfSJB##P|M2dEUDBPKW=9cY-F;L;h3Fs4E2ERdN#NSL7ctAC z?-}_a{*L@GA7JHJudxtDVA{K5Yh*k(%#x4W7w+^ zcb-+ofbT5ieG+@QG2lx&7!MyE2JWDP@$k`M;0`*d+oQmJ2A^de!3c53HFcfW_Wtv< zKghQ;*FifmI}kE4dc@1y-u;@qs|V75Z^|Q0l0?teobTE8tGl@EB?k#q_wUjypJ*R zyEI=DJ^Z+d*&}B_xoWvs27LtH7972qqMxVFcX9}c&JbeNCXUZM0`nQIkf&C}&skSt z^9fw@b^Hb)!^hE2IJq~~GktG#ZWwWG<`@V&ckVR&r=JAO4YniJewVcG`HF;59}=bf zLyz0uxf6MhuSyH#-^!ZbHxYl^mmBVrx) zyrb8sQ*qBd_WXm9c~Of$&ZP$b^)<~0%nt#7y$1Jg$e}WCK>TeUB{P>|b1FAB?%K7>;XiOfd}JQ`|IP#Vf%kVy zXa4;XFZ+>n;F>uX&3|4zqWK2u3c<>q;tzjsb1;d{u;L$-hq3qe@82(ob<3qom#%`+ z;vzYAs7TIMl_O75BXu|r`Qhc4UT*vN$3Oo0kAC!{f2#HexDy|qUpgTF;k{o6|L>7l z=?`=*LXaow1o;oNNLXsGTrvC)$R&{m=94Tf+2iTT3Y_Or z-!;^0a{kyWtO4vksG_3cyc7HQ0~detf0+2+qxq(e1NS251N}w5iTSrM)`0p8rem!j zZ56hGD=pHI*B+dd)2B`%|9f0goozCSeXPw3 z+58k~sI02Yz#lOneJzYcG)EB0|F+ggC6D|B`6}d0khAK-gz7U3EGT|M_9$ZINqZjwf>P zJCZ=ogSoE`=yV5YXrcTQZx@Un(64*AlLiyxWnCJ9I<5Nc*eK6eV1Mk}ci0*NrJ=t| zCXuJG`#7GBbPceFtFEpl{(lTm`LX=B_!H+& z>$*Hf}}y zkt@nLXFG9%v**s{z&{H4e?aqp%&l#oU8lxUxk2o%K+?aAe6jLojA& z_|J0<-%u^<;NT*%4)n2-OdqfctSl6iCHE?W_Q2zpJken#_xUJlidzs249H=b#g z?}L4-Tnp6)t_5X?_$v)vz`s9@^BME2X@w<>sKZ3=B{%*B$T5Nj%6!-Hr;I!Scj`lH z&2dHFlOISwWJ&S2vf~@I4i~(0*T%OFiuX|eD*nd2utS4$1_JM?zmp>a#CsVy6Er^z zeNNZZDE?R3pM?>~e?H_N`C`hy%m4jb;6L#8=a7l>3eJS2LGgEUxsau-Yh9l~o7=Yh z2mYg3`m5*3Ik|lKQf~euzZlCWzaN&=vHuHtOwK!2@W6)hqq$Zm|7`Nmu%9^F6UH?+ z@2ii+=iJ;ZzhiUKu$QB()nKk3FooI>Jr_IjzY6=qxYy;&mvi7BlQ?t4kRjIhb|2q? zd^K~{-^cxjVSj?!Xs=Da5IHmFzRj!Kzh~b!?`P7c&T9s77VLYB?8_?F zauM^)p;qFG!9PHLfIsnt43UnmV?Wn?Ki7aXSosgq;f?MYUuSIYwOn(5vWhb{f%$pn z4ySN-z}_%7|B);A@PA5k*7kkdr4xZ@s{e9j+9w;*RFm;XPDQwx%~;8iBzSKTIGKO z{53ZZU*OLr@S5=k;?CM^i#zkxs3Sj%z0U`L%q`qM+tP zX$aL;*^g$7UyM2Go+_4A+f)IQcy^G$h2E zb?nT$XlgTEFJI8GN6NQf%-eVn9mPilRqUbT$pN-|;FEjq@Ao&TxpZg=mEgBHB zU@grU;&sfmqlO=6|G3sU;7t8rbK$?X0y_v9$^{X`m4jZ_BR|B|@?ZCLSPPEzz`w1n zP5nA;4(kQFKm%$enjkkBxM%Y}2si&d|62L)U(dCzCGn56HN+i#6|nV-TGIo0;W;`( zW-y=1KF4dp$$mC_|6}pbb>IHoKQeZajXQB>jVR?u`R>%l1o54?6NnS*arpVopdEF; zeC5J3*M0p`*8lif;!irrcjC?(uExejsi~>4wKYwstGY^N@KY}TujLx`S=Cu+T=!dx zKWlPm->I**E{A*q-Z^FFT5$G%7Ij0_*Mo4-y6~RmyTzUB&lfae(WZfO>um}mnsDXPEbau-!13!!xd!qh*{C)6&bz0j1I{>y$D-S)b*)JMCPk!=~KL&6Ngin0p6MCOxF2L_R9t8N!$2Wpced<#`y!F;w zKTi5V_kX&X09wAIJ#anfg9Dhn0s7(C6Nj3S-mVn(i|C6ZAVq0$hE)874co};g z^hR7pe4lU$P;*ggYc4o&UTQC%liCXooIfkI3TNaBV%t~FRr}yHu7kjQ2J*3;e%;iW zvDVCh8=G80KAeyhCuY2LjrC!Od1rvF7h}zszxGV)&!)6ChP5WAjv-zQAMNJIG!JHS zwl?pLxC-V5II#(hQ`l)ZAp&M0xd4%cxmco*MIk?{BD=BK`1vpc}D39|XlV z{c&0oGdDa~TL2FT4lh=~1NL5O-P~0?V2#ie`v^CnANfGUM!b4F=JkCwd7Q`c8Na2q zJGQQk^?6w}Vg9-{|2047((lAV84uN%sK!N2?V(!_1{{v6rdgZl56f0zDMQ+q)jKzzu^ztsVken;=DjAh6G`Cw`Q4G+BjS+n*=KI~^K{W=%t zbD-rN)O4|*Q~@<#@1Vx$E!0W9`B~IZeFn87sHMXD>$M%|Bh93rdGf1lKoX3K651t&nhsl= zXxG|%@8}Bbrlp_u#t*DZX<}_0Yb{A9*1Pd_)LtqNwy6xT4pZrOY{s?N4)pPwT(i#y zT%`lRi8U#Ken4fw>H+N`{f#FF?ZxFlLZg7z7#cr4X>id z{9kUD`d2=w_Zlb{^c`5IOxWCZ1k<0T1D1Z31IU0Q2edsZ1K0xv$pQVYq2KEp&#v#Z z?{m@Lin;*Str(C2sfF^L>{R3cjY`~#)m>Wm$Y|1fzeS0-$(Q^z@} zEO*vlb-^XK9>w&Ef^=Zzo-1AFSP#9zb~X5_+){$(eB4K z8gtW+nl{q+CTh+>v(gWrsP^DB*ge(~Q$AGxJ-eYc1isti%$%nM<_&Ev?%|??PK`$p z{f-PM{Ym8k<$$)(F9)tqzFJ?h&Dk@D?Dt{4CHKJWLs8$zy6+(R)pr@0ur)xY{=uXFFzH_> z-F^tN1y(2hG8V)GpDg%wW0Px_ep~nIjD~*HCSxDi0y`H!`V*~RHs^uQsb1*bK1qGpmd zB1m`Cjw0`nLBF2|umz+a#2X$c?Lj;M?Lj;MUp*d>7j~ayNAyj@SLpeH`)BgRH}byy zyQSat!;U{@O(<<2fp&oQkIy$z`_CQ-)O@RN;QD9T4y|wIJ^%U#(BF%=`i49}j!D-) zkOwPSJaG03SMkE~BzW}b_v>LA&y)EEYO6sbdnTX*$>UF|JhZ&^MSb4}Tgbne_4n+C zwI8U4i~PI>7a3{kVa8|))*%C0|K+bIbmV~a`|G#+`TU#g zXW;bWIcWsQi9c4X*RUDpIfyoPY)2bI-r9)xulm1CJDkQd6u+f)_N=w1ElgEBjprPF z3o?Ly0RVeY_{3~fPVckRMxe2lM8hj!B8F)JO z!`AP6>u>5Y&3o9t0QxBpNE=lJx#NyIbp1gD zzUYBIPYHIv9ngk-Zt~<)62^1Zs1LLYMh@_tP^I7EX-9)Ed0^@y{k65Gp0KRcTmMWw zU|+)qx{#q0SL+4q?Q`i0>COIIF8a0Cf&C`hbMj?LmG9K&iW-?PJt*u)38tTXAP>@R zZL6uH^!RYNq$p>PKz7f-zvg>OKXcZ8h!%Vo@{VUZp|+iUD_xb(N~G|6c#oQK^nHZU zKg#F6<)+`rf~k*Xjjye+syV{bwU2glMMMs-^ss4`bYaVroXzn`YQUd__UlZL_mLs z(vO}k!~(mi|L+(5&;>r<;|OHnbXBE78LruP;{yBxZ6y7K3)nMo-{6PCI7gQi6+rF_ zkPod!Z8n}q46ykrlQS|hVB(}(2Kf7BCZ>Vc;V>ccbk2~NGaf6wGQH@W9&?Zt3v(h*P4xDrN>ex7+jH*+Qg z%^jH$&+*!v{sQ!xkWN4+>|b}qGvEd6ANzgqoVy5Qfws}ef2QqF{iiR5{pT}PS&yjo z>lron#va-p=v;m>WB+XVz|o;UJFdjo5_!RRD|6W{4}A2a#bZv)gS_`b|KsSH)Sd_JIr%<%n06TX&t{&!H#{)?4W9hlJ`R1>FyugOh3=D_{einr zu(Wf`qTkvED+gEULO0I*Hs%f;&=`=X4;N8Ovf28x$A*11`dmfy2=$+PNqX>XcG`h% zJY&A6@&)*WT^rC(Caj}2+|X|6cICm5h0OK0cGB_!wEKFZJU)OQ+TZ1q2bTx9hxnq& z$9ee|f9|0M^)#E&Pr4)f?o&DMM4w>Ksb{hF(0|wh+5_{vPow{V%TFzU2za&gjttNi zIyR9qA56dX52Qbv2aY^g`U7R43-p`#sO1A=KS2aKgfR+Yu^bQ*i-qu z%0mP;Ap)B~zZgO9lG^`325gOf?iUHF{~7jyGC)3L(eL(SQ70VzR~wLN18tnx(Cz2~ zctBl1kI)wAe+cxWHw*NW-d;=pd+>+wd$a@GBju*wFvabSaPtHiT!o#QFC+wBVwYo3s=y;z1jM+M=Fj!FZM>UzpL-eZzOT( zhmZmEfWa=%KE#V3-ZK5#v!Hzd{zc^{ctF~- z>DT-U`}5!fk$aj24`#uGdB7r`>oX5tU|d*b|N3V1lXmv%MGrvE(dXG)^-J*LA>$LE z7kut4`zE)v{@Op|(|@i#c>tM!12FQh?}PfA0`Bp%=%*RiXVzLDXnXtE@4B)5uR}a> zbNU}q+712pIrM`k^odG8dKtG$zwHmQI^c}tfjx5?egx3!e%JRm_64e+>`Ra1IRfLb z1KQ`SxmH{cZfyVS5m(&`{V}Y4j6J{b17`h6KWqZ&hfc(oR zxM%w!$F(mKy05kY&lco3%zvLCxBW+t*rxO+i=qGMvobx0-<7`VUu)ka`){=ew+Ovt zg%52_{&UbkUA8aJPWsk)gYWV4`dnxI%s?7^fGpq{ZQuu=VH{-t7w~K%_E<8`zS;V- zKTho*>;UQQul^1GT^HCt@I-q?)&4!QDgBndn?3sNKYKCQFU4LGKJ$n@Je$&w9@E$X z^p@iJ(v&`1(tq~1zc>0Vow-KR&vm!GUzT?Eqgnc)leZ9p)-Z*C!zqb=-$XG0 z^!8RfuQs5s>Q~qcz92(a_Q+KH?C*vCTr~UdTiR`JGuNH8v(J|FTiSEcPrBpmHRtmd zI2Jng0J=bXK);YY^rM?jzn?~X-Pe`GbAy{D)Y6D&1GY-EBcy%Bq?bKh?A>DD9DD!p z?{q02wno2sraGUkZv5dx+J8)&K$)No43Zr(*S`FEdL!4C)}WE}vJd%{S6-3VUw>Wp z?Aasv`T0^%P$2vE?L+Qhj~qB~K%eW)xH(=b_jU}TLD&BP*Pc9hz@Z=e0nkpLkWl}> z_5J^i(9Z7$(XG9~I3sY)`OGZ#_L06+Dy4E>UstcP-rU@xJ$&rxvo!n1Ao`P~KLU-8 z{zDgN4-&A6N!kPSYbQ&7sLufi`YtE2uN$S?e&5n>Y4(q#|KP!cc1j)T^QrUXMPFaP z_SoYO8S8G}Z$?AL4`;pE?7J5K8yWqy23>cCT2{=-)+A$X^-I9=e!@J@A&-;Ufc)`H}c(VI&;0x zrrGv()5mjP%jXzS{^|29?bLNXS0bC%p!YXI!;O457rjCEEzMkGf~B3$T}dXBO23tP z+Ci>;5UoM?C@bU@f9G1^X3=ly&ZeFH<@|RnOG--A&)fd)AUgjw?%izq{p(KJ`EP0v z2mU)P!+3t@X14DA=E2RR-|p${GZ9ETX=d+kJRZL$nSa0daI@&oUUxnZg0xd_xu>Vz lzF#z5%kSKX?YLH3ll^(hI(_`L*t#Iva2Ede*Z;>H_ + + + + + + + \ No newline at end of file diff --git a/test/ThriftTest.thrift b/test/ThriftTest.thrift index 42607cc3544..67c7efbf752 100644 --- a/test/ThriftTest.thrift +++ b/test/ThriftTest.thrift @@ -23,6 +23,7 @@ namespace c_glib TTest namespace cpp thrift.test +namespace csharp Thrift.Test namespace delphi Thrift.Test namespace go thrifttest namespace java thrift.test diff --git a/test/csharp/Makefile.am b/test/csharp/Makefile.am new file mode 100644 index 00000000000..ad166e38408 --- /dev/null +++ b/test/csharp/Makefile.am @@ -0,0 +1,95 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +GENERATED = \ + gen-csharp/Thrift/Test/Bonk.cs \ + gen-csharp/Thrift/Test/Bools.cs \ + gen-csharp/Thrift/Test/BoolTest.cs \ + gen-csharp/Thrift/Test/CrazyNesting.cs \ + gen-csharp/Thrift/Test/EmptyStruct.cs \ + gen-csharp/Thrift/Test/GuessProtocolStruct.cs \ + gen-csharp/Thrift/Test/Insanity.cs \ + gen-csharp/Thrift/Test/LargeDeltas.cs \ + gen-csharp/Thrift/Test/ListBonks.cs \ + gen-csharp/Thrift/Test/ListTypeVersioningV1.cs \ + gen-csharp/Thrift/Test/ListTypeVersioningV2.cs \ + gen-csharp/Thrift/Test/NestedListsBonk.cs \ + gen-csharp/Thrift/Test/NestedListsI32x2.cs \ + gen-csharp/Thrift/Test/NestedListsI32x3.cs \ + gen-csharp/Thrift/Test/NestedMixedx2.cs \ + gen-csharp/Thrift/Test/Numberz.cs \ + gen-csharp/Thrift/Test/OneField.cs \ + gen-csharp/Thrift/Test/SecondService.cs \ + gen-csharp/Thrift/Test/StructA.cs \ + gen-csharp/Thrift/Test/StructB.cs \ + gen-csharp/Thrift/Test/ThriftTest.Constants.cs \ + gen-csharp/Thrift/Test/ThriftTest.cs \ + gen-csharp/Thrift/Test/VersioningTestV1.cs \ + gen-csharp/Thrift/Test/VersioningTestV2.cs \ + gen-csharp/Thrift/Test/Xception.cs \ + gen-csharp/Thrift/Test/Xception2.cs \ + gen-csharp/Thrift/Test/Xtruct.cs \ + gen-csharp/Thrift/Test/Xtruct2.cs \ + gen-csharp/Thrift/Test/Xtruct3.cs + +BUILT_SOURCES = $(GENERATED) + +if MONO_MCS +CSC = mcs +else +CSC = gmcs +endif + +if NET_2_0 +CSC_DEFINES = -d:NET_2_0 +endif + +LIBDIR = $(top_builddir)/lib/csharp + +THRIFT = $(top_builddir)/compiler/cpp/thrift + +$(GENERATED): $(top_srcdir)/test/ThriftTest.thrift $(THRIFT) + $(THRIFT) --gen csharp -o . $< + +precross: TestClientServer.exe + +ThriftImpl.dll: $(GENERATED) $(LIBDIR)/Thrift.dll + $(CSC) $(CSC_DEFINES) -t:library -out:$@ -reference:$(LIBDIR)/Thrift.dll $(GENERATED) + +SRCS = TestClient.cs TestServer.cs Program.cs + +TestClientServer.exe: $(SRCS) ThriftImpl.dll + $(CSC) $(CSC_DEFINES) -out:$@ -reference:$(LIBDIR)/Thrift.dll -reference:ThriftImpl.dll $(SRCS) + +clean-local: + $(RM) -rf gen-csharp *.exe *.dll + +TESTPORT = 9500 +check-local: TestClientServer.exe + MONO_PATH=$(LIBDIR) timeout 10 mono TestClientServer.exe server --port=$(TESTPORT) & + sleep 1 + MONO_PATH=$(LIBDIR) mono TestClientServer.exe client --port=$(TESTPORT) + +EXTRA_DIST = \ + Properties/AssemblyInfo.cs \ + ThriftTest.csproj \ + ThriftTest.sln \ + Program.cs \ + TestServer.cs \ + TestClient.cs diff --git a/test/csharp/Program.cs b/test/csharp/Program.cs new file mode 100644 index 00000000000..685ce05c644 --- /dev/null +++ b/test/csharp/Program.cs @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Distributed under the Thrift Software License +// +// See accompanying file LICENSE or visit the Thrift site at: +// http://developers.facebook.com/thrift/ + +using System; +using Thrift.Transport; +using Thrift.Protocol; +//using Thrift.Test; //generated code + +namespace Test +{ + class Program + { + static int Main(string[] args) + { + if (args.Length == 0) + { + Console.WriteLine("must provide 'server' or 'client' arg"); + return -1; + } + + try + { + Console.SetBufferSize(Console.BufferWidth, 4096); + } + catch (Exception) + { + Console.WriteLine("Failed to grow scroll-back buffer"); + } + + string[] subArgs = new string[args.Length - 1]; + for(int i = 1; i < args.Length; i++) + { + subArgs[i-1] = args[i]; + } + if (args[0] == "client") + { + return TestClient.Execute(subArgs); + } + else if (args[0] == "server") + { + return TestServer.Execute(subArgs) ? 0 : 1; + } + else + { + Console.WriteLine("first argument must be 'server' or 'client'"); + } + return 0; + } + } +} diff --git a/test/csharp/Properties/AssemblyInfo.cs b/test/csharp/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..376ff253ff2 --- /dev/null +++ b/test/csharp/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ThriftTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("The Apache Software Foundation")] +[assembly: AssemblyProduct("Thrift")] +[assembly: AssemblyCopyright("The Apache Software Foundation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f41b193b-f1ab-48ee-8843-f88e43084e26")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.14.0.0")] +[assembly: AssemblyFileVersion("0.14.0.0")] diff --git a/test/csharp/TestClient.cs b/test/csharp/TestClient.cs new file mode 100644 index 00000000000..f0a57efbd93 --- /dev/null +++ b/test/csharp/TestClient.cs @@ -0,0 +1,890 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System; +using System.Linq; +using System.Diagnostics; +using System.Collections.Generic; +using System.Threading; +using System.Security.Cryptography.X509Certificates; +using Thrift.Collections; +using Thrift.Protocol; +using Thrift.Transport; +using Thrift.Test; +using System.Security.Authentication; + +namespace Test +{ + public class TestClient + { + public class TestParams + { + public int numIterations = 1; + public string host = "localhost"; + public int port = 9090; + public string url; + public string pipe; + public bool buffered; + public bool framed; + public string protocol; + public bool encrypted = false; + public bool multiplexed = false; + protected bool _isFirstTransport = true; + + + public TTransport CreateTransport() + { + if (url == null) + { + // endpoint transport + TTransport trans = null; + if (pipe != null) + trans = new TNamedPipeClientTransport(pipe); + else + { + if (encrypted) + { + string certPath = "../keys/client.p12"; + X509Certificate cert = new X509Certificate2(certPath, "thrift"); + trans = new TTLSSocket(host, port, 0, cert, + (o, c, chain, errors) => true, + null, SslProtocols.Tls); + } + else + { + trans = new TSocket(host, port); + } + } + + // layered transport + if (buffered) + trans = new TBufferedTransport(trans); + if (framed) + trans = new TFramedTransport(trans); + + if (_isFirstTransport) + { + //ensure proper open/close of transport + trans.Open(); + trans.Close(); + _isFirstTransport = false; + } + return trans; + } + else + { + return new THttpClient(new Uri(url)); + } + } + + public TProtocol CreateProtocol(TTransport transport) + { + if (protocol == "compact") + return new TCompactProtocol(transport); + else if (protocol == "json") + return new TJSONProtocol(transport); + else + return new TBinaryProtocol(transport); + } + }; + + private const int ErrorBaseTypes = 1; + private const int ErrorStructs = 2; + private const int ErrorContainers = 4; + private const int ErrorExceptions = 8; + private const int ErrorProtocol = 16; + private const int ErrorUnknown = 64; + + private class ClientTest + { + private readonly TestParams param; + private readonly TTransport transport; + private readonly SecondService.Client second; + private readonly ThriftTest.Client client; + private readonly int numIterations; + private bool done; + + public int ReturnCode { get; set; } + + public ClientTest(TestParams paramin) + { + param = paramin; + transport = param.CreateTransport(); + TProtocol protocol = param.CreateProtocol(transport); + if (param.multiplexed) + { + second = new SecondService.Client(new TMultiplexedProtocol(protocol, "SecondService")); + } + client = new ThriftTest.Client(protocol); + numIterations = param.numIterations; + } + public void Execute() + { + if (done) + { + Console.WriteLine("Execute called more than once"); + throw new InvalidOperationException(); + } + + for (int i = 0; i < numIterations; i++) + { + try + { + if (!transport.IsOpen) + transport.Open(); + } + catch (TTransportException ex) + { + Console.WriteLine("*** FAILED ***"); + Console.WriteLine("Connect failed: " + ex.Message); + ReturnCode |= ErrorUnknown; + Console.WriteLine(ex.Message + " ST: " + ex.StackTrace); + continue; + } + + try + { + ReturnCode |= ExecuteClientTest(client, second, param); + } + catch (Exception ex) + { + Console.WriteLine("*** FAILED ***"); + Console.WriteLine(ex.Message + " ST: " + ex.StackTrace); + ReturnCode |= ErrorUnknown; + } + } + try + { + transport.Close(); + } + catch(Exception ex) + { + Console.WriteLine("Error while closing transport"); + Console.WriteLine(ex.Message + " ST: " + ex.StackTrace); + } + done = true; + } + } + + public static int Execute(string[] args) + { + try + { + TestParams param = new TestParams(); + int numThreads = 1; + try + { + for (int i = 0; i < args.Length; i++) + { + if (args[i] == "-u") + { + param.url = args[++i]; + } + else if (args[i] == "-n") + { + param.numIterations = Convert.ToInt32(args[++i]); + } + else if (args[i] == "-pipe") // -pipe + { + param.pipe = args[++i]; + Console.WriteLine("Using named pipes transport"); + } + else if (args[i].Contains("--host=")) + { + param.host = args[i].Substring(args[i].IndexOf("=") + 1); + } + else if (args[i].Contains("--port=")) + { + param.port = int.Parse(args[i].Substring(args[i].IndexOf("=")+1)); + } + else if (args[i] == "-b" || args[i] == "--buffered" || args[i] == "--transport=buffered") + { + param.buffered = true; + Console.WriteLine("Using buffered sockets"); + } + else if (args[i] == "-f" || args[i] == "--framed" || args[i] == "--transport=framed") + { + param.framed = true; + Console.WriteLine("Using framed transport"); + } + else if (args[i] == "-t") + { + numThreads = Convert.ToInt32(args[++i]); + } + else if (args[i] == "--compact" || args[i] == "--protocol=compact" || args[i] == "--protocol=multic") + { + param.protocol = "compact"; + Console.WriteLine("Using compact protocol"); + } + else if (args[i] == "--json" || args[i] == "--protocol=json" || args[i] == "--protocol=multij") + { + param.protocol = "json"; + Console.WriteLine("Using JSON protocol"); + } + else if (args[i] == "--ssl") + { + param.encrypted = true; + Console.WriteLine("Using encrypted transport"); + } + + if (args[i].StartsWith("--protocol=multi")) + { + param.multiplexed = true; + } + } + } + catch (Exception ex) + { + Console.WriteLine("*** FAILED ***"); + Console.WriteLine("Error while parsing arguments"); + Console.WriteLine(ex.Message + " ST: " + ex.StackTrace); + return ErrorUnknown; + } + + var tests = Enumerable.Range(0, numThreads).Select(_ => new ClientTest(param)).ToArray(); + //issue tests on separate threads simultaneously + var threads = tests.Select(test => new Thread(test.Execute)).ToArray(); + DateTime start = DateTime.Now; + foreach (var t in threads) + t.Start(); + foreach (var t in threads) + t.Join(); + Console.WriteLine("Total time: " + (DateTime.Now - start)); + Console.WriteLine(); + return tests.Select(t => t.ReturnCode).Aggregate((r1, r2) => r1 | r2); + } + catch (Exception outerEx) + { + Console.WriteLine("*** FAILED ***"); + Console.WriteLine("Unexpected error"); + Console.WriteLine(outerEx.Message + " ST: " + outerEx.StackTrace); + return ErrorUnknown; + } + } + + public static string BytesToHex(byte[] data) { + return BitConverter.ToString(data).Replace("-", string.Empty); + } + + public static byte[] PrepareTestData(bool randomDist, bool huge) + { + // huge = true tests for THRIFT-4372 + byte[] retval = new byte[huge ? 0x12345 : 0x100]; + int initLen = retval.Length; + + // linear distribution, unless random is requested + if (!randomDist) { + for (var i = 0; i < initLen; ++i) { + retval[i] = (byte)i; + } + return retval; + } + + // random distribution + for (var i = 0; i < initLen; ++i) { + retval[i] = (byte)0; + } + var rnd = new Random(); + for (var i = 1; i < initLen; ++i) { + while( true) { + int nextPos = rnd.Next() % initLen; + if (retval[nextPos] == 0) { + retval[nextPos] = (byte)i; + break; + } + } + } + return retval; + } + + public static int ExecuteClientTest(ThriftTest.Client client, SecondService.Client second, TestParams param) + { + int returnCode = 0; + + Console.Write("testVoid()"); + client.testVoid(); + Console.WriteLine(" = void"); + + Console.Write("testString(\"Test\")"); + string s = client.testString("Test"); + Console.WriteLine(" = \"" + s + "\""); + if ("Test" != s) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorBaseTypes; + } + + if (param.multiplexed) + { + Console.WriteLine("secondTestString(\"Test2\")"); + s = second.secondtestString("Test2"); + Console.WriteLine(" = \"" + s + "\""); + if ("testString(\"Test2\")" != s) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorProtocol; + } + } + + Console.Write("testBool(true)"); + bool t = client.testBool((bool)true); + Console.WriteLine(" = " + t); + if (!t) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorBaseTypes; + } + Console.Write("testBool(false)"); + bool f = client.testBool((bool)false); + Console.WriteLine(" = " + f); + if (f) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorBaseTypes; + } + + Console.Write("testByte(1)"); + sbyte i8 = client.testByte((sbyte)1); + Console.WriteLine(" = " + i8); + if (1 != i8) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorBaseTypes; + } + + Console.Write("testI32(-1)"); + int i32 = client.testI32(-1); + Console.WriteLine(" = " + i32); + if (-1 != i32) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorBaseTypes; + } + + Console.Write("testI64(-34359738368)"); + long i64 = client.testI64(-34359738368); + Console.WriteLine(" = " + i64); + if (-34359738368 != i64) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorBaseTypes; + } + + // TODO: Validate received message + Console.Write("testDouble(5.325098235)"); + double dub = client.testDouble(5.325098235); + Console.WriteLine(" = " + dub); + if (5.325098235 != dub) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorBaseTypes; + } + Console.Write("testDouble(-0.000341012439638598279)"); + dub = client.testDouble(-0.000341012439638598279); + Console.WriteLine(" = " + dub); + if (-0.000341012439638598279 != dub) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorBaseTypes; + } + + // testUuid() + var uuidOut = new Guid("{00112233-4455-6677-8899-AABBCCDDEEFF}"); + Console.Write("testUuid({0})", uuidOut); + try + { + var uuidIn = client.testUuid(uuidOut); + Console.WriteLine(" = {0}", uuidIn); + if (!uuidIn.Equals(uuidOut)) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorBaseTypes; + } + } + catch (Thrift.TApplicationException ex) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorBaseTypes; + Console.WriteLine(ex.Message + "\n" + ex.StackTrace); + } + + for (i32 = 0; i32 < 2; ++i32) + { + var huge = (i32 > 0); + byte[] binOut = PrepareTestData(false,huge); + Console.Write("testBinary(" + BytesToHex(binOut) + ")"); + try + { + byte[] binIn = client.testBinary(binOut); + Console.WriteLine(" = " + BytesToHex(binIn)); + if (binIn.Length != binOut.Length) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorBaseTypes; + } + for (int ofs = 0; ofs < Math.Min(binIn.Length, binOut.Length); ++ofs) + if (binIn[ofs] != binOut[ofs]) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorBaseTypes; + } + } + catch (Thrift.TApplicationException ex) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorBaseTypes; + Console.WriteLine(ex.Message + " ST: " + ex.StackTrace); + } + } + + // binary equals? only with hashcode option enabled ... + Console.WriteLine("Test CrazyNesting"); + if( typeof(CrazyNesting).GetMethod("Equals").DeclaringType == typeof(CrazyNesting)) + { + CrazyNesting one = new CrazyNesting(); + CrazyNesting two = new CrazyNesting(); + one.String_field = "crazy"; + two.String_field = "crazy"; + one.Binary_field = new byte[10] { 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0xFF }; + two.Binary_field = new byte[10] { 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0xFF }; + if (!one.Equals(two)) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorContainers; + throw new Exception("CrazyNesting.Equals failed"); + } + } + + // TODO: Validate received message + Console.Write("testStruct({\"Zero\", 1, -3, -5})"); + Xtruct o = new Xtruct(); + o.String_thing = "Zero"; + o.Byte_thing = (sbyte)1; + o.I32_thing = -3; + o.I64_thing = -5; + Xtruct i = client.testStruct(o); + Console.WriteLine(" = {\"" + i.String_thing + "\", " + i.Byte_thing + ", " + i.I32_thing + ", " + i.I64_thing + "}"); + + // TODO: Validate received message + Console.Write("testNest({1, {\"Zero\", 1, -3, -5}, 5})"); + Xtruct2 o2 = new Xtruct2(); + o2.Byte_thing = (sbyte)1; + o2.Struct_thing = o; + o2.I32_thing = 5; + Xtruct2 i2 = client.testNest(o2); + i = i2.Struct_thing; + Console.WriteLine(" = {" + i2.Byte_thing + ", {\"" + i.String_thing + "\", " + i.Byte_thing + ", " + i.I32_thing + ", " + i.I64_thing + "}, " + i2.I32_thing + "}"); + + Dictionary mapout = new Dictionary(); + for (int j = 0; j < 5; j++) + { + mapout[j] = j - 10; + } + Console.Write("testMap({"); + bool first = true; + foreach (int key in mapout.Keys) + { + if (first) + { + first = false; + } + else + { + Console.Write(", "); + } + Console.Write(key + " => " + mapout[key]); + } + Console.Write("})"); + + Dictionary mapin = client.testMap(mapout); + + Console.Write(" = {"); + first = true; + foreach (int key in mapin.Keys) + { + if (first) + { + first = false; + } + else + { + Console.Write(", "); + } + Console.Write(key + " => " + mapin[key]); + } + Console.WriteLine("}"); + + // TODO: Validate received message + List listout = new List(); + for (int j = -2; j < 3; j++) + { + listout.Add(j); + } + Console.Write("testList({"); + first = true; + foreach (int j in listout) + { + if (first) + { + first = false; + } + else + { + Console.Write(", "); + } + Console.Write(j); + } + Console.Write("})"); + + List listin = client.testList(listout); + + Console.Write(" = {"); + first = true; + foreach (int j in listin) + { + if (first) + { + first = false; + } + else + { + Console.Write(", "); + } + Console.Write(j); + } + Console.WriteLine("}"); + + //set + // TODO: Validate received message + THashSet setout = new THashSet(); + for (int j = -2; j < 3; j++) + { + setout.Add(j); + } + Console.Write("testSet({"); + first = true; + foreach (int j in setout) + { + if (first) + { + first = false; + } + else + { + Console.Write(", "); + } + Console.Write(j); + } + Console.Write("})"); + + THashSet setin = client.testSet(setout); + + Console.Write(" = {"); + first = true; + foreach (int j in setin) + { + if (first) + { + first = false; + } + else + { + Console.Write(", "); + } + Console.Write(j); + } + Console.WriteLine("}"); + + + Console.Write("testEnum(ONE)"); + Numberz ret = client.testEnum(Numberz.ONE); + Console.WriteLine(" = " + ret); + if (Numberz.ONE != ret) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorStructs; + } + + Console.Write("testEnum(TWO)"); + ret = client.testEnum(Numberz.TWO); + Console.WriteLine(" = " + ret); + if (Numberz.TWO != ret) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorStructs; + } + + Console.Write("testEnum(THREE)"); + ret = client.testEnum(Numberz.THREE); + Console.WriteLine(" = " + ret); + if (Numberz.THREE != ret) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorStructs; + } + + Console.Write("testEnum(FIVE)"); + ret = client.testEnum(Numberz.FIVE); + Console.WriteLine(" = " + ret); + if (Numberz.FIVE != ret) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorStructs; + } + + Console.Write("testEnum(EIGHT)"); + ret = client.testEnum(Numberz.EIGHT); + Console.WriteLine(" = " + ret); + if (Numberz.EIGHT != ret) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorStructs; + } + + Console.Write("testTypedef(309858235082523)"); + long uid = client.testTypedef(309858235082523L); + Console.WriteLine(" = " + uid); + if (309858235082523L != uid) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorStructs; + } + + // TODO: Validate received message + Console.Write("testMapMap(1)"); + Dictionary> mm = client.testMapMap(1); + Console.Write(" = {"); + foreach (int key in mm.Keys) + { + Console.Write(key + " => {"); + Dictionary m2 = mm[key]; + foreach (int k2 in m2.Keys) + { + Console.Write(k2 + " => " + m2[k2] + ", "); + } + Console.Write("}, "); + } + Console.WriteLine("}"); + + // TODO: Validate received message + Insanity insane = new Insanity(); + insane.UserMap = new Dictionary(); + insane.UserMap[Numberz.FIVE] = 5000L; + Xtruct truck = new Xtruct(); + truck.String_thing = "Truck"; + truck.Byte_thing = (sbyte)8; + truck.I32_thing = 8; + truck.I64_thing = 8; + insane.Xtructs = new List(); + insane.Xtructs.Add(truck); + Console.Write("testInsanity()"); + Dictionary> whoa = client.testInsanity(insane); + Console.Write(" = {"); + foreach (long key in whoa.Keys) + { + Dictionary val = whoa[key]; + Console.Write(key + " => {"); + + foreach (Numberz k2 in val.Keys) + { + Insanity v2 = val[k2]; + + Console.Write(k2 + " => {"); + Dictionary userMap = v2.UserMap; + + Console.Write("{"); + if (userMap != null) + { + foreach (Numberz k3 in userMap.Keys) + { + Console.Write(k3 + " => " + userMap[k3] + ", "); + } + } + else + { + Console.Write("null"); + } + Console.Write("}, "); + + List xtructs = v2.Xtructs; + + Console.Write("{"); + if (xtructs != null) + { + foreach (Xtruct x in xtructs) + { + Console.Write("{\"" + x.String_thing + "\", " + x.Byte_thing + ", " + x.I32_thing + ", " + x.I32_thing + "}, "); + } + } + else + { + Console.Write("null"); + } + Console.Write("}"); + + Console.Write("}, "); + } + Console.Write("}, "); + } + Console.WriteLine("}"); + + sbyte arg0 = 1; + int arg1 = 2; + long arg2 = long.MaxValue; + Dictionary multiDict = new Dictionary(); + multiDict[1] = "one"; + Numberz arg4 = Numberz.FIVE; + long arg5 = 5000000; + Console.Write("Test Multi(" + arg0 + "," + arg1 + "," + arg2 + "," + multiDict + "," + arg4 + "," + arg5 + ")"); + Xtruct multiResponse = client.testMulti(arg0, arg1, arg2, multiDict, arg4, arg5); + Console.Write(" = Xtruct(byte_thing:" + multiResponse.Byte_thing + ",String_thing:" + multiResponse.String_thing + + ",i32_thing:" + multiResponse.I32_thing + ",i64_thing:" + multiResponse.I64_thing + ")\n"); + + try + { + Console.WriteLine("testException(\"Xception\")"); + client.testException("Xception"); + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorExceptions; + } + catch (Xception ex) + { + if (ex.ErrorCode != 1001 || ex.Message != "Xception") + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorExceptions; + } + } + catch (Exception ex) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorExceptions; + Console.WriteLine(ex.Message + " ST: " + ex.StackTrace); + } + try + { + Console.WriteLine("testException(\"TException\")"); + client.testException("TException"); + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorExceptions; + } + catch (Thrift.TException) + { + // OK + } + catch (Exception ex) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorExceptions; + Console.WriteLine(ex.Message + " ST: " + ex.StackTrace); + } + try + { + Console.WriteLine("testException(\"ok\")"); + client.testException("ok"); + // OK + } + catch (Exception ex) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorExceptions; + Console.WriteLine(ex.Message + " ST: " + ex.StackTrace); + } + + try + { + Console.WriteLine("testMultiException(\"Xception\", ...)"); + client.testMultiException("Xception", "ignore"); + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorExceptions; + } + catch (Xception ex) + { + if (ex.ErrorCode != 1001 || ex.Message != "This is an Xception") + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorExceptions; + } + } + catch (Exception ex) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorExceptions; + Console.WriteLine(ex.Message + " ST: " + ex.StackTrace); + } + try + { + Console.WriteLine("testMultiException(\"Xception2\", ...)"); + client.testMultiException("Xception2", "ignore"); + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorExceptions; + } + catch (Xception2 ex) + { + if (ex.ErrorCode != 2002 || ex.Struct_thing.String_thing != "This is an Xception2") + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorExceptions; + } + } + catch (Exception ex) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorExceptions; + Console.WriteLine(ex.Message + " ST: " + ex.StackTrace); + } + try + { + Console.WriteLine("testMultiException(\"success\", \"OK\")"); + if ("OK" != client.testMultiException("success", "OK").String_thing) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorExceptions; + } + } + catch (Exception ex) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorExceptions; + Console.WriteLine(ex.Message + " ST: " + ex.StackTrace); + } + + Stopwatch sw = new Stopwatch(); + sw.Start(); + Console.WriteLine("Test Oneway(1)"); + client.testOneway(1); + sw.Stop(); + if (sw.ElapsedMilliseconds > 1000) + { + Console.WriteLine("*** FAILED ***"); + returnCode |= ErrorBaseTypes; + } + + Console.Write("Test Calltime()"); + var times = 50; + sw.Reset(); + sw.Start(); + for (int k = 0; k < times; ++k) + client.testVoid(); + sw.Stop(); + Console.WriteLine(" = {0} ms a testVoid() call", sw.ElapsedMilliseconds / times); + return returnCode; + } + } +} diff --git a/test/csharp/TestServer.cs b/test/csharp/TestServer.cs new file mode 100644 index 00000000000..20ad04e3799 --- /dev/null +++ b/test/csharp/TestServer.cs @@ -0,0 +1,541 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Distributed under the Thrift Software License +// +// See accompanying file LICENSE or visit the Thrift site at: +// http://developers.facebook.com/thrift/ +using System; +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using Thrift.Collections; +using Thrift.Test; //generated code +using Thrift.Transport; +using Thrift.Protocol; +using Thrift.Server; +using Thrift; +using System.Threading; +using System.Text; +using System.Security.Authentication; + +namespace Test +{ + public class TestServer + { + public static int _clientID = -1; + public delegate void TestLogDelegate(string msg, params object[] values); + + public class TradeServerEventHandler : TServerEventHandler + { + public int callCount = 0; + public void preServe() + { + callCount++; + } + public Object createContext(Thrift.Protocol.TProtocol input, Thrift.Protocol.TProtocol output) + { + callCount++; + return null; + } + public void deleteContext(Object serverContext, Thrift.Protocol.TProtocol input, Thrift.Protocol.TProtocol output) + { + callCount++; + } + public void processContext(Object serverContext, Thrift.Transport.TTransport transport) + { + callCount++; + } + }; + + + public class TestHandler : ThriftTest.Iface, Thrift.TControllingHandler + { + public TServer server { get; set; } + private int handlerID; + private StringBuilder reusableStringBuilder = new StringBuilder(); + private TestLogDelegate testLogDelegate; + + public TestHandler() + { + handlerID = Interlocked.Increment(ref _clientID); + testLogDelegate += testConsoleLogger; + testLogDelegate.Invoke("New TestHandler instance created"); + } + + public void testConsoleLogger(string msg, params object[] values) + { + reusableStringBuilder.Clear(); + reusableStringBuilder.AppendFormat("handler{0:D3}:",handlerID); + reusableStringBuilder.AppendFormat(msg, values); + reusableStringBuilder.AppendLine(); + Console.Write( reusableStringBuilder.ToString() ); + } + + public void testVoid() + { + testLogDelegate.Invoke("testVoid()"); + } + + public string testString(string thing) + { + testLogDelegate.Invoke("testString({0})", thing); + return thing; + } + + public bool testBool(bool thing) + { + testLogDelegate.Invoke("testBool({0})", thing); + return thing; + } + + public sbyte testByte(sbyte thing) + { + testLogDelegate.Invoke("testByte({0})", thing); + return thing; + } + + public int testI32(int thing) + { + testLogDelegate.Invoke("testI32({0})", thing); + return thing; + } + + public long testI64(long thing) + { + testLogDelegate.Invoke("testI64({0})", thing); + return thing; + } + + public double testDouble(double thing) + { + testLogDelegate.Invoke("testDouble({0})", thing); + return thing; + } + + public byte[] testBinary(byte[] thing) + { + string hex = BitConverter.ToString(thing).Replace("-", string.Empty); + testLogDelegate.Invoke("testBinary({0:X})", hex); + return thing; + } + + public Guid testUuid(Guid thing) + { + testLogDelegate.Invoke("testUuid({0})", thing.ToString("B")); + return thing; + } + + public Xtruct testStruct(Xtruct thing) + { + testLogDelegate.Invoke("testStruct({{\"{0}\", {1}, {2}, {3}}})", thing.String_thing, thing.Byte_thing, thing.I32_thing, thing.I64_thing); + return thing; + } + + public Xtruct2 testNest(Xtruct2 nest) + { + Xtruct thing = nest.Struct_thing; + testLogDelegate.Invoke("testNest({{{0}, {{\"{1}\", {2}, {3}, {4}, {5}}}}})", + nest.Byte_thing, + thing.String_thing, + thing.Byte_thing, + thing.I32_thing, + thing.I64_thing, + nest.I32_thing); + return nest; + } + + public Dictionary testMap(Dictionary thing) + { + reusableStringBuilder.Clear(); + reusableStringBuilder.Append("testMap({{"); + bool first = true; + foreach (int key in thing.Keys) + { + if (first) + { + first = false; + } + else + { + reusableStringBuilder.Append(", "); + } + reusableStringBuilder.AppendFormat("{0} => {1}", key, thing[key]); + } + reusableStringBuilder.Append("}})"); + testLogDelegate.Invoke(reusableStringBuilder.ToString()); + return thing; + } + + public Dictionary testStringMap(Dictionary thing) + { + reusableStringBuilder.Clear(); + reusableStringBuilder.Append("testStringMap({{"); + bool first = true; + foreach (string key in thing.Keys) + { + if (first) + { + first = false; + } + else + { + reusableStringBuilder.Append(", "); + } + reusableStringBuilder.AppendFormat("{0} => {1}", key, thing[key]); + } + reusableStringBuilder.Append("}})"); + testLogDelegate.Invoke(reusableStringBuilder.ToString()); + return thing; + } + + public THashSet testSet(THashSet thing) + { + reusableStringBuilder.Clear(); + reusableStringBuilder.Append("testSet({{"); + bool first = true; + foreach (int elem in thing) + { + if (first) + { + first = false; + } + else + { + reusableStringBuilder.Append(", "); + } + reusableStringBuilder.AppendFormat("{0}", elem); + } + reusableStringBuilder.Append("}})"); + testLogDelegate.Invoke(reusableStringBuilder.ToString()); + return thing; + } + + public List testList(List thing) + { + reusableStringBuilder.Clear(); + reusableStringBuilder.Append("testList({{"); + bool first = true; + foreach (int elem in thing) + { + if (first) + { + first = false; + } + else + { + reusableStringBuilder.Append(", "); + } + reusableStringBuilder.AppendFormat("{0}", elem); + } + reusableStringBuilder.Append("}})"); + testLogDelegate.Invoke(reusableStringBuilder.ToString()); + return thing; + } + + public Numberz testEnum(Numberz thing) + { + testLogDelegate.Invoke("testEnum({0})", thing); + return thing; + } + + public long testTypedef(long thing) + { + testLogDelegate.Invoke("testTypedef({0})", thing); + return thing; + } + + public Dictionary> testMapMap(int hello) + { + testLogDelegate.Invoke("testMapMap({0})", hello); + Dictionary> mapmap = + new Dictionary>(); + + Dictionary pos = new Dictionary(); + Dictionary neg = new Dictionary(); + for (int i = 1; i < 5; i++) + { + pos[i] = i; + neg[-i] = -i; + } + + mapmap[4] = pos; + mapmap[-4] = neg; + + return mapmap; + } + + // Insanity + // returns: + // { 1 => { 2 => argument, + // 3 => argument, + // }, + // 2 => { 6 => , }, + // } + public Dictionary> testInsanity(Insanity argument) + { + testLogDelegate.Invoke("testInsanity()"); + + Dictionary first_map = new Dictionary(); + Dictionary second_map = new Dictionary(); ; + + first_map[Numberz.TWO] = argument; + first_map[Numberz.THREE] = argument; + + second_map[Numberz.SIX] = new Insanity(); + + Dictionary> insane = + new Dictionary>(); + insane[(long)1] = first_map; + insane[(long)2] = second_map; + + return insane; + } + + public Xtruct testMulti(sbyte arg0, int arg1, long arg2, Dictionary arg3, Numberz arg4, long arg5) + { + testLogDelegate.Invoke("testMulti()"); + + Xtruct hello = new Xtruct(); ; + hello.String_thing = "Hello2"; + hello.Byte_thing = arg0; + hello.I32_thing = arg1; + hello.I64_thing = arg2; + return hello; + } + + /** + * Print 'testException(%s)' with arg as '%s' + * @param string arg - a string indication what type of exception to throw + * if arg == "Xception" throw Xception with errorCode = 1001 and message = arg + * elsen if arg == "TException" throw TException + * else do not throw anything + */ + public void testException(string arg) + { + testLogDelegate.Invoke("testException({0})", arg); + if (arg == "Xception") + { + Xception x = new Xception(); + x.ErrorCode = 1001; + x.Message = arg; + throw x; + } + if (arg == "TException") + { + throw new Thrift.TException(); + } + return; + } + + public Xtruct testMultiException(string arg0, string arg1) + { + testLogDelegate.Invoke("testMultiException({0}, {1})", arg0,arg1); + if (arg0 == "Xception") + { + Xception x = new Xception(); + x.ErrorCode = 1001; + x.Message = "This is an Xception"; + throw x; + } + else if (arg0 == "Xception2") + { + Xception2 x = new Xception2(); + x.ErrorCode = 2002; + x.Struct_thing = new Xtruct(); + x.Struct_thing.String_thing = "This is an Xception2"; + throw x; + } + + Xtruct result = new Xtruct(); + result.String_thing = arg1; + return result; + } + + public void testStop() + { + if (server != null) + { + server.Stop(); + } + } + + public void testOneway(int arg) + { + testLogDelegate.Invoke("testOneway({0}), sleeping...", arg); + System.Threading.Thread.Sleep(arg * 1000); + testLogDelegate.Invoke("testOneway finished"); + } + + } // class TestHandler + + private enum ServerType + { + TSimpleServer, + TThreadedServer, + TThreadPoolServer, + } + + private enum ProcessorFactoryType + { + TSingletonProcessorFactory, + TPrototypeProcessorFactory, + } + + public static bool Execute(string[] args) + { + try + { + bool useBufferedSockets = false, useFramed = false, useEncryption = false, compact = false, json = false; + ServerType serverType = ServerType.TSimpleServer; + ProcessorFactoryType processorFactoryType = ProcessorFactoryType.TSingletonProcessorFactory; + int port = 9090; + string pipe = null; + for (int i = 0; i < args.Length; i++) + { + if (args[i] == "-pipe") // -pipe name + { + pipe = args[++i]; + } + else if (args[i].Contains("--port=")) + { + port = int.Parse(args[i].Substring(args[i].IndexOf("=") + 1)); + } + else if (args[i] == "-b" || args[i] == "--buffered" || args[i] == "--transport=buffered") + { + useBufferedSockets = true; + } + else if (args[i] == "-f" || args[i] == "--framed" || args[i] == "--transport=framed") + { + useFramed = true; + } + else if (args[i] == "--compact" || args[i] == "--protocol=compact") + { + compact = true; + } + else if (args[i] == "--json" || args[i] == "--protocol=json") + { + json = true; + } + else if (args[i] == "--threaded" || args[i] == "--server-type=threaded") + { + serverType = ServerType.TThreadedServer; + } + else if (args[i] == "--threadpool" || args[i] == "--server-type=threadpool") + { + serverType = ServerType.TThreadPoolServer; + } + else if (args[i] == "--prototype" || args[i] == "--processor=prototype") + { + processorFactoryType = ProcessorFactoryType.TPrototypeProcessorFactory; + } + else if (args[i] == "--ssl") + { + useEncryption = true; + } + } + + // Transport + TServerTransport trans; + if (pipe != null) + { + trans = new TNamedPipeServerTransport(pipe); + } + else + { + if (useEncryption) + { + string certPath = "../keys/server.p12"; + trans = new TTLSServerSocket(port, 0, useBufferedSockets, new X509Certificate2(certPath, "thrift"), + null, + null, SslProtocols.Tls); + } + else + { + trans = new TServerSocket(port, 0, useBufferedSockets); + } + } + + TProtocolFactory proto; + if (compact) + proto = new TCompactProtocol.Factory(); + else if (json) + proto = new TJSONProtocol.Factory(); + else + proto = new TBinaryProtocol.Factory(); + + TProcessorFactory processorFactory; + if (processorFactoryType == ProcessorFactoryType.TPrototypeProcessorFactory) + { + processorFactory = new TPrototypeProcessorFactory(); + } + else + { + // Processor + TestHandler testHandler = new TestHandler(); + ThriftTest.Processor testProcessor = new ThriftTest.Processor(testHandler); + processorFactory = new TSingletonProcessorFactory(testProcessor); + } + + TTransportFactory transFactory; + if (useFramed) + transFactory = new TFramedTransport.Factory(); + else + transFactory = new TTransportFactory(); + + TServer serverEngine; + switch (serverType) + { + case ServerType.TThreadPoolServer: + serverEngine = new TThreadPoolServer(processorFactory, trans, transFactory, proto); + break; + case ServerType.TThreadedServer: + serverEngine = new TThreadedServer(processorFactory, trans, transFactory, proto); + break; + default: + serverEngine = new TSimpleServer(processorFactory, trans, transFactory, proto); + break; + } + + //Server event handler + TradeServerEventHandler serverEvents = new TradeServerEventHandler(); + serverEngine.setEventHandler(serverEvents); + + // Run it + string where = (pipe != null ? "on pipe " + pipe : "on port " + port); + Console.WriteLine("Starting the " + serverType.ToString() + " " + where + + (processorFactoryType == ProcessorFactoryType.TPrototypeProcessorFactory ? " with processor prototype factory " : "") + + (useBufferedSockets ? " with buffered socket" : "") + + (useFramed ? " with framed transport" : "") + + (useEncryption ? " with encryption" : "") + + (compact ? " with compact protocol" : "") + + (json ? " with json protocol" : "") + + "..."); + serverEngine.Serve(); + + } + catch (Exception x) + { + Console.Error.Write(x); + return false; + } + Console.WriteLine("done."); + return true; + } + } +} diff --git a/test/csharp/ThriftTest.csproj b/test/csharp/ThriftTest.csproj new file mode 100644 index 00000000000..e2e8c193a44 --- /dev/null +++ b/test/csharp/ThriftTest.csproj @@ -0,0 +1,138 @@ + + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {48DD757F-CA95-4DD7-BDA4-58DB6F108C2C} + Exe + Properties + ThriftTest + ThriftTest + v4.5 + 512 + false + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 0.14.0.0 + false + true + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + false + + + + + + + + + + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 2.0 %28x86%29 + false + + + False + .NET Framework 3.0 %28x86%29 + false + + + False + .NET Framework 3.5 + true + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + {499eb63c-d74c-47e8-ae48-a2fc94538e9d} + Thrift + + + + + + + + + + + + \ No newline at end of file diff --git a/test/csharp/ThriftTest.sln b/test/csharp/ThriftTest.sln new file mode 100644 index 00000000000..60fd7015a97 --- /dev/null +++ b/test/csharp/ThriftTest.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32526.322 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ThriftTest", "ThriftTest.csproj", "{48DD757F-CA95-4DD7-BDA4-58DB6F108C2C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thrift", "..\..\lib\csharp\src\Thrift.csproj", "{499EB63C-D74C-47E8-AE48-A2FC94538E9D}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {48DD757F-CA95-4DD7-BDA4-58DB6F108C2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48DD757F-CA95-4DD7-BDA4-58DB6F108C2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48DD757F-CA95-4DD7-BDA4-58DB6F108C2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48DD757F-CA95-4DD7-BDA4-58DB6F108C2C}.Release|Any CPU.Build.0 = Release|Any CPU + {499EB63C-D74C-47E8-AE48-A2FC94538E9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {499EB63C-D74C-47E8-AE48-A2FC94538E9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {499EB63C-D74C-47E8-AE48-A2FC94538E9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {499EB63C-D74C-47E8-AE48-A2FC94538E9D}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0B669681-48D0-4767-B9F4-485346FC4AAC} + EndGlobalSection +EndGlobal diff --git a/test/csharp/app.config b/test/csharp/app.config new file mode 100644 index 00000000000..51278a45631 --- /dev/null +++ b/test/csharp/app.config @@ -0,0 +1,3 @@ + + + diff --git a/tutorial/csharp/CsharpClient/CsharpClient.cs b/tutorial/csharp/CsharpClient/CsharpClient.cs new file mode 100644 index 00000000000..113a4722345 --- /dev/null +++ b/tutorial/csharp/CsharpClient/CsharpClient.cs @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System; +using Thrift; +using Thrift.Protocol; +using Thrift.Server; +using Thrift.Transport; + + +namespace CSharpTutorial +{ + public class CSharpClient + { + public static void Main() + { + try + { + TTransport transport = new TSocket("localhost", 9090); + TProtocol protocol = new TBinaryProtocol(transport); + Calculator.Client client = new Calculator.Client(protocol); + + transport.Open(); + try + { + client.ping(); + Console.WriteLine("ping()"); + + int sum = client.add(1, 1); + Console.WriteLine("1+1={0}", sum); + + Work work = new Work(); + + work.Op = Operation.DIVIDE; + work.Num1 = 1; + work.Num2 = 0; + try + { + int quotient = client.calculate(1, work); + Console.WriteLine("Whoa we can divide by 0"); + } + catch (InvalidOperation io) + { + Console.WriteLine("Invalid operation: " + io.Why); + } + + work.Op = Operation.SUBTRACT; + work.Num1 = 15; + work.Num2 = 10; + try + { + int diff = client.calculate(1, work); + Console.WriteLine("15-10={0}", diff); + } + catch (InvalidOperation io) + { + Console.WriteLine("Invalid operation: " + io.Why); + } + + SharedStruct log = client.getStruct(1); + Console.WriteLine("Check log: {0}", log.Value); + + } + finally + { + transport.Close(); + } + } + catch (TApplicationException x) + { + Console.WriteLine(x.StackTrace); + } + + } + } +} diff --git a/tutorial/csharp/CsharpClient/CsharpClient.csproj b/tutorial/csharp/CsharpClient/CsharpClient.csproj new file mode 100644 index 00000000000..1ea7ff63902 --- /dev/null +++ b/tutorial/csharp/CsharpClient/CsharpClient.csproj @@ -0,0 +1,110 @@ + + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {18F24087-4760-43DA-ACAB-7B9F0E096B11} + Exe + Properties + CsharpClient + CsharpClient + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + 3.5 + + + 3.5 + + + + + + + Calculator.cs + + + InvalidOperation.cs + + + Operation.cs + + + SharedService.cs + + + SharedStruct.cs + + + tutorial.Constants.cs + + + Work.cs + + + + + + + {499eb63c-d74c-47e8-ae48-a2fc94538e9d} + Thrift + + + + + pushd "$(SolutionDir)" +thrift -gen csharp -r ../tutorial.thrift +popd + + + + \ No newline at end of file diff --git a/tutorial/csharp/CsharpClient/Properties/AssemblyInfo.cs b/tutorial/csharp/CsharpClient/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..8f5b32a2842 --- /dev/null +++ b/tutorial/csharp/CsharpClient/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CsharpClient")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("The Apache Software Foundation")] +[assembly: AssemblyProduct("Thrift")] +[assembly: AssemblyCopyright("The Apache Software Foundation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1a461214-fa28-452a-bd1d-d23ca8e947e3")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.14.0.0")] +[assembly: AssemblyFileVersion("0.14.0.0")] diff --git a/tutorial/csharp/CsharpServer/CsharpServer.cs b/tutorial/csharp/CsharpServer/CsharpServer.cs new file mode 100644 index 00000000000..439790aaf59 --- /dev/null +++ b/tutorial/csharp/CsharpServer/CsharpServer.cs @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System; +using System.Collections.Generic; +using Thrift.Server; +using Thrift.Transport; + +namespace CSharpTutorial +{ + public class CalculatorHandler : Calculator.Iface + { + Dictionary log; + + public CalculatorHandler() + { + log = new Dictionary(); + } + + public void ping() + { + Console.WriteLine("ping()"); + } + + public int add(int n1, int n2) + { + Console.WriteLine("add({0},{1})", n1, n2); + return n1 + n2; + } + + public int calculate(int logid, Work work) + { + Console.WriteLine("calculate({0}, [{1},{2},{3}])", logid, work.Op, work.Num1, work.Num2); + int val = 0; + switch (work.Op) + { + case Operation.ADD: + val = work.Num1 + work.Num2; + break; + + case Operation.SUBTRACT: + val = work.Num1 - work.Num2; + break; + + case Operation.MULTIPLY: + val = work.Num1 * work.Num2; + break; + + case Operation.DIVIDE: + if (work.Num2 == 0) + { + InvalidOperation io = new InvalidOperation(); + io.WhatOp = (int)work.Op; + io.Why = "Cannot divide by 0"; + throw io; + } + val = work.Num1 / work.Num2; + break; + + default: + { + InvalidOperation io = new InvalidOperation(); + io.WhatOp = (int)work.Op; + io.Why = "Unknown operation"; + throw io; + } + } + + SharedStruct entry = new SharedStruct(); + entry.Key = logid; + entry.Value = val.ToString(); + log[logid] = entry; + + return val; + } + + public SharedStruct getStruct(int key) + { + Console.WriteLine("getStruct({0})", key); + return log[key]; + } + + public void zip() + { + Console.WriteLine("zip()"); + } + } + + public class CSharpServer + { + public static void Main() + { + try + { + CalculatorHandler handler = new CalculatorHandler(); + Calculator.Processor processor = new Calculator.Processor(handler); + TServerTransport serverTransport = new TServerSocket(9090); + TServer server = new TSimpleServer(processor, serverTransport); + + // Use this for a multithreaded server + // server = new TThreadPoolServer(processor, serverTransport); + + Console.WriteLine("Starting the server..."); + server.Serve(); + } + catch (Exception x) + { + Console.WriteLine(x.StackTrace); + } + Console.WriteLine("done."); + } + } +} diff --git a/tutorial/csharp/CsharpServer/CsharpServer.csproj b/tutorial/csharp/CsharpServer/CsharpServer.csproj new file mode 100644 index 00000000000..07481806c33 --- /dev/null +++ b/tutorial/csharp/CsharpServer/CsharpServer.csproj @@ -0,0 +1,111 @@ + + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {66707BAE-BBF9-4F03-B53E-BE3AD58322F8} + Exe + Properties + CsharpServer + CsharpServer + v3.5 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + 3.5 + + + 3.5 + + + 3.5 + + + + + + + Calculator.cs + + + InvalidOperation.cs + + + Operation.cs + + + SharedService.cs + + + SharedStruct.cs + + + tutorial.Constants.cs + + + Work.cs + + + + + + + {499eb63c-d74c-47e8-ae48-a2fc94538e9d} + Thrift + + + + + pushd "$(SolutionDir)" +thrift -gen csharp -r ../tutorial.thrift +popd + + + + + \ No newline at end of file diff --git a/tutorial/csharp/CsharpServer/Properties/AssemblyInfo.cs b/tutorial/csharp/CsharpServer/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..0d4f0fbb1b7 --- /dev/null +++ b/tutorial/csharp/CsharpServer/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CsharpServer")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("The Apache Software Foundation")] +[assembly: AssemblyProduct("Thrift")] +[assembly: AssemblyCopyright("The Apache Software Foundation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("e3b428f4-b2e9-4fc1-8a34-84abc4339860")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.14.0.0")] +[assembly: AssemblyFileVersion("0.14.0.0")] diff --git a/tutorial/csharp/tutorial.sln b/tutorial/csharp/tutorial.sln new file mode 100644 index 00000000000..ec57a188d3b --- /dev/null +++ b/tutorial/csharp/tutorial.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Thrift", "..\..\lib\csharp\src\Thrift.csproj", "{499EB63C-D74C-47E8-AE48-A2FC94538E9D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsharpClient", "CsharpClient\CsharpClient.csproj", "{18F24087-4760-43DA-ACAB-7B9F0E096B11}" + ProjectSection(ProjectDependencies) = postProject + {499EB63C-D74C-47E8-AE48-A2FC94538E9D} = {499EB63C-D74C-47E8-AE48-A2FC94538E9D} + {66707BAE-BBF9-4F03-B53E-BE3AD58322F8} = {66707BAE-BBF9-4F03-B53E-BE3AD58322F8} + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsharpServer", "CsharpServer\CsharpServer.csproj", "{66707BAE-BBF9-4F03-B53E-BE3AD58322F8}" + ProjectSection(ProjectDependencies) = postProject + {499EB63C-D74C-47E8-AE48-A2FC94538E9D} = {499EB63C-D74C-47E8-AE48-A2FC94538E9D} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {499EB63C-D74C-47E8-AE48-A2FC94538E9D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {499EB63C-D74C-47E8-AE48-A2FC94538E9D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {499EB63C-D74C-47E8-AE48-A2FC94538E9D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {499EB63C-D74C-47E8-AE48-A2FC94538E9D}.Release|Any CPU.Build.0 = Release|Any CPU + {18F24087-4760-43DA-ACAB-7B9F0E096B11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18F24087-4760-43DA-ACAB-7B9F0E096B11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18F24087-4760-43DA-ACAB-7B9F0E096B11}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18F24087-4760-43DA-ACAB-7B9F0E096B11}.Release|Any CPU.Build.0 = Release|Any CPU + {66707BAE-BBF9-4F03-B53E-BE3AD58322F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {66707BAE-BBF9-4F03-B53E-BE3AD58322F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {66707BAE-BBF9-4F03-B53E-BE3AD58322F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {66707BAE-BBF9-4F03-B53E-BE3AD58322F8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal