├── .gitignore ├── LICENSE ├── README.md ├── Solving the N + 1 Query problem.pdf ├── assembler-cache-caffeine ├── .gitignore ├── README.md ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── github │ │ └── pellse │ │ └── assembler │ │ └── caching │ │ └── caffeine │ │ └── CaffeineCacheFactory.java │ └── test │ └── java │ └── io │ └── github │ └── pellse │ └── assembler │ └── caching │ └── caffeine │ └── AssemblerCaffeineCacheTest.java ├── assembler-kotlin-extension ├── .gitignore ├── build.gradle └── src │ ├── main │ └── kotlin │ │ └── io │ │ └── github │ │ └── pellse │ │ └── assembler │ │ └── kotlin │ │ ├── AssemblerKotlinSupport.kt │ │ └── CacheUtils.kt │ └── test │ └── kotlin │ └── io │ └── github │ └── pellse │ └── assembler │ └── test │ └── AssemblerKotlinTest.kt ├── assembler-spring-cache ├── .gitignore ├── README.md ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── github │ │ └── pellse │ │ └── assembler │ │ └── caching │ │ └── spring │ │ └── SpringCacheFactory.java │ └── test │ └── java │ └── io │ └── github │ └── pellse │ └── assembler │ └── caching │ └── spring │ └── SpringCacheAssemblerTest.java ├── assembler ├── .gitignore ├── README.md ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── github │ │ └── pellse │ │ ├── assembler │ │ ├── Assembler.java │ │ ├── AssemblerBuilder.java │ │ ├── BatchRule.java │ │ ├── ErrorHandler.java │ │ ├── LifeCycleEventBroadcaster.java │ │ ├── LifeCycleEventListener.java │ │ ├── LifeCycleEventSource.java │ │ ├── MapFactory.java │ │ ├── QueryUtils.java │ │ ├── Rule.java │ │ ├── RuleContext.java │ │ ├── RuleMapper.java │ │ ├── RuleMapperContext.java │ │ ├── RuleMapperSource.java │ │ ├── WindowingStrategy.java │ │ └── caching │ │ │ ├── Cache.java │ │ │ ├── CacheEvent.java │ │ │ ├── ConcurrentCache.java │ │ │ ├── DefaultCache.java │ │ │ ├── OneToManyCache.java │ │ │ ├── OneToOneCache.java │ │ │ ├── OptimizedCache.java │ │ │ ├── factory │ │ │ ├── AsyncCacheFactory.java │ │ │ ├── CacheContext.java │ │ │ ├── CacheFactory.java │ │ │ ├── CacheTransformer.java │ │ │ ├── ConcurrentCacheFactory.java │ │ │ ├── DeferCacheFactory.java │ │ │ ├── MapperCacheFactory.java │ │ │ ├── ObservableCacheFactory.java │ │ │ ├── SerializeCacheFactory.java │ │ │ ├── SortByCacheFactory.java │ │ │ ├── StreamTableFactory.java │ │ │ └── StreamTableFactoryBuilder.java │ │ │ └── merge │ │ │ ├── MergeFunction.java │ │ │ ├── MergeFunctionContext.java │ │ │ ├── MergeFunctionFactory.java │ │ │ └── MergeFunctions.java │ │ ├── concurrent │ │ ├── CASLockStrategy.java │ │ ├── CoreLock.java │ │ ├── Lock.java │ │ ├── LockStrategy.java │ │ ├── ReactiveGuard.java │ │ ├── ReactiveGuardEvent.java │ │ ├── ReactiveGuardEventListener.java │ │ └── StampedLockStrategy.java │ │ └── util │ │ ├── DelegateAware.java │ │ ├── ObjectUtils.java │ │ ├── collection │ │ └── CollectionUtils.java │ │ ├── function │ │ ├── BiLongPredicate.java │ │ ├── CheckedFunction3.java │ │ ├── Function10.java │ │ ├── Function11.java │ │ ├── Function12.java │ │ ├── Function3.java │ │ ├── Function4.java │ │ ├── Function5.java │ │ ├── Function6.java │ │ ├── Function7.java │ │ ├── Function8.java │ │ ├── Function9.java │ │ └── InterruptedFunction3.java │ │ ├── lock │ │ └── LockSupplier.java │ │ ├── lookup │ │ └── LookupTable.java │ │ └── reactive │ │ └── ReactiveUtils.java │ └── test │ └── java │ └── io │ └── github │ └── pellse │ └── assembler │ ├── test │ ├── AssemblerJavaTest.java │ ├── AssemblerTestUtils.java │ ├── BatchRuleTest.java │ ├── CacheTest.java │ ├── ChainedAssemblerTest.java │ ├── CompositeCorrelationIdResolverTest.java │ └── EmbeddedAssemblerTest.java │ └── util │ ├── BillingInfo.java │ ├── Customer.java │ ├── OrderItem.java │ ├── Transaction.java │ └── TransactionSet.java ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── Assembler.mp4 ├── Assembler.png ├── AssemblerExample.png └── CohereFluxExample.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | 5 | /assembler-akka-stream/ 6 | /assembler-core/ 7 | /assembler-flux/ 8 | /assembler-reactive-stream-operators/ 9 | /assembler-rxjava/ 10 | 11 | ### STS ### 12 | .apt_generated 13 | .classpath 14 | .factorypath 15 | .project 16 | .settings 17 | .springBeans 18 | 19 | ### IntelliJ IDEA ### 20 | .idea 21 | *.iws 22 | *.iml 23 | *.ipr 24 | 25 | ### NetBeans ### 26 | nbproject/private/ 27 | build/ 28 | nbbuild/ 29 | dist/ 30 | nbdist/ 31 | .nb-gradle/ 32 | -------------------------------------------------------------------------------- /Solving the N + 1 Query problem.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pellse/assembler/48dcb620ed52a0ffa59bb5fde0c37cbeda8ca59c/Solving the N + 1 Query problem.pdf -------------------------------------------------------------------------------- /assembler-cache-caffeine/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | nbproject/private/ 21 | build/ 22 | nbbuild/ 23 | dist/ 24 | nbdist/ 25 | .nb-gradle/ -------------------------------------------------------------------------------- /assembler-cache-caffeine/README.md: -------------------------------------------------------------------------------- 1 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.pellse/assembler-cache-caffeine.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.github.pellse%22%20AND%20a:%22assembler-cache-caffeine%22) [![Javadocs](http://javadoc.io/badge/io.github.pellse/assembler-cache-caffeine.svg)](http://javadoc.io/doc/io.github.pellse/assembler-cache-caffeine) 2 | -------------------------------------------------------------------------------- /assembler-cache-caffeine/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | 3 | implementation project(":assembler") 4 | implementation ("com.github.ben-manes.caffeine:caffeine:3.2.0") 5 | 6 | testImplementation project(":assembler").sourceSets.test.output 7 | } 8 | -------------------------------------------------------------------------------- /assembler-cache-caffeine/src/main/java/io/github/pellse/assembler/caching/caffeine/CaffeineCacheFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.caching.caffeine; 18 | 19 | import com.github.benmanes.caffeine.cache.AsyncCache; 20 | import com.github.benmanes.caffeine.cache.Caffeine; 21 | import io.github.pellse.assembler.caching.Cache; 22 | import io.github.pellse.assembler.caching.factory.CacheContext; 23 | import io.github.pellse.assembler.caching.factory.CacheFactory; 24 | import reactor.core.publisher.Mono; 25 | 26 | import java.time.Duration; 27 | import java.util.Map; 28 | 29 | import static com.github.benmanes.caffeine.cache.Caffeine.newBuilder; 30 | import static io.github.pellse.assembler.caching.factory.CacheFactory.toMono; 31 | import static io.github.pellse.util.ObjectUtils.also; 32 | import static java.util.Map.of; 33 | import static java.util.concurrent.CompletableFuture.completedFuture; 34 | import static reactor.core.publisher.Mono.fromFuture; 35 | 36 | public interface CaffeineCacheFactory { 37 | 38 | static > CacheFactory caffeineCache() { 39 | return caffeineCache(defaultBuilder()); 40 | } 41 | 42 | static > CacheFactory caffeineCache(long maxSize) { 43 | return caffeineCache(defaultBuilder() 44 | .maximumSize(maxSize)); 45 | } 46 | 47 | static > CacheFactory caffeineCache(Duration expireAfterAccessDuration) { 48 | return caffeineCache(defaultBuilder() 49 | .expireAfterAccess(expireAfterAccessDuration)); 50 | } 51 | 52 | static > CacheFactory caffeineCache(long maxSize, Duration expireAfterAccessDuration) { 53 | return caffeineCache(defaultBuilder() 54 | .maximumSize(maxSize) 55 | .expireAfterAccess(expireAfterAccessDuration)); 56 | } 57 | 58 | static > CacheFactory caffeineCache(Caffeine caffeine) { 59 | 60 | final AsyncCache delegateCache = caffeine.buildAsync(); 61 | 62 | return __ -> new Cache<>() { 63 | 64 | @Override 65 | public Mono> getAll(Iterable ids) { 66 | return fromFuture(delegateCache.getAll(ids, keys -> of())); 67 | } 68 | 69 | @Override 70 | public Mono> computeAll(Iterable ids, FetchFunction fetchFunction) { 71 | return fromFuture(delegateCache.getAll(ids, (keys, executor) -> fetchFunction.apply(keys).toFuture())); 72 | } 73 | 74 | @Override 75 | public Mono putAll(Map newMap) { 76 | return toMono((Map map) -> map.forEach((id, results) -> delegateCache.put(id, completedFuture(results)))).apply(newMap); 77 | } 78 | 79 | @Override 80 | public Mono removeAll(Map newMap) { 81 | return toMono((Map map) -> also(delegateCache.asMap(), cache -> map.keySet().forEach(cache::remove))).apply(newMap); 82 | } 83 | }; 84 | } 85 | 86 | private static Caffeine defaultBuilder() { 87 | return newBuilder(); 88 | } 89 | } -------------------------------------------------------------------------------- /assembler-kotlin-extension/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | nbproject/private/ 21 | build/ 22 | nbbuild/ 23 | dist/ 24 | nbdist/ 25 | .nb-gradle/ -------------------------------------------------------------------------------- /assembler-kotlin-extension/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "org.jetbrains.kotlin.jvm" version "1.9.23" 3 | } 4 | 5 | kotlin { 6 | jvmToolchain { 7 | languageVersion.set(JavaLanguageVersion.of("21")) 8 | } 9 | } 10 | 11 | dependencies { 12 | 13 | implementation project(":assembler") 14 | 15 | implementation platform("org.jetbrains.kotlin:kotlin-bom") 16 | 17 | testImplementation project(":assembler").sourceSets.test.output 18 | testImplementation project(":assembler-cache-caffeine") 19 | } 20 | -------------------------------------------------------------------------------- /assembler-kotlin-extension/src/main/kotlin/io/github/pellse/assembler/kotlin/AssemblerKotlinSupport.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.kotlin 18 | 19 | import io.github.pellse.assembler.AssemblerBuilder.WithCorrelationIdResolverBuilder 20 | import io.github.pellse.assembler.AssemblerBuilder.assemblerOf 21 | import io.github.pellse.assembler.RuleMapper 22 | import io.github.pellse.assembler.RuleMapper.oneToMany 23 | import io.github.pellse.assembler.RuleMapper.oneToOne 24 | import io.github.pellse.assembler.RuleMapperContext.OneToManyContext 25 | import io.github.pellse.assembler.RuleMapperContext.OneToOneContext 26 | import io.github.pellse.assembler.RuleMapperSource 27 | import org.reactivestreams.Publisher 28 | import reactor.core.publisher.Flux.fromIterable 29 | 30 | inline fun assembler(): WithCorrelationIdResolverBuilder = assemblerOf(T::class.java) 31 | 32 | fun , R> ((TC) -> Iterable).toPublisher(): (TC) -> Publisher = { ids -> fromIterable(this(ids)) } 33 | 34 | fun , ID, R> ((TC) -> Publisher).oneToOne(defaultResultProvider: (ID) -> R): RuleMapper = 35 | oneToOne(this, defaultResultProvider) 36 | 37 | fun , ID, R> RuleMapperSource>.oneToOne(defaultResultProvider: (ID) -> R): RuleMapper = 38 | oneToOne(this, defaultResultProvider) 39 | 40 | fun , ID, EID, R> ((TC) -> Publisher).oneToMany(idResolver: (R) -> EID): RuleMapper> = 41 | oneToMany(idResolver, this) 42 | 43 | fun , ID, EID, R> RuleMapperSource, OneToManyContext>>.oneToMany(idResolver: (R) -> EID): RuleMapper> = 44 | oneToMany(idResolver, this) 45 | 46 | fun , ID, EID, R, RC : Collection> ((TC) -> Publisher).oneToMany(idResolver: (R) -> EID, collectionFactory: () -> RC): RuleMapper = 47 | oneToMany(idResolver, this, collectionFactory) 48 | 49 | fun , ID, EID, R, RC : Collection> RuleMapperSource>.oneToMany(idResolver: (R) -> EID, collectionFactory: () -> RC): RuleMapper = 50 | oneToMany(idResolver, this, collectionFactory) 51 | -------------------------------------------------------------------------------- /assembler-kotlin-extension/src/main/kotlin/io/github/pellse/assembler/kotlin/CacheUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.kotlin 18 | 19 | import io.github.pellse.assembler.RuleMapperContext.OneToManyContext 20 | import io.github.pellse.assembler.RuleMapperContext.OneToOneContext 21 | import io.github.pellse.assembler.RuleMapperSource 22 | import io.github.pellse.assembler.caching.CacheContext.OneToManyCacheContext 23 | import io.github.pellse.assembler.caching.CacheContext.OneToOneCacheContext 24 | import io.github.pellse.assembler.caching.CacheFactory 25 | import io.github.pellse.assembler.caching.CacheFactory.cached 26 | import io.github.pellse.assembler.caching.CacheFactory.cachedMany 27 | 28 | import org.reactivestreams.Publisher 29 | import java.util.function.Function 30 | 31 | fun , ID, R> ((TC) -> Publisher).cached( 32 | vararg delegateCacheFactories: Function>, CacheFactory>> 33 | ): RuleMapperSource> = cached(this, *delegateCacheFactories) 34 | 35 | fun , ID, R> ((TC) -> Publisher).cached( 36 | cache: CacheFactory>, 37 | vararg delegateCacheFactories: Function>, CacheFactory>> 38 | ): RuleMapperSource> = cached(this, cache, *delegateCacheFactories) 39 | 40 | fun , ID, EID, R, RC: Collection> ((TC) -> Publisher).cachedMany( 41 | vararg delegateCacheFactories: Function>, CacheFactory>> 42 | ): RuleMapperSource> = cachedMany(this, *delegateCacheFactories) 43 | 44 | fun , ID, EID, R, RC: Collection> ((TC) -> Publisher).cachedMany( 45 | cache: CacheFactory>, 46 | vararg delegateCacheFactories: Function>, CacheFactory>> 47 | ): RuleMapperSource> = cachedMany(this, cache, *delegateCacheFactories) 48 | -------------------------------------------------------------------------------- /assembler-spring-cache/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | nbproject/private/ 21 | build/ 22 | nbbuild/ 23 | dist/ 24 | nbdist/ 25 | .nb-gradle/ -------------------------------------------------------------------------------- /assembler-spring-cache/README.md: -------------------------------------------------------------------------------- 1 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.pellse/assembler-spring-cache.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.github.pellse%22%20AND%20a:%22assembler-spring-cache%22) [![Javadocs](http://javadoc.io/badge/io.github.pellse/assembler-spring-cache.svg)](http://javadoc.io/doc/io.github.pellse/assembler-spring-cache) 2 | -------------------------------------------------------------------------------- /assembler-spring-cache/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '3.4.4' 3 | id 'io.spring.dependency-management' version '1.1.7' 4 | } 5 | 6 | ['bootJar','bootBuildImage'].each { taskName -> 7 | tasks.named(taskName) { 8 | enabled = false 9 | } 10 | } 11 | 12 | tasks.named('jar') { 13 | archiveClassifier = '' 14 | } 15 | 16 | dependencies { 17 | 18 | implementation project(":assembler") 19 | 20 | implementation("org.springframework.boot:spring-boot-starter-cache") 21 | 22 | testImplementation project(":assembler").sourceSets.test.output 23 | 24 | testImplementation ("com.github.ben-manes.caffeine:caffeine:3.2.0") 25 | testImplementation("org.springframework.boot:spring-boot-starter-test") 26 | } 27 | -------------------------------------------------------------------------------- /assembler-spring-cache/src/main/java/io/github/pellse/assembler/caching/spring/SpringCacheFactory.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.assembler.caching.spring; 2 | 3 | import io.github.pellse.assembler.caching.factory.CacheContext; 4 | import io.github.pellse.assembler.caching.factory.CacheFactory; 5 | import org.springframework.cache.Cache; 6 | import org.springframework.cache.Cache.ValueWrapper; 7 | import org.springframework.cache.CacheManager; 8 | import reactor.core.publisher.Mono; 9 | 10 | import java.util.Map; 11 | import java.util.Map.Entry; 12 | import java.util.function.BiFunction; 13 | 14 | import static io.github.pellse.assembler.caching.factory.AsyncCacheFactory.async; 15 | import static io.github.pellse.assembler.caching.spring.SpringCacheFactory.AsyncSupport.DEFAULT; 16 | import static io.github.pellse.util.ObjectUtils.also; 17 | import static io.github.pellse.util.collection.CollectionUtils.*; 18 | import static java.util.Map.entry; 19 | import static java.util.Objects.requireNonNull; 20 | import static reactor.core.publisher.Flux.fromIterable; 21 | import static reactor.core.publisher.Mono.just; 22 | 23 | public interface SpringCacheFactory { 24 | 25 | enum AsyncSupport { 26 | SYNC, 27 | ASYNC, 28 | DEFAULT // Let the framework detect and use async api if available by the underlying cache 29 | } 30 | 31 | static > CacheFactory springCache(CacheManager cacheManager, String cacheName) { 32 | return springCache(cacheManager, cacheName, DEFAULT); 33 | } 34 | 35 | static > CacheFactory springCache(CacheManager cacheManager, String cacheName, AsyncSupport asyncSupport) { 36 | return springCache(requireNonNull(cacheManager.getCache(cacheName)), asyncSupport); 37 | } 38 | 39 | static > CacheFactory springCache(Cache delegateCache) { 40 | return springCache(delegateCache, DEFAULT); 41 | } 42 | 43 | static > CacheFactory springCache(Cache delegateCache, AsyncSupport asyncSupport) { 44 | 45 | final BiFunction, ID, Mono> cacheGetter = switch (asyncSupport) { 46 | case SYNC -> SpringCacheFactory::get; 47 | case ASYNC -> SpringCacheFactory::retrieve; 48 | case DEFAULT -> cacheGetter(delegateCache); 49 | }; 50 | 51 | final CacheFactory springCacheFactory = cacheContext -> new io.github.pellse.assembler.caching.Cache<>() { 52 | 53 | @Override 54 | public Mono> getAll(Iterable ids) { 55 | return fromIterable(ids) 56 | .flatMap(this::buildMapEntry) 57 | .collectMap(Entry::getKey, Entry::getValue); 58 | } 59 | 60 | @Override 61 | public Mono> computeAll(Iterable ids, FetchFunction fetchFunction) { 62 | return getAll(ids) 63 | .flatMap(cachedData -> fetchFunction.apply(diff(ids, cachedData.keySet())) 64 | .doOnNext(this::addAll) 65 | .map(fetchedData -> fetchedData.isEmpty() ? cachedData : cacheContext.mapMerger().apply(cachedData, fetchedData))); 66 | } 67 | 68 | @Override 69 | public Mono putAll(Map map) { 70 | return just(also(map, this::addAll)); 71 | } 72 | 73 | @Override 74 | public Mono removeAll(Map map) { 75 | return just(also(map, m -> m.keySet().forEach(delegateCache::evict))); 76 | } 77 | 78 | private Mono> buildMapEntry(ID id) { 79 | return just(delegateCache) 80 | .transform(cacheMono -> cacheGetter.apply(cacheMono, id)) 81 | .map(value -> entry(id, value)); 82 | } 83 | 84 | private void addAll(Map map) { 85 | map.forEach(delegateCache::put); 86 | } 87 | }; 88 | 89 | return async(springCacheFactory); 90 | } 91 | 92 | private static BiFunction, ID, Mono> cacheGetter(Cache delegateCache) { 93 | try { 94 | delegateCache.retrieve(new Object()); 95 | return SpringCacheFactory::retrieve; 96 | } catch (Exception __) { 97 | return SpringCacheFactory::get; 98 | } 99 | } 100 | 101 | @SuppressWarnings("unchecked") 102 | private static Mono get(Mono delegateCacheMono, ID id) { 103 | return delegateCacheMono 104 | .mapNotNull(cache -> cache.get(id)) 105 | .mapNotNull(wrapper -> (RRC) wrapper.get()); 106 | } 107 | 108 | @SuppressWarnings("unchecked") 109 | private static Mono retrieve(Mono delegateCacheMono, ID id) { 110 | return delegateCacheMono 111 | .mapNotNull(cache -> cache.retrieve(id)) 112 | .flatMap(Mono::fromFuture) 113 | .mapNotNull(value -> (RRC) (value instanceof ValueWrapper wrapper ? wrapper.get() : value)); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /assembler/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | nbproject/private/ 21 | build/ 22 | nbbuild/ 23 | dist/ 24 | nbdist/ 25 | .nb-gradle/ -------------------------------------------------------------------------------- /assembler/README.md: -------------------------------------------------------------------------------- 1 | [![Maven Central](https://img.shields.io/maven-central/v/io.github.pellse/assembler.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.github.pellse%22%20AND%20a:%22assembler%22) [![Javadocs](http://javadoc.io/badge/io.github.pellse/assembler.svg)](http://javadoc.io/doc/io.github.pellse/assembler) 2 | -------------------------------------------------------------------------------- /assembler/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/Assembler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler; 18 | 19 | import org.reactivestreams.Publisher; 20 | import reactor.core.publisher.Flux; 21 | 22 | import java.util.List; 23 | import java.util.function.Function; 24 | import java.util.stream.Stream; 25 | 26 | import static java.util.function.Function.identity; 27 | import static reactor.core.publisher.Flux.fromIterable; 28 | import static reactor.core.publisher.Flux.fromStream; 29 | 30 | @FunctionalInterface 31 | public interface Assembler { 32 | 33 | Flux> assemblerWindow(Publisher topLevelEntities); 34 | 35 | default Flux assemble(Iterable topLevelEntities) { 36 | return assemble(fromIterable(topLevelEntities)); 37 | } 38 | 39 | default Flux assemble(Stream topLevelEntities) { 40 | return assemble(fromStream(topLevelEntities)); 41 | } 42 | 43 | default Flux assemble(Publisher topLevelEntities) { 44 | return assemblerWindow(topLevelEntities) 45 | .flatMapSequential(identity()); 46 | } 47 | 48 | default Assembler pipeWith(Assembler after) { 49 | return entities -> assemblerWindow(entities) 50 | .flatMapSequential(after::assemblerWindow); 51 | } 52 | 53 | static Function, Publisher> assemble(Function, Publisher> queryFunction, Assembler assembler) { 54 | return entities -> assembler.assemble(queryFunction.apply(entities)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/BatchRule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler; 18 | 19 | import reactor.core.publisher.Flux; 20 | import reactor.core.publisher.Mono; 21 | 22 | import java.util.Map; 23 | import java.util.Map.Entry; 24 | import java.util.function.Function; 25 | 26 | import static io.github.pellse.assembler.Rule.ruleBuilder; 27 | import static io.github.pellse.assembler.RuleContext.ruleContext; 28 | import static io.github.pellse.util.collection.CollectionUtils.size; 29 | import static io.github.pellse.util.collection.CollectionUtils.toStream; 30 | import static java.util.LinkedHashMap.newLinkedHashMap; 31 | import static java.util.function.Function.identity; 32 | import static java.util.stream.Collectors.toMap; 33 | import static reactor.core.publisher.Flux.fromIterable; 34 | 35 | @FunctionalInterface 36 | public interface BatchRule { 37 | 38 | Mono> toMono(Iterable entities); 39 | 40 | default Flux toFlux(Iterable entities) { 41 | return toMono(entities) 42 | .flatMapMany(resultMap -> fromIterable(resultMap.values())); 43 | } 44 | 45 | interface BatchRuleBuilder { 46 | 47 | BatchRule createRule( 48 | Function correlationIdResolver, 49 | RuleMapper mapper); 50 | 51 | BatchRule createRule( 52 | Function correlationIdResolver, 53 | MapFactory mapFactory, 54 | RuleMapper mapper); 55 | 56 | BatchRule createRule( 57 | Function innerIdResolver, 58 | Function outerIdResolver, 59 | RuleMapper mapper); 60 | 61 | BatchRule createRule( 62 | Function innerIdResolver, 63 | Function outerIdResolver, 64 | MapFactory mapFactory, 65 | RuleMapper mapper); 66 | } 67 | 68 | static BatchRuleBuilder withIdResolver(Function idResolver) { 69 | 70 | return new BatchRuleBuilder<>() { 71 | 72 | @Override 73 | public BatchRule createRule( 74 | Function correlationIdResolver, 75 | RuleMapper mapper) { 76 | 77 | return createBatchRule(ruleContext(correlationIdResolver), mapper); 78 | } 79 | 80 | @Override 81 | public BatchRule createRule( 82 | Function correlationIdResolver, 83 | MapFactory mapFactory, 84 | RuleMapper mapper) { 85 | 86 | return createBatchRule(ruleContext(correlationIdResolver, mapFactory), mapper); 87 | } 88 | 89 | @Override 90 | public BatchRule createRule( 91 | Function innerIdResolver, 92 | Function outerIdResolver, 93 | RuleMapper mapper) { 94 | 95 | return createBatchRule(ruleContext(innerIdResolver, outerIdResolver), mapper); 96 | } 97 | 98 | @Override 99 | public BatchRule createRule( 100 | Function innerIdResolver, 101 | Function outerIdResolver, 102 | MapFactory mapFactory, 103 | RuleMapper mapper) { 104 | 105 | return createBatchRule(ruleContext(innerIdResolver, outerIdResolver, mapFactory), mapper); 106 | } 107 | 108 | private BatchRule createBatchRule( 109 | Function, RuleContext> ruleContextBuilder, 110 | RuleMapper mapper) { 111 | 112 | return wrap(idResolver, ruleBuilder(ruleContextBuilder, mapper)); 113 | } 114 | }; 115 | } 116 | 117 | private static BatchRule wrap(Function idResolver, Rule rule) { 118 | 119 | final var queryFunction = rule.apply(idResolver); 120 | 121 | return entities -> { 122 | final var size = size(entities); 123 | 124 | final Map entityMap = toStream(entities) 125 | .collect(toMap(idResolver, identity(), (o, o2) -> o2, () -> newLinkedHashMap(size))); 126 | 127 | return queryFunction.apply(entities) 128 | .map(resultMap -> resultMap.entrySet() 129 | .stream() 130 | .collect(toMap(m -> entityMap.get(m.getKey()), Entry::getValue, (o, o2) -> o2, () -> newLinkedHashMap(size)))); 131 | }; 132 | } 133 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/ErrorHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler; 18 | 19 | import reactor.core.publisher.Flux; 20 | import reactor.core.publisher.Mono; 21 | 22 | import java.util.function.BiConsumer; 23 | import java.util.function.Consumer; 24 | import java.util.function.Function; 25 | import java.util.function.Predicate; 26 | 27 | import static io.github.pellse.util.ObjectUtils.doNothing; 28 | 29 | public sealed interface ErrorHandler { 30 | Function, Flux> toFluxErrorHandler(); 31 | 32 | record OnErrorContinue( 33 | Predicate errorPredicate, 34 | BiConsumer errorConsumer) implements ErrorHandler { 35 | 36 | public static OnErrorContinue onErrorContinue() { 37 | return onErrorContinue(doNothing()); 38 | } 39 | 40 | public static OnErrorContinue onErrorContinue(Consumer errorConsumer) { 41 | return onErrorContinue((t, o) -> errorConsumer.accept(t)); 42 | } 43 | 44 | public static OnErrorContinue onErrorContinue(BiConsumer errorConsumer) { 45 | return onErrorContinue(e -> true, errorConsumer); 46 | } 47 | 48 | public static OnErrorContinue onErrorContinue(Predicate errorPredicate, BiConsumer errorConsumer) { 49 | return new OnErrorContinue<>(errorPredicate, errorConsumer); 50 | } 51 | 52 | @Override 53 | public Function, Flux> toFluxErrorHandler() { 54 | return flux -> flux.onErrorContinue(errorPredicate(), errorConsumer()); 55 | } 56 | } 57 | 58 | record OnErrorResume( 59 | Predicate errorPredicate, 60 | Consumer errorConsumer) implements ErrorHandler { 61 | 62 | public static OnErrorResume onErrorResume() { 63 | return onErrorResume(doNothing()); 64 | } 65 | 66 | public static OnErrorResume onErrorResume(Consumer errorConsumer) { 67 | return onErrorResume(__ -> true, errorConsumer); 68 | } 69 | 70 | public static OnErrorResume onErrorResume(Predicate errorPredicate, Consumer errorConsumer) { 71 | return new OnErrorResume(errorPredicate, errorConsumer); 72 | } 73 | 74 | @Override 75 | public Function, Flux> toFluxErrorHandler() { 76 | return flux -> flux 77 | .doOnError(errorPredicate(), errorConsumer()) 78 | .onErrorResume(errorPredicate(), __ -> Mono.empty()); 79 | } 80 | } 81 | 82 | record OnErrorMap(Function mapper) implements ErrorHandler { 83 | 84 | public static OnErrorMap onErrorMap(Function mapper) { 85 | return new OnErrorMap(mapper); 86 | } 87 | 88 | @Override 89 | public Function, Flux> toFluxErrorHandler() { 90 | return flux -> flux.onErrorMap(mapper()); 91 | } 92 | } 93 | 94 | record OnErrorStop() implements ErrorHandler { 95 | 96 | public static OnErrorStop onErrorStop() { 97 | return new OnErrorStop(); 98 | } 99 | 100 | @Override 101 | public Function, Flux> toFluxErrorHandler() { 102 | return Flux::onErrorStop; 103 | } 104 | } 105 | } 106 | 107 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/LifeCycleEventBroadcaster.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler; 18 | 19 | import java.util.ArrayList; 20 | 21 | public interface LifeCycleEventBroadcaster extends LifeCycleEventSource, LifeCycleEventListener { 22 | 23 | static LifeCycleEventBroadcaster lifeCycleEventBroadcaster() { 24 | 25 | final var listeners = new ArrayList(); 26 | 27 | return new LifeCycleEventBroadcaster() { 28 | 29 | @Override 30 | public void start() { 31 | listeners.forEach(LifeCycleEventListener::start); 32 | } 33 | 34 | @Override 35 | public void stop() { 36 | listeners.forEach(LifeCycleEventListener::stop); 37 | } 38 | 39 | @Override 40 | public void addLifeCycleEventListener(LifeCycleEventListener listener) { 41 | listeners.add(listener); 42 | } 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/LifeCycleEventListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler; 18 | 19 | public interface LifeCycleEventListener { 20 | 21 | void start(); 22 | 23 | void stop(); 24 | } 25 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/LifeCycleEventSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler; 18 | 19 | import java.util.concurrent.atomic.AtomicLong; 20 | import java.util.concurrent.atomic.AtomicReference; 21 | import java.util.function.Consumer; 22 | import java.util.function.Function; 23 | 24 | @FunctionalInterface 25 | public interface LifeCycleEventSource { 26 | 27 | void addLifeCycleEventListener(LifeCycleEventListener listener); 28 | 29 | static LifeCycleEventListener concurrentLifeCycleEventListener(LifeCycleEventListener listener) { 30 | 31 | final var refCount = new AtomicLong(); 32 | 33 | return new LifeCycleEventListener() { 34 | 35 | @Override 36 | public void start() { 37 | if (refCount.getAndIncrement() == 0) { 38 | listener.start(); 39 | } 40 | } 41 | 42 | @Override 43 | public void stop() { 44 | if (refCount.decrementAndGet() == 0) { 45 | listener.stop(); 46 | } 47 | } 48 | }; 49 | } 50 | 51 | static LifeCycleEventListener lifeCycleEventAdapter(T eventSource, Function start, Consumer stop) { 52 | 53 | final var stopObj = new AtomicReference(); 54 | 55 | return new LifeCycleEventListener() { 56 | 57 | @Override 58 | public void start() { 59 | stopObj.setPlain(start.apply(eventSource)); 60 | } 61 | 62 | @Override 63 | public void stop() { 64 | stop.accept(stopObj.getPlain()); 65 | } 66 | }; 67 | } 68 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/MapFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler; 18 | 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.function.Function; 22 | 23 | public interface MapFactory extends Function> { 24 | 25 | static MapFactory defaultMapFactory() { 26 | return HashMap::newHashMap; 27 | } 28 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/QueryUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler; 18 | 19 | import io.github.pellse.util.collection.CollectionUtils; 20 | import org.reactivestreams.Publisher; 21 | import reactor.core.publisher.Flux; 22 | import reactor.core.publisher.Mono; 23 | import reactor.core.scheduler.Scheduler; 24 | 25 | import java.util.*; 26 | import java.util.function.Function; 27 | import java.util.function.Supplier; 28 | 29 | import static io.github.pellse.assembler.RuleMapperSource.nullToEmptySource; 30 | import static io.github.pellse.util.ObjectUtils.isSafeEqual; 31 | import static io.github.pellse.util.collection.CollectionUtils.*; 32 | import static io.github.pellse.util.reactive.ReactiveUtils.subscribeFluxOn; 33 | import static java.util.Objects.*; 34 | import static java.util.function.Predicate.not; 35 | import static reactor.core.publisher.Flux.fromIterable; 36 | 37 | public interface QueryUtils { 38 | 39 | static > Function, Mono>> buildQueryFunction( 40 | RuleMapperSource ruleMapperSource, 41 | CTX ctx) { 42 | 43 | return buildQueryFunction(nullToEmptySource(ruleMapperSource).apply(ctx), null, ctx); 44 | } 45 | 46 | static > Function, Mono>> buildQueryFunction( 47 | Function, Publisher> queryFunction, 48 | CTX ctx) { 49 | 50 | return buildQueryFunction(queryFunction, null, ctx); 51 | } 52 | 53 | static > Function, Mono>> buildQueryFunction( 54 | Function, Publisher> queryFunction, 55 | Scheduler scheduler, 56 | CTX ctx) { 57 | 58 | return entityList -> { 59 | var entities = asList(entityList); 60 | 61 | return safeApply(entities, queryFunction) 62 | .transform(subscribeFluxOn(scheduler)) 63 | .collect(ctx.mapCollector().apply(entities.size())) 64 | .map(map -> toResultMap(entities, map, ctx.outerIdResolver(), ctx.defaultResultProvider())); 65 | }; 66 | } 67 | 68 | static Function, Publisher> toPublisher(Function, Iterable> queryFunction) { 69 | return entities -> fromIterable(queryFunction.apply(entities)); 70 | } 71 | 72 | static > 73 | Flux safeApply(C coll, Function> queryFunction) { 74 | 75 | requireNonNull(queryFunction, "queryFunction cannot be null"); 76 | 77 | return Mono.just(coll) 78 | .filter(CollectionUtils::isNotEmpty) 79 | .flatMapMany(queryFunction); 80 | } 81 | 82 | static Map toResultMap( 83 | Collection entities, 84 | Map map, 85 | Function topLevelIdResolver, 86 | Function defaultResultProvider) { 87 | 88 | return isSafeEqual(map, Map::size, entities, Collection::size) 89 | ? map 90 | : initializeResultMap(transform(entities, topLevelIdResolver), map, defaultResultProvider); 91 | } 92 | 93 | static Map initializeResultMap(Collection ids, Map resultMap, Function defaultResultProvider) { 94 | final Function resultProvider = requireNonNullElse(defaultResultProvider, id -> null); 95 | 96 | final Map resultLinkedHashMap = toLinkedHashMap(resultMap); 97 | final Set idsFromQueryResult = resultLinkedHashMap.keySet(); 98 | final Map resultMapCopy = new LinkedHashMap<>(resultLinkedHashMap); 99 | 100 | // defaultResultProvider can provide a null value, so we cannot use a Collector here 101 | // as it would throw a NullPointerException 102 | ids.stream() 103 | .filter(not(idsFromQueryResult::contains)) 104 | .forEach(id -> resultMapCopy.put(id, resultProvider.apply(id))); 105 | 106 | return resultMapCopy; 107 | } 108 | 109 | static Supplier> toMapSupplier(int initialCapacity, MapFactory mapFactory) { 110 | 111 | final MapFactory actualMapFactory = requireNonNullElseGet(mapFactory, MapFactory::defaultMapFactory); 112 | return () -> actualMapFactory.apply(initialCapacity); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/Rule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler; 18 | 19 | import reactor.core.publisher.Mono; 20 | import reactor.core.scheduler.Scheduler; 21 | 22 | import java.util.Map; 23 | import java.util.function.Function; 24 | import java.util.function.UnaryOperator; 25 | 26 | import static io.github.pellse.assembler.RuleContext.ruleContext; 27 | import static io.github.pellse.util.reactive.ReactiveUtils.subscribeMonoOn; 28 | 29 | @FunctionalInterface 30 | public interface Rule extends Function, Function, Mono>>> { 31 | 32 | default Function, Mono>> apply(Function keyMapper, Scheduler scheduler) { 33 | return apply(keyMapper, subscribeMonoOn(scheduler)); 34 | } 35 | 36 | default Function, Mono>> apply(Function keyMapper, UnaryOperator>> transformer) { 37 | var queryFunction = apply(keyMapper); 38 | return entities -> queryFunction.apply(entities).transform(transformer); 39 | } 40 | 41 | static Rule rule( 42 | Function correlationIdResolver, 43 | RuleMapper mapper) { 44 | 45 | return ruleBuilder(ruleContext(correlationIdResolver), mapper); 46 | } 47 | 48 | static Rule rule( 49 | Function correlationIdResolver, 50 | MapFactory mapFactory, 51 | RuleMapper mapper) { 52 | 53 | return ruleBuilder(ruleContext(correlationIdResolver, mapFactory), mapper); 54 | } 55 | 56 | static Rule rule( 57 | Function innerIdResolver, 58 | Function outerIdResolver, 59 | RuleMapper mapper) { 60 | 61 | return ruleBuilder(ruleContext(innerIdResolver, outerIdResolver), mapper); 62 | } 63 | 64 | static Rule rule( 65 | Function innerIdResolver, 66 | Function outerIdResolver, 67 | MapFactory mapFactory, 68 | RuleMapper mapper) { 69 | 70 | return ruleBuilder(ruleContext(innerIdResolver, outerIdResolver, mapFactory), mapper); 71 | } 72 | 73 | static Rule ruleBuilder( 74 | Function, RuleContext> ruleContextBuilder, 75 | RuleMapper mapper) { 76 | 77 | return topLevelIdResolver -> mapper.apply(ruleContextBuilder.apply(topLevelIdResolver)); 78 | } 79 | 80 | @SuppressWarnings("unchecked") 81 | static Rule resolve(Rule rule, @SuppressWarnings("unused") Class entityClass) { 82 | return (Rule) rule; 83 | } 84 | 85 | interface RuleResolver { 86 | Rule resolve(Rule rule); 87 | 88 | static RuleResolver withType(@SuppressWarnings("unused") Class entityClass) { 89 | return new RuleResolver<>() { 90 | 91 | @Override 92 | public Rule resolve(Rule rule) { 93 | return Rule.resolve(rule, entityClass); 94 | } 95 | }; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/RuleContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler; 18 | 19 | import java.util.function.Function; 20 | 21 | import static io.github.pellse.assembler.MapFactory.defaultMapFactory; 22 | 23 | public interface RuleContext { 24 | 25 | Function topLevelIdResolver(); 26 | 27 | Function innerIdResolver(); 28 | 29 | Function outerIdResolver(); 30 | 31 | MapFactory mapFactory(); 32 | 33 | record DefaultRuleContext( 34 | Function topLevelIdResolver, 35 | Function innerIdResolver, 36 | Function outerIdResolver, 37 | MapFactory mapFactory) implements RuleContext { 38 | } 39 | 40 | static Function, RuleContext> ruleContext( 41 | Function innerIdResolver) { 42 | 43 | return ruleContext(innerIdResolver, defaultMapFactory()); 44 | } 45 | 46 | static Function, RuleContext> ruleContext( 47 | Function innerIdResolver, 48 | MapFactory mapFactory) { 49 | 50 | return topLevelIdResolver -> new DefaultRuleContext<>(topLevelIdResolver, innerIdResolver, topLevelIdResolver, mapFactory); 51 | } 52 | 53 | static Function, RuleContext> ruleContext( 54 | Function innerIdResolver, 55 | Function outerIdResolver) { 56 | 57 | return ruleContext(innerIdResolver, outerIdResolver, defaultMapFactory()); 58 | } 59 | 60 | static Function, RuleContext> ruleContext( 61 | Function innerIdResolver, 62 | Function outerIdResolver, 63 | MapFactory mapFactory) { 64 | 65 | return topLevelIdResolver -> new DefaultRuleContext<>(topLevelIdResolver, innerIdResolver, outerIdResolver, mapFactory); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/RuleMapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler; 18 | 19 | import io.github.pellse.assembler.RuleMapperContext.OneToManyContext; 20 | import io.github.pellse.assembler.RuleMapperContext.OneToOneContext; 21 | import org.reactivestreams.Publisher; 22 | import reactor.core.publisher.Mono; 23 | 24 | import java.util.*; 25 | import java.util.function.Function; 26 | 27 | import static io.github.pellse.assembler.QueryUtils.*; 28 | import static io.github.pellse.assembler.RuleMapperContext.OneToManyContext.oneToManyContext; 29 | import static io.github.pellse.assembler.RuleMapperSource.*; 30 | import static io.github.pellse.util.ObjectUtils.then; 31 | import static io.github.pellse.util.collection.CollectionUtils.*; 32 | import static io.github.pellse.util.lookup.LookupTable.lookupTableFrom; 33 | import static java.util.Comparator.comparing; 34 | 35 | @FunctionalInterface 36 | public interface RuleMapper 37 | extends Function, Function, Mono>>> { 38 | 39 | static RuleMapper oneToOne() { 40 | return oneToOne(emptySource(), id -> null); 41 | } 42 | 43 | static RuleMapper oneToOne(Function, Publisher> queryFunction) { 44 | return oneToOne(from(queryFunction), id -> null); 45 | } 46 | 47 | static RuleMapper oneToOne(RuleMapperSource> ruleMapperSource) { 48 | return oneToOne(ruleMapperSource, id -> null); 49 | } 50 | 51 | static RuleMapper oneToOne( 52 | Function, Publisher> queryFunction, 53 | Function defaultResultProvider) { 54 | 55 | return oneToOne(from(queryFunction), defaultResultProvider); 56 | } 57 | 58 | static RuleMapper oneToOne( 59 | RuleMapperSource> ruleMapperSource, 60 | Function defaultResultProvider) { 61 | 62 | return createRuleMapper( 63 | ruleMapperSource, 64 | ctx -> new OneToOneContext<>(ctx, defaultResultProvider)); 65 | } 66 | 67 | static , R> RuleMapper> oneToMany(Function idResolver) { 68 | return oneToMany(idResolver, emptySource()); 69 | } 70 | 71 | static , R> RuleMapper> oneToMany( 72 | Function idResolver, 73 | Function, Publisher> queryFunction) { 74 | 75 | return oneToMany(idResolver, from(queryFunction)); 76 | } 77 | 78 | static , R> RuleMapper> oneToMany( 79 | Function idResolver, 80 | RuleMapperSource, OneToManyContext> ruleMapperSource) { 81 | 82 | return createRuleMapper( 83 | ruleMapperSource, 84 | ctx -> oneToManyContext(ctx, idResolver, comparing(idResolver))); 85 | } 86 | 87 | private static > RuleMapper createRuleMapper( 88 | RuleMapperSource ruleMapperSource, 89 | Function, CTX> ruleMapperContextProvider) { 90 | 91 | return ctx -> { 92 | final var queryFunction = buildQueryFunction(ruleMapperSource, ruleMapperContextProvider.apply(ctx)); 93 | return entities -> runQueryFunction(queryFunction, entities, ctx); 94 | }; 95 | } 96 | 97 | private static Mono> runQueryFunction(Function, Mono>> queryFunction, Iterable entities, RuleContext ctx) { 98 | 99 | @SuppressWarnings("unchecked") 100 | final Function, Map> mappingFunction = ctx.topLevelIdResolver() == ctx.outerIdResolver() 101 | ? map -> (Map) map 102 | : then(lookupTableFrom(entities, ctx.outerIdResolver(), ctx.topLevelIdResolver()), lookupTable -> map -> newMap(m -> map.forEach((id, v) -> lookupTable.get(id).forEach(mappedId -> m.put(mappedId, v))))); 103 | 104 | return queryFunction.apply(entities) 105 | .map(mappingFunction); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/RuleMapperContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler; 18 | 19 | import java.util.*; 20 | import java.util.function.Function; 21 | import java.util.function.IntFunction; 22 | import java.util.stream.Collector; 23 | import java.util.stream.Stream; 24 | 25 | import static io.github.pellse.assembler.QueryUtils.toMapSupplier; 26 | import static java.util.function.Function.identity; 27 | import static java.util.stream.Collectors.*; 28 | 29 | public sealed interface RuleMapperContext extends RuleContext { 30 | 31 | Function idResolver(); 32 | 33 | Function defaultResultProvider(); 34 | 35 | IntFunction>> mapCollector(); 36 | 37 | Function, Stream> streamFlattener(); 38 | 39 | record OneToOneContext( 40 | Function topLevelIdResolver, 41 | Function innerIdResolver, 42 | Function outerIdResolver, 43 | MapFactory mapFactory, 44 | Function defaultResultProvider) implements RuleMapperContext { 45 | 46 | public OneToOneContext( 47 | RuleContext ruleContext, 48 | Function defaultResultProvider) { 49 | 50 | this(ruleContext.topLevelIdResolver(), 51 | ruleContext.innerIdResolver(), 52 | ruleContext.outerIdResolver(), 53 | ruleContext.mapFactory(), 54 | defaultResultProvider); 55 | } 56 | 57 | @Override 58 | public Function idResolver() { 59 | return innerIdResolver(); 60 | } 61 | 62 | @Override 63 | public IntFunction>> mapCollector() { 64 | return initialMapCapacity -> toMap( 65 | innerIdResolver(), 66 | identity(), 67 | (u1, u2) -> u2, 68 | toMapSupplier(validate(initialMapCapacity), mapFactory())); 69 | } 70 | 71 | @Override 72 | public Function, Stream> streamFlattener() { 73 | return identity(); 74 | } 75 | } 76 | 77 | record OneToManyContext( 78 | Function topLevelIdResolver, 79 | Function innerIdResolver, 80 | Function outerIdResolver, 81 | MapFactory> mapFactory, 82 | Function idResolver, 83 | Comparator idComparator) implements RuleMapperContext> { 84 | 85 | public static OneToManyContext oneToManyContext( 86 | RuleContext> ruleContext, 87 | Function idResolver, 88 | Comparator idComparator) { 89 | 90 | return new OneToManyContext<>(ruleContext.topLevelIdResolver(), 91 | ruleContext.innerIdResolver(), 92 | ruleContext.outerIdResolver(), 93 | ruleContext.mapFactory(), 94 | idResolver, 95 | idComparator); 96 | } 97 | 98 | @Override 99 | public Function> defaultResultProvider() { 100 | return id -> new ArrayList<>(); 101 | } 102 | 103 | @Override 104 | public IntFunction>>> mapCollector() { 105 | return initialMapCapacity -> groupingBy( 106 | innerIdResolver(), 107 | toMapSupplier(validate(initialMapCapacity), mapFactory()), 108 | toList()); 109 | } 110 | 111 | @Override 112 | public Function>, Stream> streamFlattener() { 113 | return stream -> stream.flatMap(Collection::stream); 114 | } 115 | } 116 | 117 | private static int validate(int initialCapacity) { 118 | return Math.max(initialCapacity, 0); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/RuleMapperSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler; 18 | 19 | import org.reactivestreams.Publisher; 20 | import reactor.core.publisher.Mono; 21 | 22 | import java.util.List; 23 | import java.util.function.Function; 24 | 25 | import static io.github.pellse.util.collection.CollectionUtils.toStream; 26 | import static java.util.Arrays.stream; 27 | import static java.util.Objects.requireNonNullElse; 28 | 29 | @FunctionalInterface 30 | public interface RuleMapperSource> 31 | extends Function, Publisher>> { 32 | 33 | RuleMapperSource> EMPTY_SOURCE = ruleContext -> ids -> Mono.empty(); 34 | 35 | @SuppressWarnings("unchecked") 36 | static > RuleMapperSource resolve( 37 | RuleMapperSource ruleMapperSource, 38 | @SuppressWarnings("unused") Class keyType) { 39 | 40 | return (RuleMapperSource) ruleMapperSource; 41 | } 42 | 43 | static > RuleMapperSource from(Function, Publisher> queryFunction) { 44 | return __ -> queryFunction; 45 | } 46 | 47 | static > RuleMapperSource call(Function, Publisher> queryFunction) { 48 | return ruleContext -> RuleMapperSource.call(ruleContext.outerIdResolver(), queryFunction).apply(ruleContext); 49 | } 50 | 51 | static > RuleMapperSource call( 52 | Function idResolver, 53 | Function, Publisher> queryFunction) { 54 | 55 | return __ -> entities -> queryFunction.apply(toStream(entities).map(idResolver).toList()); 56 | } 57 | 58 | @SuppressWarnings("unchecked") 59 | static > RuleMapperSource emptySource() { 60 | return (RuleMapperSource) EMPTY_SOURCE; 61 | } 62 | 63 | static > boolean isEmptySource(RuleMapperSource ruleMapperSource) { 64 | return emptySource().equals(nullToEmptySource(ruleMapperSource)); 65 | } 66 | 67 | static > RuleMapperSource nullToEmptySource( 68 | RuleMapperSource ruleMapperSource) { 69 | 70 | return requireNonNullElse(ruleMapperSource, RuleMapperSource.emptySource()); 71 | } 72 | 73 | @SafeVarargs 74 | static > RuleMapperSource pipe( 75 | RuleMapperSource mapper, 76 | Function, ? extends RuleMapperSource>... mappingFunctions) { 77 | 78 | return stream(mappingFunctions) 79 | .reduce(mapper, 80 | (ruleMapperSource, mappingFunction) -> mappingFunction.apply(ruleMapperSource), 81 | (ruleMapperSource1, ruleMapperSource2) -> ruleMapperSource2); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/WindowingStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler; 18 | 19 | import reactor.core.publisher.Flux; 20 | 21 | import java.util.function.Function; 22 | 23 | @FunctionalInterface 24 | public interface WindowingStrategy extends Function, Flux>> { 25 | } 26 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/Cache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.caching; 18 | 19 | import io.github.pellse.util.function.Function3; 20 | import reactor.core.publisher.Mono; 21 | import reactor.core.scheduler.Scheduler; 22 | 23 | import java.util.Map; 24 | import java.util.function.Function; 25 | 26 | import static io.github.pellse.util.collection.CollectionUtils.mergeMaps; 27 | import static io.github.pellse.util.reactive.ReactiveUtils.subscribeMonoOn; 28 | 29 | public interface Cache { 30 | 31 | @FunctionalInterface 32 | interface FetchFunction extends Function, Mono>> { 33 | 34 | default Mono> apply(Iterable ids, Scheduler scheduler) { 35 | return apply(ids).transform(subscribeMonoOn(scheduler)); 36 | } 37 | } 38 | 39 | Mono> getAll(Iterable ids); 40 | 41 | Mono> computeAll(Iterable ids, FetchFunction fetchFunction); 42 | 43 | Mono putAll(Map map); 44 | 45 | Mono removeAll(Map map); 46 | 47 | default Mono updateAll(Map mapToAdd, Map mapToRemove) { 48 | return putAll(mapToAdd).then(removeAll(mapToRemove)); 49 | } 50 | 51 | default Mono putAllWith(Map map, Function3 mergeFunction) { 52 | return getAll(map.keySet()) 53 | .map(existingMap -> mergeMaps(existingMap, map, mergeFunction)) 54 | .flatMap(this::putAll); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/CacheEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.caching; 18 | 19 | import java.util.function.Function; 20 | import java.util.function.Predicate; 21 | 22 | import static io.github.pellse.util.ObjectUtils.then; 23 | 24 | public sealed interface CacheEvent { 25 | 26 | record Updated(R value) implements CacheEvent { 27 | } 28 | 29 | record Removed(R value) implements CacheEvent { 30 | } 31 | 32 | R value(); 33 | 34 | static Function> toCacheEvent(Predicate isAddOrUpdateEvent, Function cacheEventValueExtractor) { 35 | return event -> then(cacheEventValueExtractor.apply(event), eventValue -> isAddOrUpdateEvent.test(event) ? new Updated<>(eventValue) : new Removed<>(eventValue)); 36 | } 37 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/ConcurrentCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.caching; 18 | 19 | import io.github.pellse.concurrent.LockStrategy; 20 | import io.github.pellse.concurrent.ReactiveGuard; 21 | import io.github.pellse.concurrent.ReactiveGuard.ReactiveGuardBuilder; 22 | import io.github.pellse.util.function.Function3; 23 | import reactor.core.publisher.Mono; 24 | import reactor.core.scheduler.Scheduler; 25 | 26 | import java.util.Map; 27 | 28 | import static io.github.pellse.concurrent.ReactiveGuard.reactiveGuardBuilder; 29 | import static java.util.Objects.requireNonNullElseGet; 30 | 31 | public interface ConcurrentCache extends Cache { 32 | 33 | static ConcurrentCache concurrentCache(Cache delegateCache) { 34 | return concurrentCache(delegateCache, (ReactiveGuardBuilder) null); 35 | } 36 | 37 | static ConcurrentCache concurrentCache(Cache delegateCache, Scheduler fetchFunctionScheduler) { 38 | return concurrentCache(delegateCache, (ReactiveGuardBuilder) null, fetchFunctionScheduler); 39 | } 40 | 41 | static ConcurrentCache concurrentCache(Cache delegateCache, LockStrategy lockStrategy) { 42 | return concurrentCache(delegateCache, reactiveGuardBuilder().lockingStrategy(lockStrategy)); 43 | } 44 | 45 | static ConcurrentCache concurrentCache(Cache delegateCache, LockStrategy lockStrategy, Scheduler fetchFunctionScheduler) { 46 | return concurrentCache(delegateCache, reactiveGuardBuilder().lockingStrategy(lockStrategy), fetchFunctionScheduler); 47 | } 48 | 49 | static ConcurrentCache concurrentCache(Cache delegateCache, ReactiveGuardBuilder reactiveGuardBuilder) { 50 | return concurrentCache(delegateCache, reactiveGuardBuilder, null); 51 | } 52 | 53 | static ConcurrentCache concurrentCache(Cache delegateCache, ReactiveGuardBuilder reactiveGuardBuilder, Scheduler fetchFunctionScheduler) { 54 | 55 | if (delegateCache instanceof ConcurrentCache concurrentCache) { 56 | return concurrentCache; 57 | } 58 | 59 | final var guard = requireNonNullElseGet(reactiveGuardBuilder, ReactiveGuard::reactiveGuardBuilder) 60 | .build(); 61 | 62 | return new ConcurrentCache<>() { 63 | 64 | @Override 65 | public Mono> getAll(Iterable ids) { 66 | return guard.withReadLock(delegateCache.getAll(ids), Map::of); 67 | } 68 | 69 | @Override 70 | public Mono> computeAll(Iterable ids, FetchFunction fetchFunction) { 71 | return guard.withReadLock(delegateCache.computeAll(ids, idsToFetch -> fetchFunction.apply(idsToFetch, fetchFunctionScheduler)), Map::of); 72 | } 73 | 74 | @Override 75 | public Mono putAll(Map map) { 76 | return guard.withLock(delegateCache.putAll(map)); 77 | } 78 | 79 | @Override 80 | public Mono putAllWith(Map map, Function3 mergeFunction) { 81 | return guard.withLock(delegateCache.putAllWith(map, mergeFunction)); 82 | } 83 | 84 | @Override 85 | public Mono removeAll(Map map) { 86 | return guard.withLock(delegateCache.removeAll(map)); 87 | } 88 | 89 | @Override 90 | public Mono updateAll(Map mapToAdd, Map mapToRemove) { 91 | return guard.withLock(delegateCache.updateAll(mapToAdd, mapToRemove)); 92 | } 93 | }; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/DefaultCache.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.caching; 18 | 19 | import io.github.pellse.assembler.caching.Cache.FetchFunction; 20 | import io.github.pellse.assembler.caching.factory.CacheContext; 21 | import io.github.pellse.assembler.caching.factory.CacheFactory; 22 | import reactor.core.publisher.Mono; 23 | import reactor.core.publisher.Sinks; 24 | 25 | import java.util.Map; 26 | import java.util.concurrent.ConcurrentHashMap; 27 | import java.util.function.BiFunction; 28 | import java.util.function.Function; 29 | 30 | import static io.github.pellse.assembler.caching.factory.CacheFactory.toMono; 31 | import static io.github.pellse.util.ObjectUtils.also; 32 | import static io.github.pellse.util.collection.CollectionUtils.*; 33 | import static io.github.pellse.util.reactive.ReactiveUtils.createSinkMap; 34 | import static io.github.pellse.util.reactive.ReactiveUtils.resolve; 35 | import static java.util.Optional.ofNullable; 36 | 37 | public interface DefaultCache { 38 | 39 | static > CacheFactory cache() { 40 | 41 | final var delegateMap = new ConcurrentHashMap>(); 42 | 43 | final Function, Mono>> getAll = ids -> resolve(readAll(ids, delegateMap, Sinks.One::asMono)); 44 | 45 | final BiFunction, FetchFunction, Mono>> computeAll = (ids, fetchFunction) -> { 46 | final var cachedEntitiesMap = readAll(ids, delegateMap, Sinks.One::asMono); 47 | final var missingIds = diff(ids, cachedEntitiesMap.keySet()); 48 | 49 | if (isEmpty(missingIds)) { 50 | return resolve(cachedEntitiesMap); 51 | } 52 | 53 | final var sinkMap = also(createSinkMap(missingIds), delegateMap::putAll); 54 | 55 | return fetchFunction.apply(missingIds) 56 | .doOnNext(resultMap -> sinkMap.forEach((id, sink) -> ofNullable(resultMap.get(id)) 57 | .ifPresentOrElse(sink::tryEmitValue, () -> { 58 | delegateMap.remove(id); 59 | sink.tryEmitEmpty(); 60 | }))) 61 | .doOnError(e -> sinkMap.forEach((id, sink) -> { 62 | delegateMap.remove(id); 63 | sink.tryEmitError(e); 64 | })) 65 | .flatMap(__ -> resolve(mergeMaps(cachedEntitiesMap, transformMapValues(sinkMap, Sinks.One::asMono)))); 66 | }; 67 | 68 | Function, Mono> putAll = toMono(map -> also(createSinkMap(map.keySet()), delegateMap::putAll) 69 | .forEach((id, sink) -> sink.tryEmitValue(map.get(id)))); 70 | 71 | Function, Mono> removeAll = toMono(map -> also(map.keySet(), ids -> delegateMap.keySet().removeAll(ids)) 72 | .forEach(id -> ofNullable(delegateMap.get(id)).ifPresent(Sinks.One::tryEmitEmpty))); 73 | 74 | return cache(getAll, computeAll, putAll, removeAll); 75 | } 76 | 77 | static > CacheFactory cache( 78 | Function, Mono>> getAll, 79 | BiFunction, FetchFunction, Mono>> computeAll, 80 | Function, Mono> putAll, 81 | Function, Mono> removeAll) { 82 | 83 | return __ -> new Cache<>() { 84 | 85 | @Override 86 | public Mono> getAll(Iterable ids) { 87 | return getAll.apply(ids); 88 | } 89 | 90 | @Override 91 | public Mono> computeAll(Iterable ids, FetchFunction fetchFunction) { 92 | return computeAll.apply(ids, fetchFunction); 93 | } 94 | 95 | @Override 96 | public Mono putAll(Map map) { 97 | return putAll.apply(map); 98 | } 99 | 100 | @Override 101 | public Mono removeAll(Map map) { 102 | return removeAll.apply(map); 103 | } 104 | }; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/OneToManyCache.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.assembler.caching; 2 | 3 | import io.github.pellse.assembler.caching.factory.CacheContext.OneToManyCacheContext; 4 | import io.github.pellse.util.function.Function3; 5 | import reactor.core.publisher.Mono; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import static io.github.pellse.assembler.caching.OptimizedCache.optimizedCache; 11 | import static io.github.pellse.util.collection.CollectionUtils.*; 12 | 13 | public interface OneToManyCache { 14 | 15 | static Cache> oneToManyCache( 16 | OneToManyCacheContext ctx, 17 | Cache> delegateCache) { 18 | 19 | final var optimizedCache = optimizedCache(delegateCache); 20 | 21 | return new Cache<>() { 22 | 23 | @Override 24 | public Mono>> getAll(Iterable ids) { 25 | return optimizedCache.getAll(ids); 26 | } 27 | 28 | @Override 29 | public Mono>> computeAll(Iterable ids, FetchFunction> fetchFunction) { 30 | return optimizedCache.computeAll(ids, fetchFunction); 31 | } 32 | 33 | @Override 34 | public Mono putAll(Map> map) { 35 | return putAllWith(map, (id, coll1, coll2) -> removeDuplicates(concat(coll1, coll2), ctx.idResolver())); 36 | } 37 | 38 | @Override 39 | public Mono putAllWith(Map map, Function3, UC, List> mergeFunction) { 40 | return optimizedCache.putAllWith(map, mergeFunction); 41 | } 42 | 43 | @Override 44 | public Mono removeAll(Map> map) { 45 | return optimizedCache.getAll(map.keySet()) 46 | .flatMap(existingCacheItems -> { 47 | final var updatedMap = subtractFromMap(map, existingCacheItems, ctx.idResolver()); 48 | return optimizedCache.updateAll(updatedMap, diff(existingCacheItems, updatedMap)); 49 | }); 50 | } 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/OneToOneCache.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.assembler.caching; 2 | 3 | import io.github.pellse.assembler.caching.factory.CacheContext.OneToOneCacheContext; 4 | import io.github.pellse.util.function.Function3; 5 | import reactor.core.publisher.Mono; 6 | 7 | import java.util.Map; 8 | 9 | import static io.github.pellse.assembler.caching.OptimizedCache.optimizedCache; 10 | 11 | public interface OneToOneCache { 12 | 13 | static Cache oneToOneCache(OneToOneCacheContext ctx, Cache delegateCache) { 14 | 15 | final var optimizedCache = optimizedCache(delegateCache); 16 | 17 | return new Cache<>() { 18 | 19 | @Override 20 | public Mono> getAll(Iterable ids) { 21 | return optimizedCache.getAll(ids); 22 | } 23 | 24 | @Override 25 | public Mono> computeAll(Iterable ids, FetchFunction fetchFunction) { 26 | return optimizedCache.computeAll(ids, fetchFunction); 27 | } 28 | 29 | @Override 30 | public Mono putAll(Map map) { 31 | return putAllWith(map, ctx.mergeFunction()); 32 | } 33 | 34 | @Override 35 | public Mono putAllWith(Map map, Function3 mergeFunction) { 36 | return optimizedCache.putAllWith(map, mergeFunction); 37 | } 38 | 39 | @Override 40 | public Mono removeAll(Map map) { 41 | return optimizedCache.removeAll(map); 42 | } 43 | }; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/OptimizedCache.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.assembler.caching; 2 | 3 | import io.github.pellse.util.function.Function3; 4 | import reactor.core.publisher.Mono; 5 | 6 | import java.util.Map; 7 | 8 | import static io.github.pellse.util.collection.CollectionUtils.isEmpty; 9 | import static io.github.pellse.util.collection.CollectionUtils.nullToEmptyMap; 10 | import static java.util.Map.of; 11 | import static reactor.core.publisher.Mono.just; 12 | 13 | public interface OptimizedCache { 14 | 15 | static Cache optimizedCache(Cache delegateCache) { 16 | 17 | return new Cache<>() { 18 | 19 | @Override 20 | public Mono> getAll(Iterable ids) { 21 | return isEmpty(ids) ? just(of()) : delegateCache.getAll(ids); 22 | } 23 | 24 | @Override 25 | public Mono> computeAll(Iterable ids, FetchFunction fetchFunction) { 26 | return isEmpty(ids) ? just(of()) : delegateCache.computeAll(ids, fetchFunction); 27 | } 28 | 29 | @Override 30 | public Mono putAll(Map map) { 31 | return isEmpty(map) ? just(of()) : delegateCache.putAll(map); 32 | } 33 | 34 | @Override 35 | public Mono putAllWith(Map map, Function3 mergeFunction) { 36 | return isEmpty(map) ? just(of()) : delegateCache.putAllWith(map, mergeFunction); 37 | } 38 | 39 | @Override 40 | public Mono removeAll(Map map) { 41 | return isEmpty(map) ? just(of()) : delegateCache.removeAll(map); 42 | } 43 | 44 | @Override 45 | public Mono updateAll(Map mapToAdd, Map mapToRemove) { 46 | return isEmpty(mapToAdd) && isEmpty(mapToRemove) ? just(of()) : delegateCache.updateAll(nullToEmptyMap(mapToAdd), nullToEmptyMap(mapToRemove)); 47 | } 48 | }; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/factory/AsyncCacheFactory.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.assembler.caching.factory; 2 | 3 | import io.github.pellse.assembler.caching.Cache; 4 | import io.github.pellse.util.function.Function3; 5 | import reactor.core.publisher.Mono; 6 | 7 | import java.util.Map; 8 | import java.util.Set; 9 | 10 | import static io.github.pellse.util.collection.CollectionUtils.diff; 11 | import static io.github.pellse.util.collection.CollectionUtils.isEmpty; 12 | import static java.util.concurrent.ConcurrentHashMap.newKeySet; 13 | 14 | public interface AsyncCacheFactory { 15 | 16 | static > CacheTransformer async() { 17 | return AsyncCacheFactory::async; 18 | } 19 | 20 | static > CacheFactory async(CacheFactory cacheFactory) { 21 | return context -> async(cacheFactory.create(context)); 22 | } 23 | 24 | static Cache async(Cache delegateCache) { 25 | 26 | final Set idSet = newKeySet(); 27 | 28 | return new Cache<>() { 29 | 30 | @Override 31 | public Mono> getAll(Iterable ids) { 32 | return delegateCache.getAll(ids); 33 | } 34 | 35 | @Override 36 | public Mono> computeAll(Iterable ids, FetchFunction fetchFunction) { 37 | 38 | final var missingIds = diff(ids, idSet); 39 | if (isEmpty(missingIds)) { 40 | return delegateCache.getAll(ids); 41 | } 42 | 43 | idSet.addAll(missingIds); 44 | 45 | return delegateCache.computeAll(ids, fetchFunction) 46 | .doOnNext(map -> idSet.removeAll(diff(idSet, map.keySet()))) 47 | .doOnError(error -> idSet.removeAll(missingIds)) 48 | .doOnCancel(() -> idSet.removeAll(missingIds)); 49 | } 50 | 51 | @Override 52 | public Mono putAll(Map map) { 53 | return delegateCache.putAll(map); 54 | } 55 | 56 | @Override 57 | public Mono putAllWith(Map map, Function3 mergeFunction) { 58 | return delegateCache.putAllWith(map, mergeFunction); 59 | } 60 | 61 | @Override 62 | public Mono removeAll(Map map) { 63 | return delegateCache.removeAll(map); 64 | } 65 | 66 | @Override 67 | public Mono updateAll(Map mapToAdd, Map mapToRemove) { 68 | return delegateCache.updateAll(mapToAdd, mapToRemove); 69 | } 70 | }; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/factory/CacheContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.caching.factory; 18 | 19 | import io.github.pellse.assembler.RuleMapperContext.OneToManyContext; 20 | import io.github.pellse.assembler.RuleMapperContext.OneToOneContext; 21 | import io.github.pellse.assembler.caching.merge.MergeFunction; 22 | import io.github.pellse.assembler.caching.merge.MergeFunctions; 23 | 24 | import java.util.*; 25 | import java.util.function.BiFunction; 26 | import java.util.function.Function; 27 | import java.util.function.IntFunction; 28 | import java.util.stream.Collector; 29 | 30 | import static io.github.pellse.assembler.caching.factory.ConcurrentCacheFactory.concurrent; 31 | import static io.github.pellse.util.collection.CollectionUtils.*; 32 | import static java.util.Objects.requireNonNullElse; 33 | 34 | public sealed interface CacheContext> { 35 | 36 | IntFunction>> mapCollector(); 37 | 38 | MergeFunction mergeFunction(); 39 | 40 | CacheTransformer cacheTransformer(); 41 | 42 | default BiFunction, Map, Map> mapMerger() { 43 | return (existingMap, newMap) -> mergeMaps(existingMap, newMap, mergeFunction()); 44 | } 45 | 46 | record OneToOneCacheContext( 47 | IntFunction>> mapCollector, 48 | MergeFunction mergeFunction, 49 | CacheTransformer> cacheTransformer) implements CacheContext> { 50 | 51 | public OneToOneCacheContext { 52 | mergeFunction = requireNonNullElse(mergeFunction, MergeFunctions::replace); 53 | } 54 | 55 | static OneToOneCacheContext oneToOneCacheContext(OneToOneContext ctx) { 56 | return oneToOneCacheContext(ctx, null); 57 | } 58 | 59 | static OneToOneCacheContext oneToOneCacheContext(OneToOneContext ctx, MergeFunction mergeFunction) { 60 | return new OneToOneCacheContext<>(ctx.mapCollector(), mergeFunction, concurrent()); 61 | } 62 | } 63 | 64 | record OneToManyCacheContext( 65 | Function idResolver, 66 | IntFunction>>> mapCollector, 67 | Comparator idComparator, 68 | MergeFunction> mergeFunction, 69 | CacheTransformer, OneToManyCacheContext> cacheTransformer) implements CacheContext, OneToManyCacheContext> { 70 | 71 | public OneToManyCacheContext { 72 | final var mf = requireNonNullElse(mergeFunction, (id, coll1, coll2) -> removeDuplicates(concat(coll1, coll2), idResolver())); 73 | mergeFunction = (id, coll1, coll2) -> isNotEmpty(coll1) || isNotEmpty(coll2) ? mf.apply(id, coll1, coll2) : List.of(); 74 | } 75 | 76 | static OneToManyCacheContext oneToManyCacheContext(OneToManyContext ctx) { 77 | return oneToManyCacheContext(ctx, null); 78 | } 79 | 80 | static OneToManyCacheContext oneToManyCacheContext( 81 | OneToManyContext ctx, 82 | MergeFunction> mergeFunction) { 83 | 84 | return new OneToManyCacheContext<>(ctx.idResolver(), 85 | ctx.mapCollector(), 86 | ctx.idComparator(), 87 | mergeFunction, 88 | concurrent()); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/factory/CacheTransformer.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.assembler.caching.factory; 2 | 3 | import io.github.pellse.assembler.caching.factory.CacheContext.OneToManyCacheContext; 4 | import io.github.pellse.assembler.caching.factory.CacheContext.OneToOneCacheContext; 5 | 6 | import java.util.List; 7 | import java.util.function.Function; 8 | 9 | @FunctionalInterface 10 | public interface CacheTransformer> extends Function, CacheFactory> { 11 | 12 | static > CacheTransformer defaultCacheTransformer() { 13 | return cf -> cf; 14 | } 15 | 16 | static CacheTransformer> oneToOneCacheTransformer(CacheTransformer> cacheTransformer) { 17 | return cacheTransformer; 18 | } 19 | 20 | static CacheTransformer, OneToManyCacheContext> oneToManyCacheTransformer(CacheTransformer, OneToManyCacheContext> cacheTransformer) { 21 | return cacheTransformer; 22 | } 23 | 24 | static CacheTransformer> resolve( 25 | CacheTransformer> cacheTransformer, 26 | @SuppressWarnings("unused") Class idClass) { 27 | 28 | return cacheTransformer; 29 | } 30 | 31 | static CacheTransformer, OneToManyCacheContext> resolve( 32 | CacheTransformer, OneToManyCacheContext> cacheTransformer, 33 | @SuppressWarnings("unused") Class idClass, 34 | @SuppressWarnings("unused") Class elementIdClass) { 35 | 36 | return cacheTransformer; 37 | } 38 | 39 | static ElementIDResolver withIDType(@SuppressWarnings("unused") Class idType) { 40 | return new ElementIDResolver<>() { 41 | 42 | @Override 43 | public CacheTransformer> resolve(CacheTransformer> cacheTransformer) { 44 | return CacheTransformer.resolve(cacheTransformer, idType); 45 | } 46 | 47 | @Override 48 | public OneToManyCacheTransformerResolver andElementIDType(Class elementIdType) { 49 | return new OneToManyCacheTransformerResolver<>() { 50 | 51 | @Override 52 | public CacheTransformer, OneToManyCacheContext> resolve(CacheTransformer, OneToManyCacheContext> cacheTransformer) { 53 | return CacheTransformer.resolve(cacheTransformer, idType, elementIdType); 54 | } 55 | }; 56 | } 57 | }; 58 | } 59 | 60 | interface ElementIDResolver extends OneToOneCacheTransformerResolver { 61 | OneToManyCacheTransformerResolver andElementIDType(@SuppressWarnings("unused") Class elementIdType); 62 | } 63 | 64 | @FunctionalInterface 65 | interface OneToOneCacheTransformerResolver { 66 | CacheTransformer> resolve(CacheTransformer> cacheTransformer); 67 | } 68 | 69 | @FunctionalInterface 70 | interface OneToManyCacheTransformerResolver { 71 | CacheTransformer, OneToManyCacheContext> resolve(CacheTransformer, OneToManyCacheContext> cacheTransformer); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/factory/ConcurrentCacheFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.caching.factory; 18 | 19 | import io.github.pellse.concurrent.Lock; 20 | import io.github.pellse.concurrent.LockStrategy; 21 | import io.github.pellse.concurrent.ReactiveGuard.ReactiveGuardBuilder; 22 | import io.github.pellse.concurrent.ReactiveGuardEvent; 23 | import reactor.core.scheduler.Scheduler; 24 | 25 | import java.util.Optional; 26 | import java.util.function.BiConsumer; 27 | import java.util.function.Consumer; 28 | 29 | import static io.github.pellse.assembler.caching.ConcurrentCache.concurrentCache; 30 | import static io.github.pellse.concurrent.ReactiveGuard.reactiveGuardBuilder; 31 | import static io.github.pellse.concurrent.ReactiveGuardEventListener.reactiveGuardEventAdapter; 32 | 33 | public interface ConcurrentCacheFactory { 34 | 35 | static > CacheTransformer concurrent() { 36 | return concurrent((LockStrategy) null); 37 | } 38 | 39 | static > CacheTransformer concurrent(Scheduler fetchFunctionScheduler) { 40 | return concurrent((LockStrategy) null, fetchFunctionScheduler); 41 | } 42 | 43 | static > CacheTransformer concurrent(LockStrategy lockStrategy) { 44 | return concurrent(lockStrategy, null); 45 | } 46 | 47 | static > CacheTransformer concurrent(LockStrategy lockStrategy, Scheduler fetchFunctionScheduler) { 48 | return concurrent(reactiveGuardBuilder().lockingStrategy(lockStrategy), fetchFunctionScheduler); 49 | } 50 | 51 | static > CacheTransformer concurrent(Consumer eventConsumer) { 52 | return concurrent(reactiveGuardBuilder().eventListener(reactiveGuardEventAdapter(eventConsumer))); 53 | } 54 | 55 | static > CacheTransformer concurrent(BiConsumer>> eventConsumer) { 56 | return concurrent(reactiveGuardBuilder().eventListener(reactiveGuardEventAdapter(eventConsumer))); 57 | } 58 | 59 | static > CacheTransformer concurrent(ReactiveGuardBuilder reactiveGuardBuilder) { 60 | return concurrent(reactiveGuardBuilder, null); 61 | } 62 | 63 | static > CacheTransformer concurrent(ReactiveGuardBuilder reactiveGuardBuilder, Scheduler fetchFunctionScheduler) { 64 | return cacheFactory -> context -> concurrentCache(cacheFactory.create(context), reactiveGuardBuilder, fetchFunctionScheduler); 65 | } 66 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/factory/DeferCacheFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.caching.factory; 18 | 19 | import io.github.pellse.assembler.caching.Cache; 20 | import io.github.pellse.util.function.Function3; 21 | import reactor.core.publisher.Mono; 22 | 23 | import java.util.Map; 24 | 25 | public interface DeferCacheFactory { 26 | 27 | static > CacheTransformer defer() { 28 | return DeferCacheFactory::defer; 29 | } 30 | 31 | static > CacheFactory defer(CacheFactory cacheFactory) { 32 | return context -> defer(cacheFactory.create(context)); 33 | } 34 | 35 | static Cache defer(Cache delegateCache) { 36 | 37 | return new Cache<>() { 38 | 39 | @Override 40 | public Mono> getAll(Iterable ids) { 41 | return Mono.defer(() -> delegateCache.getAll(ids)); 42 | } 43 | 44 | @Override 45 | public Mono> computeAll(Iterable ids, FetchFunction fetchFunction) { 46 | return Mono.defer(() -> delegateCache.computeAll(ids, fetchFunction)); 47 | } 48 | 49 | @Override 50 | public Mono putAll(Map map) { 51 | return Mono.defer(() -> delegateCache.putAll(map)); 52 | } 53 | 54 | @Override 55 | public Mono putAllWith(Map map, Function3 mergeFunction) { 56 | return Mono.defer(() -> delegateCache.putAllWith(map, mergeFunction)); 57 | } 58 | 59 | @Override 60 | public Mono removeAll(Map map) { 61 | return Mono.defer(() -> delegateCache.removeAll(map)); 62 | } 63 | 64 | @Override 65 | public Mono updateAll(Map mapToAdd, Map mapToRemove) { 66 | return Mono.defer(() -> delegateCache.updateAll(mapToAdd, mapToRemove)); 67 | } 68 | }; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/factory/MapperCacheFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.caching.factory; 18 | 19 | import io.github.pellse.assembler.caching.Cache; 20 | 21 | import reactor.core.publisher.Mono; 22 | 23 | import java.util.Map; 24 | import java.util.function.BiFunction; 25 | import java.util.function.Function; 26 | 27 | import static io.github.pellse.util.collection.CollectionUtils.transformMapValues; 28 | 29 | @Deprecated 30 | public interface MapperCacheFactory { 31 | 32 | static > CacheTransformer mapper(Function> mappingFunction) { 33 | return cacheFactory -> mapper(cacheFactory, mappingFunction); 34 | } 35 | 36 | static > CacheFactory mapper(CacheFactory cacheFactory, Function> mappingFunction) { 37 | 38 | return context -> { 39 | 40 | final var delegateCache = cacheFactory.create(context); 41 | 42 | return new Cache<>() { 43 | 44 | @Override 45 | public Mono> getAll(Iterable ids) { 46 | return delegateCache.getAll(ids); 47 | } 48 | 49 | @Override 50 | public Mono> computeAll(Iterable ids, FetchFunction fetchFunction) { 51 | return delegateCache.computeAll(ids, idList -> fetchFunction.apply(idList).map(m -> transformMapValues(m, mappingFunction.apply(context)))); 52 | } 53 | 54 | @Override 55 | public Mono putAll(Map map) { 56 | return delegateCache.putAll(transformMapValues(map, mappingFunction.apply(context))); 57 | } 58 | 59 | @Override 60 | public Mono removeAll(Map map) { 61 | return delegateCache.removeAll(map); 62 | } 63 | 64 | @Override 65 | public Mono updateAll(Map mapToAdd, Map mapToRemove) { 66 | return delegateCache.updateAll(transformMapValues(mapToAdd, mappingFunction.apply(context)), mapToRemove); 67 | } 68 | }; 69 | }; 70 | } 71 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/factory/ObservableCacheFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.caching.factory; 18 | 19 | import io.github.pellse.assembler.caching.Cache; 20 | import reactor.core.publisher.Mono; 21 | 22 | import java.util.Map; 23 | import java.util.function.BiConsumer; 24 | import java.util.function.Consumer; 25 | import java.util.function.Function; 26 | 27 | @Deprecated 28 | public interface ObservableCacheFactory { 29 | 30 | static > CacheTransformer observableCache( 31 | Consumer> onGetAll, 32 | Consumer> onComputeAll, 33 | Consumer> onPutAll, 34 | Consumer> onRemoveAll, 35 | BiConsumer, Map> onUpdateAll) { 36 | 37 | return cacheFactory -> cacheContext -> { 38 | 39 | final var cache = cacheFactory.create(cacheContext); 40 | 41 | return new Cache<>() { 42 | 43 | @Override 44 | public Mono> getAll(Iterable ids) { 45 | return cache.getAll(ids).transform(call(onGetAll)); 46 | } 47 | 48 | @Override 49 | public Mono> computeAll(Iterable ids, FetchFunction fetchFunction) { 50 | return cache.computeAll(ids, fetchFunction).transform(call(onComputeAll)); 51 | } 52 | 53 | @Override 54 | public Mono putAll(Map map) { 55 | return cache.putAll(map).transform(call(map, onPutAll)); 56 | } 57 | 58 | @Override 59 | public Mono removeAll(Map map) { 60 | return cache.removeAll(map).transform(call(map, onRemoveAll)); 61 | } 62 | 63 | @Override 64 | public Mono updateAll(Map mapToAdd, Map mapToRemove) { 65 | return cache.updateAll(mapToAdd, mapToRemove).transform(call(mapToAdd, mapToRemove, onUpdateAll)); 66 | } 67 | }; 68 | }; 69 | } 70 | 71 | static Function, Mono> call(Consumer consumer) { 72 | return mono -> consumer != null ? mono.doOnNext(consumer) : mono; 73 | } 74 | 75 | static Function, Mono> call(U value, Consumer consumer) { 76 | return mono -> consumer != null ? mono.doOnNext(__ -> consumer.accept(value)) : mono; 77 | } 78 | 79 | static Function, Mono> call(U value1, V value2, BiConsumer consumer) { 80 | return mono -> consumer != null ? mono.doOnNext(__ -> consumer.accept(value1, value2)) : mono; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/factory/SerializeCacheFactory.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.assembler.caching.factory; 2 | 3 | import io.github.pellse.assembler.caching.Cache; 4 | import io.github.pellse.util.function.Function3; 5 | import reactor.core.publisher.Mono; 6 | 7 | import java.util.Map; 8 | import java.util.concurrent.atomic.AtomicBoolean; 9 | 10 | import static io.github.pellse.util.lock.LockSupplier.executeWithLock; 11 | 12 | public interface SerializeCacheFactory { 13 | 14 | static > CacheTransformer serialize() { 15 | return SerializeCacheFactory::serialize; 16 | } 17 | 18 | static > CacheFactory serialize(CacheFactory cacheFactory) { 19 | return context -> serialize(cacheFactory.create(context)); 20 | } 21 | 22 | static Cache serialize(Cache delegateCache) { 23 | 24 | final var lock = new AtomicBoolean(); 25 | 26 | return new Cache<>() { 27 | 28 | @Override 29 | public Mono> getAll(Iterable ids) { 30 | return delegateCache.getAll(ids); 31 | } 32 | 33 | @Override 34 | public Mono> computeAll(Iterable ids, FetchFunction fetchFunction) { 35 | return executeWithLock(() -> delegateCache.computeAll(ids, fetchFunction), lock); 36 | } 37 | 38 | @Override 39 | public Mono putAll(Map map) { 40 | return delegateCache.putAll(map); 41 | } 42 | 43 | @Override 44 | public Mono putAllWith(Map map, Function3 mergeFunction) { 45 | return delegateCache.putAllWith(map, mergeFunction); 46 | } 47 | 48 | @Override 49 | public Mono removeAll(Map map) { 50 | return delegateCache.removeAll(map); 51 | } 52 | 53 | @Override 54 | public Mono updateAll(Map mapToAdd, Map mapToRemove) { 55 | return delegateCache.updateAll(mapToAdd, mapToRemove); 56 | } 57 | }; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/factory/SortByCacheFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.caching.factory; 18 | 19 | import io.github.pellse.assembler.caching.factory.CacheContext.OneToManyCacheContext; 20 | 21 | import java.util.Comparator; 22 | import java.util.List; 23 | import java.util.function.Function; 24 | 25 | import static io.github.pellse.assembler.caching.factory.MapperCacheFactory.mapper; 26 | 27 | @Deprecated 28 | public interface SortByCacheFactory { 29 | 30 | static CacheTransformer, OneToManyCacheContext> sortBy() { 31 | return cacheFactory -> sortBy(cacheFactory, (Comparator) null); 32 | } 33 | 34 | static CacheTransformer, OneToManyCacheContext> sortBy(Comparator comparator) { 35 | return cacheFactory -> sortBy(cacheFactory, comparator); 36 | } 37 | 38 | static CacheFactory, OneToManyCacheContext> sortBy( 39 | CacheFactory, OneToManyCacheContext> cacheFactory) { 40 | 41 | return sortBy(cacheFactory, (Comparator) null); 42 | } 43 | 44 | static CacheFactory, OneToManyCacheContext> sortBy( 45 | CacheFactory, OneToManyCacheContext> cacheFactory, 46 | Comparator comparator) { 47 | 48 | return sortBy(cacheFactory, cacheContext -> comparator != null ? comparator : cacheContext.idComparator()); 49 | } 50 | 51 | private static CacheFactory, OneToManyCacheContext> sortBy( 52 | CacheFactory, OneToManyCacheContext> cacheFactory, 53 | Function, Comparator> comparatorProvider) { 54 | 55 | return mapper(cacheFactory, 56 | cacheContext -> 57 | (__, coll) -> coll.stream() 58 | .sorted(comparatorProvider.apply(cacheContext)) 59 | .toList()); 60 | } 61 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/factory/StreamTableFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.caching.factory; 18 | 19 | import io.github.pellse.assembler.ErrorHandler; 20 | import io.github.pellse.assembler.LifeCycleEventListener; 21 | import io.github.pellse.assembler.LifeCycleEventSource; 22 | import io.github.pellse.assembler.WindowingStrategy; 23 | import io.github.pellse.assembler.caching.CacheEvent; 24 | import io.github.pellse.assembler.caching.CacheEvent.Updated; 25 | import reactor.core.Disposable; 26 | import reactor.core.publisher.Flux; 27 | import reactor.core.scheduler.Scheduler; 28 | 29 | import java.lang.System.Logger; 30 | import java.util.List; 31 | import java.util.Map; 32 | import java.util.function.*; 33 | import java.util.stream.Collector; 34 | 35 | import static io.github.pellse.assembler.ErrorHandler.OnErrorContinue.onErrorContinue; 36 | import static io.github.pellse.assembler.LifeCycleEventSource.concurrentLifeCycleEventListener; 37 | import static io.github.pellse.assembler.LifeCycleEventSource.lifeCycleEventAdapter; 38 | import static io.github.pellse.assembler.caching.CacheEvent.toCacheEvent; 39 | import static io.github.pellse.util.collection.CollectionUtils.isEmpty; 40 | import static io.github.pellse.util.reactive.ReactiveUtils.publishFluxOn; 41 | import static java.lang.System.Logger.Level.WARNING; 42 | import static java.lang.System.getLogger; 43 | import static java.util.Objects.requireNonNull; 44 | import static java.util.Objects.requireNonNullElse; 45 | import static java.util.Optional.ofNullable; 46 | import static java.util.function.Function.identity; 47 | import static java.util.stream.Collectors.partitioningBy; 48 | 49 | public interface StreamTableFactory { 50 | 51 | int MAX_WINDOW_SIZE = 1; 52 | 53 | Logger logger = getLogger(StreamTableFactory.class.getName()); 54 | 55 | static > CacheTransformer streamTable(Flux dataSource) { 56 | return streamTable(dataSource, __ -> true, identity()); 57 | } 58 | 59 | static , U> CacheTransformer streamTable( 60 | Flux dataSource, 61 | Predicate isAddOrUpdateEvent, 62 | Function cacheEventValueExtractor) { 63 | 64 | return streamTable(dataSource.map(toCacheEvent(isAddOrUpdateEvent, cacheEventValueExtractor)), null, null, null, null, null); 65 | } 66 | 67 | static , C extends CacheEvent> CacheTransformer streamTable( 68 | Flux dataSource, 69 | WindowingStrategy windowingStrategy, 70 | ErrorHandler errorHandler, 71 | LifeCycleEventSource lifeCycleEventSource, 72 | Scheduler scheduler, 73 | Function, CacheFactory> cacheTransformer) { 74 | 75 | return cacheFactory -> cacheContext -> { 76 | 77 | final var mapCollector = cacheContext.mapCollector(); 78 | 79 | final var cache = ofNullable(cacheTransformer) 80 | .map(transformer -> transformer.apply(cacheFactory)) 81 | .orElseGet(() -> cacheContext.cacheTransformer().apply(cacheFactory)) 82 | .create(cacheContext); 83 | 84 | final var cacheSourceFlux = requireNonNull(dataSource, "dataSource cannot be null") 85 | .transform(publishFluxOn(scheduler)) 86 | .transform(requireNonNullElse(windowingStrategy, flux -> flux.window(MAX_WINDOW_SIZE))) 87 | .flatMap(flux -> flux.collect(partitioningBy(Updated.class::isInstance))) 88 | .flatMap(eventMap -> cache.updateAll(toMap(eventMap.get(true), mapCollector), toMap(eventMap.get(false), mapCollector))) 89 | .transform(requireNonNullElse(errorHandler, onErrorContinue(StreamTableFactory::logError)).toFluxErrorHandler()); 90 | 91 | requireNonNullElse(lifeCycleEventSource, LifeCycleEventListener::start) 92 | .addLifeCycleEventListener(concurrentLifeCycleEventListener(lifeCycleEventAdapter(cacheSourceFlux, Flux::subscribe, Disposable::dispose))); 93 | 94 | return cache; 95 | }; 96 | } 97 | 98 | private static Map toMap(List> cacheEvents, IntFunction>> mapCollector) { 99 | return isEmpty(cacheEvents) ? Map.of() : cacheEvents.stream() 100 | .map(CacheEvent::value) 101 | .collect(mapCollector.apply(cacheEvents.size())); 102 | } 103 | 104 | private static void logError(Throwable t, Object faultyData) { 105 | logger.log(WARNING, "Error while updating cache in streamTable() with " + faultyData, t); 106 | } 107 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/merge/MergeFunction.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.assembler.caching.merge; 2 | 3 | import io.github.pellse.util.function.Function3; 4 | 5 | @FunctionalInterface 6 | public interface MergeFunction extends Function3 { 7 | 8 | RRC merge(ID id, RRC existingValue, RRC newValue); 9 | 10 | default RRC apply(ID id, RRC existingValue, RRC newValue) { 11 | return merge(id, existingValue, newValue); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/merge/MergeFunctionContext.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.assembler.caching.merge; 2 | 3 | import io.github.pellse.assembler.RuleMapperContext.OneToManyContext; 4 | 5 | import java.util.Comparator; 6 | import java.util.function.Function; 7 | 8 | public interface MergeFunctionContext { 9 | Function idResolver(); 10 | 11 | Comparator idComparator(); 12 | 13 | static MergeFunctionContext mergeFunctionContext(OneToManyContext ctx) { 14 | return new MergeFunctionContext<>() { 15 | 16 | @Override 17 | public Function idResolver() { 18 | return ctx.idResolver(); 19 | } 20 | 21 | @Override 22 | public Comparator idComparator() { 23 | return ctx.idComparator(); 24 | } 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/merge/MergeFunctionFactory.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.assembler.caching.merge; 2 | 3 | import java.util.List; 4 | import java.util.function.Function; 5 | import java.util.stream.Stream; 6 | 7 | import static java.util.function.Function.identity; 8 | 9 | public interface MergeFunctionFactory extends Function, MergeFunction>> { 10 | 11 | MergeFunction> create(MergeFunctionContext ctx); 12 | 13 | @Override 14 | default MergeFunction> apply(MergeFunctionContext ctx) { 15 | return create(ctx); 16 | } 17 | 18 | default MergeFunctionFactory pipe(Function, List> finisher) { 19 | return pipeWith(__ -> finisher); 20 | } 21 | 22 | default MergeFunctionFactory pipeWith(Function, Function, List>> finisherFactory) { 23 | return ctx -> { 24 | final var mergeFunction = create(ctx); 25 | final var finisher = finisherFactory.apply(ctx); 26 | 27 | return (id, existing, next) -> finisher.apply(mergeFunction.apply(id, existing, next)); 28 | }; 29 | } 30 | 31 | static MergeFunctionFactory from(MergeFunctionFactory mergeFunctionFactory) { 32 | return mergeFunctionFactory; 33 | } 34 | 35 | static MergeFunctionFactory from(MergeFunction> mergeFunction) { 36 | return __ -> mergeFunction; 37 | } 38 | 39 | @SafeVarargs 40 | static MergeFunctionFactory pipe( 41 | MergeFunction> mergeFunction, 42 | Function, List>... finishers) { 43 | 44 | return pipe(from(mergeFunction), finishers); 45 | } 46 | 47 | @SafeVarargs 48 | static MergeFunctionFactory pipe( 49 | MergeFunctionFactory mergeFunctionFactory, 50 | Function, List>... finishers) { 51 | 52 | final var finisher = Stream.of(finishers) 53 | .reduce(identity(), (f, acc) -> acc.andThen(f)); 54 | 55 | return mergeFunctionFactory.pipe(finisher); 56 | } 57 | 58 | @SafeVarargs 59 | static MergeFunctionFactory pipeWith( 60 | MergeFunction> mergeFunction, 61 | Function, Function, List>>... finisherFactories) { 62 | 63 | return pipeWith(from(mergeFunction), finisherFactories); 64 | } 65 | 66 | @SafeVarargs 67 | static MergeFunctionFactory pipeWith( 68 | MergeFunctionFactory mergeFunctionFactory, 69 | Function, Function, List>>... finisherFactories) { 70 | 71 | return ctx -> { 72 | final var finisher = Stream.of(finisherFactories) 73 | .map(f -> f.apply(ctx)) 74 | .reduce(identity(), (f, acc) -> acc.andThen(f)); 75 | 76 | return mergeFunctionFactory.pipe(finisher).create(ctx); 77 | }; 78 | } 79 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/assembler/caching/merge/MergeFunctions.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.assembler.caching.merge; 2 | 3 | import io.github.pellse.util.collection.CollectionUtils; 4 | 5 | import java.util.List; 6 | import java.util.function.Function; 7 | 8 | import static io.github.pellse.util.collection.CollectionUtils.asList; 9 | import static io.github.pellse.util.collection.CollectionUtils.concat; 10 | import static java.lang.Math.max; 11 | import static java.lang.Math.min; 12 | import static java.util.List.copyOf; 13 | 14 | public interface MergeFunctions { 15 | 16 | static MergeFunctionFactory removeDuplicates() { 17 | return MergeFunctions::removeDuplicates; 18 | } 19 | 20 | static MergeFunction> removeDuplicates(MergeFunctionContext ctx) { 21 | return removeDuplicates(ctx.idResolver()); 22 | } 23 | 24 | static MergeFunction> removeDuplicates(Function idResolver) { 25 | return (id, existingList, newList) -> CollectionUtils.removeDuplicates(concat(existingList, newList), idResolver); 26 | } 27 | 28 | static Function, Function, List>> removeAllDuplicates() { 29 | return MergeFunctions::removeAllDuplicates; 30 | } 31 | 32 | static Function, List> removeAllDuplicates(MergeFunctionContext ctx) { 33 | return list -> CollectionUtils.removeDuplicates(list, ctx.idResolver()); 34 | } 35 | 36 | static MergeFunction> keepFirst(int nbElements) { 37 | return (id, existingList, newList) -> concat(existingList, newList) 38 | .limit(min(nbElements, asList(existingList).size() + asList(newList).size())) 39 | .toList(); 40 | } 41 | 42 | static MergeFunction> keepLast(int nbElements) { 43 | return (id, existingList, newList) -> { 44 | final var size = asList(existingList).size() + asList(newList).size(); 45 | 46 | return concat(existingList, newList) 47 | .skip(size - min(nbElements, size)) 48 | .toList(); 49 | }; 50 | } 51 | 52 | static Function, List> keepFirstN(int nbElements) { 53 | return keep(nbElements, list -> list.subList(0, nbElements)); 54 | } 55 | 56 | static Function, List> keepLastN(int nbElements) { 57 | return keep(nbElements, list -> list.subList(max(0, list.size() - nbElements), list.size())); 58 | } 59 | 60 | static MergeFunction replace() { 61 | return MergeFunctions::replace; 62 | } 63 | 64 | static R replace(ID id, R r1, R r2) { 65 | return r2 != null ? r2 : r1; 66 | } 67 | 68 | private static Function, List> keep(int nbElements, Function, List> subListFunction) { 69 | return list -> { 70 | final var nonNullList = asList(list); 71 | 72 | if (nbElements <= 0) { 73 | return List.of(); 74 | } else if (nonNullList.size() <= nbElements) { 75 | return nonNullList; 76 | } 77 | 78 | return copyOf(subListFunction.apply(nonNullList)); 79 | }; 80 | } 81 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/concurrent/CASLockStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.concurrent; 18 | 19 | import io.github.pellse.concurrent.CoreLock.NoopLock; 20 | import io.github.pellse.concurrent.CoreLock.ReadLock; 21 | import io.github.pellse.concurrent.CoreLock.WriteLock; 22 | import io.github.pellse.util.function.BiLongPredicate; 23 | import reactor.core.publisher.Mono; 24 | 25 | import java.time.Duration; 26 | import java.util.concurrent.atomic.AtomicLong; 27 | import java.util.function.*; 28 | 29 | import static io.github.pellse.concurrent.CoreLock.NoopLock.noopLock; 30 | import static java.time.Duration.ofNanos; 31 | import static java.util.Objects.requireNonNullElse; 32 | import static java.util.concurrent.locks.LockSupport.parkNanos; 33 | import static reactor.core.publisher.Mono.*; 34 | 35 | public class CASLockStrategy implements LockStrategy { 36 | 37 | @FunctionalInterface 38 | interface LockFactory> { 39 | L create(long id, CoreLock outerLock, Consumer lockReleaser); 40 | } 41 | 42 | private static final long WRITE_LOCK_MASK = 1L << 63; // 1000000000000000000000000000000000000000000000000000000000000000 43 | private static final long READ_LOCK_MASK = ~WRITE_LOCK_MASK; // 0111111111111111111111111111111111111111111111111111111111111111 44 | 45 | private final AtomicLong idCounter = new AtomicLong(); 46 | private final AtomicLong lockState = new AtomicLong(); 47 | 48 | private final long maxRetries; 49 | private final long delay; 50 | 51 | public CASLockStrategy() { 52 | this(50, null); 53 | } 54 | 55 | public CASLockStrategy(long maxRetries, Duration waitTime) { 56 | this.maxRetries = maxRetries; 57 | this.delay = requireNonNullElse(waitTime, ofNanos(50)).toNanos(); 58 | } 59 | 60 | @Override 61 | public Mono> acquireReadLock() { 62 | return acquireLock(ReadLock::new, this::tryAcquireReadLock, this::releaseReadLock); 63 | } 64 | 65 | @Override 66 | public Mono> acquireWriteLock() { 67 | return acquireLock(WriteLock::new, this::tryAcquireWriteLock, this::releaseWriteLock); 68 | } 69 | 70 | @Override 71 | public Mono> toWriteLock(Lock lock) { 72 | return acquireLock(WriteLock::new, lock, this::tryAcquireWriteLock, this::releaseWriteLock); 73 | } 74 | 75 | @Override 76 | public void releaseReadLock(ReadLock readLock) { 77 | releaseLock(readLock, this::doReleaseReadLock); 78 | } 79 | 80 | @Override 81 | public void releaseWriteLock(WriteLock writeLock) { 82 | releaseLock(writeLock, this::doReleaseWriteLock); 83 | } 84 | 85 | private > Mono> acquireLock( 86 | LockFactory lockFactory, 87 | Predicate tryAcquireLock, 88 | Consumer lockReleaser) { 89 | 90 | return acquireLock(lockFactory, noopLock(), tryAcquireLock, lockReleaser); 91 | } 92 | 93 | private > Mono> acquireLock( 94 | LockFactory lockFactory, 95 | Lock outerLock, 96 | Predicate tryAcquireLock, 97 | Consumer lockReleaser) { 98 | 99 | // We avoid relying on Mono.retryWhen() to implement the waiting loop to prevent work stealing algo to kick in 100 | // under high contention, or thread switching with e.g. fixedDelay(maxRetries, waitTime), which can lead to 101 | // out of order task processing 102 | return defer(() -> { 103 | final var innerLock = lockFactory.create(idCounter.incrementAndGet(), outerLock.delegate(), lockReleaser); 104 | 105 | long nbAttempts = 0; 106 | boolean lockAcquired; 107 | 108 | while (!(lockAcquired = tryAcquireLock.test(innerLock)) && nbAttempts++ < maxRetries) { 109 | parkNanos(delay); 110 | } 111 | return lockAcquired ? just(innerLock) : error(LOCK_ACQUISITION_EXCEPTION); 112 | }); 113 | } 114 | 115 | private boolean tryAcquireReadLock(ReadLock innerLock) { 116 | return tryAcquireLock( 117 | innerLock, 118 | (__, currentState) -> (currentState & WRITE_LOCK_MASK) == 0, 119 | currentState -> currentState + 1); 120 | } 121 | 122 | private boolean tryAcquireWriteLock(WriteLock innerLock) { 123 | BiLongPredicate currentStatePredicate = (lock, currentState) -> switch (lock.outerLock()) { 124 | case ReadLock __ -> (currentState & WRITE_LOCK_MASK) == 0; // Nested lock, we try to convert an already acquired read lock to a write lock 125 | case WriteLock __ -> (currentState & WRITE_LOCK_MASK) == WRITE_LOCK_MASK; // Sanity check, should always be true 126 | case NoopLock __ -> currentState == 0; // Try to acquire a write lock when no other lock is acquired 127 | }; 128 | return tryAcquireLock(innerLock, currentStatePredicate, currentState -> currentState | WRITE_LOCK_MASK); 129 | } 130 | 131 | private > boolean tryAcquireLock(L innerLock, BiLongPredicate currentStatePredicate, LongUnaryOperator currentStateUpdater) { 132 | long currentState, newState; 133 | do { 134 | currentState = lockState.get(); 135 | if (!currentStatePredicate.test(innerLock, currentState)) { 136 | return false; 137 | } 138 | newState = currentStateUpdater.applyAsLong(currentState); 139 | } while (!lockState.compareAndSet(currentState, newState)); 140 | 141 | return true; 142 | } 143 | 144 | private > void releaseLock(L innerLock, ToLongFunction lockReleaser) { 145 | lockReleaser.applyAsLong(innerLock); 146 | } 147 | 148 | private long doReleaseReadLock(ReadLock innerLock) { 149 | return lockState.decrementAndGet(); 150 | } 151 | 152 | private long doReleaseWriteLock(WriteLock innerLock) { 153 | return !(innerLock.outerLock() instanceof WriteLock) ? lockState.updateAndGet(currentState -> currentState & READ_LOCK_MASK) : lockState.get(); 154 | } 155 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/concurrent/CoreLock.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.concurrent; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import static io.github.pellse.concurrent.CoreLock.NoopLock.noopLock; 6 | import static io.github.pellse.util.ObjectUtils.doNothing; 7 | import static java.lang.Thread.currentThread; 8 | 9 | public sealed interface CoreLock> extends Lock { 10 | 11 | record ReadLock(long token, CoreLock outerLock, Consumer lockReleaser, Thread acquiredOnThread) implements CoreLock { 12 | ReadLock(long token, Consumer lockReleaser) { 13 | this(token, noopLock(), lockReleaser); 14 | } 15 | 16 | ReadLock(long token, CoreLock outerLock, Consumer lockReleaser) { 17 | this(token, outerLock, lockReleaser, currentThread()); 18 | } 19 | } 20 | 21 | record WriteLock(long token, CoreLock outerLock, Consumer lockReleaser, Thread acquiredOnThread) implements CoreLock { 22 | WriteLock(long token, Consumer lockReleaser) { 23 | this(token, noopLock(), lockReleaser); 24 | } 25 | 26 | WriteLock(long token, CoreLock outerLock, Consumer lockReleaser) { 27 | this(token, outerLock, lockReleaser, currentThread()); 28 | } 29 | } 30 | 31 | final class NoopLock implements CoreLock { 32 | 33 | private static final NoopLock NOOP_LOCK = new NoopLock(); 34 | 35 | private NoopLock() { 36 | } 37 | 38 | static NoopLock noopLock() { 39 | return NOOP_LOCK; 40 | } 41 | 42 | @Override 43 | public long token() { 44 | return -1; 45 | } 46 | 47 | @Override 48 | public CoreLock outerLock() { 49 | return noopLock(); 50 | } 51 | 52 | @Override 53 | public Consumer lockReleaser() { 54 | return doNothing(); 55 | } 56 | 57 | @Override 58 | public Thread acquiredOnThread() { 59 | return null; 60 | } 61 | 62 | @Override 63 | public String log() { 64 | return "NoopLock"; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/concurrent/Lock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.concurrent; 18 | 19 | import io.github.pellse.util.DelegateAware; 20 | import io.github.pellse.util.ObjectUtils; 21 | import reactor.core.publisher.Mono; 22 | 23 | import java.util.function.Consumer; 24 | 25 | import static java.util.Map.entry; 26 | import static reactor.core.publisher.Mono.fromRunnable; 27 | 28 | public interface Lock> extends DelegateAware { 29 | 30 | long token(); 31 | 32 | CoreLock outerLock(); 33 | 34 | Consumer lockReleaser(); 35 | 36 | Thread acquiredOnThread(); 37 | 38 | default Mono release() { 39 | return fromRunnable(() -> lockReleaser().accept(delegate())); 40 | } 41 | 42 | @Override 43 | @SuppressWarnings("unchecked") 44 | default L delegate() { 45 | return (L) this; 46 | } 47 | 48 | default String log() { 49 | return ObjectUtils.toString(this, 50 | entry("token", token()), 51 | entry("outerLock", outerLock().log()), 52 | entry("acquiredOnThread", acquiredOnThread().getName())); 53 | } 54 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/concurrent/LockStrategy.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.concurrent; 2 | 3 | import io.github.pellse.concurrent.CoreLock.ReadLock; 4 | import io.github.pellse.concurrent.CoreLock.WriteLock; 5 | import reactor.core.publisher.Mono; 6 | 7 | public interface LockStrategy { 8 | 9 | LockAcquisitionException LOCK_ACQUISITION_EXCEPTION = new LockAcquisitionException(); 10 | 11 | Mono> acquireReadLock(); 12 | 13 | Mono> acquireWriteLock(); 14 | 15 | Mono> toWriteLock(Lock lock); 16 | 17 | void releaseReadLock(ReadLock readLock); 18 | 19 | void releaseWriteLock(WriteLock writeLock); 20 | 21 | class LockAcquisitionException extends Exception { 22 | LockAcquisitionException() { 23 | super(null, null, true, false); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/concurrent/ReactiveGuardEvent.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.concurrent; 2 | 3 | public sealed interface ReactiveGuardEvent { 4 | 5 | record LockAcquiredEvent(Lock lock) implements ReactiveGuardEvent { 6 | } 7 | 8 | record LockUpgradedEvent(Lock lock) implements ReactiveGuardEvent { 9 | } 10 | 11 | record LockAcquisitionFailedEvent(Throwable error) implements ReactiveGuardEvent { 12 | } 13 | 14 | record BeforeTaskExecutionEvent(Lock lock) implements ReactiveGuardEvent { 15 | } 16 | 17 | record AfterTaskExecutionEvent(Lock lock, T value) implements ReactiveGuardEvent { 18 | } 19 | 20 | record TaskExecutionFailedEvent(Lock lock, Throwable t) implements ReactiveGuardEvent { 21 | } 22 | 23 | record TaskExecutionTimeoutEvent(Lock lock) implements ReactiveGuardEvent { 24 | } 25 | 26 | record TaskExecutionCancelledEvent(Lock lock) implements ReactiveGuardEvent { 27 | } 28 | 29 | record LockReleasedEvent(Lock lock) implements ReactiveGuardEvent { 30 | } 31 | 32 | record LockReleaseFailedEvent(Lock lock, Throwable t) implements ReactiveGuardEvent { 33 | } 34 | 35 | record LockReleaseCancelledEvent(Lock lock) implements ReactiveGuardEvent { 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/concurrent/ReactiveGuardEventListener.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.concurrent; 2 | 3 | import io.github.pellse.concurrent.CoreLock.WriteLock; 4 | 5 | import java.util.Optional; 6 | import java.util.function.BiConsumer; 7 | import java.util.function.Consumer; 8 | 9 | import static io.github.pellse.concurrent.ReactiveGuardEvent.*; 10 | 11 | public interface ReactiveGuardEventListener { 12 | 13 | default void onLockAcquired(Lock lock) { 14 | } 15 | 16 | default void onLockUpgraded(WriteLock lock) { 17 | } 18 | 19 | default void onLockAcquisitionFailed(Throwable t) { 20 | } 21 | 22 | default void onBeforeTaskExecution(Lock lock) { 23 | } 24 | 25 | default void onAfterTaskExecution(Lock lock, T value) { 26 | } 27 | 28 | default void onTaskExecutionFailed(Lock lock, Throwable t) { 29 | } 30 | 31 | default void onTaskExecutionTimeout(Lock lock) { 32 | } 33 | 34 | default void onTaskExecutionCancelled(Lock lock) { 35 | } 36 | 37 | default void onLockReleased(Lock lock) { 38 | } 39 | 40 | default void onLockReleaseFailed(Lock lock, Throwable t) { 41 | } 42 | 43 | default void onLockReleaseCancelled(Lock lock) { 44 | } 45 | 46 | static ReactiveGuardEventListener defaultReactiveGuardListener() { 47 | return new ReactiveGuardEventListener<>() {}; 48 | } 49 | 50 | static ReactiveGuardEventListener reactiveGuardEventAdapter(BiConsumer>> eventConsumer) { 51 | return reactiveGuardEventAdapter(event -> { 52 | switch(event) { 53 | case LockAcquiredEvent lockAcquiredEvent -> eventConsumer.accept(lockAcquiredEvent, Optional.of(lockAcquiredEvent.lock())); 54 | case LockUpgradedEvent lockUpgradedEvent -> eventConsumer.accept(lockUpgradedEvent, Optional.of(lockUpgradedEvent.lock())); 55 | case LockAcquisitionFailedEvent lockAcquisitionFailedEvent -> eventConsumer.accept(lockAcquisitionFailedEvent, Optional.empty()); 56 | case AfterTaskExecutionEvent afterTaskExecutionEvent -> eventConsumer.accept(afterTaskExecutionEvent, Optional.of(afterTaskExecutionEvent.lock())); 57 | case BeforeTaskExecutionEvent beforeTaskExecutionEvent -> eventConsumer.accept(beforeTaskExecutionEvent, Optional.of(beforeTaskExecutionEvent.lock())); 58 | case TaskExecutionFailedEvent taskExecutionFailedEvent -> eventConsumer.accept(taskExecutionFailedEvent, Optional.of(taskExecutionFailedEvent.lock())); 59 | case TaskExecutionTimeoutEvent taskExecutionTimeoutEvent -> eventConsumer.accept(taskExecutionTimeoutEvent, Optional.of(taskExecutionTimeoutEvent.lock())); 60 | case TaskExecutionCancelledEvent taskExecutionCancelledEvent -> eventConsumer.accept(taskExecutionCancelledEvent, Optional.of(taskExecutionCancelledEvent.lock())); 61 | case LockReleasedEvent lockReleasedEvent -> eventConsumer.accept(lockReleasedEvent, Optional.of(lockReleasedEvent.lock())); 62 | case LockReleaseFailedEvent lockReleaseFailedEvent -> eventConsumer.accept(lockReleaseFailedEvent, Optional.of(lockReleaseFailedEvent.lock())); 63 | case LockReleaseCancelledEvent lockReleaseCancelledEvent -> eventConsumer.accept(lockReleaseCancelledEvent, Optional.of(lockReleaseCancelledEvent.lock())); 64 | } 65 | }); 66 | } 67 | 68 | static ReactiveGuardEventListener reactiveGuardEventAdapter(Consumer eventConsumer) { 69 | return new ReactiveGuardEventListener<>() { 70 | 71 | @Override 72 | public void onLockAcquired(Lock lock) { 73 | eventConsumer.accept(new LockAcquiredEvent(lock)); 74 | } 75 | 76 | @Override 77 | public void onLockUpgraded(WriteLock lock) { 78 | eventConsumer.accept(new LockUpgradedEvent(lock)); 79 | } 80 | 81 | @Override 82 | public void onLockAcquisitionFailed(Throwable t) { 83 | eventConsumer.accept(new LockAcquisitionFailedEvent(t)); 84 | } 85 | 86 | @Override 87 | public void onBeforeTaskExecution(Lock lock) { 88 | eventConsumer.accept(new BeforeTaskExecutionEvent(lock)); 89 | } 90 | 91 | @Override 92 | public void onAfterTaskExecution(Lock lock, T value) { 93 | eventConsumer.accept(new AfterTaskExecutionEvent<>(lock, value)); 94 | } 95 | 96 | @Override 97 | public void onTaskExecutionFailed(Lock lock, Throwable t) { 98 | eventConsumer.accept(new TaskExecutionFailedEvent(lock, t)); 99 | } 100 | 101 | @Override 102 | public void onTaskExecutionTimeout(Lock lock) { 103 | eventConsumer.accept(new TaskExecutionTimeoutEvent(lock)); 104 | } 105 | 106 | @Override 107 | public void onTaskExecutionCancelled(Lock lock) { 108 | eventConsumer.accept(new TaskExecutionCancelledEvent(lock)); 109 | } 110 | 111 | @Override 112 | public void onLockReleased(Lock lock) { 113 | eventConsumer.accept(new LockReleasedEvent(lock)); 114 | } 115 | 116 | @Override 117 | public void onLockReleaseFailed(Lock lock, Throwable t) { 118 | eventConsumer.accept(new LockReleaseFailedEvent(lock, t)); 119 | } 120 | 121 | @Override 122 | public void onLockReleaseCancelled(Lock lock) { 123 | eventConsumer.accept(new LockReleaseCancelledEvent(lock)); 124 | } 125 | }; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/concurrent/StampedLockStrategy.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.concurrent; 18 | 19 | import io.github.pellse.concurrent.CoreLock.ReadLock; 20 | import io.github.pellse.concurrent.CoreLock.WriteLock; 21 | import io.github.pellse.util.function.InterruptedFunction3; 22 | import reactor.core.publisher.Mono; 23 | 24 | import java.time.Duration; 25 | import java.util.concurrent.TimeUnit; 26 | import java.util.concurrent.locks.StampedLock; 27 | import java.util.function.BiFunction; 28 | import java.util.function.Consumer; 29 | 30 | import static java.time.Duration.ofSeconds; 31 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 32 | import static reactor.core.publisher.Mono.*; 33 | 34 | public class StampedLockStrategy implements LockStrategy { 35 | 36 | private final StampedLock stampedLock = new StampedLock(); 37 | private final long timeoutValue; 38 | 39 | public StampedLockStrategy() { 40 | this(ofSeconds(5)); 41 | } 42 | 43 | public StampedLockStrategy(Duration timeout) { 44 | this.timeoutValue = timeout.toMillis(); 45 | } 46 | 47 | @Override 48 | public Mono> acquireReadLock() { 49 | return acquireLock(StampedLock::tryReadLock, ReadLock::new, this::releaseReadLock); 50 | } 51 | 52 | @Override 53 | public Mono> acquireWriteLock() { 54 | return acquireLock(StampedLock::tryWriteLock, WriteLock::new, this::releaseWriteLock); 55 | } 56 | 57 | @Override 58 | public Mono> toWriteLock(Lock lock) { 59 | return fromSupplier(() -> { 60 | final var readLock = lock.delegate(); 61 | final var readStamp = readLock.token(); 62 | 63 | var writeStamp = stampedLock.tryConvertToWriteLock(readStamp); 64 | if (writeStamp == 0) { 65 | stampedLock.unlockRead(readStamp); 66 | writeStamp = stampedLock.writeLock(); 67 | } 68 | return new WriteLock(writeStamp, readLock, this::releaseWriteLock); 69 | }); 70 | } 71 | 72 | @Override 73 | public void releaseReadLock(ReadLock readLock) { 74 | releaseLock(readLock); 75 | } 76 | 77 | @Override 78 | public void releaseWriteLock(WriteLock writeLock) { 79 | releaseLock(writeLock); 80 | } 81 | 82 | private > Mono> acquireLock( 83 | InterruptedFunction3 stampProvider, 84 | BiFunction, Lock> lockProvider, 85 | Consumer releaseLock) { 86 | 87 | return defer(() -> { 88 | final var stamp = stampProvider.apply(stampedLock, timeoutValue, MILLISECONDS); 89 | return stamp != 0 ? just(lockProvider.apply(stamp, releaseLock)) : error(LOCK_ACQUISITION_EXCEPTION); 90 | }); 91 | } 92 | 93 | private void releaseLock(Lock lock) { 94 | final var stamp = lock.token(); 95 | if (stampedLock.validate(stamp)) { 96 | stampedLock.unlock(stamp); 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/DelegateAware.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.util; 2 | 3 | public interface DelegateAware { 4 | T delegate(); 5 | } 6 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/ObjectUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.util; 18 | 19 | import java.util.List; 20 | import java.util.Map.Entry; 21 | import java.util.Objects; 22 | import java.util.function.*; 23 | 24 | import static io.github.pellse.util.collection.CollectionUtils.isEmpty; 25 | import static java.util.Optional.ofNullable; 26 | import static java.util.stream.Collectors.joining; 27 | 28 | public interface ObjectUtils { 29 | 30 | Consumer DO_NOTHING = __ -> { 31 | }; 32 | 33 | @SuppressWarnings("unchecked") 34 | static Consumer doNothing() { 35 | return (Consumer) DO_NOTHING; 36 | } 37 | 38 | static boolean isSafeEqual(T t1, T t2, Function propertyExtractor) { 39 | return isSafeEqual(t1, propertyExtractor, t2, propertyExtractor); 40 | } 41 | 42 | static boolean isSafeEqual(T1 t1, 43 | Function propertyExtractor1, 44 | T2 t2, 45 | Function propertyExtractor2) { 46 | 47 | return ofNullable(t1) 48 | .map(propertyExtractor1) 49 | .equals(ofNullable(t2) 50 | .map(propertyExtractor2)); 51 | } 52 | 53 | static void ifNotNull(T value, Consumer codeBlock) { 54 | runIf(value, Objects::nonNull, codeBlock); 55 | } 56 | 57 | @SafeVarargs 58 | static T also(T value, Consumer... codeBlocks) { 59 | 60 | if (value == null) { 61 | return null; 62 | } 63 | 64 | for (var codeBlock : codeBlocks) { 65 | codeBlock.accept(value); 66 | } 67 | return value; 68 | } 69 | 70 | @SafeVarargs 71 | static void run(T value, Consumer... codeBlocks) { 72 | also(value, codeBlocks); 73 | } 74 | 75 | static void runIf(T value, Predicate predicate, Consumer codeBlock) { 76 | 77 | if (value != null && predicate.test(value)) { 78 | codeBlock.accept(value); 79 | } 80 | } 81 | 82 | static Consumer run(Runnable runnable) { 83 | return __ -> runnable.run(); 84 | } 85 | 86 | static Function get(R value) { 87 | return __ -> value; 88 | } 89 | 90 | static Function get(Supplier supplier) { 91 | return __ -> supplier.get(); 92 | } 93 | 94 | static R then(T value, Function mappingFunction) { 95 | return mappingFunction.apply(value); 96 | } 97 | 98 | static Predicate or(Predicate predicate1, Predicate predicate2) { 99 | return nonNull(predicate1).or(nonNull(predicate2)); 100 | } 101 | 102 | static Predicate nonNull(Predicate predicate) { 103 | return predicate != null ? predicate : t -> true; 104 | } 105 | 106 | @SafeVarargs 107 | static String toString(Object obj, Entry... attributes) { 108 | return ObjectUtils.toString(obj, List.of(attributes)); 109 | } 110 | 111 | static String toString(Object obj, List> attributes) { 112 | return obj.getClass().getSimpleName() 113 | + '[' + (!isEmpty(attributes) ? attributes.stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(joining(", ")) : "") + ']'; 114 | } 115 | 116 | @SuppressWarnings("unchecked") 117 | static T sneakyThrow(Throwable e) throws E { 118 | throw (E) e; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/function/BiLongPredicate.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.util.function; 2 | 3 | @FunctionalInterface 4 | public interface BiLongPredicate { 5 | 6 | boolean test(T t, long value); 7 | } 8 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/function/CheckedFunction3.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.util.function; 18 | 19 | import java.util.function.Consumer; 20 | 21 | import static io.github.pellse.util.ObjectUtils.doNothing; 22 | import static io.github.pellse.util.ObjectUtils.sneakyThrow; 23 | 24 | @FunctionalInterface 25 | public interface CheckedFunction3 extends Function3 { 26 | 27 | R checkedApply(T1 t1, T2 t2, T3 t3) throws E; 28 | 29 | @Override 30 | default R apply(T1 t1, T2 t2, T3 t3) { 31 | return applyWithHandler(t1, t2, t3, exceptionHandler()); 32 | } 33 | 34 | @SuppressWarnings("unchecked") 35 | default R applyWithHandler(T1 t1, T2 t2, T3 t3, Consumer exceptionHandler) { 36 | try { 37 | return checkedApply(t1, t2, t3); 38 | } catch (Throwable e) { 39 | exceptionHandler.accept((E) e); 40 | return sneakyThrow(e); 41 | } 42 | } 43 | 44 | default Consumer exceptionHandler() { 45 | return doNothing(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/function/Function10.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.util.function; 18 | 19 | import java.util.function.Function; 20 | 21 | import static java.util.Objects.requireNonNull; 22 | 23 | @FunctionalInterface 24 | public interface Function10 { 25 | 26 | R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9, T10 t10); 27 | 28 | default Function10 andThen(Function after) { 29 | requireNonNull(after); 30 | return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) -> after.apply(apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10)); 31 | } 32 | 33 | static 34 | Function10 of(Function10 function) { 35 | return function; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/function/Function11.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.util.function; 18 | 19 | import java.util.function.Function; 20 | 21 | import static java.util.Objects.requireNonNull; 22 | 23 | @FunctionalInterface 24 | public interface Function11 { 25 | 26 | R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9, T10 t10, T11 t11); 27 | 28 | default Function11 andThen(Function after) { 29 | requireNonNull(after); 30 | return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11) -> after.apply(apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11)); 31 | } 32 | 33 | static 34 | Function11 of(Function11 function) { 35 | return function; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/function/Function12.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.util.function; 18 | 19 | import java.util.function.Function; 20 | 21 | import static java.util.Objects.requireNonNull; 22 | 23 | @FunctionalInterface 24 | public interface Function12 { 25 | 26 | R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9, T10 t10, T11 t11, T12 t12); 27 | 28 | default Function12 andThen(Function after) { 29 | requireNonNull(after); 30 | return (t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) -> after.apply(apply(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12)); 31 | } 32 | 33 | static 34 | Function12 of(Function12 function) { 35 | return function; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/function/Function3.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.util.function; 18 | 19 | import java.util.function.Function; 20 | 21 | import static java.util.Objects.requireNonNull; 22 | 23 | @FunctionalInterface 24 | public interface Function3 { 25 | 26 | R apply(T1 t1, T2 t2, T3 t3); 27 | 28 | default Function3 andThen(Function after) { 29 | requireNonNull(after); 30 | return (t1, t2, t3) -> after.apply(apply(t1, t2, t3)); 31 | } 32 | 33 | static Function3 of(Function3 function) { 34 | return function; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/function/Function4.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.util.function; 18 | 19 | import java.util.function.Function; 20 | 21 | import static java.util.Objects.requireNonNull; 22 | 23 | @FunctionalInterface 24 | public interface Function4 { 25 | 26 | R apply(T1 t1, T2 t2, T3 t3, T4 t4); 27 | 28 | default Function4 andThen(Function after) { 29 | requireNonNull(after); 30 | return (t1, t2, t3, t4) -> after.apply(apply(t1, t2, t3, t4)); 31 | } 32 | 33 | static Function4 of(Function4 function) { 34 | return function; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/function/Function5.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.util.function; 18 | 19 | import java.util.function.Function; 20 | 21 | import static java.util.Objects.requireNonNull; 22 | 23 | @FunctionalInterface 24 | public interface Function5 { 25 | 26 | R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5); 27 | 28 | default Function5 andThen(Function after) { 29 | requireNonNull(after); 30 | return (t1, t2, t3, t4, t5) -> after.apply(apply(t1, t2, t3, t4, t5)); 31 | } 32 | 33 | static 34 | Function5 of(Function5 function) { 35 | return function; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/function/Function6.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.util.function; 18 | 19 | import java.util.function.Function; 20 | 21 | import static java.util.Objects.requireNonNull; 22 | 23 | @FunctionalInterface 24 | public interface Function6 { 25 | 26 | R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6); 27 | 28 | default Function6 andThen(Function after) { 29 | requireNonNull(after); 30 | return (t1, t2, t3, t4, t5, t6) -> after.apply(apply(t1, t2, t3, t4, t5, t6)); 31 | } 32 | 33 | static 34 | Function6 of(Function6 function) { 35 | return function; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/function/Function7.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.util.function; 18 | 19 | import java.util.function.Function; 20 | 21 | import static java.util.Objects.requireNonNull; 22 | 23 | @FunctionalInterface 24 | public interface Function7 { 25 | 26 | R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7); 27 | 28 | default Function7 andThen(Function after) { 29 | requireNonNull(after); 30 | return (t1, t2, t3, t4, t5, t6, t7) -> after.apply(apply(t1, t2, t3, t4, t5, t6, t7)); 31 | } 32 | 33 | static 34 | Function7 of(Function7 function) { 35 | return function; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/function/Function8.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.util.function; 18 | 19 | import java.util.function.Function; 20 | 21 | import static java.util.Objects.requireNonNull; 22 | 23 | @FunctionalInterface 24 | public interface Function8 { 25 | 26 | R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8); 27 | 28 | default Function8 andThen(Function after) { 29 | requireNonNull(after); 30 | return (t1, t2, t3, t4, t5, t6, t7, t8) -> after.apply(apply(t1, t2, t3, t4, t5, t6, t7, t8)); 31 | } 32 | 33 | static 34 | Function8 of(Function8 function) { 35 | return function; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/function/Function9.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.util.function; 18 | 19 | import java.util.function.Function; 20 | 21 | import static java.util.Objects.requireNonNull; 22 | 23 | @FunctionalInterface 24 | public interface Function9 { 25 | 26 | R apply(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9); 27 | 28 | default Function9 andThen(Function after) { 29 | requireNonNull(after); 30 | return (t1, t2, t3, t4, t5, t6, t7, t8, t9) -> after.apply(apply(t1, t2, t3, t4, t5, t6, t7, t8, t9)); 31 | } 32 | 33 | static 34 | Function9 of(Function9 function) { 35 | return function; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/function/InterruptedFunction3.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.util.function; 2 | 3 | import java.util.function.Consumer; 4 | 5 | import static java.lang.Thread.currentThread; 6 | 7 | public interface InterruptedFunction3 extends CheckedFunction3 { 8 | 9 | @Override 10 | default Consumer exceptionHandler() { 11 | return e -> currentThread().interrupt(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/lock/LockSupplier.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.util.lock; 2 | 3 | import java.util.concurrent.atomic.AtomicBoolean; 4 | import java.util.function.Supplier; 5 | 6 | public interface LockSupplier { 7 | 8 | static Supplier withLock(Supplier supplier, AtomicBoolean lock) { 9 | return () -> { 10 | while (true) { 11 | if (lock.compareAndSet(false, true)) { 12 | try { 13 | return supplier.get(); 14 | } finally { 15 | lock.set(false); 16 | } 17 | } 18 | } 19 | }; 20 | } 21 | 22 | static T executeWithLock(Supplier supplier, AtomicBoolean lock) { 23 | return withLock(supplier, lock).get(); 24 | } 25 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/lookup/LookupTable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.util.lookup; 18 | 19 | import io.github.pellse.util.ObjectUtils; 20 | 21 | import java.util.*; 22 | import java.util.function.Function; 23 | 24 | import static io.github.pellse.util.ObjectUtils.also; 25 | import static java.util.Collections.emptyList; 26 | 27 | public interface LookupTable { 28 | 29 | void put(K key, V value); 30 | 31 | List get(K key); 32 | 33 | static LookupTable lookupTable() { 34 | 35 | final Map> map = new HashMap<>(); 36 | 37 | return new LookupTable<>() { 38 | 39 | @Override 40 | public void put(K key, V value) { 41 | map.computeIfAbsent(key, ObjectUtils.get(ArrayList::new)).add(value); 42 | } 43 | 44 | @Override 45 | public List get(K key) { 46 | return map.getOrDefault(key, emptyList()); 47 | } 48 | }; 49 | } 50 | 51 | static LookupTable lookupTableFrom(Iterable elements, Function keyMapper, Function valueMapper) { 52 | return also(lookupTable(), lookupTable -> elements.forEach(e -> lookupTable.put(keyMapper.apply(e), valueMapper.apply(e)))); 53 | } 54 | } -------------------------------------------------------------------------------- /assembler/src/main/java/io/github/pellse/util/reactive/ReactiveUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.util.reactive; 18 | 19 | import reactor.core.publisher.Flux; 20 | import reactor.core.publisher.Mono; 21 | import reactor.core.publisher.Sinks; 22 | import reactor.core.scheduler.Scheduler; 23 | import reactor.core.scheduler.Schedulers; 24 | import reactor.util.function.Tuple2; 25 | 26 | import java.util.Map; 27 | import java.util.function.BiFunction; 28 | import java.util.function.Supplier; 29 | import java.util.function.UnaryOperator; 30 | 31 | import static io.github.pellse.util.collection.CollectionUtils.toLinkedHashMap; 32 | import static java.lang.Math.toIntExact; 33 | import static java.util.List.copyOf; 34 | import static java.util.function.Function.identity; 35 | import static reactor.core.publisher.Flux.concat; 36 | import static reactor.core.publisher.Mono.*; 37 | import static reactor.core.scheduler.Schedulers.*; 38 | 39 | public interface ReactiveUtils { 40 | 41 | static Mono> resolve(Map> monoMap) { 42 | 43 | final var monoLinkedMap = toLinkedHashMap(monoMap); 44 | final var keys = copyOf(monoLinkedMap.keySet()); 45 | 46 | return concat(monoLinkedMap.values()) 47 | .index() 48 | .collectMap(tuple2 -> keys.get(toIntExact(tuple2.getT1())), Tuple2::getT2); 49 | } 50 | 51 | static Map> createSinkMap(Iterable iterable) { 52 | return toLinkedHashMap(iterable, identity(), __ -> Sinks.one()); 53 | } 54 | 55 | static Mono nullToEmpty(T value) { 56 | return value != null ? just(value) : empty(); 57 | } 58 | 59 | static Mono nullToEmpty(Supplier defaultValueProvider) { 60 | return defaultValueProvider != null ? fromSupplier(defaultValueProvider) : empty(); 61 | } 62 | 63 | static boolean isVirtualThreadSupported() { 64 | return DEFAULT_BOUNDED_ELASTIC_ON_VIRTUAL_THREADS; 65 | } 66 | 67 | static UnaryOperator> subscribeFluxOn(Scheduler scheduler) { 68 | return scheduleFluxOn(scheduler, Flux::subscribeOn); 69 | } 70 | 71 | static UnaryOperator> publishFluxOn(Scheduler scheduler) { 72 | return scheduleFluxOn(scheduler, Flux::publishOn); 73 | } 74 | 75 | static UnaryOperator> scheduleFluxOn(Scheduler scheduler, BiFunction, Scheduler, Flux> scheduleFunction) { 76 | return flux -> scheduler != null ? scheduleFunction.apply(flux, scheduler) : flux; 77 | } 78 | 79 | static UnaryOperator> subscribeMonoOn(Scheduler scheduler) { 80 | return scheduleMonoOn(scheduler, Mono::subscribeOn); 81 | } 82 | 83 | static UnaryOperator> publishMonoOn(Scheduler scheduler) { 84 | return scheduleMonoOn(scheduler, Mono::publishOn); 85 | } 86 | 87 | static UnaryOperator> scheduleMonoOn(Scheduler scheduler, BiFunction, Scheduler, Mono> scheduleFunction) { 88 | return mono -> scheduler != null ? scheduleFunction.apply(mono, scheduler) : mono; 89 | } 90 | 91 | static Scheduler defaultScheduler() { 92 | return scheduler(Schedulers::parallel); 93 | } 94 | 95 | static Scheduler scheduler(Supplier schedulerSupplier) { 96 | return isVirtualThreadSupported() ? boundedElastic() : schedulerSupplier.get(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /assembler/src/test/java/io/github/pellse/assembler/test/AssemblerTestUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.test; 18 | 19 | import io.github.pellse.assembler.util.*; 20 | import org.reactivestreams.Publisher; 21 | import reactor.core.publisher.Flux; 22 | 23 | import java.sql.SQLException; 24 | import java.util.LinkedList; 25 | import java.util.List; 26 | import java.util.Set; 27 | 28 | import static java.util.Collections.emptyList; 29 | import static java.util.Collections.emptySet; 30 | 31 | public interface AssemblerTestUtils { 32 | 33 | BillingInfo billingInfo1 = new BillingInfo(1, 1L, "4540977822220971"); 34 | BillingInfo billingInfo2 = new BillingInfo(2, 2L, "4530987722349872"); 35 | BillingInfo billingInfo2Unknown = new BillingInfo(null, 2L); 36 | BillingInfo billingInfo3 = new BillingInfo(4, 3L, "4540987722211234"); 37 | 38 | OrderItem orderItem11 = new OrderItem("1", 1L, "Sweater", 19.99); 39 | OrderItem orderItem12 = new OrderItem("2", 1L, "Pants", 39.99); 40 | OrderItem orderItem13 = new OrderItem("3", 1L, "Socks", 9.99); 41 | 42 | OrderItem orderItem21 = new OrderItem("4", 2L, "Shoes", 79.99); 43 | OrderItem orderItem22 = new OrderItem("5", 2L, "Boots", 99.99); 44 | 45 | OrderItem orderItem31 = new OrderItem("7", 3L, "Gloves", 5.99); 46 | OrderItem orderItem32 = new OrderItem("8", 3L, "Dress", 45.99); 47 | OrderItem orderItem33 = new OrderItem("9", 3L, "Sneakers", 119.99); 48 | 49 | Customer customer1 = new Customer(1L, "Clair Gabriel"); 50 | Customer customer2 = new Customer(2L, "Erick Daria"); 51 | Customer customer3 = new Customer(3L, "Brenden Jacob"); 52 | 53 | Transaction transaction2WithNullBillingInfo = new Transaction(customer2, null, 54 | List.of(orderItem21, orderItem22)); 55 | 56 | Transaction transaction1 = new Transaction(customer1, billingInfo1, 57 | List.of(orderItem11, orderItem12, orderItem13)); 58 | Transaction transaction2 = new Transaction(customer2, billingInfo2Unknown, 59 | List.of(orderItem21, orderItem22)); 60 | Transaction transaction3 = new Transaction(customer3, billingInfo3, emptyList()); 61 | 62 | TransactionSet transactionSet1 = new TransactionSet(customer1, billingInfo1, 63 | Set.of(orderItem11, orderItem12, orderItem13)); 64 | TransactionSet transactionSet2 = new TransactionSet(customer2, billingInfo2Unknown, 65 | Set.of(orderItem21, orderItem22)); 66 | TransactionSet transactionSet3 = new TransactionSet(customer3, billingInfo3, emptySet()); 67 | 68 | static Publisher getBillingInfo(List customerIds) { 69 | return Flux.just(billingInfo1, billingInfo3) 70 | .filter(billingInfo -> customerIds.contains(billingInfo.customerId())); 71 | } 72 | 73 | static Publisher getBillingInfoWithSetIds(Set customerIds) { 74 | return Flux.just(billingInfo1, null, billingInfo3) 75 | .filter(billingInfo -> billingInfo == null || customerIds.contains(billingInfo.customerId())); 76 | } 77 | 78 | static Publisher getAllOrders(List customerIds) { 79 | return Flux.just(orderItem11, orderItem12, orderItem13, orderItem21, orderItem22) 80 | .filter(orderItem -> customerIds.contains(orderItem.customerId())); 81 | } 82 | 83 | static Publisher getAllOrdersWithLinkedListIds(LinkedList customerIds) { 84 | //throw new SQLException("Exception in queryDatabaseForAllOrders"); 85 | return Flux.just(orderItem11, orderItem12, orderItem13, orderItem21, orderItem22) 86 | .filter(orderItem -> customerIds.contains(orderItem.customerId())); 87 | } 88 | 89 | static Publisher throwSQLException(List customerIds) { 90 | return Flux.error(new SQLException("Unable to query database")); 91 | } 92 | 93 | static Publisher errorBillingInfos(List customers) { 94 | return Flux.error(new SQLException("Unable to query database")); 95 | } 96 | 97 | static Publisher errorOrderItems(List customerIds) { 98 | return Flux.error(new SQLException("Unable to query database")); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /assembler/src/test/java/io/github/pellse/assembler/test/BatchRuleTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.test; 18 | 19 | import io.github.pellse.assembler.BatchRule; 20 | import io.github.pellse.assembler.util.BillingInfo; 21 | import io.github.pellse.assembler.util.Customer; 22 | import io.github.pellse.assembler.util.OrderItem; 23 | import org.junit.jupiter.api.Test; 24 | import reactor.core.publisher.Flux; 25 | import reactor.core.publisher.Mono; 26 | import reactor.test.StepVerifier; 27 | 28 | import java.util.List; 29 | import java.util.Map; 30 | 31 | import static io.github.pellse.assembler.BatchRule.withIdResolver; 32 | import static io.github.pellse.assembler.RuleMapper.oneToMany; 33 | import static io.github.pellse.assembler.RuleMapper.oneToOne; 34 | import static io.github.pellse.assembler.caching.factory.CacheFactory.cached; 35 | import static io.github.pellse.assembler.caching.factory.CacheFactory.cachedMany; 36 | import static io.github.pellse.assembler.test.AssemblerTestUtils.*; 37 | 38 | public class BatchRuleTest { 39 | 40 | private final BatchRule billingInfoBatchRule = withIdResolver(Customer::customerId) 41 | .createRule(BillingInfo::customerId, oneToOne(cached(this::getBillingInfo))); 42 | 43 | private final BatchRule> orderItemBatchRule = withIdResolver(Customer::customerId) 44 | .createRule(OrderItem::customerId, oneToMany(OrderItem::id, cachedMany(this::getAllOrders))); 45 | 46 | List customers = List.of(customer1, customer2, customer3); 47 | 48 | private Flux getBillingInfo(List customers) { 49 | return Flux.just(billingInfo1, billingInfo2, billingInfo3); 50 | } 51 | 52 | private Flux getAllOrders(List customers) { 53 | return Flux.just(orderItem11, orderItem12, orderItem13, orderItem21, orderItem22, orderItem31, orderItem32, orderItem33); 54 | } 55 | 56 | private Mono> billingInfo(List customers) { 57 | return billingInfoBatchRule.toMono(customers); 58 | } 59 | 60 | private Mono>> orderItems(List customers) { 61 | return orderItemBatchRule.toMono(customers); 62 | } 63 | 64 | private Flux billingInfoFlux(List customers) { 65 | return billingInfoBatchRule.toFlux(customers); 66 | } 67 | 68 | private Flux> orderItemsFlux(List customers) { 69 | return orderItemBatchRule.toFlux(customers); 70 | } 71 | 72 | @Test 73 | public void testBatchRuleToMono() { 74 | 75 | StepVerifier.create(billingInfo(customers)) 76 | .expectSubscription() 77 | .expectNext(Map.of( 78 | customer1, billingInfo1, 79 | customer2, billingInfo2, 80 | customer3, billingInfo3)) 81 | .expectComplete() 82 | .verify(); 83 | 84 | StepVerifier.create(orderItems(customers)) 85 | .expectSubscription() 86 | .expectNext(Map.of( 87 | customer1, List.of(orderItem11, orderItem12, orderItem13), 88 | customer2, List.of(orderItem21, orderItem22), 89 | customer3, List.of(orderItem31, orderItem32, orderItem33))) 90 | .expectComplete() 91 | .verify(); 92 | } 93 | 94 | @Test 95 | public void testBatchRuleToFlux() { 96 | 97 | StepVerifier.create(billingInfoFlux(customers)) 98 | .expectSubscription() 99 | .expectNext(billingInfo1, billingInfo2, billingInfo3) 100 | .expectComplete() 101 | .verify(); 102 | 103 | StepVerifier.create(orderItemsFlux(customers)) 104 | .expectSubscription() 105 | .expectNext( 106 | List.of(orderItem11, orderItem12, orderItem13), 107 | List.of(orderItem21, orderItem22), 108 | List.of(orderItem31, orderItem32, orderItem33)) 109 | .expectComplete() 110 | .verify(); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /assembler/src/test/java/io/github/pellse/assembler/test/ChainedAssemblerTest.java: -------------------------------------------------------------------------------- 1 | package io.github.pellse.assembler.test; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import reactor.core.publisher.Flux; 5 | import reactor.test.StepVerifier; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | 10 | import static io.github.pellse.assembler.AssemblerBuilder.assemblerOf; 11 | import static io.github.pellse.assembler.Rule.rule; 12 | import static io.github.pellse.assembler.RuleMapper.oneToMany; 13 | import static io.github.pellse.assembler.RuleMapper.oneToOne; 14 | import static java.time.LocalDateTime.now; 15 | 16 | record AugmentedVitals(Patient patient, HR heartRate, List bloodPressures, String diagnosis) { 17 | AugmentedVitals(Vitals vitals, Diagnosis diagnosis) { 18 | this(vitals.patient(), vitals.heartRate(), vitals.bloodPressures(), diagnosis.diagnosis()); 19 | } 20 | } 21 | 22 | record Vitals(HR heartRate, Patient patient, List bloodPressures) { 23 | } 24 | 25 | record Patient( 26 | Integer id, 27 | String name, 28 | String healthCardNumber) { 29 | } 30 | 31 | record HR( 32 | Long id, 33 | int patientId, 34 | int heartRateValue, 35 | LocalDateTime time) { 36 | } 37 | 38 | record BP( 39 | String id, 40 | int patientId, 41 | int systolic, 42 | int diastolic, 43 | LocalDateTime time) { 44 | } 45 | 46 | record Diagnosis(Integer patientId, String diagnosis) { 47 | } 48 | 49 | public class ChainedAssemblerTest { 50 | 51 | Patient patient1 = new Patient(1, "John Deere", "123456789"); 52 | Patient patient2 = new Patient(2, "Jane Doe", "287334321"); 53 | Patient patient3 = new Patient(3, "Frank Smith Doe", "387654325"); 54 | 55 | HR hr1 = new HR(1L, 1, 60, now()); 56 | HR hr2 = new HR(2L, 2, 70, now()); 57 | HR hr3 = new HR(3L, 3, 80, now()); 58 | 59 | BP bp1_1 = new BP("1", 1, 120, 80, now()); 60 | BP bp1_2 = new BP("2", 1, 130, 85, now()); 61 | 62 | BP bp2_1 = new BP("3", 2, 140, 90, now()); 63 | BP bp2_2 = new BP("4", 2, 150, 95, now()); 64 | 65 | BP bp3_1 = new BP("5", 3, 160, 80, now()); 66 | BP bp3_2 = new BP("6", 3, 170, 85, now()); 67 | 68 | Vitals vitals1 = new Vitals(hr1, patient1, List.of(bp1_1, bp1_2)); 69 | Vitals vitals2 = new Vitals(hr2, patient2, List.of(bp2_1, bp2_2)); 70 | Vitals vitals3 = new Vitals(hr3, patient3, List.of(bp3_1, bp3_2)); 71 | 72 | Diagnosis d1 = new Diagnosis(1, "Healthy"); 73 | Diagnosis d2 = new Diagnosis(2, "Sick"); 74 | Diagnosis d3 = new Diagnosis(3, "Critical"); 75 | 76 | AugmentedVitals av1 = new AugmentedVitals(vitals1, d1); 77 | AugmentedVitals av2 = new AugmentedVitals(vitals2, d2); 78 | AugmentedVitals av3 = new AugmentedVitals(vitals3, d3); 79 | 80 | Flux
heartRateFlux = Flux.just(hr1, hr2, hr3) 81 | .repeat(2); 82 | 83 | List expectedAugmentedVitals = List.of(av1, av2, av3, av1, av2, av3, av1, av2, av3); 84 | 85 | @Test 86 | public void testChainedAssemblers() { 87 | 88 | var vitalsAssembler = assemblerOf(Vitals.class) 89 | .withCorrelationIdResolver(HR::patientId) 90 | .withRules( 91 | rule(Patient::id, oneToOne(this::getPatients)), 92 | rule(BP::patientId, oneToMany(BP::id, this::getBPs)), 93 | Vitals::new) 94 | .build(); 95 | 96 | var augmentedVitalsAssembler = assemblerOf(AugmentedVitals.class) 97 | .withCorrelationIdResolver(Vitals::patient, Patient::id) 98 | .withRules( 99 | rule(Diagnosis::patientId, oneToOne(this::getDiagnoses)), 100 | AugmentedVitals::new) 101 | .build(); 102 | 103 | var combinedAssembler = vitalsAssembler.pipeWith(augmentedVitalsAssembler); 104 | 105 | StepVerifier.create(heartRateFlux 106 | .window(3) 107 | .flatMapSequential(combinedAssembler::assemble)) 108 | .expectSubscription() 109 | .expectNextSequence(expectedAugmentedVitals) 110 | .verifyComplete(); 111 | } 112 | 113 | private Flux getPatients(List
heartRates) { 114 | return Flux.just(patient1, patient2, patient3); 115 | } 116 | 117 | private Flux getBPs(List
heartRates) { 118 | return Flux.just(bp1_1, bp1_2, bp2_1, bp2_2, bp3_1, bp3_2); 119 | } 120 | 121 | private Flux getDiagnoses(List vitalsList) { 122 | return Flux.just(d1, d2, d3); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /assembler/src/test/java/io/github/pellse/assembler/test/CompositeCorrelationIdResolverTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.test; 18 | 19 | import io.github.pellse.assembler.Assembler; 20 | import org.junit.jupiter.api.Test; 21 | import reactor.core.publisher.Flux; 22 | import reactor.test.StepVerifier; 23 | 24 | import java.util.*; 25 | import java.util.stream.Collectors; 26 | 27 | import static io.github.pellse.assembler.AssemblerBuilder.assemblerOf; 28 | import static io.github.pellse.assembler.Rule.rule; 29 | import static io.github.pellse.assembler.RuleMapper.oneToMany; 30 | import static io.github.pellse.assembler.RuleMapper.oneToOne; 31 | import static io.github.pellse.assembler.RuleMapperSource.call; 32 | 33 | public class CompositeCorrelationIdResolverTest { 34 | 35 | record Post(PostDetails post, User author, List replies) { 36 | } 37 | 38 | record PostDetails(Long id, String userId, String content) { 39 | } 40 | 41 | record User(String Id, String username) { 42 | } 43 | 44 | record Reply(Long id, Long postId, String userId, String content) { 45 | } 46 | 47 | // Creating PostDetails records 48 | PostDetails postDetails1 = new PostDetails(100L, "user1", "Content of post 1"); 49 | PostDetails postDetails2 = new PostDetails(200L, "user2", "Content of post 2"); 50 | PostDetails postDetails3 = new PostDetails(300L, "user3", "Content of post 3"); 51 | PostDetails postDetails4 = new PostDetails(400L, "user4", "Content of post 4"); 52 | PostDetails postDetails5 = new PostDetails(500L, "user5", "Content of post 5"); 53 | PostDetails postDetails6 = new PostDetails(600L, "user6", "Content of post 6"); 54 | PostDetails postDetails7 = new PostDetails(700L, "user7", "Content of post 7"); 55 | PostDetails postDetails8 = new PostDetails(800L, "user8", "Content of post 8"); 56 | PostDetails postDetails9 = new PostDetails(900L, "user9", "Content of post 9"); 57 | PostDetails postDetails10 = new PostDetails(1000L, "user10", "Content of post 10"); 58 | 59 | // Creating User records 60 | User user1 = new User("user1", "Alice"); 61 | User user2 = new User("user2", "Bob"); 62 | User user3 = new User("user3", "Charlie"); 63 | User user4 = new User("user4", "David"); 64 | User user5 = new User("user5", "Eve"); 65 | User user6 = new User("user6", "Frank"); 66 | User user7 = new User("user7", "Grace"); 67 | User user8 = new User("user8", "Henry"); 68 | User user9 = new User("user9", "Ivy"); 69 | User user10 = new User("user10", "Jack"); 70 | 71 | // Creating Reply records 72 | Reply reply1_1 = new Reply(1L, 100L, "user2", "Reply 1 to post 1"); 73 | Reply reply1_2 = new Reply(2L, 100L, "user3", "Reply 2 to post 1"); 74 | Reply reply1_3 = new Reply(3L, 100L, "user4", "Reply 3 to post 1"); 75 | 76 | Reply reply2_1 = new Reply(4L, 200L, "user3", "Reply 1 to post 2"); 77 | Reply reply2_2 = new Reply(5L, 200L, "user4", "Reply 2 to post 2"); 78 | Reply reply2_3 = new Reply(6L, 200L, "user5", "Reply 3 to post 2"); 79 | 80 | Reply reply3_1 = new Reply(7L, 300L, "user4", "Reply 1 to post 3"); 81 | Reply reply3_2 = new Reply(8L, 300L, "user5", "Reply 2 to post 3"); 82 | Reply reply3_3 = new Reply(9L, 300L, "user1", "Reply 3 to post 3"); 83 | 84 | Reply reply4_1 = new Reply(10L, 400L, "user5", "Reply 1 to post 4"); 85 | Reply reply4_2 = new Reply(11L, 400L, "user1", "Reply 2 to post 4"); 86 | Reply reply4_3 = new Reply(12L, 400L, "user2", "Reply 3 to post 4"); 87 | 88 | Reply reply5_1 = new Reply(13L, 500L, "user1", "Reply 1 to post 5"); 89 | Reply reply5_2 = new Reply(14L, 500L, "user2", "Reply 2 to post 5"); 90 | Reply reply5_3 = new Reply(15L, 500L, "user3", "Reply 3 to post 5"); 91 | 92 | // Creating additional Reply records 93 | Reply reply6_1 = new Reply(16L, 600L, "user2", "Reply 1 to post 6"); 94 | Reply reply7_1 = new Reply(17L, 700L, "user3", "Reply 1 to post 7"); 95 | Reply reply8_1 = new Reply(18L, 800L, "user4", "Reply 1 to post 8"); 96 | Reply reply9_1 = new Reply(19L, 900L, "user5", "Reply 1 to post 9"); 97 | Reply reply10_1 = new Reply(20L, 1000L, "user6", "Reply 1 to post 10"); 98 | 99 | // Creating Post records with corresponding PostDetails, User, and Replies 100 | Post post1 = new Post(postDetails1, user1, List.of(reply1_1, reply1_2, reply1_3)); 101 | Post post2 = new Post(postDetails2, user2, List.of(reply2_1, reply2_2, reply2_3)); 102 | Post post3 = new Post(postDetails3, user3, List.of(reply3_1, reply3_2, reply3_3)); 103 | Post post4 = new Post(postDetails4, user4, List.of(reply4_1, reply4_2, reply4_3)); 104 | Post post5 = new Post(postDetails5, user5, List.of(reply5_1, reply5_2, reply5_3)); 105 | Post post6 = new Post(postDetails6, user6, List.of(reply6_1)); 106 | Post post7 = new Post(postDetails7, user7, List.of(reply7_1)); 107 | Post post8 = new Post(postDetails8, user8, List.of(reply8_1)); 108 | Post post9 = new Post(postDetails9, user9, List.of()); 109 | Post post10 = new Post(postDetails10, user10, List.of()); 110 | 111 | List expectedPosts = List.of(post1, post2, post3, post4, post5, post6, post7, post8, post9, post10); 112 | 113 | private Flux getPostDetails() { 114 | return Flux.just(postDetails1, postDetails2, postDetails3, postDetails4, postDetails5, postDetails6, postDetails7, postDetails8, postDetails9, postDetails10); 115 | } 116 | 117 | private Flux getUsersById(List userIds) { 118 | return Flux.just(user1, user2, user3, user4, user5, user6, user7, user8, user9, user10) 119 | .filter(u -> userIds.contains(u.Id())); 120 | } 121 | 122 | private Flux getReplies(List postDetails) { 123 | 124 | return getRepliesByPostId(postDetails.stream() 125 | .map(PostDetails::id) 126 | .collect(Collectors.toSet())); 127 | } 128 | 129 | private Flux getRepliesByPostId(Collection postDetailsIds) { 130 | return Flux.just( 131 | reply1_1, reply1_2, reply1_3, 132 | reply2_1, reply2_2, reply2_3, 133 | reply3_1, reply3_2, reply3_3, 134 | reply4_1, reply4_2, reply4_3, 135 | reply5_1, reply5_2, reply5_3, 136 | reply6_1, 137 | reply7_1, 138 | reply8_1 139 | ) 140 | .filter(r -> postDetailsIds.contains(r.postId())); 141 | } 142 | 143 | /** 144 | *

The equivalent SQL query to retrieve the posts, authors, and replies if all data was in a single relation database:

145 | * 146 | *
{@code
147 |      * SELECT
148 |      *     p.id AS post_id,
149 |      *     p.userId AS post_userId,
150 |      *     p.content AS post_content,
151 |      *     u.id AS author_id,
152 |      *     u.username AS author_username,
153 |      *     r.id AS reply_id,
154 |      *     r.postId AS reply_postId,
155 |      *     r.userId AS reply_userId,
156 |      *     r.content AS reply_content
157 |      * FROM
158 |      *     PostDetails p
159 |      * JOIN
160 |      *     User u ON p.userId = u.id
161 |      * LEFT JOIN
162 |      *     Reply r ON p.id = r.postId
163 |      * WHERE
164 |      *     p.id IN (1, 2, 3);
165 |      * }
166 | */ 167 | @Test 168 | public void testWithGetReplies() { 169 | 170 | Assembler assembler = assemblerOf(Post.class) 171 | .withCorrelationIdResolver(PostDetails::id) 172 | .withRules( 173 | rule(User::Id, PostDetails::userId, oneToOne(call(this::getUsersById))), 174 | rule(Reply::postId, oneToMany(Reply::id, this::getReplies)), 175 | Post::new) 176 | .build(); 177 | 178 | StepVerifier.create(getPostDetails() 179 | .window(3) 180 | .flatMapSequential(assembler::assemble)) 181 | .expectSubscription() 182 | .expectNextSequence(expectedPosts) 183 | .expectComplete() 184 | .verify(); 185 | } 186 | 187 | @Test 188 | public void testWithGetRepliesByPostId() { 189 | 190 | Assembler assembler = assemblerOf(Post.class) 191 | .withCorrelationIdResolver(PostDetails::id) 192 | .withRules( 193 | rule(User::Id, PostDetails::userId, oneToOne(call(this::getUsersById))), 194 | rule(Reply::postId, oneToMany(Reply::id, call(this::getRepliesByPostId))), 195 | Post::new) 196 | .build(); 197 | 198 | StepVerifier.create(getPostDetails() 199 | .window(3) 200 | .flatMapSequential(assembler::assemble)) 201 | .expectSubscription() 202 | .expectNextSequence(expectedPosts) 203 | .expectComplete() 204 | .verify(); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /assembler/src/test/java/io/github/pellse/assembler/util/BillingInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.util; 18 | 19 | public record BillingInfo(Integer id, Long customerId, String creditCardNumber) { 20 | 21 | public BillingInfo(Long customerId) { 22 | this(null, customerId); 23 | } 24 | 25 | public BillingInfo(Integer id, Long customerId) { 26 | this(id, customerId, null); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /assembler/src/test/java/io/github/pellse/assembler/util/Customer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.util; 18 | 19 | public record Customer(Long customerId, String name) { 20 | } 21 | -------------------------------------------------------------------------------- /assembler/src/test/java/io/github/pellse/assembler/util/OrderItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.util; 18 | 19 | public record OrderItem(String id, Long customerId, String orderDescription, Double price) { 20 | } 21 | -------------------------------------------------------------------------------- /assembler/src/test/java/io/github/pellse/assembler/util/Transaction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.util; 18 | 19 | import java.util.List; 20 | 21 | public record Transaction(Customer customer, BillingInfo billingInfo, List orderItems) { 22 | } 23 | -------------------------------------------------------------------------------- /assembler/src/test/java/io/github/pellse/assembler/util/TransactionSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2024 Sebastien Pelletier 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.github.pellse.assembler.util; 18 | 19 | import java.util.Set; 20 | 21 | public record TransactionSet(Customer customer, BillingInfo billingInfo, Set orderItems) { } 22 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "java-library" 3 | id "maven-publish" 4 | id "signing" 5 | } 6 | 7 | ext { 8 | reactorVersion = "3.7.5" 9 | } 10 | 11 | subprojects { 12 | apply plugin: "java" 13 | apply plugin: "maven-publish" 14 | apply plugin: "signing" 15 | 16 | version = "0.7.11" 17 | group = "io.github.pellse" 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | 23 | java { 24 | withJavadocJar() 25 | withSourcesJar() 26 | toolchain { 27 | languageVersion = JavaLanguageVersion.of(21) 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation("io.projectreactor:reactor-core:${reactorVersion}") 33 | 34 | testImplementation("org.jspecify:jspecify:1.0.0") 35 | 36 | testImplementation("io.projectreactor:reactor-test:${reactorVersion}") 37 | testImplementation("io.projectreactor.tools:blockhound:1.0.8.RELEASE") 38 | 39 | testImplementation("org.junit.jupiter:junit-jupiter:5.8.1") 40 | testImplementation("org.hamcrest:hamcrest-library:1.3") 41 | } 42 | 43 | tasks.withType(JavaExec).configureEach { 44 | if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_13)) { 45 | jvmArgs += [ 46 | "-XX:+AllowRedefinitionToAddDeleteMethods" 47 | ] 48 | } 49 | } 50 | 51 | tasks.withType(Test).configureEach { 52 | if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_13)) { 53 | jvmArgs += [ 54 | "-XX:+AllowRedefinitionToAddDeleteMethods" 55 | ] 56 | } 57 | } 58 | 59 | test { 60 | useJUnitPlatform { 61 | testLogging { 62 | events("passed", "skipped", "failed") 63 | } 64 | } 65 | 66 | systemProperty 'reactor.schedulers.defaultBoundedElasticOnVirtualThreads', 'true' 67 | systemProperty 'reactor.schedulers.defaultBoundedElasticSize', '1000000' 68 | systemProperty 'reactor.schedulers.defaultBoundedElasticQueueSize', '1000000' 69 | } 70 | 71 | publishing { 72 | publications { 73 | mavenJava(MavenPublication) { 74 | from components.java 75 | 76 | pom { 77 | name = project.name 78 | packaging = "jar" 79 | description = "Small library allowing to efficiently assemble entities from querying/merging external datasources or aggregating microservices" 80 | url = "https://github.com/pellse/assembler" 81 | 82 | scm { 83 | connection = "scm:git@github.com:pellse/assembler.git" 84 | developerConnection = "scm:git@github.com:pellse/assembler.git" 85 | url = "https://github.com/pellse/assembler" 86 | } 87 | 88 | licenses { 89 | license { 90 | name = "The Apache License, Version 2.0" 91 | url = "http://www.apache.org/licenses/LICENSE-2.0.txt" 92 | } 93 | } 94 | 95 | developers { 96 | developer { 97 | name = "Sebastien Pelletier" 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | repositories { 105 | maven { 106 | name = "ossrh" 107 | url = "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 108 | credentials { 109 | username = findProperty("ossrhUsername") 110 | password = findProperty("ossrhPassword") 111 | } 112 | } 113 | } 114 | } 115 | 116 | signing { 117 | sign publishing.publications.mavenJava 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pellse/assembler/48dcb620ed52a0ffa59bb5fde0c37cbeda8ca59c/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https://services.gradle.org/distributions/gradle-8.8-all.zip 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /images/Assembler.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pellse/assembler/48dcb620ed52a0ffa59bb5fde0c37cbeda8ca59c/images/Assembler.mp4 -------------------------------------------------------------------------------- /images/Assembler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pellse/assembler/48dcb620ed52a0ffa59bb5fde0c37cbeda8ca59c/images/Assembler.png -------------------------------------------------------------------------------- /images/AssemblerExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pellse/assembler/48dcb620ed52a0ffa59bb5fde0c37cbeda8ca59c/images/AssemblerExample.png -------------------------------------------------------------------------------- /images/CohereFluxExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pellse/assembler/48dcb620ed52a0ffa59bb5fde0c37cbeda8ca59c/images/CohereFluxExample.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | } 7 | 8 | rootProject.name = "assembler" 9 | 10 | include "assembler", 11 | // "assembler-kotlin-extension", 12 | "assembler-cache-caffeine", 13 | "assembler-spring-cache" 14 | --------------------------------------------------------------------------------