| /* |
| * 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"); |
| } |
| } |
| } |