diff --git a/java/sqlcommenter-java/README.md b/java/sqlcommenter-java/README.md index 7139a6d0..4204496b 100644 --- a/java/sqlcommenter-java/README.md +++ b/java/sqlcommenter-java/README.md @@ -37,7 +37,7 @@ tracestate='rojo%253D00f067aa0ba902b7%2Ccongo%253Dt61rcWkgMzE''*/ - [ ] Jetty - [ ] Netty - [ ] Apache Tomcat -- [ ] gRPC +- [X] gRPC ### Using it @@ -173,3 +173,17 @@ public class JPAConfig { 1. Please follow the instructions to add the [Spring interceptor](#spring) 2. Please follow the instructions to add the [Hibernate StatementInspector](#hibernate) + +#### Spring gRPC Configuration +Add following configuration into your code +```java +@Configuration +public class gRPCConfig { + + @Bean + public GrpcSQLCommenterInterceptor grpcSQLCommenterInterceptor() { + return new GrpcSQLCommenterInterceptor(); + } + +} +``` \ No newline at end of file diff --git a/java/sqlcommenter-java/build.gradle b/java/sqlcommenter-java/build.gradle index c965bcf4..59ee044e 100644 --- a/java/sqlcommenter-java/build.gradle +++ b/java/sqlcommenter-java/build.gradle @@ -1,13 +1,29 @@ -description = 'SQL commenter integration' +buildscript { + repositories { + mavenCentral() + mavenLocal() + maven { url "https://plugins.gradle.org/m2/" } + } + dependencies { + classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.16' + classpath "gradle.plugin.com.github.sherter.google-java-format:google-java-format-gradle-plugin:0.8" + } +} -apply plugin: 'com.github.sherter.google-java-format' -apply plugin: 'idea' -apply plugin: 'java' -apply plugin: 'maven' -apply plugin: "maven-publish" -apply plugin: "net.ltgt.errorprone" -apply plugin: "jacoco" -apply plugin: "signing" +plugins { + id 'com.github.sherter.google-java-format' version "0.7.1" + id 'idea' + id 'java' + id 'maven' + id "maven-publish" + id "net.ltgt.errorprone" version "1.1.0" + id "jacoco" + id "signing" + id "com.google.protobuf" version "0.9.1" + id "com.google.osdetector" version "1.6.2" +} + +description = 'SQL commenter integration' group = "com.google.cloud" version = "2.0.0" // CURRENT_VERSION @@ -28,18 +44,6 @@ jar.manifest { 'Target-Compatibility': targetCompatibility) } -buildscript { - repositories { - mavenCentral() - mavenLocal() - maven { url "https://plugins.gradle.org/m2/" } - } - dependencies { - classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.16' - classpath "gradle.plugin.com.github.sherter.google-java-format:google-java-format-gradle-plugin:0.7.1" - } -} - def opencensusVersion = '0.28.3' def errorProneVersion = '2.3.2' @@ -49,6 +53,11 @@ def opentelemetryVersion = '1.5.0' dependencies { compile "io.opencensus:opencensus-api:${opencensusVersion}" + testImplementation 'org.projectlombok:lombok:1.18.22' + testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.22' + testImplementation group: 'org.mockito', name: 'mockito-core', version: '2.1.0' + runtime "io.opencensus:opencensus-impl:${opencensusVersion}" compileOnly "com.google.code.findbugs:jsr305:${findBugsJsr305Version}" @@ -56,6 +65,7 @@ dependencies { errorprone "com.google.errorprone:error_prone_core:${errorProneVersion}" implementation "io.opentelemetry:opentelemetry-api:${opentelemetryVersion}" implementation "io.opentelemetry:opentelemetry-sdk:${opentelemetryVersion}" + implementation 'io.grpc:grpc-all:1.40.1' // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web compile (group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: '2.1.5.RELEASE') { @@ -71,6 +81,44 @@ dependencies { testCompile 'org.springframework.boot:spring-boot-starter-data-jpa:2.1.5.RELEASE' testCompile 'org.springframework.boot:spring-boot-starter-test:2.1.5.RELEASE' testCompile 'org.hsqldb:hsqldb:2.4.0' + testCompile 'io.github.lognet:grpc-spring-boot-starter:4.9.0' + testCompile "com.google.protobuf:protobuf-java:3.19.1" + runtimeOnly 'io.grpc:grpc-netty-shaded:1.52.1' + implementation 'io.grpc:grpc-protobuf:1.52.1' + implementation 'io.grpc:grpc-stub:1.52.1' + compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+ + + errorprone("com.google.errorprone:error_prone_core:2.3.3") + errorproneJavac("com.google.errorprone:javac:9+181-r4173-1") + + testImplementation group: 'com.h2database', name: 'h2', version: '1.3.148' + + +} + +sourceSets { + test { + java { + srcDirs 'build/generated/source/proto/test/grpc' + srcDirs 'build/generated/source/proto/test/java' + } + } +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:3.21.7" + } + plugins { + grpc { + artifact = "io.grpc:protoc-gen-grpc-java:1.52.1" + } + } + generateProtoTasks { + all()*.plugins { + grpc {} + } + } } compileJava { @@ -79,17 +127,17 @@ compileJava { // We suppress the "processing" warning as suggested in // https://groups.google.com/forum/#!topic/bazel-discuss/_R3A9TJSoPM it.options.compilerArgs += ["-Xlint:all", "-Xlint:-try", "-Xlint:-processing"] - it.options.compilerArgs += ["-XepAllDisabledChecksAsWarnings", "-XepDisableWarningsInGeneratedCode"] + //it.options.compilerArgs += ["-XepAllDisabledChecksAsWarnings", "-XepDisableWarningsInGeneratedCode"] // MutableMethodReturnType can suggest returning Guava types from // API methods (https://github.com/google/error-prone/issues/982). - it.options.compilerArgs += ["-Xep:MutableMethodReturnType:OFF"] + //it.options.compilerArgs += ["-Xep:MutableMethodReturnType:OFF"] // ReturnMissingNullable conflicts with Checker Framework null analysis. - it.options.compilerArgs += ["-Xep:ReturnMissingNullable:OFF"] + //it.options.compilerArgs += ["-Xep:ReturnMissingNullable:OFF"] // We currently don't use Var annotations. - it.options.compilerArgs += ["-Xep:Var:OFF"] + //it.options.compilerArgs += ["-Xep:Var:OFF"] it.options.encoding = "UTF-8" it.options.compilerArgs += ["-Xlint:-cast"] @@ -110,6 +158,7 @@ if (JavaVersion.current().isJava8Compatible()) { tasks.verifyGoogleJavaFormat { source = sourceSets*.allJava + exclude '**/grpc/stubs/**' include '**/*.java' } } diff --git a/java/sqlcommenter-java/gradle/wrapper/gradle-wrapper.properties b/java/sqlcommenter-java/gradle/wrapper/gradle-wrapper.properties index a95009c3..ef9a9e05 100644 --- a/java/sqlcommenter-java/gradle/wrapper/gradle-wrapper.properties +++ b/java/sqlcommenter-java/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/java/sqlcommenter-java/src/main/java/com/google/cloud/sqlcommenter/interceptors/GrpcSQLCommenterInterceptor.java b/java/sqlcommenter-java/src/main/java/com/google/cloud/sqlcommenter/interceptors/GrpcSQLCommenterInterceptor.java new file mode 100644 index 00000000..56473328 --- /dev/null +++ b/java/sqlcommenter-java/src/main/java/com/google/cloud/sqlcommenter/interceptors/GrpcSQLCommenterInterceptor.java @@ -0,0 +1,43 @@ +// Copyright 2019 Google LLC +// +// 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.cloud.sqlcommenter.interceptors; + +import com.google.cloud.sqlcommenter.threadlocalstorage.State; +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; + +public class GrpcSQLCommenterInterceptor implements ServerInterceptor { + @Override + public ServerCall.Listener interceptCall( + ServerCall call, Metadata requestHeaders, ServerCallHandler next) { + + if (call == null || next == null) return null; + + if (call != null) { + String actionName = call.getMethodDescriptor().getBareMethodName(); + String serviceName = call.getMethodDescriptor().getServiceName(); + State.Holder.set( + State.newBuilder() + .withControllerName(serviceName) + .withActionName(actionName) + .withFramework("spring-grpc") + .build()); + } + + return next.startCall(call, requestHeaders); + } +} diff --git a/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/grpc/backend/GrpcSQLCommenterTests.java b/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/grpc/backend/GrpcSQLCommenterTests.java new file mode 100644 index 00000000..f67fc92f --- /dev/null +++ b/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/grpc/backend/GrpcSQLCommenterTests.java @@ -0,0 +1,50 @@ +package com.google.cloud.sqlcommenter.grpc.backend; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +import com.google.cloud.sqlcommenter.grpc.backend.service.StudentServiceImpl; +import com.google.cloud.sqlcommenter.grpc.stubs.StudentRequest; +import com.google.cloud.sqlcommenter.grpc.stubs.StudentResponse; +import com.google.cloud.sqlcommenter.threadlocalstorage.State; +import com.google.cloud.sqlcommenter.util.SCHibernateWrapper; +import io.grpc.stub.StreamObserver; +import java.util.List; +import org.junit.Test; + +public class GrpcSQLCommenterTests { + + @Test + public void givenStudentService_whenGetStudentInfoIsCalled_thenSQLStatementsShouldBeTagged() { + // given + StudentServiceImpl studentService = new StudentServiceImpl(); + StreamObserver observer = mock(StreamObserver.class); + State.Holder.set( + State.newBuilder() + .withControllerName("StudentServiceImpl") + .withActionName("getStudentInfo") + .withFramework("grpc") + .build()); + + SCHibernateWrapper.reset(); + StudentRequest studentRequest = StudentRequest.newBuilder().setStudentId("st1").build(); + + // when + studentService.getStudentInfo(studentRequest, observer); + List sqlStatements = SCHibernateWrapper.getAfterSqlStatements(); + + // then + assertEquals(1, sqlStatements.size()); + assertEquals( + 1, + sqlStatements + .stream() + .filter( + sql -> + sql.contains( + "/*action='getStudentInfo',controller='StudentServiceImpl',framework='grpc'*/")) + .count()); + + SCHibernateWrapper.reset(); + } +} diff --git a/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/grpc/backend/dao/StudentDao.java b/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/grpc/backend/dao/StudentDao.java new file mode 100644 index 00000000..8dcc1041 --- /dev/null +++ b/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/grpc/backend/dao/StudentDao.java @@ -0,0 +1,46 @@ +// Copyright 2019 Google LLC +// +// 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.cloud.sqlcommenter.grpc.backend.dao; + +import com.google.cloud.sqlcommenter.grpc.backend.domain.Student; +import java.util.NoSuchElementException; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; +import javax.persistence.Persistence; + +public class StudentDao { + public Student findById(String studentId) { + + // We use entity managers to manage our two entities. + // We use the factory design pattern to get the entity manager. + // Here we should provide the name of the persistence unit that we provided in the + // persistence.xml file. + EntityManagerFactory emf = Persistence.createEntityManagerFactory("student-management-system"); + EntityManager em = emf.createEntityManager(); + + // We can find a record in the database for a given id using the find method. + // for the find method we have to provide our entity class and the id. + Student student = em.find(Student.class, studentId); + + // If there is no record found with the provided student id, then we throw a NoSuchElement + // exception. + if (student == null) { + throw new NoSuchElementException("NO DATA FOUND WITH THE ID " + studentId); + } + + // If everything worked fine, return the result. + return student; + } +} diff --git a/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/grpc/backend/domain/Student.java b/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/grpc/backend/domain/Student.java new file mode 100644 index 00000000..762d3673 --- /dev/null +++ b/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/grpc/backend/domain/Student.java @@ -0,0 +1,35 @@ +// Copyright 2019 Google LLC +// +// 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.cloud.sqlcommenter.grpc.backend.domain; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString +@Entity +@Table(name = "student") +public class Student { + @Id private String studentId; + private String name; + private Integer age; +} diff --git a/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/grpc/backend/service/StudentServiceImpl.java b/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/grpc/backend/service/StudentServiceImpl.java new file mode 100644 index 00000000..6b8a537b --- /dev/null +++ b/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/grpc/backend/service/StudentServiceImpl.java @@ -0,0 +1,75 @@ +// Copyright 2019 Google LLC +// +// 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.cloud.sqlcommenter.grpc.backend.service; + +import com.google.cloud.sqlcommenter.grpc.backend.dao.StudentDao; +import com.google.cloud.sqlcommenter.grpc.backend.domain.Student; +import com.google.cloud.sqlcommenter.grpc.stubs.StudentRequest; +import com.google.cloud.sqlcommenter.grpc.stubs.StudentResponse; +import com.google.cloud.sqlcommenter.grpc.stubs.StudentServiceGrpc; +import io.grpc.Status; +import io.grpc.stub.StreamObserver; +import java.util.NoSuchElementException; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class StudentServiceImpl extends StudentServiceGrpc.StudentServiceImplBase { + // Let's use a logger to log everything that we want + private static final Logger logger = Logger.getLogger(StudentServiceImpl.class.getName()); + // We need to have an instance of the dao class to work with the database + private final StudentDao studentDao = new StudentDao(); + + // We have to override the getStudentInfo that was defined in the StudentService class + // The StudentService class is an autogenerated class by the proto file + // So, let's override the getStudentInfo method here. + @Override + public void getStudentInfo( + StudentRequest request, StreamObserver responseObserver) { + String studentId = + request.getStudentId(); // the student ID should be passed with the request message + + try { + Student student = + studentDao.findById( + studentId); // Let's find the student information from the student table + + /* + The getResults method will help us to fetch the results for the student from the result service. + this method will call the result service through its client and bring back the result as a list of strings + */ + + // Once all the results are clear, we can build our response message + StudentResponse studentResponse = + StudentResponse.newBuilder() + .setStudentId(studentId) + .setName(student.getName()) + .setAge(student.getAge()) + .build(); + + /* + gRPC works in an asynchronous manner, so if you have ever worked with asynchronous programming + you would know what will happen with following two methods. + with the onNext method we send the response, once the response is sent we use onCompleted() + */ + responseObserver.onNext(studentResponse); + responseObserver.onCompleted(); + } catch (NoSuchElementException e) { + logger.log(Level.SEVERE, "NO STUDENT FOUND WITH THE STUDENT ID :- " + studentId); + + // If some error occurs we sent an error with the following status which is not_found + responseObserver.onError(Status.NOT_FOUND.asRuntimeException()); + } + } +} diff --git a/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/interceptors/GrpcSQLCommenterInterceptorTest.java b/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/interceptors/GrpcSQLCommenterInterceptorTest.java new file mode 100644 index 00000000..ac73c1b8 --- /dev/null +++ b/java/sqlcommenter-java/src/test/java/com/google/cloud/sqlcommenter/interceptors/GrpcSQLCommenterInterceptorTest.java @@ -0,0 +1,204 @@ +// Copyright 2019 Google LLC +// +// 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.cloud.sqlcommenter.interceptors; + +import static com.google.common.truth.Truth.assertThat; +import static io.grpc.MethodDescriptor.generateFullMethodName; +import static org.mockito.Mockito.when; + +import com.google.cloud.sqlcommenter.threadlocalstorage.State; +import io.grpc.*; +import io.grpc.MethodDescriptor.Marshaller; +import io.grpc.MethodDescriptor.MethodType; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +@RunWith(JUnit4.class) +public class GrpcSQLCommenterInterceptorTest { + + @Rule public final MockitoRule mocks = MockitoJUnit.rule(); + + @SuppressWarnings("deprecation") // https://github.com/grpc/grpc-java/issues/7467 + @Rule + public final ExpectedException thrown = ExpectedException.none(); + + @Mock private Marshaller requestMarshaller; + + @Mock private Marshaller responseMarshaller; + + @Mock private ServerCallHandler handler; + + @Mock private ServerCall.Listener listener; + + private MethodDescriptor flowMethod; + + private ServerServiceDefinition serviceDefinition; + + private final Metadata headers = new Metadata(); + + @Before + public void setUp() { + State.Holder.remove(); + + flowMethod = + MethodDescriptor.newBuilder() + .setType(MethodType.UNKNOWN) + .setFullMethodName( + generateFullMethodName( + "com.google.cloud.sqlcommenter.grpc.backend.service.StudentServiceImpl", + "getStudentInfo")) + .setRequestMarshaller(requestMarshaller) + .setResponseMarshaller(responseMarshaller) + .build(); + + Mockito.when( + handler.startCall( + ArgumentMatchers.>any(), + ArgumentMatchers.any())) + .thenReturn(listener); + + serviceDefinition = + ServerServiceDefinition.builder( + new ServiceDescriptor( + "com.google.cloud.sqlcommenter.grpc.backend.service.StudentServiceImpl", + flowMethod)) + .addMethod(flowMethod, handler) + .build(); + } + + @After + public void tearDown() { + State.Holder.remove(); + } + + @Test + public void testPreHandlePlacesStateInThreadLocal() throws Exception { + // 0. Precursor: no state should have been set. + assertThat(State.Holder.get()).isEqualTo(null); + + GrpcSQLCommenterInterceptor grpcsci = new GrpcSQLCommenterInterceptor(); + + // 1. A handler that isn't an instance of HandlerMethod should ALWAYS return true, + // but not set the state. + grpcsci.interceptCall(null, null, null); + + ServerCall serverCall = Mockito.mock(ServerCall.class); + when(serverCall.getMethodDescriptor()).thenReturn(flowMethod); + + // 2. A handler that is an instance of HandlerMethod should ALWAYS return true, + // and also set the thread local state. + grpcsci.interceptCall(serverCall, null, null); + // assertThat(ok).isEqualTo(null); + + // 2.2. Ensure that we can retrieve the newly inserted state from threadlocal storage. + ServerCallHandler serverCallHandler = Mockito.mock(ServerCallHandler.class); + // when(serverCallHandler.startCall(any(), any())).thenReturn(ok); + grpcsci.interceptCall(serverCall, null, serverCallHandler); + State state = State.Holder.get(); + assertThat(state).isNotEqualTo(null); + assertThat(state.toString()) + .isEqualTo( + "action='getStudentInfo',controller='com.google.cloud.sqlcommenter.grpc.backend.service.StudentServiceImpl',framework='spring-grpc'"); + // 2.3. Now with SQL that it is formatted alright. + assertThat(state.formatAndAppendToSQL("SELECT * from FOO")) + .isEqualTo( + "SELECT * from FOO /*action='getStudentInfo',controller='com.google.cloud.sqlcommenter.grpc.backend.service.StudentServiceImpl',framework='spring-grpc'*/"); + + // 3.0. On a subsequent call, the state should be over-written in the same thread. + flowMethod = + MethodDescriptor.newBuilder() + .setType(MethodType.UNKNOWN) + .setFullMethodName( + generateFullMethodName( + "com.google.cloud.sqlcommenter.grpc.backend.service.TeacherServiceImpl", + "getTeacherInfo")) + .setRequestMarshaller(requestMarshaller) + .setResponseMarshaller(responseMarshaller) + .build(); + when(serverCall.getMethodDescriptor()).thenReturn(flowMethod); + grpcsci.interceptCall(serverCall, null, serverCallHandler); + // assertThat(ok).isEqualTo(true); + + state = State.Holder.get(); + assertThat(state).isNotEqualTo(null); + assertThat(state.toString()) + .isEqualTo( + "action='getTeacherInfo',controller='com.google.cloud.sqlcommenter.grpc.backend.service.TeacherServiceImpl',framework='spring-grpc'"); + // 3.1. Now with SQL that it is formatted alright. + assertThat(state.formatAndAppendToSQL("SELECT * from FOO")) + .isEqualTo( + "SELECT * from FOO /*action='getTeacherInfo',controller='com.google.cloud.sqlcommenter.grpc.backend.service.TeacherServiceImpl',framework='spring-grpc'*/"); + + // 4.0 Ensure that in a separate thread the state doesn't pre-exist + Thread th2 = + new Thread( + () -> { + State state2 = State.Holder.get(); + // 4.1. Check that in a separate thread we start with a null State. + assertThat(state2).isEqualTo(null); + + try { + MethodDescriptor flowMethod2 = + MethodDescriptor.newBuilder() + .setType(MethodType.UNKNOWN) + .setFullMethodName( + generateFullMethodName( + "com.google.cloud.sqlcommenter.grpc.backend.service.TeacherServiceImplThreaded", + "getTeacherInfoThreaded")) + .setRequestMarshaller(requestMarshaller) + .setResponseMarshaller(responseMarshaller) + .build(); + ServerCallHandler serverCallHandler2 = + Mockito.mock(ServerCallHandler.class); + + ServerCall serverCall2 = Mockito.mock(ServerCall.class); + when(serverCall2.getMethodDescriptor()).thenReturn(flowMethod2); + + ServerCall.Listener ok2 = + grpcsci.interceptCall(serverCall2, null, serverCallHandler2); + + state2 = State.Holder.get(); + assertThat(state2.toString()) + .isEqualTo( + "action='getTeacherInfoThreaded',controller='com.google.cloud.sqlcommenter.grpc.backend.service.TeacherServiceImplThreaded',framework='spring-grpc'"); + // 3.1. Now with SQL that it is formatted alright. + assertThat(state2.formatAndAppendToSQL("SELECT * from FOO")) + .isEqualTo( + "SELECT * from FOO /*action='getTeacherInfoThreaded',controller='com.google.cloud.sqlcommenter.grpc.backend.service.TeacherServiceImplThreaded',framework='spring-grpc'*/"); + } catch (Exception e) { + e.printStackTrace(); + } + }); + th2.start(); + th2.join(); + + // 3.2. Ensure that the previous state didn't interleave with the current one, + // thus they are separated by being in different thread local storage. + // So that in 3.2. our state is as it was in 3.1. + assertThat(state.formatAndAppendToSQL("SELECT * from FOO")) + .isEqualTo( + "SELECT * from FOO /*action='getTeacherInfo',controller='com.google.cloud.sqlcommenter.grpc.backend.service.TeacherServiceImpl',framework='spring-grpc'*/"); + } +} diff --git a/java/sqlcommenter-java/src/test/proto/student.proto b/java/sqlcommenter-java/src/test/proto/student.proto new file mode 100644 index 00000000..faf3d569 --- /dev/null +++ b/java/sqlcommenter-java/src/test/proto/student.proto @@ -0,0 +1,25 @@ +// If we did not define the syntax, it would take the syntax as proto2 by default +syntax = "proto3"; + +// This is where the autogenerated java classes are gonna be stored +package com.google.cloud.sqlcommenter.grpc.stubs; + +// With this line, I am saying that create multiple classes rather than a single class +option java_multiple_files = true; + +// This is the request message +message StudentRequest{ + string student_id = 1; +} + +// This is the response message +message StudentResponse{ + string student_id = 1; + string name = 2; + int32 age = 3; +} + +// Now let's define our service +service StudentService{ + rpc getStudentInfo(StudentRequest) returns (StudentResponse); +} \ No newline at end of file diff --git a/java/sqlcommenter-java/src/test/resources/META-INF/persistence.xml b/java/sqlcommenter-java/src/test/resources/META-INF/persistence.xml new file mode 100644 index 00000000..dac8abd9 --- /dev/null +++ b/java/sqlcommenter-java/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,22 @@ + + + + + + com.google.cloud.sqlcommenter.grpc.backend.domain.Student + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/sqlcommenter-java/src/test/resources/data.sql b/java/sqlcommenter-java/src/test/resources/data.sql new file mode 100644 index 00000000..3547743a --- /dev/null +++ b/java/sqlcommenter-java/src/test/resources/data.sql @@ -0,0 +1,4 @@ +INSERT INTO student(studentId, name, age) VALUES ('st1', 'John', 17); +INSERT INTO student(studentId, name, age) VALUES ('st3', 'Azam', 16); +INSERT INTO student(studentId, name, age) VALUES ('st4', 'Lisa', 16); +INSERT INTO student(studentId, name, age) VALUES ('st5', 'Shehani', 16); \ No newline at end of file