Initial commit of sample java grpc server
diff --git a/BookingService.java b/BookingService.java new file mode 100644 index 0000000..5124027 --- /dev/null +++ b/BookingService.java
@@ -0,0 +1,472 @@ +/* + * Copyright 2018, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package ext.maps.booking.partner.v2; + +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.AvailabilityUpdate; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.Booking; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.BookingStatus; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.CheckAvailabilityRequest; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.CheckAvailabilityResponse; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.CheckAvailabilityResponse.DurationRequirement; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.CreateBookingRequest; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.CreateBookingResponse; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.GetBookingStatusRequest; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.GetBookingStatusResponse; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.ListBookingsRequest; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.ListBookingsResponse; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.PrepaymentStatus; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.Slot; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.UpdateBookingRequest; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.UpdateBookingResponse; +import ext.maps.booking.partner.v2.ApitemplateV2BookingService.UserPaymentOption; +import grpc.health.v1.Health.HealthImpl; +import io.grpc.Grpc; +import io.grpc.Metadata; +import io.grpc.Server; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import io.grpc.ServerInterceptors; +import io.grpc.Status; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.NettyServerBuilder; +import io.grpc.stub.StreamObserver; +import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.SslProvider; +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Logger; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import org.bouncycastle.asn1.x500.RDN; +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; + +/** Booking Server with TLS enabled */ +public class BookingService { + private static final Logger logger = Logger.getLogger(BookingService.class.getName()); + + private Server server; + + private final String host; + private final int port; + private final String certChainFilePath; + private final String privateKeyFilePath; + private final String trustedrootsFilePath; + private final Set<String> acceptedCNs; + + public BookingService( + String host, + int port, + String certChainFilePath, + String privateKeyFilePath, + String trustedrootsFilePath, + Set<String> acceptedCNs) { + this.host = host; + this.port = port; + this.certChainFilePath = certChainFilePath; + this.privateKeyFilePath = privateKeyFilePath; + this.trustedrootsFilePath = trustedrootsFilePath; + this.acceptedCNs = acceptedCNs; + } + + private SslContextBuilder getSslContextBuilder() { + SslContextBuilder sslClientContextBuilder = + SslContextBuilder.forServer(new File(certChainFilePath), new File(privateKeyFilePath)); + if (trustedrootsFilePath != null) { + sslClientContextBuilder.trustManager(new File(trustedrootsFilePath)); + sslClientContextBuilder.clientAuth(ClientAuth.OPTIONAL); + } + return GrpcSslContexts.configure(sslClientContextBuilder, SslProvider.OPENSSL); + } + + private void start() throws IOException { + + server = + NettyServerBuilder.forAddress(new InetSocketAddress(host, port)) + .addService(ServerInterceptors.intercept(new BookingServiceImpl(), new MyInterceptor())) + .addService(new HealthImpl()) + .sslContext(getSslContextBuilder().build()) + .build() + .start(); + + logger.info("Booking Server started, listening on " + port); + Runtime.getRuntime() + .addShutdownHook( + new Thread() { + @Override + public void run() { + // Use stderr here since the logger may have been reset by its JVM shutdown hook. + System.err.println("*** shutting down gRPC server since JVM is shutting down"); + BookingService.this.stop(); + System.err.println("*** server shut down"); + } + }); + } + + private void stop() { + if (server != null) { + server.shutdown(); + } + } + + /** Await termination on the main thread since the grpc library uses daemon threads. */ + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + /** Main launches the server. */ + public static void main(String[] args) throws IOException, InterruptedException { + // Set the accepted cns of the server + Set<String> acceptedCNs = new HashSet<String>(); + acceptedCNs.add("mapsbooking.businesslink-3.net"); + + // TODO(partner): override your host name, port, server_cert_chain file path, + // private_key file path and trusted_client_roots file path here to build the server + final BookingService server = + new BookingService( + "localhost", + 8443, + "src/main/certificates/server_cert_chain.pem", + "src/main/certificates/server_private_key.pem", + "src/main/certificates/trusted_client_roots.pem", + acceptedCNs); + server.start(); + server.blockUntilShutdown(); + } + + /** + * Implement the server interceptor to do peer certificate validation that checks for a specific + * CN in the subject name, if the CN check fails, the connection will be closed with + * "PERMISSION_DENIED" status message. + */ + private class MyInterceptor implements ServerInterceptor { + @Override + public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall( + ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { + SSLSession sslSession = call.getAttributes().get(Grpc.TRANSPORT_ATTR_SSL_SESSION); + try { + Certificate[] peerCerts = sslSession.getPeerCertificates(); + if (peerCerts[0] instanceof java.security.cert.X509Certificate) { + X509Certificate peerCert = (X509Certificate) peerCerts[0]; + X500Name x500Name = new JcaX509CertificateHolder(peerCert).getSubject(); + RDN cn = x500Name.getRDNs(BCStyle.CN)[0]; + String peerCN = IETFUtils.valueToString(cn.getFirst().getValue()); + if (!acceptedCNs.contains(peerCN)) { + throw new SSLPeerUnverifiedException("Invalid CN"); + } + } else { + throw new SSLPeerUnverifiedException("Invalid certificate type"); + } + } catch (SSLPeerUnverifiedException se) { + System.err.println(se.getMessage()); + call.close(Status.PERMISSION_DENIED, headers); + return new ServerCall.Listener<ReqT>() {}; + } catch (CertificateEncodingException ce) { + System.err.println(ce.getMessage()); + call.close(Status.PERMISSION_DENIED, headers); + return new ServerCall.Listener<ReqT>() {}; + } + return next.startCall(call, headers); + } + } + + /** The implementation of Booking Service. */ + static class BookingServiceImpl extends BookingServiceGrpc.BookingServiceImplBase { + @Override + public void checkAvailability( + CheckAvailabilityRequest request, + StreamObserver<CheckAvailabilityResponse> responseObserver) { + + Slot requestedSlot = request.getSlot(); + + // TODO(partner): perform availability check + // + // Error conditions: response with corresponding canonical gRPC error code + // example: + // responseObserver.onError( + // new StatusException( + // io.grpc.Status.INVALID_ARGUMENT.withDescription("Invalid merchant id") + // ) + // ); + // + // Happy path: implement the helper functions, populate the response + + CheckAvailabilityResponse response = + CheckAvailabilityResponse.newBuilder() + .setSlot(requestedSlot) + .setCountAvailable(getAvailableSpots(requestedSlot)) + .setDurationRequirement(getDurationRequirement(requestedSlot)) // optional + .setAvailabilityUpdate(getAvailabilityUpdate(requestedSlot)) // optional + .build(); + + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + private int getAvailableSpots(Slot slot) { + // TODO(partner): get the available spots for the given slot + // + // int available_spots = ... + // return available_spots; + + throw new UnsupportedOperationException("Not implemented yet"); + } + + private DurationRequirement getDurationRequirement(Slot slot) { + // TODO(partner): optional, get the duration requirment for the given slot + // + // DurationRequirement durationRequirement = ... + // return durationRequirement; + + throw new UnsupportedOperationException("Not implemented yet"); + } + + private AvailabilityUpdate getAvailabilityUpdate(Slot slot) { + // TODO(partner): optional, get the availability update for the given slot + // + // AvailabilityUpdate availabilityUpdate = ... + // return availabilityUpdate; + + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void createBooking( + CreateBookingRequest request, StreamObserver<CreateBookingResponse> responseObserver) { + + // TODO(partner): create a booking based on the request + // + // Error conditions: + // unexpected error -> response with corresponding canonical gRPC error code + // example: + // responseObserver.onError( + // new StatusException( + // io.grpc.Status.INVALID_ARGUMENT.withDescription("Invalid merchant id") + // ) + // ); + // business logic error -> set the BookingFailure in the response + // example: + // BookingFailure bookingFailure = BookingFailure.newBuilder() + // .setCause(Cause.SLOT_UNAVAILABLE) + // .setDescription("slot is not available now") + // .build(); + // CreateBookingResponse response = CreateBookingResponse.newBuilder() + // .setBookingFailure(bookingFailure)... + // + // Happy path: implement the helper functions, populate the response + + Booking booking = createSuccessfulBooking(request); + + CreateBookingResponse response = + CreateBookingResponse.newBuilder() + .setBooking(booking) + .setUserPaymentOption(getUserPaymentOption(booking)) // optional + .build(); + + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + private Booking createSuccessfulBooking(CreateBookingRequest request) { + // TODO(partner): create a booking based on the create booking request and return the + // created booking instance + // + // Booking created = ...(e.g. + // Booking.newBuilder() + // .setBookingId("sampleBooking1") + // .setSlot(request.getSlot()) + // .setUserInformation(request.getUserInformation()) + // .setStatus(BookingStatus.CONFIRMED) + // .setPaymentInformation(request.getPaymentInformation()) + // .build() + // ) + // return created; + + throw new UnsupportedOperationException("Not implemented yet"); + } + + private UserPaymentOption getUserPaymentOption(Booking booking) { + // TODO(partner): optional, get the user payment option for the given booking + // + // UserPaymentOption paymentOption = ... + // return paymentOption; + + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void updateBooking( + UpdateBookingRequest request, StreamObserver<UpdateBookingResponse> responseObserver) { + + // TODO(partner): update the booking based on the request + // + // Error conditions: + // unexpected error -> response with corresponding canonical gRPC error code + // example: + // responseObserver.onError( + // new StatusException( + // io.grpc.Status.NOT_FOUND.withDescription("Unknown booking id") + // ) + // ); + // business logic error -> set the BookingFailure in the response + // example: + // BookingFailure bookingFailure = BookingFailure.newBuilder() + // .setCause(Cause.SLOT_UNAVAILABLE) + // .setDescription("slot is not available now") + // .build(); + // CreateBookingResponse response = CreateBookingResponse.newBuilder() + // .setBookingFailure(bookingFailure)... + // + // Happy path: implement the helper functions, populate the response + + Booking booking = updateSuccessfulBooking(request); + + UpdateBookingResponse response = + UpdateBookingResponse.newBuilder() + .setBooking(booking) + .setUserPaymentOption(getUserPaymentOption(booking)) // option + .build(); + + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + private Booking updateSuccessfulBooking(UpdateBookingRequest request) { + // TODO(partner): look up the booking with bookingId, update fields according to + // updateMask and return the updated booking instance + // + // String bookingId = request.getBooking().getBookingId(); + // FieldMask updateMask = request.getUpdateMask(); + // Booking updated = ... + // return updated; + + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void getBookingStatus( + GetBookingStatusRequest request, + StreamObserver<GetBookingStatusResponse> responseObserver) { + + String bookingId = request.getBookingId(); + + // TODO(partner): look up the booking status and prepayment status with bookingId + // + // Error conditions: + // unexpected error -> response with corresponding canonical gRPC error code + // example: + // responseObserver.onError( + // new StatusException( + // io.grpc.Status.NOT_FOUND.withDescription("Unknown booking id") + // ) + // ); + // + // Happy path: implement the helper functions, populate the response + + GetBookingStatusResponse response = + GetBookingStatusResponse.newBuilder() + .setBookingId(bookingId) + .setBookingStatus(getBookingStatus(bookingId)) + .setPrepaymentStatus(getPrepaymentStatus(bookingId)) + .build(); + + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + private BookingStatus getBookingStatus(String bookingId) { + // TODO(partner): get the booking status for the given bookingId + // + // BookingStatus bookingStatus = ... + // return bookingStatus; + + throw new UnsupportedOperationException("Not implemented yet"); + } + + private PrepaymentStatus getPrepaymentStatus(String bookingId) { + // TODO(partner): get the prepayment status for the given bookingId + // + // Prepayment prepaymentStatus = ... + // return prepaymentStatus; + + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void listBookings( + ListBookingsRequest request, StreamObserver<ListBookingsResponse> responseObserver) { + + String userId = request.getUserId(); + + // TODO(partner): look up all bookings with the given userId + // + // Error conditions: + // unexpected error -> response with corresponding canonical gRPC error code + // example: + // responseObserver.onError( + // new StatusException( + // io.grpc.Status.NOT_FOUND.withDescription("Unknown user id") + // ) + // ); + // + // Happy path: implement the helper function, populate the response + + ListBookingsResponse response = + ListBookingsResponse.newBuilder().addAllBookings(getListBooking(userId)).build(); + + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + private List<Booking> getListBooking(String userId) { + // TODO(partner): get the bookings list for the given userId + // + // List<Booking> bookings = ... + // return bookings; + + throw new UnsupportedOperationException("Not implemented yet"); + } + } +}
diff --git a/Health.java b/Health.java new file mode 100644 index 0000000..81788b6 --- /dev/null +++ b/Health.java
@@ -0,0 +1,73 @@ +/* + * Copyright 2018, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package grpc.health.v1; + +import grpc.health.v1.HealthOuterClass.HealthCheckRequest; +import grpc.health.v1.HealthOuterClass.HealthCheckResponse; +import grpc.health.v1.HealthOuterClass.HealthCheckResponse.ServingStatus; +import io.grpc.Status; +import io.grpc.StatusException; +import io.grpc.stub.StreamObserver; +import java.util.logging.Logger; + +/** gRPC Health Checking Protocol */ +public class Health { + private static final Logger logger = Logger.getLogger(Health.class.getName()); + private static final String ACCEPTED_SERVICE = "ext.maps.booking.partner.v2.BookingService"; + + /** The implementation of Health Checking Service. */ + public static class HealthImpl extends HealthGrpc.HealthImplBase { + @Override + public void check( + HealthCheckRequest request, StreamObserver<HealthCheckResponse> responseObserver) { + String service = request.getService(); + if (!service.equals(ACCEPTED_SERVICE)) { + responseObserver.onError( + new StatusException(Status.NOT_FOUND.withDescription("Unknown service"))); + } else { + HealthCheckResponse response = + HealthCheckResponse.newBuilder().setStatus(getServingStatus()).build(); + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + } + + private ServingStatus getServingStatus() { + // TODO(partner): check the health status of the service + // + // ServingStatus status = ... (e.g. ServingStatus.SERVING) + // return status; + + throw new UnsupportedOperationException("Not implemented yet"); + } + } +}
diff --git a/README.md b/README.md new file mode 100644 index 0000000..6fefbd1 --- /dev/null +++ b/README.md
@@ -0,0 +1,163 @@ +# gRPC Server Skeleton for Java + +### Get Started + +1. Create a java gradle project (grpc-booking-service), under the src/main, + create a 'proto' directory. + +2. Download the [booking service + definition](https://developers.google.com/maps-booking/download/apitemplate.v2.booking_service.proto) + and [health checking + protocol](https://github.com/grpc/grpc/blob/master/src/proto/grpc/health/v1/health.proto), + place them under src/main/proto. These files define the gRPC methods and + messages for the Reserve with Google API and Health Check. + +3. Update the ***build.gradle*** file, add dependencies and the protobuf plugin + for Gradle. The introduction and guide for protobuf-gradle-plugin can be + found [here](https://github.com/google/protobuf-gradle-plugin). + + apply plugin: 'java' + apply plugin: 'com.google.protobuf' + + repositories { + mavenCentral() + } + + // updating the version in our release process. + def grpcVersion = '1.8.0' // CURRENT_GRPC_VERSION + def nettyTcNativeVersion = '2.0.7.Final' + + dependencies { + compile "com.google.api.grpc:proto-google-common-protos:0.1.9" + compile "io.grpc:grpc-netty:${grpcVersion}" + compile "io.grpc:grpc-protobuf:${grpcVersion}" + compile "io.grpc:grpc-stub:${grpcVersion}" + compile "io.netty:netty-tcnative-boringssl-static:${nettyTcNativeVersion}" + compile "org.bouncycastle:bcmail-jdk15:1.46" + + testCompile "io.grpc:grpc-testing:${grpcVersion}" + testCompile "junit:junit:4.12" + testCompile "org.mockito:mockito-core:1.9.5" + } + + buildscript { + repositories { + mavenCentral() + } + dependencies { + // ASSUMES GRADLE 2.12 OR HIGHER. Use plugin version 0.7.5 with earlier + // gradle versions + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.1' + } + } + + protobuf { + protoc { + artifact = 'com.google.protobuf:protoc:3.4.0' + } + plugins { + grpc { + artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" + } + } + generateProtoTasks { + all()*.plugins { + grpc {} + } + } + } + + // Inform IDEs like IntelliJ IDEA, Eclipse or NetBeans about the generated code. + sourceSets { + main { + java { + srcDirs 'build/generated/source/proto/main/grpc' + srcDirs 'build/generated/source/proto/main/java' + } + } + } + + // Generate IntelliJ IDEA's .idea & .iml project files + apply plugin: 'idea' + + // Provide convenience executables for trying out the examples. + apply plugin: 'application' + + startScripts.enabled = false + +4. Run the following command to build the library and auto-generate code from + protoc build plugin: + + ./gradlew build + +5. To enable TLS on the server, under ***src/main/certificates/*** the + following files are required: + + * ***server_cert_chain.pem*** your server certificate chain in PEM format + * ***server_private_key.pem*** your private key for the server certificate + chain, needs to be the PKCS#8 private key + * ***trusted_client_roots.pem*** the root certificates that are trusted + when authenticating clients, you can choose to obtain this set of + trusted roots from an authority like + [Mozilla](https://wiki.mozilla.org/CA:IncludedCAs), or install the [set + of roots currently recommended by the Google Internet Authority + G2](https://pki.google.com/roots.pem). In the latter case, you may have + to manually update the root certificate at times + +6. Retrieve the sample code, place the **BookingService.java** under + *src/main/java/ext/maps/booking/partner/v2*, place **Health.java** under + *src/main/java/grpc/health/v1*. In both files, follow the ***TODOs*** to + complete your implementations. + +7. Update the gradle.build file to specify the generation of server executable + by adding the following code: + + task bookingService(type: CreateStartScripts) { + mainClassName = 'ext.maps.booking.partner.v2.BookingService' + applicationName = 'booking-service' + outputDir = new File(project.buildDir, 'tmp') + classpath = jar.outputs.files + project.configurations.runtime + } + + applicationDistribution.into('bin') { + from(bookingService) + fileMode = 0755 + } + +8. Compile the server: + + ./gradlew installDist + +9. Run the booking server: + + ./build/install/grpc-booking-service/bin/booking-service + +### Final Directory Structure + + src + |---main + |---certificates + |---server_cert_chain.pem + |---server_private_key.pem + |---trusted_client_roots.pem + |---java + |---ext.maps.booking.partner.v2.BookingService.java + |---grpc.health.v1.Health.java + |---proto + |---booking_service.proto + |---health.proto + |---test + +### Other Reference + +* For other building tools, visit the + [gRPC-java](https://grpc.io/docs/quickstart/java.html) and download the + example, check grpc-java/examples. + + git clone -b v1.9.0 https://github.com/grpc/grpc-java + +* [gRPC java Transport Security + (TLS)](https://github.com/grpc/grpc-java/blob/master/SECURITY.md). + +* [gRPC API + V2](https://developers.google.com/maps-booking/reference/grpc-api-v2/slot-specification).