diff --git a/pom.xml b/pom.xml index 5e248d3143..c187b2bc73 100644 --- a/pom.xml +++ b/pom.xml @@ -355,6 +355,7 @@ spring-cloud-gcp-storage spring-cloud-gcp-vision + spring-cloud-gcp-data-spanner @@ -380,8 +381,9 @@ ${project.build.outputDirectory} - true + true true + true diff --git a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/aot/SpannerRepositoryRuntimeHints.java b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/aot/SpannerRepositoryRuntimeHints.java new file mode 100644 index 0000000000..ad615cd41e --- /dev/null +++ b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/aot/SpannerRepositoryRuntimeHints.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 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 + * + * https://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.spring.data.spanner.aot; + +import java.util.Arrays; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; + +public class SpannerRepositoryRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints + .reflection() + .registerTypes( + Arrays.asList( + TypeReference.of( + com.google.cloud.spring.data.spanner.repository.support.SimpleSpannerRepository + .class)), + hint -> + hint.withMembers( + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_DECLARED_METHODS)); + } +} diff --git a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/aot/SpannerSchemaUtilsRuntimeHints.java b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/aot/SpannerSchemaUtilsRuntimeHints.java new file mode 100644 index 0000000000..9e4ac6025a --- /dev/null +++ b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/aot/SpannerSchemaUtilsRuntimeHints.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 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 + * + * https://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.spring.data.spanner.aot; + +import java.util.Arrays; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; + +public class SpannerSchemaUtilsRuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + // Called reflectively by org.springframework.expression.spel.support.ReflectiveMethodExecutor + // which is invoked transitively by SpannerPersistentEntityImpl#tableName() from + // SpannerSchemaUtils. + hints + .reflection() + .registerTypes( + Arrays.asList(TypeReference.of(java.lang.String.class)), + hint -> + hint.withMembers( + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_DECLARED_METHODS)); + } +} diff --git a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/admin/SpannerSchemaUtils.java b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/admin/SpannerSchemaUtils.java index 904df9a947..c91d48a59a 100644 --- a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/admin/SpannerSchemaUtils.java +++ b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/core/admin/SpannerSchemaUtils.java @@ -18,6 +18,7 @@ import com.google.cloud.spanner.Key; import com.google.cloud.spanner.Type; +import com.google.cloud.spring.data.spanner.aot.SpannerSchemaUtilsRuntimeHints; import com.google.cloud.spring.data.spanner.core.convert.ConversionUtils; import com.google.cloud.spring.data.spanner.core.convert.SpannerEntityProcessor; import com.google.cloud.spring.data.spanner.core.convert.SpannerTypeMapper; @@ -32,6 +33,7 @@ import java.util.Set; import java.util.StringJoiner; import java.util.function.BiFunction; +import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.data.mapping.PropertyHandler; import org.springframework.util.Assert; @@ -40,6 +42,7 @@ * * @since 1.1 */ +@ImportRuntimeHints(SpannerSchemaUtilsRuntimeHints.class) public class SpannerSchemaUtils { private final SpannerMappingContext mappingContext; diff --git a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/support/SpannerRepositoryFactoryBean.java b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/support/SpannerRepositoryFactoryBean.java index 06c9ad8bec..898cbc4c73 100644 --- a/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/support/SpannerRepositoryFactoryBean.java +++ b/spring-cloud-gcp-data-spanner/src/main/java/com/google/cloud/spring/data/spanner/repository/support/SpannerRepositoryFactoryBean.java @@ -16,11 +16,13 @@ package com.google.cloud.spring.data.spanner.repository.support; +import com.google.cloud.spring.data.spanner.aot.SpannerRepositoryRuntimeHints; import com.google.cloud.spring.data.spanner.core.SpannerTemplate; import com.google.cloud.spring.data.spanner.core.mapping.SpannerMappingContext; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport; import org.springframework.data.repository.core.support.RepositoryFactorySupport; @@ -34,6 +36,7 @@ * @param the repository type * @since 1.1 */ +@ImportRuntimeHints(SpannerRepositoryRuntimeHints.class) public class SpannerRepositoryFactoryBean, S, I> extends RepositoryFactoryBeanSupport implements ApplicationContextAware { diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/aot/CommitTimestampsRuntimeHints.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/aot/CommitTimestampsRuntimeHints.java new file mode 100644 index 0000000000..43fef9bd89 --- /dev/null +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/aot/CommitTimestampsRuntimeHints.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023 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 + * + * https://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.spring.data.spanner.aot; + +import java.util.Arrays; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; + +public class CommitTimestampsRuntimeHints implements RuntimeHintsRegistrar { + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + hints + .reflection() + .registerTypes( + Arrays.asList( + TypeReference.of( + com.google.cloud.spring.data.spanner.test.domain.CommitTimestamps.class)), + hint -> + hint.withMembers( + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS, + MemberCategory.DECLARED_CLASSES, + MemberCategory.DECLARED_FIELDS)); + } +} diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/aot/CommitTimestampsRuntimeHintsTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/aot/CommitTimestampsRuntimeHintsTests.java new file mode 100644 index 0000000000..eae35c914b --- /dev/null +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/aot/CommitTimestampsRuntimeHintsTests.java @@ -0,0 +1,34 @@ +/* + * Copyright 2023 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 + * + * https://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.spring.data.spanner.aot; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection; + +import com.google.cloud.spring.data.spanner.test.domain.CommitTimestamps; +import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.RuntimeHints; + +class CommitTimestampsRuntimeHintsTests { + @Test + void registerCommitTimestamps() { + RuntimeHints runtimeHints = new RuntimeHints(); + CommitTimestampsRuntimeHints registrar = new CommitTimestampsRuntimeHints(); + registrar.registerHints(runtimeHints, null); + assertThat(runtimeHints).matches(reflection().onType(CommitTimestamps.class)); + } +} diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/aot/SpannerRepositoryRuntimeHintsTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/aot/SpannerRepositoryRuntimeHintsTests.java new file mode 100644 index 0000000000..57adce3877 --- /dev/null +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/aot/SpannerRepositoryRuntimeHintsTests.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023 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 + * + * https://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.spring.data.spanner.aot; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection; + +import com.google.cloud.spring.data.spanner.repository.support.SimpleSpannerRepository; +import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.RuntimeHints; + +class SpannerRepositoryRuntimeHintsTests { + + @Test + void registerSimpleSpannerRepository() { + RuntimeHints runtimeHints = new RuntimeHints(); + SpannerRepositoryRuntimeHints registrar = new SpannerRepositoryRuntimeHints(); + registrar.registerHints(runtimeHints, null); + assertThat(runtimeHints).matches(reflection().onType(SimpleSpannerRepository.class)); + } +} diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/aot/SpannerSchemaUtilsRuntimeHintsTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/aot/SpannerSchemaUtilsRuntimeHintsTests.java new file mode 100644 index 0000000000..3c7b2bffeb --- /dev/null +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/aot/SpannerSchemaUtilsRuntimeHintsTests.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023 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 + * + * https://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.spring.data.spanner.aot; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.reflection; + +import org.junit.jupiter.api.Test; +import org.springframework.aot.hint.RuntimeHints; + +class SpannerSchemaUtilsRuntimeHintsTests { + @Test + void registerString() { + RuntimeHints runtimeHints = new RuntimeHints(); + SpannerSchemaUtilsRuntimeHints registrar = new SpannerSchemaUtilsRuntimeHints(); + registrar.registerHints(runtimeHints, null); + assertThat(runtimeHints) + .matches( + reflection().onType(String.class)); + } +} diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/convert/it/CommitTimestampIntegrationTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/convert/it/CommitTimestampIntegrationTests.java index 9f884e74a0..4a05f197f9 100644 --- a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/convert/it/CommitTimestampIntegrationTests.java +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/core/convert/it/CommitTimestampIntegrationTests.java @@ -26,6 +26,7 @@ import com.google.cloud.Timestamp; import com.google.cloud.spanner.DatabaseClient; import com.google.cloud.spanner.Key; +import com.google.cloud.spring.data.spanner.aot.CommitTimestampsRuntimeHints; import com.google.cloud.spring.data.spanner.core.SpannerMutationFactory; import com.google.cloud.spring.data.spanner.core.SpannerOperations; import com.google.cloud.spring.data.spanner.core.convert.CommitTimestamp; @@ -40,12 +41,14 @@ import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.ImportRuntimeHints; import org.springframework.core.convert.converter.Converter; import org.springframework.test.context.junit.jupiter.SpringExtension; /** Integration tests for the {@link CommitTimestamp} feature. */ @EnabledIfSystemProperty(named = "it.spanner", matches = "true") @ExtendWith(SpringExtension.class) +@ImportRuntimeHints(CommitTimestampsRuntimeHints.class) class CommitTimestampIntegrationTests extends AbstractSpannerIntegrationTest { @Autowired private SpannerOperations spannerOperations; diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/it/SpannerRepositoryInsertIntegrationTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/it/SpannerRepositoryInsertIntegrationTests.java index fdd9c6ddcd..beaeba1d7c 100644 --- a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/it/SpannerRepositoryInsertIntegrationTests.java +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/it/SpannerRepositoryInsertIntegrationTests.java @@ -17,7 +17,6 @@ package com.google.cloud.spring.data.spanner.repository.it; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assumptions.assumeThat; import com.google.cloud.spring.data.spanner.core.SpannerTemplate; import com.google.cloud.spring.data.spanner.core.admin.SpannerDatabaseAdminTemplate; diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/it/SpannerRepositoryIntegrationTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/it/SpannerRepositoryIntegrationTests.java index b4ca819492..5b724aab10 100644 --- a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/it/SpannerRepositoryIntegrationTests.java +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/it/SpannerRepositoryIntegrationTests.java @@ -18,15 +18,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import com.google.cloud.Timestamp; import com.google.cloud.spanner.Key; -import com.google.cloud.spanner.Statement; import com.google.cloud.spanner.Struct; -import com.google.cloud.spring.data.spanner.core.SpannerQueryOptions; -import com.google.cloud.spring.data.spanner.core.SpannerTemplate; import com.google.cloud.spring.data.spanner.core.mapping.SpannerMappingContext; import com.google.cloud.spring.data.spanner.core.mapping.SpannerPersistentEntity; import com.google.cloud.spring.data.spanner.repository.support.SimpleSpannerRepository; @@ -52,9 +47,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIfSystemProperty; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -82,8 +75,6 @@ public class SpannerRepositoryIntegrationTests extends AbstractSpannerIntegratio @Autowired SpannerMappingContext spannerMappingContext; - @SpyBean SpannerTemplate spannerTemplate; - @BeforeEach @AfterEach void cleanUpData() { @@ -474,8 +465,6 @@ void queryMethodsTest_ParentChildOperations() { @Test void queryMethodsTest_EagerFetch() { - Mockito.clearInvocations(spannerTemplate); - final Trade aTrade = Trade.makeTrade("trader1", 0, 0); aTrade.setAction("BUY"); aTrade.setSymbol("ABCD"); @@ -488,9 +477,6 @@ void queryMethodsTest_EagerFetch() { .hasValueSatisfying(t -> assertThat(t.getSymbol()).isEqualTo(aTrade.getSymbol())) .hasValueSatisfying( t -> assertThat(t.getSubTrades()).hasSize(aTrade.getSubTrades().size())); - Mockito.verify(spannerTemplate, Mockito.times(1)).executeQuery(any(Statement.class), any()); - Mockito.verify(spannerTemplate, Mockito.times(1)) - .query(eq(Trade.class), any(Statement.class), any(SpannerQueryOptions.class)); } @Test @@ -620,12 +606,11 @@ void findAllByIdReturnsOnlyRequestedRows() { Trade trade3 = insertTrade("trader2", "SELL", 102); insertTrade("trader2", "SELL", 103); - - Iterable foundTrades = this.tradeRepository.findAllById( - Arrays.asList( - Key.of(trade2.getTradeDetail().getId(), trade2.getTraderId()), - Key.of(trade3.getTradeDetail().getId(), trade3.getTraderId()) - )); + Iterable foundTrades = + this.tradeRepository.findAllById( + Arrays.asList( + Key.of(trade2.getTradeDetail().getId(), trade2.getTraderId()), + Key.of(trade3.getTradeDetail().getId(), trade3.getTraderId()))); assertThat(foundTrades).containsExactlyInAnyOrder(trade2, trade3); } diff --git a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/query/SqlSpannerQueryTests.java b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/query/SqlSpannerQueryTests.java index 71732cba8c..a0d1ac9b93 100644 --- a/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/query/SqlSpannerQueryTests.java +++ b/spring-cloud-gcp-data-spanner/src/test/java/com/google/cloud/spring/data/spanner/repository/query/SqlSpannerQueryTests.java @@ -18,6 +18,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; @@ -178,6 +179,8 @@ void noPageableParamQueryTest() throws NoSuchMethodException { sqlSpannerQuery.execute(new Object[] {}); verify(this.spannerTemplate, times(1)).executeQuery(any(), any()); + verify(this.spannerTemplate, times(1)) + .query(eq(Trade.class), any(Statement.class), any(SpannerQueryOptions.class)); } @Test