From 824933b426c53d56777384a7a58bb3d43b1f3f4d Mon Sep 17 00:00:00 2001 From: Andrus Adamchik Date: Sat, 24 Aug 2024 17:29:10 -0400 Subject: [PATCH] Support for Cayenne 5.0-M1 #114 --- RELEASE-NOTES.md | 1 + bootique-cayenne50-jcache/pom.xml | 134 +++++++ .../v50/jcache/CayenneJCacheModule.java | 96 +++++ .../jcache/CayenneJCacheModuleExtender.java | 62 ++++ .../META-INF/services/io.bootique.BQModule | 1 + .../v50/jcache/CayenneJCacheModuleIT.java | 96 +++++ .../v50/jcache/CayenneJCacheModuleTest.java | 33 ++ .../invalidation/CacheInvalidationIT.java | 163 ++++++++ .../cayenne/v50/jcache/persistent/Table1.java | 29 ++ .../cayenne/v50/jcache/persistent/Table2.java | 30 ++ .../v50/jcache/persistent/auto/_Table1.java | 71 ++++ .../v50/jcache/persistent/auto/_Table2.java | 91 +++++ .../src/test/resources/bq1.yml | 18 + .../src/test/resources/cayenne-project1.xml | 7 + .../src/test/resources/datamap1.map.xml | 48 +++ bootique-cayenne50-junit5/pom.xml | 98 +++++ .../cayenne/v50/junit5/CayenneTester.java | 305 +++++++++++++++ .../junit5/tester/CayenneRuntimeManager.java | 111 ++++++ .../tester/CayenneRuntimeManagerBuilder.java | 192 ++++++++++ .../tester/CayenneTesterCallbackType.java | 28 ++ .../tester/CayenneTesterLifecycleManager.java | 142 +++++++ .../v50/junit5/tester/CommitCounter.java | 54 +++ .../v50/junit5/tester/FilteredDataMap.java | 71 ++++ .../tester/ModelDependencyResolver.java | 42 +++ .../v50/junit5/tester/QueryCounter.java | 55 +++ .../v50/junit5/tester/RelatedEntity.java | 75 ++++ .../v50/junit5/CayenneTester_AllTablesIT.java | 76 ++++ .../CayenneTester_AssertOpCountsIT.java | 86 +++++ .../CayenneTester_CachesNotRefreshedIT.java | 80 ++++ .../CayenneTester_CachesRefreshedIT.java | 80 ++++ .../CayenneTester_DeleteBeforeEachTestIT.java | 77 ++++ .../junit5/CayenneTester_DependenciesIT.java | 109 ++++++ ...CayenneTester_IndirectRuntimeAccessIT.java | 72 ++++ .../CayenneTester_ModuleWithTestHooksIT.java | 57 +++ .../v50/junit5/CayenneTester_RunAppIT.java | 74 ++++ .../junit5/CayenneTester_UnattachedIT.java | 58 +++ .../v50/junit5/persistence/Table1.java | 9 + .../v50/junit5/persistence/Table2.java | 9 + .../v50/junit5/persistence/auto/_Table1.java | 111 ++++++ .../v50/junit5/persistence/auto/_Table2.java | 92 +++++ .../cayenne/v50/junit5/persistence3/P3T1.java | 9 + .../cayenne/v50/junit5/persistence3/P3T2.java | 9 + .../cayenne/v50/junit5/persistence3/P3T3.java | 9 + .../cayenne/v50/junit5/persistence3/P3T4.java | 9 + .../v50/junit5/persistence3/auto/_P3T1.java | 139 +++++++ .../v50/junit5/persistence3/auto/_P3T2.java | 91 +++++ .../v50/junit5/persistence3/auto/_P3T3.java | 91 +++++ .../v50/junit5/persistence3/auto/_P3T4.java | 97 +++++ .../src/test/resources/cayenne-project2.xml | 7 + .../src/test/resources/cayenne-project3.xml | 7 + .../test/resources/config-noautocommit.yml | 24 ++ .../src/test/resources/config2.yml | 18 + .../src/test/resources/config3.yml | 18 + .../src/test/resources/datamap2.map.xml | 41 +++ .../src/test/resources/datamap3.map.xml | 75 ++++ bootique-cayenne50/pom.xml | 143 ++++++++ .../v50/BQCayenneDataSourceFactory.java | 134 +++++++ .../cayenne/v50/CayenneConfigMerger.java | 33 ++ .../bootique/cayenne/v50/CayenneModule.java | 70 ++++ .../cayenne/v50/CayenneModuleExtender.java | 291 +++++++++++++++ .../cayenne/v50/CayenneRuntimeFactory.java | 347 ++++++++++++++++++ .../cayenne/v50/CayenneStartupListener.java | 33 ++ .../bootique/cayenne/v50/DataMapConfig.java | 60 +++ .../cayenne/v50/DefaultDataSourceName.java | 36 ++ .../v50/SyntheticNodeDataDomainProvider.java | 192 ++++++++++ .../v50/annotation/CayenneConfigs.java | 36 ++ .../v50/annotation/CayenneListener.java | 36 ++ .../v50/commitlog/CommitLogListenerGraph.java | 148 ++++++++ .../v50/commitlog/CommitLogModuleBuilder.java | 166 +++++++++ .../commitlog/MappedCommitLogListener.java | 49 +++ .../MappedCommitLogListenerType.java | 47 +++ .../MappedDataChannelSyncFilter.java | 43 +++ .../MappedDataChannelSyncFilterType.java | 43 +++ .../META-INF/services/io.bootique.BQModule | 1 + .../bootique/cayenne/v50/CayenneModuleIT.java | 268 ++++++++++++++ .../cayenne/v50/CayenneModuleTest.java | 34 ++ ...eModule_CommitLogListenersAnnotatedIT.java | 107 ++++++ .../CayenneModule_CommitLogListenersIT.java | 97 +++++ ...yenneModule_CommitLogListenersOrderIT.java | 110 ++++++ .../v50/CayenneModule_ListenersIT.java | 100 +++++ .../cayenne/v50/CayenneStartupListenerIT.java | 80 ++++ .../cayenne/v50/DataChannelQueryFilterIT.java | 71 ++++ .../cayenne/v50/DataChannelSyncFilterIT.java | 86 +++++ .../io/bootique/cayenne/v50/cayenne/T1.java | 9 + .../io/bootique/cayenne/v50/cayenne/T2.java | 9 + .../cayenne/v50/cayenne/auto/_T1.java | 94 +++++ .../cayenne/v50/cayenne/auto/_T2.java | 94 +++++ ...s_Plus_AddProject_DataSourceAssignment.yml | 31 ++ .../src/test/resources/cayenne-project1.xml | 7 + .../src/test/resources/cayenne-project2.xml | 7 + .../test/resources/config_explicit_maps.yml | 30 ++ .../test/resources/config_explicit_maps_2.yml | 32 ++ .../resources/config_explicit_maps_2_1.yml | 26 ++ .../resources/config_explicit_maps_2_2.yml | 26 ++ .../src/test/resources/datamap1.map.xml | 9 + .../src/test/resources/datamap2.map.xml | 9 + .../src/test/resources/datamap3.map.xml | 9 + .../src/test/resources/explicitconfig.yml | 24 ++ .../src/test/resources/fullconfig.yml | 24 ++ .../src/test/resources/genericconfig.yml | 24 ++ .../bootique/cayenne/v50/cayenne-explicit.xml | 7 + .../bootique/cayenne/v50/cayenne-generic.xml | 7 + .../io/bootique/cayenne/v50/explicit.map.xml | 21 ++ .../io/bootique/cayenne/v50/generic.map.xml | 13 + .../src/test/resources/noconfig-named.yml | 21 ++ .../src/test/resources/noconfig.yml | 20 + .../src/test/resources/noconfig_2ds.yml | 20 + .../test/resources/noconfig_2ds_unmatched.yml | 23 ++ .../src/test/resources/twoconfigs.yml | 23 ++ pom.xml | 6 +- 110 files changed, 7172 insertions(+), 1 deletion(-) create mode 100644 bootique-cayenne50-jcache/pom.xml create mode 100644 bootique-cayenne50-jcache/src/main/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModule.java create mode 100644 bootique-cayenne50-jcache/src/main/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModuleExtender.java create mode 100644 bootique-cayenne50-jcache/src/main/resources/META-INF/services/io.bootique.BQModule create mode 100644 bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModuleIT.java create mode 100644 bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModuleTest.java create mode 100644 bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/invalidation/CacheInvalidationIT.java create mode 100644 bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/Table1.java create mode 100644 bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/Table2.java create mode 100644 bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/auto/_Table1.java create mode 100644 bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/auto/_Table2.java create mode 100644 bootique-cayenne50-jcache/src/test/resources/bq1.yml create mode 100644 bootique-cayenne50-jcache/src/test/resources/cayenne-project1.xml create mode 100644 bootique-cayenne50-jcache/src/test/resources/datamap1.map.xml create mode 100644 bootique-cayenne50-junit5/pom.xml create mode 100644 bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/CayenneTester.java create mode 100644 bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneRuntimeManager.java create mode 100644 bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneRuntimeManagerBuilder.java create mode 100644 bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneTesterCallbackType.java create mode 100644 bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneTesterLifecycleManager.java create mode 100644 bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CommitCounter.java create mode 100644 bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/FilteredDataMap.java create mode 100644 bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/ModelDependencyResolver.java create mode 100644 bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/QueryCounter.java create mode 100644 bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/RelatedEntity.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_AllTablesIT.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_AssertOpCountsIT.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_CachesNotRefreshedIT.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_CachesRefreshedIT.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_DeleteBeforeEachTestIT.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_DependenciesIT.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_IndirectRuntimeAccessIT.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_ModuleWithTestHooksIT.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_RunAppIT.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_UnattachedIT.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/Table1.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/Table2.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/auto/_Table1.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/auto/_Table2.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T1.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T2.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T3.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T4.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T1.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T2.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T3.java create mode 100644 bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T4.java create mode 100644 bootique-cayenne50-junit5/src/test/resources/cayenne-project2.xml create mode 100644 bootique-cayenne50-junit5/src/test/resources/cayenne-project3.xml create mode 100644 bootique-cayenne50-junit5/src/test/resources/config-noautocommit.yml create mode 100644 bootique-cayenne50-junit5/src/test/resources/config2.yml create mode 100644 bootique-cayenne50-junit5/src/test/resources/config3.yml create mode 100644 bootique-cayenne50-junit5/src/test/resources/datamap2.map.xml create mode 100644 bootique-cayenne50-junit5/src/test/resources/datamap3.map.xml create mode 100644 bootique-cayenne50/pom.xml create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/BQCayenneDataSourceFactory.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneConfigMerger.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneModule.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneModuleExtender.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneRuntimeFactory.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneStartupListener.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/DataMapConfig.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/DefaultDataSourceName.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/SyntheticNodeDataDomainProvider.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/annotation/CayenneConfigs.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/annotation/CayenneListener.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/CommitLogListenerGraph.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/CommitLogModuleBuilder.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/MappedCommitLogListener.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/MappedCommitLogListenerType.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/syncfilter/MappedDataChannelSyncFilter.java create mode 100644 bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/syncfilter/MappedDataChannelSyncFilterType.java create mode 100644 bootique-cayenne50/src/main/resources/META-INF/services/io.bootique.BQModule create mode 100644 bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModuleIT.java create mode 100644 bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModuleTest.java create mode 100644 bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_CommitLogListenersAnnotatedIT.java create mode 100644 bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_CommitLogListenersIT.java create mode 100644 bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_CommitLogListenersOrderIT.java create mode 100644 bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_ListenersIT.java create mode 100644 bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneStartupListenerIT.java create mode 100644 bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/DataChannelQueryFilterIT.java create mode 100644 bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/DataChannelSyncFilterIT.java create mode 100644 bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/T1.java create mode 100644 bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/T2.java create mode 100644 bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/auto/_T1.java create mode 100644 bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/auto/_T2.java create mode 100644 bootique-cayenne50/src/test/resources/ConfigMaps_Plus_AddProject_DataSourceAssignment.yml create mode 100644 bootique-cayenne50/src/test/resources/cayenne-project1.xml create mode 100644 bootique-cayenne50/src/test/resources/cayenne-project2.xml create mode 100644 bootique-cayenne50/src/test/resources/config_explicit_maps.yml create mode 100644 bootique-cayenne50/src/test/resources/config_explicit_maps_2.yml create mode 100644 bootique-cayenne50/src/test/resources/config_explicit_maps_2_1.yml create mode 100644 bootique-cayenne50/src/test/resources/config_explicit_maps_2_2.yml create mode 100644 bootique-cayenne50/src/test/resources/datamap1.map.xml create mode 100644 bootique-cayenne50/src/test/resources/datamap2.map.xml create mode 100644 bootique-cayenne50/src/test/resources/datamap3.map.xml create mode 100644 bootique-cayenne50/src/test/resources/explicitconfig.yml create mode 100644 bootique-cayenne50/src/test/resources/fullconfig.yml create mode 100644 bootique-cayenne50/src/test/resources/genericconfig.yml create mode 100644 bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/cayenne-explicit.xml create mode 100644 bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/cayenne-generic.xml create mode 100644 bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/explicit.map.xml create mode 100644 bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/generic.map.xml create mode 100644 bootique-cayenne50/src/test/resources/noconfig-named.yml create mode 100644 bootique-cayenne50/src/test/resources/noconfig.yml create mode 100644 bootique-cayenne50/src/test/resources/noconfig_2ds.yml create mode 100644 bootique-cayenne50/src/test/resources/noconfig_2ds_unmatched.yml create mode 100644 bootique-cayenne50/src/test/resources/twoconfigs.yml diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 472a7fe8..8052b9b4 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -2,6 +2,7 @@ * #112 Ensure Bootique-provided version of JCache is used * #113 Upgrade Cayenne to 4.2.1 +* #114 Support for Cayenne 5.0-M1 ## 3.0-M3 diff --git a/bootique-cayenne50-jcache/pom.xml b/bootique-cayenne50-jcache/pom.xml new file mode 100644 index 00000000..e597df82 --- /dev/null +++ b/bootique-cayenne50-jcache/pom.xml @@ -0,0 +1,134 @@ + + + + + + 4.0.0 + + io.bootique.cayenne + bootique-cayenne-parent + 3.0-SNAPSHOT + + + bootique-cayenne50-jcache + jar + + bootique-cayenne50-jcache: Integrates bootique-cayenne with bootique-jcache + Integrates bootique-cayenne with bootique-jcache. + + + + + io.bootique.cayenne + bootique-cayenne50 + ${bootique.version} + + + org.apache.cayenne + cayenne-jcache + ${cayenne50.version} + + + org.apache.cayenne + cayenne-cache-invalidation + ${cayenne50.version} + + + + + + + + io.bootique.cayenne + bootique-cayenne50 + + + org.apache.cayenne + cayenne-jcache + + + javax.cache + cache-api + + + + + io.bootique.jcache + bootique-jcache + + + org.apache.cayenne + cayenne-cache-invalidation + + + + + org.mockito + mockito-core + test + + + org.ehcache + ehcache + test + + + org.slf4j + slf4j-simple + test + + + io.bootique.cayenne + bootique-cayenne50-junit5 + test + ${bootique.version} + + + io.bootique.jdbc + bootique-jdbc-junit5-derby + test + ${bootique.version} + + + + + + + gpg + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + + + + diff --git a/bootique-cayenne50-jcache/src/main/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModule.java b/bootique-cayenne50-jcache/src/main/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModule.java new file mode 100644 index 00000000..5b1cde8b --- /dev/null +++ b/bootique-cayenne50-jcache/src/main/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModule.java @@ -0,0 +1,96 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.jcache; + +import io.bootique.BQModule; +import io.bootique.ModuleCrate; +import io.bootique.cayenne.v50.CayenneModule; +import io.bootique.di.Binder; +import io.bootique.di.Key; +import io.bootique.di.Provides; +import org.apache.cayenne.cache.invalidation.CacheInvalidationModule; +import org.apache.cayenne.cache.invalidation.CacheInvalidationModuleExtender; +import org.apache.cayenne.cache.invalidation.InvalidationHandler; + +import javax.cache.CacheManager; +import javax.inject.Qualifier; +import javax.inject.Singleton; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Set; + +/** + * Bootique DI module integrating bootique-jcache to Cayenne. + */ +public class CayenneJCacheModule implements BQModule { + + /** + * @param binder DI binder passed to the Module that invokes this method. + * @return an instance of {@link CayenneJCacheModuleExtender} that can be used to load Cayenne cache + * custom extensions. + */ + public static CayenneJCacheModuleExtender extend(Binder binder) { + return new CayenneJCacheModuleExtender(binder); + } + + @Override + public ModuleCrate crate() { + return ModuleCrate.of(this) + .description("Integrates Apache Cayenne 4.2 JCache extensions") + .build(); + } + + @Override + public void configure(Binder binder) { + extend(binder).initAllExtensions(); + + CayenneModule.extend(binder).addModule(Key.get(org.apache.cayenne.di.Module.class, DefinedInCayenneJCache.class)); + } + + @Singleton + @Provides + @DefinedInCayenneJCache + org.apache.cayenne.di.Module provideDiJCacheModule(CacheManager cacheManager, Set invalidationHandlers) { + // return module composition + return b -> { + createInvalidationModule(invalidationHandlers).configure(b); + createOverridesModule(cacheManager).configure(b); + }; + } + + protected org.apache.cayenne.di.Module createInvalidationModule(Set invalidationHandlers) { + return b -> { + CacheInvalidationModuleExtender extender = CacheInvalidationModule.extend(b); + invalidationHandlers.forEach(extender::addHandler); + }; + } + + protected org.apache.cayenne.di.Module createOverridesModule(CacheManager cacheManager) { + return b -> b.bind(CacheManager.class).toInstance(cacheManager); + } + + @Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @Qualifier + @interface DefinedInCayenneJCache { + } +} diff --git a/bootique-cayenne50-jcache/src/main/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModuleExtender.java b/bootique-cayenne50-jcache/src/main/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModuleExtender.java new file mode 100644 index 00000000..17a644cc --- /dev/null +++ b/bootique-cayenne50-jcache/src/main/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModuleExtender.java @@ -0,0 +1,62 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.jcache; + +import io.bootique.ModuleExtender; +import io.bootique.di.Binder; +import io.bootique.di.SetBuilder; +import io.bootique.jcache.JCacheModule; +import org.apache.cayenne.cache.invalidation.InvalidationHandler; +import org.apache.cayenne.jcache.JCacheConstants; + +import javax.cache.configuration.Configuration; + +public class CayenneJCacheModuleExtender extends ModuleExtender { + + public CayenneJCacheModuleExtender(Binder binder) { + super(binder); + } + + @Override + public CayenneJCacheModuleExtender initAllExtensions() { + contributeInvalidationHandler(); + return this; + } + + public CayenneJCacheModuleExtender addInvalidationHandler(InvalidationHandler handler) { + contributeInvalidationHandler().addInstance(handler); + return this; + } + + public CayenneJCacheModuleExtender addInvalidationHandler(Class handlerType) { + contributeInvalidationHandler().add(handlerType); + return this; + } + + // TODO: we actually know key and value types for Cayenne QueryCache config + public CayenneJCacheModuleExtender setDefaultCacheConfiguration(Configuration config) { + JCacheModule.extend(binder).setConfiguration(JCacheConstants.DEFAULT_CACHE_NAME, config); + return this; + } + + protected SetBuilder contributeInvalidationHandler() { + return newSet(InvalidationHandler.class); + } +} diff --git a/bootique-cayenne50-jcache/src/main/resources/META-INF/services/io.bootique.BQModule b/bootique-cayenne50-jcache/src/main/resources/META-INF/services/io.bootique.BQModule new file mode 100644 index 00000000..6de352c5 --- /dev/null +++ b/bootique-cayenne50-jcache/src/main/resources/META-INF/services/io.bootique.BQModule @@ -0,0 +1 @@ +io.bootique.cayenne.v50.jcache.CayenneJCacheModule \ No newline at end of file diff --git a/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModuleIT.java b/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModuleIT.java new file mode 100644 index 00000000..8deee5d8 --- /dev/null +++ b/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModuleIT.java @@ -0,0 +1,96 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.jcache; + +import io.bootique.BQRuntime; +import io.bootique.Bootique; +import io.bootique.cayenne.v50.jcache.persistent.Table1; +import io.bootique.cayenne.v50.junit5.CayenneTester; +import io.bootique.jdbc.junit5.derby.DerbyTester; +import io.bootique.junit5.BQApp; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.cache.QueryCache; +import org.apache.cayenne.jcache.JCacheQueryCache; +import org.apache.cayenne.query.ObjectSelect; +import org.junit.jupiter.api.Test; + +import javax.cache.CacheManager; + +import static org.junit.jupiter.api.Assertions.*; + +@BQTest +public class CayenneJCacheModuleIT { + + @BQTestTool + static final DerbyTester db = DerbyTester.db(); + + @BQTestTool + static final CayenneTester cayenne = CayenneTester + .create() + .entities(Table1.class) + .deleteBeforeEachTest(); + + @BQApp(skipRun = true) + static final BQRuntime runtime = Bootique.app("-c", "classpath:bq1.yml") + .autoLoadModules() + .module(db.moduleWithTestDataSource("db")) + .module(cayenne.moduleWithTestHooks()) + .createRuntime(); + + @Test + public void cacheProvider() { + QueryCache cache = cayenne.getRuntime().getInjector().getInstance(QueryCache.class); + assertTrue(cache instanceof JCacheQueryCache, "Unexpected cache type: " + cache.getClass().getName()); + } + + @Test + public void cacheManager() { + CacheManager cacheManager = runtime.getInstance(CacheManager.class); + assertTrue(cacheManager.getClass().getName().startsWith("org.ehcache.jsr107"), + "Unexpected cache type: " + cacheManager.getClass().getName()); + + CacheManager expectedCacheManager = runtime.getInstance(CacheManager.class); + assertSame(expectedCacheManager, cacheManager); + } + + @Test + public void cachedQueries() { + + ObjectContext context = cayenne.getRuntime().newContext(); + ObjectSelect g1 = ObjectSelect.query(Table1.class).localCache("g1"); + ObjectSelect g2 = ObjectSelect.query(Table1.class).localCache("g2"); + + db.getTable(cayenne.getTableName(Table1.class)).insert(1).insert(45); + assertEquals(2, g1.select(context).size()); + + // we are still cached, must not see the new changes + db.getTable(cayenne.getTableName(Table1.class)).insert(2).insert(44); + assertEquals(2, g1.select(context).size()); + + // different cache group - must see the changes + assertEquals(4, g2.select(context).size()); + + // refresh the cache, so that "g1" could see the changes + cayenne.getRuntime().getDataDomain().getQueryCache().removeGroup("g1"); + assertEquals(4, g1.select(context).size()); + } +} diff --git a/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModuleTest.java b/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModuleTest.java new file mode 100644 index 00000000..6857309c --- /dev/null +++ b/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/CayenneJCacheModuleTest.java @@ -0,0 +1,33 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.jcache; + +import io.bootique.junit5.BQModuleTester; +import io.bootique.junit5.BQTest; +import org.junit.jupiter.api.Test; + +@BQTest +public class CayenneJCacheModuleTest { + + @Test + public void autoLoadable() { + BQModuleTester.of(CayenneJCacheModule.class).testAutoLoadable().testConfig(); + } +} diff --git a/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/invalidation/CacheInvalidationIT.java b/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/invalidation/CacheInvalidationIT.java new file mode 100644 index 00000000..a3cf39ae --- /dev/null +++ b/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/invalidation/CacheInvalidationIT.java @@ -0,0 +1,163 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.jcache.invalidation; + +import io.bootique.BQRuntime; +import io.bootique.Bootique; +import io.bootique.cayenne.v50.jcache.CayenneJCacheModule; +import io.bootique.cayenne.v50.jcache.persistent.Table1; +import io.bootique.cayenne.v50.jcache.persistent.Table2; +import io.bootique.cayenne.v50.junit5.CayenneTester; +import io.bootique.jdbc.junit5.derby.DerbyTester; +import io.bootique.junit5.BQApp; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.cache.invalidation.CacheGroupDescriptor; +import org.apache.cayenne.cache.invalidation.CacheGroups; +import org.apache.cayenne.cache.invalidation.InvalidationHandler; +import org.apache.cayenne.query.ObjectSelect; +import org.junit.jupiter.api.Test; + +import javax.cache.Cache; +import javax.cache.CacheManager; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +@BQTest +public class CacheInvalidationIT { + static final InvalidationHandler invalidationHandler = + type -> type.getAnnotation(CacheGroups.class) == null + ? p -> asList(new CacheGroupDescriptor("cayenne1"), new CacheGroupDescriptor("nocayenne1")) + : null; + + @BQTestTool + static final DerbyTester db = DerbyTester.db(); + + @BQTestTool + static final CayenneTester cayenne = CayenneTester + .create() + .entities(Table1.class, Table2.class) + .deleteBeforeEachTest(); + + @BQApp(skipRun = true) + static final BQRuntime runtime = Bootique.app("-c", "classpath:bq1.yml") + .autoLoadModules() + .module(b -> CayenneJCacheModule.extend(b).addInvalidationHandler(invalidationHandler)) + .module(db.moduleWithTestDataSource("db")) + .module(cayenne.moduleWithTestHooks()) + .createRuntime(); + + @Test + public void invalidate_CustomHandler() { + + ObjectContext context = cayenne.getRuntime().newContext(); + // no explicit cache group must still work - it lands inside default cache called 'cayenne.default.cache' + ObjectSelect g0 = ObjectSelect.query(Table1.class).localCache(); + ObjectSelect g1 = ObjectSelect.query(Table1.class).localCache("cayenne1"); + ObjectSelect g2 = ObjectSelect.query(Table1.class).localCache("cayenne2"); + + assertEquals(0, g0.select(context).size()); + assertEquals(0, g1.select(context).size()); + assertEquals(0, g2.select(context).size()); + + db.getTable(cayenne.getTableName(Table1.class)).insert(1).insert(2); + + // inserted via SQL... query results are still cached... + assertEquals(0, g0.select(context).size()); + assertEquals(0, g1.select(context).size()); + assertEquals(0, g2.select(context).size()); + + + Table1 t11 = context.newObject(Table1.class); + context.commitChanges(); + + // inserted via Cayenne... "g1" should get auto refreshed... + assertEquals(0, g0.select(context).size()); + assertEquals(3, g1.select(context).size()); + assertEquals(0, g2.select(context).size()); + + + context.deleteObject(t11); + context.commitChanges(); + + // deleted via Cayenne... "g1" should get auto refreshed + assertEquals(0, g0.select(context).size()); + assertEquals(2, g1.select(context).size()); + assertEquals(0, g2.select(context).size()); + } + + @Test + public void invalidate_CacheGroup() { + + ObjectContext context = cayenne.getRuntime().newContext(); + ObjectSelect g3 = ObjectSelect.query(Table2.class).localCache("cayenne3"); + ObjectSelect g4 = ObjectSelect.query(Table2.class).localCache("cayenne4"); + + assertEquals(0, g3.select(context).size()); + assertEquals(0, g4.select(context).size()); + + db.getTable(cayenne.getTableName(Table2.class)).insertColumns("id", "name").values(1, "x1").exec(); + + // inserted via SQL... query results are still cached... + assertEquals(0, g3.select(context).size()); + assertEquals(0, g3.select(context).size()); + + Table2 t21 = context.newObject(Table2.class); + context.commitChanges(); + + // inserted via Cayenne... "g1" should get auto refreshed... + assertEquals(2, g3.select(context).size()); + assertEquals(0, g4.select(context).size()); + + context.deleteObject(t21); + context.commitChanges(); + + // deleted via Cayenne... "g1" should get auto refreshed + assertEquals(1, g3.select(context).size()); + assertEquals(0, g4.select(context).size()); + } + + @Test + public void invalidate_CustomData() { + + ObjectContext context = cayenne.getRuntime().newContext(); + + // make sure Cayenne-specific caches are created... + ObjectSelect g1 = ObjectSelect.query(Table1.class).localCache("cayenne1"); + assertEquals(0, g1.select(context).size()); + + // add custom data + CacheManager cacheManager = runtime.getInstance(CacheManager.class); + Cache cache = cacheManager.getCache("cayenne1"); + cache.put("a", "b"); + + assertEquals("b", cache.get("a")); + + // generate commit event + context.newObject(Table1.class); + context.commitChanges(); + + // custom cache entries must expire + assertNull(cache.get("a")); + } +} diff --git a/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/Table1.java b/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/Table1.java new file mode 100644 index 00000000..07014cd3 --- /dev/null +++ b/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/Table1.java @@ -0,0 +1,29 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.jcache.persistent; + + +import io.bootique.cayenne.v50.jcache.persistent.auto._Table1; + +public class Table1 extends _Table1 { + + private static final long serialVersionUID = 1L; + +} diff --git a/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/Table2.java b/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/Table2.java new file mode 100644 index 00000000..e4f4aec2 --- /dev/null +++ b/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/Table2.java @@ -0,0 +1,30 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.jcache.persistent; + +import io.bootique.cayenne.v50.jcache.persistent.auto._Table2; +import org.apache.cayenne.cache.invalidation.CacheGroups; + +@CacheGroups("cayenne3") +public class Table2 extends _Table2 { + + private static final long serialVersionUID = 1L; + +} diff --git a/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/auto/_Table1.java b/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/auto/_Table1.java new file mode 100644 index 00000000..285e083c --- /dev/null +++ b/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/auto/_Table1.java @@ -0,0 +1,71 @@ +package io.bootique.cayenne.v50.jcache.persistent.auto; + +import io.bootique.cayenne.v50.jcache.persistent.Table1; +import org.apache.cayenne.PersistentObject; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.exp.property.SelfProperty; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * Class _Table1 was generated by Cayenne. + * It is probably a good idea to avoid changing this class manually, + * since it may be overwritten next time code is regenerated. + * If you need to make any customizations, please use subclass. + */ +public abstract class _Table1 extends PersistentObject { + + private static final long serialVersionUID = 1L; + + public static final SelfProperty SELF = PropertyFactory.createSelf(Table1.class); + + public static final String ID_PK_COLUMN = "id"; + + + + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + default: + super.writePropertyDirectly(propName, val); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + writeSerialized(out); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + readSerialized(in); + } + + @Override + protected void writeState(ObjectOutputStream out) throws IOException { + super.writeState(out); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + } + +} diff --git a/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/auto/_Table2.java b/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/auto/_Table2.java new file mode 100644 index 00000000..bfa365ae --- /dev/null +++ b/bootique-cayenne50-jcache/src/test/java/io/bootique/cayenne/v50/jcache/persistent/auto/_Table2.java @@ -0,0 +1,91 @@ +package io.bootique.cayenne.v50.jcache.persistent.auto; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import io.bootique.cayenne.v50.jcache.persistent.Table2; +import org.apache.cayenne.PersistentObject; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.exp.property.SelfProperty; +import org.apache.cayenne.exp.property.StringProperty; + +/** + * Class _Table2 was generated by Cayenne. + * It is probably a good idea to avoid changing this class manually, + * since it may be overwritten next time code is regenerated. + * If you need to make any customizations, please use subclass. + */ +public abstract class _Table2 extends PersistentObject { + + private static final long serialVersionUID = 1L; + + public static final SelfProperty SELF = PropertyFactory.createSelf(Table2.class); + + public static final String ID_PK_COLUMN = "id"; + + public static final StringProperty NAME = PropertyFactory.createString("name", String.class); + + protected String name; + + + public void setName(String name) { + beforePropertyWrite("name", this.name, name); + this.name = name; + } + + public String getName() { + beforePropertyRead("name"); + return this.name; + } + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + case "name": + return this.name; + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + case "name": + this.name = (String)val; + break; + default: + super.writePropertyDirectly(propName, val); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + writeSerialized(out); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + readSerialized(in); + } + + @Override + protected void writeState(ObjectOutputStream out) throws IOException { + super.writeState(out); + out.writeObject(this.name); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + this.name = (String)in.readObject(); + } + +} diff --git a/bootique-cayenne50-jcache/src/test/resources/bq1.yml b/bootique-cayenne50-jcache/src/test/resources/bq1.yml new file mode 100644 index 00000000..6def5cb4 --- /dev/null +++ b/bootique-cayenne50-jcache/src/test/resources/bq1.yml @@ -0,0 +1,18 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +cayenne: + configs: + - cayenne-project1.xml \ No newline at end of file diff --git a/bootique-cayenne50-jcache/src/test/resources/cayenne-project1.xml b/bootique-cayenne50-jcache/src/test/resources/cayenne-project1.xml new file mode 100644 index 00000000..8942b061 --- /dev/null +++ b/bootique-cayenne50-jcache/src/test/resources/cayenne-project1.xml @@ -0,0 +1,7 @@ + + + + diff --git a/bootique-cayenne50-jcache/src/test/resources/datamap1.map.xml b/bootique-cayenne50-jcache/src/test/resources/datamap1.map.xml new file mode 100644 index 00000000..e8f1ff79 --- /dev/null +++ b/bootique-cayenne50-jcache/src/test/resources/datamap1.map.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + TABLE + VIEW + + false + false + org.apache.cayenne.dbsync.naming.DefaultObjectNameGenerator + false + false + false + + + Default + ../java + entity + + templates/v4_1/superclass.vm + templates/v4_1/embeddable-subclass.vm + templates/v4_1/embeddable-superclass.vm + templates/v4_1/datamap-subclass.vm + templates/v4_1/datamap-superclass.vm + *.java + true + true + false + false + false + + diff --git a/bootique-cayenne50-junit5/pom.xml b/bootique-cayenne50-junit5/pom.xml new file mode 100644 index 00000000..94a0fb78 --- /dev/null +++ b/bootique-cayenne50-junit5/pom.xml @@ -0,0 +1,98 @@ + + + + + + 4.0.0 + + io.bootique.cayenne + bootique-cayenne-parent + 3.0-SNAPSHOT + + + bootique-cayenne50-junit5 + jar + + bootique-cayenne50-junit5: JUnit 5-based helper classes for unit tests with Cayenne stack + Provides JUnit 5 based helper classes for unit tests with Cayenne stack + + + + + io.bootique.cayenne + bootique-cayenne50 + ${bootique.version} + + + + + + + + io.bootique.cayenne + bootique-cayenne50 + + + io.bootique + bootique-junit5 + + + + + org.mockito + mockito-core + test + + + org.slf4j + slf4j-simple + test + + + io.bootique.jdbc + bootique-jdbc-junit5-derby + test + + + + + + + gpg + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + + + + diff --git a/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/CayenneTester.java b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/CayenneTester.java new file mode 100644 index 00000000..c9752b02 --- /dev/null +++ b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/CayenneTester.java @@ -0,0 +1,305 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5; + +import io.bootique.BQCoreModule; +import io.bootique.BQModule; +import io.bootique.cayenne.v50.CayenneModule; +import io.bootique.cayenne.v50.junit5.tester.*; +import io.bootique.di.Binder; +import io.bootique.junit5.BQTestScope; +import io.bootique.junit5.scope.BQAfterMethodCallback; +import io.bootique.junit5.scope.BQBeforeMethodCallback; +import org.apache.cayenne.Persistent; +import org.apache.cayenne.exp.property.Property; +import org.apache.cayenne.exp.property.RelationshipProperty; +import org.apache.cayenne.map.EntityResolver; +import org.apache.cayenne.map.ObjEntity; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.extension.ExtensionContext; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.function.Consumer; + +/** + * A JUnit5 extension that manages test schema, data and Cayenne runtime state between tests. A single CayenneTester + * can be used with a single {@link io.bootique.BQRuntime}. If you have multiple BQRuntimes in a test, you will need to + * declare a separate CayenneTester for each one of them. + * + * @since 2.0 + */ +public class CayenneTester implements BQBeforeMethodCallback, BQAfterMethodCallback { + + private boolean refreshCayenneCaches; + private boolean deleteBeforeEachTest; + private boolean skipSchemaCreation; + private Collection> entities; + private Collection> entityGraphRoots; + private boolean allTables; + private Collection tables; + private Collection tableGraphRoots; + private Collection relatedTables; + + private final CayenneTesterLifecycleManager lifecycleManager; + private CayenneRuntimeManager runtimeManager; + private CommitCounter commitCounter; + private QueryCounter queryCounter; + + public static CayenneTester create() { + return new CayenneTester(); + } + + protected CayenneTester() { + + this.lifecycleManager = new CayenneTesterLifecycleManager() + .callback(this::createRuntimeManager, io.bootique.cayenne.v50.junit5.tester.CayenneTesterCallbackType.onCayenneStartup) + .callback(r -> createSchema(), io.bootique.cayenne.v50.junit5.tester.CayenneTesterCallbackType.onCayenneStartup) + .callback(r -> refreshCaches(), io.bootique.cayenne.v50.junit5.tester.CayenneTesterCallbackType.beforeTestOrOnCayenneStartupWithinTest) + .callback(r -> deleteData(), io.bootique.cayenne.v50.junit5.tester.CayenneTesterCallbackType.beforeTestOrOnCayenneStartupWithinTest) + .callback(r -> commitCounter.reset(), io.bootique.cayenne.v50.junit5.tester.CayenneTesterCallbackType.beforeTestOrOnCayenneStartupWithinTest) + .callback(r -> queryCounter.reset(), io.bootique.cayenne.v50.junit5.tester.CayenneTesterCallbackType.beforeTestOrOnCayenneStartupWithinTest); + + this.refreshCayenneCaches = true; + this.deleteBeforeEachTest = false; + this.skipSchemaCreation = false; + this.commitCounter = new CommitCounter(); + this.queryCounter = new QueryCounter(); + } + + @Override + public void beforeMethod(BQTestScope scope, ExtensionContext context) { + lifecycleManager.beforeMethod(scope, context); + } + + @Override + public void afterMethod(BQTestScope scope, ExtensionContext context) { + lifecycleManager.afterMethod(scope, context); + } + + /** + * @since 2.0 + */ + public CayenneTester onInit(Consumer callback) { + lifecycleManager.callback(callback, CayenneTesterCallbackType.onCayenneStartup); + return this; + } + + public CayenneTester doNoRefreshCayenneCaches() { + this.refreshCayenneCaches = false; + return this; + } + + public CayenneTester skipSchemaCreation() { + this.skipSchemaCreation = true; + return this; + } + + /** + * @since 2.0 + */ + public final CayenneTester allTables() { + this.allTables = true; + return this; + } + + /** + * Configures the Tester to manage a subset of entities out a potentially very large number of entities in the model. + * + * @param entities a list of entities to manage (create schema for, delete test data, etc.) + * @return this tester + */ + @SafeVarargs + public final CayenneTester entities(Class... entities) { + + if (this.entities == null) { + this.entities = new HashSet<>(); + } + + Collections.addAll(this.entities, entities); + return this; + } + + @SafeVarargs + public final CayenneTester entitiesAndDependencies(Class... entities) { + if (this.entityGraphRoots == null) { + this.entityGraphRoots = new HashSet<>(); + } + + Collections.addAll(this.entityGraphRoots, entities); + return this; + } + + public CayenneTester tables(String... tables) { + + if (this.tables == null) { + this.tables = new HashSet<>(); + } + + Collections.addAll(this.tables, tables); + return this; + } + + public CayenneTester tablesAndDependencies(String... tables) { + + if (this.tableGraphRoots == null) { + this.tableGraphRoots = new HashSet<>(); + } + + Collections.addAll(this.tableGraphRoots, tables); + return this; + } + + public CayenneTester relatedTables(Class entityType, Property relationship) { + + if (this.relatedTables == null) { + this.relatedTables = new HashSet<>(); + } + + this.relatedTables.add(new RelatedEntity(entityType, relationship.getName())); + return this; + } + + /** + * Configures the Tester to delete data corresponding to the tester's entity model before each test. + * + * @return this tester + */ + public CayenneTester deleteBeforeEachTest() { + this.deleteBeforeEachTest = true; + return this; + } + + /** + * Returns a new Bootique module that registers Cayenne test extensions. This module should be passed to a single + * test {@link io.bootique.BQRuntime} to associate the tester with that runtime. This would allow the tester to + * interact with Cayenne stack inside that runtime to perform its functions through the test lifecycle. + * + * @return a new Bootique module that registers Cayenne test extensions + */ + public BQModule moduleWithTestHooks() { + return this::configure; + } + + protected void configure(Binder binder) { + + BQCoreModule.extend(binder) + .addRuntimeListener(lifecycleManager); + + CayenneModule.extend(binder) + .addStartupListener(lifecycleManager) + .addSyncFilter(commitCounter) + .addQueryFilter(queryCounter); + } + + public CayenneRuntime getRuntime() { + return lifecycleManager.getCayenneRuntime(); + } + + protected CayenneRuntimeManager getRuntimeManager() { + Assertions.assertNotNull(runtimeManager, "Cayenne runtime is not resolved. Called outside of test lifecycle?"); + return runtimeManager; + } + + public String getTableName(Class entity) { + ObjEntity e = getRuntime().getDataDomain().getEntityResolver().getObjEntity(entity); + if (e == null) { + throw new IllegalStateException("Type is not mapped in Cayenne: " + entity); + } + + return e.getDbEntity().getName(); + } + + /** + * Returns a name of a table related to a given entity via the specified relationship. Useful for navigation to + * join tables that are not directly mapped to Java classes. + * + * @param entity persistent object type for the source of the relationship + * @param relationship a relationship that we'll traverse from the source entity to some target entity + * @param tableIndex An index in a list of tables spanned by 'relationship'. Index of 0 corresponds to the target + * DbEntity of the first object in a chain of DbRelationships for a given ObjRelationship. + * @return a name of a table related to a given entity via the specified relationship. + * @since 2.0 + */ + public String getRelatedTableName(Class entity, RelationshipProperty relationship, int tableIndex) { + EntityResolver entityResolver = getRuntime().getDataDomain().getEntityResolver(); + return new RelatedEntity(entity, relationship.getName()).getRelatedTable(entityResolver, tableIndex).getName(); + } + + /** + * Checks whether Cayenne performed the expected number of DB commits within a single test method. + */ + public void assertCommitCount(int expected) { + commitCounter.assertCount(expected); + } + + /** + * Checks whether Cayenne performed the expected number of DB queries within a single test method. + * + * @since 2.0 + */ + public void assertQueryCount(int expected) { + queryCounter.assertCount(expected); + } + + protected void createRuntimeManager(CayenneRuntime runtime) { + + Objects.requireNonNull(runtime, "CayenneTester is not attached to a test app. " + + "To take advantage of CayenneTester, pass the module " + + "produced via 'moduleWithTestHooks' when assembling a test BQRuntime."); + + if (this.allTables) { + this.runtimeManager = CayenneRuntimeManager + .builder(runtime.getDataDomain()) + .dbEntities(runtime.getDataDomain().getEntityResolver().getDbEntities()) + .build(); + } else { + + this.runtimeManager = CayenneRuntimeManager + .builder(runtime.getDataDomain()) + .entities(entities) + .entityGraphRoots(entityGraphRoots) + .tables(tables) + .tableGraphRoots(tableGraphRoots) + .relatedEntities(relatedTables) + .build(); + } + } + + protected void createSchema() { + if (!skipSchemaCreation) { + getRuntimeManager().createSchema(); + } + } + + protected void refreshCaches() { + if (refreshCayenneCaches) { + getRuntimeManager().refreshCaches(); + } + } + + protected void deleteData() { + if (deleteBeforeEachTest) { + getRuntimeManager().deleteData(); + } + } +} diff --git a/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneRuntimeManager.java b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneRuntimeManager.java new file mode 100644 index 00000000..81acaa44 --- /dev/null +++ b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneRuntimeManager.java @@ -0,0 +1,111 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5.tester; + +import org.apache.cayenne.access.DataDomain; +import org.apache.cayenne.access.DataNode; +import org.apache.cayenne.access.DbGenerator; +import org.apache.cayenne.access.util.DoNothingOperationObserver; +import org.apache.cayenne.map.DataMap; +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.query.SQLTemplate; + +import java.util.Collections; +import java.util.Map; + +/** + * Manages various aspects of Cayenne stack (caching, schema generation, etc.) for a subset of selected entities. + * + * @since 2.0 + */ +public class CayenneRuntimeManager { + + private DataDomain domain; + private Map managedEntitiesByNode; + + public static CayenneRuntimeManagerBuilder builder(DataDomain domain) { + return new CayenneRuntimeManagerBuilder(domain); + } + + protected CayenneRuntimeManager(DataDomain domain, Map managedEntitiesByNode) { + this.domain = domain; + this.managedEntitiesByNode = managedEntitiesByNode; + } + + public void refreshCaches() { + if (domain.getSharedSnapshotCache() != null) { + domain.getSharedSnapshotCache().clear(); + } + + if (domain.getQueryCache() != null) { + // note that this also flushes per-context caches .. at least with JCache implementation + domain.getQueryCache().clear(); + } + } + + // keeping public for the tests + public Map getManagedEntitiesByNode() { + return managedEntitiesByNode; + } + + public void deleteData() { + managedEntitiesByNode.forEach(this::deleteData); + } + + public void createSchema() { + managedEntitiesByNode.forEach(this::createSchema); + } + + protected void deleteData(String nodeName, FilteredDataMap map) { + + DataNode node = domain.getDataNode(nodeName); + + // TODO: single transaction? + map.getEntitiesInDeleteOrder().forEach(e -> deleteData(node, e)); + } + + protected void deleteData(DataNode node, DbEntity entity) { + + String name = node.getAdapter().getQuotingStrategy().quotedFullyQualifiedName(entity); + String sql = "delete from " + name; + + // using SQLTemplate instead of SQLExec, as it can be executed directly on the DataNode + SQLTemplate query = new SQLTemplate(); + query.setDefaultTemplate(sql); + node.performQueries(Collections.singleton(query), new DoNothingOperationObserver()); + } + + protected void createSchema(String nodeName, DataMap map) { + + DataNode node = domain.getDataNode(nodeName); + + DbGenerator generator = new DbGenerator(node.getAdapter(), map, node.getJdbcEventLogger()); + generator.setShouldCreateTables(true); + generator.setShouldDropTables(false); + generator.setShouldCreateFKConstraints(true); + generator.setShouldCreatePKSupport(true); + generator.setShouldDropPKSupport(false); + + try { + generator.runGenerator(node.getDataSource()); + } catch (Exception e) { + throw new RuntimeException("Error creating schema for DataNode: " + node.getName(), e); + } + } +} diff --git a/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneRuntimeManagerBuilder.java b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneRuntimeManagerBuilder.java new file mode 100644 index 00000000..17b39c3e --- /dev/null +++ b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneRuntimeManagerBuilder.java @@ -0,0 +1,192 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5.tester; + +import org.apache.cayenne.Persistent; +import org.apache.cayenne.access.DataDomain; +import org.apache.cayenne.access.DataNode; +import org.apache.cayenne.map.*; + +import java.util.*; + +/** + * @since 2.0 + */ +public class CayenneRuntimeManagerBuilder { + + private final DataDomain domain; + + // these two entity buckets are resolved independently and then intersected + private final Set entities; + private final Set entityGraphs; + + protected CayenneRuntimeManagerBuilder(DataDomain domain) { + this.domain = domain; + this.entities = new HashSet<>(); + this.entityGraphs = new HashSet<>(); + } + + public CayenneRuntimeManagerBuilder dbEntities(Collection entities) { + if (entities != null) { + this.entities.addAll(entities); + } + + return this; + } + + public CayenneRuntimeManagerBuilder entities(Collection> entities) { + if (entities != null) { + entities.forEach(e -> resolve(this.entities, e)); + } + + return this; + } + + public CayenneRuntimeManagerBuilder entityGraphRoots(Collection> entityGraphRoots) { + if (entityGraphRoots != null) { + entityGraphRoots.forEach(e -> resolveGraph(this.entityGraphs, e)); + } + + return this; + } + + public CayenneRuntimeManagerBuilder tables(Collection tables) { + if (tables != null) { + tables.forEach(t -> resolve(this.entities, t)); + } + + return this; + } + + public CayenneRuntimeManagerBuilder tableGraphRoots(Collection tableGraphRoots) { + if (tableGraphRoots != null) { + tableGraphRoots.forEach(t -> resolveGraph(this.entityGraphs, t)); + } + + return this; + } + + public CayenneRuntimeManagerBuilder relatedEntities(Collection relatedEntities) { + if (relatedEntities != null) { + relatedEntities.forEach(t -> resolve(this.entities, t)); + } + return this; + } + + public CayenneRuntimeManager build() { + + // using LinkedHashMap to preserve insert order of entities + Map> byNode = new HashMap<>(); + buildEntitiesInInsertOrder().forEach(e -> { + DataNode node = domain.lookupDataNode(e.getDataMap()); + byNode.computeIfAbsent(node.getName(), nn -> new LinkedHashMap<>()).put(e.getName(), e); + }); + + Map managedEntitiesByNode = new HashMap<>(); + byNode.forEach((k, v) -> managedEntitiesByNode.put(k, new FilteredDataMap("CayenneTester_" + k, v))); + return new CayenneRuntimeManager(domain, managedEntitiesByNode); + } + + private Collection buildEntitiesInInsertOrder() { + + Set dbEntities; + + if (entities.isEmpty() && entityGraphs.isEmpty()) { + dbEntities = Collections.emptySet(); + } else if (entities.isEmpty()) { + dbEntities = entityGraphs; + } else if (entityGraphs.isEmpty()) { + dbEntities = entities; + } else { + dbEntities = new HashSet<>(); + dbEntities.addAll(entities); + dbEntities.addAll(entityGraphs); + } + + // Sort if needed + if (dbEntities.size() <= 1) { + return dbEntities; + } + + // Do not obtain sorter from Cayenne DI. It is not a singleton and will come uninitialized + EntitySorter sorter = domain.getEntitySorter(); + List sorted = new ArrayList<>(dbEntities); + sorter.sortDbEntities(sorted, false); + return sorted; + } + + private void resolve(Set accum, String tableName) { + accum.add(dbEntityForName(tableName)); + } + + private void resolveGraph(Set accum, String tableName) { + DbEntity entity = dbEntityForName(tableName); + ModelDependencyResolver.resolve(accum, entity); + } + + private void resolve(Set accum, Class type) { + accum.add(dbEntityForType(type)); + } + + private void resolveGraph(Set accum, Class type) { + DbEntity entity = dbEntityForType(type); + ModelDependencyResolver.resolve(accum, entity); + } + + private void resolve(Set accum, RelatedEntity re) { + + ObjEntity e = domain.getEntityResolver().getObjEntity(re.getType()); + if (e == null) { + throw new IllegalStateException("Type is not mapped in Cayenne: " + re.getType()); + } + + ObjRelationship objRelationship = e.getRelationship(re.getRelationship()); + if (objRelationship == null) { + throw new IllegalArgumentException("No relationship '" + re.getRelationship() + "' in entity " + e.getName()); + } + + List path = objRelationship.getDbRelationships(); + if (path.size() < 2) { + return; + } + + path.subList(1, path.size()) + .stream() + .map(DbRelationship::getSourceEntity) + .forEach(accum::add); + } + + private DbEntity dbEntityForType(Class type) { + ObjEntity e = domain.getEntityResolver().getObjEntity(type); + if (e == null) { + throw new IllegalStateException("Type is not mapped in Cayenne: " + type); + } + + return e.getDbEntity(); + } + + private DbEntity dbEntityForName(String name) { + DbEntity dbe = domain.getEntityResolver().getDbEntity(name); + if (dbe == null) { + throw new IllegalStateException("Table is not mapped in Cayenne: " + name); + } + + return dbe; + } +} diff --git a/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneTesterCallbackType.java b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneTesterCallbackType.java new file mode 100644 index 00000000..043b2c33 --- /dev/null +++ b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneTesterCallbackType.java @@ -0,0 +1,28 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5.tester; + +public enum CayenneTesterCallbackType { + + // run when Cayenne runtime is started + onCayenneStartup, + + // run either before each test (when Cayenne is already started) or when Cayenne is started within a test + beforeTestOrOnCayenneStartupWithinTest; +} diff --git a/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneTesterLifecycleManager.java b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneTesterLifecycleManager.java new file mode 100644 index 00000000..c7652c8d --- /dev/null +++ b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CayenneTesterLifecycleManager.java @@ -0,0 +1,142 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5.tester; + +import io.bootique.BQRuntime; +import io.bootique.BQRuntimeListener; +import io.bootique.cayenne.v50.CayenneStartupListener; +import io.bootique.di.DIRuntimeException; +import io.bootique.junit5.BQTestScope; +import io.bootique.junit5.scope.BQAfterMethodCallback; +import io.bootique.junit5.scope.BQBeforeMethodCallback; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.extension.ExtensionContext; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Consumer; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +/** + * @since 3.0 + */ +public class CayenneTesterLifecycleManager implements BQRuntimeListener, CayenneStartupListener, BQBeforeMethodCallback, BQAfterMethodCallback { + + private final Map, CayenneTesterCallbackType> callbacks; + private BQRuntime bqRuntime; + private CayenneRuntime cayenneRuntime; + private boolean withinTestMethod; + + public CayenneTesterLifecycleManager() { + // ordering of entries is important here + this.callbacks = new LinkedHashMap<>(); + } + + public BQRuntime getBqRuntime() { + assertNotNull(bqRuntime, "BQRuntime is not initialized. Not connected to a Bootique runtime?"); + return bqRuntime; + } + + public CayenneRuntime getCayenneRuntime() { + + if (cayenneRuntime == null) { + // trigger immediate initialization + getBqRuntime().getInstance(CayenneRuntime.class); + } + + assertNotNull(cayenneRuntime, "Unexpected state - CayenneRuntime is not initialized"); + return cayenneRuntime; + } + + /** + * Registers a Cayenne startup callback to be run either unconditionally, or only when startup happens within a + * test method. + */ + public CayenneTesterLifecycleManager callback(Consumer callback, CayenneTesterCallbackType type) { + callbacks.put(callback, type); + return this; + } + + // Called by "bootique" + @Override + public void onRuntimeCreated(BQRuntime runtime) { + checkUnused(runtime); + this.bqRuntime = runtime; + } + + // Called by "bootique-cayenne" + @Override + public void onRuntimeCreated(CayenneRuntime runtime) { + this.cayenneRuntime = runtime; + callbacks.forEach((k, v) -> onCayenneStarted(runtime, k, v)); + } + + // Called by "bootique-junit5" via CayenneTester + @Override + public void beforeMethod(BQTestScope scope, ExtensionContext context) { + this.withinTestMethod = true; + + // TODO: prefilter callbacks collection of "beforeTestIfStarted" in a separate collection to avoid iteration + // on every run? + if (isStarted()) { + callbacks.forEach((k, v) -> beforeTestIfStarted(cayenneRuntime, k, v)); + } + } + + // Called by "bootique-junit5" via CayenneTester + @Override + public void afterMethod(BQTestScope scope, ExtensionContext context) { + this.withinTestMethod = false; + } + + private boolean isStarted() { + return cayenneRuntime != null; + } + + private void onCayenneStarted(CayenneRuntime runtime, Consumer callback, CayenneTesterCallbackType type) { + + switch (type) { + case onCayenneStartup: + callback.accept(runtime); + break; + case beforeTestOrOnCayenneStartupWithinTest: + if (withinTestMethod) { + callback.accept(runtime); + } + break; + default: + return; + } + } + + private void beforeTestIfStarted(CayenneRuntime runtime, Consumer callback, CayenneTesterCallbackType type) { + if (type == CayenneTesterCallbackType.beforeTestOrOnCayenneStartupWithinTest) { + callback.accept(runtime); + } + } + + private void checkUnused(BQRuntime runtime) { + if (this.bqRuntime != null && this.bqRuntime != runtime) { + throw new DIRuntimeException("BQRuntime is already initialized. " + + "Likely this CayenneTester is already connected to another BQRuntime. " + + "To fix this error use one CayenneTester per BQRuntime."); + } + } +} diff --git a/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CommitCounter.java b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CommitCounter.java new file mode 100644 index 00000000..a936f801 --- /dev/null +++ b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/CommitCounter.java @@ -0,0 +1,54 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5.tester; + +import org.apache.cayenne.DataChannelSyncFilter; +import org.apache.cayenne.DataChannelSyncFilterChain; +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.graph.GraphDiff; + +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @since 2.0 + */ +public class CommitCounter implements DataChannelSyncFilter { + + private AtomicInteger count; + + public CommitCounter() { + this.count = new AtomicInteger(0); + } + + @Override + public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType, DataChannelSyncFilterChain filterChain) { + count.incrementAndGet(); + return filterChain.onSync(originatingContext, changes, syncType); + } + + public void assertCount(int expectedCommits) { + assertEquals(expectedCommits, count.get(), "Unexpected number of Cayenne commits executed"); + } + + public void reset() { + count.set(0); + } +} diff --git a/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/FilteredDataMap.java b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/FilteredDataMap.java new file mode 100644 index 00000000..01d36a1f --- /dev/null +++ b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/FilteredDataMap.java @@ -0,0 +1,71 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5.tester; + +import org.apache.cayenne.map.DataMap; +import org.apache.cayenne.map.DbEntity; + +import java.util.*; + +/** + * A DataMap decorator that provides access to a subset of DbEntities from another DataMap without changing their + * parent. Unfortunately it is not easy tio just copy some entities from one DataMap to another, as the entities' + * parent will get reset. Hence using thsi decorator. + * + * @since 2.0 + */ +public class FilteredDataMap extends DataMap { + + // expected to be in the insert order + private LinkedHashMap orderedEntities; + private List entitiesInDeleteOrder; + + public FilteredDataMap(String mapName, LinkedHashMap orderedEntities) { + super(mapName); + this.orderedEntities = orderedEntities; + } + + public List getEntitiesInDeleteOrder() { + if (entitiesInDeleteOrder == null) { + List list = new ArrayList<>(orderedEntities.values()); + Collections.reverse(list); + this.entitiesInDeleteOrder = list; + } + return entitiesInDeleteOrder; + } + + public Collection getDbEntitiesInInsertOrder() { + return orderedEntities.values(); + } + + @Override + public SortedMap getDbEntityMap() { + return new TreeMap<>(orderedEntities); + } + + @Override + public Collection getDbEntities() { + return getDbEntitiesInInsertOrder(); + } + + @Override + public DbEntity getDbEntity(String dbEntityName) { + return orderedEntities.get(dbEntityName); + } +} diff --git a/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/ModelDependencyResolver.java b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/ModelDependencyResolver.java new file mode 100644 index 00000000..66e3541d --- /dev/null +++ b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/ModelDependencyResolver.java @@ -0,0 +1,42 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5.tester; + +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.map.DbRelationship; + +import java.util.Set; + +class ModelDependencyResolver { + + static void resolve(Set resolved, DbEntity entity) { + + // if already there, assume entity's dependencies are already resolved + if (resolved.add(entity)) { + entity.getRelationships().forEach(r -> resolveDependent(resolved, r)); + } + } + + static private void resolveDependent(Set resolved, DbRelationship relationship) { + if (relationship.isFromPK() && !relationship.isToMasterPK()) { + resolve(resolved, relationship.getTargetEntity()); + } + } +} diff --git a/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/QueryCounter.java b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/QueryCounter.java new file mode 100644 index 00000000..4620c622 --- /dev/null +++ b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/QueryCounter.java @@ -0,0 +1,55 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5.tester; + +import org.apache.cayenne.DataChannelQueryFilter; +import org.apache.cayenne.DataChannelQueryFilterChain; +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.QueryResponse; +import org.apache.cayenne.query.Query; + +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @since 2.0 + */ +public class QueryCounter implements DataChannelQueryFilter { + + private AtomicInteger count; + + public QueryCounter() { + this.count = new AtomicInteger(0); + } + + @Override + public QueryResponse onQuery(ObjectContext objectContext, Query query, DataChannelQueryFilterChain dataChannelQueryFilterChain) { + count.incrementAndGet(); + return dataChannelQueryFilterChain.onQuery(objectContext, query); + } + + public void assertCount(int expectedCommits) { + assertEquals(expectedCommits, count.get(), "Unexpected number of Cayenne queries executed"); + } + + public void reset() { + count.set(0); + } +} diff --git a/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/RelatedEntity.java b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/RelatedEntity.java new file mode 100644 index 00000000..6749526a --- /dev/null +++ b/bootique-cayenne50-junit5/src/main/java/io/bootique/cayenne/v50/junit5/tester/RelatedEntity.java @@ -0,0 +1,75 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5.tester; + +import org.apache.cayenne.Persistent; +import org.apache.cayenne.map.*; + +import java.util.List; + +/** + * @since 2.0 + */ +public class RelatedEntity { + + private Class type; + private String relationship; + + public RelatedEntity(Class type, String relationship) { + this.type = type; + this.relationship = relationship; + } + + public Class getType() { + return type; + } + + public String getRelationship() { + return relationship; + } + + /** + * Returns a DbEntity related to a given entity via the specified relationship. Useful for navigation to join tables + * that are not directly mapped to Java classes. + * + * @param tableIndex An index in a list of tables spanned by 'relationship'. Index of 0 corresponds to the target + * DbEntity of the first object in a chain of DbRelationships for a given ObjRelationship. + * @return a DbEntity related to a given entity via the specified relationship. + */ + public DbEntity getRelatedTable(EntityResolver resolver, int tableIndex) { + ObjEntity entity = resolver.getObjEntity(type); + if (entity == null) { + throw new IllegalArgumentException("Not a Cayenne entity class: " + type.getName()); + } + + ObjRelationship flattened = entity.getRelationship(relationship); + + if (flattened == null) { + throw new IllegalArgumentException("No relationship '" + relationship + "' in entity " + type.getName()); + } + + List path = flattened.getDbRelationships(); + + if (path.size() < tableIndex + 1) { + throw new IllegalArgumentException("Index " + tableIndex + " is out of bounds for relationship '" + relationship); + } + + return path.get(tableIndex).getTargetEntity(); + } +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_AllTablesIT.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_AllTablesIT.java new file mode 100644 index 00000000..d9261563 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_AllTablesIT.java @@ -0,0 +1,76 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5; + +import io.bootique.BQRuntime; +import io.bootique.Bootique; +import io.bootique.cayenne.v50.junit5.tester.FilteredDataMap; +import io.bootique.jdbc.junit5.derby.DerbyTester; +import io.bootique.junit5.BQApp; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.Test; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BQTest +public class CayenneTester_AllTablesIT { + + @BQTestTool + static final DerbyTester db = DerbyTester.db(); + + @BQApp(skipRun = true) + static final BQRuntime app = Bootique + .app("-c", "classpath:config2.yml") + .autoLoadModules() + .module(db.moduleWithTestDataSource("db")) + .createRuntime(); + + private Set getTables(CayenneTester ct) { + ct.createRuntimeManager(app.getInstance(CayenneRuntime.class)); + Map entities = ct.getRuntimeManager().getManagedEntitiesByNode(); + + assertEquals(1, entities.size()); + FilteredDataMap map = entities.values().iterator().next(); + + return map.getDbEntitiesInInsertOrder() + .stream() + .map(DbEntity::getName) + .collect(Collectors.toSet()); + } + + @Test + public void allTables() { + + CayenneTester ct = CayenneTester.create().allTables(); + Set tables = getTables(ct); + + assertEquals(2, tables.size()); + assertTrue(tables.contains("table1")); + assertTrue(tables.contains("table2")); + } +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_AssertOpCountsIT.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_AssertOpCountsIT.java new file mode 100644 index 00000000..32309361 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_AssertOpCountsIT.java @@ -0,0 +1,86 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5; + +import io.bootique.BQRuntime; +import io.bootique.Bootique; +import io.bootique.cayenne.v50.junit5.persistence.Table1; +import io.bootique.cayenne.v50.junit5.persistence.Table2; +import io.bootique.jdbc.junit5.derby.DerbyTester; +import io.bootique.junit5.BQApp; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.query.ObjectSelect; +import org.junit.jupiter.api.RepeatedTest; + +@BQTest +public class CayenneTester_AssertOpCountsIT { + + @BQTestTool + static final DerbyTester db = DerbyTester.db(); + + @BQTestTool + static final CayenneTester cayenne = CayenneTester + .create() + .entities(Table1.class, Table2.class) + .deleteBeforeEachTest(); + + @BQApp(skipRun = true) + static final BQRuntime app = Bootique + .app("-c", "classpath:config2.yml") + .autoLoadModules() + .module(db.moduleWithTestDataSource("db")) + .module(cayenne.moduleWithTestHooks()) + .createRuntime(); + + @RepeatedTest(3) + public void commitCount() { + + // must be reset at every run + cayenne.assertCommitCount(0); + ObjectContext context = cayenne.getRuntime().newContext(); + + Table1 t1 = context.newObject(Table1.class); + t1.setA(7L); + t1.setB(8L); + context.commitChanges(); + + cayenne.assertCommitCount(1); + } + + @RepeatedTest(3) + public void queryCount() { + + // must be reset at every run + cayenne.assertQueryCount(0); + ObjectContext context = cayenne.getRuntime().newContext(); + + ObjectSelect.query(Table1.class).select(context); + cayenne.assertQueryCount(1); + + ObjectSelect.query(Table1.class).select(context); + cayenne.assertQueryCount(2); + + // reset context, same counter should be in use + ObjectSelect.query(Table1.class).select(cayenne.getRuntime().newContext()); + cayenne.assertQueryCount(3); + } +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_CachesNotRefreshedIT.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_CachesNotRefreshedIT.java new file mode 100644 index 00000000..ce515b24 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_CachesNotRefreshedIT.java @@ -0,0 +1,80 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5; + +import io.bootique.BQRuntime; +import io.bootique.Bootique; +import io.bootique.cayenne.v50.junit5.persistence.Table1; +import io.bootique.cayenne.v50.junit5.persistence.Table2; +import io.bootique.jdbc.junit5.derby.DerbyTester; +import io.bootique.junit5.BQApp; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.ObjectContext; +import org.junit.jupiter.api.*; + +@BQTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class CayenneTester_CachesNotRefreshedIT { + + @BQTestTool + static final DerbyTester db = DerbyTester.db(); + + @BQTestTool + static final CayenneTester cayenne = CayenneTester + .create() + .doNoRefreshCayenneCaches() + .entities(Table1.class, Table2.class); + + @BQApp(skipRun = true) + static final BQRuntime app = Bootique.app("-c", "classpath:config2.yml") + .autoLoadModules() + .module(db.moduleWithTestDataSource("db")) + .module(cayenne.moduleWithTestHooks()) + .createRuntime(); + + @Test + @Order(1) + public void crossTestInterference1() { + verifyCachesEmptyAndAddObjectsToCache(); + } + + @Test + @Order(2) + public void crossTestInterference2() { + verifyCaches(1); + } + + private void verifyCaches(int expectedCount) { + Assertions.assertEquals(expectedCount, cayenne.getRuntime().getDataDomain().getSharedSnapshotCache().size()); + } + + private void verifyCachesEmptyAndAddObjectsToCache() { + // verify that there's no data in the cache + verifyCaches(0); + + // seed the cache for the next test + ObjectContext context = cayenne.getRuntime().newContext(); + Table1 t1 = context.newObject(Table1.class); + t1.setA(5L); + t1.setB(6L); + context.commitChanges(); + } +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_CachesRefreshedIT.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_CachesRefreshedIT.java new file mode 100644 index 00000000..191ffd77 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_CachesRefreshedIT.java @@ -0,0 +1,80 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5; + +import io.bootique.BQRuntime; +import io.bootique.Bootique; +import io.bootique.cayenne.v50.junit5.persistence.Table1; +import io.bootique.cayenne.v50.junit5.persistence.Table2; +import io.bootique.jdbc.junit5.derby.DerbyTester; +import io.bootique.junit5.BQApp; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.ObjectContext; +import org.junit.jupiter.api.*; + +@BQTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class CayenneTester_CachesRefreshedIT { + + @BQTestTool + static final DerbyTester db = DerbyTester.db(); + + @BQTestTool + static final CayenneTester cayenne = CayenneTester + .create() + .entities(Table1.class, Table2.class); + + @BQApp(skipRun = true) + static final BQRuntime app = Bootique.app("-c", "classpath:config2.yml") + .autoLoadModules() + .module(db.moduleWithTestDataSource("db")) + .module(cayenne.moduleWithTestHooks()) + .createRuntime(); + + @Test + @Order(1) + public void crossTestInterference1() { + verifyCachesEmptyAndAddObjectsToCache(); + } + + @Test + @Order(2) + public void crossTestInterference2() { + verifyCachesEmpty(); + } + + private void verifyCachesEmpty() { + // verify that there's no data in the cache + Assertions.assertEquals(0, cayenne.getRuntime().getDataDomain().getSharedSnapshotCache().size()); + } + + private void verifyCachesEmptyAndAddObjectsToCache() { + // verify that there's no data in the cache + verifyCachesEmpty(); + + // seed the cache for the next test + ObjectContext context = cayenne.getRuntime().newContext(); + Table1 t1 = context.newObject(Table1.class); + t1.setA(5L); + t1.setB(6L); + context.commitChanges(); + } +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_DeleteBeforeEachTestIT.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_DeleteBeforeEachTestIT.java new file mode 100644 index 00000000..5824d3b2 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_DeleteBeforeEachTestIT.java @@ -0,0 +1,77 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5; + +import io.bootique.BQRuntime; +import io.bootique.Bootique; +import io.bootique.cayenne.v50.junit5.persistence.Table1; +import io.bootique.cayenne.v50.junit5.persistence.Table2; +import io.bootique.jdbc.junit5.Table; +import io.bootique.jdbc.junit5.derby.DerbyTester; +import io.bootique.junit5.BQApp; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.Persistent; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +@BQTest +public class CayenneTester_DeleteBeforeEachTestIT { + + @BQTestTool + static final DerbyTester db = DerbyTester.db(); + + @BQTestTool + static final CayenneTester cayenne = CayenneTester + .create() + .entities(Table1.class, Table2.class) + .deleteBeforeEachTest(); + + @BQApp(skipRun = true) + static final BQRuntime app = Bootique + .app("-c", "classpath:config2.yml") + .autoLoadModules() + .module(db.moduleWithTestDataSource("db")) + .module(cayenne.moduleWithTestHooks()) + .createRuntime(); + + @Test + public void noSuchTable() { + assertThrows(IllegalStateException.class, () -> cayenne.getTableName(Persistent.class)); + } + + @RepeatedTest(2) + public void test1() { + + Table t1 = db.getTable(cayenne.getTableName(Table1.class)); + Table t2 = db.getTable(cayenne.getTableName(Table2.class)); + + t1.matcher().assertNoMatches(); + t2.matcher().assertNoMatches(); + + t1.insert(1, 2, 3); + t2.insert(5, "x"); + + t1.matcher().assertOneMatch(); + t2.matcher().assertOneMatch(); + } +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_DependenciesIT.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_DependenciesIT.java new file mode 100644 index 00000000..68e0024d --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_DependenciesIT.java @@ -0,0 +1,109 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5; + +import io.bootique.BQRuntime; +import io.bootique.Bootique; +import io.bootique.cayenne.v50.junit5.persistence3.P3T1; +import io.bootique.cayenne.v50.junit5.persistence3.P3T3; +import io.bootique.cayenne.v50.junit5.persistence3.P3T4; +import io.bootique.cayenne.v50.junit5.tester.FilteredDataMap; +import io.bootique.jdbc.junit5.derby.DerbyTester; +import io.bootique.junit5.BQApp; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.map.DbEntity; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.Test; + +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BQTest +public class CayenneTester_DependenciesIT { + + @BQTestTool + static final DerbyTester db = DerbyTester.db(); + + @BQApp(skipRun = true) + static final BQRuntime app = Bootique + .app("-c", "classpath:config3.yml") + .autoLoadModules() + .module(db.moduleWithTestDataSource("db")) + .createRuntime(); + + private Set getTables(CayenneTester ct) { + ct.createRuntimeManager(app.getInstance(CayenneRuntime.class)); + Map entities = ct.getRuntimeManager().getManagedEntitiesByNode(); + + assertEquals(1, entities.size()); + FilteredDataMap map = entities.values().iterator().next(); + + return map.getDbEntitiesInInsertOrder() + .stream() + .map(DbEntity::getName) + .collect(Collectors.toSet()); + } + + @Test + public void dependentEntities1() { + + CayenneTester ct = CayenneTester.create().entitiesAndDependencies(P3T1.class); + Set tables = getTables(ct); + + assertEquals(4, tables.size()); + assertTrue(tables.contains("p3_t1")); + assertTrue(tables.contains("p3_t1_t4")); + assertTrue(tables.contains("p3_t2")); + assertTrue(tables.contains("p3_t3")); + } + + @Test + public void dependentEntities2() { + CayenneTester ct = CayenneTester.create().entitiesAndDependencies(P3T4.class); + Set tables = getTables(ct); + + assertEquals(2, tables.size()); + assertTrue(tables.contains("p3_t4")); + assertTrue(tables.contains("p3_t1_t4")); + } + + @Test + public void dependentEntities3() { + CayenneTester ct = CayenneTester.create().entitiesAndDependencies(P3T3.class); + Set tables = getTables(ct); + + assertEquals(1, tables.size()); + assertTrue(tables.contains("p3_t3")); + } + + @Test + public void dependentTables1() { + CayenneTester ct = CayenneTester.create().tablesAndDependencies("p3_t1_t4"); + Set tables = getTables(ct); + + assertEquals(1, tables.size()); + assertTrue(tables.contains("p3_t1_t4")); + } +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_IndirectRuntimeAccessIT.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_IndirectRuntimeAccessIT.java new file mode 100644 index 00000000..c43bdf97 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_IndirectRuntimeAccessIT.java @@ -0,0 +1,72 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5; + +import io.bootique.BQRuntime; +import io.bootique.Bootique; +import io.bootique.cayenne.v50.junit5.persistence.Table1; +import io.bootique.jdbc.junit5.derby.DerbyTester; +import io.bootique.junit5.BQApp; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.RepeatedTest; + +@BQTest +public class CayenneTester_IndirectRuntimeAccessIT { + + @BQTestTool + static final DerbyTester db = DerbyTester.db(); + + @BQTestTool + static final CayenneTester cayenne = CayenneTester + .create() + .entities(Table1.class) + .deleteBeforeEachTest(); + + @BQApp(skipRun = true) + static final BQRuntime app = Bootique + .app("-c", "classpath:config2.yml") + .autoLoadModules() + .module(db.moduleWithTestDataSource("db")) + .module(cayenne.moduleWithTestHooks()) + .createRuntime(); + + + @RepeatedTest(2) + @DisplayName("Eager init of BQRuntime must work without calling 'CayenneTester.getRuntime()'") + public void dbAccess() { + + // schema is only created after the first access to CayenneRuntime + app.getInstance(CayenneRuntime.class); + db.getTable("table1").matcher().assertNoMatches(); + + ObjectContext context = app.getInstance(CayenneRuntime.class).newContext(); + Table1 t1 = context.newObject(Table1.class); + t1.setA(5L); + t1.setB(6L); + + context.commitChanges(); + + db.getTable("table1").matcher().assertMatches(1); + } +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_ModuleWithTestHooksIT.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_ModuleWithTestHooksIT.java new file mode 100644 index 00000000..b26f469c --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_ModuleWithTestHooksIT.java @@ -0,0 +1,57 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5; + +import io.bootique.BQRuntime; +import io.bootique.di.DIRuntimeException; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestFactory; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +@BQTest +public class CayenneTester_ModuleWithTestHooksIT { + + @BQTestTool + static final BQTestFactory testFactory = new BQTestFactory(); + + @Test + @DisplayName("Tester should work with BQTestFactory-produced runtimes") + public void withBQTestFactory() { + CayenneTester tester = CayenneTester.create(); + BQRuntime runtime = testFactory.app().autoLoadModules().module(tester.moduleWithTestHooks()).createRuntime(); + CayenneRuntime cayenneRuntime = runtime.getInstance(CayenneRuntime.class); + assertSame(cayenneRuntime, tester.getRuntime()); + } + + @Test + @DisplayName("Reusing tester for multiple runtimes must be disallowed") + public void disallowMultipleRuntimes() { + CayenneTester tester = CayenneTester.create(); + testFactory.app().autoLoadModules().module(tester.moduleWithTestHooks()).createRuntime(); + + assertThrows(DIRuntimeException.class, () -> + testFactory.app().autoLoadModules().module(tester.moduleWithTestHooks()).createRuntime()); + } +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_RunAppIT.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_RunAppIT.java new file mode 100644 index 00000000..e875a149 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_RunAppIT.java @@ -0,0 +1,74 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5; + +import io.bootique.BQCoreModule; +import io.bootique.BQRuntime; +import io.bootique.Bootique; +import io.bootique.cayenne.v50.junit5.persistence.Table1; +import io.bootique.cli.Cli; +import io.bootique.command.CommandOutcome; +import io.bootique.jdbc.junit5.derby.DerbyTester; +import io.bootique.junit5.BQApp; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.ObjectContext; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@BQTest +public class CayenneTester_RunAppIT { + + @BQTestTool + static final DerbyTester db = DerbyTester.db(); + + @BQTestTool + static final CayenneTester cayenne = CayenneTester + .create() + .entities(Table1.class) + .deleteBeforeEachTest(); + + @BQApp + static final BQRuntime app = Bootique + .app("-c", "classpath:config2.yml") + .autoLoadModules() + .module(b -> BQCoreModule.extend(b).setDefaultCommand(CayenneTester_RunAppIT::triggerCayenneInit)) + .module(db.moduleWithTestDataSource("db")) + .module(cayenne.moduleWithTestHooks()) + .createRuntime(); + + private static CommandOutcome triggerCayenneInit(Cli cli) { + cayenne.getRuntime(); + return CommandOutcome.succeeded(); + } + + @Test + @DisplayName("Eager init of BQRuntime must work") + public void dbAccess() { + ObjectContext context = cayenne.getRuntime().newContext(); + Table1 t1 = context.newObject(Table1.class); + t1.setA(5L); + t1.setB(6L); + + context.commitChanges(); + + db.getTable("table1").matcher().assertMatches(1); + } +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_UnattachedIT.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_UnattachedIT.java new file mode 100644 index 00000000..708e4161 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/CayenneTester_UnattachedIT.java @@ -0,0 +1,58 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.junit5; + +import io.bootique.BQRuntime; +import io.bootique.Bootique; +import io.bootique.cayenne.v50.junit5.persistence.Table1; +import io.bootique.jdbc.junit5.derby.DerbyTester; +import io.bootique.junit5.BQApp; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@BQTest +public class CayenneTester_UnattachedIT { + + @BQTestTool + static final DerbyTester db = DerbyTester.db(); + + // declare CayenneTester, but don't use it in the app. While wasteful, this should not blow up + @BQTestTool + static final CayenneTester cayenne = CayenneTester + .create() + .entities(Table1.class) + .deleteBeforeEachTest(); + + @BQApp(skipRun = true) + static final BQRuntime app = Bootique + .app("-c", "classpath:config2.yml") + .autoLoadModules() + .module(db.moduleWithTestDataSource("db")) + .createRuntime(); + + @Test + @DisplayName("CayenneTester declared, but not attached to the app") + public void dbAccess() { + app.getInstance(CayenneRuntime.class).newContext(); + } +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/Table1.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/Table1.java new file mode 100644 index 00000000..3187afd7 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/Table1.java @@ -0,0 +1,9 @@ +package io.bootique.cayenne.v50.junit5.persistence; + +import io.bootique.cayenne.v50.junit5.persistence.auto._Table1; + +public class Table1 extends _Table1 { + + private static final long serialVersionUID = 1L; + +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/Table2.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/Table2.java new file mode 100644 index 00000000..c7cbd2ae --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/Table2.java @@ -0,0 +1,9 @@ +package io.bootique.cayenne.v50.junit5.persistence; + +import io.bootique.cayenne.v50.junit5.persistence.auto._Table2; + +public class Table2 extends _Table2 { + + private static final long serialVersionUID = 1L; + +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/auto/_Table1.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/auto/_Table1.java new file mode 100644 index 00000000..0cd37bdd --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/auto/_Table1.java @@ -0,0 +1,111 @@ +package io.bootique.cayenne.v50.junit5.persistence.auto; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.apache.cayenne.PersistentObject; +import org.apache.cayenne.exp.property.NumericProperty; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.exp.property.SelfProperty; + +import io.bootique.cayenne.v50.junit5.persistence.Table1; + +/** + * Class _Table1 was generated by Cayenne. + * It is probably a good idea to avoid changing this class manually, + * since it may be overwritten next time code is regenerated. + * If you need to make any customizations, please use subclass. + */ +public abstract class _Table1 extends PersistentObject { + + private static final long serialVersionUID = 1L; + + public static final SelfProperty SELF = PropertyFactory.createSelf(Table1.class); + + public static final String ID_PK_COLUMN = "id"; + + public static final NumericProperty A = PropertyFactory.createNumeric("a", Long.class); + public static final NumericProperty B = PropertyFactory.createNumeric("b", Long.class); + + protected Long a; + protected Long b; + + + public void setA(Long a) { + beforePropertyWrite("a", this.a, a); + this.a = a; + } + + public Long getA() { + beforePropertyRead("a"); + return this.a; + } + + public void setB(Long b) { + beforePropertyWrite("b", this.b, b); + this.b = b; + } + + public Long getB() { + beforePropertyRead("b"); + return this.b; + } + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + case "a": + return this.a; + case "b": + return this.b; + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + case "a": + this.a = (Long)val; + break; + case "b": + this.b = (Long)val; + break; + default: + super.writePropertyDirectly(propName, val); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + writeSerialized(out); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + readSerialized(in); + } + + @Override + protected void writeState(ObjectOutputStream out) throws IOException { + super.writeState(out); + out.writeObject(this.a); + out.writeObject(this.b); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + this.a = (Long)in.readObject(); + this.b = (Long)in.readObject(); + } + +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/auto/_Table2.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/auto/_Table2.java new file mode 100644 index 00000000..3663ee55 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence/auto/_Table2.java @@ -0,0 +1,92 @@ +package io.bootique.cayenne.v50.junit5.persistence.auto; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.apache.cayenne.PersistentObject; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.exp.property.SelfProperty; +import org.apache.cayenne.exp.property.StringProperty; + +import io.bootique.cayenne.v50.junit5.persistence.Table2; + +/** + * Class _Table2 was generated by Cayenne. + * It is probably a good idea to avoid changing this class manually, + * since it may be overwritten next time code is regenerated. + * If you need to make any customizations, please use subclass. + */ +public abstract class _Table2 extends PersistentObject { + + private static final long serialVersionUID = 1L; + + public static final SelfProperty SELF = PropertyFactory.createSelf(Table2.class); + + public static final String ID_PK_COLUMN = "id"; + + public static final StringProperty NAME = PropertyFactory.createString("name", String.class); + + protected String name; + + + public void setName(String name) { + beforePropertyWrite("name", this.name, name); + this.name = name; + } + + public String getName() { + beforePropertyRead("name"); + return this.name; + } + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + case "name": + return this.name; + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + case "name": + this.name = (String)val; + break; + default: + super.writePropertyDirectly(propName, val); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + writeSerialized(out); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + readSerialized(in); + } + + @Override + protected void writeState(ObjectOutputStream out) throws IOException { + super.writeState(out); + out.writeObject(this.name); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + this.name = (String)in.readObject(); + } + +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T1.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T1.java new file mode 100644 index 00000000..4aecae14 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T1.java @@ -0,0 +1,9 @@ +package io.bootique.cayenne.v50.junit5.persistence3; + +import io.bootique.cayenne.v50.junit5.persistence3.auto._P3T1; + +public class P3T1 extends _P3T1 { + + private static final long serialVersionUID = 1L; + +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T2.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T2.java new file mode 100644 index 00000000..66dd402e --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T2.java @@ -0,0 +1,9 @@ +package io.bootique.cayenne.v50.junit5.persistence3; + +import io.bootique.cayenne.v50.junit5.persistence3.auto._P3T2; + +public class P3T2 extends _P3T2 { + + private static final long serialVersionUID = 1L; + +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T3.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T3.java new file mode 100644 index 00000000..42cee556 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T3.java @@ -0,0 +1,9 @@ +package io.bootique.cayenne.v50.junit5.persistence3; + +import io.bootique.cayenne.v50.junit5.persistence3.auto._P3T3; + +public class P3T3 extends _P3T3 { + + private static final long serialVersionUID = 1L; + +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T4.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T4.java new file mode 100644 index 00000000..1226fc7c --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/P3T4.java @@ -0,0 +1,9 @@ +package io.bootique.cayenne.v50.junit5.persistence3; + +import io.bootique.cayenne.v50.junit5.persistence3.auto._P3T4; + +public class P3T4 extends _P3T4 { + + private static final long serialVersionUID = 1L; + +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T1.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T1.java new file mode 100644 index 00000000..dd60e6fc --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T1.java @@ -0,0 +1,139 @@ +package io.bootique.cayenne.v50.junit5.persistence3.auto; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.List; + +import org.apache.cayenne.PersistentObject; +import org.apache.cayenne.exp.property.EntityProperty; +import org.apache.cayenne.exp.property.ListProperty; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.exp.property.SelfProperty; + +import io.bootique.cayenne.v50.junit5.persistence3.P3T1; +import io.bootique.cayenne.v50.junit5.persistence3.P3T2; +import io.bootique.cayenne.v50.junit5.persistence3.P3T3; +import io.bootique.cayenne.v50.junit5.persistence3.P3T4; + +/** + * Class _P3T1 was generated by Cayenne. + * It is probably a good idea to avoid changing this class manually, + * since it may be overwritten next time code is regenerated. + * If you need to make any customizations, please use subclass. + */ +public abstract class _P3T1 extends PersistentObject { + + private static final long serialVersionUID = 1L; + + public static final SelfProperty SELF = PropertyFactory.createSelf(P3T1.class); + + public static final String ID_PK_COLUMN = "id"; + + public static final ListProperty T2S = PropertyFactory.createList("t2s", P3T2.class); + public static final EntityProperty T3 = PropertyFactory.createEntity("t3", P3T3.class); + public static final ListProperty T4S = PropertyFactory.createList("t4s", P3T4.class); + + + protected Object t2s; + protected Object t3; + protected Object t4s; + + public void addToT2s(P3T2 obj) { + addToManyTarget("t2s", obj, true); + } + + public void removeFromT2s(P3T2 obj) { + removeToManyTarget("t2s", obj, true); + } + + @SuppressWarnings("unchecked") + public List getT2s() { + return (List)readProperty("t2s"); + } + + public void setT3(P3T3 t3) { + setToOneTarget("t3", t3, true); + } + + public P3T3 getT3() { + return (P3T3)readProperty("t3"); + } + + public void addToT4s(P3T4 obj) { + addToManyTarget("t4s", obj, true); + } + + public void removeFromT4s(P3T4 obj) { + removeToManyTarget("t4s", obj, true); + } + + @SuppressWarnings("unchecked") + public List getT4s() { + return (List)readProperty("t4s"); + } + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + case "t2s": + return this.t2s; + case "t3": + return this.t3; + case "t4s": + return this.t4s; + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + case "t2s": + this.t2s = val; + break; + case "t3": + this.t3 = val; + break; + case "t4s": + this.t4s = val; + break; + default: + super.writePropertyDirectly(propName, val); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + writeSerialized(out); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + readSerialized(in); + } + + @Override + protected void writeState(ObjectOutputStream out) throws IOException { + super.writeState(out); + out.writeObject(this.t2s); + out.writeObject(this.t3); + out.writeObject(this.t4s); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + this.t2s = in.readObject(); + this.t3 = in.readObject(); + this.t4s = in.readObject(); + } + +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T2.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T2.java new file mode 100644 index 00000000..3e493eb0 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T2.java @@ -0,0 +1,91 @@ +package io.bootique.cayenne.v50.junit5.persistence3.auto; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.apache.cayenne.PersistentObject; +import org.apache.cayenne.exp.property.EntityProperty; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.exp.property.SelfProperty; + +import io.bootique.cayenne.v50.junit5.persistence3.P3T1; +import io.bootique.cayenne.v50.junit5.persistence3.P3T2; + +/** + * Class _P3T2 was generated by Cayenne. + * It is probably a good idea to avoid changing this class manually, + * since it may be overwritten next time code is regenerated. + * If you need to make any customizations, please use subclass. + */ +public abstract class _P3T2 extends PersistentObject { + + private static final long serialVersionUID = 1L; + + public static final SelfProperty SELF = PropertyFactory.createSelf(P3T2.class); + + public static final String ID_PK_COLUMN = "id"; + + public static final EntityProperty T1 = PropertyFactory.createEntity("t1", P3T1.class); + + + protected Object t1; + + public void setT1(P3T1 t1) { + setToOneTarget("t1", t1, true); + } + + public P3T1 getT1() { + return (P3T1)readProperty("t1"); + } + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + case "t1": + return this.t1; + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + case "t1": + this.t1 = val; + break; + default: + super.writePropertyDirectly(propName, val); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + writeSerialized(out); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + readSerialized(in); + } + + @Override + protected void writeState(ObjectOutputStream out) throws IOException { + super.writeState(out); + out.writeObject(this.t1); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + this.t1 = in.readObject(); + } + +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T3.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T3.java new file mode 100644 index 00000000..00d002e9 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T3.java @@ -0,0 +1,91 @@ +package io.bootique.cayenne.v50.junit5.persistence3.auto; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.apache.cayenne.PersistentObject; +import org.apache.cayenne.exp.property.EntityProperty; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.exp.property.SelfProperty; + +import io.bootique.cayenne.v50.junit5.persistence3.P3T1; +import io.bootique.cayenne.v50.junit5.persistence3.P3T3; + +/** + * Class _P3T3 was generated by Cayenne. + * It is probably a good idea to avoid changing this class manually, + * since it may be overwritten next time code is regenerated. + * If you need to make any customizations, please use subclass. + */ +public abstract class _P3T3 extends PersistentObject { + + private static final long serialVersionUID = 1L; + + public static final SelfProperty SELF = PropertyFactory.createSelf(P3T3.class); + + public static final String ID_PK_COLUMN = "id"; + + public static final EntityProperty T1 = PropertyFactory.createEntity("t1", P3T1.class); + + + protected Object t1; + + public void setT1(P3T1 t1) { + setToOneTarget("t1", t1, true); + } + + public P3T1 getT1() { + return (P3T1)readProperty("t1"); + } + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + case "t1": + return this.t1; + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + case "t1": + this.t1 = val; + break; + default: + super.writePropertyDirectly(propName, val); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + writeSerialized(out); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + readSerialized(in); + } + + @Override + protected void writeState(ObjectOutputStream out) throws IOException { + super.writeState(out); + out.writeObject(this.t1); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + this.t1 = in.readObject(); + } + +} diff --git a/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T4.java b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T4.java new file mode 100644 index 00000000..42dbff22 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/java/io/bootique/cayenne/v50/junit5/persistence3/auto/_P3T4.java @@ -0,0 +1,97 @@ +package io.bootique.cayenne.v50.junit5.persistence3.auto; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.List; + +import org.apache.cayenne.PersistentObject; +import org.apache.cayenne.exp.property.ListProperty; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.exp.property.SelfProperty; + +import io.bootique.cayenne.v50.junit5.persistence3.P3T1; +import io.bootique.cayenne.v50.junit5.persistence3.P3T4; + +/** + * Class _P3T4 was generated by Cayenne. + * It is probably a good idea to avoid changing this class manually, + * since it may be overwritten next time code is regenerated. + * If you need to make any customizations, please use subclass. + */ +public abstract class _P3T4 extends PersistentObject { + + private static final long serialVersionUID = 1L; + + public static final SelfProperty SELF = PropertyFactory.createSelf(P3T4.class); + + public static final String ID_PK_COLUMN = "id"; + + public static final ListProperty T1S = PropertyFactory.createList("t1s", P3T1.class); + + + protected Object t1s; + + public void addToT1s(P3T1 obj) { + addToManyTarget("t1s", obj, true); + } + + public void removeFromT1s(P3T1 obj) { + removeToManyTarget("t1s", obj, true); + } + + @SuppressWarnings("unchecked") + public List getT1s() { + return (List)readProperty("t1s"); + } + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + case "t1s": + return this.t1s; + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + case "t1s": + this.t1s = val; + break; + default: + super.writePropertyDirectly(propName, val); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + writeSerialized(out); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + readSerialized(in); + } + + @Override + protected void writeState(ObjectOutputStream out) throws IOException { + super.writeState(out); + out.writeObject(this.t1s); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + this.t1s = in.readObject(); + } + +} diff --git a/bootique-cayenne50-junit5/src/test/resources/cayenne-project2.xml b/bootique-cayenne50-junit5/src/test/resources/cayenne-project2.xml new file mode 100644 index 00000000..e1333473 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/resources/cayenne-project2.xml @@ -0,0 +1,7 @@ + + + + diff --git a/bootique-cayenne50-junit5/src/test/resources/cayenne-project3.xml b/bootique-cayenne50-junit5/src/test/resources/cayenne-project3.xml new file mode 100644 index 00000000..f85c93ee --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/resources/cayenne-project3.xml @@ -0,0 +1,7 @@ + + + + diff --git a/bootique-cayenne50-junit5/src/test/resources/config-noautocommit.yml b/bootique-cayenne50-junit5/src/test/resources/config-noautocommit.yml new file mode 100644 index 00000000..3aae5ced --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/resources/config-noautocommit.yml @@ -0,0 +1,24 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +jdbc: + ds: + jdbcUrl: jdbc:derby:target/derby/derby3;create=true + autoCommit: false + +cayenne: + datasource: ds + configs: + - cayenne-project2.xml diff --git a/bootique-cayenne50-junit5/src/test/resources/config2.yml b/bootique-cayenne50-junit5/src/test/resources/config2.yml new file mode 100644 index 00000000..774d2bfc --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/resources/config2.yml @@ -0,0 +1,18 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +cayenne: + configs: + - cayenne-project2.xml diff --git a/bootique-cayenne50-junit5/src/test/resources/config3.yml b/bootique-cayenne50-junit5/src/test/resources/config3.yml new file mode 100644 index 00000000..bd189ddd --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/resources/config3.yml @@ -0,0 +1,18 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +cayenne: + configs: + - cayenne-project3.xml diff --git a/bootique-cayenne50-junit5/src/test/resources/datamap2.map.xml b/bootique-cayenne50-junit5/src/test/resources/datamap2.map.xml new file mode 100644 index 00000000..9648b46d --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/resources/datamap2.map.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + Default + ../java + entity + + templates/v4_1/superclass.vm + templates/v4_1/embeddable-subclass.vm + templates/v4_1/embeddable-superclass.vm + templates/v4_1/datamap-subclass.vm + templates/v4_1/datamap-superclass.vm + *.java + true + true + false + false + false + + diff --git a/bootique-cayenne50-junit5/src/test/resources/datamap3.map.xml b/bootique-cayenne50-junit5/src/test/resources/datamap3.map.xml new file mode 100644 index 00000000..0bba4f84 --- /dev/null +++ b/bootique-cayenne50-junit5/src/test/resources/datamap3.map.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + ../java + entity + + templates/v4_1/superclass.vm + templates/v4_1/embeddable-subclass.vm + templates/v4_1/embeddable-superclass.vm + templates/v4_1/datamap-subclass.vm + templates/v4_1/datamap-superclass.vm + *.java + true + true + false + false + false + + diff --git a/bootique-cayenne50/pom.xml b/bootique-cayenne50/pom.xml new file mode 100644 index 00000000..a3ef4cc8 --- /dev/null +++ b/bootique-cayenne50/pom.xml @@ -0,0 +1,143 @@ + + + + + + 4.0.0 + + io.bootique.cayenne + bootique-cayenne-parent + 3.0-SNAPSHOT + + + bootique-cayenne50 + jar + + bootique-cayenne50: Cayenne Integration Bundle for Bootique + Provides Apache Cayenne integration with Bootique + + + + + org.apache.cayenne + cayenne + ${cayenne50.version} + + + org.slf4j + slf4j-api + + + + + org.apache.cayenne + cayenne-commitlog + ${cayenne50.version} + + + + + + + + + io.bootique + bootique + + + io.bootique.jdbc + bootique-jdbc + + + org.apache.cayenne + cayenne + + + org.apache.cayenne + cayenne-commitlog + + + org.slf4j + slf4j-api + + + + + org.mockito + mockito-core + test + + + org.slf4j + slf4j-simple + test + + + io.bootique.jdbc + bootique-jdbc-junit5-derby + test + + + io.bootique.jdbc + bootique-jdbc-hikaricp + test + + + + + + + gpg + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + + + + rat + + + + org.apache.rat + apache-rat-plugin + + + derby.log + + + + + + + + diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/BQCayenneDataSourceFactory.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/BQCayenneDataSourceFactory.java new file mode 100644 index 00000000..0bb9dee1 --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/BQCayenneDataSourceFactory.java @@ -0,0 +1,134 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import io.bootique.jdbc.DataSourceFactory; +import org.apache.cayenne.configuration.DataNodeDescriptor; +import org.apache.cayenne.configuration.runtime.DelegatingDataSourceFactory; + +import javax.sql.DataSource; +import java.util.Collection; + +public class BQCayenneDataSourceFactory extends DelegatingDataSourceFactory { + + private static final String PARAM_PREFIX = "bqds:"; + + private DataSourceFactory bqDataSourceFactory; + private String defaultDataSourceName; + + public BQCayenneDataSourceFactory(DataSourceFactory bqDataSourceFactory, String defaultDataSourceName) { + this.bqDataSourceFactory = bqDataSourceFactory; + this.defaultDataSourceName = defaultDataSourceName; + } + + static String encodeDataSourceRef(String bqDataSource) { + return PARAM_PREFIX + bqDataSource; + } + + static String decodeDataSourceRef(String ref, String defaultName) { + return ref != null && ref.startsWith(PARAM_PREFIX) ? ref.substring(PARAM_PREFIX.length()) : defaultName; + } + + @Override + public DataSource getDataSource(DataNodeDescriptor nodeDescriptor) throws Exception { + + DataSource dataSource; + + // 1. try DataSource explicitly mapped in Bootique + dataSource = mappedBootiqueDataSource(nodeDescriptor); + if (dataSource != null) { + return dataSource; + } + + // 2. try loading from Cayenne XML + dataSource = cayenneDataSource(nodeDescriptor); + if (dataSource != null) { + return dataSource; + } + + // 3. try default DataSource from Bootique + dataSource = defaultBootiqueDataSource(); + if (dataSource != null) { + return dataSource; + } + + // 4. throw + return throwOnNoDataSource(); + } + + protected DataSource throwOnNoDataSource() { + Collection names = bqDataSourceFactory.allNames(); + if (names.isEmpty()) { + throw new IllegalStateException("No DataSources are available for Cayenne. " + + "Add a DataSource via 'bootique-jdbc' or map it in Cayenne project."); + } + + if (defaultDataSourceName == null) { + throw new IllegalStateException( + String.format("Can't map Cayenne DataSource: 'cayenne.datasource' is missing. " + + "Available DataSources are %s", names)); + } + + throw new IllegalStateException( + String.format("Can't map Cayenne DataSource: 'cayenne.datasource' is set to '%s'. " + + "Available DataSources: %s", defaultDataSourceName, names)); + } + + protected DataSource cayenneDataSource(DataNodeDescriptor nodeDescriptor) throws Exception { + + // trying to guess whether Cayenne will be able to provide a DataSource without our help... + if (shouldConfigureDataSourceFromProperties(nodeDescriptor) + || nodeDescriptor.getDataSourceFactoryType() != null + || nodeDescriptor.getDataSourceDescriptor() != null) { + + return super.getDataSource(nodeDescriptor); + } + + return null; + } + + protected DataSource defaultBootiqueDataSource() { + Collection names = bqDataSourceFactory.allNames(); + if (names.size() == 1) { + return mappedBootiqueDataSource(names.iterator().next()); + } + + return null; + } + + protected DataSource mappedBootiqueDataSource(DataNodeDescriptor nodeDescriptor) { + String datasource = decodeDataSourceRef(nodeDescriptor.getParameters(), defaultDataSourceName); + return mappedBootiqueDataSource(datasource); + } + + protected DataSource mappedBootiqueDataSource(String datasource) { + + if (datasource == null) { + return null; + } + + DataSource ds = bqDataSourceFactory.forName(datasource); + if (ds == null) { + throw new IllegalStateException("Unknown 'defaultDataSourceName': " + datasource); + } + + return ds; + } +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneConfigMerger.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneConfigMerger.java new file mode 100644 index 00000000..ecbab83c --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneConfigMerger.java @@ -0,0 +1,33 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import java.util.Collection; +import java.util.Objects; + +/** + * A simple merger that uses "last wins" strategy, returning the last collection passed to the method. + */ +public class CayenneConfigMerger { + + public Collection merge(Collection configs1, Collection configs2) { + return configs2 == null || configs2.isEmpty() ? Objects.requireNonNull(configs1) : configs2; + } +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneModule.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneModule.java new file mode 100644 index 00000000..715e0da9 --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneModule.java @@ -0,0 +1,70 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import io.bootique.BQModule; +import io.bootique.ModuleCrate; +import io.bootique.config.ConfigurationFactory; +import io.bootique.di.Binder; +import io.bootique.di.Provides; +import org.apache.cayenne.runtime.CayenneRuntime; + +import javax.inject.Singleton; + +/** + * @since 2.0 + */ +public class CayenneModule implements BQModule { + + private static final String CONFIG_PREFIX = "cayenne"; + + /** + * @param binder DI binder passed to the Module that invokes this method. + * @return an instance of {@link CayenneModuleExtender} that can be used to load Cayenne custom extensions. + */ + public static CayenneModuleExtender extend(Binder binder) { + return new CayenneModuleExtender(binder); + } + + @Override + public ModuleCrate crate() { + return ModuleCrate.of(this) + .description("Integrates Apache Cayenne ORM, v4.2") + .config(CONFIG_PREFIX, CayenneRuntimeFactory.class) + .build(); + } + + @Override + public void configure(Binder binder) { + extend(binder).initAllExtensions(); + } + + @Provides + @Singleton + CayenneConfigMerger provideConfigMerger() { + return new CayenneConfigMerger(); + } + + @Provides + @Singleton + CayenneRuntime createCayenneRuntime(ConfigurationFactory configFactory) { + return configFactory.config(CayenneRuntimeFactory.class, CONFIG_PREFIX).create(); + } +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneModuleExtender.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneModuleExtender.java new file mode 100644 index 00000000..fb299cb5 --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneModuleExtender.java @@ -0,0 +1,291 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import io.bootique.ModuleExtender; +import io.bootique.cayenne.v50.annotation.CayenneConfigs; +import io.bootique.cayenne.v50.annotation.CayenneListener; +import io.bootique.cayenne.v50.commitlog.MappedCommitLogListener; +import io.bootique.cayenne.v50.commitlog.MappedCommitLogListenerType; +import io.bootique.cayenne.v50.syncfilter.MappedDataChannelSyncFilter; +import io.bootique.cayenne.v50.syncfilter.MappedDataChannelSyncFilterType; +import io.bootique.di.Binder; +import io.bootique.di.Key; +import io.bootique.di.SetBuilder; +import org.apache.cayenne.DataChannelQueryFilter; +import org.apache.cayenne.DataChannelSyncFilter; +import org.apache.cayenne.access.types.ExtendedType; +import org.apache.cayenne.access.types.ValueObjectType; +import org.apache.cayenne.commitlog.CommitLogListener; +import org.apache.cayenne.di.Module; + +public class CayenneModuleExtender extends ModuleExtender { + + static final String COMMIT_LOG_ANNOTATION = CayenneModuleExtender.class.getPackageName() + ".commit_log_annotation"; + + private SetBuilder syncFilters; + private SetBuilder syncFilterTypes; + private SetBuilder queryFilters; + private SetBuilder listeners; + private SetBuilder projects; + private SetBuilder modules; + private SetBuilder startupListeners; + private SetBuilder commitLogListeners; + private SetBuilder commitLogListenerTypes; + private SetBuilder extendedType; + private SetBuilder valueObjectTypes; + + public CayenneModuleExtender(Binder binder) { + super(binder); + } + + @Override + public CayenneModuleExtender initAllExtensions() { + contributeListeners(); + contributeQueryFilters(); + contributeSyncFilters(); + contributeSyncFilterTypes(); + contributeModules(); + contributeProjects(); + contributeStartupListeners(); + contributeCommitLogListeners(); + contributeCommitLogListenerTypes(); + contributeExtendedTypes(); + contributeValueObjectTypes(); + return this; + } + + /** + * @since 3.0 + */ + public CayenneModuleExtender addStartupListener(CayenneStartupListener listener) { + contributeStartupListeners().addInstance(listener); + return this; + } + + /** + * @since 3.0 + */ + public CayenneModuleExtender addStartupListener(Class listenerType) { + contributeStartupListeners().add(listenerType); + return this; + } + + /** + * @since 1.1 + * @deprecated in favor of {@link #addSyncFilter(DataChannelSyncFilter, boolean)} + */ + @Deprecated(since = "3.0") + public CayenneModuleExtender addSyncFilter(DataChannelSyncFilter filter) { + return addSyncFilter(filter, false); + } + + /** + * @since 3.0 + */ + public CayenneModuleExtender addSyncFilter(DataChannelSyncFilter filter, boolean includeInTransaction) { + contributeSyncFilters().addInstance(new MappedDataChannelSyncFilter(filter, includeInTransaction)); + return this; + } + + /** + * @since 1.1 + * @deprecated in favor of {@link #addSyncFilter(Class, boolean)} + */ + @Deprecated(since = "3.0") + public CayenneModuleExtender addSyncFilter(Class filterType) { + return addSyncFilter(filterType, false); + } + + /** + * @since 3.0 + */ + public CayenneModuleExtender addSyncFilter(Class filterType, boolean includeInTransaction) { + contributeSyncFilterTypes().addInstance(new MappedDataChannelSyncFilterType(filterType, includeInTransaction)); + return this; + } + + /** + * @since 1.1 + */ + public CayenneModuleExtender addQueryFilter(DataChannelQueryFilter filter) { + contributeQueryFilters().addInstance(filter); + return this; + } + + /** + * @since 1.1 + */ + public CayenneModuleExtender addQueryFilter(Class filterType) { + contributeQueryFilters().add(filterType); + return this; + } + + public CayenneModuleExtender addListener(Object listener) { + contributeListeners().addInstance(listener); + return this; + } + + public CayenneModuleExtender addListener(Class listenerType) { + contributeListeners().add(listenerType); + return this; + } + + public CayenneModuleExtender addProject(String projectConfig) { + contributeProjects().addInstance(projectConfig); + return this; + } + + public CayenneModuleExtender addModule(Module module) { + contributeModules().addInstance(module); + return this; + } + + public CayenneModuleExtender addModule(Class moduleType) { + contributeModules().add(moduleType); + return this; + } + + public CayenneModuleExtender addModule(Key moduleKey) { + contributeModules().add(moduleKey); + return this; + } + + /** + * @since 3.0 + */ + public CayenneModuleExtender addCommitLogListener(CommitLogListener listener, boolean includeInTransaction) { + contributeCommitLogListeners().addInstance(new MappedCommitLogListener(listener, includeInTransaction, null)); + return this; + } + + /** + * @since 3.0 + */ + public CayenneModuleExtender addCommitLogListener(CommitLogListener listener, boolean includeInTransaction, Class after) { + contributeCommitLogListeners().addInstance(new MappedCommitLogListener(listener, includeInTransaction, after)); + return this; + } + + /** + * @since 3.0 + */ + public CayenneModuleExtender addCommitLogListener(Class listenerType, boolean includeInTransaction) { + contributeCommitLogListenerTypes().addInstance(new MappedCommitLogListenerType(listenerType, includeInTransaction, null)); + return this; + } + + /** + * @since 3.0 + */ + public CayenneModuleExtender addCommitLogListener(Class listenerType, boolean includeInTransaction, Class after) { + contributeCommitLogListenerTypes().addInstance(new MappedCommitLogListenerType(listenerType, includeInTransaction, after)); + return this; + } + + /** + * Enables entity filtering and change event preprocessing for commit log events. If called, Cayenne will be + * configured to respect {@link org.apache.cayenne.commitlog.CommitLog} annotation on entities. This annotation + * allows to explicitly specify change tracking for a subset of entities, obfuscate confidential properties + * (such as passwords), etc. + * + * @since 3.0 + */ + public CayenneModuleExtender applyCommitLogAnnotation() { + binder.bind(Key.get(Boolean.class, COMMIT_LOG_ANNOTATION)).toInstance(Boolean.TRUE); + return this; + } + + /** + * @since 3.0.M2 + */ + public CayenneModuleExtender addExtendedType(ExtendedType type) { + contributeExtendedTypes().addInstance(type); + return this; + } + + /** + * @since 3.0.M2 + */ + public CayenneModuleExtender addExtendedType(Class> type) { + contributeExtendedTypes().add(type); + return this; + } + + /** + * @since 3.0.M2 + */ + public CayenneModuleExtender addValueObjectType(ValueObjectType type) { + contributeValueObjectTypes().addInstance(type); + return this; + } + + /** + * @since 3.0.M2 + */ + public CayenneModuleExtender addValueObjectType(Class> type) { + contributeValueObjectTypes().add(type); + return this; + } + + protected SetBuilder contributeQueryFilters() { + return queryFilters != null ? queryFilters : (queryFilters = newSet(DataChannelQueryFilter.class)); + } + + protected SetBuilder contributeSyncFilters() { + return syncFilters != null ? syncFilters : (syncFilters = newSet(MappedDataChannelSyncFilter.class)); + } + + protected SetBuilder contributeSyncFilterTypes() { + return syncFilterTypes != null ? syncFilterTypes : (syncFilterTypes = newSet(MappedDataChannelSyncFilterType.class)); + } + + protected SetBuilder contributeListeners() { + return listeners != null ? listeners : (listeners = newSet(Object.class, CayenneListener.class)); + } + + protected SetBuilder contributeProjects() { + return projects != null ? projects : (projects = newSet(String.class, CayenneConfigs.class)); + } + + protected SetBuilder contributeModules() { + return modules != null ? modules : (modules = newSet(Module.class)); + } + + protected SetBuilder contributeStartupListeners() { + return startupListeners != null ? startupListeners : (startupListeners = newSet(CayenneStartupListener.class)); + } + + protected SetBuilder contributeCommitLogListeners() { + return commitLogListeners != null ? commitLogListeners : (commitLogListeners = newSet(MappedCommitLogListener.class)); + } + + protected SetBuilder contributeCommitLogListenerTypes() { + return commitLogListenerTypes != null ? commitLogListenerTypes : (commitLogListenerTypes = newSet(MappedCommitLogListenerType.class)); + } + + protected SetBuilder contributeExtendedTypes() { + return extendedType != null ? extendedType : (extendedType = newSet(ExtendedType.class)); + } + + protected SetBuilder contributeValueObjectTypes() { + return valueObjectTypes != null ? valueObjectTypes : (valueObjectTypes = newSet(ValueObjectType.class)); + } +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneRuntimeFactory.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneRuntimeFactory.java new file mode 100644 index 00000000..49810e27 --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneRuntimeFactory.java @@ -0,0 +1,347 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import io.bootique.annotation.BQConfig; +import io.bootique.annotation.BQConfigProperty; +import io.bootique.cayenne.v50.annotation.CayenneConfigs; +import io.bootique.cayenne.v50.annotation.CayenneListener; +import io.bootique.cayenne.v50.commitlog.CommitLogModuleBuilder; +import io.bootique.cayenne.v50.commitlog.MappedCommitLogListener; +import io.bootique.cayenne.v50.commitlog.MappedCommitLogListenerType; +import io.bootique.cayenne.v50.syncfilter.MappedDataChannelSyncFilter; +import io.bootique.cayenne.v50.syncfilter.MappedDataChannelSyncFilterType; +import io.bootique.di.Injector; +import io.bootique.jdbc.DataSourceFactory; +import io.bootique.shutdown.ShutdownManager; +import org.apache.cayenne.DataChannelQueryFilter; +import org.apache.cayenne.DataChannelSyncFilter; +import org.apache.cayenne.access.DataDomain; +import org.apache.cayenne.access.dbsync.CreateIfNoSchemaStrategy; +import org.apache.cayenne.access.dbsync.SchemaUpdateStrategyFactory; +import org.apache.cayenne.access.types.ExtendedType; +import org.apache.cayenne.access.types.ValueObjectType; +import org.apache.cayenne.configuration.runtime.CoreModule; +import org.apache.cayenne.di.Key; +import org.apache.cayenne.di.ListBuilder; +import org.apache.cayenne.di.Module; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.apache.cayenne.runtime.CayenneRuntimeBuilder; +import org.apache.cayenne.tx.TransactionFilter; + +import javax.inject.Inject; +import java.util.*; + +@BQConfig("Configures Cayenne stack, providing injectable CayenneRuntime.") +public class CayenneRuntimeFactory { + + private static final String DEFAULT_CONFIG = "cayenne-project.xml"; + + private final Injector injector; + private final ShutdownManager shutdownManager; + private final DataSourceFactory dataSourceFactory; + private final CayenneConfigMerger configMerger; + private final Set injectedCayenneConfigs; + private final Set customModules; + private final Set listeners; + private final Set queryFilters; + private final Set syncFilters; + private final Set syncFilterTypes; + private final Set startupCallbacks; + private final Set commitLogListeners; + private final Set commitLogListenerTypes; + private final Set extendedTypes; + private final Set valueObjectTypes; + + private String name; + private Collection configs; + private Map maps; + private String datasource; + private boolean createSchema; + + @Inject + public CayenneRuntimeFactory( + Injector injector, + ShutdownManager shutdownManager, + DataSourceFactory dataSourceFactory, + CayenneConfigMerger configMerger, + @CayenneConfigs Set injectedCayenneConfigs, + Set customModules, + @CayenneListener Set listeners, + Set queryFilters, + Set syncFilters, + Set syncFilterTypes, + Set startupCallbacks, + Set commitLogListeners, + Set commitLogListenerTypes, + Set extendedTypes, + Set valueObjectTypes) { + + this.injector = injector; + + this.shutdownManager = shutdownManager; + this.dataSourceFactory = dataSourceFactory; + this.configMerger = configMerger; + this.injectedCayenneConfigs = injectedCayenneConfigs; + this.customModules = customModules; + this.listeners = listeners; + this.queryFilters = queryFilters; + this.syncFilters = syncFilters; + this.syncFilterTypes = syncFilterTypes; + this.startupCallbacks = startupCallbacks; + this.commitLogListeners = commitLogListeners; + this.commitLogListenerTypes = commitLogListenerTypes; + this.extendedTypes = extendedTypes; + this.valueObjectTypes = valueObjectTypes; + } + + /** + * Sets an optional collection of Cayenne projects to load in runtime. If missing, will try to locate a file + * 'cayenne-project.xml' on classpath. + * + * @param configs a collection of Cayenne config XML files. + */ + @BQConfigProperty("An optional collection of Cayenne projects to load in runtime. If missing, will try to locate a " + + "file 'cayenne-project.xml' on classpath.") + public void setConfigs(Collection configs) { + this.configs = configs; + } + + /** + * Sets a map of DataMaps that are included in the app runtime without an explicit refrence in 'cayenne-project.xml'. + * + * @param maps map of DataMap configs + */ + @BQConfigProperty("A list of DataMaps that are included in the app runtime without an explicit refrence in " + + "'cayenne-project.xml'.") + public void setMaps(Map maps) { + this.maps = maps; + } + + /** + * Sets an optional name of the Cayenne stack we are created. This will be the name assigned to Cayenne DataDomain and + * used in event dispatches, etc. + * + * @param name a name of Cayenne stack created by the factory. + */ + @BQConfigProperty("An optional name of the Cayenne stack we are created. This will be the name assigned to Cayenne" + + " DataDomain and used in event dispatches, etc.") + public void setName(String name) { + this.name = name; + } + + @BQConfigProperty("An optional name of the DataSource to use in Cayenne. A DataSource with the matching name " + + "must be defined in 'bootique-jdbc' configuration. If missing, a DataSource from Cayenne project or a " + + "default DataSource from 'bootique-jdbc' is used.") + public void setDatasource(String datasource) { + this.datasource = datasource; + } + + /** + * Sets a flag that defines whether to attempt creation of the DB schema on startup based on Cayenne mapping. The + * default is 'false'. Automatic schema creation is often used in unit tests. + * + * @param createSchema if true, Cayenne will attempt to create database schema if it is missing. + */ + @BQConfigProperty("Whether to attempt creation of the DB schema on startup based on Cayenne mapping. The default is " + + "'false'. Automatic schema creation is often used in unit tests.") + public void setCreateSchema(boolean createSchema) { + this.createSchema = createSchema; + } + + public CayenneRuntime create() { + + Collection factoryConfigs = configs(); + + Collection extras = new ArrayList<>(customModules); + + appendExtendedTypesModule(extras, extendedTypes); + appendValueObjectTypesModule(extras, valueObjectTypes); + + appendQueryFiltersModule(extras, queryFilters); + appendSyncFiltersModule(extras, injector, syncFilters, syncFilterTypes); + appendCommitLogModules(extras, injector, commitLogListeners, commitLogListenerTypes); + + CayenneRuntime runtime = cayenneBuilder(dataSourceFactory) + .addConfigs(configMerger.merge(factoryConfigs, injectedCayenneConfigs)) + .addModules(extras) + .build(); + + shutdownManager.onShutdown(runtime, CayenneRuntime::shutdown); + + // TODO: listeners should be wrapped in a CayenneModule and added to Cayenne via DI, just like filters... + if (!listeners.isEmpty()) { + DataDomain domain = runtime.getDataDomain(); + listeners.forEach(domain::addListener); + } + + startupCallbacks.forEach(c -> c.onRuntimeCreated(runtime)); + + return runtime; + } + + /** + * Creates and returns a preconfigured {@link CayenneRuntimeBuilder} with Cayenne config, name, Java8 integration + * module and a DataSource. Override to add custom modules, extra projects, etc. + * + * @param dataSourceFactory injected Bootique {@link DataSourceFactory} + * @return a {@link CayenneRuntimeBuilder} that can be extended in subclasses. + */ + protected CayenneRuntimeBuilder cayenneBuilder(DataSourceFactory dataSourceFactory) { + return CayenneRuntime.builder(name).addModule(factoryModule(dataSourceFactory)); + } + + protected Module factoryModule(DataSourceFactory dataSourceFactory) { + return binder -> { + // provide schema creation hook + if (createSchema) { + binder.bind(SchemaUpdateStrategyFactory.class).toInstance(descriptor -> new CreateIfNoSchemaStrategy()); + } + + DefaultDataSourceName defaultDataSourceName = defaultDataSourceName(dataSourceFactory); + binder.bind(Key.get(DefaultDataSourceName.class)).toInstance(defaultDataSourceName); + binder.bindMap(DataMapConfig.class).putAll(maps != null ? maps : Map.of()); + + // provide default DataNode + // TODO: copied from Cayenne, as the corresponding provider is not public or rather + // until https://issues.apache.org/jira/browse/CAY-2095 is implemented + binder.bind(DataDomain.class).toProvider(SyntheticNodeDataDomainProvider.class); + + // Bootique DataSource hooks... + BQCayenneDataSourceFactory bqCayenneDSFactory = new BQCayenneDataSourceFactory(dataSourceFactory, datasource); + binder.bind(org.apache.cayenne.configuration.runtime.DataSourceFactory.class).toInstance(bqCayenneDSFactory); + }; + } + + Collection configs() { + + // order is important, so using ordered set... + Collection configs = new LinkedHashSet<>(); + + if (this.configs != null) { + configs.addAll(this.configs); + } + + return configs.isEmpty() ? defaultConfigs() : configs; + } + + Collection defaultConfigs() { + + // #54: if "maps" are specified explicitly, default config should be ignored + + if (maps != null && !maps.isEmpty()) { + return Collections.emptySet(); + } + + return getClass().getClassLoader().getResource(DEFAULT_CONFIG) != null + ? Collections.singleton(DEFAULT_CONFIG) + : Collections.emptySet(); + } + + DefaultDataSourceName defaultDataSourceName(DataSourceFactory dataSourceFactory) { + + if (datasource != null) { + return new DefaultDataSourceName(datasource); + } + + Collection allNames = dataSourceFactory.allNames(); + if (allNames.size() == 1) { + return new DefaultDataSourceName(allNames.iterator().next()); + } + + return new DefaultDataSourceName(null); + } + + protected void appendExtendedTypesModule(Collection modules, Set types) { + if (!types.isEmpty()) { + modules.add(b -> { + ListBuilder listBinder = CoreModule.contributeUserTypes(b); + types.forEach(listBinder::add); + }); + } + } + + protected void appendValueObjectTypesModule(Collection modules, Set types) { + if (!types.isEmpty()) { + modules.add(b -> { + ListBuilder listBinder = CoreModule.contributeValueObjectTypes(b); + types.forEach(listBinder::add); + }); + } + } + + protected void appendQueryFiltersModule(Collection modules, Set queryFilters) { + modules.add(b -> { + ListBuilder listBinder = CoreModule.contributeDomainQueryFilters(b); + queryFilters.forEach(listBinder::add); + }); + } + + protected void appendSyncFiltersModule( + Collection modules, + Injector injector, + Set syncFilters, + Set syncFilterTypes) { + + if (syncFilters.isEmpty() && syncFilterTypes.isEmpty()) { + return; + } + + List combined = new ArrayList<>(syncFilters.size() + syncFilterTypes.size()); + combined.addAll(syncFilters); + syncFilterTypes.stream() + .map(t -> new MappedDataChannelSyncFilter(injector.getInstance(t.getFilterType()), t.isIncludeInTransaction())) + .forEach(combined::add); + + modules.add(b -> { + ListBuilder listBinder = CoreModule.contributeDomainSyncFilters(b); + combined.forEach(mf -> { + if (mf.isIncludeInTransaction()) { + listBinder.insertBefore(mf.getFilter(), TransactionFilter.class); + } else { + listBinder.addAfter(mf.getFilter(), TransactionFilter.class); + } + }); + }); + } + + protected void appendCommitLogModules( + Collection modules, + Injector injector, + Set commitLogListeners, + Set commitLogListenerTypes) { + + if (commitLogListeners.isEmpty() && commitLogListenerTypes.isEmpty()) { + return; + } + + CommitLogModuleBuilder builder = new CommitLogModuleBuilder(); + commitLogListeners.forEach(builder::add); + commitLogListenerTypes.forEach(t -> builder.add(t.resolve(injector))); + + boolean applyCommitLogAnnotation = injector.hasProvider( + io.bootique.di.Key.get(Boolean.class, CayenneModuleExtender.COMMIT_LOG_ANNOTATION)); + if (applyCommitLogAnnotation) { + builder.applyCommitLogAnnotation(); + } + + builder.appendModules(modules); + } +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneStartupListener.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneStartupListener.java new file mode 100644 index 00000000..791ddb4a --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/CayenneStartupListener.java @@ -0,0 +1,33 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import org.apache.cayenne.runtime.CayenneRuntime; + +/** + * An injectable callback invoked after Cayenne stack is started. Used to run explicit actions, such as creating DB + * schema, etc. All other Cayenne extensions, such as service overrides, etc., should be contributed as Modules. + * + * @since 3.0 + */ +@FunctionalInterface +public interface CayenneStartupListener { + + void onRuntimeCreated(CayenneRuntime runtime); +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/DataMapConfig.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/DataMapConfig.java new file mode 100644 index 00000000..41628849 --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/DataMapConfig.java @@ -0,0 +1,60 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import io.bootique.annotation.BQConfig; +import io.bootique.annotation.BQConfigProperty; +import io.bootique.resource.ResourceFactory; + +@BQConfig("Includes a DataMap in the runtime directly, bypassing declaration in 'cayenne-project.xml'.") +public class DataMapConfig { + + private ResourceFactory location; + private String datasource; + private String name; + + public ResourceFactory getLocation() { + return location; + } + + @BQConfigProperty("DataMap XML file location.") + public void setLocation(ResourceFactory location) { + this.location = location; + } + + public String getDatasource() { + return datasource; + } + + @BQConfigProperty("Name of the DataSource to use with DataMap. A DataSource with this name must be defined in" + + " 'bootique-jdbc' config. If not set, the app use the default DataSource from bootique-jdbc.") + public void setDatasource(String datasource) { + this.datasource = datasource; + } + + public String getName() { + return name; + } + + @BQConfigProperty("Assigns a name to the DataMap. If not set, the name will be derived from location.") + public void setName(String name) { + this.name = name; + } +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/DefaultDataSourceName.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/DefaultDataSourceName.java new file mode 100644 index 00000000..d76cd045 --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/DefaultDataSourceName.java @@ -0,0 +1,36 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +/** + * A holder of an optional DefaultDataSource name. + */ +public class DefaultDataSourceName { + + private String optionalName; + + public DefaultDataSourceName(String optionalName) { + this.optionalName = optionalName; + } + + public String getOptionalName() { + return optionalName; + } +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/SyntheticNodeDataDomainProvider.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/SyntheticNodeDataDomainProvider.java new file mode 100644 index 00000000..1c0ca5c0 --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/SyntheticNodeDataDomainProvider.java @@ -0,0 +1,192 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import org.apache.cayenne.configuration.DataChannelDescriptor; +import org.apache.cayenne.configuration.DataMapLoader; +import org.apache.cayenne.configuration.DataNodeDescriptor; +import org.apache.cayenne.configuration.runtime.DataDomainProvider; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.di.Provider; +import org.apache.cayenne.map.DataMap; +import org.apache.cayenne.resource.Resource; +import org.apache.cayenne.resource.URLResource; + +import java.net.URL; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class SyntheticNodeDataDomainProvider extends DataDomainProvider { + + static final String DEFAULT_NAME = "cayenne"; + + @Inject + protected Provider xmlDataMapLoaderProvider; + + @Inject + private Map dataMapConfigs; + + @Inject + private DefaultDataSourceName defaultDatasource; + + @Override + protected DataChannelDescriptor loadDescriptor() { + DataChannelDescriptor d1 = super.loadDescriptor(); + DataChannelDescriptor d2 = mergeExplicitMaps(d1); + DataChannelDescriptor d3 = resolveMissingDataNodes(d2); + return d3; + } + + private DataChannelDescriptor mergeExplicitMaps(DataChannelDescriptor mainDescriptor) { + return dataMapConfigs.isEmpty() + ? mainDescriptor + : descriptorMerger.merge(mainDescriptor, loadExplicitMaps(mainDescriptor)); + } + + private DataChannelDescriptor loadExplicitMaps(DataChannelDescriptor mainDescriptor) { + DataChannelDescriptor descriptor = new DataChannelDescriptor(); + + // using the same name as main, to preserve the original name during merging + descriptor.setName(mainDescriptor.getName()); + + // clone properties from main to preserve the original properties during merging + descriptor.getProperties().putAll(mainDescriptor.getProperties()); + + Map nodeDescriptors = new HashMap<>(); + + for (DataMapConfig config : dataMapConfigs.values()) { + + DataMap dataMap = loadDataMap(config); + descriptor.getDataMaps().add(dataMap); + + String dataSourceName = config.getDatasource() != null + ? config.getDatasource() + : defaultDatasource.getOptionalName(); + + if (dataSourceName != null) { + createOrUpdateNodeDescriptor(nodeDescriptors, dataMap.getName(), dataSourceName); + } + } + + descriptor.getNodeDescriptors().addAll(nodeDescriptors.values()); + + return descriptor; + } + + private DataMap loadDataMap(DataMapConfig config) { + + URL url = config.getLocation().getUrl(); + String dataMapName = config.getName() != null ? config.getName() : url.toExternalForm(); + Resource location = new URLResource(url); + DataMap dataMap = xmlDataMapLoaderProvider.get().load(location); + dataMap.setName(dataMapName); + + return dataMap; + } + + private DataChannelDescriptor resolveMissingDataNodes(DataChannelDescriptor descriptor) { + + // create a DataNode even if there are no maps... + if (descriptor.getDataMaps().isEmpty() + && descriptor.getNodeDescriptors().isEmpty() + && defaultDatasource.getOptionalName() != null) { + getOrCreateDefaultNodeDescriptor(descriptor); + return descriptor; + } + + Set unresolvedMaps = new HashSet<>(); + descriptor.getDataMaps().forEach(dm -> unresolvedMaps.add(dm.getName())); + descriptor.getNodeDescriptors().forEach(nd -> nd.getDataMapNames().forEach(n -> unresolvedMaps.remove(n))); + + if (unresolvedMaps.size() > 0) { + getOrCreateDefaultNodeDescriptor(descriptor).getDataMapNames().addAll(unresolvedMaps); + } + + return descriptor; + } + + private void createOrUpdateNodeDescriptor( + Map nodes, + String dataMapName, + String dataSourceName) { + + // not assigning parent channel to new DataNodeDescriptor, as this object is going to get cloned on merge anyways, + // with the right final parent assigned.. + + nodes + .computeIfAbsent(dataSourceName, k -> createDataNodeDescriptor(dataSourceName)) + .getDataMapNames() + .add(dataMapName); + } + + private DataNodeDescriptor createDataNodeDescriptor(String dataSourceName) { + DataNodeDescriptor descriptor = new DataNodeDescriptor(createSyntheticDataNodeName(dataSourceName)); + descriptor.setParameters(BQCayenneDataSourceFactory.encodeDataSourceRef(dataSourceName)); + return descriptor; + } + + private DataNodeDescriptor getOrCreateDefaultNodeDescriptor(DataChannelDescriptor descriptor) { + + if (defaultDatasource.getOptionalName() == null) { + // TODO: more diagnostics + throw new IllegalStateException("No default DataSources is available."); + } + + // search for an existing node that has a matching DS ref + String parameters = BQCayenneDataSourceFactory.encodeDataSourceRef(defaultDatasource.getOptionalName()); + for (DataNodeDescriptor existing : descriptor.getNodeDescriptors()) { + if (parameters.equals(existing.getParameters())) { + + // TODO: should we renaming it to follow naming for default Node? + // (see TODO on "createSyntheticDataNodeName" though.. this whole thing may be moot eventually) + return existing; + } + } + + String name = createSyntheticDataNodeName(descriptor); + + DataNodeDescriptor nodeDescriptor = new DataNodeDescriptor(name); + nodeDescriptor.setDataChannelDescriptor(descriptor); + nodeDescriptor.setParameters(parameters); + + descriptor.getNodeDescriptors().add(nodeDescriptor); + descriptor.setDefaultNodeName(name); + + return nodeDescriptor; + } + + // TODO: in 2.0 simplify the naming.. just use the DS name for nodes. This will be the least confusing approach + protected String createSyntheticDataNodeName(String datasource) { + return datasource + "_node"; + } + + // TODO: in 2.0 simplify the naming.. just use the DS name for nodes. This will be the least confusing approach + protected String createSyntheticDataNodeName(DataChannelDescriptor descriptor) { + + // using Domain's name for the node name.. distinguishing nodes by name + // may be useful in case of multiple stacks used in the same + // transaction... + + return descriptor.getName() != null ? descriptor.getName() : DEFAULT_NAME; + } +} + diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/annotation/CayenneConfigs.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/annotation/CayenneConfigs.java new file mode 100644 index 00000000..eee6781b --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/annotation/CayenneConfigs.java @@ -0,0 +1,36 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.annotation; + +import javax.inject.Qualifier; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A binding annotation used for objects that are Cayenne project configs. + */ +@Target({ ElementType.PARAMETER, ElementType.FIELD }) +@Retention(RetentionPolicy.RUNTIME) +@Qualifier +public @interface CayenneConfigs { + +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/annotation/CayenneListener.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/annotation/CayenneListener.java new file mode 100644 index 00000000..f96bb9d7 --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/annotation/CayenneListener.java @@ -0,0 +1,36 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.annotation; + +import javax.inject.Qualifier; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * A binding annotation used for objects that are Cayenne listeners. + */ +@Target({ ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Qualifier +public @interface CayenneListener { + +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/CommitLogListenerGraph.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/CommitLogListenerGraph.java new file mode 100644 index 00000000..7aaa2402 --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/CommitLogListenerGraph.java @@ -0,0 +1,148 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.commitlog; + +import io.bootique.BootiqueException; +import org.apache.cayenne.commitlog.CommitLogListener; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * @since 3.0 + */ +class CommitLogListenerGraph { + + static List resolveAndSort(List unsorted) { + if (unsorted.isEmpty()) { + return Collections.emptyList(); + } else if (unsorted.size() == 1) { + return List.of(unsorted.get(0).getListener()); + } + + // separately handling the case of no sorting (because of the sorting limitations below) + boolean hasSort = false; + for (MappedCommitLogListener listener : unsorted) { + if (listener.getAfter() != null) { + hasSort = true; + break; + } + } + + if (!hasSort) { + return unsorted.stream().map(MappedCommitLogListener::getListener).collect(Collectors.toList()); + } + + // Second-guessing the DI-resolved "after" instances. "After" types may not be declared as singletons, but we + // need to resolve them to the List listeners... This will only work as long as each listener is of a unique + // type + + Map, CommitLogListener> listenersByType = new HashMap<>(); + unsorted.forEach(l -> { + if (listenersByType.put(l.getListener().getClass(), l.getListener()) != null) { + throw new IllegalStateException( + "Can't sort a list of listeners when they are not of unique types. " + + "More than one instance found of " + l.getListener().getClass()); + } + }); + + CommitLogListenerGraph graph = new CommitLogListenerGraph(unsorted.size()); + unsorted.forEach(l -> graph.add(l.getListener())); + unsorted.stream() + .filter(l -> listenersByType.get(l.getAfter()) != null) + .forEach(l -> graph.add(l.getListener(), listenersByType.get(l.getAfter()))); + + return graph.topSort(); + } + + private final Map> neighbors; + + CommitLogListenerGraph(int size) { + neighbors = new LinkedHashMap<>(size); + } + + void add(CommitLogListener vertex) { + neighbors.putIfAbsent(vertex, new ArrayList<>(0)); + } + + void add(CommitLogListener from, CommitLogListener to) { + neighbors.computeIfAbsent(from, k -> new ArrayList<>()).add(to); + this.add(to); + } + + private Map inDegree() { + + Map result = new LinkedHashMap<>(); + + for (CommitLogListener v : neighbors.keySet()) { + result.put(v, 0); + } + + for (CommitLogListener from : neighbors.keySet()) { + for (CommitLogListener to : neighbors.get(from)) { + result.put(to, result.get(to) + 1); + } + } + + return result; + } + + /** + * Return (as a List) the topological sort of the vertices. Throws an exception if cycles are detected. + */ + List topSort() { + Map degree = inDegree(); + Deque zeroDegree = new ArrayDeque<>(neighbors.size()); + List result = new ArrayList<>(neighbors.size()); + + degree.forEach((k, v) -> { + if (v == 0) { + zeroDegree.push(k); + } + }); + + while (!zeroDegree.isEmpty()) { + CommitLogListener v = zeroDegree.pop(); + result.add(v); + + neighbors.get(v).forEach(neighbor -> + degree.computeIfPresent(neighbor, (k, oldValue) -> { + int newValue = --oldValue; + if (newValue == 0) { + zeroDegree.push(k); + } + return newValue; + }) + ); + } + + // Check that we have used the entire graph (if not, there was a cycle) + if (result.size() != neighbors.size()) { + Set remainingKeys = new HashSet<>(neighbors.keySet()); + String cycleString = remainingKeys.stream() + .filter(o -> !result.contains(o)) + .map(l -> l.getClass().getSimpleName()) + .collect(Collectors.joining(" -> ")); + throw new BootiqueException(1, "Circular override dependency between listeners: " + cycleString); + } + + Collections.reverse(result); + return result; + } +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/CommitLogModuleBuilder.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/CommitLogModuleBuilder.java new file mode 100644 index 00000000..357edad2 --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/CommitLogModuleBuilder.java @@ -0,0 +1,166 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.commitlog; + +import org.apache.cayenne.DataChannel; +import org.apache.cayenne.commitlog.CommitLogFilter; +import org.apache.cayenne.commitlog.CommitLogListener; +import org.apache.cayenne.commitlog.meta.AnnotationCommitLogEntityFactory; +import org.apache.cayenne.commitlog.meta.CommitLogEntityFactory; +import org.apache.cayenne.commitlog.meta.IncludeAllCommitLogEntityFactory; +import org.apache.cayenne.configuration.runtime.CoreModule; +import org.apache.cayenne.di.DIRuntimeException; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.di.Module; +import org.apache.cayenne.di.Provider; +import org.apache.cayenne.tx.TransactionFilter; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +/** + * A helper to conditionally build extensions related to Cayenne CommitLog module. + * + * @since 3.0 + */ +public class CommitLogModuleBuilder { + + private boolean applyCommitLogAnnotation; + private List preTx; + private List postTx; + + public void appendModules(Collection modules) { + if (preTx != null && !preTx.isEmpty()) { + modules.add(preTxModule()); + } + + if (postTx != null && !postTx.isEmpty()) { + modules.add(postTxModule()); + } + } + + public void applyCommitLogAnnotation() { + this.applyCommitLogAnnotation = true; + } + + public void add(MappedCommitLogListener mappedListener) { + get(mappedListener.isIncludeInTransaction()).add(mappedListener); + } + + private List get(boolean includeInTx) { + return includeInTx ? getPreTx() : getPostTx(); + } + + private List getPreTx() { + if (preTx == null) { + preTx = new ArrayList<>(); + } + + return preTx; + } + + private List getPostTx() { + if (postTx == null) { + postTx = new ArrayList<>(); + } + + return postTx; + } + + private Module preTxModule() { + + // reimplementing CommitLogModuleExtender.module() to allow both pre and post commit filters. + // TODO: Maybe this should go to Cayenne? + + return binder -> { + binder.bind(PreTxCommitLogFilter.class).toProviderInstance(new PreTxCommitLogFilterProvider()); + CoreModule + .contributeDomainSyncFilters(binder).insertBefore(PreTxCommitLogFilter.class, TransactionFilter.class); + }; + + } + + private Module postTxModule() { + + // reimplementing CommitLogModuleExtender.module() to allow both pre and post commit filters. + // TODO: Maybe this should go to Cayenne? + + return binder -> { + binder.bind(PostTxCommitLogFilter.class).toProviderInstance(new PostTxCommitLogFilterProvider()); + CoreModule + .contributeDomainSyncFilters(binder).addAfter(PostTxCommitLogFilter.class, TransactionFilter.class); + }; + } + + private CommitLogEntityFactory createEntityFactory(Provider dataChannelProvider) { + return applyCommitLogAnnotation + ? new AnnotationCommitLogEntityFactory(dataChannelProvider) + : new IncludeAllCommitLogEntityFactory(); + } + + // TODO: We need 4 classes to create what are essentially two instances of CommitLogFilter, because the filter + // factory code needs access to both this Builder ivars and "Provider dataChannelProvider" from the + // Cayenne injection stack. So aside from moving this logic to Cayenne, Cayenne DI should do a better job bridging + // between manually created and DI-created instances. + + class PreTxCommitLogFilterProvider implements Provider { + + @Inject + Provider dataChannelProvider; + + @Override + public PreTxCommitLogFilter get() throws DIRuntimeException { + Objects.requireNonNull(dataChannelProvider); + + return new PreTxCommitLogFilter( + createEntityFactory(dataChannelProvider), + CommitLogListenerGraph.resolveAndSort(preTx)); + } + } + + class PostTxCommitLogFilterProvider implements Provider { + + @Inject + Provider dataChannelProvider; + + @Override + public PostTxCommitLogFilter get() throws DIRuntimeException { + Objects.requireNonNull(dataChannelProvider); + + return new PostTxCommitLogFilter( + createEntityFactory(dataChannelProvider), + CommitLogListenerGraph.resolveAndSort(postTx)); + } + } + + static class PreTxCommitLogFilter extends CommitLogFilter { + + public PreTxCommitLogFilter(CommitLogEntityFactory entityFactory, List listeners) { + super(entityFactory, listeners); + } + } + + static class PostTxCommitLogFilter extends CommitLogFilter { + public PostTxCommitLogFilter(CommitLogEntityFactory entityFactory, List listeners) { + super(entityFactory, listeners); + } + } +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/MappedCommitLogListener.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/MappedCommitLogListener.java new file mode 100644 index 00000000..ac302086 --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/MappedCommitLogListener.java @@ -0,0 +1,49 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.commitlog; + +import org.apache.cayenne.commitlog.CommitLogListener; + +/** + * @since 3.0 + */ +public class MappedCommitLogListener { + + private final CommitLogListener listener; + private final boolean includeInTransaction; + private final Class after; + + public MappedCommitLogListener(CommitLogListener listener, boolean includeInTransaction, Class after) { + this.listener = listener; + this.includeInTransaction = includeInTransaction; + this.after = after; + } + + public Class getAfter() { + return after; + } + + public CommitLogListener getListener() { + return listener; + } + + public boolean isIncludeInTransaction() { + return includeInTransaction; + } +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/MappedCommitLogListenerType.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/MappedCommitLogListenerType.java new file mode 100644 index 00000000..f769aab6 --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/commitlog/MappedCommitLogListenerType.java @@ -0,0 +1,47 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.commitlog; + +import io.bootique.di.Injector; +import org.apache.cayenne.commitlog.CommitLogListener; + +/** + * @since 3.0 + */ +public class MappedCommitLogListenerType { + + private final Class listenerType; + private final boolean includeInTransaction; + private final Class after; + + public MappedCommitLogListenerType( + Class listenerType, + boolean includeInTransaction, + Class after) { + + this.listenerType = listenerType; + this.includeInTransaction = includeInTransaction; + this.after = after; + } + + public MappedCommitLogListener resolve(Injector listenerResolver) { + CommitLogListener listener = listenerResolver.getInstance(listenerType); + return new MappedCommitLogListener(listener, includeInTransaction, after); + } +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/syncfilter/MappedDataChannelSyncFilter.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/syncfilter/MappedDataChannelSyncFilter.java new file mode 100644 index 00000000..6f151fc2 --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/syncfilter/MappedDataChannelSyncFilter.java @@ -0,0 +1,43 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.syncfilter; + +import org.apache.cayenne.DataChannelSyncFilter; + +/** + * @since 3.0 + */ +public class MappedDataChannelSyncFilter { + + private final DataChannelSyncFilter filter; + private final boolean includeInTransaction; + + public MappedDataChannelSyncFilter(DataChannelSyncFilter filter, boolean includeInTransaction) { + this.filter = filter; + this.includeInTransaction = includeInTransaction; + } + + public DataChannelSyncFilter getFilter() { + return filter; + } + + public boolean isIncludeInTransaction() { + return includeInTransaction; + } +} diff --git a/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/syncfilter/MappedDataChannelSyncFilterType.java b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/syncfilter/MappedDataChannelSyncFilterType.java new file mode 100644 index 00000000..1d4d302d --- /dev/null +++ b/bootique-cayenne50/src/main/java/io/bootique/cayenne/v50/syncfilter/MappedDataChannelSyncFilterType.java @@ -0,0 +1,43 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50.syncfilter; + +import org.apache.cayenne.DataChannelSyncFilter; + +/** + * @since 3.0 + */ +public class MappedDataChannelSyncFilterType { + + private final Class filterType; + private final boolean includeInTransaction; + + public MappedDataChannelSyncFilterType(Class filterType, boolean includeInTransaction) { + this.filterType = filterType; + this.includeInTransaction = includeInTransaction; + } + + public Class getFilterType() { + return filterType; + } + + public boolean isIncludeInTransaction() { + return includeInTransaction; + } +} diff --git a/bootique-cayenne50/src/main/resources/META-INF/services/io.bootique.BQModule b/bootique-cayenne50/src/main/resources/META-INF/services/io.bootique.BQModule new file mode 100644 index 00000000..cb9f3e87 --- /dev/null +++ b/bootique-cayenne50/src/main/resources/META-INF/services/io.bootique.BQModule @@ -0,0 +1 @@ +io.bootique.cayenne.v50.CayenneModule \ No newline at end of file diff --git a/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModuleIT.java b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModuleIT.java new file mode 100644 index 00000000..7b82557a --- /dev/null +++ b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModuleIT.java @@ -0,0 +1,268 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import io.bootique.di.BQModule; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestFactory; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.access.DataDomain; +import org.apache.cayenne.configuration.runtime.DataDomainLoadException; +import org.apache.cayenne.di.Key; +import org.apache.cayenne.query.SQLSelect; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; + +import static org.junit.jupiter.api.Assertions.*; + +@BQTest +public class CayenneModuleIT { + + @BQTestTool + final BQTestFactory testFactory = new BQTestFactory(); + + @Test + public void noConfig() { + + CayenneRuntime runtime = testFactory.app("-c", "classpath:noconfig.yml") + .autoLoadModules() + .createRuntime() + .getInstance(CayenneRuntime.class); + + DataDomain domain = runtime.getDataDomain(); + assertEquals("cayenne", domain.getName()); + assertEquals("cayenne", domain.getDefaultNode().getName()); + + assertTrue(domain.getEntityResolver().getDbEntities().isEmpty()); + } + + @Test + public void noConfig_Name() { + + CayenneRuntime runtime = testFactory.app("-c", "classpath:noconfig-named.yml") + .autoLoadModules() + .createRuntime() + .getInstance(CayenneRuntime.class); + + DataDomain domain = runtime.getDataDomain(); + assertEquals("my", domain.getName()); + assertEquals("my", domain.getDefaultNode().getName()); + } + + @Test + public void fullConfig() { + CayenneRuntime runtime = testFactory.app("-c", "classpath:fullconfig.yml") + .autoLoadModules() + .createRuntime() + .getInstance(CayenneRuntime.class); + + DataDomain domain = runtime.getDataDomain(); + assertNotNull(domain.getEntityResolver().getDbEntity("db_entity2")); + + // trigger a DB op + SQLSelect.dataRowQuery("SELECT * FROM db_entity2").select(runtime.newContext()); + } + + @Test + public void twoConfigs() { + CayenneRuntime runtime = testFactory.app("-c", "classpath:twoconfigs.yml") + .autoLoadModules() + .createRuntime() + .getInstance(CayenneRuntime.class); + + DataDomain domain = runtime.getDataDomain(); + assertNotNull(domain.getEntityResolver().getDbEntity("db_entity")); + assertNotNull(domain.getEntityResolver().getDbEntity("db_entity2")); + + // trigger a DB op + SQLSelect.dataRowQuery("SELECT * FROM db_entity2").select(runtime.newContext()); + } + + @Test + public void config_ExplicitMaps_SharedDataSource() { + + CayenneRuntime runtime = testFactory.app("-c", "classpath:config_explicit_maps.yml") + .autoLoadModules() + .createRuntime() + .getInstance(CayenneRuntime.class); + + DataDomain domain = runtime.getDataDomain(); + assertNotNull(domain.getEntityResolver().getDbEntity("db_entity")); + assertNotNull(domain.getEntityResolver().getDbEntity("db_entity2")); + + // trigger a DB op + SQLSelect.dataRowQuery("map1", "SELECT * FROM db_entity").select(runtime.newContext()); + SQLSelect.dataRowQuery("map2", "SELECT * FROM db_entity2").select(runtime.newContext()); + } + + @Test + public void config_ExplicitMaps_DifferentDataSources() { + + CayenneRuntime runtime = testFactory.app("-c", "classpath:config_explicit_maps_2.yml") + .autoLoadModules() + .createRuntime() + .getInstance(CayenneRuntime.class); + + DataDomain domain = runtime.getDataDomain(); + assertNotNull(domain.getEntityResolver().getDbEntity("db_entity")); + assertNotNull(domain.getEntityResolver().getDbEntity("db_entity2")); + + // trigger a DB op + SQLSelect.dataRowQuery("map1", "SELECT * FROM db_entity").select(runtime.newContext()); + SQLSelect.dataRowQuery("map2", "SELECT * FROM db_entity2").select(runtime.newContext()); + } + + @Test + public void defaultDataSource() throws SQLException { + + CayenneRuntime runtime = testFactory.app("-c", "classpath:noconfig.yml") + .autoLoadModules() + .createRuntime() + .getInstance(CayenneRuntime.class); + + DataDomain domain = runtime.getDataDomain(); + assertNotNull(domain.getDataNode("cayenne")); + + try (Connection c = domain.getDataNode("cayenne").getDataSource().getConnection()) { + DatabaseMetaData md = c.getMetaData(); + assertEquals("jdbc:derby:target/derby/bqjdbc_noconfig", md.getURL()); + } + } + + @Test + public void undefinedDataSource() { + + CayenneRuntime runtime = testFactory.app("-c", "classpath:noconfig_2ds.yml") + .autoLoadModules() + .createRuntime() + .getInstance(CayenneRuntime.class); + + try { + runtime.getDataDomain(); + } catch (DataDomainLoadException e) { + assertTrue(e.getCause().getMessage() + .startsWith("Can't map Cayenne DataSource: 'cayenne.datasource' is missing.")); + } + } + + @Test + public void unmatchedDataSource() { + + CayenneRuntime runtime = testFactory.app("-c", "classpath:noconfig_2ds_unmatched.yml") + .autoLoadModules() + .createRuntime() + .getInstance(CayenneRuntime.class); + + try { + runtime.getDataDomain(); + fail(); + } catch (DataDomainLoadException e) { + String message = e.getCause().getMessage(); + assertEquals("No configuration present for DataSource named 'ds3'", message); + } + } + + @Test + public void contributeModules() { + + Key key = Key.get(Object.class, "_test_"); + Object value = new Object(); + + BQModule bqModule = b -> { + org.apache.cayenne.di.Module cayenneModule = (cb) -> cb.bind(key).toInstance(value); + CayenneModule.extend(b).addModule(cayenneModule); + }; + + CayenneRuntime runtime = testFactory.app("-c", "classpath:fullconfig.yml") + .autoLoadModules() + .module(bqModule) + .createRuntime() + .getInstance(CayenneRuntime.class); + + assertSame(value, runtime.getInjector().getInstance(key)); + } + + @Test + public void mergeConfigs() { + + BQModule cayenneProjectModule = binder -> CayenneModule.extend(binder).addProject("cayenne-project2.xml"); + + CayenneRuntime runtime = testFactory.app("-c", "classpath:noconfig.yml") + .autoLoadModules() + .module(cayenneProjectModule) + .createRuntime() + .getInstance(CayenneRuntime.class); + + DataDomain domain = runtime.getDataDomain(); + assertFalse(domain.getEntityResolver().getDbEntities().isEmpty()); + } + + @Test + public void configMaps_Plus_AddProject_DataSourceAssignment() { + + // see https://github.com/bootique/bootique-cayenne/issues/69 + // module-provided project has no DataNode .. must be assigned the default one + + CayenneRuntime runtime = testFactory + .app("--config=classpath:ConfigMaps_Plus_AddProject_DataSourceAssignment.yml") + .autoLoadModules() + .module(b -> CayenneModule.extend(b).addProject("cayenne-project1.xml")) + .createRuntime() + .getInstance(CayenneRuntime.class); + + DataDomain domain = runtime.getDataDomain(); + + assertEquals(3, domain.getDataMaps().size()); + assertNotNull(domain.getDataMap("datamap1")); + assertNotNull(domain.getDataMap("map2")); + assertNotNull(domain.getDataMap("map3")); + + assertEquals(2, domain.getDataNodes().size()); + assertNotNull(domain.getDataNode("ds1_node")); + assertNotNull(domain.getDataNode("ds2_node")); + + assertSame(domain.getDataNode("ds1_node"), domain.lookupDataNode(domain.getDataMap("datamap1"))); + assertSame(domain.getDataNode("ds2_node"), domain.lookupDataNode(domain.getDataMap("map2"))); + assertSame(domain.getDataNode("ds1_node"), domain.lookupDataNode(domain.getDataMap("map3"))); + } + + @Test + public void config_ExplicitMaps_DifferentConfig() { + + CayenneRuntime runtime = testFactory.app( + "-c", "classpath:config_explicit_maps_2_1.yml", + "-c", "classpath:config_explicit_maps_2_2.yml") + .autoLoadModules() + .createRuntime() + .getInstance(CayenneRuntime.class); + + DataDomain domain = runtime.getDataDomain(); + assertNotNull(domain.getEntityResolver().getDbEntity("db_entity")); + assertNotNull(domain.getEntityResolver().getDbEntity("db_entity2")); + + // trigger a DB op + SQLSelect.dataRowQuery("map1", "SELECT * FROM db_entity").select(runtime.newContext()); + SQLSelect.dataRowQuery("map2", "SELECT * FROM db_entity2").select(runtime.newContext()); + } +} diff --git a/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModuleTest.java b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModuleTest.java new file mode 100644 index 00000000..7c50a101 --- /dev/null +++ b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModuleTest.java @@ -0,0 +1,34 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import io.bootique.cayenne.v50.CayenneModule; +import io.bootique.junit5.BQModuleTester; +import io.bootique.junit5.BQTest; +import org.junit.jupiter.api.Test; + +@BQTest +public class CayenneModuleTest { + + @Test + public void check() { + BQModuleTester.of(CayenneModule.class).testAutoLoadable().testConfig(); + } +} diff --git a/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_CommitLogListenersAnnotatedIT.java b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_CommitLogListenersAnnotatedIT.java new file mode 100644 index 00000000..fdcc7e4a --- /dev/null +++ b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_CommitLogListenersAnnotatedIT.java @@ -0,0 +1,107 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import io.bootique.BQRuntime; +import io.bootique.Bootique; +import io.bootique.cayenne.v50.cayenne.T1; +import io.bootique.cayenne.v50.cayenne.T2; +import io.bootique.junit5.BQApp; +import io.bootique.junit5.BQTest; +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.ObjectId; +import org.apache.cayenne.commitlog.CommitLogListener; +import org.apache.cayenne.commitlog.Confidential; +import org.apache.cayenne.commitlog.model.ChangeMap; +import org.apache.cayenne.commitlog.model.ObjectChange; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +@BQTest +public class CayenneModule_CommitLogListenersAnnotatedIT { + + @BQApp(skipRun = true) + final BQRuntime app = Bootique + .app("--config=classpath:explicitconfig.yml") + .autoLoadModules() + .module(b -> CayenneModule.extend(b) + .applyCommitLogAnnotation() + .addCommitLogListener(new L1(), true) + .addCommitLogListener(L2.class, false)) + .createRuntime(); + + @BeforeEach + void clearListenerState() { + L1.tracked.clear(); + L2.tracked.clear(); + } + + @Test + public void listeners() { + + ObjectContext c = app.getInstance(CayenneRuntime.class).newContext(); + T1 t1 = c.newObject(T1.class); + t1.setName("t1"); + + T2 t2 = c.newObject(T2.class); + t2.setName("t2"); + + c.commitChanges(); + + assertEquals(Set.of(t2.getObjectId()), L1.tracked.keySet()); + assertEquals(Set.of(t2.getObjectId()), L2.tracked); + + assertSame( + Confidential.getInstance(), + L1.tracked.get(t2.getObjectId()).getAttributeChanges().get(T2.NAME.getName()).getNewValue(), + "Expected confidential name to be hidden"); + } + + static class L1 implements CommitLogListener { + + static Map tracked = new HashMap(); + + @Override + public void onPostCommit(ObjectContext originatingContext, ChangeMap changes) { + changes.getChanges().keySet().stream().filter(id -> !id.isTemporary()).forEach(id -> tracked.put(id, changes.getChanges().get(id))); + } + } + + static class L2 implements CommitLogListener { + + static Set tracked = new HashSet<>(); + + @Override + public void onPostCommit(ObjectContext originatingContext, ChangeMap changes) { + changes.getChanges().keySet().stream().filter(id -> !id.isTemporary()).forEach(tracked::add); + } + } +} + + diff --git a/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_CommitLogListenersIT.java b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_CommitLogListenersIT.java new file mode 100644 index 00000000..096578ed --- /dev/null +++ b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_CommitLogListenersIT.java @@ -0,0 +1,97 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import io.bootique.BQRuntime; +import io.bootique.Bootique; +import io.bootique.junit5.BQApp; +import io.bootique.junit5.BQTest; +import org.apache.cayenne.*; +import org.apache.cayenne.commitlog.CommitLogListener; +import org.apache.cayenne.commitlog.model.ChangeMap; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.Set; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@BQTest +public class CayenneModule_CommitLogListenersIT { + + @BQApp(skipRun = true) + final BQRuntime app = Bootique + .app("--config=classpath:genericconfig.yml") + .autoLoadModules() + .module(b -> CayenneModule.extend(b) + .addCommitLogListener(new L1(), true) + .addCommitLogListener(L2.class, false)) + .createRuntime(); + + @BeforeEach + void clearListenerState() { + L1.tracked.clear(); + L2.tracked.clear(); + } + + @Test + public void listeners() { + + GenericPersistentObject o1 = new GenericPersistentObject(); + o1.setObjectId(ObjectId.of("T1")); + o1.writeProperty("name", "n" + 1); + + GenericPersistentObject o2 = new GenericPersistentObject(); + o2.setObjectId(ObjectId.of("T1")); + o2.writeProperty("name", "n" + 2); + + ObjectContext c = app.getInstance(CayenneRuntime.class).newContext(); + c.registerNewObject(o1); + c.registerNewObject(o2); + c.commitChanges(); + + assertEquals(Set.of(o1.getObjectId(), o2.getObjectId()), L1.tracked); + assertEquals(Set.of(o1.getObjectId(), o2.getObjectId()), L2.tracked); + + } + + static class L1 implements CommitLogListener { + + static Set tracked = new HashSet<>(); + + @Override + public void onPostCommit(ObjectContext originatingContext, ChangeMap changes) { + changes.getChanges().keySet().stream().filter(id -> !id.isTemporary()).forEach(tracked::add); + } + } + + static class L2 implements CommitLogListener { + + static Set tracked = new HashSet<>(); + + @Override + public void onPostCommit(ObjectContext originatingContext, ChangeMap changes) { + changes.getChanges().keySet().stream().filter(id -> !id.isTemporary()).forEach(tracked::add); + } + } +} + + diff --git a/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_CommitLogListenersOrderIT.java b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_CommitLogListenersOrderIT.java new file mode 100644 index 00000000..a49e08e1 --- /dev/null +++ b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_CommitLogListenersOrderIT.java @@ -0,0 +1,110 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import io.bootique.BQRuntime; +import io.bootique.Bootique; +import io.bootique.junit5.BQApp; +import io.bootique.junit5.BQTest; +import org.apache.cayenne.GenericPersistentObject; +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.ObjectId; +import org.apache.cayenne.commitlog.CommitLogListener; +import org.apache.cayenne.commitlog.model.ChangeMap; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@BQTest +public class CayenneModule_CommitLogListenersOrderIT { + + static final List sequence = new ArrayList<>(); + + @BQApp(skipRun = true) + final BQRuntime app = Bootique + .app("--config=classpath:genericconfig.yml") + .autoLoadModules() + .module(b -> CayenneModule.extend(b) + .addCommitLogListener(L2.class, true) + .addCommitLogListener(new L1(), true, L2.class) + .addCommitLogListener(L2.class, false) + .addCommitLogListener(L3.class, true, L4.class) + .addCommitLogListener(L4.class, true, L1.class) + ) + .createRuntime(); + + @BeforeEach + void clearListenerState() { + sequence.clear(); + } + + @Test + public void listeners() { + + GenericPersistentObject o1 = new GenericPersistentObject(); + o1.setObjectId(ObjectId.of("T1")); + o1.writeProperty("name", "n" + 1); + + ObjectContext c = app.getInstance(CayenneRuntime.class).newContext(); + c.registerNewObject(o1); + c.commitChanges(); + + assertEquals("L2:L1:L4:L3:L2", String.join(":", sequence)); + } + + public static class L1 implements CommitLogListener { + + @Override + public void onPostCommit(ObjectContext originatingContext, ChangeMap changes) { + sequence.add("L1"); + } + } + + public static class L2 implements CommitLogListener { + + @Override + public void onPostCommit(ObjectContext originatingContext, ChangeMap changes) { + sequence.add("L2"); + } + } + + public static class L3 implements CommitLogListener { + + @Override + public void onPostCommit(ObjectContext originatingContext, ChangeMap changes) { + sequence.add("L3"); + } + } + + public static class L4 implements CommitLogListener { + + @Override + public void onPostCommit(ObjectContext originatingContext, ChangeMap changes) { + sequence.add("L4"); + } + } +} + + diff --git a/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_ListenersIT.java b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_ListenersIT.java new file mode 100644 index 00000000..a6d1aa2d --- /dev/null +++ b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneModule_ListenersIT.java @@ -0,0 +1,100 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import io.bootique.di.BQModule; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestFactory; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.GenericPersistentObject; +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.ObjectId; +import org.apache.cayenne.annotation.PostPersist; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@BQTest +public class CayenneModule_ListenersIT { + + @BQTestTool + final BQTestFactory testFactory = new BQTestFactory(); + + private CayenneRuntime runtimeWithListeners(Object... listeners) { + + BQModule listenersModule = (binder) -> { + CayenneModuleExtender extender = CayenneModule.extend(binder); + Arrays.asList(listeners).forEach(extender::addListener); + }; + + return testFactory.app("--config=classpath:genericconfig.yml") + .autoLoadModules() + .module(listenersModule) + .createRuntime() + .getInstance(CayenneRuntime.class); + } + + @Test + public void listeners() { + + L1 l1 = new L1(); + + GenericPersistentObject o1 = new GenericPersistentObject(); + o1.setObjectId(ObjectId.of("T1")); + o1.writeProperty("name", "n" + 1); + + GenericPersistentObject o2 = new GenericPersistentObject(); + o2.setObjectId(ObjectId.of("T1")); + o2.writeProperty("name", "n" + 2); + + CayenneRuntime runtime = runtimeWithListeners(l1); + try { + ObjectContext c = runtime.newContext(); + c.registerNewObject(o1); + c.registerNewObject(o2); + c.commitChanges(); + + } finally { + runtime.shutdown(); + } + + assertEquals(2, l1.postPersisted.size()); + assertTrue(l1.postPersisted.contains(o1)); + assertTrue(l1.postPersisted.contains(o2)); + } + + static class L1 { + + List postPersisted = new ArrayList<>(); + + @PostPersist + public void postPersist(Object o) { + postPersisted.add(o); + } + } +} + + diff --git a/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneStartupListenerIT.java b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneStartupListenerIT.java new file mode 100644 index 00000000..e6306ea9 --- /dev/null +++ b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/CayenneStartupListenerIT.java @@ -0,0 +1,80 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import io.bootique.BQRuntime; +import io.bootique.di.BQModule; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestFactory; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.*; + +@BQTest +public class CayenneStartupListenerIT { + + @BQTestTool + final BQTestFactory testFactory = new BQTestFactory(); + + private BQRuntime runtimeWithCallbacks(CayenneStartupListener... callbacks) { + + BQModule callbacksModule = b -> { + CayenneModuleExtender extender = CayenneModule.extend(b); + Arrays.asList(callbacks).forEach(extender::addStartupListener); + }; + + return testFactory.app() + .autoLoadModules() + .module(callbacksModule) + .createRuntime(); + } + + @Test + public void onSync() { + + TestStartupListener l1 = new TestStartupListener(); + TestStartupListener l2 = new TestStartupListener(); + + BQRuntime rt = runtimeWithCallbacks(l1, l2); + + // lazy init... Cayenne stack won't start until someone requests CayenneRuntime + assertFalse(l1.invoked); + assertFalse(l2.invoked); + + rt.getInstance(CayenneRuntime.class); + + assertTrue(l1.invoked); + assertTrue(l2.invoked); + } + + static final class TestStartupListener implements CayenneStartupListener { + + boolean invoked; + + @Override + public void onRuntimeCreated(CayenneRuntime runtime) { + assertNotNull(runtime); + this.invoked = true; + } + } +} diff --git a/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/DataChannelQueryFilterIT.java b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/DataChannelQueryFilterIT.java new file mode 100644 index 00000000..fc0f7d45 --- /dev/null +++ b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/DataChannelQueryFilterIT.java @@ -0,0 +1,71 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import io.bootique.di.BQModule; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestFactory; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.DataChannelQueryFilter; +import org.apache.cayenne.QueryResponse; +import org.apache.cayenne.query.ObjectSelect; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@BQTest +public class DataChannelQueryFilterIT { + + @BQTestTool + final BQTestFactory testFactory = new BQTestFactory(); + + private CayenneRuntime runtimeWithFilters(DataChannelQueryFilter... filters) { + + BQModule filtersModule = (binder) -> { + CayenneModuleExtender extender = CayenneModule.extend(binder); + Arrays.asList(filters).forEach(extender::addQueryFilter); + }; + + return testFactory.app("--config=classpath:genericconfig.yml") + .autoLoadModules() + .module(filtersModule) + .createRuntime() + .getInstance(CayenneRuntime.class); + } + + @Test + public void onQuery() { + + DataChannelQueryFilter f = mock(DataChannelQueryFilter.class); + when(f.onQuery(any(), any(), any())).thenReturn(mock(QueryResponse.class)); + + CayenneRuntime runtime = runtimeWithFilters(f); + try { + ObjectSelect.dbQuery("T1").select(runtime.newContext()); + } finally { + runtime.shutdown(); + } + + verify(f).onQuery(any(), any(), any()); + } +} diff --git a/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/DataChannelSyncFilterIT.java b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/DataChannelSyncFilterIT.java new file mode 100644 index 00000000..ed51366c --- /dev/null +++ b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/DataChannelSyncFilterIT.java @@ -0,0 +1,86 @@ +/* + * Licensed to ObjectStyle LLC under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ObjectStyle LLC licenses + * this file to you 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 io.bootique.cayenne.v50; + +import io.bootique.di.BQModule; +import io.bootique.junit5.BQTest; +import io.bootique.junit5.BQTestFactory; +import io.bootique.junit5.BQTestTool; +import org.apache.cayenne.DataChannelSyncFilter; +import org.apache.cayenne.GenericPersistentObject; +import org.apache.cayenne.ObjectContext; +import org.apache.cayenne.ObjectId; +import org.apache.cayenne.graph.GraphDiff; +import org.apache.cayenne.runtime.CayenneRuntime; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.*; + +@BQTest +public class DataChannelSyncFilterIT { + + @BQTestTool + final BQTestFactory testFactory = new BQTestFactory(); + + private CayenneRuntime runtimeWithFilters(DataChannelSyncFilter... filters) { + + BQModule filtersModule = (binder) -> { + CayenneModuleExtender extender = CayenneModule.extend(binder); + Arrays.asList(filters).forEach(f -> extender.addSyncFilter(f, false)); + }; + + return testFactory.app("--config=classpath:genericconfig.yml") + .autoLoadModules() + .module(filtersModule) + .createRuntime() + .getInstance(CayenneRuntime.class); + } + + @Test + public void onSync() { + + DataChannelSyncFilter f = mock(DataChannelSyncFilter.class); + when(f.onSync(any(), any(), anyInt(), any())).thenReturn(mock(GraphDiff.class)); + + GenericPersistentObject o1 = new GenericPersistentObject(); + o1.setObjectId(ObjectId.of("T1")); + o1.writeProperty("name", "n" + 1); + + GenericPersistentObject o2 = new GenericPersistentObject(); + o2.setObjectId(ObjectId.of("T1")); + o2.writeProperty("name", "n" + 2); + + CayenneRuntime runtime = runtimeWithFilters(f); + try { + ObjectContext c = runtime.newContext(); + c.registerNewObject(o1); + c.registerNewObject(o2); + c.commitChanges(); + + } finally { + runtime.shutdown(); + } + + verify(f).onSync(any(), any(), anyInt(), any()); + } +} diff --git a/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/T1.java b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/T1.java new file mode 100644 index 00000000..92403c2e --- /dev/null +++ b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/T1.java @@ -0,0 +1,9 @@ +package io.bootique.cayenne.v50.cayenne; + +import io.bootique.cayenne.v50.cayenne.auto._T1; + +public class T1 extends _T1 { + + private static final long serialVersionUID = 1L; + +} diff --git a/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/T2.java b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/T2.java new file mode 100644 index 00000000..0baddcd9 --- /dev/null +++ b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/T2.java @@ -0,0 +1,9 @@ +package io.bootique.cayenne.v50.cayenne; + +import io.bootique.cayenne.v50.cayenne.auto._T2; +import org.apache.cayenne.commitlog.CommitLog; + +@CommitLog(confidential = "name") +public class T2 extends _T2 { + +} diff --git a/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/auto/_T1.java b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/auto/_T1.java new file mode 100644 index 00000000..1c08b868 --- /dev/null +++ b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/auto/_T1.java @@ -0,0 +1,94 @@ +package io.bootique.cayenne.v50.cayenne.auto; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.apache.cayenne.PersistentObject; +import org.apache.cayenne.exp.property.NumericIdProperty; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.exp.property.SelfProperty; +import org.apache.cayenne.exp.property.StringProperty; + +import io.bootique.cayenne.v50.cayenne.T1; + +/** + * Class _T1 was generated by Cayenne. + * It is probably a good idea to avoid changing this class manually, + * since it may be overwritten next time code is regenerated. + * If you need to make any customizations, please use subclass. + */ +public abstract class _T1 extends PersistentObject { + + private static final long serialVersionUID = 1L; + + public static final SelfProperty SELF = PropertyFactory.createSelf(T1.class); + + public static final NumericIdProperty ID_PK_PROPERTY = PropertyFactory.createNumericId("id", "T1", Integer.class); + public static final String ID_PK_COLUMN = "id"; + + public static final StringProperty NAME = PropertyFactory.createString("name", String.class); + + protected String name; + + + public void setName(String name) { + beforePropertyWrite("name", this.name, name); + this.name = name; + } + + public String getName() { + beforePropertyRead("name"); + return this.name; + } + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + case "name": + return this.name; + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + case "name": + this.name = (String)val; + break; + default: + super.writePropertyDirectly(propName, val); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + writeSerialized(out); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + readSerialized(in); + } + + @Override + protected void writeState(ObjectOutputStream out) throws IOException { + super.writeState(out); + out.writeObject(this.name); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + this.name = (String)in.readObject(); + } + +} diff --git a/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/auto/_T2.java b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/auto/_T2.java new file mode 100644 index 00000000..ee41ac57 --- /dev/null +++ b/bootique-cayenne50/src/test/java/io/bootique/cayenne/v50/cayenne/auto/_T2.java @@ -0,0 +1,94 @@ +package io.bootique.cayenne.v50.cayenne.auto; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.apache.cayenne.PersistentObject; +import org.apache.cayenne.exp.property.NumericIdProperty; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.exp.property.SelfProperty; +import org.apache.cayenne.exp.property.StringProperty; + +import io.bootique.cayenne.v50.cayenne.T2; + +/** + * Class _T2 was generated by Cayenne. + * It is probably a good idea to avoid changing this class manually, + * since it may be overwritten next time code is regenerated. + * If you need to make any customizations, please use subclass. + */ +public abstract class _T2 extends PersistentObject { + + private static final long serialVersionUID = 1L; + + public static final SelfProperty SELF = PropertyFactory.createSelf(T2.class); + + public static final NumericIdProperty ID_PK_PROPERTY = PropertyFactory.createNumericId("id", "T2", Integer.class); + public static final String ID_PK_COLUMN = "id"; + + public static final StringProperty NAME = PropertyFactory.createString("name", String.class); + + protected String name; + + + public void setName(String name) { + beforePropertyWrite("name", this.name, name); + this.name = name; + } + + public String getName() { + beforePropertyRead("name"); + return this.name; + } + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + case "name": + return this.name; + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + case "name": + this.name = (String)val; + break; + default: + super.writePropertyDirectly(propName, val); + } + } + + private void writeObject(ObjectOutputStream out) throws IOException { + writeSerialized(out); + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + readSerialized(in); + } + + @Override + protected void writeState(ObjectOutputStream out) throws IOException { + super.writeState(out); + out.writeObject(this.name); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + this.name = (String)in.readObject(); + } + +} diff --git a/bootique-cayenne50/src/test/resources/ConfigMaps_Plus_AddProject_DataSourceAssignment.yml b/bootique-cayenne50/src/test/resources/ConfigMaps_Plus_AddProject_DataSourceAssignment.yml new file mode 100644 index 00000000..0ddbc653 --- /dev/null +++ b/bootique-cayenne50/src/test/resources/ConfigMaps_Plus_AddProject_DataSourceAssignment.yml @@ -0,0 +1,31 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +jdbc: + ds1: + jdbcUrl: jdbc:derby:target/derby/cayenne1;create=true + ds2: + jdbcUrl: jdbc:derby:target/derby/cayenne2;create=true + +cayenne: + datasource: ds1 + maps: + m1: + location: classpath:datamap2.map.xml + name: map2 + datasource: ds2 + m2: + location: classpath:datamap3.map.xml + name: map3 \ No newline at end of file diff --git a/bootique-cayenne50/src/test/resources/cayenne-project1.xml b/bootique-cayenne50/src/test/resources/cayenne-project1.xml new file mode 100644 index 00000000..8942b061 --- /dev/null +++ b/bootique-cayenne50/src/test/resources/cayenne-project1.xml @@ -0,0 +1,7 @@ + + + + diff --git a/bootique-cayenne50/src/test/resources/cayenne-project2.xml b/bootique-cayenne50/src/test/resources/cayenne-project2.xml new file mode 100644 index 00000000..e1333473 --- /dev/null +++ b/bootique-cayenne50/src/test/resources/cayenne-project2.xml @@ -0,0 +1,7 @@ + + + + diff --git a/bootique-cayenne50/src/test/resources/config_explicit_maps.yml b/bootique-cayenne50/src/test/resources/config_explicit_maps.yml new file mode 100644 index 00000000..271069c2 --- /dev/null +++ b/bootique-cayenne50/src/test/resources/config_explicit_maps.yml @@ -0,0 +1,30 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +jdbc: + ds: + jdbcUrl: jdbc:derby:target/derby/bqjdbc_config_explicit_maps;create=true + +cayenne: + datasource: ds + createSchema: true + maps: + m1: + location: classpath:datamap1.map.xml + name: map1 + datasource: ds + m2: + location: classpath:datamap2.map.xml + name: map2 diff --git a/bootique-cayenne50/src/test/resources/config_explicit_maps_2.yml b/bootique-cayenne50/src/test/resources/config_explicit_maps_2.yml new file mode 100644 index 00000000..293b7bae --- /dev/null +++ b/bootique-cayenne50/src/test/resources/config_explicit_maps_2.yml @@ -0,0 +1,32 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +jdbc: + ds1: + jdbcUrl: jdbc:derby:target/derby/bqjdbc_config_explicit_maps1;create=true + ds2: + jdbcUrl: jdbc:derby:target/derby/bqjdbc_config_explicit_maps2;create=true + +cayenne: + datasource: ds1 + createSchema: true + maps: + m1: + location: classpath:datamap1.map.xml + name: map1 + m2: + location: classpath:datamap2.map.xml + name: map2 + datasource: ds2 diff --git a/bootique-cayenne50/src/test/resources/config_explicit_maps_2_1.yml b/bootique-cayenne50/src/test/resources/config_explicit_maps_2_1.yml new file mode 100644 index 00000000..ec582c18 --- /dev/null +++ b/bootique-cayenne50/src/test/resources/config_explicit_maps_2_1.yml @@ -0,0 +1,26 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +jdbc: + ds1: + jdbcUrl: jdbc:derby:target/derby/bqjdbc_config_explicit_maps1;create=true + +# module 1 +cayenne: + maps: + m1: + datasource: ds1 + name: map1 + location: "classpath:datamap1.map.xml" diff --git a/bootique-cayenne50/src/test/resources/config_explicit_maps_2_2.yml b/bootique-cayenne50/src/test/resources/config_explicit_maps_2_2.yml new file mode 100644 index 00000000..bad4a704 --- /dev/null +++ b/bootique-cayenne50/src/test/resources/config_explicit_maps_2_2.yml @@ -0,0 +1,26 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +jdbc: + ds2: + jdbcUrl: jdbc:derby:target/derby/bqjdbc_config_explicit_maps2;create=true + +# module 2 +cayenne: + maps: + m2: + name: map2 + datasource: ds2 + location: "classpath:datamap2.map.xml" \ No newline at end of file diff --git a/bootique-cayenne50/src/test/resources/datamap1.map.xml b/bootique-cayenne50/src/test/resources/datamap1.map.xml new file mode 100644 index 00000000..25007d51 --- /dev/null +++ b/bootique-cayenne50/src/test/resources/datamap1.map.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/bootique-cayenne50/src/test/resources/datamap2.map.xml b/bootique-cayenne50/src/test/resources/datamap2.map.xml new file mode 100644 index 00000000..68f58e3d --- /dev/null +++ b/bootique-cayenne50/src/test/resources/datamap2.map.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/bootique-cayenne50/src/test/resources/datamap3.map.xml b/bootique-cayenne50/src/test/resources/datamap3.map.xml new file mode 100644 index 00000000..53496b6a --- /dev/null +++ b/bootique-cayenne50/src/test/resources/datamap3.map.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/bootique-cayenne50/src/test/resources/explicitconfig.yml b/bootique-cayenne50/src/test/resources/explicitconfig.yml new file mode 100644 index 00000000..1ed063c2 --- /dev/null +++ b/bootique-cayenne50/src/test/resources/explicitconfig.yml @@ -0,0 +1,24 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +jdbc: + ds: + jdbcUrl: jdbc:derby:target/derby/bqjdbc_explicitconfig;create=true + +cayenne: + datasource: ds + createSchema: true + configs: + - io/bootique/cayenne/v50/cayenne-explicit.xml diff --git a/bootique-cayenne50/src/test/resources/fullconfig.yml b/bootique-cayenne50/src/test/resources/fullconfig.yml new file mode 100644 index 00000000..04c2a0a7 --- /dev/null +++ b/bootique-cayenne50/src/test/resources/fullconfig.yml @@ -0,0 +1,24 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +jdbc: + ds: + jdbcUrl: jdbc:derby:target/derby/bqjdbc_fullconfig;create=true + +cayenne: + datasource: ds + createSchema: true + configs: + - cayenne-project2.xml diff --git a/bootique-cayenne50/src/test/resources/genericconfig.yml b/bootique-cayenne50/src/test/resources/genericconfig.yml new file mode 100644 index 00000000..c69b851e --- /dev/null +++ b/bootique-cayenne50/src/test/resources/genericconfig.yml @@ -0,0 +1,24 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +jdbc: + ds: + jdbcUrl: jdbc:derby:target/derby/bqjdbc_fullconfig;create=true + +cayenne: + datasource: ds + createSchema: true + configs: + - io/bootique/cayenne/v50/cayenne-generic.xml diff --git a/bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/cayenne-explicit.xml b/bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/cayenne-explicit.xml new file mode 100644 index 00000000..cb369084 --- /dev/null +++ b/bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/cayenne-explicit.xml @@ -0,0 +1,7 @@ + + + + diff --git a/bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/cayenne-generic.xml b/bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/cayenne-generic.xml new file mode 100644 index 00000000..bb7f826a --- /dev/null +++ b/bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/cayenne-generic.xml @@ -0,0 +1,7 @@ + + + + diff --git a/bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/explicit.map.xml b/bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/explicit.map.xml new file mode 100644 index 00000000..6440e04a --- /dev/null +++ b/bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/explicit.map.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/generic.map.xml b/bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/generic.map.xml new file mode 100644 index 00000000..b8ecc1cb --- /dev/null +++ b/bootique-cayenne50/src/test/resources/io/bootique/cayenne/v50/generic.map.xml @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/bootique-cayenne50/src/test/resources/noconfig-named.yml b/bootique-cayenne50/src/test/resources/noconfig-named.yml new file mode 100644 index 00000000..113fe56e --- /dev/null +++ b/bootique-cayenne50/src/test/resources/noconfig-named.yml @@ -0,0 +1,21 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +jdbc: + ds: + jdbcUrl: jdbc:derby:target/derby/bqjdbc_noconfig;create=true + +cayenne: + name: my diff --git a/bootique-cayenne50/src/test/resources/noconfig.yml b/bootique-cayenne50/src/test/resources/noconfig.yml new file mode 100644 index 00000000..fc3658c7 --- /dev/null +++ b/bootique-cayenne50/src/test/resources/noconfig.yml @@ -0,0 +1,20 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +jdbc: + ds: + jdbcUrl: jdbc:derby:target/derby/bqjdbc_noconfig;create=true + +cayenne: {} diff --git a/bootique-cayenne50/src/test/resources/noconfig_2ds.yml b/bootique-cayenne50/src/test/resources/noconfig_2ds.yml new file mode 100644 index 00000000..f91c1c42 --- /dev/null +++ b/bootique-cayenne50/src/test/resources/noconfig_2ds.yml @@ -0,0 +1,20 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +jdbc: + ds1: + jdbcUrl: jdbc:derby:target/derby/cayenne1;create=true + ds2: + jdbcUrl: jdbc:derby:target/derby/cayenne2;create=true diff --git a/bootique-cayenne50/src/test/resources/noconfig_2ds_unmatched.yml b/bootique-cayenne50/src/test/resources/noconfig_2ds_unmatched.yml new file mode 100644 index 00000000..32406dc6 --- /dev/null +++ b/bootique-cayenne50/src/test/resources/noconfig_2ds_unmatched.yml @@ -0,0 +1,23 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +jdbc: + ds1: + jdbcUrl: jdbc:derby:target/derby/cayenne1;create=true + ds2: + jdbcUrl: jdbc:derby:target/derby/cayenne2;create=true + +cayenne: + datasource: ds3 \ No newline at end of file diff --git a/bootique-cayenne50/src/test/resources/twoconfigs.yml b/bootique-cayenne50/src/test/resources/twoconfigs.yml new file mode 100644 index 00000000..dbf892b9 --- /dev/null +++ b/bootique-cayenne50/src/test/resources/twoconfigs.yml @@ -0,0 +1,23 @@ +# Licensed to ObjectStyle LLC under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ObjectStyle LLC licenses this file to You 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. + +jdbc: + ds: + jdbcUrl: jdbc:derby:target/derby/bqjdbc_fullconfig;create=true + +cayenne: + configs: + - cayenne-project1.xml + - cayenne-project2.xml diff --git a/pom.xml b/pom.xml index 50f211d0..ffa347db 100644 --- a/pom.xml +++ b/pom.xml @@ -37,7 +37,6 @@ - bootique-cayenne41 bootique-cayenne41-jcache bootique-cayenne41-test @@ -47,6 +46,10 @@ bootique-cayenne42-jcache bootique-cayenne42-junit5 + bootique-cayenne50 + bootique-cayenne50-jcache + bootique-cayenne50-junit5 + bootique-cayenne41-default-it bootique-cayenne41-default-multi-map-it @@ -56,6 +59,7 @@ 3.1.3 4.1.1 4.2.1 + 5.0-M1