├── .gitignore ├── LICENSE ├── README.md ├── collapse-executor-core ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── icodening │ └── collapse │ └── core │ ├── AbstractCollapseExecutor.java │ ├── AsyncSameOutputCollapseExecutor.java │ ├── BatchCollector.java │ ├── BlockingSameOutputCollapseExecutor.java │ ├── Bundle.java │ ├── CollapseExecutor.java │ ├── CollapseExecutorAsyncSupport.java │ ├── CollapseExecutorBlockingSupport.java │ ├── CollectorListener.java │ ├── EqualsInputGrouper.java │ ├── GroupListeningBatchCollector.java │ ├── Input.java │ ├── InputGrouper.java │ ├── LengthLimitedInputGrouper.java │ ├── ListeningCollector.java │ ├── NamedCollapseExecutor.java │ ├── NoOpInputGrouper.java │ ├── SingleThreadExecutor.java │ ├── SuspendableCollector.java │ ├── ThreadlessExecutor.java │ ├── support │ ├── AsyncCallableGroupCollapseExecutor.java │ ├── BlockingCallableGroupCollapseExecutor.java │ ├── CallableGroup.java │ └── FutureCallableGroupCollapseExecutor.java │ └── util │ ├── CacheableSupplier.java │ ├── ThrowableCallable.java │ └── VirtualThreadExecutorServiceProvider.java ├── collapse-executor-integration ├── collapse-executor-aop │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── icodening │ │ └── collapse │ │ └── aop │ │ ├── AopCallableGroup.java │ │ ├── CollapseMethodInterceptor.java │ │ ├── CollapsibleAnnotationAspect.java │ │ └── annotation │ │ └── Collapsible.java ├── collapse-executor-pattern │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── icodening │ │ └── collapse │ │ └── web │ │ └── pattern │ │ ├── AbstractConfigurationCollapseGroupResolver.java │ │ ├── CollapseDefinitionProperties.java │ │ ├── CollapseGroupDefinition.java │ │ ├── CollapseGroupResolver.java │ │ ├── CollapsePolicyDefinition.java │ │ ├── RegexConfigurationCollapseGroupResolver.java │ │ ├── RequestAttributes.java │ │ └── RequestCollapseGroup.java ├── collapse-executor-servlet │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── icodening │ │ └── collapse │ │ └── web │ │ └── server │ │ ├── AsyncServletExecutor.java │ │ ├── CollapseHttpRequestServletFilter.java │ │ ├── HttpServletRequestAttributes.java │ │ ├── RecordableServletOutputStream.java │ │ ├── RecordableServletResponse.java │ │ ├── ServletCollapseRequest.java │ │ └── ServletCollapseResponse.java ├── collapse-executor-spring-boot-autoconfigure │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── icodening │ │ │ └── collapse │ │ │ └── spring │ │ │ └── boot │ │ │ └── autoconfigure │ │ │ ├── CollapseComponentScanner.java │ │ │ ├── CollapseExecutorAutoConfiguration.java │ │ │ ├── CollapseExecutorProperties.java │ │ │ ├── ComponentScanMark.java │ │ │ ├── ConditionalOnCollapseEnabled.java │ │ │ ├── aop │ │ │ └── CollapseAopAutoConfiguration.java │ │ │ └── web │ │ │ ├── client │ │ │ ├── CollapseHttpRequestInterceptorInitializer.java │ │ │ ├── CollapseRestTemplateAutoConfiguration.java │ │ │ ├── CollapseRestTemplateProperties.java │ │ │ └── reactive │ │ │ │ ├── CollapseWebClientAutoConfiguration.java │ │ │ │ └── CollapseWebClientProperties.java │ │ │ └── server │ │ │ ├── CollapseServletAutoConfiguration.java │ │ │ └── CollapseServletProperties.java │ │ └── resources │ │ └── META-INF │ │ ├── spring.factories │ │ └── spring │ │ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports ├── collapse-executor-spring-web │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── icodening │ │ └── collapse │ │ └── spring │ │ └── web │ │ ├── client │ │ ├── CollapseHttpRequestInterceptor.java │ │ ├── CollapseHttpRequestInterceptorConfigurator.java │ │ ├── RepeatableReadResponse.java │ │ ├── RestTemplateRequestAttributes.java │ │ ├── SameHttpRequestUrlInputGrouper.java │ │ └── reactive │ │ │ ├── CollapseExchangeFilterFunction.java │ │ │ └── WebClientRequestAttributes.java │ │ └── pattern │ │ └── PathPatternCollapseGroupResolver.java └── pom.xml ├── collapse-executor-samples ├── README.md ├── collapse-executor-sample-advanced │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── icodening │ │ └── collapse │ │ └── sample │ │ └── advanced │ │ ├── CollapseExecutorAdvancedExample.java │ │ └── support │ │ ├── CustomAsyncCollapseExecutor.java │ │ ├── CustomBlockingCollapseExecutor.java │ │ ├── UserEntity.java │ │ └── UserService.java ├── collapse-executor-sample-sequence │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── icodening │ │ │ └── collapse │ │ │ └── sample │ │ │ └── sequence │ │ │ ├── SequenceApplication.java │ │ │ ├── generator │ │ │ ├── AbstractRepositorySequenceGenerator.java │ │ │ ├── SequenceGenerator.java │ │ │ └── SimpleJdbcSequenceGenerator.java │ │ │ └── service │ │ │ └── SequenceGeneratorSampleService.java │ │ └── resources │ │ ├── application.yaml │ │ ├── data.sql │ │ └── schema.sql ├── collapse-executor-sample-simple │ ├── README.md │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── icodening │ │ └── collapse │ │ └── sample │ │ └── simple │ │ ├── AsyncCollapseExecutorExample.java │ │ ├── BlockingCollapseExecutorExample.java │ │ └── FutureCollapseExecutorExample.java ├── collapse-executor-sample-spring-boot │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── icodening │ │ │ └── collapse │ │ │ └── sample │ │ │ └── spring │ │ │ └── boot │ │ │ ├── SpringBootSampleApplication.java │ │ │ ├── config │ │ │ └── SampleConfiguration.java │ │ │ ├── controller │ │ │ ├── RestTemplateController.java │ │ │ ├── StressTestController.java │ │ │ ├── UserController.java │ │ │ └── WebClientController.java │ │ │ ├── entity │ │ │ └── UserEntity.java │ │ │ ├── executor │ │ │ ├── AbstractBatchGetExecutor.java │ │ │ ├── RestTemplateBatchGetExecutor.java │ │ │ └── WebClientBatchGetExecutor.java │ │ │ └── service │ │ │ ├── AbstractBlockingCallSample.java │ │ │ ├── RestTemplateCollapseBlockingCallSample.java │ │ │ ├── UserService.java │ │ │ └── WebClientCollapseBlockingCallSample.java │ │ └── resources │ │ └── application.yaml └── pom.xml ├── docs └── images │ ├── collapse-executor-simple.png │ ├── collapse-executor.png │ ├── rest-template-collapse.png │ ├── rest-template-without-collapse.png │ ├── webclient-collapse.png │ ├── webclient-without-collapse.png │ ├── with-collapse.png │ ├── without-collapse-executor.png │ └── without-collapse.png └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | # https://github.com/takari/maven-wrapper#usage-without-binary-jar 11 | .mvn/wrapper/maven-wrapper.jar 12 | .idea 13 | .vscode 14 | # Eclipse m2e generated files 15 | # Eclipse Core 16 | .project 17 | # JDT-specific (Eclipse Java Development Tools) 18 | .classpath 19 | -------------------------------------------------------------------------------- /collapse-executor-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | collapse-executor-parent 22 | com.icodening.collapse 23 | 1.0.0 24 | 25 | 4.0.0 26 | 27 | collapse-executor-core 28 | 29 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/AsyncSameOutputCollapseExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import java.util.List; 19 | import java.util.concurrent.CompletableFuture; 20 | 21 | /** 22 | * @author icodening 23 | * @date 2023.05.21 24 | */ 25 | public abstract class AsyncSameOutputCollapseExecutor extends CollapseExecutorAsyncSupport { 26 | 27 | public AsyncSameOutputCollapseExecutor() { 28 | super(); 29 | } 30 | 31 | public AsyncSameOutputCollapseExecutor(ListeningCollector collector) { 32 | super(collector); 33 | } 34 | 35 | @Override 36 | protected void bindingOutput(OUTPUT batchOutput, List>> bundles) { 37 | for (Bundle> bundle : bundles) { 38 | bundle.bindOutput(CompletableFuture.completedFuture(batchOutput)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/BlockingSameOutputCollapseExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import java.util.List; 19 | 20 | /** 21 | * @author icodening 22 | * @date 2023.05.14 23 | */ 24 | public abstract class BlockingSameOutputCollapseExecutor extends CollapseExecutorBlockingSupport { 25 | 26 | public BlockingSameOutputCollapseExecutor() { 27 | super(); 28 | } 29 | 30 | public BlockingSameOutputCollapseExecutor(ListeningCollector collector) { 31 | super(collector); 32 | } 33 | 34 | @Override 35 | protected void bindingOutput(OUTPUT batchOutput, List> bundles) { 36 | for (Bundle bundle : bundles) { 37 | bundle.bindOutput(batchOutput); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/Bundle.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import java.util.concurrent.CompletableFuture; 19 | import java.util.concurrent.Executor; 20 | 21 | /** 22 | * input output bundle 23 | * 24 | * @author icodening 25 | * @date 2023.05.14 26 | */ 27 | public final class Bundle { 28 | 29 | private final CollapseExecutor collapseExecutor; 30 | 31 | private final INPUT input; 32 | 33 | private volatile OUTPUT output; 34 | 35 | private volatile Throwable throwable; 36 | 37 | private final Executor callbackExecutor; 38 | 39 | private final CompletableFuture listeningResult; 40 | 41 | private volatile boolean completed = false; 42 | 43 | Bundle(CollapseExecutor collapseExecutor, INPUT input, Executor callbackExecutor, CompletableFuture listeningResult) { 44 | this.collapseExecutor = collapseExecutor; 45 | this.input = input; 46 | this.callbackExecutor = callbackExecutor; 47 | this.listeningResult = listeningResult; 48 | } 49 | 50 | CollapseExecutor getCollapseExecutor() { 51 | return collapseExecutor; 52 | } 53 | 54 | public INPUT getInput() { 55 | return input; 56 | } 57 | 58 | public OUTPUT getOutput() { 59 | return output; 60 | } 61 | 62 | public Throwable getThrowable() { 63 | return throwable; 64 | } 65 | 66 | public boolean isCompleted() { 67 | return completed; 68 | } 69 | 70 | public void bindOutput(OUTPUT output) { 71 | this.bindOutput(output, null); 72 | } 73 | 74 | public void bindOutput(OUTPUT output, Throwable throwable) { 75 | if (completed) { 76 | return; 77 | } 78 | completed = true; 79 | if (throwable != null) { 80 | this.throwable = throwable; 81 | callbackExecutor.execute(() -> listeningResult.completeExceptionally(throwable)); 82 | } else { 83 | this.output = output; 84 | callbackExecutor.execute(() -> listeningResult.complete(output)); 85 | } 86 | } 87 | 88 | Executor getCallbackExecutor() { 89 | return callbackExecutor; 90 | } 91 | 92 | CompletableFuture getListeningResult() { 93 | return listeningResult; 94 | } 95 | } -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/CollapseExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | /** 19 | * @author icodening 20 | * @date 2023.05.14 21 | */ 22 | public interface CollapseExecutor { 23 | 24 | OUTPUT execute(INPUT input) throws Throwable; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/CollapseExecutorAsyncSupport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import com.icodening.collapse.core.util.CacheableSupplier; 19 | 20 | import java.util.concurrent.CompletableFuture; 21 | import java.util.concurrent.Executor; 22 | import java.util.function.Supplier; 23 | 24 | /** 25 | * @author icodening 26 | * @date 2023.05.21 27 | */ 28 | public abstract class CollapseExecutorAsyncSupport extends AbstractCollapseExecutor, BATCH_OUTPUT> { 29 | 30 | private Supplier executorSupplier; 31 | 32 | public CollapseExecutorAsyncSupport() { 33 | super(); 34 | } 35 | 36 | public CollapseExecutorAsyncSupport(ListeningCollector collector) { 37 | super(collector); 38 | } 39 | 40 | public void setExecutor(Executor executor) { 41 | this.executorSupplier = () -> executor; 42 | } 43 | 44 | public void setExecutorSupplier(Supplier executorSupplier) { 45 | this.executorSupplier = CacheableSupplier.from(executorSupplier); 46 | } 47 | 48 | @Override 49 | protected Executor getCallbackExecutor() { 50 | return executorSupplier.get(); 51 | } 52 | 53 | @Override 54 | public CompletableFuture execute(INPUT input) { 55 | try { 56 | return super.execute(input); 57 | } catch (Throwable e) { 58 | CompletableFuture result = new CompletableFuture<>(); 59 | result.completeExceptionally(e); 60 | return result; 61 | } 62 | } 63 | 64 | @Override 65 | protected CompletableFuture returning(Bundle> bundle) { 66 | CompletableFuture result = new CompletableFuture<>(); 67 | CompletableFuture> listeningResult = bundle.getListeningResult(); 68 | listeningResult.whenComplete((future, throwable) -> { 69 | if (throwable != null) { 70 | result.completeExceptionally(throwable); 71 | } else { 72 | future.whenComplete((output, ex) -> { 73 | if (ex != null) { 74 | result.completeExceptionally(ex); 75 | } else { 76 | result.complete(output); 77 | } 78 | }); 79 | } 80 | }); 81 | return result; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/CollapseExecutorBlockingSupport.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import java.util.concurrent.CompletableFuture; 19 | import java.util.concurrent.ExecutionException; 20 | import java.util.concurrent.Executor; 21 | 22 | /** 23 | * @author icodening 24 | * @date 2023.05.23 25 | */ 26 | public abstract class CollapseExecutorBlockingSupport extends AbstractCollapseExecutor { 27 | 28 | public CollapseExecutorBlockingSupport() { 29 | super(); 30 | } 31 | 32 | public CollapseExecutorBlockingSupport(ListeningCollector collector) { 33 | super(collector); 34 | } 35 | 36 | @Override 37 | protected final OUTPUT returning(Bundle bundle) throws Throwable { 38 | ThreadlessExecutor threadlessExecutor = (ThreadlessExecutor) bundle.getCallbackExecutor(); 39 | CompletableFuture completableFuture = bundle.getListeningResult(); 40 | try { 41 | while (!completableFuture.isDone()) { 42 | threadlessExecutor.waitAndDrain(); 43 | } 44 | return completableFuture.get(); 45 | } catch (ExecutionException executionException) { 46 | //throw actual exception 47 | throw executionException.getCause(); 48 | } finally { 49 | threadlessExecutor.shutdown(); 50 | } 51 | } 52 | 53 | @Override 54 | protected final Executor getCallbackExecutor() { 55 | return new ThreadlessExecutor(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/CollectorListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import java.util.Collection; 19 | 20 | /** 21 | * @author icodening 22 | * @date 2023.07.04 23 | * @see GroupListeningBatchCollector 24 | */ 25 | @FunctionalInterface 26 | public interface CollectorListener { 27 | 28 | void onCollected(Collection collection); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/EqualsInputGrouper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Collection; 20 | import java.util.function.Supplier; 21 | import java.util.stream.Collectors; 22 | 23 | /** 24 | * @author icodening 25 | * @date 2023.05.14 26 | */ 27 | public class EqualsInputGrouper implements InputGrouper { 28 | 29 | private static final InputGrouper INSTANCE = new EqualsInputGrouper(); 30 | 31 | @SuppressWarnings("unchecked") 32 | public static InputGrouper getInstance() { 33 | return (InputGrouper) INSTANCE; 34 | } 35 | 36 | private EqualsInputGrouper() { 37 | } 38 | 39 | @Override 40 | public Collection>> grouping(Collection> inputs) { 41 | return inputs.stream() 42 | .collect(Collectors.groupingBy( 43 | (Input::value), 44 | Collectors.toCollection((Supplier>>) ArrayList::new) 45 | )) 46 | .values(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/GroupListeningBatchCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Collection; 20 | import java.util.Map; 21 | import java.util.Objects; 22 | import java.util.concurrent.ConcurrentHashMap; 23 | import java.util.concurrent.Executor; 24 | import java.util.function.Function; 25 | import java.util.function.Supplier; 26 | import java.util.stream.Collectors; 27 | 28 | /** 29 | * @author icodening 30 | * @date 2023.05.14 31 | */ 32 | public abstract class GroupListeningBatchCollector extends BatchCollector { 33 | 34 | private final Map> listeners = new ConcurrentHashMap<>(256); 35 | 36 | private final Function classifier; 37 | 38 | public GroupListeningBatchCollector(Function classifier) { 39 | super(); 40 | this.classifier = Objects.requireNonNull(classifier, "classifier must be not null."); 41 | } 42 | 43 | public GroupListeningBatchCollector(Executor dispatcher, 44 | Function classifier) { 45 | super(dispatcher); 46 | this.classifier = Objects.requireNonNull(classifier, "classifier must be not null."); 47 | } 48 | 49 | public void addListener(K key, CollectorListener listener) { 50 | this.listeners.put(key, listener); 51 | } 52 | 53 | public void removeListener(K key) { 54 | this.listeners.remove(key); 55 | } 56 | 57 | @Override 58 | protected void onCollected(Collection elements) { 59 | Map> groups = elements.stream() 60 | .collect(Collectors.groupingBy( 61 | classifier, 62 | Collectors.toCollection((Supplier>) ArrayList::new) 63 | )); 64 | for (Map.Entry> entry : groups.entrySet()) { 65 | K key = entry.getKey(); 66 | Collection collection = entry.getValue(); 67 | CollectorListener listener = this.listeners.get(key); 68 | listener.onCollected(collection); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/Input.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import java.util.HashMap; 19 | import java.util.Map; 20 | 21 | /** 22 | * input holder 23 | * 24 | * @author icodening 25 | * @date 2023.05.14 26 | */ 27 | public class Input { 28 | 29 | private final E input; 30 | 31 | private final Bundle bundle; 32 | 33 | private Map attachments; 34 | 35 | Input(E input, Bundle bundle) { 36 | this.input = input; 37 | this.bundle = bundle; 38 | } 39 | 40 | @SuppressWarnings("unchecked") 41 | Bundle getBundle() { 42 | return (Bundle) bundle; 43 | } 44 | 45 | public E value() { 46 | return input; 47 | } 48 | 49 | private void initAttachmentMap() { 50 | this.attachments = new HashMap<>(8); 51 | } 52 | 53 | @SuppressWarnings("unchecked") 54 | public T getAttachment(String key) { 55 | if (this.attachments == null) { 56 | initAttachmentMap(); 57 | return null; 58 | } 59 | Object value = this.attachments.get(key); 60 | if (value == null) { 61 | return null; 62 | } 63 | return (T) value; 64 | } 65 | 66 | public void setAttachment(String key, Object value) { 67 | if (this.attachments == null) { 68 | initAttachmentMap(); 69 | } 70 | this.attachments.put(key, value); 71 | } 72 | 73 | public void removeAttachment(String key) { 74 | if (this.attachments == null) { 75 | return; 76 | } 77 | this.attachments.remove(key); 78 | } 79 | 80 | public void putAll(Map map) { 81 | if (this.attachments == null) { 82 | initAttachmentMap(); 83 | } 84 | if (map == null) { 85 | this.attachments.clear(); 86 | return; 87 | } 88 | this.attachments.putAll(map); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/InputGrouper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import java.util.Collection; 19 | 20 | /** 21 | * input sharding tool 22 | * 23 | * @author icodening 24 | * @date 2023.05.14 25 | */ 26 | @FunctionalInterface 27 | public interface InputGrouper { 28 | 29 | Collection>> grouping(Collection> inputs); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/LengthLimitedInputGrouper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Collection; 20 | import java.util.Iterator; 21 | import java.util.List; 22 | import java.util.Objects; 23 | 24 | /** 25 | * @author icodening 26 | * @date 2023.05.14 27 | */ 28 | public class LengthLimitedInputGrouper implements InputGrouper { 29 | 30 | private static final int DEFAULT_CHUCK_SIZE = 16; 31 | 32 | private final int chuckSize; 33 | 34 | private final InputGrouper delegate; 35 | 36 | public static InputGrouper newInstance(InputGrouper delegate) { 37 | return newInstance(DEFAULT_CHUCK_SIZE, delegate); 38 | } 39 | 40 | @SuppressWarnings("unchecked") 41 | public static InputGrouper newInstance(int chuckSize, InputGrouper delegate) { 42 | return (InputGrouper) new LengthLimitedInputGrouper(chuckSize, (InputGrouper) delegate); 43 | } 44 | 45 | private LengthLimitedInputGrouper(InputGrouper delegate) { 46 | this(DEFAULT_CHUCK_SIZE, delegate); 47 | } 48 | 49 | private LengthLimitedInputGrouper(int chuckSize, InputGrouper delegate) { 50 | this.chuckSize = chuckSize; 51 | this.delegate = Objects.requireNonNull(delegate, "delegate must be not null."); 52 | } 53 | 54 | @SuppressWarnings("unchecked") 55 | public InputGrouper getDelegate() { 56 | return (InputGrouper) delegate; 57 | } 58 | 59 | @Override 60 | public Collection>> grouping(Collection> inputs) { 61 | Collection>> groups = delegate.grouping(inputs); 62 | List>> result = new ArrayList<>(groups.size()); 63 | for (Collection> group : groups) { 64 | Iterator> iterator = group.iterator(); 65 | int remaining = group.size(); 66 | do { 67 | List> tmp = new ArrayList<>(Math.min(chuckSize, remaining)); 68 | for (int i = 0; i < chuckSize && iterator.hasNext(); i++) { 69 | tmp.add(iterator.next()); 70 | } 71 | result.add(tmp); 72 | remaining -= chuckSize; 73 | } while (iterator.hasNext()); 74 | } 75 | return result; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/ListeningCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import java.util.concurrent.Executor; 19 | 20 | /** 21 | * @author icodening 22 | * @date 2023.05.14 23 | */ 24 | public class ListeningCollector extends GroupListeningBatchCollector, Bundle> { 25 | 26 | public ListeningCollector() { 27 | super(Bundle::getCollapseExecutor); 28 | } 29 | 30 | public ListeningCollector(Executor dispatcher) { 31 | super(dispatcher, Bundle::getCollapseExecutor); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/NamedCollapseExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | /** 19 | * indicator a CollapseExecutor's name 20 | * 21 | * @author icodening 22 | * @date 2023.07.06 23 | */ 24 | public interface NamedCollapseExecutor { 25 | 26 | /** 27 | * get name of the {@link CollapseExecutor} 28 | * 29 | * @return collapse executor's name 30 | */ 31 | default String getName() { 32 | return this.getClass().getSimpleName(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/NoOpInputGrouper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import java.util.Collection; 19 | import java.util.Collections; 20 | 21 | /** 22 | * @author icodening 23 | * @date 2023.05.14 24 | */ 25 | public class NoOpInputGrouper implements InputGrouper { 26 | 27 | private static final InputGrouper INSTANCE = new NoOpInputGrouper(); 28 | 29 | @SuppressWarnings("unchecked") 30 | public static InputGrouper getInstance() { 31 | return (InputGrouper) INSTANCE; 32 | } 33 | 34 | private NoOpInputGrouper() { 35 | } 36 | 37 | @Override 38 | public Collection>> grouping(Collection> inputs) { 39 | return Collections.singletonList(inputs); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/SingleThreadExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import com.icodening.collapse.core.util.VirtualThreadExecutorServiceProvider; 19 | 20 | import java.util.Objects; 21 | import java.util.SortedSet; 22 | import java.util.concurrent.Executor; 23 | import java.util.concurrent.ExecutorService; 24 | import java.util.concurrent.Executors; 25 | import java.util.concurrent.RejectedExecutionException; 26 | import java.util.concurrent.ThreadFactory; 27 | import java.util.concurrent.atomic.AtomicInteger; 28 | import java.util.logging.Logger; 29 | 30 | /** 31 | * @author icodening 32 | * @date 2023.05.14 33 | */ 34 | public class SingleThreadExecutor implements Executor { 35 | 36 | private final ExecutorService delegate; 37 | 38 | private volatile boolean closed = false; 39 | 40 | public SingleThreadExecutor() { 41 | this.delegate = DelegateExecutorServiceProvider.getDelegateExecutorService(); 42 | } 43 | 44 | @Override 45 | public void execute(Runnable command) { 46 | Objects.requireNonNull(command, "command must be not null."); 47 | if (closed) { 48 | throw new RejectedExecutionException("SingleThreadExecutor has been shutdown."); 49 | } 50 | this.delegate.execute(command); 51 | } 52 | 53 | public void shutdown() { 54 | if (closed) { 55 | return; 56 | } 57 | this.closed = true; 58 | this.delegate.shutdown(); 59 | } 60 | 61 | private static class DelegateExecutorServiceProvider { 62 | 63 | private static final String THREAD_PREFIX = "SingleThreadExecutor"; 64 | 65 | private static final ExecutorService DELEGATE_EXECUTOR_SERVICE; 66 | 67 | static { 68 | ExecutorService delegate = null; 69 | if (isJava21()) { 70 | delegate = VirtualThreadExecutorServiceProvider.tryInstantiateVitrualThreadExecutorService(); 71 | if (delegate == null) { 72 | Logger logger = Logger.getLogger(DelegateExecutorServiceProvider.class.getName()); 73 | logger.warning("Current java version is 21+, but initialize 'VirtualThreadPerTaskExecutor' failed, will use default executor instead."); 74 | } 75 | } 76 | if (delegate == null) { 77 | delegate = initGenericExecutorService(); 78 | } 79 | DELEGATE_EXECUTOR_SERVICE = delegate; 80 | } 81 | 82 | private static ExecutorService getDelegateExecutorService() { 83 | return DELEGATE_EXECUTOR_SERVICE; 84 | } 85 | 86 | private DelegateExecutorServiceProvider() { 87 | } 88 | 89 | private static boolean isJava21() { 90 | try { 91 | //'getFirst' since 21 92 | SortedSet.class.getDeclaredMethod("getFirst"); 93 | return true; 94 | } catch (NoSuchMethodException e) { 95 | return false; 96 | } 97 | } 98 | 99 | private static ExecutorService initGenericExecutorService() { 100 | ThreadFactory threadFactory = new ThreadFactory() { 101 | 102 | private final AtomicInteger COUNT = new AtomicInteger(0); 103 | 104 | @Override 105 | public Thread newThread(Runnable r) { 106 | Thread thread = new Thread(r); 107 | thread.setName(THREAD_PREFIX + "-Platform-" + COUNT.incrementAndGet()); 108 | thread.setDaemon(true); 109 | return thread; 110 | } 111 | }; 112 | return Executors.newSingleThreadExecutor(threadFactory); 113 | } 114 | } 115 | } 116 | 117 | 118 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/SuspendableCollector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import java.util.Collection; 19 | import java.util.Iterator; 20 | import java.util.Objects; 21 | import java.util.concurrent.Executor; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | /** 25 | * @author icodening 26 | * @date 2023.05.16 27 | */ 28 | public class SuspendableCollector extends ListeningCollector { 29 | 30 | private static final TimeUnit DEFAULT_UNIT = TimeUnit.MILLISECONDS; 31 | 32 | private volatile int threshold = 0; 33 | 34 | private long duration = 0; 35 | 36 | private TimeUnit timeUnit = DEFAULT_UNIT; 37 | 38 | public SuspendableCollector() { 39 | 40 | } 41 | 42 | public SuspendableCollector(Executor dispatcher) { 43 | super(dispatcher); 44 | } 45 | 46 | public void setThreshold(int threshold) { 47 | this.threshold = threshold; 48 | } 49 | 50 | public void setDuration(long duration) { 51 | this.duration = duration; 52 | } 53 | 54 | public void setTimeUnit(TimeUnit timeUnit) { 55 | this.timeUnit = Objects.requireNonNull(timeUnit, "timeUnit must be not null."); 56 | } 57 | 58 | @Override 59 | protected Collection> onCollecting(Iterator> iterator) { 60 | Collection> bundles = super.onCollecting(iterator); 61 | if (bundles.size() >= threshold) { 62 | return bundles; 63 | } 64 | if (duration == 0) { 65 | Thread.yield(); 66 | } else if (duration > 0) { 67 | pause(duration); 68 | } 69 | bundles.addAll(super.onCollecting(iterator)); 70 | return bundles; 71 | } 72 | 73 | private void pause(long duration) { 74 | try { 75 | timeUnit.sleep(duration); 76 | } catch (InterruptedException ignore) { 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/ThreadlessExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core; 17 | 18 | import java.util.Queue; 19 | import java.util.concurrent.ConcurrentLinkedQueue; 20 | import java.util.concurrent.Executor; 21 | import java.util.concurrent.RejectedExecutionException; 22 | import java.util.concurrent.locks.LockSupport; 23 | 24 | /** 25 | * @author icodening 26 | * @date 2023.05.14 27 | */ 28 | public class ThreadlessExecutor implements Executor { 29 | 30 | private static final Object SHUTDOWN = new Object(); 31 | 32 | private final Queue queue = new ConcurrentLinkedQueue<>(); 33 | 34 | private volatile Object waiter; 35 | 36 | public void waitAndDrain() throws InterruptedException { 37 | throwIfInterrupted(); 38 | Runnable runnable = queue.poll(); 39 | if (runnable == null) { 40 | waiter = Thread.currentThread(); 41 | try { 42 | while ((runnable = queue.poll()) == null) { 43 | LockSupport.park(this); 44 | throwIfInterrupted(); 45 | } 46 | } finally { 47 | waiter = null; 48 | } 49 | } 50 | do { 51 | runnable.run(); 52 | } while ((runnable = queue.poll()) != null); 53 | } 54 | 55 | private static void throwIfInterrupted() throws InterruptedException { 56 | if (Thread.interrupted()) { 57 | throw new InterruptedException(); 58 | } 59 | } 60 | 61 | @Override 62 | public void execute(Runnable runnable) { 63 | queue.add(runnable); 64 | if (waiter != SHUTDOWN) { 65 | LockSupport.unpark((Thread) waiter); 66 | } else if (queue.remove(runnable)) { 67 | throw new RejectedExecutionException(); 68 | } 69 | } 70 | 71 | public void shutdown() { 72 | waiter = SHUTDOWN; 73 | Runnable runnable; 74 | while ((runnable = queue.poll()) != null) { 75 | runnable.run(); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/support/AsyncCallableGroupCollapseExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core.support; 17 | 18 | import com.icodening.collapse.core.AsyncSameOutputCollapseExecutor; 19 | import com.icodening.collapse.core.Input; 20 | import com.icodening.collapse.core.ListeningCollector; 21 | import com.icodening.collapse.core.util.ThrowableCallable; 22 | 23 | import java.util.Collection; 24 | import java.util.concurrent.CompletableFuture; 25 | import java.util.concurrent.Executor; 26 | 27 | /** 28 | * @author icodening 29 | * @date 2023.05.21 30 | */ 31 | public class AsyncCallableGroupCollapseExecutor { 32 | 33 | private final InternalSameOutputCollapseExecutorAsyncCallableGroupCollapseExecutor collapseExecutor; 34 | 35 | public AsyncCallableGroupCollapseExecutor() { 36 | this.collapseExecutor = new InternalSameOutputCollapseExecutorAsyncCallableGroupCollapseExecutor<>(); 37 | this.collapseExecutor.setName(this.getClass().getSimpleName()); 38 | } 39 | 40 | public AsyncCallableGroupCollapseExecutor(ListeningCollector collector) { 41 | this.collapseExecutor = new InternalSameOutputCollapseExecutorAsyncCallableGroupCollapseExecutor<>(collector); 42 | this.collapseExecutor.setName(this.getClass().getSimpleName()); 43 | } 44 | 45 | public void setExecutor(Executor executor) { 46 | this.collapseExecutor.setExecutor(executor); 47 | } 48 | 49 | public void setName(String name) { 50 | this.collapseExecutor.setName(name); 51 | } 52 | 53 | @SuppressWarnings("unchecked") 54 | public CompletableFuture execute(Object group, ThrowableCallable callable) { 55 | CallableGroup callableGroup = new CallableGroup<>(group, callable); 56 | return (CompletableFuture) collapseExecutor.execute((CallableGroup) callableGroup); 57 | } 58 | 59 | private static class InternalSameOutputCollapseExecutorAsyncCallableGroupCollapseExecutor extends AsyncSameOutputCollapseExecutor, R> { 60 | 61 | public InternalSameOutputCollapseExecutorAsyncCallableGroupCollapseExecutor() { 62 | super(); 63 | } 64 | 65 | private InternalSameOutputCollapseExecutorAsyncCallableGroupCollapseExecutor(ListeningCollector collector) { 66 | super(collector); 67 | } 68 | 69 | @Override 70 | protected R doExecute(Collection>> inputs) throws Throwable { 71 | return inputs.iterator().next().value().getCallable().call(); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/support/BlockingCallableGroupCollapseExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core.support; 17 | 18 | import com.icodening.collapse.core.BlockingSameOutputCollapseExecutor; 19 | import com.icodening.collapse.core.Input; 20 | import com.icodening.collapse.core.ListeningCollector; 21 | import com.icodening.collapse.core.util.ThrowableCallable; 22 | 23 | import java.util.Collection; 24 | 25 | /** 26 | * @author icodening 27 | * @date 2023.05.14 28 | */ 29 | public class BlockingCallableGroupCollapseExecutor { 30 | 31 | private final InternalBlockingCallableGroupCollapseExecutor collapseExecutor; 32 | 33 | public BlockingCallableGroupCollapseExecutor() { 34 | this.collapseExecutor = new InternalBlockingCallableGroupCollapseExecutor<>(); 35 | this.collapseExecutor.setName(this.getClass().getSimpleName()); 36 | } 37 | 38 | public BlockingCallableGroupCollapseExecutor(ListeningCollector collector) { 39 | this.collapseExecutor = new InternalBlockingCallableGroupCollapseExecutor<>(collector); 40 | this.collapseExecutor.setName(this.getClass().getSimpleName()); 41 | } 42 | 43 | public void setName(String name) { 44 | this.collapseExecutor.setName(name); 45 | } 46 | 47 | @SuppressWarnings("unchecked") 48 | public R execute(Object group, ThrowableCallable callable) throws Throwable { 49 | CallableGroup callableGroup = new CallableGroup<>(group, callable); 50 | return (R) collapseExecutor.execute((CallableGroup) callableGroup); 51 | } 52 | 53 | private static class InternalBlockingCallableGroupCollapseExecutor extends BlockingSameOutputCollapseExecutor, R> { 54 | 55 | public InternalBlockingCallableGroupCollapseExecutor() { 56 | super(); 57 | } 58 | 59 | private InternalBlockingCallableGroupCollapseExecutor(ListeningCollector collector) { 60 | super(collector); 61 | } 62 | 63 | @Override 64 | protected R doExecute(Collection>> inputs) throws Throwable { 65 | return inputs.iterator().next().value().getCallable().call(); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/support/CallableGroup.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core.support; 17 | 18 | import com.icodening.collapse.core.util.ThrowableCallable; 19 | 20 | import java.util.Objects; 21 | 22 | /** 23 | * @author icodening 24 | * @date 2023.05.22 25 | */ 26 | class CallableGroup { 27 | 28 | private final Object group; 29 | 30 | private final ThrowableCallable callable; 31 | 32 | CallableGroup(Object group, ThrowableCallable callable) { 33 | this.group = group; 34 | this.callable = callable; 35 | } 36 | 37 | public Object getGroup() { 38 | return group; 39 | } 40 | 41 | public ThrowableCallable getCallable() { 42 | return callable; 43 | } 44 | 45 | @Override 46 | public boolean equals(Object o) { 47 | if (this == o) return true; 48 | if (o == null || getClass() != o.getClass()) return false; 49 | CallableGroup that = (CallableGroup) o; 50 | return Objects.equals(group, that.group); 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return Objects.hash(group); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/support/FutureCallableGroupCollapseExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core.support; 17 | 18 | import com.icodening.collapse.core.Bundle; 19 | import com.icodening.collapse.core.CollapseExecutorAsyncSupport; 20 | import com.icodening.collapse.core.Input; 21 | import com.icodening.collapse.core.ListeningCollector; 22 | import com.icodening.collapse.core.util.ThrowableCallable; 23 | 24 | import java.util.Collection; 25 | import java.util.List; 26 | import java.util.concurrent.CompletableFuture; 27 | import java.util.concurrent.Executor; 28 | 29 | /** 30 | * @author icodening 31 | * @date 2023.06.09 32 | */ 33 | public class FutureCallableGroupCollapseExecutor { 34 | 35 | private static final Executor DIRECT_EXECUTOR = Runnable::run; 36 | 37 | private final InternalFutureCallableGroupCollapseExecutor collapseExecutor; 38 | 39 | public FutureCallableGroupCollapseExecutor() { 40 | this.collapseExecutor = new InternalFutureCallableGroupCollapseExecutor<>(); 41 | this.collapseExecutor.setName(this.getClass().getSimpleName()); 42 | setExecutor(DIRECT_EXECUTOR); 43 | } 44 | 45 | public FutureCallableGroupCollapseExecutor(ListeningCollector collector) { 46 | this.collapseExecutor = new InternalFutureCallableGroupCollapseExecutor<>(collector); 47 | this.collapseExecutor.setName(this.getClass().getSimpleName()); 48 | setExecutor(DIRECT_EXECUTOR); 49 | } 50 | 51 | public void setExecutor(Executor executor) { 52 | this.collapseExecutor.setExecutor(executor); 53 | } 54 | 55 | public void setName(String name) { 56 | this.collapseExecutor.setName(name); 57 | } 58 | 59 | @SuppressWarnings("all") 60 | public CompletableFuture execute(Object group, ThrowableCallable> callable) { 61 | CallableGroup> callableGroup = new CallableGroup<>(group, callable); 62 | return (CompletableFuture) collapseExecutor.execute((CallableGroup) callableGroup); 63 | } 64 | 65 | private static class InternalFutureCallableGroupCollapseExecutor extends CollapseExecutorAsyncSupport>, R, CompletableFuture> { 66 | 67 | public InternalFutureCallableGroupCollapseExecutor() { 68 | super(); 69 | } 70 | 71 | private InternalFutureCallableGroupCollapseExecutor(ListeningCollector collector) { 72 | super(collector); 73 | } 74 | 75 | @Override 76 | protected CompletableFuture doExecute(Collection>>> inputs) throws Throwable { 77 | return inputs.iterator().next().value().getCallable().call(); 78 | } 79 | 80 | @Override 81 | protected void bindingOutput(CompletableFuture responseFuture, List>, CompletableFuture>> bundles) { 82 | for (Bundle>, CompletableFuture> bundle : bundles) { 83 | bundle.bindOutput(responseFuture); 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/util/CacheableSupplier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core.util; 17 | 18 | import java.util.function.Supplier; 19 | 20 | /** 21 | * @author icodening 22 | * @date 2023.05.22 23 | */ 24 | public class CacheableSupplier implements Supplier { 25 | 26 | private final Supplier supplier; 27 | 28 | private volatile T object; 29 | 30 | private volatile boolean initialized = false; 31 | 32 | private CacheableSupplier(Supplier supplier) { 33 | this.supplier = supplier; 34 | } 35 | 36 | public static Supplier from(Supplier supplier) { 37 | return new CacheableSupplier<>(supplier); 38 | } 39 | 40 | @Override 41 | public T get() { 42 | if (initialized) { 43 | return object; 44 | } 45 | synchronized (this) { 46 | if (!initialized) { 47 | this.object = supplier.get(); 48 | this.initialized = true; 49 | } 50 | } 51 | return object; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/util/ThrowableCallable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core.util; 17 | 18 | /** 19 | * @author icodening 20 | * @date 2023.07.06 21 | */ 22 | @FunctionalInterface 23 | public interface ThrowableCallable { 24 | 25 | V call() throws Throwable; 26 | } 27 | -------------------------------------------------------------------------------- /collapse-executor-core/src/main/java/com/icodening/collapse/core/util/VirtualThreadExecutorServiceProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.core.util; 17 | 18 | import java.util.concurrent.ExecutorService; 19 | import java.util.concurrent.Executors; 20 | 21 | /** 22 | * @author icodening 23 | * @date 2024.02.03 24 | */ 25 | public abstract class VirtualThreadExecutorServiceProvider { 26 | 27 | private VirtualThreadExecutorServiceProvider() { 28 | } 29 | 30 | public static ExecutorService tryInstantiateVitrualThreadExecutorService() { 31 | return initVirtualThreadExecutorService(); 32 | } 33 | 34 | private static ExecutorService initVirtualThreadExecutorService() { 35 | try { 36 | return (ExecutorService) Executors.class 37 | .getDeclaredMethod("newVirtualThreadPerTaskExecutor") 38 | .invoke(null); 39 | } catch (Throwable ignored) { 40 | return null; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-aop/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | collapse-executor-integration 22 | com.icodening.collapse 23 | 1.0.0 24 | 25 | 4.0.0 26 | 27 | collapse-executor-aop 28 | 29 | 30 | 1.0 31 | 32 | 33 | 34 | 35 | com.icodening.collapse 36 | collapse-executor-core 37 | ${project.version} 38 | 39 | 40 | aopalliance 41 | aopalliance 42 | ${aopalliance.version} 43 | 44 | 45 | org.aspectj 46 | aspectjrt 47 | 48 | 49 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-aop/src/main/java/com/icodening/collapse/aop/AopCallableGroup.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.aop; 17 | 18 | import java.lang.reflect.Method; 19 | import java.util.Arrays; 20 | import java.util.Objects; 21 | 22 | /** 23 | * @author icodening 24 | * @date 2023.07.07 25 | */ 26 | public class AopCallableGroup { 27 | 28 | private final Class declaringClass; 29 | 30 | private final Method method; 31 | 32 | private final Object[] arguments; 33 | 34 | private final Object target; 35 | 36 | public AopCallableGroup(Method method, Object[] arguments, Object target) { 37 | this.declaringClass = method.getDeclaringClass(); 38 | this.method = method; 39 | this.arguments = arguments; 40 | this.target = target; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) return true; 46 | if (o == null || getClass() != o.getClass()) return false; 47 | AopCallableGroup that = (AopCallableGroup) o; 48 | return declaringClass.equals(that.declaringClass) && method.equals(that.method) && Arrays.equals(arguments, that.arguments) && target.equals(that.target); 49 | } 50 | 51 | @Override 52 | public int hashCode() { 53 | int result = Objects.hash(declaringClass, method, target); 54 | result = 31 * result + Arrays.hashCode(arguments); 55 | return result; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-aop/src/main/java/com/icodening/collapse/aop/CollapseMethodInterceptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.aop; 17 | 18 | import com.icodening.collapse.core.support.BlockingCallableGroupCollapseExecutor; 19 | import org.aopalliance.intercept.MethodInterceptor; 20 | import org.aopalliance.intercept.MethodInvocation; 21 | 22 | import java.lang.reflect.Method; 23 | 24 | /** 25 | * @author icodening 26 | * @date 2023.07.06 27 | */ 28 | public class CollapseMethodInterceptor implements MethodInterceptor { 29 | 30 | private BlockingCallableGroupCollapseExecutor collapseExecutor; 31 | 32 | public CollapseMethodInterceptor() { 33 | } 34 | 35 | public CollapseMethodInterceptor(BlockingCallableGroupCollapseExecutor collapseExecutor) { 36 | this.collapseExecutor = collapseExecutor; 37 | } 38 | 39 | public void setCollapseExecutor(BlockingCallableGroupCollapseExecutor collapseExecutor) { 40 | this.collapseExecutor = collapseExecutor; 41 | } 42 | 43 | @Override 44 | public Object invoke(MethodInvocation invocation) throws Throwable { 45 | BlockingCallableGroupCollapseExecutor collapseExecutor = this.collapseExecutor; 46 | if (collapseExecutor == null) { 47 | return invocation.proceed(); 48 | } 49 | Method method = invocation.getMethod(); 50 | Object[] arguments = invocation.getArguments(); 51 | Object target = invocation.getThis(); 52 | AopCallableGroup group = new AopCallableGroup(method, arguments, target); 53 | return collapseExecutor.execute(group, invocation::proceed); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-aop/src/main/java/com/icodening/collapse/aop/CollapsibleAnnotationAspect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.aop; 17 | 18 | import com.icodening.collapse.core.support.BlockingCallableGroupCollapseExecutor; 19 | import org.aspectj.lang.JoinPoint; 20 | import org.aspectj.lang.ProceedingJoinPoint; 21 | import org.aspectj.lang.annotation.Around; 22 | import org.aspectj.lang.annotation.Aspect; 23 | import org.aspectj.lang.annotation.Pointcut; 24 | import org.aspectj.lang.reflect.MethodSignature; 25 | 26 | import java.lang.reflect.Method; 27 | 28 | /** 29 | * @author icodening 30 | * @date 2023.07.06 31 | */ 32 | @Aspect 33 | public class CollapsibleAnnotationAspect { 34 | 35 | private BlockingCallableGroupCollapseExecutor collapseExecutor; 36 | 37 | public CollapsibleAnnotationAspect() { 38 | } 39 | 40 | public CollapsibleAnnotationAspect(BlockingCallableGroupCollapseExecutor collapseExecutor) { 41 | this.collapseExecutor = collapseExecutor; 42 | } 43 | 44 | public void setCollapseExecutor(BlockingCallableGroupCollapseExecutor collapseExecutor) { 45 | this.collapseExecutor = collapseExecutor; 46 | } 47 | 48 | @Pointcut(value = "@within(cn.icodening.collapse.aop.annotation.Collapsible) || @annotation(cn.icodening.collapse.aop.annotation.Collapsible)") 49 | public void collapsiblePointcut() { 50 | 51 | } 52 | 53 | @Around(value = "collapsiblePointcut()") 54 | public Object collapseExecute(ProceedingJoinPoint pjp) throws Throwable { 55 | BlockingCallableGroupCollapseExecutor collapseExecutor = this.collapseExecutor; 56 | if (collapseExecutor == null) { 57 | return pjp.proceed(); 58 | } 59 | String kind = pjp.getKind(); 60 | if (!JoinPoint.METHOD_EXECUTION.equals(kind)) { 61 | return pjp.proceed(); 62 | } 63 | MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); 64 | Method method = methodSignature.getMethod(); 65 | Object[] arguments = pjp.getArgs(); 66 | AopCallableGroup group = new AopCallableGroup(method, arguments, pjp.getTarget()); 67 | return collapseExecutor.execute(group, pjp::proceed); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-aop/src/main/java/com/icodening/collapse/aop/annotation/Collapsible.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.aop.annotation; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Inherited; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * @author icodening 26 | * @date 2023.07.06 27 | */ 28 | @Inherited 29 | @Retention(RetentionPolicy.RUNTIME) 30 | @Target({ElementType.TYPE, ElementType.METHOD}) 31 | public @interface Collapsible { 32 | } 33 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-pattern/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | collapse-executor-integration 22 | com.icodening.collapse 23 | 1.0.0 24 | 25 | 4.0.0 26 | 27 | collapse-executor-pattern 28 | 29 | 30 | 31 | com.icodening.collapse 32 | collapse-executor-core 33 | ${project.version} 34 | 35 | 36 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-pattern/src/main/java/com/icodening/collapse/web/pattern/CollapseDefinitionProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.web.pattern; 17 | 18 | import java.util.ArrayList; 19 | import java.util.LinkedHashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | /** 24 | * @author icodening 25 | * @date 2023.06.29 26 | */ 27 | public class CollapseDefinitionProperties { 28 | 29 | private boolean enabled = false; 30 | 31 | private Map collapsePolicies = new LinkedHashMap<>(); 32 | 33 | private List collapseGroups = new ArrayList<>(); 34 | 35 | public CollapseDefinitionProperties() { 36 | this.collapsePolicies.put("*", CollapsePolicyDefinition.DEFAULT_POLICY); 37 | } 38 | 39 | public boolean isEnabled() { 40 | return enabled; 41 | } 42 | 43 | public void setEnabled(boolean enabled) { 44 | this.enabled = enabled; 45 | } 46 | 47 | public Map getCollapsePolicies() { 48 | return collapsePolicies; 49 | } 50 | 51 | public void setCollapsePolicies(Map collapsePolicies) { 52 | this.collapsePolicies = collapsePolicies; 53 | } 54 | 55 | public List getCollapseGroups() { 56 | return collapseGroups; 57 | } 58 | 59 | public void setCollapseGroups(List collapseGroups) { 60 | this.collapseGroups = collapseGroups; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-pattern/src/main/java/com/icodening/collapse/web/pattern/CollapseGroupDefinition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.web.pattern; 17 | 18 | import java.util.LinkedHashSet; 19 | import java.util.Set; 20 | 21 | /** 22 | * @author icodening 23 | * @date 2023.06.29 24 | */ 25 | public class CollapseGroupDefinition { 26 | 27 | private String collapsePolicyName; 28 | 29 | private Set patterns = new LinkedHashSet<>(); 30 | 31 | public String getCollapsePolicyName() { 32 | return collapsePolicyName; 33 | } 34 | 35 | public void setCollapsePolicyName(String collapsePolicyName) { 36 | this.collapsePolicyName = collapsePolicyName; 37 | } 38 | 39 | public Set getPatterns() { 40 | return patterns; 41 | } 42 | 43 | public void setPatterns(Set patterns) { 44 | this.patterns = patterns; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-pattern/src/main/java/com/icodening/collapse/web/pattern/CollapseGroupResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.web.pattern; 17 | 18 | /** 19 | * @author icodening 20 | * @date 2023.06.29 21 | */ 22 | public interface CollapseGroupResolver { 23 | 24 | /** 25 | * resolve collapse group from request attributes 26 | * @param requestAttributes request attributes, include http url、http method、http header 27 | * @return null or collapse group, return null when not resolved any collapse group. 28 | */ 29 | RequestCollapseGroup resolve(RequestAttributes requestAttributes); 30 | } 31 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-pattern/src/main/java/com/icodening/collapse/web/pattern/CollapsePolicyDefinition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.web.pattern; 17 | 18 | import java.util.HashSet; 19 | import java.util.Set; 20 | 21 | /** 22 | * @author icodening 23 | * @date 2023.06.29 24 | */ 25 | public class CollapsePolicyDefinition { 26 | 27 | public static final CollapsePolicyDefinition DEFAULT_POLICY = new CollapsePolicyDefinition(); 28 | 29 | private Set collapseRequestHeaders = new HashSet<>(); 30 | 31 | private Set collapseRequestQueries = new HashSet<>(); 32 | 33 | public Set getCollapseRequestHeaders() { 34 | return collapseRequestHeaders; 35 | } 36 | 37 | public void setCollapseRequestHeaders(Set collapseRequestHeaders) { 38 | this.collapseRequestHeaders = collapseRequestHeaders; 39 | } 40 | 41 | public Set getCollapseRequestQueries() { 42 | return collapseRequestQueries; 43 | } 44 | 45 | public void setCollapseRequestQueries(Set collapseRequestQueries) { 46 | this.collapseRequestQueries = collapseRequestQueries; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-pattern/src/main/java/com/icodening/collapse/web/pattern/RegexConfigurationCollapseGroupResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.web.pattern; 17 | 18 | import java.net.URI; 19 | import java.util.regex.Pattern; 20 | 21 | /** 22 | * @author icodening 23 | * @date 2023.07.16 24 | */ 25 | public class RegexConfigurationCollapseGroupResolver extends AbstractConfigurationCollapseGroupResolver { 26 | 27 | public RegexConfigurationCollapseGroupResolver() { 28 | } 29 | 30 | public RegexConfigurationCollapseGroupResolver(CollapseDefinitionProperties collapseDefinitionProperties) { 31 | super(collapseDefinitionProperties); 32 | } 33 | 34 | @Override 35 | protected boolean matches(RequestAttributes request, String pattern) { 36 | URI uri = request.getURI(); 37 | String path = uri.getPath(); 38 | return Pattern.matches(pattern, path); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-pattern/src/main/java/com/icodening/collapse/web/pattern/RequestAttributes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.web.pattern; 17 | 18 | import java.net.URI; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | /** 23 | * @author icodening 24 | * @date 2023.06.29 25 | */ 26 | public interface RequestAttributes { 27 | 28 | /** 29 | * get request method type. 30 | * e.g. GET POST PUT .... 31 | * 32 | * @return request method 33 | */ 34 | String getMethod(); 35 | 36 | /** 37 | * get request uri 38 | * 39 | * @return uri 40 | */ 41 | URI getURI(); 42 | 43 | /** 44 | * get request header, it's a case insensitive map. 45 | * 46 | * @return request header. 47 | */ 48 | Map> getHeaders(); 49 | 50 | } 51 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-pattern/src/main/java/com/icodening/collapse/web/pattern/RequestCollapseGroup.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.web.pattern; 17 | 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.Objects; 22 | import java.util.TreeMap; 23 | 24 | /** 25 | * @author icodening 26 | * @date 2023.06.29 27 | */ 28 | public class RequestCollapseGroup { 29 | 30 | private String identifier; 31 | 32 | private String method; 33 | 34 | private String path; 35 | 36 | private Map> headers = new TreeMap<>(String::compareToIgnoreCase); 37 | 38 | private Map> queries = new HashMap<>(); 39 | 40 | public String getIdentifier() { 41 | return identifier; 42 | } 43 | 44 | public void setIdentifier(String identifier) { 45 | this.identifier = identifier; 46 | } 47 | 48 | public String getMethod() { 49 | return method; 50 | } 51 | 52 | public void setMethod(String method) { 53 | this.method = method; 54 | } 55 | 56 | public String getPath() { 57 | return path; 58 | } 59 | 60 | public void setPath(String path) { 61 | this.path = path; 62 | } 63 | 64 | public Map> getHeaders() { 65 | return headers; 66 | } 67 | 68 | public void setHeaders(Map> headers) { 69 | this.headers = headers; 70 | } 71 | 72 | public Map> getQueries() { 73 | return queries; 74 | } 75 | 76 | public void setQueries(Map> queries) { 77 | this.queries = queries; 78 | } 79 | 80 | @Override 81 | public boolean equals(Object o) { 82 | if (this == o) return true; 83 | if (o == null || getClass() != o.getClass()) return false; 84 | RequestCollapseGroup that = (RequestCollapseGroup) o; 85 | return Objects.equals(identifier, that.identifier) && Objects.equals(method, that.method) && Objects.equals(path, that.path) && Objects.equals(headers, that.headers) && Objects.equals(queries, that.queries); 86 | } 87 | 88 | @Override 89 | public int hashCode() { 90 | return Objects.hash(identifier, method, path, headers, queries); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-servlet/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | collapse-executor-integration 22 | com.icodening.collapse 23 | 1.0.0 24 | 25 | 4.0.0 26 | 27 | collapse-executor-servlet 28 | 29 | 30 | 31 | com.icodening.collapse 32 | collapse-executor-pattern 33 | ${project.version} 34 | 35 | 36 | 37 | jakarta.servlet 38 | jakarta.servlet-api 39 | provided 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-servlet/src/main/java/com/icodening/collapse/web/server/HttpServletRequestAttributes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.web.server; 17 | 18 | import com.icodening.collapse.web.pattern.RequestAttributes; 19 | 20 | import javax.servlet.http.HttpServletRequest; 21 | import java.net.URI; 22 | import java.util.ArrayList; 23 | import java.util.Enumeration; 24 | import java.util.List; 25 | import java.util.Map; 26 | import java.util.Objects; 27 | import java.util.TreeMap; 28 | 29 | /** 30 | * @author icodening 31 | * @date 2023.06.29 32 | */ 33 | public class HttpServletRequestAttributes implements RequestAttributes { 34 | 35 | private final String httpMethod; 36 | 37 | private final URI uri; 38 | 39 | private final Map> httpHeaders; 40 | 41 | public HttpServletRequestAttributes(final HttpServletRequest httpServletRequest) { 42 | Objects.requireNonNull(httpServletRequest, "httpServletRequest must be not null."); 43 | this.httpMethod = httpServletRequest.getMethod(); 44 | this.uri = createURI(httpServletRequest); 45 | this.httpHeaders = resolveHttpHeaders(httpServletRequest); 46 | } 47 | 48 | private URI createURI(HttpServletRequest httpServletRequest) { 49 | String url = httpServletRequest.getRequestURL().toString(); 50 | String queryString = httpServletRequest.getQueryString(); 51 | String query = queryString == null ? "" : "?" + queryString; 52 | String fullURI = url + query; 53 | return URI.create(fullURI); 54 | } 55 | 56 | @Override 57 | public String getMethod() { 58 | return httpMethod; 59 | } 60 | 61 | @Override 62 | public URI getURI() { 63 | return uri; 64 | } 65 | 66 | @Override 67 | public Map> getHeaders() { 68 | return httpHeaders; 69 | } 70 | 71 | private static Map> resolveHttpHeaders(HttpServletRequest httpServletRequest) { 72 | Map> headers = new TreeMap<>(String::compareToIgnoreCase); 73 | Enumeration headerNames = httpServletRequest.getHeaderNames(); 74 | while (headerNames.hasMoreElements()) { 75 | String headerName = headerNames.nextElement(); 76 | Enumeration headerValues = httpServletRequest.getHeaders(headerName); 77 | while (headerValues.hasMoreElements()) { 78 | String headerValue = headerValues.nextElement(); 79 | headers.computeIfAbsent(headerName, (key) -> new ArrayList<>()).add(headerValue); 80 | } 81 | } 82 | return headers; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-servlet/src/main/java/com/icodening/collapse/web/server/RecordableServletOutputStream.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.web.server; 17 | 18 | import javax.servlet.ServletOutputStream; 19 | import javax.servlet.WriteListener; 20 | import java.io.ByteArrayOutputStream; 21 | import java.io.IOException; 22 | 23 | /** 24 | * @author icodening 25 | * @date 2023.05.20 26 | */ 27 | public class RecordableServletOutputStream extends ServletOutputStream { 28 | 29 | private final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(512); 30 | 31 | private final ServletOutputStream outputStream; 32 | 33 | public RecordableServletOutputStream(ServletOutputStream outputStream) { 34 | this.outputStream = outputStream; 35 | } 36 | 37 | @Override 38 | public boolean isReady() { 39 | return true; 40 | } 41 | 42 | @Override 43 | public void setWriteListener(WriteListener listener) { 44 | this.outputStream.setWriteListener(listener); 45 | } 46 | 47 | @Override 48 | public void write(int b) throws IOException { 49 | byteArrayOutputStream.write(b); 50 | outputStream.write(b); 51 | } 52 | 53 | @Override 54 | public void flush() throws IOException { 55 | super.flush(); 56 | outputStream.flush(); 57 | } 58 | 59 | @Override 60 | public void close() throws IOException { 61 | super.close(); 62 | outputStream.close(); 63 | } 64 | 65 | public byte[] getRecordBytes() { 66 | return this.byteArrayOutputStream.toByteArray(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-servlet/src/main/java/com/icodening/collapse/web/server/RecordableServletResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.web.server; 17 | 18 | import javax.servlet.http.HttpServletResponse; 19 | import javax.servlet.http.HttpServletResponseWrapper; 20 | import java.io.IOException; 21 | import java.io.PrintWriter; 22 | 23 | /** 24 | * @author icodening 25 | * @date 2023.05.20 26 | */ 27 | class RecordableServletResponse extends HttpServletResponseWrapper { 28 | 29 | private final RecordableServletOutputStream recordableServletOutputStream; 30 | private final PrintWriter writer; 31 | 32 | public RecordableServletResponse(HttpServletResponse response) throws IOException { 33 | super(response); 34 | this.recordableServletOutputStream = new RecordableServletOutputStream(response.getOutputStream()); 35 | this.writer = new PrintWriter(recordableServletOutputStream, true); 36 | } 37 | 38 | @Override 39 | public RecordableServletOutputStream getOutputStream() throws IOException { 40 | return recordableServletOutputStream; 41 | } 42 | 43 | @Override 44 | public PrintWriter getWriter() throws IOException { 45 | return writer; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-servlet/src/main/java/com/icodening/collapse/web/server/ServletCollapseRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.web.server; 17 | 18 | import com.icodening.collapse.web.pattern.RequestCollapseGroup; 19 | 20 | import javax.servlet.AsyncContext; 21 | import java.util.Objects; 22 | 23 | /** 24 | * @author icodening 25 | * @date 2023.05.22 26 | */ 27 | public class ServletCollapseRequest { 28 | 29 | private final RequestCollapseGroup groupKey; 30 | 31 | private final AsyncContext asyncContext; 32 | 33 | public ServletCollapseRequest(RequestCollapseGroup groupKey, AsyncContext asyncContext) { 34 | this.groupKey = groupKey; 35 | this.asyncContext = asyncContext; 36 | } 37 | 38 | public RequestCollapseGroup getGroupKey() { 39 | return groupKey; 40 | } 41 | 42 | public AsyncContext getAsyncContext() { 43 | return asyncContext; 44 | } 45 | 46 | @Override 47 | public boolean equals(Object o) { 48 | if (this == o) return true; 49 | if (o == null || getClass() != o.getClass()) return false; 50 | ServletCollapseRequest that = (ServletCollapseRequest) o; 51 | return groupKey.equals(that.groupKey); 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | return Objects.hash(groupKey); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-servlet/src/main/java/com/icodening/collapse/web/server/ServletCollapseResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.web.server; 17 | 18 | import javax.servlet.http.HttpServletResponse; 19 | 20 | /** 21 | * @author icodening 22 | * @date 2023.05.22 23 | */ 24 | public class ServletCollapseResponse { 25 | 26 | private final HttpServletResponse response; 27 | 28 | private final RecordableServletOutputStream recordableServletOutputStream; 29 | 30 | public ServletCollapseResponse(HttpServletResponse response, RecordableServletOutputStream recordableServletOutputStream) { 31 | this.response = response; 32 | this.recordableServletOutputStream = recordableServletOutputStream; 33 | } 34 | 35 | public HttpServletResponse getResponse() { 36 | return response; 37 | } 38 | 39 | public RecordableServletOutputStream getRecordableServletOutputStream() { 40 | return recordableServletOutputStream; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-boot-autoconfigure/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | collapse-executor-integration 22 | com.icodening.collapse 23 | 1.0.0 24 | 25 | 4.0.0 26 | 27 | collapse-executor-spring-boot-autoconfigure 28 | 29 | 30 | 31 | com.icodening.collapse 32 | collapse-executor-aop 33 | ${project.version} 34 | 35 | 36 | com.icodening.collapse 37 | collapse-executor-spring-web 38 | ${project.version} 39 | 40 | 41 | com.icodening.collapse 42 | collapse-executor-servlet 43 | ${project.version} 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-web 49 | compile 50 | true 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-webflux 55 | compile 56 | true 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-undertow 61 | compile 62 | true 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-starter-jetty 67 | compile 68 | true 69 | 70 | 71 | org.springframework.boot 72 | spring-boot-configuration-processor 73 | compile 74 | true 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-boot-autoconfigure/src/main/java/com/icodening/collapse/spring/boot/autoconfigure/CollapseComponentScanner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.boot.autoconfigure; 17 | 18 | import org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter; 19 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 20 | import org.springframework.context.annotation.ComponentScan; 21 | import org.springframework.context.annotation.FilterType; 22 | 23 | /** 24 | * @author icodening 25 | * @date 2023.05.24 26 | */ 27 | @ConfigurationPropertiesScan(basePackageClasses = ComponentScanMark.class) 28 | @ComponentScan(basePackageClasses = ComponentScanMark.class, 29 | excludeFilters = @ComponentScan.Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)) 30 | class CollapseComponentScanner { 31 | } 32 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-boot-autoconfigure/src/main/java/com/icodening/collapse/spring/boot/autoconfigure/ComponentScanMark.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.boot.autoconfigure; 17 | 18 | /** 19 | * Mark the package that needs to be scanned 20 | * 21 | * @author icodening 22 | * @date 2023.06.06 23 | */ 24 | interface ComponentScanMark { 25 | } 26 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-boot-autoconfigure/src/main/java/com/icodening/collapse/spring/boot/autoconfigure/ConditionalOnCollapseEnabled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.boot.autoconfigure; 17 | 18 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 19 | 20 | import java.lang.annotation.Documented; 21 | import java.lang.annotation.ElementType; 22 | import java.lang.annotation.Inherited; 23 | import java.lang.annotation.Retention; 24 | import java.lang.annotation.RetentionPolicy; 25 | import java.lang.annotation.Target; 26 | 27 | /** 28 | * @author icodening 29 | * @date 2023.05.25 30 | */ 31 | @Target(ElementType.TYPE) 32 | @Retention(RetentionPolicy.RUNTIME) 33 | @Documented 34 | @Inherited 35 | @ConditionalOnProperty(prefix = "collapse.executor", name = "enabled", havingValue = "true", matchIfMissing = true) 36 | public @interface ConditionalOnCollapseEnabled { 37 | } 38 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-boot-autoconfigure/src/main/java/com/icodening/collapse/spring/boot/autoconfigure/aop/CollapseAopAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.boot.autoconfigure.aop; 17 | 18 | import com.icodening.collapse.aop.CollapseMethodInterceptor; 19 | import com.icodening.collapse.aop.CollapsibleAnnotationAspect; 20 | import com.icodening.collapse.core.support.BlockingCallableGroupCollapseExecutor; 21 | import com.icodening.collapse.spring.boot.autoconfigure.ConditionalOnCollapseEnabled; 22 | import org.aopalliance.intercept.MethodInterceptor; 23 | import org.aspectj.lang.JoinPoint; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 25 | import org.springframework.context.annotation.Bean; 26 | import org.springframework.context.annotation.Configuration; 27 | 28 | /** 29 | * @author icodening 30 | * @date 2023.07.07 31 | */ 32 | @ConditionalOnCollapseEnabled 33 | @Configuration(proxyBeanMethods = false) 34 | public class CollapseAopAutoConfiguration { 35 | 36 | @ConditionalOnClass({JoinPoint.class, CollapsibleAnnotationAspect.class}) 37 | static class AspectAutoConfiguration { 38 | 39 | @Bean 40 | public CollapsibleAnnotationAspect collapsibleAnnotationAspect(BlockingCallableGroupCollapseExecutor collapseExecutor) { 41 | return new CollapsibleAnnotationAspect(collapseExecutor); 42 | } 43 | } 44 | 45 | @ConditionalOnClass({MethodInterceptor.class, CollapseMethodInterceptor.class}) 46 | static class AopallianceAutoConfiguration { 47 | 48 | @Bean 49 | public CollapseMethodInterceptor collapseMethodInterceptor(BlockingCallableGroupCollapseExecutor collapseExecutor) { 50 | return new CollapseMethodInterceptor(collapseExecutor); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-boot-autoconfigure/src/main/java/com/icodening/collapse/spring/boot/autoconfigure/web/client/CollapseHttpRequestInterceptorInitializer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.boot.autoconfigure.web.client; 17 | 18 | import com.icodening.collapse.spring.web.client.CollapseHttpRequestInterceptorConfigurator; 19 | import org.springframework.beans.factory.SmartInitializingSingleton; 20 | import org.springframework.context.ApplicationContext; 21 | import org.springframework.context.support.ApplicationObjectSupport; 22 | import org.springframework.web.client.RestTemplate; 23 | 24 | import java.util.Arrays; 25 | import java.util.Collections; 26 | import java.util.List; 27 | import java.util.Map; 28 | import java.util.logging.Logger; 29 | 30 | /** 31 | * @author icodening 32 | * @date 2023.10.26 33 | */ 34 | public class CollapseHttpRequestInterceptorInitializer extends ApplicationObjectSupport implements SmartInitializingSingleton { 35 | 36 | private static final Logger LOGGER = Logger.getLogger(CollapseHttpRequestInterceptorInitializer.class.getName()); 37 | 38 | private final CollapseHttpRequestInterceptorConfigurator collapseHttpRequestInterceptorConfigurator; 39 | 40 | private List restTemplateBeanNames = Collections.emptyList(); 41 | 42 | public CollapseHttpRequestInterceptorInitializer(CollapseHttpRequestInterceptorConfigurator collapseHttpRequestInterceptorConfigurator) { 43 | this.collapseHttpRequestInterceptorConfigurator = collapseHttpRequestInterceptorConfigurator; 44 | } 45 | 46 | public void setCandidateRestTemplateBeanNames(List restTemplateBeanNames) { 47 | this.restTemplateBeanNames = restTemplateBeanNames; 48 | } 49 | 50 | @Override 51 | public void afterSingletonsInstantiated() { 52 | ApplicationContext applicationContext = obtainApplicationContext(); 53 | Map restTemplates = applicationContext.getBeansOfType(RestTemplate.class); 54 | LOGGER.info("Found RestTemplate bean names. " + Arrays.toString(restTemplates.keySet().toArray(new String[0]))); 55 | for (String restTemplateBeanName : restTemplateBeanNames) { 56 | RestTemplate restTemplate = restTemplates.get(restTemplateBeanName); 57 | if (restTemplate == null) { 58 | LOGGER.warning(String.format("[%s] RestTemplate not exists, will be ignore initialization.", restTemplateBeanName)); 59 | continue; 60 | } 61 | collapseHttpRequestInterceptorConfigurator.configurer(restTemplate); 62 | LOGGER.info(String.format("RestTemplate [%s] configurer completed.", restTemplateBeanName)); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-boot-autoconfigure/src/main/java/com/icodening/collapse/spring/boot/autoconfigure/web/client/CollapseRestTemplateAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.boot.autoconfigure.web.client; 17 | 18 | import com.icodening.collapse.core.ListeningCollector; 19 | import com.icodening.collapse.core.support.BlockingCallableGroupCollapseExecutor; 20 | import com.icodening.collapse.spring.boot.autoconfigure.ConditionalOnCollapseEnabled; 21 | import com.icodening.collapse.spring.web.client.CollapseHttpRequestInterceptor; 22 | import com.icodening.collapse.spring.web.client.CollapseHttpRequestInterceptorConfigurator; 23 | import com.icodening.collapse.spring.web.pattern.PathPatternCollapseGroupResolver; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 25 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 26 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 27 | import org.springframework.context.annotation.Bean; 28 | import org.springframework.context.annotation.Configuration; 29 | import org.springframework.web.client.RestTemplate; 30 | 31 | import java.util.List; 32 | 33 | /** 34 | * @author icodening 35 | * @date 2023.05.25 36 | */ 37 | @ConditionalOnClass(RestTemplate.class) 38 | @ConditionalOnCollapseEnabled 39 | @Configuration(proxyBeanMethods = false) 40 | public class CollapseRestTemplateAutoConfiguration { 41 | 42 | @Bean 43 | @ConditionalOnMissingBean(CollapseHttpRequestInterceptor.class) 44 | public CollapseHttpRequestInterceptor collapseHttpRequestInterceptor(ListeningCollector listeningCollector, 45 | CollapseRestTemplateProperties collapseRestTemplateProperties) { 46 | CollapseHttpRequestInterceptor collapseHttpRequestInterceptor = new CollapseHttpRequestInterceptor(); 47 | collapseHttpRequestInterceptor.setCollapseGroupResolver(new PathPatternCollapseGroupResolver(collapseRestTemplateProperties)); 48 | collapseHttpRequestInterceptor.setBlockingCallableGroupCollapseExecutor(new BlockingCallableGroupCollapseExecutor(listeningCollector)); 49 | return collapseHttpRequestInterceptor; 50 | } 51 | 52 | @Bean 53 | @ConditionalOnBean(CollapseHttpRequestInterceptor.class) 54 | public CollapseHttpRequestInterceptorConfigurator collapseHttpRequestInterceptorConfigurer(CollapseHttpRequestInterceptor collapseHttpRequestInterceptor) { 55 | return new CollapseHttpRequestInterceptorConfigurator(collapseHttpRequestInterceptor); 56 | } 57 | 58 | @Bean 59 | @ConditionalOnBean(CollapseHttpRequestInterceptorConfigurator.class) 60 | public CollapseHttpRequestInterceptorInitializer collapseHttpRequestInterceptorInitializer(CollapseRestTemplateProperties collapseRestTemplateProperties, 61 | CollapseHttpRequestInterceptorConfigurator configurator) { 62 | List applyBeans = collapseRestTemplateProperties.getApplyBeanNames(); 63 | CollapseHttpRequestInterceptorInitializer initializer = new CollapseHttpRequestInterceptorInitializer(configurator); 64 | initializer.setCandidateRestTemplateBeanNames(applyBeans); 65 | return initializer; 66 | } 67 | 68 | } 69 | 70 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-boot-autoconfigure/src/main/java/com/icodening/collapse/spring/boot/autoconfigure/web/client/CollapseRestTemplateProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.boot.autoconfigure.web.client; 17 | 18 | import com.icodening.collapse.web.pattern.CollapseDefinitionProperties; 19 | import org.springframework.boot.context.properties.ConfigurationProperties; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | /** 25 | * @author icodening 26 | * @date 2023.06.29 27 | */ 28 | @ConfigurationProperties(prefix = "collapse.executor.rest-template") 29 | public class CollapseRestTemplateProperties extends CollapseDefinitionProperties { 30 | 31 | /** 32 | * Specify the RestTemplate bean that needs to take effect 33 | */ 34 | private List applyBeanNames = new ArrayList<>(); 35 | 36 | public List getApplyBeanNames() { 37 | return applyBeanNames; 38 | } 39 | 40 | public void setApplyBeanNames(List applyBeanNames) { 41 | this.applyBeanNames = applyBeanNames; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-boot-autoconfigure/src/main/java/com/icodening/collapse/spring/boot/autoconfigure/web/client/reactive/CollapseWebClientAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.boot.autoconfigure.web.client.reactive; 17 | 18 | import com.icodening.collapse.core.ListeningCollector; 19 | import com.icodening.collapse.core.support.FutureCallableGroupCollapseExecutor; 20 | import com.icodening.collapse.spring.boot.autoconfigure.ConditionalOnCollapseEnabled; 21 | import com.icodening.collapse.spring.web.client.reactive.CollapseExchangeFilterFunction; 22 | import com.icodening.collapse.spring.web.pattern.PathPatternCollapseGroupResolver; 23 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 24 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 25 | import org.springframework.context.annotation.Bean; 26 | import org.springframework.context.annotation.Configuration; 27 | import org.springframework.web.reactive.function.client.WebClient; 28 | 29 | /** 30 | * @author icodening 31 | * @date 2023.06.23 32 | */ 33 | @Configuration(proxyBeanMethods = false) 34 | @ConditionalOnCollapseEnabled 35 | @ConditionalOnClass(WebClient.class) 36 | public class CollapseWebClientAutoConfiguration { 37 | 38 | @Bean 39 | @ConditionalOnMissingBean(CollapseExchangeFilterFunction.class) 40 | public CollapseExchangeFilterFunction collapseExchangeFilterFunction(ListeningCollector listeningCollector, 41 | CollapseWebClientProperties collapseWebClientProperties) { 42 | CollapseExchangeFilterFunction collapseExchangeFilterFunction = new CollapseExchangeFilterFunction(); 43 | collapseExchangeFilterFunction.setCollapseGroupResolver(new PathPatternCollapseGroupResolver(collapseWebClientProperties)); 44 | collapseExchangeFilterFunction.setFutureCallableGroupCollapseExecutor(new FutureCallableGroupCollapseExecutor(listeningCollector)); 45 | return collapseExchangeFilterFunction; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-boot-autoconfigure/src/main/java/com/icodening/collapse/spring/boot/autoconfigure/web/client/reactive/CollapseWebClientProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.boot.autoconfigure.web.client.reactive; 17 | 18 | import com.icodening.collapse.web.pattern.CollapseDefinitionProperties; 19 | import org.springframework.boot.context.properties.ConfigurationProperties; 20 | 21 | /** 22 | * @author icodening 23 | * @date 2023.06.29 24 | */ 25 | @ConfigurationProperties(prefix = "collapse.executor.web-client") 26 | public class CollapseWebClientProperties extends CollapseDefinitionProperties { 27 | 28 | } 29 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-boot-autoconfigure/src/main/java/com/icodening/collapse/spring/boot/autoconfigure/web/server/CollapseServletProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.boot.autoconfigure.web.server; 17 | 18 | import com.icodening.collapse.web.pattern.CollapseDefinitionProperties; 19 | import org.springframework.boot.context.properties.ConfigurationProperties; 20 | 21 | /** 22 | * @author icodening 23 | * @date 2023.05.23 24 | */ 25 | @ConfigurationProperties(prefix = "collapse.executor.servlet") 26 | public class CollapseServletProperties extends CollapseDefinitionProperties { 27 | 28 | private int batchSize = 32; 29 | 30 | public int getBatchSize() { 31 | return batchSize; 32 | } 33 | 34 | public void setBatchSize(int batchSize) { 35 | this.batchSize = batchSize; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.icodening.collapse.spring.boot.autoconfigure.CollapseComponentScanner,\ 3 | com.icodening.collapse.spring.boot.autoconfigure.CollapseExecutorAutoConfiguration,\ 4 | com.icodening.collapse.spring.boot.autoconfigure.aop.CollapseAopAutoConfiguration,\ 5 | com.icodening.collapse.spring.boot.autoconfigure.web.server.CollapseServletAutoConfiguration,\ 6 | com.icodening.collapse.spring.boot.autoconfigure.web.client.reactive.CollapseWebClientAutoConfiguration,\ 7 | com.icodening.collapse.spring.boot.autoconfigure.web.client.CollapseRestTemplateAutoConfiguration -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports: -------------------------------------------------------------------------------- 1 | com.icodening.collapse.spring.boot.autoconfigure.CollapseComponentScanner 2 | com.icodening.collapse.spring.boot.autoconfigure.CollapseExecutorAutoConfiguration 3 | com.icodening.collapse.spring.boot.autoconfigure.aop.CollapseAopAutoConfiguration 4 | com.icodening.collapse.spring.boot.autoconfigure.web.server.CollapseServletAutoConfiguration 5 | com.icodening.collapse.spring.boot.autoconfigure.web.client.reactive.CollapseWebClientAutoConfiguration 6 | com.icodening.collapse.spring.boot.autoconfigure.web.client.CollapseRestTemplateAutoConfiguration -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | collapse-executor-integration 22 | com.icodening.collapse 23 | 1.0.0 24 | 25 | 4.0.0 26 | 27 | collapse-executor-spring-web 28 | 29 | 30 | 31 | com.icodening.collapse 32 | collapse-executor-core 33 | ${project.version} 34 | 35 | 36 | com.icodening.collapse 37 | collapse-executor-pattern 38 | ${project.version} 39 | 40 | 41 | 42 | org.springframework 43 | spring-webflux 44 | provided 45 | 46 | 47 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-web/src/main/java/com/icodening/collapse/spring/web/client/CollapseHttpRequestInterceptorConfigurator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.web.client; 17 | 18 | import org.springframework.http.client.ClientHttpRequestInterceptor; 19 | import org.springframework.util.Assert; 20 | import org.springframework.web.client.RestTemplate; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Arrays; 24 | import java.util.List; 25 | 26 | /** 27 | * @author icodening 28 | * @date 2023.10.26 29 | */ 30 | public class CollapseHttpRequestInterceptorConfigurator { 31 | 32 | private final CollapseHttpRequestInterceptor collapseHttpRequestInterceptor; 33 | 34 | public CollapseHttpRequestInterceptorConfigurator(CollapseHttpRequestInterceptor collapseHttpRequestInterceptor) { 35 | Assert.notNull(collapseHttpRequestInterceptor, "collapseHttpRequestInterceptor must be not null."); 36 | this.collapseHttpRequestInterceptor = collapseHttpRequestInterceptor; 37 | } 38 | 39 | public void configurer(RestTemplate... restTemplate) { 40 | configurer(Arrays.asList(restTemplate)); 41 | } 42 | 43 | public void configurer(List restTemplates) { 44 | doConfigurer(restTemplates, collapseHttpRequestInterceptor); 45 | } 46 | 47 | private void doConfigurer(List restTemplates, 48 | CollapseHttpRequestInterceptor collapseHttpRequestInterceptor) { 49 | for (RestTemplate restTemplate : restTemplates) { 50 | List interceptors = new ArrayList<>(restTemplate.getInterceptors()); 51 | interceptors.add(collapseHttpRequestInterceptor); 52 | restTemplate.setInterceptors(interceptors); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-web/src/main/java/com/icodening/collapse/spring/web/client/RepeatableReadResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.web.client; 17 | 18 | import org.springframework.http.HttpHeaders; 19 | import org.springframework.http.HttpStatus; 20 | import org.springframework.http.client.ClientHttpResponse; 21 | 22 | import java.io.ByteArrayInputStream; 23 | import java.io.InputStream; 24 | 25 | /** 26 | * @author icodening 27 | * @date 2023.05.16 28 | */ 29 | class RepeatableReadResponse implements ClientHttpResponse { 30 | 31 | private final int rawStatusCode; 32 | 33 | private final HttpStatus statusCode; 34 | 35 | private final String statusText; 36 | 37 | private final HttpHeaders headers; 38 | 39 | private final byte[] body; 40 | 41 | RepeatableReadResponse(int rawStatusCode, HttpStatus statusCode, String statusText, HttpHeaders headers, byte[] body) { 42 | this.rawStatusCode = rawStatusCode; 43 | this.statusCode = statusCode; 44 | this.statusText = statusText; 45 | this.headers = headers; 46 | this.body = body; 47 | } 48 | 49 | @Override 50 | public void close() { 51 | //No op 52 | } 53 | 54 | @Override 55 | public int getRawStatusCode() { 56 | return rawStatusCode; 57 | } 58 | 59 | @Override 60 | public HttpStatus getStatusCode() { 61 | return statusCode; 62 | } 63 | 64 | @Override 65 | public String getStatusText() { 66 | return statusText; 67 | } 68 | 69 | @Override 70 | public HttpHeaders getHeaders() { 71 | return headers; 72 | } 73 | 74 | @Override 75 | public InputStream getBody() { 76 | return new ByteArrayInputStream(body); 77 | } 78 | } -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-web/src/main/java/com/icodening/collapse/spring/web/client/RestTemplateRequestAttributes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.web.client; 17 | 18 | import com.icodening.collapse.web.pattern.RequestAttributes; 19 | import org.springframework.http.HttpHeaders; 20 | import org.springframework.http.HttpRequest; 21 | import org.springframework.util.Assert; 22 | 23 | import java.net.URI; 24 | 25 | /** 26 | * @author icodening 27 | * @date 2023.06.29 28 | */ 29 | public class RestTemplateRequestAttributes implements RequestAttributes { 30 | 31 | private final HttpRequest request; 32 | 33 | public RestTemplateRequestAttributes(HttpRequest request) { 34 | Assert.notNull(request, "request must be not null."); 35 | this.request = request; 36 | } 37 | 38 | @Override 39 | public String getMethod() { 40 | if (request.getMethod() == null) { 41 | return null; 42 | } 43 | return request.getMethod().name(); 44 | } 45 | 46 | @Override 47 | public URI getURI() { 48 | return request.getURI(); 49 | } 50 | 51 | @Override 52 | public HttpHeaders getHeaders() { 53 | return request.getHeaders(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-web/src/main/java/com/icodening/collapse/spring/web/client/SameHttpRequestUrlInputGrouper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.web.client; 17 | 18 | import com.icodening.collapse.core.Input; 19 | import com.icodening.collapse.core.InputGrouper; 20 | import org.springframework.http.RequestEntity; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Collection; 24 | import java.util.function.Supplier; 25 | import java.util.stream.Collectors; 26 | 27 | /** 28 | * @author icodening 29 | * @date 2023.05.19 30 | */ 31 | public class SameHttpRequestUrlInputGrouper implements InputGrouper> { 32 | 33 | private static final InputGrouper INSTANCE = new SameHttpRequestUrlInputGrouper(); 34 | 35 | @SuppressWarnings("unchecked") 36 | public static InputGrouper getInstance() { 37 | return (InputGrouper) INSTANCE; 38 | } 39 | 40 | @Override 41 | public Collection>>> grouping(Collection>> inputs) { 42 | return inputs.stream() 43 | .collect(Collectors.groupingBy( 44 | //eg. GET http://localhost:8080/users/1 45 | (input) -> input.value().getMethod() + " " + input.value().getUrl(), 46 | Collectors.toCollection((Supplier>>>) ArrayList::new) 47 | )) 48 | .values(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-web/src/main/java/com/icodening/collapse/spring/web/client/reactive/WebClientRequestAttributes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.web.client.reactive; 17 | 18 | import com.icodening.collapse.web.pattern.RequestAttributes; 19 | import org.springframework.http.HttpHeaders; 20 | import org.springframework.util.Assert; 21 | import org.springframework.web.reactive.function.client.ClientRequest; 22 | 23 | import java.net.URI; 24 | 25 | /** 26 | * @author icodening 27 | * @date 2023.06.29 28 | */ 29 | public class WebClientRequestAttributes implements RequestAttributes { 30 | 31 | private final ClientRequest clientRequest; 32 | 33 | public WebClientRequestAttributes(ClientRequest clientRequest) { 34 | Assert.notNull(clientRequest, "clientRequest must be not null."); 35 | this.clientRequest = clientRequest; 36 | } 37 | 38 | @Override 39 | public String getMethod() { 40 | return clientRequest.method().name(); 41 | } 42 | 43 | @Override 44 | public URI getURI() { 45 | return clientRequest.url(); 46 | } 47 | 48 | @Override 49 | public HttpHeaders getHeaders() { 50 | return clientRequest.headers(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /collapse-executor-integration/collapse-executor-spring-web/src/main/java/com/icodening/collapse/spring/web/pattern/PathPatternCollapseGroupResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.spring.web.pattern; 17 | 18 | import com.icodening.collapse.web.pattern.AbstractConfigurationCollapseGroupResolver; 19 | import com.icodening.collapse.web.pattern.CollapseDefinitionProperties; 20 | import com.icodening.collapse.web.pattern.RequestAttributes; 21 | import org.springframework.http.server.PathContainer; 22 | import org.springframework.web.util.pattern.PathPatternParser; 23 | 24 | /** 25 | * @author icodening 26 | * @date 2023.07.15 27 | * @see PathPatternParser 28 | */ 29 | public class PathPatternCollapseGroupResolver extends AbstractConfigurationCollapseGroupResolver { 30 | 31 | private static final PathPatternParser PATTERN_PARSER = PathPatternParser.defaultInstance; 32 | 33 | public PathPatternCollapseGroupResolver() { 34 | super(); 35 | } 36 | 37 | public PathPatternCollapseGroupResolver(CollapseDefinitionProperties collapseDefinitionProperties) { 38 | super(collapseDefinitionProperties); 39 | } 40 | 41 | @Override 42 | protected boolean matches(RequestAttributes request, String pattern) { 43 | String path = request.getURI().getPath(); 44 | return PATTERN_PARSER.parse(pattern) 45 | .matches(PathContainer.parsePath(path)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /collapse-executor-integration/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | collapse-executor-parent 22 | com.icodening.collapse 23 | 1.0.0 24 | 25 | 4.0.0 26 | 27 | collapse-executor-integration 28 | pom 29 | 30 | collapse-executor-spring-web 31 | collapse-executor-aop 32 | collapse-executor-spring-boot-autoconfigure 33 | collapse-executor-servlet 34 | collapse-executor-pattern 35 | 36 | 37 | 38 | 2.4.13 39 | 40 | 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-dependencies 46 | ${spring.boot.version} 47 | pom 48 | import 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /collapse-executor-samples/README.md: -------------------------------------------------------------------------------- 1 | # collapse-executor-samples 2 | 该模块为折叠执行器的使用例子 3 | 4 | # 1. collapse-executor-sample-simple 5 | 包含折叠执行器的简单使用例子 6 | 7 | # 2. collapse-executor-sample-advanced 8 | 包含折叠执行器的高级特性使用例子 9 | 10 | # 3. collapse-executor-sample-spring-boot 11 | 包含折叠执行器与Spring Boot集成后的使用例子 12 | 13 | # 4. collapse-executor-sample-sequence 14 | 使用折叠执行器封装一个简单且高性能的序号生成器例子 -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-advanced/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | collapse-executor-samples 22 | com.icodening.collapse 23 | 1.0.0 24 | 25 | 4.0.0 26 | 27 | collapse-executor-sample-advanced 28 | 29 | 30 | 31 | com.icodening.collapse 32 | collapse-executor-core 33 | ${project.version} 34 | 35 | 36 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-advanced/src/main/java/com/icodening/collapse/sample/advanced/CollapseExecutorAdvancedExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.advanced; 17 | 18 | import com.icodening.collapse.core.SuspendableCollector; 19 | import com.icodening.collapse.sample.advanced.support.CustomBlockingCollapseExecutor; 20 | import com.icodening.collapse.sample.advanced.support.UserEntity; 21 | import com.icodening.collapse.sample.advanced.support.UserService; 22 | 23 | import java.util.concurrent.CountDownLatch; 24 | import java.util.concurrent.ExecutorService; 25 | import java.util.concurrent.Executors; 26 | 27 | /** 28 | * @author icodening 29 | * @date 2023.06.27 30 | */ 31 | public class CollapseExecutorAdvancedExample { 32 | 33 | private static final ExecutorService BIZ_EXECUTOR_SERVICE = Executors.newFixedThreadPool(50, r -> { 34 | Thread thread = new Thread(r); 35 | thread.setDaemon(true); 36 | return thread; 37 | }); 38 | 39 | public static void main(String[] args) throws InterruptedException { 40 | SuspendableCollector listenableCollector = new SuspendableCollector(); 41 | CustomBlockingCollapseExecutor customBlockingCollapseExecutor = new CustomBlockingCollapseExecutor(listenableCollector, new UserService()); 42 | customBlockingCollapseExecutor.setBatchSize(5); 43 | //query id [1,13] 44 | int max = 13; 45 | CountDownLatch blockingWaiter = new CountDownLatch(max); 46 | for (long i = 1; i <= max; i++) { 47 | Long queryId = i; 48 | BIZ_EXECUTOR_SERVICE.execute(() -> { 49 | try { 50 | UserEntity userEntity = customBlockingCollapseExecutor.execute(queryId); 51 | System.out.println(Thread.currentThread().getName() + " query id [" + queryId + "], result is:" + userEntity); 52 | } catch (Throwable e) { 53 | e.printStackTrace(); 54 | } finally { 55 | blockingWaiter.countDown(); 56 | } 57 | }); 58 | } 59 | blockingWaiter.await(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-advanced/src/main/java/com/icodening/collapse/sample/advanced/support/CustomAsyncCollapseExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.advanced.support; 17 | 18 | import com.icodening.collapse.core.Bundle; 19 | import com.icodening.collapse.core.CollapseExecutorAsyncSupport; 20 | import com.icodening.collapse.core.Input; 21 | import com.icodening.collapse.core.LengthLimitedInputGrouper; 22 | import com.icodening.collapse.core.ListeningCollector; 23 | import com.icodening.collapse.core.NoOpInputGrouper; 24 | 25 | import java.util.Collection; 26 | import java.util.HashMap; 27 | import java.util.List; 28 | import java.util.Map; 29 | import java.util.concurrent.CompletableFuture; 30 | import java.util.concurrent.ExecutorService; 31 | import java.util.stream.Collectors; 32 | 33 | /** 34 | * @author icodening 35 | * @date 2023.06.27 36 | */ 37 | public class CustomAsyncCollapseExecutor extends CollapseExecutorAsyncSupport> { 38 | 39 | /** 40 | * mock remote service 41 | */ 42 | private final UserService userService; 43 | 44 | public CustomAsyncCollapseExecutor(ListeningCollector collector, UserService userService) { 45 | super(collector); 46 | this.userService = userService; 47 | this.setBatchSize(Integer.MAX_VALUE); 48 | } 49 | 50 | public void setBatchSize(int size) { 51 | this.setInputGrouper(LengthLimitedInputGrouper.newInstance(size, NoOpInputGrouper.getInstance())); 52 | } 53 | 54 | public void setAsyncExecutor(ExecutorService executorService) { 55 | this.setExecutor(executorService); 56 | } 57 | 58 | @Override 59 | protected Map doExecute(Collection> inputs) { 60 | //merge inputs and batch fetch 61 | List ids = inputs.stream().map(Input::value).collect(Collectors.toList()); 62 | List userList = userService.query(ids); 63 | System.out.println("[" + Thread.currentThread().getName() + "] async batch query ids:" + ids + "\n--------------------------------------------------------------------------------------------------"); 64 | Map idUsers = new HashMap<>(); 65 | for (UserEntity userEntity : userList) { 66 | if (userEntity == null) { 67 | continue; 68 | } 69 | Long id = userEntity.getId(); 70 | idUsers.put(id, userEntity); 71 | } 72 | return idUsers; 73 | } 74 | 75 | @Override 76 | protected void bindingOutput(Map users, List>> bundles) { 77 | for (Bundle> bundle : bundles) { 78 | Long inputId = bundle.getInput(); 79 | UserEntity userEntity = users.get(inputId); 80 | if (userEntity == null) { 81 | bundle.bindOutput(null); 82 | continue; 83 | } 84 | //copy a response 85 | UserEntity duplicate = new UserEntity(); 86 | duplicate.setId(userEntity.getId()); 87 | duplicate.setUsername(userEntity.getUsername()); 88 | bundle.bindOutput(CompletableFuture.completedFuture(duplicate)); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-advanced/src/main/java/com/icodening/collapse/sample/advanced/support/CustomBlockingCollapseExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.advanced.support; 17 | 18 | import com.icodening.collapse.core.Bundle; 19 | import com.icodening.collapse.core.CollapseExecutorBlockingSupport; 20 | import com.icodening.collapse.core.Input; 21 | import com.icodening.collapse.core.LengthLimitedInputGrouper; 22 | import com.icodening.collapse.core.ListeningCollector; 23 | import com.icodening.collapse.core.NoOpInputGrouper; 24 | 25 | import java.util.Collection; 26 | import java.util.HashMap; 27 | import java.util.List; 28 | import java.util.Map; 29 | import java.util.stream.Collectors; 30 | 31 | /** 32 | * @author icodening 33 | * @date 2023.06.27 34 | */ 35 | public class CustomBlockingCollapseExecutor extends CollapseExecutorBlockingSupport> { 36 | 37 | /** 38 | * mock remote service 39 | */ 40 | private final UserService userService; 41 | 42 | public CustomBlockingCollapseExecutor(ListeningCollector collector, UserService userService) { 43 | super(collector); 44 | this.userService = userService; 45 | this.setBatchSize(Integer.MAX_VALUE); 46 | } 47 | 48 | public void setBatchSize(int size) { 49 | this.setInputGrouper(LengthLimitedInputGrouper.newInstance(size, NoOpInputGrouper.getInstance())); 50 | } 51 | 52 | @Override 53 | protected Map doExecute(Collection> inputs) { 54 | //merge inputs and batch fetch 55 | List ids = inputs.stream().map(Input::value).collect(Collectors.toList()); 56 | List userList = userService.query(ids); 57 | System.out.println("[" + Thread.currentThread().getName() + "] batch query ids:" + ids + "\n--------------------------------------------------------------------------------------------------"); 58 | Map idUsers = new HashMap<>(); 59 | for (UserEntity userEntity : userList) { 60 | if (userEntity == null) { 61 | continue; 62 | } 63 | Long id = userEntity.getId(); 64 | idUsers.put(id, userEntity); 65 | } 66 | return idUsers; 67 | } 68 | 69 | @Override 70 | protected void bindingOutput(Map users, List> bundles) { 71 | for (Bundle bundle : bundles) { 72 | Long inputId = bundle.getInput(); 73 | UserEntity userEntity = users.get(inputId); 74 | if (userEntity == null) { 75 | bundle.bindOutput(null); 76 | continue; 77 | } 78 | //copy a response 79 | UserEntity duplicate = new UserEntity(); 80 | duplicate.setId(userEntity.getId()); 81 | duplicate.setUsername(userEntity.getUsername()); 82 | bundle.bindOutput(duplicate); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-advanced/src/main/java/com/icodening/collapse/sample/advanced/support/UserEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.advanced.support; 17 | 18 | /** 19 | * @author icodening 20 | * @date 2023.06.27 21 | */ 22 | public class UserEntity { 23 | 24 | private Long id; 25 | 26 | private String username; 27 | 28 | public UserEntity() { 29 | } 30 | 31 | public UserEntity(Long id, String username) { 32 | this.id = id; 33 | this.username = username; 34 | } 35 | 36 | public Long getId() { 37 | return id; 38 | } 39 | 40 | public void setId(Long id) { 41 | this.id = id; 42 | } 43 | 44 | public String getUsername() { 45 | return username; 46 | } 47 | 48 | public void setUsername(String username) { 49 | this.username = username; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "UserEntity{" + 55 | "id=" + id + 56 | ", username='" + username + '\'' + 57 | '}'; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-advanced/src/main/java/com/icodening/collapse/sample/advanced/support/UserService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.advanced.support; 17 | 18 | import java.util.Collections; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.UUID; 22 | import java.util.concurrent.ConcurrentHashMap; 23 | import java.util.stream.Collectors; 24 | 25 | /** 26 | * @author icodening 27 | * @date 2023.06.27 28 | */ 29 | public class UserService { 30 | 31 | private final Map users = new ConcurrentHashMap<>(); 32 | 33 | public UserService() { 34 | initialize(); 35 | } 36 | 37 | public void initialize() { 38 | for (long i = 1; i <= 10; i++) { 39 | users.put(i, new UserEntity(i, UUID.randomUUID().toString())); 40 | } 41 | } 42 | 43 | public UserEntity getOne(Long id) { 44 | if (id == null) { 45 | return null; 46 | } 47 | return users.get(id); 48 | } 49 | 50 | public List query(List ids) { 51 | if (ids == null) { 52 | return Collections.emptyList(); 53 | } 54 | return ids.stream().map(users::get).collect(Collectors.toList()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-sequence/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | collapse-executor-samples 22 | com.icodening.collapse 23 | 1.0.0 24 | 25 | 4.0.0 26 | 27 | collapse-executor-sample-sequence 28 | 29 | 30 | 31 | com.icodening.collapse 32 | collapse-executor-core 33 | ${project.version} 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-jdbc 38 | 39 | 40 | com.h2database 41 | h2 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-sequence/src/main/java/com/icodening/collapse/sample/sequence/SequenceApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.sequence; 17 | 18 | import com.icodening.collapse.sample.sequence.service.SequenceGeneratorSampleService; 19 | import org.springframework.boot.SpringApplication; 20 | import org.springframework.boot.autoconfigure.SpringBootApplication; 21 | import org.springframework.context.ConfigurableApplicationContext; 22 | 23 | /** 24 | * @author icodening 25 | * @date 2023.07.14 26 | */ 27 | @SpringBootApplication 28 | public class SequenceApplication { 29 | 30 | public static void main(String[] args) throws InterruptedException { 31 | ConfigurableApplicationContext context = SpringApplication.run(SequenceApplication.class); 32 | SequenceGeneratorSampleService sampleService = context.getBean(SequenceGeneratorSampleService.class); 33 | System.out.println("\n========================= get 'order' sequence==========================="); 34 | sampleService.concurrentIncrement("order"); 35 | 36 | Thread.sleep(300); 37 | System.out.println("\n========================= get 'product' sequence==========================="); 38 | sampleService.concurrentIncrement("product"); 39 | 40 | Thread.sleep(300); 41 | System.out.println("\n========================= get 'member' sequence [not exists]==========================="); 42 | sampleService.concurrentIncrement("member"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-sequence/src/main/java/com/icodening/collapse/sample/sequence/generator/AbstractRepositorySequenceGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.sequence.generator; 17 | 18 | import com.icodening.collapse.core.Bundle; 19 | import com.icodening.collapse.core.CollapseExecutorBlockingSupport; 20 | import com.icodening.collapse.core.EqualsInputGrouper; 21 | import com.icodening.collapse.core.Input; 22 | import com.icodening.collapse.core.InputGrouper; 23 | import com.icodening.collapse.core.LengthLimitedInputGrouper; 24 | 25 | import java.util.Collection; 26 | import java.util.List; 27 | 28 | /** 29 | * @author icodening 30 | * @date 2023.07.14 31 | */ 32 | public abstract class AbstractRepositorySequenceGenerator extends CollapseExecutorBlockingSupport implements SequenceGenerator { 33 | 34 | public AbstractRepositorySequenceGenerator() { 35 | super(); 36 | } 37 | 38 | @Override 39 | public final void setInputGrouper(InputGrouper inputGrouper) { 40 | if (!((InputGrouper) inputGrouper instanceof LengthLimitedInputGrouper)) { 41 | return; 42 | } 43 | //only support EqualsInputGrouper 44 | InputGrouper delegate = ((LengthLimitedInputGrouper) (InputGrouper) inputGrouper).getDelegate(); 45 | if (!(delegate instanceof EqualsInputGrouper)) { 46 | return; 47 | } 48 | super.setInputGrouper(inputGrouper); 49 | } 50 | 51 | @Override 52 | public final long increment(String group) { 53 | try { 54 | return execute(group); 55 | } catch (RuntimeException e) { 56 | throw e; 57 | } catch (Throwable e) { 58 | throw new RuntimeException(e); 59 | } 60 | } 61 | 62 | @Override 63 | protected final Long doExecute(Collection> inputs) { 64 | String group = inputs.iterator().next().value(); 65 | int incrementBy = inputs.size(); 66 | return increment(group, incrementBy); 67 | } 68 | 69 | @Override 70 | protected final void bindingOutput(Long result, List> bundles) { 71 | //map sequence for each thread 72 | for (int idx = 0, decrement = bundles.size() - 1; idx < bundles.size(); idx++, decrement--) { 73 | long incr = result - decrement; 74 | Bundle bundle = bundles.get(idx); 75 | bundle.bindOutput(incr); 76 | } 77 | } 78 | 79 | protected abstract Long increment(String group, int incrementBy); 80 | 81 | } 82 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-sequence/src/main/java/com/icodening/collapse/sample/sequence/generator/SequenceGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.sequence.generator; 17 | 18 | /** 19 | * ordered sequence generator 20 | * 21 | * @author icodening 22 | * @date 2023.07.14 23 | */ 24 | public interface SequenceGenerator { 25 | 26 | long increment(String group); 27 | 28 | } 29 | 30 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-sequence/src/main/java/com/icodening/collapse/sample/sequence/generator/SimpleJdbcSequenceGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.sequence.generator; 17 | 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 20 | import org.springframework.stereotype.Component; 21 | import org.springframework.transaction.TransactionDefinition; 22 | import org.springframework.transaction.support.TransactionTemplate; 23 | 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | /** 28 | * @author icodening 29 | * @date 2023.07.14 30 | */ 31 | @Component 32 | public class SimpleJdbcSequenceGenerator extends AbstractRepositorySequenceGenerator { 33 | 34 | private static final String UPDATE_SQL = "update t_sequence set sequence = sequence + :incrementBy where business_type = :businessType"; 35 | 36 | private static final String QUERY_SQL = "select sequence from t_sequence where business_type = :businessType"; 37 | 38 | private NamedParameterJdbcTemplate namedParameterJdbcTemplate; 39 | 40 | private TransactionTemplate transactionTemplate; 41 | 42 | @Autowired 43 | public void setNamedParameterJdbcTemplate(NamedParameterJdbcTemplate namedParameterJdbcTemplate) { 44 | this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; 45 | } 46 | 47 | @Autowired 48 | public void setTransactionTemplate(TransactionTemplate transactionTemplate) { 49 | transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); 50 | this.transactionTemplate = transactionTemplate; 51 | } 52 | 53 | @Override 54 | protected Long increment(String businessType, int incrementBy) { 55 | Map parameters = new HashMap<>(); 56 | parameters.put("incrementBy", incrementBy); 57 | parameters.put("businessType", businessType); 58 | return transactionTemplate.execute(status -> { 59 | int update = namedParameterJdbcTemplate.update(UPDATE_SQL, parameters); 60 | if (update == 0) { 61 | throw new IllegalArgumentException("business type not exists. ['" + businessType + "']"); 62 | } 63 | return namedParameterJdbcTemplate.queryForObject(QUERY_SQL, parameters, Long.class); 64 | }); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-sequence/src/main/java/com/icodening/collapse/sample/sequence/service/SequenceGeneratorSampleService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.sequence.service; 17 | 18 | import com.icodening.collapse.sample.sequence.generator.SequenceGenerator; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | import org.springframework.scheduling.concurrent.CustomizableThreadFactory; 22 | import org.springframework.stereotype.Service; 23 | 24 | import java.util.concurrent.ExecutorService; 25 | import java.util.concurrent.LinkedBlockingQueue; 26 | import java.util.concurrent.ThreadPoolExecutor; 27 | import java.util.concurrent.TimeUnit; 28 | 29 | /** 30 | * @author icodening 31 | * @date 2023.07.14 32 | */ 33 | @Service 34 | public class SequenceGeneratorSampleService { 35 | 36 | private static final Logger LOGGER = LoggerFactory.getLogger(SequenceGeneratorSampleService.class); 37 | 38 | private final SequenceGenerator sequenceGenerator; 39 | 40 | private final ExecutorService executorService; 41 | 42 | public SequenceGeneratorSampleService(SequenceGenerator sequenceGenerator) { 43 | this.sequenceGenerator = sequenceGenerator; 44 | CustomizableThreadFactory threadFactory = new CustomizableThreadFactory("sequence-generator"); 45 | threadFactory.setDaemon(true); 46 | this.executorService = new ThreadPoolExecutor(10, 10, 47 | 0, TimeUnit.MILLISECONDS, 48 | new LinkedBlockingQueue<>(), 49 | threadFactory); 50 | } 51 | 52 | public void concurrentIncrement(final String businessType) { 53 | for (int i = 0; i < 10; i++) { 54 | this.executorService.execute(() -> { 55 | try { 56 | long sequence = this.sequenceGenerator.increment(businessType); 57 | LOGGER.info("current sequence: {}", sequence); 58 | } catch (IllegalArgumentException e) { 59 | LOGGER.error(e.getMessage()); 60 | } 61 | }); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-sequence/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | spring: 2 | main: 3 | web-application-type: none 4 | datasource: 5 | url: jdbc:h2:mem:sequence 6 | driver-class-name: org.h2.Driver 7 | sql: 8 | init: 9 | mode: embedded 10 | schema-locations: classpath:schema.sql 11 | data-locations: classpath:data.sql -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-sequence/src/main/resources/data.sql: -------------------------------------------------------------------------------- 1 | insert into `t_sequence`(`business_type`, `sequence`) 2 | values ('order', 0); 3 | insert into `t_sequence`(`business_type`, `sequence`) 4 | values ('product', 0); -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-sequence/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE t_sequence 2 | ( 3 | `business_type` VARCHAR(32) PRIMARY KEY NOT NULL, 4 | `sequence` INT NOT NULL 5 | ); -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-simple/README.md: -------------------------------------------------------------------------------- 1 | # collapse-executor-sample-simple 2 | 该模块为折叠执行器简单使用案例 3 | 4 | ### 1.同步阻塞调用 5 | 6 | [BlockingCollapseExecutorExample.java](src/main/java/com/icodening/collapse/sample/simple/BlockingCollapseExecutorExample.java) 7 | 8 | ````java 9 | public class BlockingCollapseExecutorExample { 10 | 11 | public static void main(String[] args) throws Throwable { 12 | BlockingCallableGroupCollapseExecutor blockingCollapseExecutor = new BlockingCallableGroupCollapseExecutor(); 13 | String outputString = blockingCollapseExecutor.execute("example group", () -> "Hello World Collapse Executor. Blocking"); 14 | System.out.println(outputString); 15 | } 16 | } 17 | ```` 18 | 19 | ### 2.异步调用 20 | 21 | [AsyncCollapseExecutorExample.java](src/main/java/com/icodening/collapse/sample/simple/AsyncCollapseExecutorExample.java) 22 | 23 | ````java 24 | public class AsyncCollapseExecutorExample { 25 | 26 | public static void main(String[] args) throws Throwable { 27 | AsyncCallableGroupCollapseExecutor asyncCallableGroupCollapseExecutor = new AsyncCallableGroupCollapseExecutor(); 28 | asyncCallableGroupCollapseExecutor.setExecutor(new ThreadPoolExecutor(10, 10, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), r -> { 29 | Thread thread = new Thread(r); 30 | thread.setDaemon(true); 31 | return thread; 32 | })); 33 | asyncCallableGroupCollapseExecutor.execute("example group", () -> "Hello World Collapse Executor. Async") 34 | .thenAccept(System.out::println) 35 | .thenRun(() -> System.exit(0)); 36 | System.in.read(); 37 | } 38 | } 39 | ```` 40 | 41 | ### 3.非阻塞异步调用 42 | 43 | [FutureCollapseExecutorExample.java](src/main/java/com/icodening/collapse/sample/simple/FutureCollapseExecutorExample.java) 44 | > 这种方式必须保证Callable中的处理逻辑是非阻塞的!!! 45 | 46 | ````java 47 | public class FutureCollapseExecutorExample { 48 | 49 | public static void main(String[] args) throws Throwable { 50 | FutureCallableGroupCollapseExecutor futureCollapseExecutor = new FutureCallableGroupCollapseExecutor(); 51 | futureCollapseExecutor.execute("example group", () -> CompletableFuture.completedFuture("Hello World Collapse Executor. Future")) 52 | .thenAccept(System.out::println) 53 | .thenRun(() -> System.exit(0)); 54 | System.in.read(); 55 | } 56 | } 57 | ```` -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-simple/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | collapse-executor-samples 22 | com.icodening.collapse 23 | 1.0.0 24 | 25 | 4.0.0 26 | 27 | collapse-executor-sample-simple 28 | 29 | 30 | 31 | com.icodening.collapse 32 | collapse-executor-core 33 | ${project.version} 34 | 35 | 36 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-simple/src/main/java/com/icodening/collapse/sample/simple/AsyncCollapseExecutorExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.simple; 17 | 18 | import com.icodening.collapse.core.support.AsyncCallableGroupCollapseExecutor; 19 | 20 | import java.util.concurrent.LinkedBlockingQueue; 21 | import java.util.concurrent.ThreadPoolExecutor; 22 | import java.util.concurrent.TimeUnit; 23 | 24 | /** 25 | * @author icodening 26 | * @date 2023.06.20 27 | */ 28 | public class AsyncCollapseExecutorExample { 29 | 30 | public static void main(String[] args) throws Throwable { 31 | AsyncCallableGroupCollapseExecutor asyncCallableGroupCollapseExecutor = new AsyncCallableGroupCollapseExecutor(); 32 | asyncCallableGroupCollapseExecutor.setExecutor(new ThreadPoolExecutor(10, 10, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), r -> { 33 | Thread thread = new Thread(r); 34 | thread.setDaemon(true); 35 | return thread; 36 | })); 37 | asyncCallableGroupCollapseExecutor.execute("example group", () -> "Hello World Collapse Executor. Async") 38 | .thenAccept(System.out::println) 39 | .thenRun(() -> System.exit(0)); 40 | System.in.read(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-simple/src/main/java/com/icodening/collapse/sample/simple/BlockingCollapseExecutorExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.simple; 17 | 18 | import com.icodening.collapse.core.support.BlockingCallableGroupCollapseExecutor; 19 | 20 | /** 21 | * @author icodening 22 | * @date 2023.06.20 23 | */ 24 | public class BlockingCollapseExecutorExample { 25 | 26 | public static void main(String[] args) throws Throwable { 27 | BlockingCallableGroupCollapseExecutor blockingCollapseExecutor = new BlockingCallableGroupCollapseExecutor(); 28 | String outputString = blockingCollapseExecutor.execute("example group", () -> "Hello World Collapse Executor. Blocking"); 29 | System.out.println(outputString); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-simple/src/main/java/com/icodening/collapse/sample/simple/FutureCollapseExecutorExample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.simple; 17 | 18 | import com.icodening.collapse.core.support.FutureCallableGroupCollapseExecutor; 19 | 20 | import java.util.concurrent.CompletableFuture; 21 | 22 | /** 23 | * @author icodening 24 | * @date 2023.06.20 25 | */ 26 | public class FutureCollapseExecutorExample { 27 | 28 | public static void main(String[] args) throws Throwable { 29 | FutureCallableGroupCollapseExecutor futureCollapseExecutor = new FutureCallableGroupCollapseExecutor(); 30 | futureCollapseExecutor.execute("example group", () -> CompletableFuture.completedFuture("Hello World Collapse Executor. Future")) 31 | .thenAccept(System.out::println) 32 | .thenRun(() -> System.exit(0)); 33 | System.in.read(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-spring-boot/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | collapse-executor-samples 22 | com.icodening.collapse 23 | 1.0.0 24 | 25 | 4.0.0 26 | 27 | collapse-executor-sample-spring-boot 28 | 29 | 30 | 31 | com.icodening.collapse 32 | collapse-executor-spring-boot-autoconfigure 33 | ${project.version} 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-web 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-webflux 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-spring-boot/src/main/java/com/icodening/collapse/sample/spring/boot/SpringBootSampleApplication.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.spring.boot; 17 | 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | 21 | /** 22 | * @author icodening 23 | * @date 2023.05.17 24 | */ 25 | @SpringBootApplication 26 | public class SpringBootSampleApplication { 27 | 28 | public static void main(String[] args) { 29 | SpringApplication.run(SpringBootSampleApplication.class); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-spring-boot/src/main/java/com/icodening/collapse/sample/spring/boot/config/SampleConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.spring.boot.config; 17 | 18 | import com.icodening.collapse.spring.web.client.reactive.CollapseExchangeFilterFunction; 19 | import org.springframework.boot.web.client.RestTemplateBuilder; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.context.annotation.Configuration; 22 | import org.springframework.web.client.RestTemplate; 23 | import org.springframework.web.reactive.function.client.WebClient; 24 | 25 | import java.util.concurrent.ExecutorService; 26 | import java.util.concurrent.LinkedBlockingQueue; 27 | import java.util.concurrent.ThreadPoolExecutor; 28 | import java.util.concurrent.TimeUnit; 29 | 30 | /** 31 | * @author icodening 32 | * @date 2023.05.17 33 | */ 34 | @Configuration 35 | public class SampleConfiguration { 36 | 37 | @Bean 38 | public RestTemplate restTemplate() { 39 | return new RestTemplateBuilder() 40 | .build(); 41 | } 42 | 43 | @Bean 44 | public WebClient webClient(CollapseExchangeFilterFunction exchangeFilterFunction) { 45 | return WebClient.builder().filter(exchangeFilterFunction).build(); 46 | } 47 | 48 | @Bean 49 | public ExecutorService executorService() { 50 | return new ThreadPoolExecutor(50, 100, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), r -> { 51 | Thread thread = new Thread(r); 52 | thread.setDaemon(true); 53 | return thread; 54 | }); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-spring-boot/src/main/java/com/icodening/collapse/sample/spring/boot/controller/RestTemplateController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.spring.boot.controller; 17 | 18 | import com.icodening.collapse.sample.spring.boot.entity.UserEntity; 19 | import com.icodening.collapse.spring.web.client.CollapseHttpRequestInterceptor; 20 | import org.springframework.beans.factory.annotation.Value; 21 | import org.springframework.boot.web.client.RestTemplateBuilder; 22 | import org.springframework.web.bind.annotation.RequestMapping; 23 | import org.springframework.web.bind.annotation.RestController; 24 | import org.springframework.web.client.RestTemplate; 25 | import org.springframework.web.util.UriComponentsBuilder; 26 | 27 | /** 28 | * @author icodening 29 | * @date 2023.06.30 30 | */ 31 | @RestController 32 | @RequestMapping("/rest-template") 33 | public class RestTemplateController { 34 | 35 | private final RestTemplate restTemplateWithCollapse; 36 | 37 | private final RestTemplate restTemplateWithoutCollapse; 38 | 39 | public RestTemplateController(CollapseHttpRequestInterceptor collapseHttpRequestInterceptor, @Value("${server.port}") int port) { 40 | UriComponentsBuilder baseUrl = UriComponentsBuilder.newInstance().scheme("http").host("localhost").port(port); 41 | this.restTemplateWithCollapse = new RestTemplateBuilder().rootUri(baseUrl.toUriString()).additionalInterceptors(collapseHttpRequestInterceptor).build(); 42 | this.restTemplateWithoutCollapse = new RestTemplateBuilder().rootUri(baseUrl.toUriString()).build(); 43 | } 44 | 45 | @RequestMapping(value = "/collapse/noop0") 46 | public UserEntity noop0WithCollapse() { 47 | return this.restTemplateWithCollapse.getForObject("/test/noop0", UserEntity.class); 48 | } 49 | 50 | @RequestMapping(value = "/collapse/noop1") 51 | public UserEntity noop1WithCollapse() { 52 | return this.restTemplateWithCollapse.getForObject("/test/noop1", UserEntity.class); 53 | } 54 | 55 | @RequestMapping(value = "/collapse/noop100") 56 | public UserEntity noop100WithCollapse() { 57 | return this.restTemplateWithCollapse.getForObject("/test/noop100", UserEntity.class); 58 | } 59 | 60 | @RequestMapping(value = "/without/noop0") 61 | public UserEntity noop0WithoutCollapse() { 62 | return this.restTemplateWithoutCollapse.getForObject("/test/noop0", UserEntity.class); 63 | } 64 | 65 | @RequestMapping(value = "/without/noop1") 66 | public UserEntity noop1WithoutCollapse() { 67 | return this.restTemplateWithoutCollapse.getForObject("/test/noop1", UserEntity.class); 68 | } 69 | 70 | @RequestMapping(value = "/without/noop100") 71 | public UserEntity noop100WithoutCollapse() { 72 | return this.restTemplateWithoutCollapse.getForObject("/test/noop100", UserEntity.class); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-spring-boot/src/main/java/com/icodening/collapse/sample/spring/boot/controller/StressTestController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.spring.boot.controller; 17 | 18 | import com.icodening.collapse.sample.spring.boot.entity.UserEntity; 19 | import com.icodening.collapse.sample.spring.boot.service.UserService; 20 | import org.springframework.web.bind.annotation.RequestMapping; 21 | import org.springframework.web.bind.annotation.RestController; 22 | 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | import java.util.concurrent.atomic.AtomicInteger; 26 | 27 | /** 28 | * @author icodening 29 | * @date 2023.05.24 30 | */ 31 | @RequestMapping("/test") 32 | @RestController 33 | public class StressTestController { 34 | 35 | private final AtomicInteger collapseCounter = new AtomicInteger(0); 36 | 37 | private final AtomicInteger noOpCounter = new AtomicInteger(0); 38 | 39 | private final UserService userService; 40 | 41 | public StressTestController(UserService userService) { 42 | this.userService = userService; 43 | } 44 | 45 | @RequestMapping("/collapse0") 46 | public UserEntity collapse0() { 47 | collapseCounter.incrementAndGet(); 48 | return userService.getUser(1L); 49 | } 50 | 51 | @RequestMapping("/collapse1") 52 | public UserEntity collapse1() throws InterruptedException { 53 | collapseCounter.incrementAndGet(); 54 | Thread.sleep(1); 55 | return userService.getUser(1L); 56 | } 57 | 58 | @RequestMapping("/collapse100") 59 | public UserEntity collapse100() throws InterruptedException { 60 | collapseCounter.incrementAndGet(); 61 | Thread.sleep(100); 62 | return userService.getUser(1L); 63 | } 64 | 65 | @RequestMapping("/noop0") 66 | public UserEntity noop0() { 67 | noOpCounter.incrementAndGet(); 68 | return userService.getUser(1L); 69 | } 70 | 71 | @RequestMapping("/noop1") 72 | public UserEntity noop1() throws InterruptedException { 73 | noOpCounter.incrementAndGet(); 74 | Thread.sleep(1); 75 | return userService.getUser(1L); 76 | } 77 | 78 | @RequestMapping("/noop100") 79 | public UserEntity noop100() throws InterruptedException { 80 | noOpCounter.incrementAndGet(); 81 | Thread.sleep(100); 82 | return userService.getUser(1L); 83 | } 84 | 85 | @RequestMapping("/counter") 86 | public Map counter() { 87 | Map result = new HashMap<>(); 88 | result.put("collapse", collapseCounter.get()); 89 | result.put("noop", noOpCounter.get()); 90 | return result; 91 | } 92 | 93 | @RequestMapping("/reset") 94 | public String reset() { 95 | collapseCounter.set(0); 96 | noOpCounter.set(0); 97 | return "reset success."; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-spring-boot/src/main/java/com/icodening/collapse/sample/spring/boot/controller/UserController.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.spring.boot.controller; 17 | 18 | import com.icodening.collapse.sample.spring.boot.entity.UserEntity; 19 | import com.icodening.collapse.sample.spring.boot.service.UserService; 20 | import org.springframework.web.bind.annotation.GetMapping; 21 | import org.springframework.web.bind.annotation.PathVariable; 22 | import org.springframework.web.bind.annotation.RequestMapping; 23 | import org.springframework.web.bind.annotation.RequestParam; 24 | import org.springframework.web.bind.annotation.RestController; 25 | 26 | import java.util.List; 27 | import java.util.concurrent.atomic.AtomicInteger; 28 | 29 | /** 30 | * @author icodening 31 | * @date 2023.05.17 32 | */ 33 | @RestController 34 | @RequestMapping("/user") 35 | public class UserController { 36 | 37 | public static final AtomicInteger SINGLE_GET_COUNTER = new AtomicInteger(0); 38 | 39 | public static final AtomicInteger BATCH_GET_COUNTER = new AtomicInteger(0); 40 | 41 | private final UserService userService; 42 | 43 | public UserController(UserService userService) { 44 | this.userService = userService; 45 | } 46 | 47 | @GetMapping("/{id}") 48 | public UserEntity singleGetUser(@PathVariable Long id) { 49 | SINGLE_GET_COUNTER.incrementAndGet(); 50 | return userService.getUser(id); 51 | } 52 | 53 | @GetMapping("/batchGet") 54 | public List batchGetUser(@RequestParam(name = "id") Long[] ids) { 55 | BATCH_GET_COUNTER.incrementAndGet(); 56 | return userService.batchGet(ids); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-spring-boot/src/main/java/com/icodening/collapse/sample/spring/boot/entity/UserEntity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.spring.boot.entity; 17 | 18 | /** 19 | * @author icodening 20 | * @date 2023.05.17 21 | */ 22 | public class UserEntity { 23 | 24 | private Long id; 25 | 26 | private String username; 27 | 28 | private String password; 29 | 30 | public Long getId() { 31 | return id; 32 | } 33 | 34 | public void setId(Long id) { 35 | this.id = id; 36 | } 37 | 38 | public String getUsername() { 39 | return username; 40 | } 41 | 42 | public void setUsername(String username) { 43 | this.username = username; 44 | } 45 | 46 | public String getPassword() { 47 | return password; 48 | } 49 | 50 | public void setPassword(String password) { 51 | this.password = password; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "UserEntity{" + 57 | "id=" + id + 58 | ", username='" + username + '\'' + 59 | ", password='" + password + '\'' + 60 | '}'; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-spring-boot/src/main/java/com/icodening/collapse/sample/spring/boot/executor/AbstractBatchGetExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.spring.boot.executor; 17 | 18 | import com.icodening.collapse.core.Bundle; 19 | import com.icodening.collapse.core.CollapseExecutorBlockingSupport; 20 | import com.icodening.collapse.core.Input; 21 | import com.icodening.collapse.core.ListeningCollector; 22 | import com.icodening.collapse.core.NoOpInputGrouper; 23 | import com.icodening.collapse.sample.spring.boot.entity.UserEntity; 24 | import org.springframework.beans.factory.annotation.Value; 25 | import org.springframework.util.LinkedMultiValueMap; 26 | import org.springframework.util.MultiValueMap; 27 | import org.springframework.web.util.UriComponentsBuilder; 28 | 29 | import java.net.URI; 30 | import java.util.ArrayList; 31 | import java.util.Collection; 32 | import java.util.HashMap; 33 | import java.util.List; 34 | import java.util.Map; 35 | import java.util.Objects; 36 | import java.util.stream.Collectors; 37 | 38 | /** 39 | * @author icodening 40 | * @date 2023.06.25 41 | */ 42 | public abstract class AbstractBatchGetExecutor extends CollapseExecutorBlockingSupport> { 43 | 44 | private int serverPort; 45 | 46 | @Value("${server.port}") 47 | public void setServerPort(int serverPort) { 48 | this.serverPort = serverPort; 49 | } 50 | 51 | public AbstractBatchGetExecutor(ListeningCollector collector) { 52 | super(collector); 53 | this.setInputGrouper(NoOpInputGrouper.getInstance()); 54 | } 55 | 56 | /** 57 | * batch request 58 | * 59 | * @param inputs ids 60 | * @return ids -> UserEntities 61 | */ 62 | @Override 63 | protected List doExecute(Collection> inputs) throws Throwable { 64 | MultiValueMap queryParams = new LinkedMultiValueMap<>(); 65 | queryParams.addAll("id", new ArrayList<>(inputs.stream() 66 | .map(Input::value) 67 | .map(Objects::toString) 68 | .collect(Collectors.toSet()))); 69 | //eg. http://localhost:8080/user/batchGet?id=1&id=2&id=3&id=4 70 | URI uri = UriComponentsBuilder.newInstance() 71 | .scheme("http") 72 | .host("localhost") 73 | .port(serverPort) 74 | .path("/user/batchGet") 75 | .queryParams(queryParams) 76 | .build() 77 | .toUri(); 78 | return doBatchGet(uri); 79 | } 80 | 81 | protected abstract List doBatchGet(URI uri); 82 | 83 | 84 | /** 85 | * map response to inputs 86 | * 87 | * @param batchOutput batch response 88 | * @param bundles input output bundle 89 | */ 90 | @Override 91 | protected void bindingOutput(List batchOutput, List> bundles) { 92 | Map entityMap = new HashMap<>(); 93 | for (UserEntity userEntity : batchOutput) { 94 | entityMap.put(userEntity.getId(), userEntity); 95 | } 96 | for (Bundle bundle : bundles) { 97 | Long id = bundle.getInput(); 98 | UserEntity userEntity = entityMap.get(id); 99 | if (userEntity == null) { 100 | bundle.bindOutput(null); 101 | continue; 102 | } 103 | //copy a response 104 | UserEntity duplicate = new UserEntity(); 105 | duplicate.setId(userEntity.getId()); 106 | duplicate.setUsername(userEntity.getUsername()); 107 | duplicate.setPassword(userEntity.getPassword()); 108 | bundle.bindOutput(duplicate); 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-spring-boot/src/main/java/com/icodening/collapse/sample/spring/boot/executor/RestTemplateBatchGetExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.spring.boot.executor; 17 | 18 | import com.icodening.collapse.core.ListeningCollector; 19 | import com.icodening.collapse.sample.spring.boot.entity.UserEntity; 20 | import org.springframework.core.ParameterizedTypeReference; 21 | import org.springframework.http.HttpMethod; 22 | import org.springframework.stereotype.Component; 23 | import org.springframework.web.client.RestTemplate; 24 | 25 | import java.net.URI; 26 | import java.util.List; 27 | 28 | /** 29 | * @author icodening 30 | * @date 2023.05.17 31 | */ 32 | @Component 33 | public class RestTemplateBatchGetExecutor extends AbstractBatchGetExecutor { 34 | 35 | private final RestTemplate restTemplate; 36 | 37 | public RestTemplateBatchGetExecutor(ListeningCollector collector) { 38 | super(collector); 39 | this.restTemplate = new RestTemplate(); 40 | } 41 | 42 | @Override 43 | protected List doBatchGet(URI uri) { 44 | return restTemplate.exchange(uri, HttpMethod.GET, null, new ParameterizedTypeReference>() { 45 | }).getBody(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-spring-boot/src/main/java/com/icodening/collapse/sample/spring/boot/executor/WebClientBatchGetExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.spring.boot.executor; 17 | 18 | import com.icodening.collapse.core.ListeningCollector; 19 | import com.icodening.collapse.sample.spring.boot.entity.UserEntity; 20 | import org.springframework.core.ParameterizedTypeReference; 21 | import org.springframework.http.ResponseEntity; 22 | import org.springframework.stereotype.Component; 23 | import org.springframework.util.Assert; 24 | import org.springframework.web.reactive.function.client.WebClient; 25 | 26 | import java.net.URI; 27 | import java.util.List; 28 | 29 | /** 30 | * @author icodening 31 | * @date 2023.06.24 32 | */ 33 | @Component 34 | public class WebClientBatchGetExecutor extends AbstractBatchGetExecutor { 35 | 36 | private final WebClient webClient; 37 | 38 | public WebClientBatchGetExecutor(ListeningCollector collector) { 39 | super(collector); 40 | this.webClient = WebClient.create(); 41 | } 42 | 43 | @Override 44 | protected List doBatchGet(URI uri) { 45 | ResponseEntity> entity = this.webClient.get().uri(uri).retrieve().toEntity(new ParameterizedTypeReference>() { 46 | }).block(); 47 | Assert.notNull(entity, "'ResponseEntity' must be not null."); 48 | return entity.getBody(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-spring-boot/src/main/java/com/icodening/collapse/sample/spring/boot/service/RestTemplateCollapseBlockingCallSample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.spring.boot.service; 17 | 18 | import com.icodening.collapse.sample.spring.boot.entity.UserEntity; 19 | import com.icodening.collapse.sample.spring.boot.executor.RestTemplateBatchGetExecutor; 20 | import org.springframework.stereotype.Component; 21 | import org.springframework.web.client.RestTemplate; 22 | import org.springframework.web.util.UriComponentsBuilder; 23 | 24 | /** 25 | * @author icodening 26 | * @date 2023.06.24 27 | */ 28 | @Component 29 | public class RestTemplateCollapseBlockingCallSample extends AbstractBlockingCallSample { 30 | 31 | private final RestTemplate restTemplate; 32 | 33 | private final RestTemplateBatchGetExecutor restTemplateBatchGetExecutor; 34 | 35 | public RestTemplateCollapseBlockingCallSample(RestTemplate restTemplate, 36 | RestTemplateBatchGetExecutor restTemplateBatchGetExecutor) { 37 | super(RestTemplate.class.getSimpleName()); 38 | this.restTemplate = restTemplate; 39 | this.restTemplateBatchGetExecutor = restTemplateBatchGetExecutor; 40 | } 41 | 42 | @Override 43 | protected UserEntity doBatchQuery(long id) throws Throwable { 44 | return restTemplateBatchGetExecutor.execute(id); 45 | } 46 | 47 | @Override 48 | protected UserEntity doSingleQuery(UriComponentsBuilder baseUri, long id) { 49 | return restTemplate.getForObject(baseUri.path("/user/" + id).build().toUri(), UserEntity.class); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-spring-boot/src/main/java/com/icodening/collapse/sample/spring/boot/service/UserService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.spring.boot.service; 17 | 18 | import com.icodening.collapse.sample.spring.boot.entity.UserEntity; 19 | import org.springframework.stereotype.Service; 20 | 21 | import javax.annotation.PostConstruct; 22 | import java.util.Arrays; 23 | import java.util.List; 24 | import java.util.Map; 25 | import java.util.UUID; 26 | import java.util.concurrent.ConcurrentHashMap; 27 | import java.util.stream.Collectors; 28 | 29 | /** 30 | * @author icodening 31 | * @date 2023.05.17 32 | */ 33 | @Service 34 | public class UserService { 35 | 36 | private final Map idUserMap = new ConcurrentHashMap<>(); 37 | 38 | @PostConstruct 39 | public void initUsers() { 40 | for (long i = 1; i <= 50; i++) { 41 | UserEntity userEntity = new UserEntity(); 42 | userEntity.setId(i); 43 | userEntity.setUsername("User " + i); 44 | userEntity.setPassword(UUID.randomUUID().toString()); 45 | idUserMap.put(i, userEntity); 46 | } 47 | } 48 | 49 | public UserEntity getUser(Long id) { 50 | return idUserMap.get(id); 51 | } 52 | 53 | public List batchGet(Long[] ids) { 54 | return Arrays.stream(ids).map(idUserMap::get).collect(Collectors.toList()); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-spring-boot/src/main/java/com/icodening/collapse/sample/spring/boot/service/WebClientCollapseBlockingCallSample.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023-2024 the original author or authors. 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 | package com.icodening.collapse.sample.spring.boot.service; 17 | 18 | import com.icodening.collapse.sample.spring.boot.entity.UserEntity; 19 | import com.icodening.collapse.sample.spring.boot.executor.WebClientBatchGetExecutor; 20 | import org.springframework.stereotype.Component; 21 | import org.springframework.web.reactive.function.client.WebClient; 22 | import org.springframework.web.util.UriComponentsBuilder; 23 | 24 | /** 25 | * @author icodening 26 | * @date 2023.06.24 27 | */ 28 | @Component 29 | public class WebClientCollapseBlockingCallSample extends AbstractBlockingCallSample { 30 | 31 | private final WebClient webClient; 32 | 33 | private final WebClientBatchGetExecutor webClientBatchGetExecutor; 34 | 35 | public WebClientCollapseBlockingCallSample(WebClient webClient, 36 | WebClientBatchGetExecutor webClientBatchGetExecutor) { 37 | super(WebClient.class.getSimpleName()); 38 | this.webClient = webClient; 39 | this.webClientBatchGetExecutor = webClientBatchGetExecutor; 40 | } 41 | 42 | @Override 43 | protected UserEntity doBatchQuery(long id) throws Throwable { 44 | return webClientBatchGetExecutor.execute(id); 45 | } 46 | 47 | @Override 48 | protected UserEntity doSingleQuery(UriComponentsBuilder baseUri, long id) { 49 | return webClient.get() 50 | .uri(baseUri.path("/user/" + id).build().toUri()) 51 | .retrieve() 52 | .bodyToMono(UserEntity.class) 53 | .block(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /collapse-executor-samples/collapse-executor-sample-spring-boot/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | shutdown: graceful 4 | tomcat: 5 | threads: 6 | max: 400 7 | 8 | collapse: 9 | executor: 10 | enabled: true 11 | wait-threshold: 10 12 | collecting-wait-time: 0 13 | 14 | #[Servlet] collapse request configuration 15 | servlet: 16 | enabled: true 17 | batch-size: 64 18 | collapse-policies: 19 | test-policy: 20 | collapse-request-headers: 21 | - auth 22 | - user-id 23 | collapse-groups: 24 | - collapse-policy-name: test-policy 25 | patterns: 26 | - /test/collapse1 27 | - /test/collapse100 28 | 29 | #[RestTemplate] collapse request configuration 30 | rest-template: 31 | enabled: true 32 | apply-bean-names: restTemplate 33 | collapse-policies: 34 | sample-policy: 35 | collapse-request-headers: 36 | - authorization 37 | collapse-request-queries: 38 | - sample 39 | collapse-groups: 40 | - patterns: 41 | - /user/* 42 | - /test/noop* 43 | - collapse-policy-name: sample-policy 44 | patterns: 45 | - /samples/* 46 | 47 | 48 | #[WebClient] collapse request configuration 49 | web-client: 50 | enabled: true 51 | collapse-groups: 52 | - patterns: 53 | - /user/* 54 | - /test/noop* -------------------------------------------------------------------------------- /collapse-executor-samples/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | collapse-executor-parent 22 | com.icodening.collapse 23 | 1.0.0 24 | 25 | 4.0.0 26 | 27 | collapse-executor-samples 28 | pom 29 | 30 | collapse-executor-sample-spring-boot 31 | collapse-executor-sample-simple 32 | collapse-executor-sample-advanced 33 | collapse-executor-sample-sequence 34 | 35 | 36 | 37 | 2.7.18 38 | 39 | 40 | 41 | 42 | 43 | 44 | org.springframework.boot 45 | spring-boot-dependencies 46 | ${spring.boot.version} 47 | pom 48 | import 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/images/collapse-executor-simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icodening/collapse-executor/5ba677a905035a4d3442def41896d98601dd5bb2/docs/images/collapse-executor-simple.png -------------------------------------------------------------------------------- /docs/images/collapse-executor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icodening/collapse-executor/5ba677a905035a4d3442def41896d98601dd5bb2/docs/images/collapse-executor.png -------------------------------------------------------------------------------- /docs/images/rest-template-collapse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icodening/collapse-executor/5ba677a905035a4d3442def41896d98601dd5bb2/docs/images/rest-template-collapse.png -------------------------------------------------------------------------------- /docs/images/rest-template-without-collapse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icodening/collapse-executor/5ba677a905035a4d3442def41896d98601dd5bb2/docs/images/rest-template-without-collapse.png -------------------------------------------------------------------------------- /docs/images/webclient-collapse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icodening/collapse-executor/5ba677a905035a4d3442def41896d98601dd5bb2/docs/images/webclient-collapse.png -------------------------------------------------------------------------------- /docs/images/webclient-without-collapse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icodening/collapse-executor/5ba677a905035a4d3442def41896d98601dd5bb2/docs/images/webclient-without-collapse.png -------------------------------------------------------------------------------- /docs/images/with-collapse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icodening/collapse-executor/5ba677a905035a4d3442def41896d98601dd5bb2/docs/images/with-collapse.png -------------------------------------------------------------------------------- /docs/images/without-collapse-executor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icodening/collapse-executor/5ba677a905035a4d3442def41896d98601dd5bb2/docs/images/without-collapse-executor.png -------------------------------------------------------------------------------- /docs/images/without-collapse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/icodening/collapse-executor/5ba677a905035a4d3442def41896d98601dd5bb2/docs/images/without-collapse.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 4.0.0 21 | 22 | com.icodening.collapse 23 | collapse-executor-parent 24 | pom 25 | 1.0.0 26 | 27 | collapse-executor-core 28 | collapse-executor-integration 29 | collapse-executor-samples 30 | 31 | 32 | 33 | 34 | 35 | org.apache.maven.plugins 36 | maven-compiler-plugin 37 | 3.12.1 38 | 39 | 1.8 40 | 1.8 41 | 42 | 43 | 44 | org.apache.maven.plugins 45 | maven-source-plugin 46 | 3.3.0 47 | 48 | 49 | attach-sources 50 | 51 | jar 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | --------------------------------------------------------------------------------