diff --git a/endpoints/bookstore-grpc/Dockerfile b/endpoints/bookstore-grpc/Dockerfile new file mode 100644 index 00000000000..99be2e6d0d9 --- /dev/null +++ b/endpoints/bookstore-grpc/Dockerfile @@ -0,0 +1,15 @@ +# https://github.com/GoogleCloudPlatform/openjdk-runtime +FROM gcr.io/google_appengine/openjdk8 + +RUN echo 'deb http://httpredir.debian.org/debian jessie-backports main' > /etc/apt/sources.list.d/jessie-backports.list \ + && apt-get update \ + && apt-get install --no-install-recommends -y -q ca-certificates \ + && apt-get -y -q upgrade \ + && apt-get install --no-install-recommends -y openjdk-8-jre-headless \ + && rm -rf /var/lib/apt/lists/* + +ADD ./server/build/libs/server.jar /bookstore/server.jar + +EXPOSE 8000 + +ENTRYPOINT ["java", "-jar", "/bookstore/server.jar"] diff --git a/endpoints/bookstore-grpc/README.md b/endpoints/bookstore-grpc/README.md new file mode 100644 index 00000000000..46be153ce16 --- /dev/null +++ b/endpoints/bookstore-grpc/README.md @@ -0,0 +1,20 @@ +# Google Cloud Endpoints Bookstore App in Java + +## Prerequisites + +* [Java 8](http://openjdk.java.net/install/) +* [Docker](https://www.docker.com/products/docker) + +## Building and Running the Server + +The Java Bookstore gRPC example is built using Gradle: + + ./gradlew build + +To run the Java server and client locally: + + # Start the server (listens on port 8000 by default) + java -jar ./server/build/libs/server.jar + + # Run the client (connects to localhost:8000 by default) + java -jar ./client/build/libs/client.jar diff --git a/endpoints/bookstore-grpc/api/build.gradle b/endpoints/bookstore-grpc/api/build.gradle new file mode 100644 index 00000000000..f8331ff246b --- /dev/null +++ b/endpoints/bookstore-grpc/api/build.gradle @@ -0,0 +1,54 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +apply plugin: 'java' +apply plugin: 'com.google.protobuf' + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0' + + } +} + +dependencies { + repositories { + mavenCentral() + } + compile 'io.grpc:grpc-netty:1.0.1' + compile 'io.grpc:grpc-protobuf:1.0.1' + compile 'io.grpc:grpc-stub:1.0.1' +} + +protobuf { + protoc { + artifact = 'com.google.protobuf:protoc:3.0.2' + } + + plugins { + grpc { + artifact = 'io.grpc:protoc-gen-grpc-java:1.0.1' + } + } + generateProtoTasks { + all()*.plugins { + grpc {} + } + } +} diff --git a/endpoints/bookstore-grpc/api/src/main/proto/bookstore.proto b/endpoints/bookstore-grpc/api/src/main/proto/bookstore.proto new file mode 100644 index 00000000000..c3f685f1a0c --- /dev/null +++ b/endpoints/bookstore-grpc/api/src/main/proto/bookstore.proto @@ -0,0 +1,126 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +syntax = "proto3"; + +package endpoints.examples.bookstore; + +option java_multiple_files = true; +option java_outer_classname = "BookstoreProto"; +option java_package = "com.google.endpoints.examples.bookstore"; + + +import "google/protobuf/empty.proto"; + +// A simple Bookstore API. +// +// The API manages shelves and books resources. Shelves contain books. +service Bookstore { + // Returns a list of all shelves in the bookstore. + rpc ListShelves(google.protobuf.Empty) returns (ListShelvesResponse) {} + // Creates a new shelf in the bookstore. + rpc CreateShelf(CreateShelfRequest) returns (Shelf) {} + // Returns a specific bookstore shelf. + rpc GetShelf(GetShelfRequest) returns (Shelf) {} + // Deletes a shelf, including all books that are stored on the shelf. + rpc DeleteShelf(DeleteShelfRequest) returns (google.protobuf.Empty) {} + // Returns a list of books on a shelf. + rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {} + // Creates a new book. + rpc CreateBook(CreateBookRequest) returns (Book) {} + // Returns a specific book. + rpc GetBook(GetBookRequest) returns (Book) {} + // Deletes a book from a shelf. + rpc DeleteBook(DeleteBookRequest) returns (google.protobuf.Empty) {} +} + +// A shelf resource. +message Shelf { + // A unique shelf id. + int64 id = 1; + // A theme of the shelf (fiction, poetry, etc). + string theme = 2; +} + +// A book resource. +message Book { + // A unique book id. + int64 id = 1; + // An author of the book. + string author = 2; + // A book title. + string title = 3; +} + +// Response to ListShelves call. +message ListShelvesResponse { + // Shelves in the bookstore. + repeated Shelf shelves = 1; +} + +// Request message for CreateShelf method. +message CreateShelfRequest { + // The shelf resource to create. + Shelf shelf = 1; +} + +// Request message for GetShelf method. +message GetShelfRequest { + // The ID of the shelf resource to retrieve. + int64 shelf = 1; +} + +// Request message for DeleteShelf method. +message DeleteShelfRequest { + // The ID of the shelf to delete. + int64 shelf = 1; +} + +// Request message for ListBooks method. +message ListBooksRequest { + // ID of the shelf which books to list. + int64 shelf = 1; +} + +// Response message to ListBooks method. +message ListBooksResponse { + // The books on the shelf. + repeated Book books = 1; +} + +// Request message for CreateBook method. +message CreateBookRequest { + // The ID of the shelf on which to create a book. + int64 shelf = 1; + // A book resource to create on the shelf. + Book book = 2; +} + +// Request message for GetBook method. +message GetBookRequest { + // The ID of the shelf from which to retrieve a book. + int64 shelf = 1; + // The ID of the book to retrieve. + int64 book = 2; +} + +// Request message for DeleteBook method. +message DeleteBookRequest { + // The ID of the shelf from which to delete a book. + int64 shelf = 1; + // The ID of the book to delete. + int64 book = 2; +} diff --git a/endpoints/bookstore-grpc/build.gradle b/endpoints/bookstore-grpc/build.gradle new file mode 100644 index 00000000000..af180a559db --- /dev/null +++ b/endpoints/bookstore-grpc/build.gradle @@ -0,0 +1,7 @@ +subprojects { + apply plugin: 'java' + + repositories { + mavenCentral() + } +} diff --git a/endpoints/bookstore-grpc/client/build.gradle b/endpoints/bookstore-grpc/client/build.gradle new file mode 100644 index 00000000000..2bc4d2d74b2 --- /dev/null +++ b/endpoints/bookstore-grpc/client/build.gradle @@ -0,0 +1,33 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +apply plugin: 'application' + +mainClassName = "com.google.endpoints.examples.bookstore.BookstoreClient" + +jar { + manifest { + attributes "Main-Class": "$mainClassName" + } + from { + configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } + } +} + +dependencies { + compile project(':api') + compile 'commons-cli:commons-cli:1.3' +} diff --git a/endpoints/bookstore-grpc/client/src/main/java/com/google/endpoints/examples/bookstore/BookstoreClient.java b/endpoints/bookstore-grpc/client/src/main/java/com/google/endpoints/examples/bookstore/BookstoreClient.java new file mode 100644 index 00000000000..cac08cea444 --- /dev/null +++ b/endpoints/bookstore-grpc/client/src/main/java/com/google/endpoints/examples/bookstore/BookstoreClient.java @@ -0,0 +1,238 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.google.endpoints.examples.bookstore; + +import com.google.protobuf.Empty; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ClientInterceptors; +import io.grpc.ForwardingClientCall; +import io.grpc.ManagedChannelBuilder; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; + +import java.util.logging.Logger; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +/** + * A client application which calls the Bookstore API over gRPC. + */ +public final class BookstoreClient { + + private static final String DEFAULT_ADDRESS = "localhost:8000"; + + public static void main(String[] args) throws Exception { + Options options = createOptions(); + CommandLineParser parser = new DefaultParser(); + CommandLine params; + try { + params = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Invalid command line: " + e.getMessage()); + printUsage(options); + return; + } + + String address = params.getOptionValue("bookstore", DEFAULT_ADDRESS); + String apiKey = params.getOptionValue("api_key"); + String authToken = params.getOptionValue("auth_token"); + String operation = params.getOptionValue("operation", "list"); + + // Create gRPC stub. + BookstoreGrpc.BookstoreBlockingStub bookstore = createBookstoreStub( + address, apiKey, authToken); + + if ("list".equals(operation)) { + listShelves(bookstore); + } else if ("create".equals(operation)) { + createShelf(bookstore); + } else if ("enumerate".equals(operation)) { + enumerate(bookstore); + } + } + + /** + * Lists all shelves in the bookstore. + * + * @param bookstore a client stub to call Bookstore service. + */ + static void listShelves(BookstoreGrpc.BookstoreBlockingStub bookstore) { + ListShelvesResponse shelves = bookstore.listShelves(Empty.getDefaultInstance()); + System.out.println(shelves); + } + + /** + * Creates a new shelf in the bookstore. + * + * @param bookstore a client stub to call Bookstore service. + */ + static void createShelf(BookstoreGrpc.BookstoreBlockingStub bookstore) { + CreateShelfRequest.Builder builder = CreateShelfRequest.newBuilder(); + builder.getShelfBuilder().setTheme("Computers"); + Shelf shelf = bookstore.createShelf(builder.build()); + System.out.println(shelf); + } + + /** + * Enumerates all books in the bookstore. + * + * @param bookstore a client stub to call Bookstore service. + */ + static void enumerate(BookstoreGrpc.BookstoreBlockingStub bookstore) { + System.out.println("Calling listShelves"); + ListShelvesResponse shelves = bookstore.listShelves(Empty.getDefaultInstance()); + System.out.println(shelves); + + for (Shelf s : shelves.getShelvesList()) { + System.out.format("Getting shelf %d\n", s.getId()); + GetShelfRequest getShelfRequest = GetShelfRequest.newBuilder() + .setShelf(s.getId()) + .build(); + Shelf shelf = bookstore.getShelf(getShelfRequest); + System.out.println(shelf); + + System.out.format("Getting books from shelf %d:\n", shelf.getId()); + ListBooksRequest listBooksRequest = ListBooksRequest.newBuilder() + .setShelf(shelf.getId()) + .build(); + + ListBooksResponse books = bookstore.listBooks(listBooksRequest); + System.out.println(books); + + for (Book b : books.getBooksList()) { + System.out.format("Getting book %d from shelf %d:\n", b.getId(), shelf.getId()); + + GetBookRequest getBookRequest = GetBookRequest.newBuilder() + .setShelf(shelf.getId()) + .setBook(b.getId()) + .build(); + + Book book = bookstore.getBook(getBookRequest); + System.out.println(book); + } + } + } + + private static final class Interceptor implements ClientInterceptor { + private final String apiKey; + private final String authToken; + + private static Logger LOGGER = Logger.getLogger("InfoLogging"); + + private static Metadata.Key API_KEY_HEADER = + Metadata.Key.of("x-api-key", Metadata.ASCII_STRING_MARSHALLER); + private static Metadata.Key AUTHORIZATION_HEADER = + Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER); + + public Interceptor(String apiKey, String authToken) { + this.apiKey = apiKey; + this.authToken = authToken; + } + + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + logger.info("Intercepted " + method.getFullMethodName()); + ClientCall call = next.newCall(method, callOptions); + + call = new ForwardingClientCall.SimpleForwardingClientCall(call) { + @Override + public void start(Listener responseListener, Metadata headers) { + if (apiKey != null && !apiKey.isEmpty()) { + logger.info("Attaching API Key: " + apiKey); + headers.put(API_KEY_HEADER, apiKey); + } + if (authToken != null && !authToken.isEmpty()) { + System.out.println("Attaching auth token"); + headers.put(AUTHORIZATION_HEADER, "Bearer " + authToken); + } + super.start(responseListener, headers); + } + }; + return call; + } + } + + static BookstoreGrpc.BookstoreBlockingStub createBookstoreStub( + String address, String apiKey, String authToken) { + Channel channel = ManagedChannelBuilder.forTarget(address) + .usePlaintext(true) + .build(); + + channel = ClientInterceptors.intercept(channel, new Interceptor(apiKey, authToken)); + + return BookstoreGrpc.newBlockingStub(channel); + } + + private static Options createOptions() { + Options options = new Options(); + + // bookstore + options.addOption(Option.builder() + .longOpt("bookstore") + .desc("The address of the bookstore server") + .hasArg() + .argName("address") + .type(String.class) + .build()); + + // api_key + options.addOption(Option.builder() + .longOpt("api_key") + .desc("The API key to use for RPC calls") + .hasArg() + .argName("key") + .type(String.class) + .build()); + + // auth_token + options.addOption(Option.builder() + .longOpt("auth_token") + .desc("The auth token to use for RPC calls") + .hasArg() + .argName("token") + .type(String.class) + .build()); + + // operation + options.addOption(Option.builder() + .longOpt("operation") + .desc("The bookstore operation to perform: list|create|enumerate") + .hasArg() + .argName("op") + .type(String.class) + .build()); + + return options; + } + + private static void printUsage(Options options) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("client", + "A simple Bookstore gRPC client for use with Endpoints.", options, "", true); + } +} diff --git a/endpoints/bookstore-grpc/gradle/wrapper/gradle-wrapper.properties b/endpoints/bookstore-grpc/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..4571a882ebb --- /dev/null +++ b/endpoints/bookstore-grpc/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Jul 07 01:07:24 UTC 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-bin.zip diff --git a/endpoints/bookstore-grpc/gradlew b/endpoints/bookstore-grpc/gradlew new file mode 100755 index 00000000000..27309d92314 --- /dev/null +++ b/endpoints/bookstore-grpc/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/endpoints/bookstore-grpc/gradlew.bat b/endpoints/bookstore-grpc/gradlew.bat new file mode 100644 index 00000000000..f6d5974e72f --- /dev/null +++ b/endpoints/bookstore-grpc/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/endpoints/bookstore-grpc/server/build.gradle b/endpoints/bookstore-grpc/server/build.gradle new file mode 100644 index 00000000000..b1245eae51f --- /dev/null +++ b/endpoints/bookstore-grpc/server/build.gradle @@ -0,0 +1,34 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +apply plugin: 'application' + +mainClassName = "com.google.endpoints.examples.bookstore.BookstoreServer" + +jar { + manifest { + attributes "Main-Class": "$mainClassName" + } + from { + configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } + } +} + +dependencies { + compile project(':api') + compile 'com.google.auto.value:auto-value:1.1' + compile 'commons-cli:commons-cli:1.3' +} diff --git a/endpoints/bookstore-grpc/server/src/main/java/com/google/endpoints/examples/bookstore/BookstoreData.java b/endpoints/bookstore-grpc/server/src/main/java/com/google/endpoints/examples/bookstore/BookstoreData.java new file mode 100644 index 00000000000..95a04226a93 --- /dev/null +++ b/endpoints/bookstore-grpc/server/src/main/java/com/google/endpoints/examples/bookstore/BookstoreData.java @@ -0,0 +1,171 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.google.endpoints.examples.bookstore; + +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +import io.grpc.Status; +import io.grpc.StatusException; + +import java.util.HashMap; +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * The in-memory Bookstore database implementation. + */ +final class BookstoreData { + private static final class ShelfInfo { + private final Shelf shelf; + private final Map books; + private long lastBookId; + + private ShelfInfo(Shelf shelf) { + this.shelf = shelf; + this.books = new HashMap<>(); + this.lastBookId = 0; + } + } + + private final Object lock; + private final Map shelves; + private long lastShelfId; + private final Function shelfInfoToShelf = + new Function() { + @Nullable + @Override + public Shelf apply(@Nullable ShelfInfo shelfInfo) { + if (shelfInfo == null) { + return null; + } + return shelfInfo.shelf; + } + }; + + BookstoreData() { + lock = new Object(); + shelves = new HashMap<>(); + lastShelfId = 0; + } + + public ShelfEntity createShelf(Shelf shelf) { + synchronized (lock) { + lastShelfId++; + shelf = shelf.toBuilder() + .setId(lastShelfId) + .build(); + shelves.put(lastShelfId, new ShelfInfo(shelf)); + return ShelfEntity.create(lastShelfId, shelf); + } + } + + public Iterable listShelves() { + synchronized (lock) { + return Iterables.transform(ImmutableList.copyOf(shelves.values()), + shelfInfoToShelf); + } + } + + public Shelf getShelf(long shelfId) throws StatusException { + synchronized (lock) { + @Nullable Shelf shelf = shelfInfoToShelf.apply(shelves.get(shelfId)); + if (shelf == null) { + throw Status.NOT_FOUND + .withDescription("Unknown shelf ID") + .asException(); + } + return shelf; + } + } + + public void deleteShelf(long shelfId) throws StatusException { + synchronized (lock) { + if (shelves.remove(shelfId) == null) { + throw Status.NOT_FOUND + .withDescription("Unknown shelf ID") + .asException(); + } + } + } + + public Iterable listBooks(long shelfId) throws StatusException { + synchronized (lock) { + @Nullable ShelfInfo shelfInfo = shelves.get(shelfId); + if (shelfInfo == null) { + throw Status.NOT_FOUND + .withDescription("Unknown shelf ID") + .asException(); + } + return ImmutableList.copyOf(shelfInfo.books.values()); + } + } + + public Book createBook(long shelfId, Book book) throws StatusException { + synchronized (lock) { + @Nullable ShelfInfo shelfInfo = shelves.get(shelfId); + if (shelfInfo == null) { + throw Status.NOT_FOUND + .withDescription("Unknown shelf ID") + .asException(); + } + shelfInfo.lastBookId++; + book = book.toBuilder() + .setId(shelfInfo.lastBookId) + .build(); + shelfInfo.books.put(shelfInfo.lastBookId, book); + } + return book; + } + + public Book getBook(long shelfId, long bookId) throws StatusException { + synchronized (lock) { + @Nullable ShelfInfo shelfInfo = shelves.get(shelfId); + if (shelfInfo == null) { + throw Status.NOT_FOUND + .withDescription("Unknown shelf ID") + .asException(); + } + @Nullable Book book = shelfInfo.books.get(bookId); + if (book == null) { + throw Status.NOT_FOUND + .withDescription("Unknown book ID") + .asException(); + } + return book; + } + } + + public void deleteBook(long shelfId, long bookId) throws StatusException { + synchronized (lock) { + @Nullable ShelfInfo shelfInfo = shelves.get(shelfId); + if (shelfInfo == null) { + throw Status.NOT_FOUND + .withDescription("Unknown shelf ID") + .asException(); + } + if (shelfInfo.books.remove(bookId) == null) { + throw Status.NOT_FOUND + .withDescription("Unknown book ID") + .asException(); + } + } + } +} + diff --git a/endpoints/bookstore-grpc/server/src/main/java/com/google/endpoints/examples/bookstore/BookstoreServer.java b/endpoints/bookstore-grpc/server/src/main/java/com/google/endpoints/examples/bookstore/BookstoreServer.java new file mode 100644 index 00000000000..6cd64a0f4ff --- /dev/null +++ b/endpoints/bookstore-grpc/server/src/main/java/com/google/endpoints/examples/bookstore/BookstoreServer.java @@ -0,0 +1,136 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.google.endpoints.examples.bookstore; + +import io.grpc.Server; +import io.grpc.ServerBuilder; +import io.grpc.StatusException; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +/** + * Builds and starts a GRPC-based Bookstore server. + */ +public final class BookstoreServer { + + private static final int DEFAULT_PORT = 8000; + + public static void main(String[] args) throws Exception { + Options options = createOptions(); + CommandLineParser parser = new DefaultParser(); + CommandLine line; + try { + line = parser.parse(options, args); + } catch (ParseException e) { + System.err.println("Invalid command line: " + e.getMessage()); + printUsage(options); + return; + } + + int port = DEFAULT_PORT; + + if (line.hasOption("port")) { + String portOption = line.getOptionValue("port"); + try { + port = Integer.parseInt(portOption); + } catch (java.lang.NumberFormatException e) { + System.err.println("Invalid port number: " + portOption); + printUsage(options); + return; + } + } + + final BookstoreData data = initializeBookstoreData(); + final BookstoreServer server = new BookstoreServer(); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + try { + System.out.println("Shutting down"); + server.stop(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + server.start(port, data); + System.out.format("Bookstore service listening on %d\n", port); + server.blockUntilShutdown(); + } + + private Server server; + + private void start(int port, BookstoreData data) throws IOException { + server = ServerBuilder.forPort(port) + .addService(new BookstoreService(data)) + .build().start(); + } + + private void stop() throws Exception { + server.shutdownNow(); + if (!server.awaitTermination(5, TimeUnit.SECONDS)) { + System.err.println("Timed out waiting for server shutdown"); + } + } + + private void blockUntilShutdown() throws InterruptedException { + if (server != null) { + server.awaitTermination(); + } + } + + private static BookstoreData initializeBookstoreData() throws StatusException { + BookstoreData data = new BookstoreData(); + ShelfEntity shelf = data.createShelf(Shelf.newBuilder().setTheme("Fiction").build()); + data.createBook(shelf.getShelfId(), + Book.newBuilder().setAuthor("Neal Stephenson").setTitle("REAMDE").build()); + shelf = data.createShelf(Shelf.newBuilder().setTheme("Fantasy").build()); + data.createBook(shelf.getShelfId(), + Book.newBuilder().setAuthor("George R. R. Martin").setTitle("A Game of Thrones").build()); + return data; + } + + private static Options createOptions() { + Options options = new Options(); + + // port + options.addOption(Option.builder() + .longOpt("port") + .desc("The port on which the server listens.") + .hasArg() + .argName("port") + .type(Integer.class) + .build()); + + return options; + } + + private static void printUsage(Options options) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("client", + "A simple Bookstore gRPC server for use with Endpoints.", options, "", true); + } +} diff --git a/endpoints/bookstore-grpc/server/src/main/java/com/google/endpoints/examples/bookstore/BookstoreService.java b/endpoints/bookstore-grpc/server/src/main/java/com/google/endpoints/examples/bookstore/BookstoreService.java new file mode 100644 index 00000000000..f46cc87010c --- /dev/null +++ b/endpoints/bookstore-grpc/server/src/main/java/com/google/endpoints/examples/bookstore/BookstoreService.java @@ -0,0 +1,148 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.google.endpoints.examples.bookstore; + +import com.google.protobuf.Empty; + +import io.grpc.stub.StreamObserver; + +import java.util.concurrent.Executor; + +/** + * Implements the Bookstore GRPC service. + */ +public final class BookstoreService extends BookstoreGrpc.BookstoreImplBase { + private final BookstoreData data; + + public BookstoreService(BookstoreData data) { + this.data = data; + } + + @Override + public void listShelves(Empty request, StreamObserver responseObserver) { + ListShelvesResponse response; + try { + response = ListShelvesResponse.newBuilder() + .addAllShelves(data.listShelves()) + .build(); + } catch (Throwable t) { + responseObserver.onError(t); + return; + } + + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + @Override + public void createShelf(CreateShelfRequest request, StreamObserver responseObserver) { + Shelf response; + try { + response = data.createShelf(request.getShelf()).getShelf(); + } catch (Throwable t) { + responseObserver.onError(t); + return; + } + + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + @Override + public void getShelf(GetShelfRequest request, StreamObserver responseObserver) { + Shelf response; + try { + response = data.getShelf(request.getShelf()); + } catch (Throwable t) { + responseObserver.onError(t); + return; + } + + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + @Override + public void deleteShelf(DeleteShelfRequest request, StreamObserver responseObserver) { + try { + data.deleteShelf(request.getShelf()); + } catch (Throwable t) { + responseObserver.onError(t); + return; + } + + responseObserver.onNext(Empty.getDefaultInstance()); + responseObserver.onCompleted(); + } + + @Override + public void listBooks(ListBooksRequest request, StreamObserver responseObserver) { + ListBooksResponse response; + try { + response = ListBooksResponse.newBuilder() + .addAllBooks(data.listBooks(request.getShelf())) + .build(); + } catch (Throwable t) { + responseObserver.onError(t); + return; + } + + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + @Override + public void createBook(CreateBookRequest request, StreamObserver responseObserver) { + Book response; + try { + response = data.createBook(request.getShelf(), request.getBook()); + } catch (Throwable t) { + responseObserver.onError(t); + return; + } + + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + @Override + public void getBook(GetBookRequest request, StreamObserver responseObserver) { + Book response; + try { + response = data.getBook(request.getShelf(), request.getBook()); + } catch (Throwable t) { + responseObserver.onError(t); + return; + } + + responseObserver.onNext(response); + responseObserver.onCompleted(); + } + + @Override + public void deleteBook(DeleteBookRequest request, StreamObserver responseObserver) { + try { + data.deleteBook(request.getShelf(), request.getBook()); + } catch (Throwable t) { + responseObserver.onError(t); + return; + } + + responseObserver.onNext(Empty.getDefaultInstance()); + responseObserver.onCompleted(); + } +} diff --git a/endpoints/bookstore-grpc/server/src/main/java/com/google/endpoints/examples/bookstore/ShelfEntity.java b/endpoints/bookstore-grpc/server/src/main/java/com/google/endpoints/examples/bookstore/ShelfEntity.java new file mode 100644 index 00000000000..82ed11da7dd --- /dev/null +++ b/endpoints/bookstore-grpc/server/src/main/java/com/google/endpoints/examples/bookstore/ShelfEntity.java @@ -0,0 +1,32 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +package com.google.endpoints.examples.bookstore; + +import com.google.auto.value.AutoValue; + +/** + * An entity describing a shelf. + */ +@AutoValue +public abstract class ShelfEntity { + public abstract long getShelfId(); + public abstract Shelf getShelf(); + + public static ShelfEntity create(long id, Shelf shelf) { + return new AutoValue_ShelfEntity(id, shelf); + } +} diff --git a/endpoints/bookstore-grpc/settings.gradle b/endpoints/bookstore-grpc/settings.gradle new file mode 100644 index 00000000000..9ebbb2e1738 --- /dev/null +++ b/endpoints/bookstore-grpc/settings.gradle @@ -0,0 +1 @@ +include "api", "client", "server"