diff --git a/.checkstyle/checkstyle-suppressions.xml b/.checkstyle/checkstyle-suppressions.xml
new file mode 100644
index 0000000..063764e
--- /dev/null
+++ b/.checkstyle/checkstyle-suppressions.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.checkstyle/checkstyle.xml b/.checkstyle/checkstyle.xml
new file mode 100644
index 0000000..9b2cc54
--- /dev/null
+++ b/.checkstyle/checkstyle.xml
@@ -0,0 +1,227 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..cd7a907
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: Citymonstret
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: [ 'https://paypal.me/Sauilitired' ]# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
\ No newline at end of file
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..1a64867
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,66 @@
+name: Build disruptor
+on:
+ push:
+ branches: [ "**" ]
+ tags-ignore: [ "**" ]
+ pull_request:
+ release:
+ types: [ published ]
+jobs:
+ build:
+ # Only run on PRs if the source branch is on someone else's repo
+ if: ${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }}
+ runs-on: "ubuntu-latest"
+ steps:
+ - uses: actions/checkout@v4
+ - uses: gradle/wrapper-validation-action@v3
+ - name: Set up JDK
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: 21
+ - uses: gradle/actions/setup-gradle@v4
+ with:
+ # allow master and *-dev branches to write caches (default is only master/main)
+ cache-read-only: ${{ github.ref != 'refs/heads/master' && !(endsWith(github.ref, '-dev') && startsWith(github.ref, 'refs/heads/')) }}
+ - name: Build
+ run: ./gradlew build
+ - name: Upload Test Results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: Test Results
+ path: |
+ **/build/test-results/test/TEST-*.xml
+ - name: Determine Status
+ run: |
+ if [ "$(./gradlew properties | awk '/^version:/ { print $2; }' | grep '\-SNAPSHOT')" ]; then
+ echo "STATUS=snapshot" >> $GITHUB_ENV
+ else
+ echo "STATUS=release" >> $GITHUB_ENV
+ fi
+ - name: Publish Snapshot
+ if: "${{ env.STATUS != 'release' && github.event_name == 'push' && github.ref == 'refs/heads/master' }}"
+ run: ./gradlew publish
+ env:
+ ORG_GRADLE_PROJECT_sonatypeUsername: "${{ secrets.SONATYPE_USERNAME }}"
+ ORG_GRADLE_PROJECT_sonatypePassword: "${{ secrets.SONATYPE_PASSWORD }}"
+ - name: Publish Release
+ if: "${{ env.STATUS == 'release' && github.event_name == 'release' }}"
+ run: ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository
+ env:
+ ORG_GRADLE_PROJECT_sonatypeUsername: "${{ secrets.SONATYPE_USERNAME }}"
+ ORG_GRADLE_PROJECT_sonatypePassword: "${{ secrets.SONATYPE_PASSWORD }}"
+ ORG_GRADLE_PROJECT_signingKey: "${{ secrets.SIGNING_KEY }}"
+ ORG_GRADLE_PROJECT_signingPassword: "${{ secrets.SIGNING_PASSWORD }}"
+ event_file:
+ name: "Event File"
+ # Only run on PRs if the source branch is on someone else's repo
+ if: ${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Upload
+ uses: actions/upload-artifact@v4
+ with:
+ name: Event File
+ path: ${{ github.event_path }}
\ No newline at end of file
diff --git a/.github/workflows/test_results.yml b/.github/workflows/test_results.yml
new file mode 100644
index 0000000..f16d7a4
--- /dev/null
+++ b/.github/workflows/test_results.yml
@@ -0,0 +1,33 @@
+name: Test Results
+
+on:
+ workflow_run:
+ workflows: [ "Build disruptor" ]
+ types:
+ - completed
+permissions: { }
+
+jobs:
+ test-results:
+ name: Test Results
+ runs-on: ubuntu-latest
+ if: github.event.workflow_run.conclusion != 'skipped'
+ permissions:
+ checks: write
+ # needed unless run with comment_mode: off
+ pull-requests: write
+ # required by download step to access artifacts API
+ actions: read
+ steps:
+ - name: Download and Extract Artifacts
+ uses: dawidd6/action-download-artifact@v6
+ with:
+ run_id: ${{ github.event.workflow_run.id }}
+ path: artifacts
+ - name: Publish Test Results
+ uses: EnricoMi/publish-unit-test-result-action@v2
+ with:
+ commit: ${{ github.event.workflow_run.head_sha }}
+ event_file: artifacts/Event File/event.json
+ event_name: ${{ github.event.workflow_run.event }}
+ files: "artifacts/**/*.xml"
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..465281b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,160 @@
+# Created by https://www.toptal.com/developers/gitignore/api/intellij+all,java,gradle,git
+# Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all,java,gradle,git
+
+### Git ###
+# Created by git for backups. To disable backups in Git:
+# $ git config --global mergetool.keepBackup false
+*.orig
+
+# Created by git when using merge tools for conflicts
+*.BACKUP.*
+*.BASE.*
+*.LOCAL.*
+*.REMOTE.*
+*_BACKUP_*.txt
+*_BASE_*.txt
+*_LOCAL_*.txt
+*_REMOTE_*.txt
+
+### Intellij+all ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Intellij+all Patch ###
+# Ignore everything but code style settings and run configurations
+# that are supposed to be shared within teams.
+
+.idea/*
+
+!.idea/codeStyles
+!.idea/runConfigurations
+
+### Java ###
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+replay_pid*
+
+### Gradle ###
+.gradle
+**/build/
+!src/**/build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Avoid ignore Gradle wrappper properties
+!gradle-wrapper.properties
+
+# Cache of project
+.gradletasknamecache
+
+# Eclipse Gradle plugin generated files
+# Eclipse Core
+.project
+# JDT-specific (Eclipse Java Development Tools)
+.classpath
+
+### Gradle Patch ###
+# Java heap dump
+*.hprof
+
+# End of https://www.toptal.com/developers/gitignore/api/intellij+all,java,gradle,git
\ No newline at end of file
diff --git a/.spotless/disruptor.importorder b/.spotless/disruptor.importorder
new file mode 100644
index 0000000..f84b813
--- /dev/null
+++ b/.spotless/disruptor.importorder
@@ -0,0 +1,3 @@
+# disruptor import order
+0=
+1=\#
\ No newline at end of file
diff --git a/HEADER b/HEADER
new file mode 100644
index 0000000..51ce1f2
--- /dev/null
+++ b/HEADER
@@ -0,0 +1,23 @@
+//
+// MIT License
+//
+// Copyright (c) 2024 Incendo
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9335dcf
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Incendo
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2af0f8a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,43 @@
+
+
+library for introducing disruptions to your code to replicate an unstable live environment.
+
+## Modules
+
+- **core:** core disruptor API
+- ~~**spring:** spring integration~~
+- ~~**feign:** feign integration~~
+
+## Example
+
+### Pure Java
+
+```java
+final Disruptor disruptor = Disruptor.builder()
+ .group(
+ // Creates a new group called "test"...
+ "test",
+ group -> group
+ .config(
+ // which triggers 25% of the time, and lasts for 5 seconds once triggered
+ DisruptionTrigger.random(0.25f).lasting(Duration.ofSeconds(5L)),
+ config -> config
+ // running before the method that is disrupted
+ .mode(DisruptionMode.BEFORE)
+ // introducing a delay of 5 seconds
+ .delay(Duration.ofSeconds(5L))
+ // then throwing an exception
+ .throwException(ctx -> new RuntimeException("hello :)"))
+ )
+ )
+ .build();
+
+disruptor.disruptWithoutResult("test", () -> {
+ System.out.println("test");
+});
+```
\ No newline at end of file
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..e1445f0
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,6 @@
+# TODO
+
+1. [x] Create core API
+2. [x] Tests :)
+3. [ ] Create Spring (AOP) integration
+4. [ ] Create Feign integration
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..d6b61f8
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,26 @@
+plugins {
+ alias(libs.plugins.cloud.buildLogic.rootProject.publishing)
+ alias(libs.plugins.cloud.buildLogic.rootProject.spotless)
+}
+
+spotlessPredeclare {
+ kotlin { ktlint(libs.versions.ktlint.get()) }
+ kotlinGradle { ktlint(libs.versions.ktlint.get()) }
+}
+
+subprojects {
+ afterEvaluate {
+ tasks.withType().configureEach {
+ options.compilerArgs.remove("-Werror")
+ }
+ }
+}
+
+tasks {
+ spotlessCheck {
+ dependsOn(gradle.includedBuild("build-logic").task(":spotlessCheck") )
+ }
+ spotlessApply {
+ dependsOn(gradle.includedBuild("build-logic").task(":spotlessApply"))
+ }
+}
\ No newline at end of file
diff --git a/core/build.gradle.kts b/core/build.gradle.kts
new file mode 100644
index 0000000..24c8e62
--- /dev/null
+++ b/core/build.gradle.kts
@@ -0,0 +1,13 @@
+plugins {
+ id("disruptor.base-conventions")
+ id("disruptor.publishing-conventions")
+}
+
+dependencies {
+ testImplementation(libs.junit.jupiter)
+ testRuntimeOnly(libs.junit.platform)
+}
+
+tasks.named("test") {
+ useJUnitPlatform()
+}
diff --git a/core/src/main/java/org/incendo/disruptor/DisruptionConfig.java b/core/src/main/java/org/incendo/disruptor/DisruptionConfig.java
new file mode 100644
index 0000000..fc3d198
--- /dev/null
+++ b/core/src/main/java/org/incendo/disruptor/DisruptionConfig.java
@@ -0,0 +1,160 @@
+//
+// MIT License
+//
+// Copyright (c) 2024 Incendo
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+package org.incendo.disruptor;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import org.apiguardian.api.API;
+import org.incendo.disruptor.disruption.Disruption;
+import org.incendo.disruptor.trigger.DisruptionTrigger;
+
+@API(status = API.Status.STABLE, since = "1.0.0")
+public interface DisruptionConfig {
+
+ /**
+ * Returns a new mutable {@link DisruptionConfig} builder.
+ *
+ * @return the builder
+ */
+ static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Returns the disruption trigger.
+ *
+ * @return the trigger
+ */
+ DisruptionTrigger trigger();
+
+ /**
+ * Return the disruptions, in the order they will be invoked.
+ *
+ * @return the disruptions
+ */
+ List disruptions();
+
+ /**
+ * Returns the disruption mode.
+ *
+ * @return the mode
+ */
+ DisruptionMode mode();
+
+ @API(status = API.Status.STABLE, since = "1.0.0")
+ final class Builder {
+
+ private final List disruptions = new ArrayList<>();
+ private DisruptionTrigger trigger = DisruptionTrigger.never();
+ private DisruptionMode mode = DisruptionMode.BEFORE;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets the trigger.
+ *
+ * @param trigger new trigger
+ * @return {@code this}
+ */
+ public Builder trigger(final DisruptionTrigger trigger) {
+ this.trigger = Objects.requireNonNull(trigger, "trigger");
+ return this;
+ }
+
+ /**
+ * Adds the given {@code disruptions}.
+ *
+ * @param disruptions disruptions to add
+ * @return {@code this}
+ */
+ public Builder disruptions(final Disruption... disruptions) {
+ Objects.requireNonNull(disruptions, "disruptions");
+ for (final Disruption disruption : disruptions) {
+ Objects.requireNonNull(disruption, "disruption");
+ }
+ this.disruptions.addAll(Arrays.asList(disruptions));
+ return this;
+ }
+
+ /**
+ * Adds the given {@code disruptions}.
+ *
+ * @param disruptions disruptions to add
+ * @return {@code this}
+ */
+ public Builder disruptions(final List disruptions) {
+ Objects.requireNonNull(disruptions, "disruptions");
+ for (final Disruption disruption : disruptions) {
+ Objects.requireNonNull(disruption, "disruption");
+ }
+ this.disruptions.addAll(disruptions);
+ return this;
+ }
+
+ /**
+ * Adds a {@link Disruption#delaying(Duration)} disruption.
+ *
+ * @param duration duration to delay for
+ * @return {@code this}
+ */
+ public Builder delay(final Duration duration) {
+ return this.disruptions(Disruption.delaying(duration));
+ }
+
+ /**
+ * Adds a {@link Disruption#throwing(Function)} disruption.
+ *
+ * @param generator throwable generator
+ * @return {@code this}
+ */
+ public Builder throwException(final Function generator) {
+ return this.disruptions(Disruption.throwing(generator));
+ }
+
+ /**
+ * Sets the disruption mode to the given {@code mode}.
+ *
+ * @param mode new mode
+ * @return {@code this}
+ */
+ public Builder mode(final DisruptionMode mode) {
+ this.mode = Objects.requireNonNull(mode, "mode");
+ return this;
+ }
+
+ /**
+ * Build a new {@link DisruptionConfig} instance using {@code this} builder.
+ *
+ * @return the config instance
+ */
+ public DisruptionConfig build() {
+ return new DisruptionConfigImpl(this.trigger, List.copyOf(this.disruptions), this.mode);
+ }
+ }
+}
diff --git a/core/src/main/java/org/incendo/disruptor/DisruptionConfigImpl.java b/core/src/main/java/org/incendo/disruptor/DisruptionConfigImpl.java
new file mode 100644
index 0000000..57a4e9e
--- /dev/null
+++ b/core/src/main/java/org/incendo/disruptor/DisruptionConfigImpl.java
@@ -0,0 +1,38 @@
+//
+// MIT License
+//
+// Copyright (c) 2024 Incendo
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+package org.incendo.disruptor;
+
+import java.util.List;
+import org.apiguardian.api.API;
+import org.incendo.disruptor.disruption.Disruption;
+import org.incendo.disruptor.trigger.DisruptionTrigger;
+
+@API(status = API.Status.INTERNAL, since = "1.0.0")
+record DisruptionConfigImpl(
+ DisruptionTrigger trigger,
+ List disruptions,
+ DisruptionMode mode
+) implements DisruptionConfig {
+
+}
diff --git a/core/src/main/java/org/incendo/disruptor/DisruptionException.java b/core/src/main/java/org/incendo/disruptor/DisruptionException.java
new file mode 100644
index 0000000..4216c23
--- /dev/null
+++ b/core/src/main/java/org/incendo/disruptor/DisruptionException.java
@@ -0,0 +1,40 @@
+//
+// MIT License
+//
+// Copyright (c) 2024 Incendo
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+package org.incendo.disruptor;
+
+/**
+ * Exception that wraps exceptions thrown by a {@link org.incendo.disruptor.disruption.Disruption}
+ * if the original exception was not a runtime exception.
+ */
+public final class DisruptionException extends RuntimeException {
+
+ /**
+ * Creates a new disruption exception.
+ *
+ * @param cause cause of the exception
+ */
+ public DisruptionException(final Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/core/src/main/java/org/incendo/disruptor/DisruptionMode.java b/core/src/main/java/org/incendo/disruptor/DisruptionMode.java
new file mode 100644
index 0000000..7183eda
--- /dev/null
+++ b/core/src/main/java/org/incendo/disruptor/DisruptionMode.java
@@ -0,0 +1,38 @@
+//
+// MIT License
+//
+// Copyright (c) 2024 Incendo
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+package org.incendo.disruptor;
+
+import org.apiguardian.api.API;
+
+@API(status = API.Status.STABLE, since = "1.0.0")
+public enum DisruptionMode {
+ /**
+ * The disruption takes place before the invocation.
+ */
+ BEFORE,
+ /**
+ * The disruption takes place after the invocation.
+ */
+ AFTER
+}
diff --git a/core/src/main/java/org/incendo/disruptor/Disruptor.java b/core/src/main/java/org/incendo/disruptor/Disruptor.java
new file mode 100644
index 0000000..09a54a7
--- /dev/null
+++ b/core/src/main/java/org/incendo/disruptor/Disruptor.java
@@ -0,0 +1,184 @@
+//
+// MIT License
+//
+// Copyright (c) 2024 Incendo
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+package org.incendo.disruptor;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import org.apiguardian.api.API;
+
+/**
+ * The disruptor contains the configuration used by the Incendo Disruptor library.
+ * The configuration is immutable and may not be modified once it has been constructed.
+ *
+ *
Note: This interface should not be implemented. An instance should be built using {@link #builder()}.
+ *
+ * @since 1.0.0
+ */
+@API(status = API.Status.STABLE, since = "1.0.0")
+public interface Disruptor {
+
+ /**
+ * Creates a new {@link Disruptor} builder. The builder is mutable.
+ *
+ * @return a mutable builder
+ */
+ static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Return an empty disruptor instance with no groups configured.
+ *
+ * @return empty instance
+ */
+ static Disruptor empty() {
+ return DisruptorImpl.empty();
+ }
+
+ /**
+ * Returns the group identified by the given {@code name}, if it exists
+ *
+ * @param name group name
+ * @return optional that contain the group if it exists
+ */
+ Optional group(String name);
+
+ /**
+ * Runs the given {@code supplier}, invoking any relevant disruptions before and after.
+ * If a disruption throws an exception, it'll be propagated and the execution will terminate.
+ *
+ * @param group disruptor group
+ * @param supplier result supplier
+ * @return the result
+ * @param result type
+ */
+ default T disrupt(final String group, Supplier supplier) {
+ final DisruptorGroup disruptorGroup = this.group(group).orElse(null);
+ if (disruptorGroup == null) {
+ return supplier.get();
+ }
+
+ final DisruptorContext context = DisruptorContext.of(group);
+
+ this.triggerDisruptions(context, disruptorGroup, DisruptionMode.BEFORE);
+ final T result = supplier.get();
+ this.triggerDisruptions(context, disruptorGroup, DisruptionMode.AFTER);
+ return result;
+ }
+
+ /**
+ * Like {@link #disrupt(String, Supplier)} but without returning a result.
+ *
+ * @param group disruption group
+ * @param runnable runnable to wrap
+ */
+ default void disruptWithoutResult(final String group, Runnable runnable) {
+ this.disrupt(group, (Supplier