├── .bazelproject ├── .bazelrc ├── .github ├── actions │ └── install │ │ └── action.yaml └── workflows │ └── bazel_test.yaml ├── .gitignore ├── BUILD.bazel ├── LICENSE ├── PATENTS ├── README.md ├── RELEASE-NOTES.md ├── WORKSPACE ├── config.pb.json ├── src ├── main │ ├── java │ │ └── me │ │ │ └── dinowernli │ │ │ └── grpc │ │ │ └── polyglot │ │ │ ├── BUILD │ │ │ ├── Main.java │ │ │ ├── command │ │ │ ├── BUILD │ │ │ ├── ServiceCall.java │ │ │ └── ServiceList.java │ │ │ ├── config │ │ │ ├── BUILD │ │ │ ├── CommandLineArgs.java │ │ │ └── ConfigurationLoader.java │ │ │ ├── grpc │ │ │ ├── BUILD │ │ │ ├── ChannelFactory.java │ │ │ ├── CompositeStreamObserver.java │ │ │ ├── DoneObserver.java │ │ │ ├── DynamicGrpcClient.java │ │ │ ├── ServerReflectionClient.java │ │ │ └── SingleResponseCallback.java │ │ │ ├── io │ │ │ ├── BUILD │ │ │ ├── LoggingStatsWriter.java │ │ │ ├── MessageReader.java │ │ │ ├── MessageWriter.java │ │ │ ├── Output.java │ │ │ ├── OutputImpl.java │ │ │ └── testing │ │ │ │ ├── BUILD │ │ │ │ └── TestData.java │ │ │ ├── oauth2 │ │ │ ├── BUILD │ │ │ ├── OauthCredentialsFactory.java │ │ │ └── RefreshTokenCredentials.java │ │ │ ├── protobuf │ │ │ ├── BUILD │ │ │ ├── DynamicMessageMarshaller.java │ │ │ ├── ProtoMethodName.java │ │ │ ├── ProtocInvoker.java │ │ │ ├── ServiceResolver.java │ │ │ └── WellKnownTypes.java │ │ │ ├── server │ │ │ ├── BUILD │ │ │ ├── HelloServiceImpl.java │ │ │ └── Main.java │ │ │ └── testing │ │ │ ├── BUILD │ │ │ ├── RecordingOutput.java │ │ │ ├── RecordingTestService.java │ │ │ ├── TestServer.java │ │ │ ├── TestUtils.java │ │ │ └── test-certificates │ │ │ ├── ca.pem │ │ │ ├── client.key │ │ │ ├── client.pem │ │ │ ├── openssl.cnf │ │ │ ├── readme.txt │ │ │ ├── server.key │ │ │ └── server.pem │ └── proto │ │ ├── BUILD │ │ ├── config.proto │ │ ├── greeting.proto │ │ ├── hello.proto │ │ └── testing │ │ ├── BUILD │ │ ├── foo │ │ ├── BUILD │ │ └── foo.proto │ │ ├── protobuf │ │ ├── BUILD │ │ └── standalone.proto │ │ └── test_service.proto ├── test │ └── java │ │ └── me │ │ └── dinowernli │ │ └── grpc │ │ └── polyglot │ │ ├── command │ │ ├── BUILD │ │ └── ServiceListTest.java │ │ ├── config │ │ ├── BUILD │ │ ├── CommandLineArgsTest.java │ │ └── ConfigurationLoaderTest.java │ │ ├── grpc │ │ ├── BUILD │ │ ├── CompositeStreamObserverTest.java │ │ └── DynamicGrpcClientTest.java │ │ ├── integration │ │ ├── BUILD │ │ ├── ClientServerIntegrationTest.java │ │ └── TlsIntegrationTest.java │ │ ├── io │ │ ├── BUILD │ │ ├── MessageReaderTest.java │ │ ├── MessageWriterTest.java │ │ ├── OutputTest.java │ │ ├── ReadWriteRoundTripTest.java │ │ └── testdata │ │ │ ├── request.pb.ascii │ │ │ ├── request_empty.pb.ascii │ │ │ ├── request_with_primitives.pb.ascii │ │ │ ├── requests_multi.pb.ascii │ │ │ ├── requests_multi_interrupted.pb.ascii │ │ │ └── response_any.pb.ascii │ │ ├── oauth2 │ │ ├── BUILD │ │ └── OauthCredentialsFactoryTest.java │ │ └── protobuf │ │ ├── BUILD │ │ ├── ProtoMethodNameTest.java │ │ ├── ProtocInvokerTest.java │ │ └── ServiceResolverTest.java └── tools │ ├── build-release.sh │ ├── check-buildifier.sh │ ├── example │ ├── README.md │ ├── call-command-example-reflection.sh │ ├── call-command-example.sh │ ├── list-command-with-filter.sh │ ├── list-command.sh │ ├── request.pb.ascii │ ├── requests_multi.pb.ascii │ ├── run-server.sh │ └── stream-call-command-example.sh │ ├── generate-eclipse.py │ ├── generate-intellij.py │ └── idea-template │ ├── compiler.xml │ ├── copyright │ └── profiles_settings.xml │ ├── misc.xml │ ├── modules.xml │ ├── vcs.xml │ └── workspace.xml └── third_party ├── google-oauth ├── BUILD └── google-oauth.LICENSE ├── grpc ├── BUILD └── grpc.LICENSE ├── guava ├── BUILD └── guava.LICENSE ├── jackson-core ├── BUILD └── jackson-core.LICENSE ├── jcommander ├── BUILD └── jcommander.LICENSE ├── logging ├── BUILD └── slf4j.LICENSE ├── netty ├── BUILD └── netty.LICENSE ├── protobuf ├── BUILD └── protobuf.LICENSE ├── protoc-jar ├── BUILD └── protoc-jar.LICENSE └── testing ├── BUILD ├── junit.LICENSE ├── mockito.LICENSE └── truth.LICENSE /.bazelproject: -------------------------------------------------------------------------------- 1 | directories: 2 | # Add the directories you want added as source here 3 | # By default, we've added your entire workspace ('.') 4 | src/main 5 | src/test 6 | src/tools 7 | third_party 8 | 9 | targets: 10 | # Add targets that reach the source code that you want to resolve here 11 | # By default, we've added all targets in your workspace 12 | //... 13 | 14 | additional_languages: 15 | # Uncomment any additional languages you want supported 16 | # android 17 | # dart 18 | # python 19 | # scala 20 | 21 | -------------------------------------------------------------------------------- /.bazelrc: -------------------------------------------------------------------------------- 1 | test --test_output=errors 2 | -------------------------------------------------------------------------------- /.github/actions/install/action.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Install 3 | runs: 4 | using: composite 5 | steps: 6 | - name: Install Bazel (linux) 7 | run: | 8 | if [ "$RUNNER_OS" == "macOS" ]; then 9 | curl -L -O "https://github.com/bazelbuild/bazel/releases/download/4.2.1/bazel-4.2.1-installer-darwin-x86_64.sh" 10 | chmod a+x ./bazel-4.2.1-installer-darwin-x86_64.sh 11 | ./bazel-4.2.1-installer-darwin-x86_64.sh 12 | elif [ "$RUNNER_OS" == "Linux" ]; then 13 | wget 'https://github.com/bazelbuild/bazel/releases/download/4.2.1/bazel_4.2.1-linux-x86_64.deb' 14 | chmod +x bazel_4.2.1-linux-x86_64.deb 15 | sudo dpkg -i bazel_4.2.1-linux-x86_64.deb 16 | else 17 | echo 'Unrecognized OS' 18 | fi 19 | shell: bash 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/bazel_test.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bazel Test 3 | 4 | on: [push, pull_request] 5 | 6 | jobs: 7 | test: 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, macos-latest] 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Setup Bazel cache 17 | uses: actions/cache@v2 18 | with: 19 | path: | 20 | ~/.cache/bazel/_bazel_runner 21 | /private/var/tmp/_bazel_runner 22 | key: bazel-${{ matrix.os }} 23 | 24 | - name: Install Bazel 25 | uses: ./.github/actions/install 26 | 27 | - name: Run tests 28 | run: bazel test //src/... 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | // Bazel stuff. 2 | bazel-* 3 | 4 | // Vim stuff. 5 | *swp 6 | 7 | // Eclipse project. 8 | .classpath* 9 | .project* 10 | 11 | // Intell-J 12 | out/ 13 | .idea 14 | .settings 15 | project.iml 16 | .ijwb 17 | 18 | // Phabricator stuff. 19 | .arcconfig 20 | 21 | // The build output. 22 | output 23 | 24 | // Mac 25 | .DS_Store 26 | -------------------------------------------------------------------------------- /BUILD.bazel: -------------------------------------------------------------------------------- 1 | load("@com_github_bazelbuild_buildtools//buildifier:def.bzl", "buildifier") 2 | 3 | buildifier( 4 | name = "buildifier", 5 | ) 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, gRPC Ecosystem 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of polyglot nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /PATENTS: -------------------------------------------------------------------------------- 1 | Additional IP Rights Grant (Patents) 2 | 3 | "This implementation" means the copyrightable works distributed by 4 | Google as part of the GRPC project. 5 | 6 | Google hereby grants to You a perpetual, worldwide, non-exclusive, 7 | no-charge, royalty-free, irrevocable (except as stated in this section) 8 | patent license to make, have made, use, offer to sell, sell, import, 9 | transfer and otherwise run, modify and propagate the contents of this 10 | implementation of GRPC, where such license applies only to those patent 11 | claims, both currently owned or controlled by Google and acquired in 12 | the future, licensable by Google that are necessarily infringed by this 13 | implementation of GRPC. This grant does not include claims that would be 14 | infringed only as a consequence of further modification of this 15 | implementation. If you or your agent or exclusive licensee institute or 16 | order or agree to the institution of patent litigation against any 17 | entity (including a cross-claim or counterclaim in a lawsuit) alleging 18 | that this implementation of GRPC or any code incorporated within this 19 | implementation of GRPC constitutes direct or contributory patent 20 | infringement, or inducement of patent infringement, then any patent 21 | rights granted to you under this License for this implementation of GRPC 22 | shall terminate as of the date such litigation is filed. 23 | Status API Training Shop Blog About 24 | -------------------------------------------------------------------------------- /RELEASE-NOTES.md: -------------------------------------------------------------------------------- 1 | # Polyglot Release Notes 2 | 3 | Quick links: 4 | 5 | * [Releases page](https://github.com/dinowernli/polyglot/releases) 6 | 7 | ## Upcoming release 8 | 9 | * Notes go here. 10 | 11 | ## 2.0.0 12 | 13 | Overhauled Polyglot's command line options by using a different underlying library. This allows us to 14 | * distinguish between commands (`list`) and options (`--add_protoc_includes`). 15 | * distinguish between general options (applicable to all commands), and command specific options. 16 | * have options with values that contain spaces (e.g. `--metadata` with authentication details) . 17 | 18 | Invoking Polyglot takes the following form now: 19 | 20 | `java -jar polyglot.jar [options] [command] [command specific options]` 21 | 22 | Both `=` and spaces are allowed to separate options from values (`--add_protoc_includes=.` or `--add_protoc_includes .`). 23 | 24 | Please note that this change will break existing scripts after updating to the new version. Use the `--help` output to find out which option goes where on the command line. 25 | 26 | ## 1.6.0 27 | 28 | * Fixed a bug where Polyglot would bail out if the specified output file didn't already exist. 29 | * Added support for protobuf's well-known-types, including proper handling of "Any". 30 | 31 | ## 1.5.0 32 | 33 | * Made sure exit code 1 is returned if command line arguments fail to parse or the rpc fails. 34 | * Fixed a bug where Polyglot would error out if the remote server didn't support reflection. 35 | 36 | ## 1.4.0 37 | 38 | * Added support for grpc server reflection. 39 | * Added support for custom metadata. 40 | * Upgraded the protoc version used to compile files to 3.2.0. 41 | 42 | ## 1.3.0 43 | 44 | * Upgraded to grpc 1.4.0. 45 | * Added support for client certificates to TLS. 46 | 47 | ## 1.2.0 48 | 49 | * Upgraded to grpc 1.0.0. 50 | * Removed the separate per-platform binaries, the same binary can now be used on all platforms. 51 | 52 | ## 1.1.0 53 | 54 | * Added a new command, called `list` as well as a documentation section in the README. The default command is `call`, which makes an rpc call to a remote endpoint. 55 | * Added support for rpcs which contain multiple requests (`client_streaming` and `bidi_streaming`). 56 | * Response messages are now printed to `stdout` rather than the logs. This makes piping the results to files easier. 57 | * The `--help` output now no longer contains a stack trace. 58 | 59 | ## 1.0.0 60 | 61 | * Initial release. 62 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | workspace(name = "polyglot") 2 | 3 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") 4 | 5 | # JVM 6 | 7 | RULES_JVM_EXTERNAL_TAG = "4.1" 8 | 9 | RULES_JVM_EXTERNAL_SHA = "f36441aa876c4f6427bfb2d1f2d723b48e9d930b62662bf723ddfb8fc80f0140" 10 | 11 | http_archive( 12 | name = "rules_jvm_external", 13 | sha256 = RULES_JVM_EXTERNAL_SHA, 14 | strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, 15 | urls = ["https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG], 16 | ) 17 | 18 | load("@rules_jvm_external//:defs.bzl", "maven_install") 19 | 20 | # GRPC 21 | 22 | http_archive( 23 | name = "io_grpc_grpc_java", 24 | sha256 = "a61a678f995f1d612bb23d5fb721d83b6960508cc1e0b0dc3c164d6d8d8d24e0", 25 | strip_prefix = "grpc-java-1.41.0", 26 | url = "https://github.com/grpc/grpc-java/archive/v1.41.0.zip", 27 | ) 28 | 29 | load("@io_grpc_grpc_java//:repositories.bzl", "IO_GRPC_GRPC_JAVA_ARTIFACTS") 30 | load("@io_grpc_grpc_java//:repositories.bzl", "IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS") 31 | 32 | # Autotest 33 | 34 | http_archive( 35 | name = "autotest", 36 | strip_prefix = "bazel-junit-autotest-0.0.2", 37 | urls = ["https://github.com/dinowernli/bazel-junit-autotest/archive/v0.0.2.zip"], 38 | ) 39 | 40 | load("@autotest//bzl:autotest.bzl", "autotest_junit_repo") 41 | 42 | autotest_junit_repo( 43 | autotest_workspace = "@autotest", 44 | junit_jar = "//third_party/testing", 45 | ) 46 | 47 | # Direct java deps 48 | 49 | MAVEN_ARTIFACTS = [ 50 | "com.beust:jcommander:1.72", 51 | "com.fasterxml.jackson.core:jackson-core:2.6.3", 52 | "com.github.os72:protoc-jar:3.2.0", 53 | "com.google.truth:truth:0.28", 54 | "com.google.guava:guava:30.1-jre", 55 | "junit:junit:4.12", 56 | "org.mockito:mockito-all:1.10.19", 57 | "org.slf4j:jul-to-slf4j:1.7.13", 58 | "org.slf4j:slf4j-api:1.7.13", 59 | "org.slf4j:slf4j-simple:1.7.13", 60 | "com.google.oauth-client:google-oauth-client:1.30.1", 61 | ] 62 | 63 | maven_install( 64 | artifacts = MAVEN_ARTIFACTS + IO_GRPC_GRPC_JAVA_ARTIFACTS, 65 | generate_compat_repositories = True, 66 | override_targets = IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS, 67 | repositories = [ 68 | "https://jcenter.bintray.com/", 69 | "https://maven.google.com", 70 | "https://repo1.maven.org/maven2", 71 | ], 72 | ) 73 | 74 | load("@maven//:compat.bzl", "compat_repositories") 75 | 76 | compat_repositories() 77 | 78 | load("@io_grpc_grpc_java//:repositories.bzl", "grpc_java_repositories") 79 | 80 | grpc_java_repositories() 81 | 82 | # Protobuf 83 | 84 | load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps") 85 | 86 | protobuf_deps() 87 | 88 | # Buildifier 89 | 90 | http_archive( 91 | name = "io_bazel_rules_go", 92 | sha256 = "8e968b5fcea1d2d64071872b12737bbb5514524ee5f0a4f54f5920266c261acb", 93 | urls = [ 94 | "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.28.0/rules_go-v0.28.0.zip", 95 | "https://github.com/bazelbuild/rules_go/releases/download/v0.28.0/rules_go-v0.28.0.zip", 96 | ], 97 | ) 98 | 99 | http_archive( 100 | name = "bazel_gazelle", 101 | sha256 = "de69a09dc70417580aabf20a28619bb3ef60d038470c7cf8442fafcf627c21cb", 102 | urls = [ 103 | "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz", 104 | "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.24.0/bazel-gazelle-v0.24.0.tar.gz", 105 | ], 106 | ) 107 | 108 | load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") 109 | load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") 110 | 111 | go_rules_dependencies() 112 | 113 | go_register_toolchains(version = "1.17.2") 114 | 115 | gazelle_dependencies() 116 | 117 | http_archive( 118 | name = "com_github_bazelbuild_buildtools", 119 | strip_prefix = "buildtools-master", 120 | url = "https://github.com/bazelbuild/buildtools/archive/master.zip", 121 | ) 122 | -------------------------------------------------------------------------------- /config.pb.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "default", 5 | "call_config": { 6 | "use_tls": "false" 7 | } 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_binary( 4 | name = "polyglot", 5 | main_class = "me.dinowernli.grpc.polyglot.Main", 6 | runtime_deps = [ 7 | ":polyglot-lib", 8 | ], 9 | ) 10 | 11 | java_library( 12 | name = "polyglot-lib", 13 | srcs = glob(["*.java"]), 14 | deps = [ 15 | "//src/main/java/me/dinowernli/grpc/polyglot/command", 16 | "//src/main/java/me/dinowernli/grpc/polyglot/config", 17 | "//src/main/java/me/dinowernli/grpc/polyglot/io", 18 | "//src/main/java/me/dinowernli/grpc/polyglot/protobuf", 19 | "//src/main/proto:config_java_proto", 20 | "//third_party/grpc", 21 | "//third_party/logging:logging-api", 22 | "//third_party/logging:logging-impl-stdout", 23 | "//third_party/protobuf", 24 | ], 25 | ) 26 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/Main.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot; 2 | 3 | import java.util.logging.LogManager; 4 | 5 | import com.google.protobuf.DescriptorProtos.FileDescriptorSet; 6 | import me.dinowernli.grpc.polyglot.command.ServiceCall; 7 | import me.dinowernli.grpc.polyglot.command.ServiceList; 8 | import me.dinowernli.grpc.polyglot.config.CommandLineArgs; 9 | import me.dinowernli.grpc.polyglot.config.ConfigurationLoader; 10 | import me.dinowernli.grpc.polyglot.io.Output; 11 | import me.dinowernli.grpc.polyglot.protobuf.ProtocInvoker; 12 | import me.dinowernli.grpc.polyglot.protobuf.ProtocInvoker.ProtocInvocationException; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | import org.slf4j.bridge.SLF4JBridgeHandler; 16 | import polyglot.ConfigProto.Configuration; 17 | import polyglot.ConfigProto.ProtoConfiguration; 18 | 19 | public class Main { 20 | private static final Logger logger = LoggerFactory.getLogger(Main.class); 21 | private static final String VERSION = "2.0.0+dev"; 22 | 23 | public static void main(String[] args) { 24 | // Fix the logging setup. 25 | setupJavaUtilLogging(); 26 | 27 | logger.info("Polyglot version: " + VERSION); 28 | 29 | final CommandLineArgs arguments; 30 | try { 31 | arguments = CommandLineArgs.parse(args); 32 | } catch (Throwable t) { 33 | throw new RuntimeException("Unable to parse command line flags", t); 34 | } 35 | 36 | // Catch the help case. 37 | if (arguments.isHelp()) { 38 | logger.info(CommandLineArgs.getUsage()); 39 | return; 40 | } 41 | 42 | // Check for command 43 | String command = arguments.command().orElseThrow(() -> new RuntimeException("Missing command")); 44 | 45 | final ConfigurationLoader configLoader = arguments.configSetPath() 46 | .map(ConfigurationLoader::forFile).orElseGet(() -> ConfigurationLoader.forDefaultConfigSet()) 47 | .withOverrides(arguments); 48 | Configuration config = arguments.configName() 49 | .map(configLoader::getNamedConfiguration).orElseGet(() -> configLoader.getDefaultConfiguration()); 50 | logger.info("Loaded configuration: " + config.getName()); 51 | 52 | try(Output commandLineOutput = Output.forConfiguration(config.getOutputConfig())) { 53 | switch (command) { 54 | case CommandLineArgs.LIST_SERVICES_COMMAND: 55 | FileDescriptorSet fileDescriptorSet = getFileDescriptorSet(config.getProtoConfig()); 56 | ServiceList.listServices( 57 | commandLineOutput, 58 | fileDescriptorSet, config.getProtoConfig().getProtoDiscoveryRoot(), 59 | arguments.serviceFilter(), arguments.methodFilter(), arguments.withMessage()); 60 | break; 61 | 62 | case CommandLineArgs.CALL_COMMAND: 63 | ServiceCall.callEndpoint( 64 | commandLineOutput, 65 | config.getProtoConfig(), 66 | arguments.endpoint(), 67 | arguments.fullMethod(), 68 | arguments.protoDiscoveryRoot(), 69 | arguments.configSetPath(), 70 | arguments.additionalProtocIncludes(), 71 | config.getCallConfig()); 72 | break; 73 | 74 | default: 75 | throw new RuntimeException("Unknown command: " + arguments.command().get()); 76 | } 77 | } catch (Throwable t) { 78 | logger.warn("Caught top-level exception during command execution", t); 79 | throw new RuntimeException(t); 80 | } 81 | } 82 | 83 | /** Invokes protoc and returns a {@link FileDescriptorSet} used for discovery. */ 84 | private static FileDescriptorSet getFileDescriptorSet(ProtoConfiguration protoConfig) { 85 | try { 86 | return ProtocInvoker.forConfig(protoConfig).invoke(); 87 | } catch (ProtocInvocationException e) { 88 | throw new RuntimeException("Failed to invoke the protoc binary", e); 89 | } 90 | } 91 | 92 | /** Redirects the output of standard java loggers to our slf4j handler. */ 93 | private static void setupJavaUtilLogging() { 94 | LogManager.getLogManager().reset(); 95 | SLF4JBridgeHandler.removeHandlersForRootLogger(); 96 | SLF4JBridgeHandler.install(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/command/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "command", 5 | srcs = glob(["*.java"]), 6 | deps = [ 7 | "//src/main/java/me/dinowernli/grpc/polyglot/grpc", 8 | "//src/main/java/me/dinowernli/grpc/polyglot/io", 9 | "//src/main/java/me/dinowernli/grpc/polyglot/oauth2", 10 | "//src/main/java/me/dinowernli/grpc/polyglot/protobuf", 11 | "//src/main/proto:config_java_proto", 12 | "//third_party/google-oauth", 13 | "//third_party/grpc", 14 | "//third_party/guava", 15 | "//third_party/logging:logging-api", 16 | "//third_party/protobuf", 17 | ], 18 | ) 19 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/command/ServiceList.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.command; 2 | 3 | import java.io.File; 4 | import java.util.List; 5 | import java.util.Optional; 6 | import java.util.stream.Collectors; 7 | 8 | import com.google.common.base.Joiner; 9 | import com.google.protobuf.DescriptorProtos.FileDescriptorSet; 10 | import com.google.protobuf.Descriptors.Descriptor; 11 | import com.google.protobuf.Descriptors.FieldDescriptor; 12 | import com.google.protobuf.Descriptors.MethodDescriptor; 13 | import com.google.protobuf.Descriptors.ServiceDescriptor; 14 | 15 | import me.dinowernli.grpc.polyglot.io.Output; 16 | import me.dinowernli.grpc.polyglot.protobuf.ServiceResolver; 17 | 18 | /** Utility to list the services, methods and message definitions for the known GRPC end-points */ 19 | public class ServiceList { 20 | 21 | /** Lists the GRPC services - filtered by service name (contains) or method name (contains) */ 22 | public static void listServices( 23 | Output output, 24 | FileDescriptorSet fileDescriptorSet, 25 | String protoDiscoveryRoot, 26 | Optional serviceFilter, 27 | Optional methodFilter, 28 | Optional withMessage) { 29 | 30 | ServiceResolver serviceResolver = ServiceResolver.fromFileDescriptorSet(fileDescriptorSet); 31 | 32 | // Add white-space before the rendered output 33 | output.newLine(); 34 | 35 | for (ServiceDescriptor descriptor : serviceResolver.listServices()) { 36 | boolean matchingDescriptor = 37 | !serviceFilter.isPresent() 38 | || descriptor.getFullName().toLowerCase().contains(serviceFilter.get().toLowerCase()); 39 | 40 | if (matchingDescriptor) { 41 | listMethods(output, protoDiscoveryRoot, descriptor, methodFilter, withMessage); 42 | } 43 | } 44 | } 45 | 46 | /** Lists the methods on the service (the methodFilter will be applied if non-empty) */ 47 | private static void listMethods( 48 | Output output, 49 | String protoDiscoveryRoot, 50 | ServiceDescriptor descriptor, 51 | Optional methodFilter, 52 | Optional withMessage) { 53 | 54 | boolean printedService = false; 55 | 56 | // Due to the way the protos are discovered, the leaf directly of the protoDiscoveryRoot 57 | // is the same as the root directory as the proto file 58 | File protoDiscoveryDir = new File(protoDiscoveryRoot).getParentFile(); 59 | 60 | for (MethodDescriptor method : descriptor.getMethods()) { 61 | if (!methodFilter.isPresent() || method.getName().contains(methodFilter.get())) { 62 | 63 | // Only print the service name once - and only if a method is going to be printed 64 | if (!printedService) { 65 | File pFile = new File(protoDiscoveryDir, descriptor.getFile().getName()); 66 | output.writeLine(descriptor.getFullName() + " -> " + pFile.getAbsolutePath()); 67 | printedService = true; 68 | } 69 | 70 | output.writeLine(" " + descriptor.getFullName() + "/" + method.getName()); 71 | 72 | // If requested, add the message definition 73 | if (withMessage.isPresent() && withMessage.get()) { 74 | output.writeLine(renderDescriptor(method.getInputType(), " ")); 75 | output.newLine(); 76 | } 77 | } 78 | } 79 | 80 | if (printedService) { 81 | output.newLine(); 82 | } 83 | } 84 | 85 | /** Creates a human-readable string to help the user build a message to send to an end-point */ 86 | private static String renderDescriptor(Descriptor descriptor, String indent) { 87 | if (descriptor.getFields().size() == 0) { 88 | return indent + ""; 89 | } 90 | 91 | List fieldsAsStrings = descriptor.getFields().stream() 92 | .map(field -> renderDescriptor(field, indent + " ")) 93 | .collect(Collectors.toList()); 94 | 95 | return Joiner.on(System.lineSeparator()).join(fieldsAsStrings); 96 | } 97 | 98 | /** Create a readable string from the field to help the user build a message */ 99 | private static String renderDescriptor(FieldDescriptor descriptor, String indent) { 100 | String isOpt = descriptor.isOptional() ? "" : ""; 101 | String isRep = descriptor.isRepeated() ? "" : ""; 102 | String fieldPrefix = indent + descriptor.getJsonName() + "[" + isOpt + " " + isRep + "]"; 103 | 104 | if (descriptor.getJavaType() == FieldDescriptor.JavaType.MESSAGE) { 105 | return fieldPrefix + " {" + System.lineSeparator() 106 | + renderDescriptor(descriptor.getMessageType(), indent + " ") 107 | + System.lineSeparator() + indent + "}"; 108 | 109 | } else if (descriptor.getJavaType() == FieldDescriptor.JavaType.ENUM) { 110 | return fieldPrefix + ": " + descriptor.getEnumType().getValues(); 111 | 112 | } else { 113 | return fieldPrefix + ": " + descriptor.getJavaType(); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/config/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "config", 5 | srcs = glob(["*.java"]), 6 | deps = [ 7 | "//src/main/java/me/dinowernli/grpc/polyglot/protobuf", 8 | "//src/main/proto:config_java_proto", 9 | "//third_party/grpc", 10 | "//third_party/guava", 11 | "//third_party/jcommander", 12 | "//third_party/netty", 13 | "//third_party/protobuf", 14 | ], 15 | ) 16 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoader.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.config; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | import java.util.Map; 8 | import java.util.Optional; 9 | import java.util.stream.Collectors; 10 | 11 | import com.google.common.annotations.VisibleForTesting; 12 | import com.google.common.base.Joiner; 13 | import com.google.common.base.Preconditions; 14 | import com.google.protobuf.util.JsonFormat; 15 | 16 | import polyglot.ConfigProto.Configuration; 17 | import polyglot.ConfigProto.ConfigurationSet; 18 | import polyglot.ConfigProto.OutputConfiguration.Destination; 19 | 20 | /** A utility which manipulating and reading a single {@link ConfigurationSet}. */ 21 | public class ConfigurationLoader { 22 | private static final String DEFAULT_FILE_NAME = "config.pb.json"; 23 | private static final String DEFAULT_LOCATION = ".polyglot"; 24 | 25 | /** If this is absent, we hand out default instances of configs. */ 26 | private final Optional configSet; 27 | 28 | /** Optional overrides for the configuration. */ 29 | private final Optional maybeOverrides; 30 | 31 | /** 32 | * Returns a {@link ConfigurationLoader} backed by a {@link ConfigurationSet} in the current 33 | * user's home directory. If no such file exists, it falls back to a default config. 34 | */ 35 | public static ConfigurationLoader forDefaultConfigSet() { 36 | String homeDirectory = System.getProperty("user.home"); 37 | Path defaultLocation = Paths.get(homeDirectory, DEFAULT_LOCATION, DEFAULT_FILE_NAME); 38 | if (Files.exists(defaultLocation)) { 39 | return ConfigurationLoader.forFile(defaultLocation); 40 | } else { 41 | return new ConfigurationLoader(Optional.empty(), Optional.empty()); 42 | } 43 | } 44 | 45 | /** Constructs a {@link ConfigurationLoader} from an explicit {@link ConfigurationSet}. */ 46 | public static ConfigurationLoader forConfigSet(ConfigurationSet configSet) { 47 | return new ConfigurationLoader(Optional.of(configSet), Optional.empty() /* overrides */); 48 | } 49 | 50 | /** Returns a loader backed by config set obtained from the supplied file. */ 51 | public static ConfigurationLoader forFile(Path configFile) { 52 | try { 53 | ConfigurationSet.Builder configSetBuilder = ConfigurationSet.newBuilder(); 54 | String fileContent = Joiner.on('\n').join(Files.readAllLines(configFile)); 55 | JsonFormat.parser().merge(fileContent, configSetBuilder); 56 | return ConfigurationLoader.forConfigSet(configSetBuilder.build()); 57 | } catch (IOException e) { 58 | throw new RuntimeException("Unable to read config file: " + configFile.toString(), e); 59 | } 60 | } 61 | 62 | @VisibleForTesting 63 | ConfigurationLoader(Optional configSet, Optional maybeOverrides) { 64 | this.configSet = configSet; 65 | this.maybeOverrides = maybeOverrides; 66 | } 67 | 68 | /** Returns a new instance of {@link ConfigurationLoader} with the supplied overrides. */ 69 | public ConfigurationLoader withOverrides(CommandLineArgs overrides) { 70 | return new ConfigurationLoader(configSet, Optional.of(overrides)); 71 | } 72 | 73 | /** Returns the default configuration from the loaded configuration set. */ 74 | public Configuration getDefaultConfiguration() { 75 | return applyOverrides(getDefaultConfigurationInternal()); 76 | } 77 | 78 | /** Returns a config with the supplied name and throws if no such config is found. */ 79 | public Configuration getNamedConfiguration(String name) { 80 | return applyOverrides(getNamedConfigurationInternal(name)); 81 | } 82 | 83 | private Configuration getDefaultConfigurationInternal() { 84 | if (isEmptyConfig()) { 85 | return Configuration.getDefaultInstance(); 86 | } 87 | if (configSet.get().getConfigurationsList().isEmpty()) { 88 | throw new IllegalStateException("No configs present in config set"); 89 | } 90 | return configSet.get().getConfigurations(0); 91 | } 92 | 93 | private Configuration getNamedConfigurationInternal(String name) { 94 | Preconditions.checkState(!isEmptyConfig(), "Cannot load named config with a config set"); 95 | return configSet.get().getConfigurationsList().stream() 96 | .filter(config -> config.getName().equals(name)) 97 | .findAny() 98 | .orElseThrow(() -> new IllegalArgumentException("Could not find named config: " + name)); 99 | } 100 | 101 | /** Returns the {@link Configuration} with overrides, if any, applied to it. */ 102 | private Configuration applyOverrides(Configuration configuration) { 103 | if (!maybeOverrides.isPresent()) { 104 | return configuration; 105 | } 106 | 107 | CommandLineArgs overrides = maybeOverrides.get(); 108 | Configuration.Builder resultBuilder = configuration.toBuilder(); 109 | 110 | resultBuilder.getProtoConfigBuilder().setUseReflection(overrides.useReflection()); 111 | 112 | overrides.useTls().ifPresent(resultBuilder.getCallConfigBuilder()::setUseTls); 113 | overrides.outputFilePath().ifPresent(path -> { 114 | resultBuilder.getOutputConfigBuilder().setDestination(Destination.FILE); 115 | resultBuilder.getOutputConfigBuilder().setFilePath(path.toString()); 116 | }); 117 | 118 | resultBuilder.getProtoConfigBuilder().addAllIncludePaths( 119 | overrides.additionalProtocIncludes().stream() 120 | .map(Path::toString) 121 | .collect(Collectors.toList())); 122 | 123 | overrides.protoDiscoveryRoot().ifPresent( 124 | root -> resultBuilder.getProtoConfigBuilder().setProtoDiscoveryRoot(root.toString())); 125 | 126 | overrides.getRpcDeadlineMs().ifPresent( 127 | resultBuilder.getCallConfigBuilder()::setDeadlineMs); 128 | 129 | overrides.tlsCaCertPath().ifPresent( 130 | path -> resultBuilder.getCallConfigBuilder().setTlsCaCertPath(path.toString())); 131 | 132 | overrides.tlsClientCertPath().ifPresent( 133 | path -> resultBuilder.getCallConfigBuilder().setTlsClientCertPath(path.toString())); 134 | 135 | overrides.tlsClientKeyPath().ifPresent( 136 | path -> resultBuilder.getCallConfigBuilder().setTlsClientKeyPath(path.toString())); 137 | 138 | overrides.tlsClientOverrideAuthority() 139 | .ifPresent(resultBuilder.getCallConfigBuilder()::setTlsClientOverrideAuthority); 140 | 141 | overrides.metadata().ifPresent(metadata -> { 142 | for (Map.Entry keyValue : metadata.entries().asList()) { 143 | resultBuilder.getCallConfigBuilder().addMetadataBuilder() 144 | .setName(keyValue.getKey()) 145 | .setValue(keyValue.getValue()) 146 | .build(); 147 | } 148 | }); 149 | 150 | return resultBuilder.build(); 151 | } 152 | 153 | /** Returns false iff this is backed by a real config set (rather than the special empty one). */ 154 | private boolean isEmptyConfig() { 155 | return !configSet.isPresent(); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/grpc/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "grpc", 5 | srcs = glob(["*.java"]), 6 | deps = [ 7 | "//src/main/java/me/dinowernli/grpc/polyglot/oauth2", 8 | "//src/main/java/me/dinowernli/grpc/polyglot/protobuf", 9 | "//src/main/proto:config_java_proto", 10 | "//third_party/google-oauth", 11 | "//third_party/grpc", 12 | "//third_party/guava", 13 | "//third_party/logging:logging-api", 14 | "//third_party/netty", 15 | "//third_party/protobuf", 16 | ], 17 | ) 18 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/grpc/ChannelFactory.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.grpc; 2 | 3 | import java.io.File; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | import java.security.NoSuchAlgorithmException; 8 | import java.security.spec.InvalidKeySpecException; 9 | import java.util.concurrent.Executors; 10 | 11 | import javax.net.ssl.SSLException; 12 | 13 | import com.google.auth.Credentials; 14 | import com.google.common.base.Preconditions; 15 | import com.google.common.net.HostAndPort; 16 | import com.google.common.util.concurrent.ListeningExecutorService; 17 | import com.google.common.util.concurrent.ThreadFactoryBuilder; 18 | import io.grpc.CallOptions; 19 | import io.grpc.Channel; 20 | import io.grpc.ClientCall; 21 | import io.grpc.ClientInterceptor; 22 | import io.grpc.ClientInterceptors; 23 | import io.grpc.Metadata; 24 | import io.grpc.StatusException; 25 | import io.grpc.auth.ClientAuthInterceptor; 26 | import io.grpc.netty.GrpcSslContexts; 27 | import io.grpc.netty.NegotiationType; 28 | import io.grpc.netty.NettyChannelBuilder; 29 | import io.netty.handler.ssl.SslContext; 30 | import io.netty.handler.ssl.SslContextBuilder; 31 | import polyglot.ConfigProto; 32 | 33 | import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator; 34 | 35 | /** Knows how to construct grpc channels. */ 36 | public class ChannelFactory { 37 | private final ConfigProto.CallConfiguration callConfiguration; 38 | private final ListeningExecutorService authExecutor; 39 | 40 | public static ChannelFactory create(ConfigProto.CallConfiguration callConfiguration) { 41 | ListeningExecutorService authExecutor = listeningDecorator( 42 | Executors.newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true).build())); 43 | return new ChannelFactory(callConfiguration, authExecutor); 44 | } 45 | 46 | public ChannelFactory( 47 | ConfigProto.CallConfiguration callConfiguration, ListeningExecutorService authExecutor) { 48 | this.callConfiguration = callConfiguration; 49 | this.authExecutor = authExecutor; 50 | } 51 | 52 | public Channel createChannel(HostAndPort endpoint) { 53 | NettyChannelBuilder nettyChannelBuilder = createChannelBuilder(endpoint); 54 | 55 | if (!callConfiguration.getTlsClientOverrideAuthority().isEmpty()) { 56 | nettyChannelBuilder.overrideAuthority(callConfiguration.getTlsClientOverrideAuthority()); 57 | } 58 | 59 | return nettyChannelBuilder.build(); 60 | } 61 | 62 | public Channel createChannelWithCredentials(HostAndPort endpoint, Credentials credentials) { 63 | return ClientInterceptors.intercept( 64 | createChannel(endpoint), new ClientAuthInterceptor(credentials, authExecutor)); 65 | } 66 | 67 | private NettyChannelBuilder createChannelBuilder(HostAndPort endpoint) { 68 | if (!callConfiguration.getUseTls()) { 69 | return NettyChannelBuilder.forAddress(endpoint.getHost(), endpoint.getPort()) 70 | .negotiationType(NegotiationType.PLAINTEXT) 71 | .intercept(metadataInterceptor()); 72 | } else { 73 | return NettyChannelBuilder.forAddress(endpoint.getHost(), endpoint.getPort()) 74 | .sslContext(createSslContext()) 75 | .negotiationType(NegotiationType.TLS) 76 | .intercept(metadataInterceptor()); 77 | } 78 | } 79 | 80 | private ClientInterceptor metadataInterceptor() { 81 | ClientInterceptor interceptor = new ClientInterceptor() { 82 | @Override 83 | public ClientCall interceptCall( 84 | final io.grpc.MethodDescriptor method, CallOptions callOptions, final Channel next) { 85 | return new ClientInterceptors.CheckedForwardingClientCall(next.newCall(method, callOptions)) { 86 | @Override 87 | protected void checkedStart(Listener responseListener, Metadata headers) 88 | throws StatusException { 89 | for (ConfigProto.CallMetadataEntry entry : callConfiguration.getMetadataList()) { 90 | Metadata.Key key = Metadata.Key.of(entry.getName(), Metadata.ASCII_STRING_MARSHALLER); 91 | headers.put(key, entry.getValue()); 92 | } 93 | delegate().start(responseListener, headers); 94 | } 95 | }; 96 | } 97 | }; 98 | 99 | return interceptor; 100 | } 101 | 102 | private SslContext createSslContext() { 103 | SslContextBuilder resultBuilder = GrpcSslContexts.forClient(); 104 | if (!callConfiguration.getTlsCaCertPath().isEmpty()) { 105 | resultBuilder.trustManager(loadFile(callConfiguration.getTlsCaCertPath())); 106 | } 107 | if (!callConfiguration.getTlsClientCertPath().isEmpty()) { 108 | File cert = loadFile(callConfiguration.getTlsClientCertPath()); 109 | File key = loadFile(callConfiguration.getTlsClientKeyPath()); 110 | 111 | try { 112 | resultBuilder.keyManager(cert, key); 113 | } catch (IllegalArgumentException e) { 114 | if (e.getCause() instanceof NoSuchAlgorithmException 115 | || e.getCause() instanceof InvalidKeySpecException) { 116 | // Catching the illegal argument seems a bit nasty, but it's the only way to react to 117 | // netty not being able to parse a key which is not in the PKCS8 format. 118 | throw new RuntimeException( 119 | "Unable to load private key. Please make sure that the key is in PKCS8 format. See " 120 | + "https://github.com/grpc-ecosystem/polyglot/issues/85 for details.", e); 121 | } else { 122 | // For all other cases, just let the exception bubble up. 123 | throw e; 124 | } 125 | } 126 | } 127 | try { 128 | return resultBuilder.build(); 129 | } catch (SSLException e) { 130 | throw new RuntimeException("Unable to build sslcontext for client call", e); 131 | } 132 | } 133 | 134 | private static File loadFile(String fileName) { 135 | Path filePath = Paths.get(fileName); 136 | Preconditions.checkArgument(Files.exists(filePath), "File " + fileName + " was not found"); 137 | return filePath.toFile(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/grpc/CompositeStreamObserver.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.grpc; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import com.google.common.collect.ImmutableList; 7 | 8 | import io.grpc.stub.StreamObserver; 9 | 10 | /** 11 | * A {@link StreamObserver} which groups multiple observers and executes them all. 12 | */ 13 | public class CompositeStreamObserver implements StreamObserver { 14 | private static final Logger logger = LoggerFactory.getLogger(CompositeStreamObserver.class); 15 | private final ImmutableList> observers; 16 | 17 | @SafeVarargs 18 | public static CompositeStreamObserver of(StreamObserver... observers) { 19 | return new CompositeStreamObserver(ImmutableList.copyOf(observers)); 20 | } 21 | 22 | private CompositeStreamObserver(ImmutableList> observers) { 23 | this.observers = observers; 24 | } 25 | 26 | @Override 27 | public void onCompleted() { 28 | for (StreamObserver observer : observers) { 29 | try { 30 | observer.onCompleted(); 31 | } catch (Throwable t) { 32 | logger.error("Exception in composite onComplete, moving on", t); 33 | } 34 | } 35 | } 36 | 37 | @Override 38 | public void onError(Throwable t) { 39 | for (StreamObserver observer : observers) { 40 | try { 41 | observer.onError(t); 42 | } catch (Throwable s) { 43 | logger.error("Exception in composite onError, moving on", s); 44 | } 45 | } 46 | } 47 | 48 | @Override 49 | public void onNext(T value) { 50 | for (StreamObserver observer : observers) { 51 | try { 52 | observer.onNext(value); 53 | } catch (Throwable t) { 54 | logger.error("Exception in composite onNext, moving on", t); 55 | } 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/grpc/DoneObserver.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.grpc; 2 | 3 | import com.google.common.util.concurrent.ListenableFuture; 4 | import com.google.common.util.concurrent.SettableFuture; 5 | import io.grpc.stub.StreamObserver; 6 | 7 | /** 8 | * A {@link StreamObserver} holding a future which completes when the rpc terminates. 9 | */ 10 | class DoneObserver implements StreamObserver { 11 | private final SettableFuture doneFuture; 12 | 13 | DoneObserver() { 14 | this.doneFuture = SettableFuture.create(); 15 | } 16 | 17 | @Override 18 | public synchronized void onCompleted() { 19 | doneFuture.set(null); 20 | } 21 | 22 | @Override 23 | public synchronized void onError(Throwable t) { 24 | doneFuture.setException(t); 25 | } 26 | 27 | @Override 28 | public void onNext(T next) { 29 | // Do nothing. 30 | } 31 | 32 | /** 33 | * Returns a future which completes when the rpc finishes. The returned future fails if the rpc 34 | * fails. 35 | */ 36 | ListenableFuture getCompletionFuture() { 37 | return doneFuture; 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/grpc/DynamicGrpcClient.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.grpc; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import com.google.common.base.Preconditions; 5 | import com.google.common.collect.ImmutableList; 6 | import com.google.common.util.concurrent.ListenableFuture; 7 | import com.google.protobuf.Descriptors.MethodDescriptor; 8 | import com.google.protobuf.DynamicMessage; 9 | import io.grpc.CallOptions; 10 | import io.grpc.Channel; 11 | import io.grpc.ClientCall; 12 | import io.grpc.MethodDescriptor.MethodType; 13 | import io.grpc.stub.ClientCalls; 14 | import io.grpc.stub.StreamObserver; 15 | import me.dinowernli.grpc.polyglot.protobuf.DynamicMessageMarshaller; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | /** A grpc client which operates on dynamic messages. */ 20 | public class DynamicGrpcClient { 21 | private static final Logger logger = LoggerFactory.getLogger(DynamicGrpcClient.class); 22 | private final MethodDescriptor protoMethodDescriptor; 23 | private final Channel channel; 24 | 25 | /** Creates a client for the supplied method, talking to the supplied endpoint. */ 26 | public static DynamicGrpcClient create(MethodDescriptor protoMethod, Channel channel) { 27 | return new DynamicGrpcClient(protoMethod, channel); 28 | } 29 | 30 | @VisibleForTesting 31 | DynamicGrpcClient(MethodDescriptor protoMethodDescriptor, Channel channel) { 32 | this.protoMethodDescriptor = protoMethodDescriptor; 33 | this.channel = channel; 34 | } 35 | 36 | /** 37 | * Makes an rpc to the remote endpoint and respects the supplied callback. Returns a future which 38 | * terminates once the call has ended. For calls which are single-request, this throws 39 | * {@link IllegalArgumentException} if the size of {@code requests} is not exactly 1. 40 | */ 41 | public ListenableFuture call( 42 | ImmutableList requests, 43 | StreamObserver responseObserver, 44 | CallOptions callOptions) { 45 | Preconditions.checkArgument(!requests.isEmpty(), "Can't make call without any requests"); 46 | MethodType methodType = getMethodType(); 47 | long numRequests = requests.size(); 48 | if (methodType == MethodType.UNARY) { 49 | logger.info("Making unary call"); 50 | Preconditions.checkArgument(numRequests == 1, 51 | "Need exactly 1 request for unary call, but got: " + numRequests); 52 | return callUnary(requests.get(0), responseObserver, callOptions); 53 | } else if (methodType == MethodType.SERVER_STREAMING) { 54 | logger.info("Making server streaming call"); 55 | Preconditions.checkArgument(numRequests == 1, 56 | "Need exactly 1 request for server streaming call, but got: " + numRequests); 57 | return callServerStreaming(requests.get(0), responseObserver, callOptions); 58 | } else if (methodType == MethodType.CLIENT_STREAMING) { 59 | logger.info("Making client streaming call with " + requests.size() + " requests"); 60 | return callClientStreaming(requests, responseObserver, callOptions); 61 | } else { 62 | // Bidi streaming. 63 | logger.info("Making bidi streaming call with " + requests.size() + " requests"); 64 | return callBidiStreaming(requests, responseObserver, callOptions); 65 | } 66 | } 67 | 68 | private ListenableFuture callBidiStreaming( 69 | ImmutableList requests, 70 | StreamObserver responseObserver, 71 | CallOptions callOptions) { 72 | DoneObserver doneObserver = new DoneObserver<>(); 73 | StreamObserver requestObserver = ClientCalls.asyncBidiStreamingCall( 74 | createCall(callOptions), 75 | CompositeStreamObserver.of(responseObserver, doneObserver)); 76 | requests.forEach(requestObserver::onNext); 77 | requestObserver.onCompleted(); 78 | return doneObserver.getCompletionFuture(); 79 | } 80 | 81 | private ListenableFuture callClientStreaming( 82 | ImmutableList requests, 83 | StreamObserver responseObserver, 84 | CallOptions callOptions) { 85 | DoneObserver doneObserver = new DoneObserver<>(); 86 | StreamObserver requestObserver = ClientCalls.asyncClientStreamingCall( 87 | createCall(callOptions), 88 | CompositeStreamObserver.of(responseObserver, doneObserver)); 89 | requests.forEach(requestObserver::onNext); 90 | requestObserver.onCompleted(); 91 | return doneObserver.getCompletionFuture(); 92 | } 93 | 94 | private ListenableFuture callServerStreaming( 95 | DynamicMessage request, 96 | StreamObserver responseObserver, 97 | CallOptions callOptions) { 98 | DoneObserver doneObserver = new DoneObserver<>(); 99 | ClientCalls.asyncServerStreamingCall( 100 | createCall(callOptions), 101 | request, 102 | CompositeStreamObserver.of(responseObserver, doneObserver)); 103 | return doneObserver.getCompletionFuture(); 104 | } 105 | 106 | private ListenableFuture callUnary( 107 | DynamicMessage request, 108 | StreamObserver responseObserver, 109 | CallOptions callOptions) { 110 | DoneObserver doneObserver = new DoneObserver<>(); 111 | ClientCalls.asyncUnaryCall( 112 | createCall(callOptions), 113 | request, 114 | CompositeStreamObserver.of(responseObserver, doneObserver)); 115 | return doneObserver.getCompletionFuture(); 116 | } 117 | 118 | private ClientCall createCall(CallOptions callOptions) { 119 | return channel.newCall(createGrpcMethodDescriptor(), callOptions); 120 | } 121 | 122 | private io.grpc.MethodDescriptor createGrpcMethodDescriptor() { 123 | return io.grpc.MethodDescriptor.create( 124 | getMethodType(), 125 | getFullMethodName(), 126 | new DynamicMessageMarshaller(protoMethodDescriptor.getInputType()), 127 | new DynamicMessageMarshaller(protoMethodDescriptor.getOutputType())); 128 | } 129 | 130 | private String getFullMethodName() { 131 | String serviceName = protoMethodDescriptor.getService().getFullName(); 132 | String methodName = protoMethodDescriptor.getName(); 133 | return io.grpc.MethodDescriptor.generateFullMethodName(serviceName, methodName); 134 | } 135 | 136 | /** Returns the appropriate method type based on whether the client or server expect streams. */ 137 | private MethodType getMethodType() { 138 | boolean clientStreaming = protoMethodDescriptor.toProto().getClientStreaming(); 139 | boolean serverStreaming = protoMethodDescriptor.toProto().getServerStreaming(); 140 | 141 | if (!clientStreaming && !serverStreaming) { 142 | return MethodType.UNARY; 143 | } else if (!clientStreaming && serverStreaming) { 144 | return MethodType.SERVER_STREAMING; 145 | } else if (clientStreaming && !serverStreaming) { 146 | return MethodType.CLIENT_STREAMING; 147 | } else { 148 | return MethodType.BIDI_STREAMING; 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/grpc/SingleResponseCallback.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.grpc; 2 | 3 | import com.google.common.util.concurrent.FutureCallback; 4 | 5 | import io.grpc.stub.StreamObserver; 6 | 7 | /** 8 | * A {@link FutureCallback} which provides an adapter from a future to a stream with a single 9 | * response. 10 | */ 11 | class SingleResponseCallback implements FutureCallback { 12 | private final StreamObserver streamObserver; 13 | 14 | SingleResponseCallback(StreamObserver streamObserver) { 15 | this.streamObserver = streamObserver; 16 | } 17 | 18 | @Override 19 | public void onFailure(Throwable t) { 20 | streamObserver.onError(t); 21 | } 22 | 23 | @Override 24 | public void onSuccess(T result) { 25 | streamObserver.onNext(result); 26 | streamObserver.onCompleted(); 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/io/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "io", 5 | srcs = glob(["*.java"]), 6 | deps = [ 7 | "//src/main/java/me/dinowernli/grpc/polyglot/protobuf", 8 | "//src/main/proto:config_java_proto", 9 | "//third_party/grpc", 10 | "//third_party/guava", 11 | "//third_party/logging:logging-api", 12 | "//third_party/protobuf", 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/io/LoggingStatsWriter.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.io; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import com.google.protobuf.DynamicMessage; 7 | 8 | import io.grpc.stub.StreamObserver; 9 | 10 | /** 11 | * A {@link StreamObserver} which logs the progress of the rpc and some stats about the results 12 | * once the rpc has completed. Note that this does *not* log the contents of the response. 13 | */ 14 | public class LoggingStatsWriter implements StreamObserver { 15 | private static final Logger logger = LoggerFactory.getLogger(LoggingStatsWriter.class); 16 | private int numResponses; 17 | 18 | public LoggingStatsWriter() { 19 | numResponses = 0; 20 | } 21 | 22 | @Override 23 | public void onCompleted() { 24 | logger.info("Completed rpc with " + numResponses + " response(s)"); 25 | } 26 | 27 | @Override 28 | public void onError(Throwable t) { 29 | logger.error("Aborted rpc due to error", t); 30 | } 31 | 32 | @Override 33 | public void onNext(DynamicMessage message) { 34 | logger.info("Got response message"); 35 | ++numResponses; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/io/MessageReader.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.io; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import com.google.common.base.Strings; 5 | import com.google.common.collect.ImmutableList; 6 | import com.google.protobuf.Descriptors.Descriptor; 7 | import com.google.protobuf.DynamicMessage; 8 | import com.google.protobuf.util.JsonFormat; 9 | import com.google.protobuf.util.JsonFormat.TypeRegistry; 10 | 11 | import java.io.BufferedReader; 12 | import java.io.IOException; 13 | import java.io.InputStreamReader; 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | 17 | /** A utility class which knows how to read proto files written using {@link MessageWriter}. */ 18 | public class MessageReader { 19 | private final JsonFormat.Parser jsonParser; 20 | private final Descriptor descriptor; 21 | private final BufferedReader bufferedReader; 22 | private final String source; 23 | 24 | /** Creates a {@link MessageReader} which reads messages from stdin. */ 25 | public static MessageReader forStdin(Descriptor descriptor, TypeRegistry registry) { 26 | BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); 27 | return new MessageReader( 28 | JsonFormat.parser().usingTypeRegistry(registry), 29 | descriptor, 30 | reader, 31 | "STDIN"); 32 | } 33 | 34 | /** Creates a {@link MessageReader} which reads the messages from a file. */ 35 | public static MessageReader forFile(Path path, Descriptor descriptor) { 36 | return forFile(path, descriptor, TypeRegistry.getEmptyTypeRegistry()); 37 | } 38 | 39 | /** Creates a {@link MessageReader} which reads the messages from a file. */ 40 | public static MessageReader forFile(Path path, Descriptor descriptor, TypeRegistry registry) { 41 | try { 42 | return new MessageReader( 43 | JsonFormat.parser().usingTypeRegistry(registry), 44 | descriptor, 45 | Files.newBufferedReader(path), 46 | path.toString()); 47 | } catch (IOException e) { 48 | throw new IllegalArgumentException("Unable to read file: " + path.toString(), e); 49 | } 50 | } 51 | 52 | @VisibleForTesting 53 | MessageReader( 54 | JsonFormat.Parser jsonParser, 55 | Descriptor descriptor, 56 | BufferedReader bufferedReader, 57 | String source) { 58 | this.jsonParser = jsonParser; 59 | this.descriptor = descriptor; 60 | this.bufferedReader = bufferedReader; 61 | this.source = source; 62 | } 63 | 64 | /** Parses all the messages and returns them in a list. */ 65 | public ImmutableList read() { 66 | ImmutableList.Builder resultBuilder = ImmutableList.builder(); 67 | try { 68 | String line; 69 | boolean wasLastLineEmpty = false; 70 | while (true) { 71 | line = bufferedReader.readLine(); 72 | 73 | // Two consecutive empty lines mark the end of the stream. 74 | if (Strings.isNullOrEmpty(line)) { 75 | if (wasLastLineEmpty) { 76 | return resultBuilder.build(); 77 | } 78 | wasLastLineEmpty = true; 79 | continue; 80 | } else { 81 | wasLastLineEmpty = false; 82 | } 83 | 84 | // Read the next full message. 85 | StringBuilder stringBuilder = new StringBuilder(); 86 | while (!Strings.isNullOrEmpty(line)) { 87 | stringBuilder.append(line); 88 | line = bufferedReader.readLine(); 89 | } 90 | wasLastLineEmpty = true; 91 | 92 | DynamicMessage.Builder nextMessage = DynamicMessage.newBuilder(descriptor); 93 | jsonParser.merge(stringBuilder.toString(), nextMessage); 94 | 95 | // Clean up and prepare for next message. 96 | resultBuilder.add(nextMessage.build()); 97 | } 98 | } catch (Exception e) { 99 | throw new IllegalArgumentException("Unable to read messages from: " + source, e); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/io/MessageWriter.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.io; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.PrintStream; 5 | 6 | import com.google.protobuf.util.JsonFormat.TypeRegistry; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import com.google.common.annotations.VisibleForTesting; 11 | import com.google.common.collect.ImmutableList; 12 | import com.google.protobuf.InvalidProtocolBufferException; 13 | import com.google.protobuf.Message; 14 | import com.google.protobuf.util.JsonFormat; 15 | 16 | import io.grpc.stub.StreamObserver; 17 | 18 | /** 19 | * A {@link StreamObserver} which writes the contents of the received messages to an 20 | * {@link Output}. The messages are writting in a newline-separated json format. 21 | */ 22 | public class MessageWriter implements StreamObserver { 23 | private static final Logger logger = LoggerFactory.getLogger(MessageWriter.class); 24 | 25 | /** Used to separate the individual plaintext json proto messages. */ 26 | private static final String MESSAGE_SEPARATOR = "\n\n"; 27 | 28 | private final JsonFormat.Printer jsonPrinter; 29 | private final Output output; 30 | 31 | /** 32 | * Creates a new {@link MessageWriter} which writes the messages it sees to the supplied 33 | * {@link Output}. 34 | */ 35 | public static MessageWriter create(Output output, TypeRegistry registry) { 36 | return new MessageWriter<>(JsonFormat.printer().usingTypeRegistry(registry), output); 37 | } 38 | 39 | /** 40 | * Returns the string representation of the stream of supplied messages. Each individual message 41 | * is represented as valid json, but not that the whole result is, itself, *not* valid json. 42 | */ 43 | public static String writeJsonStream(ImmutableList messages) { 44 | return writeJsonStream(messages, TypeRegistry.getEmptyTypeRegistry()); 45 | } 46 | 47 | /** 48 | * Returns the string representation of the stream of supplied messages. Each individual message 49 | * is represented as valid json, but not that the whole result is, itself, *not* valid json. 50 | */ 51 | public static String writeJsonStream( 52 | ImmutableList messages, TypeRegistry registry) { 53 | ByteArrayOutputStream resultStream = new ByteArrayOutputStream(); 54 | MessageWriter writer = 55 | MessageWriter.create(Output.forStream(new PrintStream(resultStream)), registry); 56 | writer.writeAll(messages); 57 | return resultStream.toString(); 58 | } 59 | 60 | @VisibleForTesting 61 | MessageWriter(JsonFormat.Printer jsonPrinter, Output output) { 62 | this.jsonPrinter = jsonPrinter; 63 | this.output = output; 64 | } 65 | 66 | @Override 67 | public void onCompleted() { 68 | // Nothing to do. 69 | } 70 | 71 | @Override 72 | public void onError(Throwable t) { 73 | // Nothing to do. 74 | } 75 | 76 | @Override 77 | public void onNext(T message) { 78 | try { 79 | output.write(jsonPrinter.print(message) + MESSAGE_SEPARATOR); 80 | } catch (InvalidProtocolBufferException e) { 81 | logger.error("Skipping invalid response message", e); 82 | } 83 | } 84 | 85 | /** Writes all the supplied messages and closes the stream. */ 86 | public void writeAll(ImmutableList messages) { 87 | messages.forEach(this::onNext); 88 | onCompleted(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/io/Output.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.io; 2 | 3 | import java.io.PrintStream; 4 | import java.nio.file.Path; 5 | import java.nio.file.Paths; 6 | 7 | import org.slf4j.LoggerFactory; 8 | 9 | import me.dinowernli.grpc.polyglot.io.OutputImpl.LogWriter; 10 | import me.dinowernli.grpc.polyglot.io.OutputImpl.PrintStreamWriter; 11 | import polyglot.ConfigProto.OutputConfiguration; 12 | import polyglot.ConfigProto.OutputConfiguration.Destination; 13 | 14 | /** 15 | * A one-stop-shop for output of the binary. Supports writing to logs, to streams, to files, etc. 16 | */ 17 | public interface Output extends AutoCloseable { 18 | /** Writes a single string of output. */ 19 | void write(String content); 20 | 21 | /** Writes a line of content. */ 22 | void writeLine(String line); 23 | 24 | /** Writes a blank line. */ 25 | void newLine(); 26 | 27 | /** 28 | * Creates a new {@link OutputImpl} instance for the supplied config. The retruned instance must ] 29 | * be closed after use or written content could go missing. 30 | */ 31 | public static Output forConfiguration(OutputConfiguration outputConfig) { 32 | Destination destination = outputConfig.getDestination(); 33 | switch(destination) { 34 | case STDOUT: 35 | return new OutputImpl(PrintStreamWriter.forStdout()); 36 | case FILE: 37 | Path filePath = Paths.get(outputConfig.getFilePath()); 38 | return new OutputImpl(PrintStreamWriter.forFile(filePath)); 39 | case LOG: 40 | return new OutputImpl(new LogWriter(LoggerFactory.getLogger("Output"))); 41 | default: 42 | throw new IllegalArgumentException("Unrecognized output destination " + destination); 43 | } 44 | } 45 | 46 | public static Output forStream(PrintStream printStream) { 47 | return new OutputImpl(PrintStreamWriter.forStream(printStream)); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/io/OutputImpl.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.io; 2 | 3 | import java.io.FileNotFoundException; 4 | import java.io.PrintStream; 5 | import java.nio.file.Path; 6 | 7 | import org.slf4j.Logger; 8 | 9 | class OutputImpl implements Output { 10 | private final OutputWriter writer; 11 | 12 | OutputImpl(OutputWriter writer) { 13 | this.writer = writer; 14 | } 15 | 16 | @Override 17 | public void close() { 18 | writer.close(); 19 | } 20 | 21 | @Override 22 | public void write(String content) { 23 | writer.write(content); 24 | } 25 | 26 | @Override 27 | public void writeLine(String content) { 28 | write(content + "\n"); 29 | } 30 | 31 | @Override 32 | public void newLine() { 33 | write("\n"); 34 | } 35 | 36 | private interface OutputWriter { 37 | void write(String content); 38 | void close(); 39 | } 40 | 41 | /** An {@link OutputWriter} which writes to a logger. */ 42 | static class LogWriter implements OutputWriter { 43 | private final Logger logger; 44 | 45 | LogWriter(Logger logger) { 46 | this.logger = logger; 47 | } 48 | 49 | @Override 50 | public void write(String content) { 51 | logger.info(content); 52 | } 53 | 54 | @Override 55 | public void close() { 56 | // Do nothing. 57 | } 58 | } 59 | 60 | /** An {@link OutputWriter} which writes to a stream. */ 61 | static class PrintStreamWriter implements OutputWriter { 62 | private final PrintStream printStream; 63 | 64 | static PrintStreamWriter forStdout() { 65 | return PrintStreamWriter.forStream(System.out); 66 | } 67 | 68 | static PrintStreamWriter forStream(PrintStream printStream) { 69 | return new PrintStreamWriter(printStream); 70 | } 71 | 72 | static PrintStreamWriter forFile(Path path) { 73 | try { 74 | return new PrintStreamWriter(new PrintStream(path.toString())); 75 | } catch (FileNotFoundException e) { 76 | throw new IllegalArgumentException("Could not create writer for file: " + path, e); 77 | } 78 | } 79 | 80 | private PrintStreamWriter(PrintStream printStream) { 81 | this.printStream = printStream; 82 | } 83 | 84 | @Override 85 | public void write(String content) { 86 | printStream.print(content); 87 | } 88 | 89 | @Override 90 | public void close() { 91 | printStream.close(); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/io/testing/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "testing", 5 | testonly = 1, 6 | srcs = glob(["*.java"]), 7 | deps = [ 8 | "//src/main/proto/testing:test_service_java_proto", 9 | "//third_party/guava", 10 | "//third_party/protobuf", 11 | ], 12 | ) 13 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/io/testing/TestData.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.io.testing; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.protobuf.DynamicMessage; 5 | import polyglot.test.TestProto.TestRequest; 6 | 7 | /** Test data used for unit tests of the io package. */ 8 | public class TestData { 9 | public static final DynamicMessage REQUEST = makeProto("some message"); 10 | 11 | public static final ImmutableList REQUESTS_MULTI = ImmutableList.of( 12 | makeProto("message!"), 13 | makeProto("more message!"), 14 | makeProto("even more message")); 15 | 16 | public static final DynamicMessage REQUEST_WITH_PRIMITIVE = DynamicMessage.newBuilder( 17 | TestRequest.newBuilder() 18 | .setMessage("some message") 19 | .setNumber(3) 20 | .build()) 21 | .build(); 22 | 23 | private static DynamicMessage makeProto(String content) { 24 | return DynamicMessage.newBuilder( 25 | TestRequest.newBuilder() 26 | .setMessage(content) 27 | .build()) 28 | .build(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/oauth2/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "oauth2", 5 | srcs = glob(["*.java"]), 6 | deps = [ 7 | "//src/main/proto:config_java_proto", 8 | "//third_party/google-oauth", 9 | "//third_party/guava", 10 | "//third_party/jackson-core", 11 | "//third_party/logging:logging-api", 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/oauth2/OauthCredentialsFactory.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.oauth2; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import com.google.auth.Credentials; 12 | import com.google.auth.oauth2.AccessToken; 13 | import com.google.auth.oauth2.OAuth2Credentials; 14 | import com.google.common.base.Joiner; 15 | 16 | import polyglot.ConfigProto.OauthConfiguration; 17 | import polyglot.ConfigProto.OauthConfiguration.AccessTokenCredentials; 18 | import polyglot.ConfigProto.OauthConfiguration.CredentialsCase; 19 | import polyglot.ConfigProto.OauthConfiguration.OauthClient; 20 | 21 | /** A utility class used to create credentials from a {@link OauthConfiguration}. */ 22 | public class OauthCredentialsFactory { 23 | private static final Logger logger = LoggerFactory.getLogger(OauthCredentialsFactory.class); 24 | private final OauthConfiguration oauthConfig; 25 | 26 | public OauthCredentialsFactory(OauthConfiguration oauthConfig) { 27 | this.oauthConfig = oauthConfig; 28 | } 29 | 30 | /** Returns a set of {@link Credentials} which can be used to authenticate requests. */ 31 | public Credentials getCredentials() { 32 | if (oauthConfig.getCredentialsCase() == CredentialsCase.ACCESS_TOKEN_CREDENTIALS) { 33 | return createAccessTokenCredentials(oauthConfig.getAccessTokenCredentials()); 34 | } else if (oauthConfig.getCredentialsCase() == CredentialsCase.REFRESH_TOKEN_CREDENTIALS) { 35 | return createRefreshTokenCredentials(oauthConfig.getRefreshTokenCredentials()); 36 | } else { 37 | throw new IllegalArgumentException( 38 | "Unknown oauth credential type: " + oauthConfig.getCredentialsCase()); 39 | } 40 | } 41 | 42 | private Credentials createAccessTokenCredentials(AccessTokenCredentials accessTokenCreds) { 43 | AccessToken accessToken = new AccessToken( 44 | readFile(Paths.get(accessTokenCreds.getAccessTokenPath())), null); 45 | 46 | logger.info("Using access token credentials"); 47 | return OAuth2Credentials.create(accessToken); 48 | } 49 | 50 | private Credentials createRefreshTokenCredentials( 51 | OauthConfiguration.RefreshTokenCredentials refreshTokenCreds) { 52 | String exchangeUrl = oauthConfig.getRefreshTokenCredentials().getTokenEndpointUrl(); 53 | String refreshToken = readFile( 54 | Paths.get(oauthConfig.getRefreshTokenCredentials().getRefreshTokenPath())); 55 | OauthClient oauthClient = oauthConfig.getRefreshTokenCredentials().getClient(); 56 | 57 | logger.info("Using refresh token credentials"); 58 | return RefreshTokenCredentials.create(oauthClient, refreshToken, exchangeUrl); 59 | } 60 | 61 | private static String readFile(Path path) { 62 | try { 63 | return Joiner.on('\n').join(Files.readAllLines(path)); 64 | } catch (IOException e) { 65 | throw new RuntimeException("Unable to read file: " + path.toString(), e); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/oauth2/RefreshTokenCredentials.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.oauth2; 2 | 3 | import java.io.IOException; 4 | import java.time.Clock; 5 | import java.util.Date; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | import com.google.api.client.auth.oauth2.RefreshTokenRequest; 11 | import com.google.api.client.auth.oauth2.TokenResponse; 12 | import com.google.api.client.http.BasicAuthentication; 13 | import com.google.api.client.http.GenericUrl; 14 | import com.google.api.client.http.javanet.NetHttpTransport; 15 | import com.google.api.client.json.jackson2.JacksonFactory; 16 | import com.google.auth.oauth2.AccessToken; 17 | import com.google.auth.oauth2.OAuth2Credentials; 18 | import com.google.common.annotations.VisibleForTesting; 19 | 20 | import polyglot.ConfigProto.OauthConfiguration.OauthClient; 21 | 22 | /** 23 | * Represents a refresh token in a specific oauth2 ecosystem. Swaps the refresh token for an access 24 | * token if the access token expires. 25 | */ 26 | public class RefreshTokenCredentials extends OAuth2Credentials { 27 | private static final Logger logger = LoggerFactory.getLogger(RefreshTokenCredentials.class); 28 | 29 | /** 30 | * A factor applied to the access token lifetime to make sure we refresh the token a bit earlier 31 | * than it actually expires. 32 | */ 33 | private static final double ACCESS_TOKEN_EXPIRY_MARGIN = 0.8; 34 | 35 | private final String refreshTokenSecret; 36 | private final OauthClient oauthClient; 37 | private final String tokenExchangeUrl; 38 | private final Clock clock; 39 | private final RefreshRequestFactory requestFactory; 40 | 41 | /** Create a new set of credentials for the given refresh token and oauth configuration. */ 42 | public static RefreshTokenCredentials create( 43 | OauthClient oauthConfig, String refreshTokenSecret, String tokenExchangeUrl) { 44 | RefreshRequestFactory requestFactory = new RefreshRequestFactory(); 45 | Clock clock = Clock.systemDefaultZone(); 46 | return new RefreshTokenCredentials( 47 | requestFactory, refreshTokenSecret, tokenExchangeUrl, oauthConfig, clock); 48 | } 49 | 50 | @VisibleForTesting 51 | RefreshTokenCredentials( 52 | RefreshRequestFactory requestFactory, 53 | String refreshTokenSecret, 54 | String tokenExchangeUrl, 55 | OauthClient oauthClient, 56 | Clock clock) { 57 | this.requestFactory = requestFactory; 58 | this.refreshTokenSecret = refreshTokenSecret; 59 | this.oauthClient = oauthClient; 60 | this.tokenExchangeUrl = tokenExchangeUrl; 61 | this.clock = clock; 62 | } 63 | 64 | @Override 65 | public AccessToken refreshAccessToken() throws IOException { 66 | logger.info("Exchanging refresh token for access token"); 67 | RefreshTokenRequest refreshRequest = requestFactory.newRequest( 68 | oauthClient, refreshTokenSecret, tokenExchangeUrl); 69 | TokenResponse refreshResponse = refreshRequest.execute(); 70 | 71 | logger.info("Refresh successful, got access token"); 72 | return new AccessToken( 73 | refreshResponse.getAccessToken(), 74 | computeExpirtyDate(refreshResponse.getExpiresInSeconds())); 75 | } 76 | 77 | private Date computeExpirtyDate(long expiresInSeconds) { 78 | long expiresInSecondsWithMargin = (long) (expiresInSeconds * ACCESS_TOKEN_EXPIRY_MARGIN); 79 | return Date.from(clock.instant().plusSeconds(expiresInSecondsWithMargin)); 80 | } 81 | 82 | @VisibleForTesting 83 | static class RefreshRequestFactory { 84 | RefreshTokenRequest newRequest( 85 | OauthClient oauthClient, String refreshTokenSecret, String tokenEndpoint) { 86 | RefreshTokenRequest result = new RefreshTokenRequest( 87 | new NetHttpTransport(), 88 | new JacksonFactory(), 89 | new GenericUrl(tokenEndpoint), 90 | refreshTokenSecret); 91 | result.setClientAuthentication( 92 | new BasicAuthentication(oauthClient.getId(), oauthClient.getSecret())); 93 | return result; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/protobuf/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "protobuf", 5 | srcs = glob(["*.java"]), 6 | resources = [ 7 | "@com_google_protobuf//:well_known_protos", 8 | ], 9 | deps = [ 10 | "//src/main/proto:config_java_proto", 11 | "//third_party/google-oauth", 12 | "//third_party/grpc", 13 | "//third_party/guava", 14 | "//third_party/logging:logging-api", 15 | "//third_party/protobuf", 16 | "//third_party/protoc-jar", 17 | ], 18 | ) 19 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/protobuf/DynamicMessageMarshaller.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.protobuf; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | import com.google.protobuf.Descriptors.Descriptor; 7 | import com.google.protobuf.DynamicMessage; 8 | import com.google.protobuf.ExtensionRegistryLite; 9 | 10 | import com.google.protobuf.util.JsonFormat.TypeRegistry; 11 | import io.grpc.MethodDescriptor.Marshaller; 12 | 13 | /** A {@link Marshaller} for dynamic messages. */ 14 | public class DynamicMessageMarshaller implements Marshaller { 15 | private final Descriptor messageDescriptor; 16 | 17 | public DynamicMessageMarshaller(Descriptor messageDescriptor) { 18 | this.messageDescriptor = messageDescriptor; 19 | } 20 | 21 | @Override 22 | public DynamicMessage parse(InputStream inputStream) { 23 | try { 24 | return DynamicMessage.newBuilder(messageDescriptor) 25 | .mergeFrom(inputStream, ExtensionRegistryLite.getEmptyRegistry()) 26 | .build(); 27 | } catch (IOException e) { 28 | throw new RuntimeException("Unable to merge from the supplied input stream", e); 29 | } 30 | } 31 | 32 | @Override 33 | public InputStream stream(DynamicMessage abstractMessage) { 34 | return abstractMessage.toByteString().newInput(); 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/protobuf/ProtoMethodName.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.protobuf; 2 | 3 | import com.google.common.base.Joiner; 4 | 5 | /** Represents a full grpc method, including package, service and method names. */ 6 | public class ProtoMethodName { 7 | // TODO(dino): Use autovalue for this class. 8 | private final String packageName; 9 | private final String serviceName; 10 | private final String methodName; 11 | 12 | /** Parses a string of the form"./". */ 13 | public static ProtoMethodName parseFullGrpcMethodName(String fullMethodName) { 14 | // Ask grpc for the service name. 15 | String fullServiceName = io.grpc.MethodDescriptor.extractFullServiceName(fullMethodName); 16 | if (fullServiceName == null) { 17 | throw new IllegalArgumentException("Could not extract full service from " + fullMethodName); 18 | } 19 | 20 | // Make sure there is a '/' and use the rest as the method name. 21 | int serviceLength = fullServiceName.length(); 22 | if (serviceLength + 1 >= fullMethodName.length() 23 | || fullMethodName.charAt(serviceLength) != '/') { 24 | throw new IllegalArgumentException("Could not extract method name from " + fullMethodName); 25 | } 26 | String methodName = fullMethodName.substring(fullServiceName.length() + 1); 27 | 28 | // Extract the leading package from the service name. 29 | int index = fullServiceName.lastIndexOf('.'); 30 | if (index == -1) { 31 | throw new IllegalArgumentException("Could not extract package name from " + fullServiceName); 32 | } 33 | String packageName = fullServiceName.substring(0, index); 34 | 35 | // Make sure there is a '.' and use the rest as the service name. 36 | if (index + 1 >= fullServiceName.length() || fullServiceName.charAt(index) != '.') { 37 | throw new IllegalArgumentException("Could not extract service from " + fullServiceName); 38 | } 39 | String serviceName = fullServiceName.substring(index + 1); 40 | 41 | return new ProtoMethodName(packageName, serviceName, methodName); 42 | } 43 | 44 | private ProtoMethodName(String packageName, String serviceName, String methodName) { 45 | this.packageName = packageName; 46 | this.serviceName = serviceName; 47 | this.methodName = methodName; 48 | } 49 | 50 | /** Returns the full package name of the method. */ 51 | public String getPackageName() { 52 | return packageName; 53 | } 54 | 55 | /** Returns the (unqualified) service name of the method. */ 56 | public String getServiceName() { 57 | return serviceName; 58 | } 59 | 60 | /** Returns the fully qualified service name of the method. */ 61 | public String getFullServiceName() { 62 | return Joiner.on(".").join(packageName, serviceName); 63 | } 64 | 65 | /** Returns the (unqualified) method name of the method. */ 66 | public String getMethodName() { 67 | return methodName; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/protobuf/ProtocInvoker.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.protobuf; 2 | 3 | import com.github.os72.protocjar.Protoc; 4 | import com.google.common.base.Preconditions; 5 | import com.google.common.collect.ImmutableList; 6 | import com.google.common.collect.ImmutableSet; 7 | import com.google.protobuf.DescriptorProtos.FileDescriptorSet; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import polyglot.ConfigProto.ProtoConfiguration; 11 | 12 | import java.io.ByteArrayOutputStream; 13 | import java.io.IOException; 14 | import java.io.PrintStream; 15 | import java.nio.file.FileSystems; 16 | import java.nio.file.Files; 17 | import java.nio.file.Path; 18 | import java.nio.file.PathMatcher; 19 | import java.nio.file.Paths; 20 | import java.util.stream.Collectors; 21 | import java.util.stream.Stream; 22 | 23 | /** 24 | * A utility class which facilitates invoking the protoc compiler on all proto files in a 25 | * directory tree. 26 | */ 27 | public class ProtocInvoker { 28 | private static final Logger logger = LoggerFactory.getLogger(ProtocInvoker.class); 29 | private static final PathMatcher PROTO_MATCHER = 30 | FileSystems.getDefault().getPathMatcher("glob:**/*.proto"); 31 | 32 | private final ImmutableList protocIncludePaths; 33 | private final Path discoveryRoot; 34 | 35 | /** Creates a new {@link ProtocInvoker} with the supplied configuration. */ 36 | public static ProtocInvoker forConfig(ProtoConfiguration protoConfig) { 37 | Preconditions.checkArgument(!protoConfig.getProtoDiscoveryRoot().isEmpty(), 38 | "A proto discovery root is required for proto analysis"); 39 | Path discoveryRootPath = Paths.get(protoConfig.getProtoDiscoveryRoot()); 40 | Preconditions.checkArgument(Files.exists(discoveryRootPath), 41 | "Invalid proto discovery root path: " + discoveryRootPath); 42 | 43 | ImmutableList.Builder includePaths = ImmutableList.builder(); 44 | for (String includePathString : protoConfig.getIncludePathsList()) { 45 | Path path = Paths.get(includePathString); 46 | Preconditions.checkArgument(Files.exists(path), "Invalid proto include path: " + path); 47 | includePaths.add(path.toAbsolutePath()); 48 | } 49 | 50 | return new ProtocInvoker(discoveryRootPath, includePaths.build()); 51 | } 52 | 53 | /** 54 | * Takes an optional path to pass to protoc as --proto_path. Uses the invocation-time proto root 55 | * if none is passed. 56 | */ 57 | private ProtocInvoker(Path discoveryRoot, ImmutableList protocIncludePaths) { 58 | this.protocIncludePaths = protocIncludePaths; 59 | this.discoveryRoot = discoveryRoot; 60 | } 61 | 62 | /** 63 | * Executes protoc on all .proto files in the subtree rooted at the supplied path and returns a 64 | * {@link FileDescriptorSet} which describes all the protos. 65 | */ 66 | public FileDescriptorSet invoke() throws ProtocInvocationException { 67 | Path wellKnownTypesInclude; 68 | try { 69 | wellKnownTypesInclude = setupWellKnownTypes(); 70 | } catch (IOException e) { 71 | throw new ProtocInvocationException("Unable to extract well known types", e); 72 | } 73 | 74 | Path descriptorPath; 75 | try { 76 | descriptorPath = Files.createTempFile("descriptor", ".pb.bin"); 77 | } catch (IOException e) { 78 | throw new ProtocInvocationException("Unable to create temporary file", e); 79 | } 80 | 81 | ImmutableList protocArgs = ImmutableList.builder() 82 | .addAll(scanProtoFiles(discoveryRoot)) 83 | .addAll(includePathArgs(wellKnownTypesInclude)) 84 | .add("--descriptor_set_out=" + descriptorPath.toAbsolutePath().toString()) 85 | .add("--include_imports") 86 | .build(); 87 | 88 | invokeBinary(protocArgs); 89 | 90 | try { 91 | return FileDescriptorSet.parseFrom(Files.readAllBytes(descriptorPath)); 92 | } catch (IOException e) { 93 | throw new ProtocInvocationException("Unable to parse the generated descriptors", e); 94 | } 95 | } 96 | 97 | private ImmutableList includePathArgs(Path wellKnownTypesInclude) { 98 | ImmutableList.Builder resultBuilder = ImmutableList.builder(); 99 | for (Path path : protocIncludePaths) { 100 | resultBuilder.add("-I" + path.toString()); 101 | } 102 | 103 | // Add the include path which makes sure that protoc finds the well known types. Note that we 104 | // add this *after* the user types above in case users want to provide their own well known 105 | // types. 106 | resultBuilder.add("-I" + wellKnownTypesInclude.toString()); 107 | 108 | // Protoc requires that all files being compiled are present in the subtree rooted at one of 109 | // the import paths (or the proto_root argument, which we don't use). Therefore, the safest 110 | // thing to do is to add the discovery path itself as the *last* include. 111 | resultBuilder.add("-I" + discoveryRoot.toAbsolutePath().toString()); 112 | 113 | return resultBuilder.build(); 114 | } 115 | 116 | private void invokeBinary(ImmutableList protocArgs) throws ProtocInvocationException { 117 | int status; 118 | String[] protocLogLines; 119 | 120 | // The "protoc" library unconditionally writes to stdout. So, we replace stdout right before 121 | // calling into the library in order to gather its output. 122 | PrintStream stdoutBackup = System.out; 123 | try { 124 | ByteArrayOutputStream protocStdout = new ByteArrayOutputStream(); 125 | System.setOut(new PrintStream(protocStdout)); 126 | 127 | status = Protoc.runProtoc(protocArgs.toArray(new String[0])); 128 | protocLogLines = protocStdout.toString().split("\n"); 129 | } catch (IOException | InterruptedException e) { 130 | throw new ProtocInvocationException("Unable to execute protoc binary", e); 131 | } finally { 132 | // Restore stdout. 133 | System.setOut(stdoutBackup); 134 | } 135 | if (status != 0) { 136 | // If protoc failed, we dump its output as a warning. 137 | logger.warn("Protoc invocation failed with status: " + status); 138 | for (String line : protocLogLines) { 139 | logger.warn("[Protoc log] " + line); 140 | } 141 | 142 | throw new ProtocInvocationException( 143 | String.format("Got exit code [%d] from protoc with args [%s]", status, protocArgs)); 144 | } 145 | } 146 | 147 | private ImmutableSet scanProtoFiles(Path protoRoot) throws ProtocInvocationException { 148 | try (final Stream protoPaths = Files.walk(protoRoot)) { 149 | return ImmutableSet.copyOf(protoPaths 150 | .filter(path -> PROTO_MATCHER.matches(path)) 151 | .map(path -> path.toAbsolutePath().toString()) 152 | .collect(Collectors.toSet())); 153 | } catch (IOException e) { 154 | throw new ProtocInvocationException("Unable to scan proto tree for files", e); 155 | } 156 | } 157 | 158 | /** 159 | * Extracts the .proto files for the well-known-types into a directory and returns a proto 160 | * include path which can be used to point protoc to the files. 161 | */ 162 | private static Path setupWellKnownTypes() throws IOException { 163 | Path tmpdir = Files.createTempDirectory("polyglot-well-known-types"); 164 | Path protoDir = Files.createDirectories(Paths.get(tmpdir.toString(), "google", "protobuf")); 165 | for (String file : WellKnownTypes.fileNames()) { 166 | Files.copy( 167 | ProtocInvoker.class.getResourceAsStream("/google/protobuf/" + file), 168 | Paths.get(protoDir.toString(), file)); 169 | } 170 | return tmpdir; 171 | } 172 | 173 | /** An error indicating that something went wrong while invoking protoc. */ 174 | public class ProtocInvocationException extends Exception { 175 | private static final long serialVersionUID = 1L; 176 | 177 | private ProtocInvocationException(String message) { 178 | super(message); 179 | } 180 | 181 | private ProtocInvocationException(String message, Throwable cause) { 182 | super(message, cause); 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/protobuf/ServiceResolver.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.protobuf; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableMap; 5 | import com.google.common.collect.ImmutableSet; 6 | import com.google.protobuf.DescriptorProtos.FileDescriptorProto; 7 | import com.google.protobuf.DescriptorProtos.FileDescriptorSet; 8 | import com.google.protobuf.Descriptors.Descriptor; 9 | import com.google.protobuf.Descriptors.DescriptorValidationException; 10 | import com.google.protobuf.Descriptors.FileDescriptor; 11 | import com.google.protobuf.Descriptors.MethodDescriptor; 12 | import com.google.protobuf.Descriptors.ServiceDescriptor; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.util.ArrayList; 17 | import java.util.HashMap; 18 | import java.util.Map; 19 | 20 | /** A locator used to read proto file descriptors and extract method definitions. */ 21 | public class ServiceResolver { 22 | private static final Logger logger = LoggerFactory.getLogger(ServiceResolver.class); 23 | private final ImmutableList fileDescriptors; 24 | 25 | /** Creates a resolver which searches the supplied {@link FileDescriptorSet}. */ 26 | public static ServiceResolver fromFileDescriptorSet(FileDescriptorSet descriptorSet) { 27 | ImmutableMap descriptorProtoIndex = 28 | computeDescriptorProtoIndex(descriptorSet); 29 | Map descriptorCache = new HashMap<>(); 30 | 31 | ImmutableList.Builder result = ImmutableList.builder(); 32 | for (FileDescriptorProto descriptorProto : descriptorSet.getFileList()) { 33 | try { 34 | result.add(descriptorFromProto(descriptorProto, descriptorProtoIndex, descriptorCache)); 35 | } catch (DescriptorValidationException e) { 36 | logger.warn("Skipped descriptor " + descriptorProto.getName() + " due to error", e); 37 | continue; 38 | } 39 | } 40 | return new ServiceResolver(result.build()); 41 | } 42 | 43 | /** Lists all of the services found in the file descriptors */ 44 | public Iterable listServices() { 45 | ArrayList serviceDescriptors = new ArrayList(); 46 | for (FileDescriptor fileDescriptor: fileDescriptors) { 47 | serviceDescriptors.addAll(fileDescriptor.getServices()); 48 | } 49 | return serviceDescriptors; 50 | } 51 | 52 | /** Lists all the known message types. */ 53 | public ImmutableSet listMessageTypes() { 54 | ImmutableSet.Builder resultBuilder = ImmutableSet.builder(); 55 | fileDescriptors.forEach(d -> resultBuilder.addAll(d.getMessageTypes())); 56 | return resultBuilder.build(); 57 | } 58 | 59 | private ServiceResolver(Iterable fileDescriptors) { 60 | this.fileDescriptors = ImmutableList.copyOf(fileDescriptors); 61 | } 62 | 63 | /** 64 | * Returns the descriptor of a protobuf method with the supplied grpc method name. If the method 65 | * cannot be found, this throws {@link IllegalArgumentException}. 66 | */ 67 | public MethodDescriptor resolveServiceMethod(ProtoMethodName method) { 68 | return resolveServiceMethod( 69 | method.getServiceName(), 70 | method.getMethodName(), 71 | method.getPackageName()); 72 | } 73 | 74 | private MethodDescriptor resolveServiceMethod( 75 | String serviceName, String methodName, String packageName) { 76 | ServiceDescriptor service = findService(serviceName, packageName); 77 | MethodDescriptor method = service.findMethodByName(methodName); 78 | if (method == null) { 79 | throw new IllegalArgumentException( 80 | "Unable to find method " + methodName + " in service " + serviceName); 81 | } 82 | return method; 83 | } 84 | 85 | private ServiceDescriptor findService(String serviceName, String packageName) { 86 | // TODO(dino): Consider creating an index. 87 | for (FileDescriptor fileDescriptor : fileDescriptors) { 88 | if (!fileDescriptor.getPackage().equals(packageName)) { 89 | // Package does not match this file, ignore. 90 | continue; 91 | } 92 | 93 | ServiceDescriptor serviceDescriptor = fileDescriptor.findServiceByName(serviceName); 94 | if (serviceDescriptor != null) { 95 | return serviceDescriptor; 96 | } 97 | } 98 | throw new IllegalArgumentException("Unable to find service with name: " + serviceName); 99 | } 100 | 101 | /** 102 | * Returns a map from descriptor proto name as found inside the descriptors to protos. 103 | */ 104 | private static ImmutableMap computeDescriptorProtoIndex( 105 | FileDescriptorSet fileDescriptorSet) { 106 | ImmutableMap.Builder resultBuilder = ImmutableMap.builder(); 107 | for (FileDescriptorProto descriptorProto : fileDescriptorSet.getFileList()) { 108 | resultBuilder.put(descriptorProto.getName(), descriptorProto); 109 | } 110 | return resultBuilder.build(); 111 | } 112 | 113 | /** 114 | * Recursively constructs file descriptors for all dependencies of the supplied proto and returns 115 | * a {@link FileDescriptor} for the supplied proto itself. For maximal efficiency, reuse the 116 | * descriptorCache argument across calls. 117 | */ 118 | private static FileDescriptor descriptorFromProto( 119 | FileDescriptorProto descriptorProto, 120 | ImmutableMap descriptorProtoIndex, 121 | Map descriptorCache) throws DescriptorValidationException { 122 | // First, check the cache. 123 | String descritorName = descriptorProto.getName(); 124 | if (descriptorCache.containsKey(descritorName)) { 125 | return descriptorCache.get(descritorName); 126 | } 127 | 128 | // Then, fetch all the required dependencies recursively. 129 | ImmutableList.Builder dependencies = ImmutableList.builder(); 130 | for (String dependencyName : descriptorProto.getDependencyList()) { 131 | if (!descriptorProtoIndex.containsKey(dependencyName)) { 132 | throw new IllegalArgumentException("Could not find dependency: " + dependencyName); 133 | } 134 | FileDescriptorProto dependencyProto = descriptorProtoIndex.get(dependencyName); 135 | dependencies.add(descriptorFromProto(dependencyProto, descriptorProtoIndex, descriptorCache)); 136 | } 137 | 138 | // Finally, construct the actual descriptor. 139 | FileDescriptor[] empty = new FileDescriptor[0]; 140 | return FileDescriptor.buildFrom(descriptorProto, dependencies.build().toArray(empty)); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/protobuf/WellKnownTypes.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.protobuf; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import com.google.protobuf.AnyProto; 5 | import com.google.protobuf.ApiProto; 6 | import com.google.protobuf.DescriptorProtos.DescriptorProto; 7 | import com.google.protobuf.DescriptorProtos.FileDescriptorProto; 8 | import com.google.protobuf.DurationProto; 9 | import com.google.protobuf.EmptyProto; 10 | import com.google.protobuf.FieldMaskProto; 11 | import com.google.protobuf.SourceContextProto; 12 | import com.google.protobuf.StructProto; 13 | import com.google.protobuf.TimestampProto; 14 | import com.google.protobuf.TypeProto; 15 | import com.google.protobuf.WrappersProto; 16 | 17 | /** 18 | * Central place to store information about the protobuf well-known-types. 19 | */ 20 | public class WellKnownTypes { 21 | private static final ImmutableSet DESCRIPTORS = ImmutableSet.of( 22 | AnyProto.getDescriptor().getFile().toProto(), 23 | ApiProto.getDescriptor().getFile().toProto(), 24 | DescriptorProto.getDescriptor().getFile().toProto(), 25 | DurationProto.getDescriptor().getFile().toProto(), 26 | EmptyProto.getDescriptor().getFile().toProto(), 27 | FieldMaskProto.getDescriptor().getFile().toProto(), 28 | SourceContextProto.getDescriptor().getFile().toProto(), 29 | StructProto.getDescriptor().getFile().toProto(), 30 | TimestampProto.getDescriptor().getFile().toProto(), 31 | TypeProto.getDescriptor().getFile().toProto(), 32 | WrappersProto.getDescriptor().getFile().toProto()); 33 | 34 | private static final ImmutableSet FILES = ImmutableSet.of( 35 | "any.proto", 36 | "api.proto", 37 | "descriptor.proto", 38 | "duration.proto", 39 | "empty.proto", 40 | "field_mask.proto", 41 | "source_context.proto", 42 | "struct.proto", 43 | "timestamp.proto", 44 | "type.proto", 45 | "wrappers.proto"); 46 | 47 | public static ImmutableSet descriptors() { 48 | return DESCRIPTORS; 49 | } 50 | 51 | public static ImmutableSet fileNames() { 52 | return FILES; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/server/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_binary( 4 | name = "main", 5 | srcs = glob(["*.java"]), 6 | main_class = "me.dinowernli.grpc.polyglot.server.Main", 7 | deps = [ 8 | "//src/main/proto:hello_java_proto", 9 | "//src/main/proto:hello_proto_grpc", 10 | "//third_party/grpc", 11 | "//third_party/guava", 12 | "//third_party/logging:logging-api", 13 | "//third_party/logging:logging-impl-stdout", 14 | "//third_party/netty", 15 | "//third_party/protobuf", 16 | ], 17 | ) 18 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/server/HelloServiceImpl.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.server; 2 | 3 | import com.google.protobuf.Any; 4 | import com.google.protobuf.Timestamp; 5 | import io.grpc.Status; 6 | import io.grpc.stub.StreamObserver; 7 | import polyglot.HelloProto.AnyPayload; 8 | import polyglot.HelloProto.HelloRequest; 9 | import polyglot.HelloProto.HelloResponse; 10 | import polyglot.HelloServiceGrpc.HelloServiceImplBase; 11 | 12 | public class HelloServiceImpl extends HelloServiceImplBase { 13 | private static final long STREAM_SLEEP_MILLIS = 250; 14 | private static final int STREAM_MESSAGES_NUMBER = 8; 15 | 16 | @Override 17 | public void sayHello(HelloRequest request, StreamObserver responseStream) { 18 | responseStream.onNext(HelloResponse.newBuilder() 19 | .setMessage("Hello, " + request.getRecipient()) 20 | .setTimestamp(Timestamp.newBuilder() 21 | .setSeconds(1234) 22 | .setNanos(5678) 23 | .build()) 24 | .setAny(Any.pack(AnyPayload.newBuilder() 25 | .setNumber(42) 26 | .build())) 27 | .build()); 28 | responseStream.onCompleted(); 29 | } 30 | 31 | @Override 32 | public void sayHelloStream(HelloRequest request, StreamObserver responseStream) { 33 | for (int i = 0; i < STREAM_MESSAGES_NUMBER; ++i) { 34 | responseStream.onNext(HelloResponse.newBuilder() 35 | .setMessage("Hello, " + request.getRecipient() + ", part " + i) 36 | .build()); 37 | try { 38 | Thread.sleep(STREAM_SLEEP_MILLIS); 39 | } catch (InterruptedException e) { 40 | responseStream.onError(Status.ABORTED.asException()); 41 | } 42 | } 43 | responseStream.onCompleted(); 44 | } 45 | 46 | @Override 47 | public StreamObserver sayHelloBidi( 48 | final StreamObserver responseStream) { 49 | return new StreamObserver() { 50 | @Override 51 | public void onNext(HelloRequest request) { 52 | responseStream.onNext(HelloResponse.newBuilder() 53 | .setMessage("Hello, " + request.getRecipient()) 54 | .build()); 55 | } 56 | 57 | @Override 58 | public void onError(Throwable t) { 59 | responseStream.onError(Status.ABORTED.asException()); 60 | } 61 | 62 | @Override 63 | public void onCompleted() { 64 | responseStream.onCompleted(); 65 | } 66 | }; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/server/Main.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.server; 2 | 3 | import java.io.IOException; 4 | 5 | import io.grpc.ServerBuilder; 6 | import io.grpc.protobuf.services.ProtoReflectionService; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * A binary which starts a simple gRPC server. This is used to test the client code. 12 | */ 13 | public class Main { 14 | private static final Logger logger = LoggerFactory.getLogger(Main.class); 15 | private static final int SERVER_PORT = 12345; 16 | 17 | public static void main(String[] args) { 18 | logger.info("Starting grpc server on port: " + SERVER_PORT); 19 | try { 20 | ServerBuilder.forPort(SERVER_PORT) 21 | .addService(new HelloServiceImpl()) 22 | .addService(ProtoReflectionService.newInstance()) 23 | .build() 24 | .start() 25 | .awaitTermination(); 26 | } catch (InterruptedException | IOException e) { 27 | logger.info("Caught exception, shutting down", e); 28 | System.exit(0); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/testing/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "testing", 5 | testonly = 1, 6 | srcs = glob(["*.java"]), 7 | data = glob(["test-certificates/*"]), 8 | deps = [ 9 | "//src/main/java/me/dinowernli/grpc/polyglot/io", 10 | "//src/main/proto/testing:test_service_java_proto", 11 | "//src/main/proto/testing:test_service_proto_grpc", 12 | "//third_party/grpc", 13 | "//third_party/guava", 14 | "//third_party/logging:logging-api", 15 | "//third_party/netty", 16 | "//third_party/protobuf", 17 | ], 18 | ) 19 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/testing/RecordingOutput.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.testing; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import com.google.common.base.Joiner; 8 | import com.google.common.base.Preconditions; 9 | import com.google.common.collect.ImmutableList; 10 | 11 | import me.dinowernli.grpc.polyglot.io.Output; 12 | 13 | /** An implementation of {@link Output} which just records all the contents. */ 14 | public class RecordingOutput implements Output { 15 | private final List contents; 16 | private boolean closed; 17 | 18 | public RecordingOutput() { 19 | this.closed = false; 20 | this.contents = new ArrayList<>(); 21 | } 22 | 23 | @Override 24 | public void close() throws IOException { 25 | Preconditions.checkState(!closed, "Output already closed previously"); 26 | this.closed = true; 27 | } 28 | 29 | @Override 30 | public void write(String content) { 31 | contents.add(content); 32 | } 33 | 34 | @Override 35 | public void newLine() { 36 | write("\n"); 37 | } 38 | 39 | @Override 40 | public void writeLine(String line) { 41 | write(line + "\n"); 42 | } 43 | 44 | public ImmutableList getContents() { 45 | Preconditions.checkState(closed, "Output not yet closed, can't get contents"); 46 | return ImmutableList.copyOf(contents); 47 | } 48 | 49 | public String getContentsAsString() { 50 | return Joiner.on("").join(getContents()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/testing/RecordingTestService.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.testing; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import io.grpc.stub.StreamObserver; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import polyglot.test.TestProto.TestRequest; 10 | import polyglot.test.TestProto.TestResponse; 11 | import polyglot.test.TestServiceGrpc.TestServiceImplBase; 12 | 13 | /** 14 | * An implementation of {@link TestServiceImplBase} which records the calls and produces a constant 15 | * response. 16 | */ 17 | public class RecordingTestService extends TestServiceImplBase { 18 | private static final Logger logger = LoggerFactory.getLogger(RecordingTestService.class); 19 | 20 | /** The number of messages to wait for from client stream before ending the rpc. */ 21 | private static final int NUM_MESSAGES_FROM_CLIENT = 3; 22 | 23 | /** We fake some expensive computation to shake out race conditions. */ 24 | private static final long RESPONSE_COMPUTATION_TIME_MS = 1000L; 25 | 26 | private final TestResponse unaryResponse; 27 | private final TestResponse streamResponse; 28 | private final TestResponse clientStreamResponse; 29 | private final TestResponse bidiStreamResponse; 30 | private final List recordedRequests; 31 | 32 | public RecordingTestService( 33 | TestResponse unaryResponse, 34 | TestResponse streamResponse, 35 | TestResponse clientStreamResponse, 36 | TestResponse bidiStreamResponse) { 37 | this.unaryResponse = unaryResponse; 38 | this.streamResponse = streamResponse; 39 | this.clientStreamResponse = clientStreamResponse; 40 | this.bidiStreamResponse = bidiStreamResponse; 41 | this.recordedRequests = new ArrayList<>(); 42 | } 43 | 44 | @Override 45 | public void testMethod(TestRequest request, StreamObserver responseStream) { 46 | logger.info("Handling unary method call"); 47 | recordedRequests.add(request); 48 | fakeExpensiveComputation(); 49 | responseStream.onNext(unaryResponse); 50 | responseStream.onCompleted(); 51 | } 52 | 53 | public int numRequests() { 54 | return recordedRequests.size(); 55 | } 56 | 57 | public TestRequest getRequest(int index) { 58 | return recordedRequests.get(index); 59 | } 60 | 61 | @Override 62 | public StreamObserver testMethodBidi(StreamObserver responseStream) { 63 | logger.info("Handling bidi method call"); 64 | return new StreamObserver() { 65 | int numRequestMessages = 0; 66 | 67 | @Override 68 | public void onNext(TestRequest testRequest) { 69 | ++numRequestMessages; 70 | recordedRequests.add(testRequest); 71 | responseStream.onNext(bidiStreamResponse); 72 | if (numRequestMessages >= NUM_MESSAGES_FROM_CLIENT) { 73 | fakeExpensiveComputation(); 74 | responseStream.onCompleted(); 75 | } 76 | } 77 | 78 | @Override 79 | public void onError(Throwable t) { 80 | logger.error("Got incoming error", t); 81 | } 82 | 83 | @Override 84 | public void onCompleted() { 85 | // Do nothing. 86 | } 87 | }; 88 | } 89 | 90 | @Override 91 | public void testMethodStream(TestRequest request, StreamObserver responseStream) { 92 | logger.info("Handling server streaming method call"); 93 | recordedRequests.add(request); 94 | fakeExpensiveComputation(); 95 | responseStream.onNext(streamResponse); 96 | responseStream.onCompleted(); 97 | } 98 | 99 | @Override 100 | public StreamObserver testMethodClientStream( 101 | StreamObserver responseStream) { 102 | logger.info("Handling client streaming method call"); 103 | return new StreamObserver() { 104 | int numRequestMessages = 0; 105 | 106 | @Override 107 | public void onNext(TestRequest testRequest) { 108 | ++numRequestMessages; 109 | recordedRequests.add(testRequest); 110 | if (numRequestMessages >= NUM_MESSAGES_FROM_CLIENT) { 111 | fakeExpensiveComputation(); 112 | responseStream.onNext(clientStreamResponse); 113 | responseStream.onCompleted(); 114 | } 115 | } 116 | 117 | @Override 118 | public void onError(Throwable t) { 119 | logger.error("Got incoming error", t); 120 | } 121 | 122 | @Override 123 | public void onCompleted() { 124 | // Do nothing. 125 | } 126 | }; 127 | } 128 | 129 | private static void fakeExpensiveComputation() { 130 | try { 131 | Thread.sleep(RESPONSE_COMPUTATION_TIME_MS); 132 | } catch (InterruptedException e) { 133 | logger.error("Interrupted while sleeping", e); 134 | } 135 | } 136 | } -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/testing/TestServer.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.testing; 2 | 3 | import com.google.common.base.Throwables; 4 | import com.google.protobuf.Any; 5 | import com.google.protobuf.Duration; 6 | import io.grpc.Server; 7 | import io.grpc.netty.GrpcSslContexts; 8 | import io.grpc.netty.NettyServerBuilder; 9 | import io.grpc.protobuf.services.ProtoReflectionService; 10 | import io.netty.handler.ssl.ClientAuth; 11 | import io.netty.handler.ssl.SslContext; 12 | import io.netty.handler.ssl.SslContextBuilder; 13 | import io.netty.handler.ssl.SslProvider; 14 | import polyglot.test.TestProto.TestResponse; 15 | import polyglot.test.TestProto.TunnelMessage; 16 | import polyglot.test.TestServiceGrpc.TestServiceImplBase; 17 | 18 | import java.io.File; 19 | import java.io.IOException; 20 | import java.util.Optional; 21 | import java.util.Random; 22 | 23 | /** 24 | * Holds a real grpc server for use in tests. The server returns canned responses for a fixed set of 25 | * methods and is optimized for ease of setup in tests (rather than configurability). 26 | */ 27 | public class TestServer { 28 | /** A response sent whenever the test server sees a request to its unary method. */ 29 | public static final TestResponse UNARY_SERVER_RESPONSE = TestResponse.newBuilder() 30 | .setMessage("some fancy message") 31 | .setAny(Any.pack(TunnelMessage.newBuilder() 32 | .setNumber(12345) 33 | .build())) 34 | .build(); 35 | 36 | /** A response sent whenever the test server sees a request to its streaming method. */ 37 | public static final TestResponse STREAMING_SERVER_RESPONSE = TestResponse.newBuilder() 38 | .setMessage("some other message") 39 | .setDuration(Duration.newBuilder() 40 | .setSeconds(12) 41 | .setNanos(45)) 42 | .build(); 43 | 44 | /** A response sent whenever the test server sees a request to its client streaming method. */ 45 | public static final TestResponse CLIENT_STREAMING_SERVER_RESPONSE = TestResponse.newBuilder() 46 | .setMessage("woohoo client stream") 47 | .build(); 48 | 49 | /** A response sent whenever the test server sees a request to its client streaming method. */ 50 | public static final TestResponse BIDI_SERVER_RESPONSE = TestResponse.newBuilder() 51 | .setMessage("woohoo bidi stream") 52 | .build(); 53 | 54 | private static final long NUM_SERVER_START_TRIES = 3; 55 | private static final int MIN_SERVER_PORT = 50_000; 56 | private static final int MAX_SERVER_PORT = 60_000; 57 | 58 | private final Server grpcServer; 59 | private final int grpcServerPort; 60 | private final RecordingTestService recordingService; 61 | 62 | /** 63 | * Tries a few times to start a server. If this returns, the server has been started. Throws 64 | * {@link RuntimeException} on failure. 65 | */ 66 | public static TestServer createAndStart(Optional sslContext) { 67 | RecordingTestService recordingTestService = new RecordingTestService( 68 | UNARY_SERVER_RESPONSE, 69 | STREAMING_SERVER_RESPONSE, 70 | CLIENT_STREAMING_SERVER_RESPONSE, 71 | BIDI_SERVER_RESPONSE); 72 | 73 | Random random = new Random(); 74 | for (int i = 0; i < NUM_SERVER_START_TRIES; ++i) { 75 | int port = random.nextInt(MAX_SERVER_PORT - MIN_SERVER_PORT + 1) + MIN_SERVER_PORT; 76 | try { 77 | Server server = tryStartServer(port, recordingTestService, sslContext); 78 | 79 | // If we got this far, we have successfully started the server. 80 | return new TestServer(server, port, recordingTestService); 81 | } catch (IOException e) { 82 | // The random port might have been in use, try again... 83 | continue; 84 | } 85 | } 86 | 87 | // If we got to here, we didn't manage to start a server. 88 | throw new RuntimeException("Unable to start server after " + NUM_SERVER_START_TRIES + " tries"); 89 | } 90 | 91 | public TestServer( 92 | Server grpcServer, int grpcServerPort, RecordingTestService recordingTestService) { 93 | this.grpcServer = grpcServer; 94 | this.grpcServerPort = grpcServerPort; 95 | this.recordingService = recordingTestService; 96 | } 97 | 98 | public int getGrpcServerPort() { 99 | return grpcServerPort; 100 | } 101 | 102 | public RecordingTestService getServiceImpl() { 103 | return recordingService; 104 | } 105 | 106 | public void blockingShutdown() { 107 | try { 108 | grpcServer.shutdown().awaitTermination(); 109 | } catch (InterruptedException e) { 110 | // Propagate, ensuring the test fails. 111 | Throwables.propagate(e); 112 | } 113 | } 114 | 115 | /** An {@link SslContext} for use in unit test servers. Loads our testing certificates. */ 116 | public static SslContext serverSslContextForTesting() throws IOException { 117 | return getSslContextBuilder().build(); 118 | } 119 | 120 | /** An {@link SslContext} for use in unit test servers with client certs. Loads our testing certificates. */ 121 | public static SslContext serverSslContextWithClientCertsForTesting() throws IOException { 122 | return getSslContextBuilder() 123 | .clientAuth(ClientAuth.REQUIRE) 124 | .build(); 125 | } 126 | 127 | private static SslContextBuilder getSslContextBuilder() { 128 | return GrpcSslContexts.forServer(TestUtils.loadServerChainCert(), TestUtils.loadServerKey()) 129 | .trustManager(TestUtils.loadRootCaCert()) 130 | .sslProvider(SslProvider.OPENSSL); 131 | } 132 | 133 | /** Starts a grpc server on the given port, throws {@link IOException} on failure. */ 134 | private static Server tryStartServer( 135 | int port, 136 | TestServiceImplBase testService, 137 | Optional sslContext) throws IOException { 138 | NettyServerBuilder serverBuilder = NettyServerBuilder.forPort(port) 139 | .addService(testService) 140 | .addService(ProtoReflectionService.newInstance()); 141 | if (sslContext.isPresent()) { 142 | serverBuilder.sslContext(sslContext.get()); 143 | } 144 | return serverBuilder.build().start(); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/testing/TestUtils.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.testing; 2 | 3 | import java.io.File; 4 | import java.nio.file.Path; 5 | import java.nio.file.Paths; 6 | 7 | import com.google.common.collect.ImmutableList; 8 | import com.google.protobuf.DynamicMessage; 9 | import com.google.protobuf.InvalidProtocolBufferException; 10 | 11 | import com.google.protobuf.util.JsonFormat.TypeRegistry; 12 | import me.dinowernli.grpc.polyglot.io.MessageReader; 13 | import polyglot.test.TestProto.TestResponse; 14 | import polyglot.test.TestProto.TunnelMessage; 15 | 16 | /** Utilities shared across tests. */ 17 | public class TestUtils { 18 | /** The root directory of the "testing" proto files. */ 19 | public static final Path TESTING_PROTO_ROOT = Paths.get(getWorkspaceRoot().toString(), 20 | "src", "main", "proto", "testing"); 21 | 22 | /** The root directory of the certificates we use for testing. */ 23 | public static final Path TESTING_CERTS_DIR = Paths.get(getWorkspaceRoot().toString(), 24 | "src", "main", "java", "me", "dinowernli", "grpc","polyglot", "testing", "test-certificates"); 25 | 26 | /** Returns the root directory of the runtime workspace. */ 27 | public static Path getWorkspaceRoot() { 28 | // Bazel runs binaries with the workspace root as working directory. 29 | return Paths.get(".").toAbsolutePath(); 30 | } 31 | 32 | public static ImmutableList makePolyglotCallArgs(String endpoint, String method) { 33 | return ImmutableList.builder() 34 | .add(makeArgument("proto_discovery_root", TestUtils.TESTING_PROTO_ROOT.toString())) 35 | .add(makeArgument("add_protoc_includes", TestUtils.getWorkspaceRoot().toString())) 36 | .add("call") 37 | .add(makeArgument("endpoint", endpoint)) 38 | .add(makeArgument("full_method", method)) 39 | .build(); 40 | } 41 | 42 | public static String makeArgument(String key, String value) { 43 | return String.format("--%s=%s", key, value); 44 | } 45 | 46 | /** Attempts to read a response proto from the supplied file. */ 47 | public static ImmutableList readResponseFile(Path file) 48 | throws InvalidProtocolBufferException { 49 | TypeRegistry registry = TypeRegistry.newBuilder() 50 | .add(TunnelMessage.getDescriptor()) 51 | .build(); 52 | MessageReader reader = MessageReader.forFile(file, TestResponse.getDescriptor(), registry); 53 | ImmutableList responses = reader.read(); 54 | 55 | ImmutableList.Builder resultBuilder = ImmutableList.builder(); 56 | for (DynamicMessage response : responses) { 57 | resultBuilder.add(TestResponse.parseFrom(response.toByteString())); 58 | } 59 | return resultBuilder.build(); 60 | } 61 | 62 | /** Returns a file containing a root CA certificate for use in tests. */ 63 | public static File loadRootCaCert() { 64 | return Paths.get(TESTING_CERTS_DIR.toString(), "ca.pem").toFile(); 65 | } 66 | 67 | /** Returns a file containing a client certificate for use in tests. */ 68 | public static File loadClientCert() { 69 | return Paths.get(TESTING_CERTS_DIR.toString(), "client.pem").toFile(); 70 | } 71 | 72 | /** Returns a file containing a client key for use in tests. */ 73 | public static File loadClientKey() { 74 | return Paths.get(TESTING_CERTS_DIR.toString(), "client.key").toFile(); 75 | } 76 | 77 | /** Returns a file containing a certificate chain from our testing root CA to our server. */ 78 | public static File loadServerChainCert() { 79 | return Paths.get(TESTING_CERTS_DIR.toString(), "server.pem").toFile(); 80 | } 81 | 82 | /** Returns a file containing the key pair of our server. */ 83 | public static File loadServerKey() { 84 | return Paths.get(TESTING_CERTS_DIR.toString(), "server.key").toFile(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICZjCCAc+gAwIBAgIJAJqx8JYIArI0MA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNzA4MzEyMzM3 5 | MDNaFw0yNzA4MjkyMzM3MDNaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l 6 | LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV 7 | BAMMCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzroPZgt6 8 | jX0qwmuo5Y04J8eATK2Lq/ohFfI+LoaqXJ7nOrHViPLpQfqErtHCrcuOD++BcTJ4 9 | 0iw6d0uwxTT0L73cfKI+1zJKcI6jAJ1a86kpYJVqNY5mIDqVXSP2/Ig6Q7I3qDOZ 10 | RM/sNIypd56bQy8GmQYcL1ng1zqy1kBn4jsCAwEAAaM2MDQwCQYDVR0TBAIwADAL 11 | BgNVHQ8EBAMCBeAwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3 12 | DQEBCwUAA4GBAAPiapcOh3JBfC6f7dkUAP9KDpNHK8JA1My4+CxkRyShC0rKf+K6 13 | 3wRLLb6f9qyvs3FkSF5uTcD12Irj88SzlMPiu/civVv4ldY/5w1XKmh3BoWwe+cH 14 | jaqPi0MX/uarPCgbgkt219INsBi/Sc8V8Yp1qjZp+pvJ0A80/XdD56/x 15 | -----END CERTIFICATE----- 16 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALRoMQCMeQfDcMgk 3 | rs1BREc+4EcGenojetkw10HJ5erZBM05PgdtDTkBT+QVrddVDmn0HUn6S5jZGZGe 4 | NXPOwDoVBHTYdCDKRrnaBg8v8hB2pgMVUaw6idg4FlUbNlit7n07thhXEBGhhw7x 5 | g7cjd8IsV8s1f84+7eC3H2ySVk8hAgMBAAECgYBEH0/ZoDGXj+JHgSqMkQeiS4jO 6 | 6RBYjIs39ixiSEXMX3RjtijJDxG+I9OyEcmaFSEjOy3QIHZpWhlAllgiycBly6zi 7 | QjIrdIwTrQHDapxRotT6ap+76/ev1e6145wgyUetSCwzuXdjmP24ujG9coCIP5EB 8 | Rcb5DO8QKZm1gOsgAQJBAOR/PHZSdTT3RsWBW2q03GA+sHpkjuiDaOek7rWJf3U6 9 | ptZCHjt28VFJa9qDqLKzdcvltqpT+IyskAOrNnyf7CECQQDKHyO5YnEjgMAAnFM6 10 | 3wsm6u+B1ru7UUKOhAr/n+nfSd775A1qF6wQ9YGoHFUHt3k4TjzRWNilQC0jsfUn 11 | sgMBAkAfOM+PL2c6jItME4flRb9TG13L64+nb8VW2a+QeLBE2XXQkwpEf9UrkUe+ 12 | fP0BJgpziPjzvzOYLUAkcDGqx8NhAkEAnOGWpOnXzyq73L15jJRa35Yy2KCHjlkA 13 | RAYRU2AX8wwvW2wjTVmaYH9uZ8G17gtmt2Fiq0s+vOUXJEGYe3scAQJBALmnIp1R 14 | JsAPzxaMa4pJkciHxyZyfH7roLwVUxT3Af+bWPyjOffR7lx9nzsbYf0UuTbiMfEp 15 | YHvy7BUwpIerndI= 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/client.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 2 (0x2) 5 | Signature Algorithm: sha256WithRSAEncryption 6 | Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=localhost 7 | Validity 8 | Not Before: Aug 31 23:37:41 2017 GMT 9 | Not After : Aug 29 23:37:41 2027 GMT 10 | Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=localhost-client 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (1024 bit) 14 | Modulus: 15 | 00:b4:68:31:00:8c:79:07:c3:70:c8:24:ae:cd:41: 16 | 44:47:3e:e0:47:06:7a:7a:23:7a:d9:30:d7:41:c9: 17 | e5:ea:d9:04:cd:39:3e:07:6d:0d:39:01:4f:e4:15: 18 | ad:d7:55:0e:69:f4:1d:49:fa:4b:98:d9:19:91:9e: 19 | 35:73:ce:c0:3a:15:04:74:d8:74:20:ca:46:b9:da: 20 | 06:0f:2f:f2:10:76:a6:03:15:51:ac:3a:89:d8:38: 21 | 16:55:1b:36:58:ad:ee:7d:3b:b6:18:57:10:11:a1: 22 | 87:0e:f1:83:b7:23:77:c2:2c:57:cb:35:7f:ce:3e: 23 | ed:e0:b7:1f:6c:92:56:4f:21 24 | Exponent: 65537 (0x10001) 25 | X509v3 extensions: 26 | X509v3 Basic Constraints: 27 | CA:FALSE 28 | X509v3 Key Usage: 29 | Digital Signature, Non Repudiation, Key Encipherment 30 | X509v3 Subject Key Identifier: 31 | E2:53:8F:A6:30:9A:43:C6:24:84:5A:FB:4D:8F:9D:5B:C8:24:28:F8 32 | X509v3 Authority Key Identifier: 33 | DirName:/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=localhost 34 | serial:9A:B1:F0:96:08:02:B2:34 35 | 36 | X509v3 Issuer Alternative Name: 37 | DNS:localhost, IP Address:127.0.0.1 38 | Signature Algorithm: sha256WithRSAEncryption 39 | 4f:a3:04:df:c8:bc:7e:b1:76:a1:5f:8a:35:61:78:60:4b:a5: 40 | 7a:7f:78:91:59:6f:d9:cb:4c:34:8f:b7:b7:dc:53:46:a3:4e: 41 | 08:e4:6b:33:ab:1b:ec:b9:f3:68:34:7d:c1:10:21:66:fd:41: 42 | ef:56:98:9b:bb:d7:1d:ce:6e:ac:55:23:78:05:99:0f:f3:8a: 43 | df:67:28:9c:4b:2f:b1:6f:b7:eb:16:b3:0b:e0:81:ea:f6:fe: 44 | 1d:a6:e5:66:fe:ab:6a:e3:e8:a5:a1:3d:4a:c0:47:4c:bb:6d: 45 | 4f:68:96:88:fe:93:90:77:c0:12:5a:b9:ea:2a:fa:9c:4d:85: 46 | 46:33 47 | -----BEGIN CERTIFICATE----- 48 | MIIC+zCCAmSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBZMQswCQYDVQQGEwJBVTET 49 | MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ 50 | dHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTcwODMxMjMzNzQxWhcNMjcw 51 | ODI5MjMzNzQxWjBgMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh 52 | MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRkwFwYDVQQDDBBsb2Nh 53 | bGhvc3QtY2xpZW50MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0aDEAjHkH 54 | w3DIJK7NQURHPuBHBnp6I3rZMNdByeXq2QTNOT4HbQ05AU/kFa3XVQ5p9B1J+kuY 55 | 2RmRnjVzzsA6FQR02HQgyka52gYPL/IQdqYDFVGsOonYOBZVGzZYre59O7YYVxAR 56 | oYcO8YO3I3fCLFfLNX/OPu3gtx9sklZPIQIDAQABo4HLMIHIMAkGA1UdEwQCMAAw 57 | CwYDVR0PBAQDAgXgMB0GA1UdDgQWBBTiU4+mMJpDxiSEWvtNj51byCQo+DBzBgNV 58 | HSMEbDBqoV2kWzBZMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh 59 | MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2Nh 60 | bGhvc3SCCQCasfCWCAKyNDAaBgNVHRIEEzARgglsb2NhbGhvc3SHBH8AAAEwDQYJ 61 | KoZIhvcNAQELBQADgYEAT6ME38i8frF2oV+KNWF4YEulen94kVlv2ctMNI+3t9xT 62 | RqNOCORrM6sb7LnzaDR9wRAhZv1B71aYm7vXHc5urFUjeAWZD/OK32conEsvsW+3 63 | 6xazC+CB6vb+HablZv6rauPopaE9SsBHTLttT2iWiP6TkHfAElq56ir6nE2FRjM= 64 | -----END CERTIFICATE----- 65 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/readme.txt: -------------------------------------------------------------------------------- 1 | The required files for the tests are: 2 | * ca.pem 3 | * server.pem 4 | * server.key 5 | * client.pem 6 | * client.key 7 | 8 | Generating these files requires only: 9 | * openssl.cnf 10 | 11 | In order to generate the files above from "openssl.cnf", do the following: 12 | 13 | 1) openssl req -x509 -new -newkey rsa:1024 -nodes -out ca.pem -config openssl.cnf -days 3650 -extensions v3_req 14 | 15 | all default, except common name: "localhost" 16 | 17 | 2) openssl genrsa -out server.key.rsa 1024 18 | 3) openssl pkcs8 -topk8 -in server.key.rsa -out server.key -nocrypt 19 | 4) openssl req -new -key server.key -out server.csr 20 | 21 | all default, except common name: "localhost" 22 | 23 | 5) openssl genrsa -out client.key.rsa 1024 24 | 6) openssl pkcs8 -topk8 -in client.key.rsa -out client.key -nocrypt 25 | 7) openssl req -new -key client.key -out client.csr 26 | 27 | all default, except common name: "localhost-client" 28 | 29 | 8) mv privkey.pem ca.key 30 | 9) touch index.txt 31 | 10) echo "01" > serial 32 | 11) openssl ca -in server.csr -out server.pem -keyfile ca.key -cert ca.pem -verbose -config openssl.cnf -days 3650 -batch 33 | 12) openssl ca -in client.csr -out client.pem -keyfile ca.key -cert ca.pem -verbose -config openssl.cnf -days 3650 -batch 34 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMxSJo9P48u4mDxI 3 | m+D+C/ad3ZClg6IUO0VbzMtuxBVwAk1SMFusPZzwyKMJYar7qRwB5+pLuXRK8vE/ 4 | 0S6SoBZvQaKP5364y7qwCbLC8gf8OKvexKIgVybRKHZIkdacaE1Cuq0QgVzu0w/T 5 | shmJwIa/erG7f2C/LKy0ClvADek7AgMBAAECgYAT7MLz4M+PE05NOqtw0nVqNFTi 6 | ATIIAT8ScXRUNlYK3SRsU+KBXheYEWcPdx++I9KG96ydDYtlStXMLvQAPa48tTIU 7 | yI9T2/CFez1a/ZaDPXDqhHTPSUhSENU9yBlyA/Zb4zMRuiCGm5rHwf+aoU7P2UHW 8 | zn5O/RtiDSSiOQn8SQJBAO48yGEuwh3kyxBLzw+uq9xIDpkGxV9cowXhNXoftm+7 9 | iG6r9pc5PVTdLDw/8bdoA9SbXHcpwz4LqlOqvtU7I78CQQDbjgGIPjpKiKqoFHtl 10 | VVofsXy7VXxf5dTxKMkk/5mb5XRfdIn2m5yIKPJC1RwMcySAfst1uCJvSfsGa2o4 11 | FWmFAkEAn5RxeL90YcfOyaSuF0gecJiHxrNFZEJOJPMc+ifh8WgB1Hg13kgGMCFS 12 | ryz6AauX3UMQJfYAhUAVIKQf3f8WSwJBAMymcYcteeg/u9MwRFUQWhFwv3NfG6/H 13 | 69Vezx9NoUFPgEn5tx/HrQC+KhNh0eNI8J1VkxEHshFKRFKXjUr5qoECQQCyOtfU 14 | VAJBWi31jHil8UXryxUVWYCTzuNAM6acRaBJd3sHei+4PzzGqh7I7Us0kNoGNQFK 15 | ymC8EYUIcFtqLSly 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /src/main/java/me/dinowernli/grpc/polyglot/testing/test-certificates/server.pem: -------------------------------------------------------------------------------- 1 | Certificate: 2 | Data: 3 | Version: 3 (0x2) 4 | Serial Number: 1 (0x1) 5 | Signature Algorithm: sha256WithRSAEncryption 6 | Issuer: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=localhost 7 | Validity 8 | Not Before: Aug 31 23:37:36 2017 GMT 9 | Not After : Aug 29 23:37:36 2027 GMT 10 | Subject: C=AU, ST=Some-State, O=Internet Widgits Pty Ltd, CN=localhost 11 | Subject Public Key Info: 12 | Public Key Algorithm: rsaEncryption 13 | Public-Key: (1024 bit) 14 | Modulus: 15 | 00:cc:52:26:8f:4f:e3:cb:b8:98:3c:48:9b:e0:fe: 16 | 0b:f6:9d:dd:90:a5:83:a2:14:3b:45:5b:cc:cb:6e: 17 | c4:15:70:02:4d:52:30:5b:ac:3d:9c:f0:c8:a3:09: 18 | 61:aa:fb:a9:1c:01:e7:ea:4b:b9:74:4a:f2:f1:3f: 19 | d1:2e:92:a0:16:6f:41:a2:8f:e7:7e:b8:cb:ba:b0: 20 | 09:b2:c2:f2:07:fc:38:ab:de:c4:a2:20:57:26:d1: 21 | 28:76:48:91:d6:9c:68:4d:42:ba:ad:10:81:5c:ee: 22 | d3:0f:d3:b2:19:89:c0:86:bf:7a:b1:bb:7f:60:bf: 23 | 2c:ac:b4:0a:5b:c0:0d:e9:3b 24 | Exponent: 65537 (0x10001) 25 | X509v3 extensions: 26 | X509v3 Basic Constraints: 27 | CA:FALSE 28 | X509v3 Key Usage: 29 | Digital Signature, Non Repudiation, Key Encipherment 30 | X509v3 Subject Key Identifier: 31 | DA:88:D3:69:24:78:C9:79:E6:D4:36:21:2E:0A:B4:AC:C6:11:85:F3 32 | X509v3 Authority Key Identifier: 33 | DirName:/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=localhost 34 | serial:9A:B1:F0:96:08:02:B2:34 35 | 36 | X509v3 Issuer Alternative Name: 37 | DNS:localhost, IP Address:127.0.0.1 38 | Signature Algorithm: sha256WithRSAEncryption 39 | 60:2d:71:74:f5:03:c8:bd:7e:ed:52:6d:ed:12:35:73:bd:06: 40 | fe:9d:16:8a:cb:56:97:61:1c:f8:b8:42:9a:39:44:33:76:19: 41 | 8f:85:22:c1:ce:18:19:da:e4:08:ff:1c:6f:b4:86:51:b6:07: 42 | 2d:3e:2f:63:9a:f0:c6:1e:19:cd:9d:48:0f:f7:27:1c:03:9c: 43 | c3:d8:d1:20:cb:c0:5d:4f:88:73:72:c6:4b:84:f9:01:2b:a7: 44 | 91:f1:5b:90:e8:ee:ba:e4:41:cd:75:ae:d5:12:ab:a3:89:31: 45 | 4b:0b:bf:f6:12:61:ba:e6:8b:f2:6c:b4:a3:cc:63:63:14:dd: 46 | d9:9e 47 | -----BEGIN CERTIFICATE----- 48 | MIIC9DCCAl2gAwIBAgIBATANBgkqhkiG9w0BAQsFADBZMQswCQYDVQQGEwJBVTET 49 | MBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQ 50 | dHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTcwODMxMjMzNzM2WhcNMjcw 51 | ODI5MjMzNzM2WjBZMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEh 52 | MB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2Nh 53 | bGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMxSJo9P48u4mDxIm+D+ 54 | C/ad3ZClg6IUO0VbzMtuxBVwAk1SMFusPZzwyKMJYar7qRwB5+pLuXRK8vE/0S6S 55 | oBZvQaKP5364y7qwCbLC8gf8OKvexKIgVybRKHZIkdacaE1Cuq0QgVzu0w/TshmJ 56 | wIa/erG7f2C/LKy0ClvADek7AgMBAAGjgcswgcgwCQYDVR0TBAIwADALBgNVHQ8E 57 | BAMCBeAwHQYDVR0OBBYEFNqI02kkeMl55tQ2IS4KtKzGEYXzMHMGA1UdIwRsMGqh 58 | XaRbMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQK 59 | DBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdIIJ 60 | AJqx8JYIArI0MBoGA1UdEgQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0B 61 | AQsFAAOBgQBgLXF09QPIvX7tUm3tEjVzvQb+nRaKy1aXYRz4uEKaOUQzdhmPhSLB 62 | zhgZ2uQI/xxvtIZRtgctPi9jmvDGHhnNnUgP9yccA5zD2NEgy8BdT4hzcsZLhPkB 63 | K6eR8VuQ6O665EHNda7VEqujiTFLC7/2EmG65ovybLSjzGNjFN3Zng== 64 | -----END CERTIFICATE----- 65 | -------------------------------------------------------------------------------- /src/main/proto/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@io_grpc_grpc_java//:java_grpc_library.bzl", "java_grpc_library") 4 | 5 | proto_library( 6 | name = "hello_proto", 7 | srcs = ["hello.proto"], 8 | deps = [ 9 | ":greeting_proto", 10 | "@com_google_protobuf//:any_proto", 11 | "@com_google_protobuf//:duration_proto", 12 | "@com_google_protobuf//:timestamp_proto", 13 | ], 14 | ) 15 | 16 | java_proto_library( 17 | name = "hello_java_proto", 18 | deps = [":hello_proto"], 19 | ) 20 | 21 | proto_library( 22 | name = "greeting_proto", 23 | srcs = ["greeting.proto"], 24 | ) 25 | 26 | java_proto_library( 27 | name = "greeting_java_proto", 28 | deps = [":greeting_proto"], 29 | ) 30 | 31 | proto_library( 32 | name = "config_proto", 33 | srcs = ["config.proto"], 34 | ) 35 | 36 | java_proto_library( 37 | name = "config_java_proto", 38 | deps = [":config_proto"], 39 | ) 40 | 41 | java_grpc_library( 42 | name = "hello_proto_grpc", 43 | srcs = [":hello_proto"], 44 | deps = [":hello_java_proto"], 45 | ) 46 | -------------------------------------------------------------------------------- /src/main/proto/config.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package polyglot; 4 | 5 | option java_outer_classname = "ConfigProto"; 6 | 7 | // Holds multiple named configurations. By default, the first configuration is 8 | // used, but a command line flag can have Polyglot use a specific config. 9 | message ConfigurationSet { 10 | repeated Configuration configurations = 1; 11 | } 12 | 13 | // Holds parameters used by Polylgot at runtime. 14 | message Configuration { 15 | string name = 1; 16 | 17 | CallConfiguration call_config = 2; 18 | ProtoConfiguration proto_config = 3; 19 | OutputConfiguration output_config = 4; 20 | } 21 | 22 | // Holds parameters used to make rpc calls. 23 | message CallConfiguration { 24 | uint32 deadline_ms = 1; 25 | bool use_tls = 2; 26 | OauthConfiguration oauth_config = 3; 27 | 28 | // If set, this file will be used as a root certificate for calls using TLS. 29 | string tls_ca_cert_path = 4; 30 | 31 | // If set, this will use client certs for authentication 32 | string tls_client_cert_path = 5; 33 | string tls_client_key_path = 6; 34 | string tls_client_override_authority = 7; 35 | 36 | // Metadata to be included with the call. 37 | // Entries will be appended to any existing metadata. 38 | // Entries whose name already exists as a metadata entry will have the value appended to the values list. 39 | repeated CallMetadataEntry metadata = 8; 40 | } 41 | 42 | message CallMetadataEntry { 43 | string name = 1; 44 | string value = 2; 45 | } 46 | 47 | 48 | // Holds the necessary parameters for adding authentication to requests using 49 | // Oauth2. 50 | message OauthConfiguration { 51 | // Credentials obtained by exchanging a refresh token for an access token. 52 | message RefreshTokenCredentials { 53 | // A url to talk to in order to exchange the refresh token for an access token. 54 | string token_endpoint_url = 1; 55 | 56 | // A client identity to use when exchanging tokens. 57 | OauthClient client = 2; 58 | 59 | // A path to a file containing a refresh token used for the exchange. 60 | string refresh_token_path = 3; 61 | } 62 | 63 | // Credentials used directly adding an access token to the rpc. 64 | message AccessTokenCredentials { 65 | // A path to a file containing an access token. 66 | string access_token_path = 1; 67 | } 68 | 69 | // Contains the necessary information to identify ourselves as an Oauth2 client. 70 | message OauthClient { 71 | string id = 1; 72 | string secret = 2; 73 | } 74 | 75 | oneof credentials { 76 | // If present, a refresh token will be exchanged for an access token and 77 | // the access token will be used to authenticate the request. 78 | RefreshTokenCredentials refresh_token_credentials = 1; 79 | 80 | // If present, the access token will be used to authenticate the request. 81 | AccessTokenCredentials access_token_credentials = 2; 82 | } 83 | } 84 | 85 | // Contains parameters controlling the output protos of polyglot. 86 | message OutputConfiguration { 87 | enum Destination { 88 | // Content is written to standard output. 89 | STDOUT = 0; 90 | 91 | // Content is written to the execution log, i.e., to standard error. 92 | LOG = 1; 93 | 94 | // Content is written to a file. 95 | FILE = 2; 96 | } 97 | Destination destination = 1; 98 | 99 | // When using a destination of type FILE, indicates which file to write to. 100 | string file_path = 2; 101 | } 102 | 103 | // Contains the necessary information to locate .proto files for services. 104 | message ProtoConfiguration { 105 | // A root directory to scan for .proto files. All files found this way will 106 | // be analyzed for service definitions by invoking protoc. 107 | string proto_discovery_root = 1; 108 | 109 | // Include paths used to resolve imports of the files being analyzed when 110 | // resolving service definitions using protoc. 111 | repeated string include_paths = 2; 112 | 113 | // If true, protos will first be resolved by reflection if applicable. 114 | bool use_reflection = 3; 115 | } 116 | -------------------------------------------------------------------------------- /src/main/proto/greeting.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package polyglot; 4 | 5 | option java_outer_classname = "GreetingProto"; 6 | 7 | message Greeting { 8 | string message = 1; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/proto/hello.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package polyglot; 4 | 5 | option java_outer_classname = "HelloProto"; 6 | 7 | import "google/protobuf/any.proto"; 8 | import "google/protobuf/timestamp.proto"; 9 | import "src/main/proto/greeting.proto"; 10 | 11 | message HelloRequest { 12 | string recipient = 1; 13 | } 14 | 15 | message HelloResponse { 16 | string message = 1; 17 | Greeting greeting = 2; 18 | google.protobuf.Any any = 3; 19 | google.protobuf.Timestamp timestamp = 4; 20 | } 21 | 22 | // A message used as the payload of the "any" field above. 23 | message AnyPayload { 24 | int32 number = 1; 25 | } 26 | 27 | service HelloService { 28 | rpc SayHello (HelloRequest) returns (HelloResponse) {} 29 | rpc SayHelloStream (HelloRequest) returns (stream HelloResponse) {} 30 | rpc SayHelloBidi (stream HelloRequest) returns (stream HelloResponse) {} 31 | } 32 | -------------------------------------------------------------------------------- /src/main/proto/testing/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@io_grpc_grpc_java//:java_grpc_library.bzl", "java_grpc_library") 4 | 5 | proto_library( 6 | name = "test_service_proto", 7 | srcs = ["test_service.proto"], 8 | deps = [ 9 | "//src/main/proto/testing/foo:foo_proto", 10 | "@com_google_protobuf//:any_proto", 11 | "@com_google_protobuf//:duration_proto", 12 | ], 13 | ) 14 | 15 | java_proto_library( 16 | name = "test_service_java_proto", 17 | deps = [":test_service_proto"], 18 | ) 19 | 20 | java_grpc_library( 21 | name = "test_service_proto_grpc", 22 | srcs = [":test_service_proto"], 23 | deps = [":test_service_java_proto"], 24 | ) 25 | 26 | filegroup( 27 | name = "proto_files", 28 | srcs = glob(["*.proto"]), 29 | data = [ 30 | "//src/main/proto/testing/foo:proto_files", 31 | ], 32 | ) 33 | -------------------------------------------------------------------------------- /src/main/proto/testing/foo/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | proto_library( 4 | name = "foo_proto", 5 | srcs = ["foo.proto"], 6 | ) 7 | 8 | java_proto_library( 9 | name = "foo_java_proto", 10 | deps = [":foo_proto"], 11 | ) 12 | 13 | filegroup( 14 | name = "proto_files", 15 | srcs = glob(["*.proto"]), 16 | ) 17 | -------------------------------------------------------------------------------- /src/main/proto/testing/foo/foo.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package polyglot.test.foo; 4 | 5 | option java_outer_classname = "FooProto"; 6 | 7 | message Foo { 8 | string message = 1; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/proto/testing/protobuf/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | filegroup( 4 | name = "proto_files", 5 | srcs = glob(["*.proto"]), 6 | ) 7 | -------------------------------------------------------------------------------- /src/main/proto/testing/protobuf/standalone.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package polyglot.test.protoc; 4 | 5 | import "google/protobuf/any.proto"; 6 | 7 | // This is a very simple file which is completely self-contained. It is used to 8 | // test the case where we pass no imports to the protoc invoker. 9 | message Standalone { 10 | // This checks that the ProtocInvoker can do well-known-types. 11 | google.protobuf.Any any = 1; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/proto/testing/test_service.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package polyglot.test; 4 | 5 | option java_outer_classname = "TestProto"; 6 | 7 | import "google/protobuf/any.proto"; 8 | import "google/protobuf/duration.proto"; 9 | import "src/main/proto/testing/foo/foo.proto"; 10 | 11 | message TestRequest { 12 | string message = 1; 13 | foo.Foo foo = 2; 14 | int32 number = 3; 15 | } 16 | 17 | message TestResponse { 18 | string message = 1; 19 | google.protobuf.Any any = 2; 20 | google.protobuf.Duration duration = 3; 21 | } 22 | 23 | // Used as a payload type for the any message above. 24 | message TunnelMessage { 25 | int32 number = 1; 26 | } 27 | 28 | service TestService { 29 | rpc TestMethod (TestRequest) returns (TestResponse) {} 30 | rpc TestMethodStream (TestRequest) returns (stream TestResponse) {} 31 | rpc TestMethodClientStream (stream TestRequest) returns (TestResponse) {} 32 | rpc TestMethodBidi (stream TestRequest) returns (stream TestResponse) {} 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/command/BUILD: -------------------------------------------------------------------------------- 1 | load("@autotest//bzl:autotest.bzl", "auto_java_test") 2 | 3 | auto_java_test( 4 | name = "tests", 5 | size = "small", 6 | srcs = glob(["*.java"]), 7 | deps = [ 8 | "//src/main/java/me/dinowernli/grpc/polyglot/command", 9 | "//src/main/java/me/dinowernli/grpc/polyglot/protobuf", 10 | "//src/main/java/me/dinowernli/grpc/polyglot/testing", 11 | "//src/main/proto/testing:test_service_java_proto", 12 | "//src/main/proto/testing/foo:foo_java_proto", 13 | "//third_party/guava", 14 | "//third_party/protobuf", 15 | "//third_party/testing", 16 | ], 17 | ) 18 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/command/ServiceListTest.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.command; 2 | 3 | import java.util.Optional; 4 | 5 | import com.google.common.collect.ImmutableList; 6 | import com.google.protobuf.Any; 7 | import com.google.protobuf.DescriptorProtos.FileDescriptorSet; 8 | import me.dinowernli.grpc.polyglot.protobuf.WellKnownTypes; 9 | import me.dinowernli.grpc.polyglot.testing.RecordingOutput; 10 | import me.dinowernli.junit.TestClass; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import polyglot.test.TestProto; 14 | import polyglot.test.foo.FooProto; 15 | 16 | import static com.google.common.truth.Truth.assertThat; 17 | 18 | /** Unit tests for {@link ServiceList}. */ 19 | @TestClass 20 | public class ServiceListTest { 21 | private static FileDescriptorSet PROTO_FILE_DESCRIPTORS = FileDescriptorSet.newBuilder() 22 | .addFile(TestProto.getDescriptor().toProto()) 23 | .addFile(FooProto.getDescriptor().toProto()) 24 | .addAllFile(WellKnownTypes.descriptors()) 25 | .build(); 26 | 27 | private static final String EXPECTED_SERVICE = "polyglot.test.TestService"; 28 | private static final ImmutableList EXPECTED_METHOD_NAMES = ImmutableList.of( 29 | "TestMethod", "TestMethodStream", "TestMethodClientStream", "TestMethodBidi"); 30 | 31 | private RecordingOutput recordingOutput; 32 | 33 | @Before 34 | public void setUp() throws Throwable { 35 | recordingOutput = new RecordingOutput(); 36 | } 37 | 38 | @Test 39 | public void testServiceListOutput() throws Throwable { 40 | ServiceList.listServices( 41 | recordingOutput, 42 | PROTO_FILE_DESCRIPTORS, 43 | "", 44 | Optional.empty(), 45 | Optional.empty(), 46 | Optional.empty()); 47 | recordingOutput.close(); 48 | 49 | validateOutput(recordingOutput.getContentsAsString(), EXPECTED_SERVICE, EXPECTED_METHOD_NAMES); 50 | } 51 | 52 | @Test 53 | public void testServiceListOutputWithServiceFilter() throws Throwable { 54 | ServiceList.listServices( 55 | recordingOutput, 56 | PROTO_FILE_DESCRIPTORS, 57 | "", 58 | Optional.of("TestService"), 59 | Optional.empty(), 60 | Optional.empty()); 61 | recordingOutput.close(); 62 | 63 | validateOutput(recordingOutput.getContentsAsString(), EXPECTED_SERVICE, EXPECTED_METHOD_NAMES); 64 | } 65 | 66 | @Test 67 | public void testServiceListOutputWithMethodFilter() throws Throwable { 68 | ServiceList.listServices( 69 | recordingOutput, 70 | PROTO_FILE_DESCRIPTORS, 71 | "", 72 | Optional.of("TestService"), 73 | Optional.of("TestMethodStream"), 74 | Optional.empty()); 75 | recordingOutput.close(); 76 | 77 | validateOutput( 78 | recordingOutput.getContentsAsString(), 79 | EXPECTED_SERVICE, 80 | ImmutableList.of("TestMethodStream")); 81 | } 82 | 83 | @Test 84 | public void testServiceListOutputWithMessageDetail() throws Throwable { 85 | ServiceList.listServices( 86 | recordingOutput, 87 | PROTO_FILE_DESCRIPTORS, 88 | "", 89 | Optional.of("TestService"), 90 | Optional.of("TestMethodStream"), 91 | Optional.of(true)); 92 | recordingOutput.close(); 93 | 94 | validateMessageOutput(recordingOutput.getContentsAsString()); 95 | } 96 | 97 | /** Compares the actual output with the expected output format */ 98 | private void validateOutput( 99 | String output, String serviceName, ImmutableList methodNames) { 100 | // Assuming no filters, we expect output of the form (note that [tmp_path] 101 | // is a placeholder): 102 | // 103 | // polyglot.test.TestService -> 104 | // [tmp_path]/src/main/proto/testing/test_service.proto 105 | // polyglot.test.TestService/TestMethod 106 | // polyglot.test.TestService/TestMethodStream 107 | // polyglot.test.TestService/TestMethodBidi 108 | 109 | String[] lines = output.trim().split("\n"); 110 | assertThat(lines.length).isEqualTo(methodNames.size() + 1); 111 | 112 | // Parse the first line (always [ServiceName] -> [FileName] 113 | assertThat(lines[0]).startsWith(serviceName + " -> "); 114 | 115 | // Parse the subsequent lines (always [ServiceName]/[MethodName]) 116 | for (int i = 0; i < methodNames.size(); i++) { 117 | assertThat(lines[i + 1].trim()).isEqualTo(serviceName + "/" + methodNames.get(i)); 118 | } 119 | } 120 | 121 | /** Ensures that the message-rendering logic is correct */ 122 | private void validateMessageOutput(String output) { 123 | // Assuming the filter is for TestService/TestMethodStream, then the message 124 | // should render as: 125 | // 126 | // polyglot.test.TestService -> 127 | // [tmp_path]/src/main/proto/testing/test_service.proto 128 | // polyglot.test.TestService/TestMethodStream 129 | // message[ ]: STRING 130 | // foo[ ] { 131 | // message[ ]: STRING 132 | // } 133 | 134 | String[] lines = output.trim().split("\n"); 135 | 136 | // Parse the first line (always [ServiceName] -> [FileName] 137 | assertThat(lines[0]).startsWith("polyglot.test.TestService -> "); 138 | 139 | ImmutableList expectedLines = ImmutableList.of( 140 | "polyglot.test.TestService/TestMethodStream", 141 | "message[ ]: STRING", 142 | "foo[ ] {", 143 | "message[ ]: STRING", 144 | "}"); 145 | 146 | for (int i = 0; i < expectedLines.size(); i++) { 147 | assertThat(lines[i + 1].trim()).isEqualTo(expectedLines.get(i)); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/config/BUILD: -------------------------------------------------------------------------------- 1 | load("@autotest//bzl:autotest.bzl", "auto_java_test") 2 | 3 | auto_java_test( 4 | name = "tests", 5 | size = "small", 6 | srcs = glob(["*.java"]), 7 | deps = [ 8 | "//src/main/java/me/dinowernli/grpc/polyglot/config", 9 | "//src/main/proto:config_java_proto", 10 | "//src/main/proto:config_proto", 11 | "//third_party/guava", 12 | "//third_party/testing", 13 | ], 14 | ) 15 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/config/CommandLineArgsTest.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.config; 2 | 3 | import static com.google.common.truth.Truth.assertThat; 4 | 5 | import com.google.common.collect.ImmutableMultimap; 6 | import me.dinowernli.junit.TestClass; 7 | 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.nio.file.Paths; 11 | import java.util.Optional; 12 | import java.util.Collections; 13 | import java.util.List; 14 | 15 | import org.junit.After; 16 | import org.junit.Before; 17 | import org.junit.Rule; 18 | import org.junit.Test; 19 | 20 | import com.google.common.collect.ImmutableList; 21 | import org.junit.rules.TemporaryFolder; 22 | 23 | /** Unit tests for {@link CommandLineArgs}. */ 24 | @TestClass 25 | public class CommandLineArgsTest { 26 | @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); 27 | private Path tempFile1; 28 | private Path tempFile2; 29 | 30 | @Before 31 | public void setUp() throws Throwable { 32 | tempFile1 = Paths.get(tempFolder.newFile().toURI()); 33 | tempFile2 = Paths.get(tempFolder.newFile().toURI()); 34 | } 35 | 36 | @Test 37 | public void parsesAdditionalIncludesSingle() { 38 | CommandLineArgs params = parseArgs(ImmutableList.of( 39 | makeArg("add_protoc_includes", String.format("%s,%s", tempFile1.toString(), tempFile2.toString()))), 40 | Collections.emptyList()); 41 | assertThat(params.additionalProtocIncludes()).hasSize(2); 42 | } 43 | 44 | @Test 45 | public void parsesAdditionalIncludesSingle_usingSpaceSeparator() { 46 | CommandLineArgs params = parseArgs(ImmutableList.of( 47 | makeArg("add_protoc_includes", ' ', String.format("%s,%s", tempFile1.toString(), tempFile2.toString()))), 48 | Collections.emptyList(), ' '); 49 | assertThat(params.additionalProtocIncludes()).hasSize(2); 50 | } 51 | 52 | @Test 53 | public void parsesAdditionalIncludesSingle_usingMixedSeparators() { 54 | CommandLineArgs params = parseArgs(ImmutableList.of( 55 | makeArg("add_protoc_includes", '=', String.format("%s,%s", tempFile1.toString(), tempFile2.toString()))), 56 | Collections.emptyList(), ' '); 57 | assertThat(params.additionalProtocIncludes()).hasSize(2); 58 | } 59 | 60 | @Test 61 | public void parsesAdditionalIncludesMulti() { 62 | CommandLineArgs params = parseArgs(ImmutableList.of( 63 | makeArg("add_protoc_includes", tempFile1.toString())), 64 | Collections.emptyList()); 65 | assertThat(params.additionalProtocIncludes()).hasSize(1); 66 | } 67 | 68 | @Test 69 | public void parseMetadata() { 70 | CommandLineArgs params = parseArgs(Collections.emptyList(), ImmutableList.of( 71 | makeArg("metadata", String.format("%s:%s,%s:%s,%s:%s", 72 | "key1", "value1", "key1", "value2", "key2", "value2")))); 73 | 74 | ImmutableMultimap expectedResult = 75 | ImmutableMultimap.of("key1", "value1", "key1", "value2", "key2", "value2"); 76 | assertThat(params.metadata()).isEqualTo(Optional.of(expectedResult)); 77 | } 78 | 79 | @Test 80 | public void parseMetadata_usingSpaceSeparator() { 81 | CommandLineArgs params = parseArgs(Collections.emptyList(), ImmutableList.of( 82 | makeArg("metadata", ' ', String.format("%s:%s,%s:%s,%s:%s", 83 | "key1", "value1", "key1", "value2", "key2", "value2"))), ' '); 84 | 85 | ImmutableMultimap expectedResult = 86 | ImmutableMultimap.of("key1", "value1", "key1", "value2", "key2", "value2"); 87 | assertThat(params.metadata()).isEqualTo(Optional.of(expectedResult)); 88 | } 89 | 90 | @Test 91 | public void parseMetadataWithSpaces() { 92 | CommandLineArgs params = parseArgs(Collections.emptyList(), ImmutableList.of( 93 | makeArg("metadata", String.format("%s:%s,%s:%s,%s:%s", 94 | "key1", "value1 ", "key2", " value2", "key3", "value 3")))); 95 | 96 | ImmutableMultimap expectedResult = 97 | ImmutableMultimap.of("key1", "value1 ", "key2", " value2", "key3", "value 3"); 98 | assertThat(params.metadata()).isEqualTo(Optional.of(expectedResult)); 99 | } 100 | 101 | @Test 102 | public void parseMetadataWithSpaces_usingSpaceSeparator() { 103 | CommandLineArgs params = parseArgs(Collections.emptyList(), ImmutableList.of( 104 | makeArg("metadata", ' ', String.format("%s:%s,%s:%s,%s:%s", 105 | "key1", "value1 ", "key2", " value2", "key3", "value 3"))), ' '); 106 | 107 | ImmutableMultimap expectedResult = 108 | ImmutableMultimap.of("key1", "value1 ", "key2", " value2", "key3", "value 3"); 109 | assertThat(params.metadata()).isEqualTo(Optional.of(expectedResult)); 110 | } 111 | 112 | @Test(expected = IllegalArgumentException.class) 113 | public void parseMetadataWithKeyWithoutValue() { 114 | CommandLineArgs params = parseArgs(Collections.emptyList(), ImmutableList.of( 115 | makeArg("metadata", String.format("%s:%s,%s", "key1", "value1", "key2")))); 116 | 117 | params.metadata(); 118 | } 119 | 120 | @Test 121 | public void parseMetadataWithColons() { 122 | CommandLineArgs params = parseArgs(Collections.emptyList(), ImmutableList.of( 123 | makeArg("metadata", String.format("%s:%s,%s:%s,%s:%s", 124 | "key1", "value:1", "key2", "value:2", "key3", "value:3")))); 125 | 126 | ImmutableMultimap expectedResult = 127 | ImmutableMultimap.of("key1", "value:1", "key2", "value:2", "key3", "value:3"); 128 | assertThat(params.metadata()).isEqualTo(Optional.of(expectedResult)); 129 | 130 | } 131 | 132 | @Test 133 | public void parseOutputFileEvenIfAbsent() { 134 | Path filePath = Paths.get(tempFolder.getRoot().getAbsolutePath(), "some-file.txt"); 135 | CommandLineArgs params = parseArgs(ImmutableList.of( 136 | makeArg("output_file_path", filePath.toAbsolutePath().toString())), 137 | Collections.emptyList()); 138 | assertThat(params.outputFilePath().isPresent()).isTrue(); 139 | } 140 | 141 | @Test(expected = IllegalArgumentException.class) 142 | public void parseOptionWithoutCommand() { 143 | CommandLineArgs.parse(new String[]{makeArg("endpoint", "somehost:1234")}); 144 | } 145 | 146 | @Test 147 | public void usage() { 148 | assertThat(CommandLineArgs.getUsage()).startsWith("Usage: java -jar polyglot.jar [options] [command] [command options]"); 149 | } 150 | 151 | @Test 152 | public void help() { 153 | assertThat(CommandLineArgs.parse(new String[]{"--help"}).isHelp()).isTrue(); 154 | } 155 | 156 | private static CommandLineArgs parseArgs(List args, List callArgs) { 157 | return parseArgs(args, callArgs, '='); 158 | } 159 | 160 | private static CommandLineArgs parseArgs(List args, List callArgs, char separator) { 161 | ImmutableList allArgs = ImmutableList.builder() 162 | .addAll(args) 163 | .add(makeArg("proto_discovery_root", separator, ".")) 164 | .add(makeArg("use_reflection", separator, "true")) 165 | .add("call") 166 | .addAll(callArgs) 167 | .add(makeArg("endpoint", separator, "somehost:1234")) 168 | .add(makeArg("full_method", separator, "some.package/Method")) 169 | .build(); 170 | return CommandLineArgs.parse(allArgs.toArray(new String[0])); 171 | } 172 | 173 | private static String makeArg(String option, String value) { 174 | return makeArg(option, '=', value); 175 | } 176 | 177 | private static String makeArg(String option, char separator, String value) { 178 | return String.format("--%s%c%s", option, separator, value); 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/config/ConfigurationLoaderTest.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.config; 2 | 3 | import java.nio.file.Paths; 4 | import java.util.Optional; 5 | 6 | import com.google.common.collect.ImmutableList; 7 | import com.google.common.collect.ImmutableMultimap; 8 | import me.dinowernli.junit.TestClass; 9 | import org.junit.After; 10 | import org.junit.Before; 11 | import org.junit.Rule; 12 | import org.junit.Test; 13 | import org.junit.rules.TemporaryFolder; 14 | import org.mockito.Mock; 15 | import org.mockito.junit.MockitoJUnit; 16 | import org.mockito.junit.MockitoRule; 17 | import polyglot.ConfigProto.CallConfiguration; 18 | import polyglot.ConfigProto.Configuration; 19 | import polyglot.ConfigProto.ConfigurationSet; 20 | import polyglot.ConfigProto.OutputConfiguration.Destination; 21 | 22 | import static com.google.common.truth.Truth.assertThat; 23 | import static org.mockito.Mockito.when; 24 | 25 | /** Unit tests for {@link ConfigurationLoader}. */ 26 | @TestClass 27 | public class ConfigurationLoaderTest { 28 | @Rule public TemporaryFolder tempDirectory = new TemporaryFolder(); 29 | @Rule public MockitoRule mockitoJunitRule = MockitoJUnit.rule(); 30 | @Mock private CommandLineArgs mockOverrides; 31 | 32 | private String storedHomeProperty; 33 | 34 | @Before 35 | public void setUp() { 36 | storedHomeProperty = System.getProperty("user.home"); 37 | System.setProperty("user.home", tempDirectory.getRoot().getAbsolutePath()); 38 | } 39 | 40 | @After 41 | public void tearDown() { 42 | System.setProperty("user.home", storedHomeProperty); 43 | } 44 | 45 | @Test 46 | public void loadsDefaultConfig() { 47 | Configuration defaultConfig = 48 | ConfigurationLoader.forDefaultConfigSet().getDefaultConfiguration(); 49 | assertThat(defaultConfig).isEqualTo(Configuration.getDefaultInstance()); 50 | 51 | assertThat(defaultConfig.getCallConfig().getUseTls()).isFalse(); 52 | assertThat(defaultConfig.getOutputConfig().getDestination()).isEqualTo(Destination.STDOUT); 53 | } 54 | 55 | @Test(expected = IllegalStateException.class) 56 | public void throwsIfAskedToLoadNamedFromDefaultSet() { 57 | ConfigurationLoader.forDefaultConfigSet().getNamedConfiguration("asdf"); 58 | } 59 | 60 | @Test(expected = IllegalArgumentException.class) 61 | public void throwsIfNamedConfigMissing() { 62 | ConfigurationLoader.forConfigSet(ConfigurationSet.getDefaultInstance()) 63 | .getNamedConfiguration("asfd"); 64 | } 65 | 66 | @Test 67 | public void loadsNamedConfig() { 68 | ConfigurationLoader loader = ConfigurationLoader.forConfigSet(ConfigurationSet.newBuilder() 69 | .addConfigurations(namedConfig("foo")) 70 | .addConfigurations(namedConfig("bar")) 71 | .build()); 72 | assertThat(loader.getNamedConfiguration("foo").getName()).isEqualTo("foo"); 73 | } 74 | 75 | @Test 76 | public void appliesOverrides() { 77 | when(mockOverrides.useTls()).thenReturn(Optional.of(true)); 78 | when(mockOverrides.outputFilePath()).thenReturn(Optional.of(Paths.get("asdf"))); 79 | when(mockOverrides.additionalProtocIncludes()).thenReturn(ImmutableList.of(Paths.get("."))); 80 | when(mockOverrides.protoDiscoveryRoot()).thenReturn(Optional.of(Paths.get("."))); 81 | when(mockOverrides.getRpcDeadlineMs()).thenReturn(Optional.of(25)); 82 | when(mockOverrides.tlsCaCertPath()).thenReturn(Optional.of(Paths.get("asdf"))); 83 | when(mockOverrides.tlsClientCertPath()).thenReturn(Optional.of(Paths.get("client_cert"))); 84 | when(mockOverrides.tlsClientKeyPath()).thenReturn(Optional.of(Paths.get("client_key"))); 85 | when(mockOverrides.tlsClientOverrideAuthority()).thenReturn(Optional.of("override_authority")); 86 | ImmutableMultimap metadata = ImmutableMultimap.of("key1", "value1", "key2", "value2"); 87 | when(mockOverrides.metadata()).thenReturn(Optional.of(metadata)); 88 | 89 | Configuration config = ConfigurationLoader 90 | .forDefaultConfigSet() 91 | .withOverrides(mockOverrides) 92 | .getDefaultConfiguration(); 93 | 94 | assertThat(config.getOutputConfig().getDestination()).isEqualTo(Destination.FILE); 95 | 96 | CallConfiguration callConfig = config.getCallConfig(); 97 | assertThat(callConfig.getUseTls()).isTrue(); 98 | assertThat(callConfig.getDeadlineMs()).isEqualTo(25); 99 | assertThat(callConfig.getTlsCaCertPath()).isNotEmpty(); 100 | assertThat(callConfig.getTlsClientCertPath()).isEqualTo("client_cert"); 101 | assertThat(callConfig.getTlsClientKeyPath()).isEqualTo("client_key"); 102 | assertThat(callConfig.getTlsClientOverrideAuthority()).isEqualTo("override_authority"); 103 | assertThat(callConfig.getMetadataCount()).isEqualTo(2); 104 | } 105 | 106 | private static Configuration namedConfig(String name) { 107 | return Configuration.newBuilder() 108 | .setName(name) 109 | .build(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/grpc/BUILD: -------------------------------------------------------------------------------- 1 | load("@autotest//bzl:autotest.bzl", "auto_java_test") 2 | 3 | auto_java_test( 4 | name = "tests", 5 | size = "small", 6 | srcs = glob(["*.java"]), 7 | deps = [ 8 | "//src/main/java/me/dinowernli/grpc/polyglot/grpc", 9 | "//src/main/proto/testing:test_service_java_proto", 10 | "//src/main/proto/testing:test_service_proto", 11 | "//third_party/grpc", 12 | "//third_party/guava", 13 | "//third_party/protobuf", 14 | "//third_party/testing", 15 | ], 16 | ) 17 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/grpc/CompositeStreamObserverTest.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.grpc; 2 | 3 | import static org.mockito.Mockito.doThrow; 4 | import static org.mockito.Mockito.verify; 5 | 6 | import me.dinowernli.junit.TestClass; 7 | import org.junit.Before; 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.mockito.Mock; 11 | import org.mockito.junit.MockitoJUnit; 12 | import org.mockito.junit.MockitoRule; 13 | 14 | import io.grpc.stub.StreamObserver; 15 | 16 | 17 | /** Unit tests for {@link CompositeStreamObserver}. */ 18 | @TestClass 19 | public class CompositeStreamObserverTest { 20 | @Rule public MockitoRule mockitoJunitRule = MockitoJUnit.rule(); 21 | @Mock private StreamObserver mockFirstObserver; 22 | @Mock private StreamObserver mockSecondObserver; 23 | 24 | private CompositeStreamObserver compositeObserver; 25 | 26 | @Before 27 | public void setUp() { 28 | compositeObserver = CompositeStreamObserver.of(mockFirstObserver, mockSecondObserver); 29 | } 30 | 31 | @Test 32 | public void callsOnNext() { 33 | SomeType value = new SomeType(); 34 | compositeObserver.onNext(value); 35 | verify(mockFirstObserver).onNext(value); 36 | verify(mockSecondObserver).onNext(value); 37 | } 38 | 39 | @Test 40 | public void callOthersEvenIfOneErrorsOut() { 41 | SomeType value = new SomeType(); 42 | doThrow(new RuntimeException()).when(mockFirstObserver).onNext(value); 43 | compositeObserver.onNext(value); 44 | verify(mockFirstObserver).onNext(value); 45 | verify(mockSecondObserver).onNext(value); 46 | } 47 | 48 | @Test 49 | public void callsOnComplete() { 50 | compositeObserver.onCompleted(); 51 | verify(mockFirstObserver).onCompleted(); 52 | verify(mockSecondObserver).onCompleted(); 53 | } 54 | 55 | @Test 56 | public void forwardsErrors() { 57 | Throwable t = new Exception(); 58 | compositeObserver.onError(t); 59 | verify(mockFirstObserver).onError(t); 60 | verify(mockSecondObserver).onError(t); 61 | } 62 | 63 | private static class SomeType { 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/grpc/DynamicGrpcClientTest.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.grpc; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.protobuf.Descriptors.MethodDescriptor; 5 | import com.google.protobuf.DynamicMessage; 6 | import io.grpc.CallOptions; 7 | import io.grpc.Channel; 8 | import io.grpc.ClientCall; 9 | import io.grpc.stub.StreamObserver; 10 | import me.dinowernli.junit.TestClass; 11 | import org.junit.Before; 12 | import org.junit.Rule; 13 | import org.junit.Test; 14 | import org.mockito.ArgumentCaptor; 15 | import org.mockito.Captor; 16 | import org.mockito.Matchers; 17 | import org.mockito.Mock; 18 | import org.mockito.junit.MockitoJUnit; 19 | import org.mockito.junit.MockitoRule; 20 | import polyglot.test.TestProto; 21 | import polyglot.test.TestProto.TestRequest; 22 | 23 | import java.util.concurrent.TimeUnit; 24 | 25 | import static com.google.common.truth.Truth.assertThat; 26 | import static org.mockito.Mockito.verify; 27 | import static org.mockito.Mockito.when; 28 | 29 | /** Unit tests for {@link DynamicGrpcClient}. */ 30 | @TestClass 31 | public class DynamicGrpcClientTest { 32 | private static final CallOptions CALL_OPTIONS = 33 | CallOptions.DEFAULT.withDeadlineAfter(1234L, TimeUnit.NANOSECONDS); 34 | 35 | private static final MethodDescriptor UNARY_METHOD = TestProto 36 | .getDescriptor() 37 | .findServiceByName("TestService") 38 | .findMethodByName("TestMethod"); 39 | 40 | private static final MethodDescriptor SERVER_STREAMING_METHOD = TestProto 41 | .getDescriptor() 42 | .findServiceByName("TestService") 43 | .findMethodByName("TestMethodStream"); 44 | 45 | private static final MethodDescriptor BIDI_STREAMING_METHOD = TestProto 46 | .getDescriptor() 47 | .findServiceByName("TestService") 48 | .findMethodByName("TestMethodBidi"); 49 | 50 | private static final DynamicMessage REQUEST = DynamicMessage.newBuilder( 51 | TestRequest.newBuilder() 52 | .setMessage("some message") 53 | .build()) 54 | .build(); 55 | 56 | @Rule public MockitoRule mockitoJunitRule = MockitoJUnit.rule(); 57 | @Mock private Channel mockChannel; 58 | @Mock private StreamObserver mockStreamObserver; 59 | @Mock private ClientCall mockClientCall; 60 | @Captor private ArgumentCaptor callOptionsCaptor; 61 | 62 | private DynamicGrpcClient client; 63 | 64 | @Before 65 | public void setUp() { 66 | when(mockChannel.newCall( 67 | Matchers.>any(), 68 | Matchers.any())) 69 | .thenReturn(mockClientCall); 70 | } 71 | 72 | @Test 73 | public void unaryMethodCall() { 74 | client = new DynamicGrpcClient(UNARY_METHOD, mockChannel); 75 | client.call(ImmutableList.of(REQUEST), mockStreamObserver, CALL_OPTIONS); 76 | // No crash. 77 | } 78 | 79 | @Test 80 | public void passesCallOptions() { 81 | client = new DynamicGrpcClient(UNARY_METHOD, mockChannel); 82 | client.call(ImmutableList.of(REQUEST), mockStreamObserver, CALL_OPTIONS); 83 | 84 | verify(mockChannel).newCall( 85 | Matchers.>any(), 86 | callOptionsCaptor.capture()); 87 | assertThat(callOptionsCaptor.getValue()).isEqualTo(CALL_OPTIONS); 88 | } 89 | 90 | // TODO(dino): Add some more tests for the streaming and bidi cases. 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/integration/BUILD: -------------------------------------------------------------------------------- 1 | load("@autotest//bzl:autotest.bzl", "auto_java_test") 2 | 3 | auto_java_test( 4 | name = "plain", 5 | size = "medium", 6 | srcs = [ 7 | "ClientServerIntegrationTest.java", 8 | ], 9 | data = [ 10 | "//src/main/proto/testing:proto_files", 11 | ], 12 | deps = [ 13 | "//src/main/java/me/dinowernli/grpc/polyglot:polyglot-lib", 14 | "//src/main/java/me/dinowernli/grpc/polyglot/io", 15 | "//src/main/java/me/dinowernli/grpc/polyglot/testing", 16 | "//src/main/proto/testing:test_service_java_proto", 17 | "//src/main/proto/testing:test_service_proto_grpc", 18 | "//third_party/grpc", 19 | "//third_party/guava", 20 | "//third_party/protobuf", 21 | "//third_party/testing", 22 | ], 23 | ) 24 | 25 | auto_java_test( 26 | name = "tls", 27 | size = "medium", 28 | srcs = [ 29 | "TlsIntegrationTest.java", 30 | ], 31 | data = [ 32 | "//src/main/proto/testing:proto_files", 33 | ], 34 | deps = [ 35 | "//src/main/java/me/dinowernli/grpc/polyglot:polyglot-lib", 36 | "//src/main/java/me/dinowernli/grpc/polyglot/io", 37 | "//src/main/java/me/dinowernli/grpc/polyglot/testing", 38 | "//src/main/proto/testing:test_service_java_proto", 39 | "//src/main/proto/testing:test_service_proto_grpc", 40 | "//third_party/grpc", 41 | "//third_party/guava", 42 | "//third_party/protobuf", 43 | "//third_party/testing", 44 | ], 45 | ) 46 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/integration/TlsIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.integration; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.InputStream; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.util.Optional; 8 | 9 | import com.google.common.base.Charsets; 10 | import com.google.common.base.Joiner; 11 | import com.google.common.collect.ImmutableList; 12 | import me.dinowernli.grpc.polyglot.io.MessageWriter; 13 | import me.dinowernli.grpc.polyglot.testing.TestServer; 14 | import me.dinowernli.grpc.polyglot.testing.TestUtils; 15 | import me.dinowernli.junit.TestClass; 16 | import org.junit.After; 17 | import org.junit.Before; 18 | import org.junit.Rule; 19 | import org.junit.Test; 20 | import org.junit.rules.TemporaryFolder; 21 | import polyglot.test.TestProto.TestRequest; 22 | import polyglot.test.TestProto.TestResponse; 23 | 24 | import static com.google.common.truth.Truth.assertThat; 25 | import static me.dinowernli.grpc.polyglot.testing.TestUtils.makeArgument; 26 | 27 | /** 28 | * An integration test suite which tests Polyglot's ability to connect to servers using TLS. 29 | */ 30 | @TestClass 31 | public class TlsIntegrationTest { 32 | private static final String TEST_UNARY_METHOD = "polyglot.test.TestService/TestMethod"; 33 | 34 | private static final TestRequest REQUEST = TestRequest.newBuilder() 35 | .setMessage("i am totally a message") 36 | .build(); 37 | 38 | @Rule public TemporaryFolder tempDirectory = new TemporaryFolder(); 39 | 40 | private TestServer testServer; 41 | private InputStream storedStdin; 42 | private Path responseFilePath; 43 | 44 | private String storedHomeProperty; 45 | private String storedSecurityProperty; 46 | 47 | private static final String PROPERTY_HOME = "user.home"; 48 | private static final String PROPERTY_SECURITY_CA = "jdk.security.allowNonCaAnchor"; 49 | 50 | @Before 51 | public void setUp() throws Throwable { 52 | storedHomeProperty = System.getProperty(PROPERTY_HOME); 53 | System.setProperty(PROPERTY_HOME, tempDirectory.getRoot().getAbsolutePath()); 54 | 55 | // A check for specific certificate extensions was added to the JDK as part of "Oracle 56 | // Security-in-Depth Fix 8230318: Better trust store usage". The certificates we use for 57 | // testing are valid enough to test the end-to-end flow, but happen to not have this particular 58 | // extension. Until we generate new certs with the right extension, we need to disable this one 59 | // check. 60 | // TODO(dino): Remove this once we have updated the certificates used for TLS testing. 61 | storedSecurityProperty = System.getProperty(PROPERTY_SECURITY_CA); 62 | System.setProperty(PROPERTY_SECURITY_CA, "true"); 63 | 64 | responseFilePath = Files.createTempFile("response", "pb.ascii"); 65 | storedStdin = System.in; 66 | } 67 | 68 | @After 69 | public void tearDown() throws Throwable { 70 | if (storedSecurityProperty == null) { 71 | System.clearProperty(PROPERTY_SECURITY_CA); 72 | } else { 73 | System.setProperty(PROPERTY_SECURITY_CA, storedSecurityProperty); 74 | } 75 | 76 | if (storedHomeProperty == null) { 77 | System.clearProperty(PROPERTY_HOME); 78 | } else { 79 | System.setProperty(PROPERTY_HOME, storedHomeProperty); 80 | } 81 | 82 | testServer.blockingShutdown(); 83 | System.setIn(storedStdin); 84 | Files.delete(responseFilePath); 85 | } 86 | 87 | @Test 88 | public void makesRoundTripUnary() throws Throwable { 89 | testServer = TestServer.createAndStart(Optional.of(TestServer.serverSslContextForTesting())); 90 | ImmutableList args = ImmutableList.builder() 91 | .add(makeArgument("output_file_path", responseFilePath.toString())) 92 | .addAll(makeArgs(testServer.getGrpcServerPort(), TEST_UNARY_METHOD)) 93 | .add(makeArgument("tls_ca_cert_path", TestUtils.loadRootCaCert().getAbsolutePath())) 94 | .add(makeArgument("use_tls", "true")) 95 | .build(); 96 | setStdinContents(MessageWriter.writeJsonStream(ImmutableList.of(REQUEST))); 97 | 98 | // Run the full client. 99 | me.dinowernli.grpc.polyglot.Main.main(args.toArray(new String[0])); 100 | 101 | // Make sure we can parse the response from the file. 102 | ImmutableList responses = TestUtils.readResponseFile(responseFilePath); 103 | assertThat(responses).hasSize(1); 104 | assertThat(responses.get(0)).isEqualTo(TestServer.UNARY_SERVER_RESPONSE); 105 | } 106 | 107 | @Test 108 | public void makesRoundTripWithClientCerts() throws Throwable { 109 | testServer = TestServer.createAndStart( 110 | Optional.of(TestServer.serverSslContextWithClientCertsForTesting())); 111 | ImmutableList args = ImmutableList.builder() 112 | .add(makeArgument("output_file_path", responseFilePath.toString())) 113 | .addAll(makeArgs(testServer.getGrpcServerPort(), TEST_UNARY_METHOD)) 114 | .add(makeArgument("tls_ca_cert_path", TestUtils.loadRootCaCert().getAbsolutePath())) 115 | .add(makeArgument("tls_client_cert_path", TestUtils.loadClientCert().getAbsolutePath())) 116 | .add(makeArgument("tls_client_key_path", TestUtils.loadClientKey().getAbsolutePath())) 117 | .add(makeArgument("use_tls", "true")) 118 | .build(); 119 | setStdinContents(MessageWriter.writeJsonStream(ImmutableList.of(REQUEST))); 120 | 121 | // Run the full client. 122 | me.dinowernli.grpc.polyglot.Main.main(args.toArray(new String[0])); 123 | 124 | // Make sure we can parse the response from the file. 125 | ImmutableList responses = TestUtils.readResponseFile(responseFilePath); 126 | assertThat(responses).hasSize(1); 127 | assertThat(responses.get(0)).isEqualTo(TestServer.UNARY_SERVER_RESPONSE); 128 | } 129 | 130 | private static ImmutableList makeArgs(int port, String method) { 131 | return TestUtils.makePolyglotCallArgs(Joiner.on(':').join("localhost", port), method); 132 | } 133 | 134 | private static void setStdinContents(String contents) { 135 | System.setIn(new ByteArrayInputStream(contents.getBytes(Charsets.UTF_8))); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/io/BUILD: -------------------------------------------------------------------------------- 1 | load("@autotest//bzl:autotest.bzl", "auto_java_test") 2 | 3 | auto_java_test( 4 | name = "tests", 5 | size = "small", 6 | srcs = glob(["*.java"]), 7 | data = [ 8 | ":testdata", 9 | ], 10 | deps = [ 11 | "//src/main/java/me/dinowernli/grpc/polyglot/io", 12 | "//src/main/java/me/dinowernli/grpc/polyglot/io/testing", 13 | "//src/main/java/me/dinowernli/grpc/polyglot/testing", 14 | "//src/main/proto:config_java_proto", 15 | "//src/main/proto/testing:test_service_java_proto", 16 | "//third_party/grpc", 17 | "//third_party/guava", 18 | "//third_party/protobuf", 19 | "//third_party/testing", 20 | ], 21 | ) 22 | 23 | filegroup( 24 | name = "testdata", 25 | srcs = glob(["testdata/*"]), 26 | ) 27 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/io/MessageReaderTest.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.io; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.StringReader; 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | 8 | import com.google.common.collect.ImmutableList; 9 | import com.google.protobuf.Descriptors.Descriptor; 10 | import com.google.protobuf.DynamicMessage; 11 | import com.google.protobuf.util.JsonFormat; 12 | import com.google.protobuf.util.JsonFormat.TypeRegistry; 13 | import me.dinowernli.junit.TestClass; 14 | import me.dinowernli.grpc.polyglot.io.testing.TestData; 15 | import me.dinowernli.grpc.polyglot.testing.TestUtils; 16 | import org.junit.Test; 17 | import polyglot.test.TestProto.TestRequest; 18 | import polyglot.test.TestProto.TestResponse; 19 | import polyglot.test.TestProto.TunnelMessage; 20 | 21 | import static com.google.common.truth.Truth.assertThat; 22 | 23 | /** Unit tests for {@link MessageReader}. */ 24 | @TestClass 25 | public class MessageReaderTest { 26 | private static final String SOURCE = "TEST_SOURCE"; 27 | private static final Descriptor DESCRIPTOR = TestRequest.getDescriptor(); 28 | private static String TESTDATA_ROOT = Paths.get(TestUtils.getWorkspaceRoot().toString(), 29 | "src", "test", "java", "me", "dinowernli", "grpc", "polyglot", "io", "testdata").toString(); 30 | 31 | private MessageReader reader; 32 | 33 | @Test 34 | public void readsSingle() { 35 | reader = MessageReader.forFile(dataFilePath("request.pb.ascii"), DESCRIPTOR); 36 | ImmutableList result = reader.read(); 37 | assertThat(result).containsExactly(TestData.REQUEST); 38 | } 39 | 40 | @Test 41 | public void readsMultiple() { 42 | reader = MessageReader.forFile(dataFilePath("requests_multi.pb.ascii"), DESCRIPTOR); 43 | ImmutableList result = reader.read(); 44 | assertThat(result).containsExactlyElementsIn(TestData.REQUESTS_MULTI); 45 | } 46 | 47 | @Test 48 | public void readsMultipleWithInterruption() { 49 | reader = MessageReader.forFile(dataFilePath("requests_multi_interrupted.pb.ascii"), DESCRIPTOR); 50 | ImmutableList result = reader.read(); 51 | assertThat(result).containsExactlyElementsIn(TestData.REQUESTS_MULTI); 52 | } 53 | 54 | @Test 55 | public void handlesEmptyStream() { 56 | reader = MessageReader.forFile(dataFilePath("request_empty.pb.ascii"), DESCRIPTOR); 57 | ImmutableList result = reader.read(); 58 | assertThat(result).isEmpty(); 59 | } 60 | 61 | @Test 62 | public void handlesPrimitives() { 63 | reader = MessageReader.forFile(dataFilePath("request_with_primitives.pb.ascii"), DESCRIPTOR); 64 | ImmutableList result = reader.read(); 65 | assertThat(result).containsExactly(TestData.REQUEST_WITH_PRIMITIVE); 66 | } 67 | 68 | @Test 69 | public void handlesAnyType() { 70 | TypeRegistry registry = TypeRegistry.newBuilder() 71 | .add(TunnelMessage.getDescriptor()) 72 | .build(); 73 | reader = MessageReader.forFile( 74 | dataFilePath("response_any.pb.ascii"), TestResponse.getDescriptor(), registry); 75 | ImmutableList result = reader.read(); 76 | 77 | // If this doesn't crash, then we're happy. 78 | assertThat(result).hasSize(1); 79 | } 80 | 81 | @Test 82 | public void handlesEmptyString() { 83 | reader = createWithInput(""); 84 | ImmutableList result = reader.read(); 85 | assertThat(result).isEmpty(); 86 | } 87 | 88 | @Test 89 | public void handlesSingleNewline() { 90 | reader = createWithInput("\n"); 91 | ImmutableList result = reader.read(); 92 | assertThat(result).isEmpty(); 93 | } 94 | 95 | @Test 96 | public void handlesTwoNewlines() { 97 | reader = createWithInput("\n\n"); 98 | ImmutableList result = reader.read(); 99 | assertThat(result).isEmpty(); 100 | } 101 | 102 | @Test(expected = IllegalArgumentException.class) 103 | public void rejectsMalformed() { 104 | reader = createWithInput("ha! try parsing this as a stream"); 105 | ImmutableList result = reader.read(); 106 | assertThat(result).isEmpty(); 107 | } 108 | 109 | @Test(expected = IllegalArgumentException.class) 110 | public void rejectsBadProto() { 111 | reader = createWithInput("{ 'message': 'as \n\n"); 112 | ImmutableList result = reader.read(); 113 | assertThat(result).isEmpty(); 114 | } 115 | 116 | private static Path dataFilePath(String filename) { 117 | return Paths.get(TESTDATA_ROOT, filename); 118 | } 119 | 120 | private static MessageReader createWithInput(String input) { 121 | return new MessageReader( 122 | JsonFormat.parser(), 123 | TestRequest.getDescriptor(), 124 | new BufferedReader(new StringReader(input)), 125 | SOURCE); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/io/MessageWriterTest.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.io; 2 | 3 | import java.io.IOException; 4 | import java.nio.file.Files; 5 | import java.nio.file.Path; 6 | import java.nio.file.Paths; 7 | 8 | import com.google.protobuf.Any; 9 | import com.google.protobuf.Duration; 10 | import com.google.protobuf.DynamicMessage; 11 | import com.google.protobuf.Message; 12 | import com.google.protobuf.util.JsonFormat; 13 | import com.google.protobuf.util.JsonFormat.TypeRegistry; 14 | import me.dinowernli.junit.TestClass; 15 | import me.dinowernli.grpc.polyglot.io.testing.TestData; 16 | import me.dinowernli.grpc.polyglot.testing.RecordingOutput; 17 | import me.dinowernli.grpc.polyglot.testing.TestUtils; 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | import polyglot.test.TestProto.TestResponse; 21 | import polyglot.test.TestProto.TunnelMessage; 22 | 23 | import static com.google.common.truth.Truth.assertThat; 24 | 25 | /** Unit tests for {@link MessageWriter}. */ 26 | @TestClass 27 | public class MessageWriterTest { 28 | private static final String TESTDATA_ROOT = Paths.get(TestUtils.getWorkspaceRoot().toString(), 29 | "src", "test", "java", "me", "dinowernli", "grpc", "polyglot", "io", "testdata").toString(); 30 | private static final TypeRegistry REGISTRY = TypeRegistry.newBuilder() 31 | .add(TunnelMessage.getDescriptor()) 32 | .build(); 33 | 34 | private MessageWriter writer; 35 | private RecordingOutput recordingOutput; 36 | 37 | @Before 38 | public void setUp() { 39 | recordingOutput = new RecordingOutput(); 40 | writer = new MessageWriter<>(JsonFormat.printer().usingTypeRegistry(REGISTRY), recordingOutput); 41 | } 42 | 43 | @Test 44 | public void writesSingleMessage() throws Throwable { 45 | writer.onNext(TestData.REQUEST); 46 | writer.onCompleted(); 47 | 48 | recordingOutput.close(); 49 | 50 | assertThat(recordingOutput.getContentsAsString()).isEqualTo(loadTestFile("request.pb.ascii")); 51 | } 52 | 53 | @Test 54 | public void writesMultipleMessages() throws Throwable { 55 | TestData.REQUESTS_MULTI.forEach(writer::onNext); 56 | writer.onCompleted(); 57 | 58 | recordingOutput.close(); 59 | 60 | assertThat(recordingOutput.getContentsAsString()) 61 | .isEqualTo(loadTestFile("requests_multi.pb.ascii")); 62 | } 63 | 64 | @Test 65 | public void writesMessageWithAny() throws Throwable { 66 | TestResponse proto = TestResponse.newBuilder() 67 | .setDuration(Duration.newBuilder() 68 | .setSeconds(5678) 69 | .setNanos(1234)) 70 | .setAny(Any.pack(TunnelMessage.newBuilder() 71 | .setNumber(42) 72 | .build())) 73 | .build(); 74 | 75 | writer.onNext(DynamicMessage.newBuilder(proto).build()); 76 | writer.onCompleted(); 77 | recordingOutput.close(); 78 | 79 | assertThat(recordingOutput.getContentsAsString()) 80 | .isEqualTo(loadTestFile("response_any.pb.ascii")); 81 | } 82 | 83 | private static String loadTestFile(String filename) { 84 | Path filePath = Paths.get(TESTDATA_ROOT, filename); 85 | try { 86 | return new String(Files.readAllBytes(filePath)); 87 | } catch (IOException e) { 88 | throw new RuntimeException("Could not load file: " + filePath.toString()); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/io/OutputTest.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.io; 2 | 3 | import com.google.common.io.Files; 4 | import me.dinowernli.junit.TestClass; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.junit.rules.TemporaryFolder; 8 | import polyglot.ConfigProto.OutputConfiguration; 9 | import polyglot.ConfigProto.OutputConfiguration.Destination; 10 | 11 | import java.io.File; 12 | import java.nio.charset.Charset; 13 | import java.nio.file.Path; 14 | import java.nio.file.Paths; 15 | 16 | import static com.google.common.truth.Truth.assertThat; 17 | 18 | /** Unit tests for {@link Output}. */ 19 | @TestClass 20 | public class OutputTest { 21 | @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); 22 | 23 | @Test 24 | public void handlesMissingFile() throws Throwable { 25 | Path filePath = Paths.get(tempFolder.getRoot().getAbsolutePath(), "some-file.txt"); 26 | Output output = Output.forConfiguration(OutputConfiguration.newBuilder() 27 | .setDestination(Destination.FILE) 28 | .setFilePath(filePath.toString()) 29 | .build()); 30 | 31 | output.write("foo"); 32 | output.close(); 33 | 34 | String line = Files.readFirstLine(new File(filePath.toUri()), Charset.defaultCharset()); 35 | assertThat(line).isEqualTo("foo"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/io/ReadWriteRoundTripTest.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.io; 2 | 3 | import static com.google.common.truth.Truth.assertThat; 4 | 5 | import java.io.IOException; 6 | import java.io.PrintStream; 7 | import java.nio.file.Files; 8 | import java.nio.file.Path; 9 | 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | 13 | import com.google.common.collect.ImmutableList; 14 | import com.google.protobuf.Descriptors.Descriptor; 15 | import com.google.protobuf.DynamicMessage; 16 | import com.google.protobuf.Message; 17 | import com.google.protobuf.util.JsonFormat; 18 | 19 | import me.dinowernli.junit.TestClass; 20 | import me.dinowernli.grpc.polyglot.io.testing.TestData; 21 | import polyglot.test.TestProto.TestRequest; 22 | 23 | /** Test for the read/write round trip through {@link MessageReader} and {@link MessageWriter}. */ 24 | @TestClass 25 | public class ReadWriteRoundTripTest { 26 | private static final Descriptor DESCRIPTOR = TestRequest.getDescriptor(); 27 | private Path responseFilePath; 28 | 29 | @Before 30 | public void setUp() throws Throwable { 31 | responseFilePath = Files.createTempFile("response", "pb.ascii"); 32 | } 33 | 34 | @Test 35 | public void emptyStream() throws Throwable { 36 | MessageWriter writer = makeWriter(); 37 | writer.onCompleted(); 38 | 39 | ImmutableList results = makeReader().read(); 40 | assertThat(results).isEmpty(); 41 | } 42 | 43 | @Test 44 | public void singleMessage() throws Throwable { 45 | MessageWriter writer = makeWriter(); 46 | writer.onNext(TestData.REQUEST); 47 | writer.onCompleted(); 48 | 49 | ImmutableList results = makeReader().read(); 50 | assertThat(results).containsExactly(TestData.REQUEST); 51 | } 52 | 53 | @Test 54 | public void multipleMessages() throws Throwable { 55 | makeWriter().writeAll(TestData.REQUESTS_MULTI); 56 | ImmutableList results = makeReader().read(); 57 | assertThat(results).containsExactlyElementsIn(TestData.REQUESTS_MULTI); 58 | } 59 | 60 | private MessageReader makeReader() { 61 | return MessageReader.forFile(responseFilePath, DESCRIPTOR); 62 | } 63 | 64 | private MessageWriter makeWriter() throws IOException { 65 | return new MessageWriter( 66 | JsonFormat.printer(), 67 | Output.forStream(new PrintStream(Files.newOutputStream(responseFilePath)))); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/io/testdata/request.pb.ascii: -------------------------------------------------------------------------------- 1 | { 2 | "message": "some message" 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/io/testdata/request_empty.pb.ascii: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/io/testdata/request_with_primitives.pb.ascii: -------------------------------------------------------------------------------- 1 | { 2 | "message": "some message", 3 | "number": 3 4 | } 5 | 6 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/io/testdata/requests_multi.pb.ascii: -------------------------------------------------------------------------------- 1 | { 2 | "message": "message!" 3 | } 4 | 5 | { 6 | "message": "more message!" 7 | } 8 | 9 | { 10 | "message": "even more message" 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/io/testdata/requests_multi_interrupted.pb.ascii: -------------------------------------------------------------------------------- 1 | { 2 | 'message': 'message!' 3 | } 4 | 5 | { 6 | 'message': 'more message!' 7 | } 8 | 9 | { 'message': 'even more message' } 10 | 11 | 12 | { 'message': 'this does not count, happens after two empty lines' } 13 | this won't even parse, but that's ok! -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/io/testdata/response_any.pb.ascii: -------------------------------------------------------------------------------- 1 | { 2 | "any": { 3 | "@type": "type.googleapis.com/polyglot.test.TunnelMessage", 4 | "number": 42 5 | }, 6 | "duration": "5678.000001234s" 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/oauth2/BUILD: -------------------------------------------------------------------------------- 1 | load("@autotest//bzl:autotest.bzl", "auto_java_test") 2 | 3 | auto_java_test( 4 | name = "tests", 5 | size = "small", 6 | srcs = glob(["*.java"]), 7 | deps = [ 8 | "//src/main/java/me/dinowernli/grpc/polyglot/oauth2", 9 | "//src/main/proto:config_java_proto", 10 | "//src/main/proto:config_proto", 11 | "//third_party/google-oauth", 12 | "//third_party/grpc", 13 | "//third_party/guava", 14 | "//third_party/protobuf", 15 | "//third_party/testing", 16 | ], 17 | ) 18 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/oauth2/OauthCredentialsFactoryTest.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.oauth2; 2 | 3 | import static com.google.common.truth.Truth.assertThat; 4 | 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import com.google.auth.oauth2.OAuth2Credentials; 13 | import me.dinowernli.junit.TestClass; 14 | 15 | import polyglot.ConfigProto.OauthConfiguration; 16 | import polyglot.ConfigProto.OauthConfiguration.AccessTokenCredentials; 17 | 18 | /** Unit tests for {@link OauthCredentialsFactory}. */ 19 | @TestClass 20 | public class OauthCredentialsFactoryTest { 21 | private Path tempFile; 22 | 23 | @Before 24 | public void setUp() throws Throwable { 25 | tempFile = Files.createTempFile("token", "txt"); 26 | } 27 | 28 | @After 29 | public void tearDown() throws Throwable { 30 | Files.delete(tempFile); 31 | } 32 | 33 | @Test 34 | public void producesRefreshTokenCredentials() { 35 | OauthCredentialsFactory factory = new OauthCredentialsFactory(OauthConfiguration.newBuilder() 36 | .setRefreshTokenCredentials(OauthConfiguration.RefreshTokenCredentials.newBuilder() 37 | .setRefreshTokenPath(tempFile.toString())) 38 | .build()); 39 | assertThat(factory.getCredentials()).isInstanceOf(RefreshTokenCredentials.class); 40 | } 41 | 42 | @Test 43 | public void producesAccessTokenCredentials() { 44 | OauthCredentialsFactory factory = new OauthCredentialsFactory(OauthConfiguration.newBuilder() 45 | .setAccessTokenCredentials(AccessTokenCredentials.newBuilder() 46 | .setAccessTokenPath(tempFile.toString())) 47 | .build()); 48 | assertThat(factory.getCredentials()).isInstanceOf(OAuth2Credentials.class); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/protobuf/BUILD: -------------------------------------------------------------------------------- 1 | load("@autotest//bzl:autotest.bzl", "auto_java_test") 2 | 3 | auto_java_test( 4 | name = "tests", 5 | size = "small", 6 | srcs = glob(["*.java"]), 7 | data = [ 8 | "//src/main/proto/testing/protobuf:proto_files", 9 | ], 10 | deps = [ 11 | "//src/main/java/me/dinowernli/grpc/polyglot/protobuf", 12 | "//src/main/java/me/dinowernli/grpc/polyglot/testing", 13 | "//src/main/proto:config_java_proto", 14 | "//src/main/proto/testing:test_service_java_proto", 15 | "//src/main/proto/testing/foo:foo_java_proto", 16 | "//third_party/protobuf", 17 | "//third_party/testing", 18 | ], 19 | ) 20 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/protobuf/ProtoMethodNameTest.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.protobuf; 2 | 3 | import static com.google.common.truth.Truth.assertThat; 4 | 5 | import me.dinowernli.junit.TestClass; 6 | 7 | import org.junit.Test; 8 | 9 | /** Unit tests for {@link ProtoMethodName}. */ 10 | @TestClass 11 | public class ProtoMethodNameTest { 12 | @Test 13 | public void parsesCorrectly() { 14 | ProtoMethodName method = ProtoMethodName.parseFullGrpcMethodName("foo.bar.BazService/doStuff"); 15 | assertThat(method.getMethodName()).isEqualTo("doStuff"); 16 | assertThat(method.getPackageName()).isEqualTo("foo.bar"); 17 | assertThat(method.getServiceName()).isEqualTo("BazService"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/protobuf/ProtocInvokerTest.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.protobuf; 2 | 3 | import java.nio.file.Path; 4 | import java.nio.file.Paths; 5 | 6 | import org.junit.Test; 7 | 8 | import me.dinowernli.junit.TestClass; 9 | import me.dinowernli.grpc.polyglot.testing.TestUtils; 10 | import polyglot.ConfigProto.ProtoConfiguration; 11 | 12 | /** Unit tests for {@link ProtocInvoker}. */ 13 | @TestClass 14 | public class ProtocInvokerTest { 15 | private static final Path TEST_PROTO_FILES = 16 | Paths.get(TestUtils.TESTING_PROTO_ROOT.toString(), "protobuf"); 17 | 18 | @Test 19 | public void handlesStandaloneProtoFileWithoutImports() throws Throwable { 20 | ProtocInvoker invoker = ProtocInvoker.forConfig(ProtoConfiguration.newBuilder() 21 | .setProtoDiscoveryRoot(TEST_PROTO_FILES.toString()) 22 | .build()); 23 | invoker.invoke(); 24 | // No crash. 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/me/dinowernli/grpc/polyglot/protobuf/ServiceResolverTest.java: -------------------------------------------------------------------------------- 1 | package me.dinowernli.grpc.polyglot.protobuf; 2 | 3 | import com.google.protobuf.DescriptorProtos.FileDescriptorSet; 4 | import me.dinowernli.junit.TestClass; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import polyglot.test.TestProto; 8 | import polyglot.test.foo.FooProto; 9 | 10 | 11 | /** Unit tests for {@link ServiceResolver}. */ 12 | @TestClass 13 | public class ServiceResolverTest { 14 | private static FileDescriptorSet PROTO_FILE_DESCRIPTORS = FileDescriptorSet.newBuilder() 15 | .addFile(TestProto.getDescriptor().toProto()) 16 | .addFile(FooProto.getDescriptor().toProto()) 17 | .addAllFile(WellKnownTypes.descriptors()) 18 | .build(); 19 | 20 | private ServiceResolver serviceResolver; 21 | 22 | @Before 23 | public void setUp() { 24 | serviceResolver = ServiceResolver.fromFileDescriptorSet(PROTO_FILE_DESCRIPTORS); 25 | } 26 | 27 | @Test(expected = IllegalArgumentException.class) 28 | public void resolveMissingService() { 29 | ProtoMethodName method = ProtoMethodName.parseFullGrpcMethodName("asdf/doesnotexist"); 30 | serviceResolver.resolveServiceMethod(method); 31 | } 32 | 33 | @Test(expected = IllegalArgumentException.class) 34 | public void resolveMissingMethod() { 35 | ProtoMethodName method = ProtoMethodName.parseFullGrpcMethodName("TestService/doesnotexist"); 36 | serviceResolver.resolveServiceMethod(method); 37 | } 38 | 39 | @Test 40 | public void resolveHappyCase() { 41 | serviceResolver.resolveServiceMethod( 42 | ProtoMethodName.parseFullGrpcMethodName("polyglot.test.TestService/TestMethod")); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/tools/build-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ ! -f WORKSPACE ]; then 4 | echo "This must be invoked from the WORKSPACE root" 5 | exit 1 6 | fi 7 | 8 | rm -rf output && mkdir output && chmod +x output 9 | 10 | echo "Building binary..." 11 | bazel build src/main/java/me/dinowernli/grpc/polyglot:polyglot_deploy.jar 12 | cp ./bazel-bin/src/main/java/me/dinowernli/grpc/polyglot/polyglot_deploy.jar ./output/polyglot.jar 13 | echo "Done" 14 | 15 | -------------------------------------------------------------------------------- /src/tools/check-buildifier.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [[ "$1" == "--fix" ]]; then 6 | MODE="-mode=fix" 7 | else 8 | MODE="-mode=check" 9 | fi 10 | 11 | bazel build @com_github_bazelbuild_buildtools//buildifier:buildifier 12 | 13 | BINARY=`find bazel-bin/external/com_github_bazelbuild_buildtools -name buildifier -type f` 14 | RESULT=`find . -name BUILD -or -name WORKSPACE | xargs $BINARY $MODE -v` 15 | 16 | # In theory, buildifier should return a non-zero exit code in check mode if there are errors. From 17 | # staring at the buildifier code, it seems buildifier only returns the correct exit code for the 18 | # *last* scanned file. So the only reliable way to determine if buildifier's checks passed is to test 19 | # for empty output. 20 | if [ -n "$RESULT" ]; then 21 | echo 'Buildifier formatting checks failed. Please run with "--fix"' 22 | exit 1 23 | fi 24 | -------------------------------------------------------------------------------- /src/tools/example/README.md: -------------------------------------------------------------------------------- 1 | # Examples showing Polyglot usage 2 | 3 | Before running any example, make sure you are in the project root directory. 4 | 5 | ### Starting the test server 6 | 7 | In order to run the example Polyglot invocations we need to run example hello server. Use [`run-server.sh`](https://github.com/grpc-ecosystem/polyglot/tree/master/src/tools/example/run-server.sh) to start server: 8 | 9 | `$ bash src/tools/example/run-server.sh` 10 | 11 | ### Example RPC calls 12 | 13 | In order to run a simple rpc call invoke [`call-command-example.sh`](https://github.com/grpc-ecosystem/polyglot/tree/master/src/tools/example/call-command-example.sh): 14 | 15 | `$ bash src/tools/example/call-command-example.sh` 16 | 17 | It uses [simple JSON](https://github.com/grpc-ecosystem/polyglot/tree/master/src/tools/example/request.pb.ascii) as a request. 18 | 19 | In order to run streaming rpc call invoke [`stream-call-command-example.sh`](https://github.com/grpc-ecosystem/polyglot/tree/master/src/tools/example/stream-call-command-example.sh): 20 | 21 | `$ bash src/tools/example/stream-call-command-example.sh` 22 | 23 | It uses [stream of jsons](https://github.com/grpc-ecosystem/polyglot/tree/master/src/tools/example/requests_multi.pb.ascii) as requests. 24 | 25 | ### Example services listing 26 | 27 | In order to simply list the services invoke [`list-command.sh`](https://github.com/grpc-ecosystem/polyglot/tree/master/src/tools/example/list-command.sh): 28 | 29 | `$ bash src/tools/example/list-command.sh` 30 | 31 | In order to list and filter the services invoke [`list-command-with-filter.sh`](https://github.com/grpc-ecosystem/polyglot/tree/master/src/tools/example/list-command-with-filter.sh): 32 | 33 | `$ bash src/tools/example/list-command-with-filter.sh` -------------------------------------------------------------------------------- /src/tools/example/call-command-example-reflection.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | POLYGLOT_PATH="src/main/java/me/dinowernli/grpc/polyglot" 4 | POLYGLOT_BIN="./bazel-bin/${POLYGLOT_PATH}/polyglot" 5 | 6 | if [ ! -f WORKSPACE ]; then 7 | echo "Could not find WORKSPACE file - this must be run from the project root directory" 8 | exit 1 9 | fi 10 | 11 | bazel build ${POLYGLOT_PATH} && \ 12 | cat src/tools/example/request.pb.ascii | ${POLYGLOT_BIN} \ 13 | --config_set_path=config.pb.json \ 14 | call \ 15 | --full_method=polyglot.HelloService/SayHello \ 16 | --endpoint=localhost:12345 \ 17 | --deadline_ms=3000 \ 18 | $@ 19 | -------------------------------------------------------------------------------- /src/tools/example/call-command-example.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | POLYGLOT_PATH="src/main/java/me/dinowernli/grpc/polyglot" 4 | POLYGLOT_BIN="./bazel-bin/${POLYGLOT_PATH}/polyglot" 5 | 6 | if [ ! -f WORKSPACE ]; then 7 | echo "Could not find WORKSPACE file - this must be run from the project root directory" 8 | exit 1 9 | fi 10 | 11 | bazel build ${POLYGLOT_PATH} && \ 12 | cat src/tools/example/request.pb.ascii | ${POLYGLOT_BIN} \ 13 | --proto_discovery_root=./src/main/proto \ 14 | --add_protoc_includes=. \ 15 | --config_set_path=config.pb.json \ 16 | --use_reflection=false \ 17 | call \ 18 | --full_method=polyglot.HelloService/SayHello \ 19 | --endpoint=localhost:12345 \ 20 | --deadline_ms=3000 \ 21 | $@ 22 | -------------------------------------------------------------------------------- /src/tools/example/list-command-with-filter.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | POLYGLOT_PATH="src/main/java/me/dinowernli/grpc/polyglot" 4 | POLYGLOT_BIN="./bazel-bin/${POLYGLOT_PATH}/polyglot" 5 | 6 | if [ ! -f WORKSPACE ]; then 7 | echo "Could not find WORKSPACE file - this must be run from the project root directory" 8 | exit 1 9 | fi 10 | 11 | bazel build ${POLYGLOT_PATH} && ${POLYGLOT_BIN} \ 12 | --proto_discovery_root=./src/main/proto \ 13 | --add_protoc_includes=. \ 14 | list_services \ 15 | --service_filter=HelloService \ 16 | --with_message=true 17 | -------------------------------------------------------------------------------- /src/tools/example/list-command.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | POLYGLOT_PATH="src/main/java/me/dinowernli/grpc/polyglot" 4 | POLYGLOT_BIN="./bazel-bin/${POLYGLOT_PATH}/polyglot" 5 | 6 | if [ ! -f WORKSPACE ]; then 7 | echo "Could not find WORKSPACE file - this must be run from the project root directory" 8 | exit 1 9 | fi 10 | 11 | bazel build ${POLYGLOT_PATH} && ${POLYGLOT_BIN} \ 12 | --proto_discovery_root=./src/main/proto \ 13 | --add_protoc_includes=. \ 14 | list_services 15 | -------------------------------------------------------------------------------- /src/tools/example/request.pb.ascii: -------------------------------------------------------------------------------- 1 | { 2 | 'recipient': 'polyglot' 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/tools/example/requests_multi.pb.ascii: -------------------------------------------------------------------------------- 1 | { 2 | 'recipient': 'polyglot' 3 | } 4 | 5 | { 6 | 'recipient': 'polyglot number 2' 7 | } 8 | 9 | { 10 | 'recipient': 'ultimate polyglot number 3' 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/tools/example/run-server.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ ! -f WORKSPACE ]; then 4 | echo "Could not find WORKSPACE file - this must be run from the project root directory" 5 | exit 1 6 | fi 7 | 8 | bazel build src/main/java/me/dinowernli/grpc/polyglot/server:main && \ 9 | ./bazel-bin/src/main/java/me/dinowernli/grpc/polyglot/server/main 10 | -------------------------------------------------------------------------------- /src/tools/example/stream-call-command-example.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | POLYGLOT_PATH="src/main/java/me/dinowernli/grpc/polyglot" 4 | POLYGLOT_BIN="./bazel-bin/${POLYGLOT_PATH}/polyglot" 5 | 6 | if [ ! -f WORKSPACE ]; then 7 | echo "Could not find WORKSPACE file - this must be run from the project root directory" 8 | exit 1 9 | fi 10 | 11 | bazel build ${POLYGLOT_PATH} && \ 12 | cat src/tools/example/requests_multi.pb.ascii | ${POLYGLOT_BIN} \ 13 | --proto_discovery_root=./src/main/proto \ 14 | --add_protoc_includes=. \ 15 | --config_set_path=config.pb.json \ 16 | call \ 17 | --full_method=polyglot.HelloService/SayHelloBidi \ 18 | --endpoint=localhost:12345 \ 19 | --deadline_ms=3000 20 | -------------------------------------------------------------------------------- /src/tools/generate-eclipse.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import os 4 | import subprocess 5 | from xml.etree.ElementTree import Element, SubElement, tostring 6 | 7 | LIBRARY_JAR_ROOT = os.path.join('bazel-genfiles', 'external') 8 | BUILD_EVERYTHING_COMMAND = ['bazel', 'build', 'src/...'] 9 | PROTO_JAR_ROOT = os.path.join('bazel-bin', 'src', 'main', 'proto') 10 | 11 | def main(): 12 | # Using relative paths for certain things makes our lives much easier, but 13 | # this requires being run from the root of the Bazel workspace. 14 | if not os.path.isfile(os.path.join(os.getcwd(), 'WORKSPACE')): 15 | print('This script must be invoked from the WORKSPACE root.') 16 | return 17 | 18 | # Build the project to make sure all jars are present. 19 | print('Building project...') 20 | subprocess.check_output(BUILD_EVERYTHING_COMMAND) 21 | 22 | print('Generating .classpath file ...') 23 | with open('.classpath', 'w') as file: 24 | file.write(generate_classpath_contents()) 25 | 26 | print('Generating .project file ...') 27 | with open('.project', 'w') as file: 28 | file.write(generate_project_contents()) 29 | 30 | print('Done') 31 | 32 | def generate_project_contents(): 33 | project_name = os.path.basename(os.getcwd()) 34 | print('Using project name: ' + project_name) 35 | return PROJECT_FILE_TEMPLATE % { 'project_name': project_name } 36 | 37 | def generate_classpath_contents(): 38 | jar_paths = discover_jars(LIBRARY_JAR_ROOT) 39 | jar_entries = '\n'.join([CLASSPATH_INDENT + jar_entry(p) for p in jar_paths]) 40 | return CLASSPATH_TEMPLATE % { 'jar_entries': jar_entries } 41 | 42 | def jar_entry(jar_path): 43 | return JAR_CLASSPATH_ENTRY_TEMPLATE % {'path': jar_path} 44 | 45 | def discover_jars(root): 46 | jar_paths = [] 47 | 48 | trees = [LIBRARY_JAR_ROOT, PROTO_JAR_ROOT] 49 | for tree in trees: 50 | for root, _, files in os.walk(tree): 51 | for file in files: 52 | if os.path.splitext(file)[1] == '.jar': 53 | jar_paths.append(os.path.abspath(os.path.join(root, file))) 54 | return jar_paths 55 | 56 | CLASSPATH_TEMPLATE = """ 57 | 58 | 59 | 60 | 61 | %(jar_entries)s 62 | 63 | """ 64 | 65 | CLASSPATH_INDENT = """ """ 66 | 67 | JAR_CLASSPATH_ENTRY_TEMPLATE = '' 68 | 69 | PROJECT_FILE_TEMPLATE = """ 70 | 71 | %(project_name)s 72 | 73 | 74 | 75 | 76 | 77 | 78 | org.eclipse.jdt.core.javanature 79 | 80 | 81 | """ 82 | 83 | if __name__ == '__main__': 84 | main() 85 | -------------------------------------------------------------------------------- /src/tools/generate-intellij.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | import os 4 | import os.path 5 | import subprocess 6 | import shutil 7 | 8 | LIBRARY_JAR_ROOT_GEN = os.path.join('bazel-genfiles', 'external') 9 | LIBRARY_JAR_ROOT_BIN = os.path.join('bazel-bin', 'external') 10 | BUILD_EVERYTHING_COMMAND = ['bazel', 'build', 'src/...'] 11 | PROTO_JAR_ROOT = os.path.join('bazel-bin', 'src', 'main', 'proto') 12 | 13 | PROJECT_NAME = 'Polyglot' 14 | 15 | IDEA_TEMPLATE = 'src/tools/idea-template' 16 | IDEA_TARGET = '.idea' 17 | 18 | # This directory will contain the set of xml files describing the library dependencies 19 | LIBRARY_DIR = os.path.join(IDEA_TARGET, 'libraries') 20 | 21 | # This path must match the module in modules.xml 22 | IML_FILE = 'src/project.iml' 23 | 24 | 25 | def main(): 26 | # Using relative paths for certain things makes our lives much easier, but 27 | # this requires being run from the root of the Bazel workspace. 28 | if not os.path.isfile(os.path.join(os.getcwd(), 'WORKSPACE')): 29 | print('This script must be invoked from the WORKSPACE root.') 30 | return 31 | 32 | # Build the project to make sure all jars are present. 33 | print('Building project...') 34 | subprocess.check_output(BUILD_EVERYTHING_COMMAND) 35 | 36 | # Copying project template... 37 | if os.path.exists(IDEA_TARGET): 38 | print('Removing existing ' + IDEA_TARGET + ' directory...') 39 | shutil.rmtree(IDEA_TARGET) 40 | 41 | print('Copying templates to ' + IDEA_TARGET + ' directory...') 42 | shutil.copytree(IDEA_TEMPLATE, IDEA_TARGET) 43 | 44 | print('Setting project name to ' + PROJECT_NAME) 45 | with open(os.path.join(IDEA_TARGET, '.name'), 'w') as file: 46 | file.write(PROJECT_NAME) 47 | 48 | print('Generating libraries files...') 49 | jars = discover_jars() 50 | os.mkdir(LIBRARY_DIR) 51 | order_entries = [] 52 | for jar in jars: 53 | # Strip off .jar and replace with .xml 54 | jar_name = os.path.basename(jar)[:-4] 55 | jar_path = 'jar://$PROJECT_DIR$' + jar[len(os.getcwd()):] 56 | with open(os.path.join(LIBRARY_DIR, jar_name + '.xml'), 'w') as file: 57 | file.write(LIBRARY_TEMPLATE.format(library_name=jar_name, library_path=jar_path)) 58 | 59 | # Jar entries in the main.iml & test.iml files 60 | order_entries.append( 61 | ' ' 62 | .format(library_name=jar_name)) 63 | 64 | with open(IML_FILE, 'w') as file: 65 | file.write(IML_TEMPLATE.format(order_entries="\n".join(order_entries))) 66 | 67 | print('Done') 68 | 69 | 70 | def discover_jars(): 71 | jar_paths = [] 72 | 73 | trees = [LIBRARY_JAR_ROOT_GEN, PROTO_JAR_ROOT, LIBRARY_JAR_ROOT_BIN] 74 | for tree in trees: 75 | for jar_root, _, files in os.walk(tree): 76 | for file in files: 77 | if os.path.splitext(file)[1] == '.jar': 78 | jar_paths.append(os.path.abspath(os.path.join(jar_root, file))) 79 | return jar_paths 80 | 81 | LIBRARY_TEMPLATE = """ 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | """ 91 | 92 | IML_TEMPLATE = """ 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | {order_entries} 103 | 104 | 105 | """ 106 | 107 | if __name__ == '__main__': 108 | main() 109 | -------------------------------------------------------------------------------- /src/tools/idea-template/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/tools/idea-template/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/tools/idea-template/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 1.8 28 | 29 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/tools/idea-template/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/tools/idea-template/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /third_party/google-oauth/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "google-oauth", 5 | licenses = ["permissive"], 6 | exports = [ 7 | "@maven//:com_google_auth_google_auth_library_credentials", 8 | "@maven//:com_google_auth_google_auth_library_oauth2_http", 9 | "@maven//:com_google_http_client_google_http_client", 10 | "@maven//:com_google_http_client_google_http_client_jackson2", 11 | "@maven//:com_google_oauth_client_google_oauth_client", 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /third_party/grpc/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | load("@io_grpc_grpc_java//:java_grpc_library.bzl", "java_grpc_library") 4 | 5 | # TODO(dino): Couldn't find a grpc java build target for the common grpc 6 | # protos. This is copied from the grpc repo itself. 7 | java_grpc_library( 8 | name = "_reflection_java_grpc", 9 | srcs = ["@io_grpc_grpc_proto//:reflection_proto_deprecated"], 10 | visibility = ["//visibility:private"], 11 | deps = ["@io_grpc_grpc_proto//:reflection_java_proto_deprecated"], 12 | ) 13 | 14 | java_library( 15 | name = "grpc", 16 | licenses = ["permissive"], 17 | exports = [ 18 | ":_reflection_java_grpc", 19 | "@io_grpc_grpc_java//api", 20 | "@io_grpc_grpc_java//auth", 21 | "@io_grpc_grpc_java//core", 22 | "@io_grpc_grpc_java//netty", 23 | "@io_grpc_grpc_java//services:reflection", 24 | "@io_grpc_grpc_java//stub", 25 | "@io_grpc_grpc_proto//:reflection_java_proto_deprecated", 26 | "@io_grpc_grpc_proto//:reflection_proto_deprecated", 27 | ], 28 | ) 29 | 30 | java_library( 31 | name = "grpc_testing", 32 | testonly = 1, 33 | licenses = ["permissive"], 34 | exports = [ 35 | "@io_grpc_grpc_java//testing", 36 | ], 37 | ) 38 | -------------------------------------------------------------------------------- /third_party/grpc/grpc.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015-2016, Google Inc. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Google Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /third_party/guava/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "guava", 5 | licenses = ["permissive"], 6 | exports = [ 7 | "@maven//:com_google_guava_guava", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /third_party/jackson-core/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "jackson-core", 5 | licenses = ["permissive"], 6 | exports = [ 7 | "@maven//:com_fasterxml_jackson_core_jackson_core", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /third_party/jcommander/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "jcommander", 5 | licenses = ["permissive"], 6 | exports = [ 7 | "@maven//:com_beust_jcommander", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /third_party/logging/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "logging-api", 5 | licenses = ["permissive"], 6 | exports = [ 7 | "@maven//:org_slf4j_jul_to_slf4j", 8 | "@maven//:org_slf4j_slf4j_api", 9 | ], 10 | ) 11 | 12 | java_library( 13 | name = "logging-impl-stdout", 14 | licenses = ["permissive"], 15 | exports = [ 16 | "@maven//:org_slf4j_slf4j_simple", 17 | ], 18 | ) 19 | -------------------------------------------------------------------------------- /third_party/logging/slf4j.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2004-2013 QOS.ch 2 | All rights reserved. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /third_party/netty/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "netty", 5 | licenses = ["permissive"], 6 | exports = [ 7 | "@maven//:io_netty_netty_buffer", 8 | "@maven//:io_netty_netty_codec", 9 | "@maven//:io_netty_netty_codec_http2", 10 | "@maven//:io_netty_netty_common", 11 | "@maven//:io_netty_netty_handler", 12 | "@maven//:io_netty_netty_handler_proxy", 13 | "@maven//:io_netty_netty_resolver", 14 | "@maven//:io_netty_netty_tcnative_boringssl_static", 15 | "@maven//:io_netty_netty_transport", 16 | ], 17 | ) 18 | -------------------------------------------------------------------------------- /third_party/protobuf/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "protobuf", 5 | licenses = ["permissive"], 6 | exports = [ 7 | "@com_google_protobuf//:protobuf_java", 8 | "@com_google_protobuf//:protobuf_java_util", 9 | "@maven//:com_google_code_gson_gson", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /third_party/protobuf/protobuf.LICENSE: -------------------------------------------------------------------------------- 1 | This license applies to all parts of Protocol Buffers except the following: 2 | 3 | - Atomicops support for generic gcc, located in 4 | src/google/protobuf/stubs/atomicops_internals_generic_gcc.h. 5 | This file is copyrighted by Red Hat Inc. 6 | 7 | - Atomicops support for AIX/POWER, located in 8 | src/google/protobuf/stubs/atomicops_internals_power.h. 9 | This file is copyrighted by Bloomberg Finance LP. 10 | 11 | Copyright 2014, Google Inc. All rights reserved. 12 | 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are 15 | met: 16 | 17 | * Redistributions of source code must retain the above copyright 18 | notice, this list of conditions and the following disclaimer. 19 | * Redistributions in binary form must reproduce the above 20 | copyright notice, this list of conditions and the following disclaimer 21 | in the documentation and/or other materials provided with the 22 | distribution. 23 | * Neither the name of Google Inc. nor the names of its 24 | contributors may be used to endorse or promote products derived from 25 | this software without specific prior written permission. 26 | 27 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 28 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 29 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 30 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 31 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 32 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 33 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 34 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 35 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 36 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 37 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 38 | 39 | Code generated by the Protocol Buffer compiler is owned by the owner 40 | of the input file used when generating it. This code is not 41 | standalone and requires a support library to be linked with it. This 42 | support library is itself covered by the above license. 43 | -------------------------------------------------------------------------------- /third_party/protoc-jar/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "protoc-jar", 5 | licenses = ["permissive"], 6 | exports = [ 7 | "@maven//:com_github_os72_protoc_jar", 8 | ], 9 | ) 10 | -------------------------------------------------------------------------------- /third_party/testing/BUILD: -------------------------------------------------------------------------------- 1 | package(default_visibility = ["//visibility:public"]) 2 | 3 | java_library( 4 | name = "testing", 5 | licenses = ["permissive"], 6 | exports = [ 7 | "@maven//:com_google_truth_truth", 8 | "@maven//:junit_junit", 9 | "@maven//:org_mockito_mockito_all", 10 | ], 11 | ) 12 | -------------------------------------------------------------------------------- /third_party/testing/mockito.LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2007 Mockito contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | --------------------------------------------------------------------------------