From c34e3b77c6125ee85170f60d4392632bd308b104 Mon Sep 17 00:00:00 2001 From: Tal Moran Date: Tue, 10 Nov 2015 01:49:17 +0200 Subject: [PATCH] HelloWorld for protobuf-based servlet with integration test example --- build.gradle-template | 71 +++++--- bulletin-board-server/build.gradle | 26 ++- bulletin-board-server/gradle-app.setting | 29 ++++ .../bulletinboard/service/HelloProtoBuf.java | 31 ++++ .../webapp/HelloProtoWebApp.java | 22 +++ .../src/main/webapp/WEB-INF/web.xml | 2 +- .../HelloProtoIntegrationTest.java | 41 +++++ meerkat-common/build.gradle | 21 ++- polling-station/build.gradle | 24 ++- restful-api-common/build.gradle | 161 ++++++++++++++++++ restful-api-common/gradlew | 1 + .../src/main/java/meerkat/rest/Constants.java | 8 + .../rest/ProtobufMessageBodyReader.java | 41 +++++ .../rest/ProtobufMessageBodyWriter.java | 41 +++++ settings.gradle | 3 +- voting-booth/build.gradle | 25 ++- 16 files changed, 495 insertions(+), 52 deletions(-) create mode 100644 bulletin-board-server/gradle-app.setting create mode 100644 bulletin-board-server/src/main/java/meerkat/bulletinboard/service/HelloProtoBuf.java create mode 100644 bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/HelloProtoWebApp.java create mode 100644 bulletin-board-server/src/test/java/meerkat/bulletinboard/HelloProtoIntegrationTest.java create mode 100644 restful-api-common/build.gradle create mode 120000 restful-api-common/gradlew create mode 100644 restful-api-common/src/main/java/meerkat/rest/Constants.java create mode 100644 restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyReader.java create mode 100644 restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyWriter.java diff --git a/build.gradle-template b/build.gradle-template index 1d778fe..361c26c 100644 --- a/build.gradle-template +++ b/build.gradle-template @@ -63,50 +63,67 @@ protobuf { } } + idea { module { - // add protobuf generated sources to generated source dir. - sourceDirs += file(protobuf.generatedFilesBaseDir) - generatedSourceDirs += file(protobuf.generatedFilesBaseDir) + project.sourceSets.each { sourceSet -> - // Don't exclude build directory - excludeDirs -= file(buildDir) + def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" + + println "Adding $srcDir" + // add protobuf generated sources to generated source dir. + if ("test".equals(sourceSet.name)) { + testSourceDirs += file(srcDir) + } else { + sourceDirs += file(srcDir) + } + generatedSourceDirs += file(srcDir) + + } + + // Don't exclude build directory + excludeDirs -= file(buildDir) } } + /*=================================== * "Fat" Build targets *===================================*/ -task mavenCapsule(type: MavenCapsule){ - description = "Generate a capsule jar that automatically downloads and caches dependencies when run." - applicationClass mainClassName - destinationDir = buildDir -} +if (project.hasProperty('mainClassName') && (mainClassName != null)) { -task fatCapsule(type: FatCapsule){ - description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class" - - destinationDir = buildDir - - def fatMain = hasProperty('fatmain') ? fatmain : mainClassName - - applicationClass fatMain - - def testJar = hasProperty('test') - - if (hasProperty('fatmain')) { - appendix = "fat-${fatMain}" - } else { - appendix = "fat" + task mavenCapsule(type: MavenCapsule) { + description = "Generate a capsule jar that automatically downloads and caches dependencies when run." + applicationClass mainClassName + destinationDir = buildDir } - if (testJar) { - from sourceSets.test.output + task fatCapsule(type: FatCapsule) { + description = "Generate a single capsule jar containing everything. Use -Pfatmain=... to override main class" + + destinationDir = buildDir + + def fatMain = hasProperty('fatmain') ? fatmain : mainClassName + + applicationClass fatMain + + def testJar = hasProperty('test') + + if (hasProperty('fatmain')) { + appendix = "fat-${fatMain}" + } else { + appendix = "fat" + } + + if (testJar) { + from sourceSets.test.output + } } } + /*=================================== * Repositories *===================================*/ diff --git a/bulletin-board-server/build.gradle b/bulletin-board-server/build.gradle index 62af04a..ae8e028 100644 --- a/bulletin-board-server/build.gradle +++ b/bulletin-board-server/build.gradle @@ -27,7 +27,7 @@ ext { nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" } -description = "TODO: Add a description" +description = "Bulletin-board server web application" // Your project version version = "0.0.1" @@ -38,6 +38,7 @@ version += "${isSnapshot ? '-SNAPSHOT' : ''}" dependencies { // Meerkat common compile project(':meerkat-common') + compile project(':restful-api-common') // Jersey for RESTful API compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.22.+' @@ -82,17 +83,30 @@ protobuf { } } + idea { module { - // add protobuf generated sources to generated source dir. - sourceDirs += file(protobuf.generatedFilesBaseDir) - generatedSourceDirs += file(protobuf.generatedFilesBaseDir) + project.sourceSets.each { sourceSet -> - // Don't exclude build directory - excludeDirs -= file(buildDir) + def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" + + println "Adding $srcDir" + // add protobuf generated sources to generated source dir. + if ("test".equals(sourceSet.name)) { + testSourceDirs += file(srcDir) + } else { + sourceDirs += file(srcDir) + } + generatedSourceDirs += file(srcDir) + + } + + // Don't exclude build directory + excludeDirs -= file(buildDir) } } + /*=================================== * "Fat" Build targets *===================================*/ diff --git a/bulletin-board-server/gradle-app.setting b/bulletin-board-server/gradle-app.setting new file mode 100644 index 0000000..b0bad25 --- /dev/null +++ b/bulletin-board-server/gradle-app.setting @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/service/HelloProtoBuf.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/service/HelloProtoBuf.java new file mode 100644 index 0000000..f848a47 --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/service/HelloProtoBuf.java @@ -0,0 +1,31 @@ +package meerkat.bulletinboard.service; + +import com.google.protobuf.ByteString; +import com.google.protobuf.Message; +import meerkat.protobuf.Crypto; +import meerkat.protobuf.Voting; +import meerkat.protobuf.Voting.BulletinBoardMessage; + +import java.util.Arrays; +import java.util.List; + +/** + * Created by talm on 10/11/15. + */ +public class HelloProtoBuf { + public Message sayHello() { + BulletinBoardMessage.Builder msg = BulletinBoardMessage.newBuilder(); + + Voting.UnsignedBulletinBoardMessage.Builder unsigned = Voting.UnsignedBulletinBoardMessage.newBuilder(); + unsigned.setData(ByteString.copyFromUtf8("Hello World!")); + List tags = Arrays.asList("Greetings", "FirstPrograms"); + unsigned.addAllTags(tags); + msg.setMsg(unsigned); + + Crypto.Signature.Builder sig = Crypto.Signature.newBuilder(); + sig.setData(ByteString.copyFromUtf8("deadbeef")); + msg.setSig(sig); + + return msg.build(); + } +} diff --git a/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/HelloProtoWebApp.java b/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/HelloProtoWebApp.java new file mode 100644 index 0000000..cbe2ae5 --- /dev/null +++ b/bulletin-board-server/src/main/java/meerkat/bulletinboard/webapp/HelloProtoWebApp.java @@ -0,0 +1,22 @@ +package meerkat.bulletinboard.webapp; + + +import com.google.protobuf.Message; +import meerkat.bulletinboard.service.HelloProtoBuf; +import meerkat.rest.Constants; +import service.HelloWorldService; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +@Path("/proto") +public class HelloProtoWebApp { + private static HelloProtoBuf helloProtoBuf = new HelloProtoBuf(); + + @GET + @Produces(Constants.MEDIATYPE_PROTOBUF) + public Message hello() { + return helloProtoBuf.sayHello(); + } +} diff --git a/bulletin-board-server/src/main/webapp/WEB-INF/web.xml b/bulletin-board-server/src/main/webapp/WEB-INF/web.xml index f2c6e23..ba674d2 100644 --- a/bulletin-board-server/src/main/webapp/WEB-INF/web.xml +++ b/bulletin-board-server/src/main/webapp/WEB-INF/web.xml @@ -6,7 +6,7 @@ jersey.config.server.provider.packages - webapp + webapp, meerkat 1 diff --git a/bulletin-board-server/src/test/java/meerkat/bulletinboard/HelloProtoIntegrationTest.java b/bulletin-board-server/src/test/java/meerkat/bulletinboard/HelloProtoIntegrationTest.java new file mode 100644 index 0000000..4a6cbb3 --- /dev/null +++ b/bulletin-board-server/src/test/java/meerkat/bulletinboard/HelloProtoIntegrationTest.java @@ -0,0 +1,41 @@ +package meerkat.bulletinboard; + +import meerkat.protobuf.Voting; +import meerkat.rest.Constants; +import meerkat.rest.ProtobufMessageBodyReader; +import meerkat.rest.ProtobufMessageBodyWriter; +import org.junit.Test; + +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Created by talm on 10/11/15. + */ +public class HelloProtoIntegrationTest { + + private static String PROP_GETTY_URL = "gretty.httpBaseURI"; + private static String BASE_URL = System.getProperty(PROP_GETTY_URL); + private static String HELLO_URL = BASE_URL + "/proto"; + + @Test + public void testHello() throws Exception { + Client client = ClientBuilder.newClient(); + client.register(ProtobufMessageBodyReader.class); + client.register(ProtobufMessageBodyWriter.class); + + WebTarget webTarget = client.target(HELLO_URL); + Voting.BulletinBoardMessage response = webTarget.request(Constants.MEDIATYPE_PROTOBUF).get(Voting.BulletinBoardMessage.class); + + System.out.println(response.getMsg().getData()); + + assertThat(response.getMsg().getData().toStringUtf8(), is("Hello World!")); + assertThat(response.getMsg().getTagsCount(), is(2)); + assertThat(response.getMsg().getTags(0), is("Greetings")); + assertThat(response.getMsg().getTags(1), is("FirstPrograms")); + } +} diff --git a/meerkat-common/build.gradle b/meerkat-common/build.gradle index c91fe10..9768dd8 100644 --- a/meerkat-common/build.gradle +++ b/meerkat-common/build.gradle @@ -63,12 +63,23 @@ protobuf { idea { module { - // add protobuf generated sources to generated source dir. - sourceDirs += file(protobuf.generatedFilesBaseDir) - generatedSourceDirs += file(protobuf.generatedFilesBaseDir) + project.sourceSets.each { sourceSet -> - // Don't exclude build directory - excludeDirs -= file(buildDir) + def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" + + println "Adding $srcDir" + // add protobuf generated sources to generated source dir. + if ("test".equals(sourceSet.name)) { + testSourceDirs += file(srcDir) + } else { + sourceDirs += file(srcDir) + } + generatedSourceDirs += file(srcDir) + + } + + // Don't exclude build directory + excludeDirs -= file(buildDir) } } diff --git a/polling-station/build.gradle b/polling-station/build.gradle index 51e5558..01614db 100644 --- a/polling-station/build.gradle +++ b/polling-station/build.gradle @@ -25,7 +25,7 @@ ext { nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" } -description = "TODO: Add a description" +description = "Meerkat polling-station application" // Your project version version = "0.0" @@ -61,14 +61,26 @@ protobuf { } } + idea { module { - // add protobuf generated sources to generated source dir. - sourceDirs += file(protobuf.generatedFilesBaseDir) - generatedSourceDirs += file(protobuf.generatedFilesBaseDir) + project.sourceSets.each { sourceSet -> - // Don't exclude build directory - excludeDirs -= file(buildDir) + def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" + + println "Adding $srcDir" + // add protobuf generated sources to generated source dir. + if ("test".equals(sourceSet.name)) { + testSourceDirs += file(srcDir) + } else { + sourceDirs += file(srcDir) + } + generatedSourceDirs += file(srcDir) + + } + + // Don't exclude build directory + excludeDirs -= file(buildDir) } } diff --git a/restful-api-common/build.gradle b/restful-api-common/build.gradle new file mode 100644 index 0000000..1dea7a3 --- /dev/null +++ b/restful-api-common/build.gradle @@ -0,0 +1,161 @@ + +plugins { + id "us.kirchmeier.capsule" version "1.0.1" + id 'com.google.protobuf' version '0.7.0' +} + +apply plugin: 'java' +apply plugin: 'eclipse' +apply plugin: 'idea' + +// Uncomment both lines below to define an application (must set mainClassName +//apply plugin: 'application' +//mainClassName='your.main.ApplicationClass' + +apply plugin: 'maven-publish' + +// Is this a snapshot version? +ext { isSnapshot = false } + +ext { + groupId = 'org.factcenter.meerkat' + nexusRepository = "https://cs.idc.ac.il/nexus/content/groups/${isSnapshot ? 'unstable' : 'public'}/" + + // Credentials for IDC nexus repositories (needed only for using unstable repositories and publishing) + // Should be set in ${HOME}/.gradle/gradle.properties + nexusUser = project.hasProperty('nexusUser') ? project.property('nexusUser') : "" + nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" +} + +description = "Common classes for implementing Meerkat's RESTful API" + +// Your project version +version = "0.0.1" + +version += "${isSnapshot ? '-SNAPSHOT' : ''}" + + +dependencies { + // Meerkat common + compile project(':meerkat-common') + + // Jersey for RESTful API + compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.22.+' + + // Logging + compile 'org.slf4j:slf4j-api:1.7.7' + runtime 'ch.qos.logback:logback-classic:1.1.2' + runtime 'ch.qos.logback:logback-core:1.1.2' + + // Google protobufs + compile 'com.google.protobuf:protobuf-java:3.+' + + testCompile 'junit:junit:4.+' + + runtime 'org.codehaus.groovy:groovy:2.4.+' +} + + +/*==== You probably don't have to edit below this line =======*/ + +protobuf { + // Configure the protoc executable + protoc { + // Download from repositories + artifact = 'com.google.protobuf:protoc:3.+' + } +} + + +idea { + module { + project.sourceSets.each { sourceSet -> + + def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" + + println "Adding $srcDir" + // add protobuf generated sources to generated source dir. + if ("test".equals(sourceSet.name)) { + testSourceDirs += file(srcDir) + } else { + sourceDirs += file(srcDir) + } + generatedSourceDirs += file(srcDir) + + } + + // Don't exclude build directory + excludeDirs -= file(buildDir) + } +} + + + +/*=================================== + * Repositories + *===================================*/ + +repositories { + + // Prefer the local nexus repository (it may have 3rd party artifacts not found in mavenCentral) + maven { + url nexusRepository + + if (isSnapshot) { + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } + + // Use local maven repository + mavenLocal() + + // Use 'maven central' for other dependencies. + mavenCentral() +} + +task "info" << { + println "Project: ${project.name}" +println "Description: ${project.description}" + println "--------------------------" + println "GroupId: $groupId" + println "Version: $version (${isSnapshot ? 'snapshot' : 'release'})" + println "" +} +info.description 'Print some information about project parameters' + + +/*=================================== + * Publishing + *===================================*/ + +publishing { + publications { + mavenJava(MavenPublication) { + groupId project.groupId + pom.withXml { + asNode().appendNode('description', project.description) + } + from project.components.java + + } + } + repositories { + maven { + url "https://cs.idc.ac.il/nexus/content/repositories/${project.isSnapshot ? 'snapshots' : 'releases'}" + credentials { username + password + + username nexusUser + password nexusPassword + } + } + } +} + + + diff --git a/restful-api-common/gradlew b/restful-api-common/gradlew new file mode 120000 index 0000000..502f5a2 --- /dev/null +++ b/restful-api-common/gradlew @@ -0,0 +1 @@ +../gradlew \ No newline at end of file diff --git a/restful-api-common/src/main/java/meerkat/rest/Constants.java b/restful-api-common/src/main/java/meerkat/rest/Constants.java new file mode 100644 index 0000000..2c04248 --- /dev/null +++ b/restful-api-common/src/main/java/meerkat/rest/Constants.java @@ -0,0 +1,8 @@ +package meerkat.rest; + +/** + * Created by talm on 10/11/15. + */ +public interface Constants { + public static final String MEDIATYPE_PROTOBUF = "application/x-protobuf"; +} diff --git a/restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyReader.java b/restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyReader.java new file mode 100644 index 0000000..853d962 --- /dev/null +++ b/restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyReader.java @@ -0,0 +1,41 @@ +package meerkat.rest; + +import com.google.protobuf.GeneratedMessage; +import com.google.protobuf.Message; + + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.Provider; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import static meerkat.rest.Constants.*; + +@Provider +@Consumes(MEDIATYPE_PROTOBUF) +public class ProtobufMessageBodyReader implements MessageBodyReader { + @Override + public boolean isReadable(Class type, Type genericType, Annotation[] annotations, + MediaType mediaType) { + return Message.class.isAssignableFrom(type); + } + + @Override + public Message readFrom(Class type, Type genericType, Annotation[] annotations, + MediaType mediaType, MultivaluedMap httpHeaders, + InputStream entityStream) throws IOException, WebApplicationException { + try { + Method newBuilder = type.getMethod("newBuilder"); + GeneratedMessage.Builder builder = (GeneratedMessage.Builder) newBuilder.invoke(type); + return builder.mergeFrom(entityStream).build(); + } catch (Exception e) { + throw new WebApplicationException(e); + } + } +} diff --git a/restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyWriter.java b/restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyWriter.java new file mode 100644 index 0000000..b8ea503 --- /dev/null +++ b/restful-api-common/src/main/java/meerkat/rest/ProtobufMessageBodyWriter.java @@ -0,0 +1,41 @@ +package meerkat.rest; + +import com.google.protobuf.Message; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyWriter; +import javax.ws.rs.ext.Provider; + +import java.io.IOException; +import java.io.OutputStream; +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; + +import static meerkat.rest.Constants.*; + +@Provider +@Produces(MEDIATYPE_PROTOBUF) +public class ProtobufMessageBodyWriter implements MessageBodyWriter { + @Override + public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, + MediaType mediaType) { + return Message.class.isAssignableFrom(type); + } + + @Override + public long getSize(Message message, Class type, Type genericType, Annotation[] annotations, + MediaType mediaType) { + return -1; + } + + @Override + public void writeTo(Message message, Class type, Type genericType, Annotation[] annotations, + MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) + throws IOException, WebApplicationException { + message.writeTo(entityStream); + } +} + + diff --git a/settings.gradle b/settings.gradle index ffb5b3f..e4ef054 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,5 @@ include 'meerkat-common' include 'voting-booth' include 'bulletin-board-server' -include 'polling-station' \ No newline at end of file +include 'polling-station' +include 'restful-api-common' diff --git a/voting-booth/build.gradle b/voting-booth/build.gradle index 89c9ade..1946bda 100644 --- a/voting-booth/build.gradle +++ b/voting-booth/build.gradle @@ -25,7 +25,7 @@ ext { nexusPassword = project.hasProperty('nexusPassword') ? project.property('nexusPassword') : "" } -description = "TODO: Add a description" +description = "Meerkat voting booth application" // Your project version version = "0.0" @@ -61,17 +61,30 @@ protobuf { } } + idea { module { - // add protobuf generated sources to generated source dir. - sourceDirs += file(protobuf.generatedFilesBaseDir) - generatedSourceDirs += file(protobuf.generatedFilesBaseDir) + project.sourceSets.each { sourceSet -> - // Don't exclude build directory - excludeDirs -= file(buildDir) + def srcDir = "${protobuf.generatedFilesBaseDir}/$sourceSet.name/java" + + println "Adding $srcDir" + // add protobuf generated sources to generated source dir. + if ("test".equals(sourceSet.name)) { + testSourceDirs += file(srcDir) + } else { + sourceDirs += file(srcDir) + } + generatedSourceDirs += file(srcDir) + + } + + // Don't exclude build directory + excludeDirs -= file(buildDir) } } + /*=================================== * "Fat" Build targets *===================================*/