diff --git a/.github/workflows/maven-publish.yml b/.github/workflows/maven-publish.yml index d58053c..7d494f9 100644 --- a/.github/workflows/maven-publish.yml +++ b/.github/workflows/maven-publish.yml @@ -1,7 +1,7 @@ name: Maven Publish on: release: - types: [created] + types: [ created ] jobs: publish: runs-on: ubuntu-latest @@ -15,8 +15,14 @@ jobs: server-id: ossrh server-username: MAVEN_USERNAME server-password: MAVEN_PASSWORD + + - name: Install GPG secret key + run: | + cat <(echo -e "${{ secrets.OSSRH_GPG_SECRET_KEY }}") | gpg --batch --import + gpg --list-secret-keys --keyid-format LONG + - name: Publish package - run: mvn --batch-mode deploy + run: mvn --batch-mode -Dgpg.passphrase=${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} clean deploy -P release env: MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} diff --git a/pom.xml b/pom.xml index a9bb1b3..4ed88f6 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.digitalpetri.fsm strict-machine - 1.0.0-SNAPSHOT + 1.0.0 Strict Machine @@ -44,6 +44,9 @@ 11 UTF-8 + + 2.0.16 + 5.10.2 @@ -54,6 +57,12 @@ + + org.slf4j + slf4j-api + 2.0.16 + + org.junit.jupiter junit-jupiter-api diff --git a/src/main/java/com/digitalpetri/fsm/FsmContext.java b/src/main/java/com/digitalpetri/fsm/FsmContext.java index 270a4fa..48b8827 100644 --- a/src/main/java/com/digitalpetri/fsm/FsmContext.java +++ b/src/main/java/com/digitalpetri/fsm/FsmContext.java @@ -76,7 +76,7 @@ public interface FsmContext { * * @return the user-configurable context associated with this FSM instance. */ - Object getContext(); + Object getUserContext(); final class Key { diff --git a/src/main/java/com/digitalpetri/fsm/FsmLogging.java b/src/main/java/com/digitalpetri/fsm/FsmLogging.java deleted file mode 100644 index 59546f6..0000000 --- a/src/main/java/com/digitalpetri/fsm/FsmLogging.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2024 Kevin Herron - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ - -package com.digitalpetri.fsm; - -public final class FsmLogging { - - private FsmLogging() {} - - /** - * Configure the {@link Callback} to use for logging. - * - * @param callback the {@link Callback} to use for logging. - */ - public static void configure(Callback callback) { - Log.CALLBACK.set(callback); - } - - public interface Callback { - - /** - * Log a message at the given {@link Level}. - * - * @param context the user-configurable context. May be {@code null} even when configured if - * the message originates from a global context. - * @param level the {@link Level} to log at. - * @param message the message. - */ - void log(Object context, Level level, String message); - - /** - * Check if logging is enabled for the given {@link Level}. - * - * @param level the {@link Level} to check. - * @return {@code true} if logging is enabled for the given {@link Level}. - */ - default boolean isEnabled(Level level) { - return true; - } - - } - - public enum Level { - TRACE, - DEBUG, - INFO, - WARN, - ERROR - } - - /** - * A logging {@link Callback} that logs to {@link System#out}. - */ - public static final Callback SYSTEM_OUT_CALLBACK = - (context, level, message) -> System.out.printf("%s: %s%n", level, message); - -} diff --git a/src/main/java/com/digitalpetri/fsm/Log.java b/src/main/java/com/digitalpetri/fsm/Log.java deleted file mode 100644 index 1a22798..0000000 --- a/src/main/java/com/digitalpetri/fsm/Log.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2024 Kevin Herron - * - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - */ - -package com.digitalpetri.fsm; - -import com.digitalpetri.fsm.FsmLogging.Level; -import java.util.concurrent.atomic.AtomicReference; - -public class Log { - - public static final AtomicReference CALLBACK = new AtomicReference<>(); - - private Log() {} - - /** - * Log a message at {@link Level#TRACE}. - * - * @param context the user-configurable context. May be {@code null} even when configured. - * @param format the message format. - * @param args the message arguments. - */ - public static void trace(Object context, String format, Object... args) { - log(context, Level.TRACE, format, args); - } - - /** - * Log a message at {@link Level#DEBUG}. - * - * @param context the user-configurable context. May be {@code null} even when configured. - * @param format the message format. - * @param args the message arguments. - */ - public static void debug(Object context, String format, Object... args) { - log(context, Level.DEBUG, format, args); - } - - /** - * Log a message at {@link Level#INFO}. - * - * @param context the user-configurable context. May be {@code null} even when configured. - * @param format the message format. - * @param args the message arguments. - */ - public static void info(Object context, String format, Object... args) { - log(context, Level.INFO, format, args); - } - - /** - * Log a message at {@link Level#WARN}. - * - * @param context the user-configurable context. May be {@code null} even when configured. - * @param format the message format. - * @param args the message arguments. - */ - public static void warn(Object context, String format, Object... args) { - log(context, Level.WARN, format, args); - } - - /** - * Log a message at {@link Level#ERROR}. - * - * @param context the user-configurable context. May be {@code null} even when configured. - * @param format the message format. - * @param args the message arguments. - */ - public static void error(Object context, String format, Object... args) { - log(context, Level.ERROR, format, args); - } - - /** - * Check if logging is enabled for the given {@link Level}. - * - * @param level the {@link Level} to check. - * @return {@code true} if logging is enabled for the given {@link Level}. - */ - public static boolean isLevelEnabled(FsmLogging.Level level) { - FsmLogging.Callback callback = CALLBACK.get(); - - return callback == null || callback.isEnabled(level); - } - - /** - * Log a message at the given {@link Level}. - * - * @param context the user-configurable context. May be {@code null} even when configured. - * @param level the {@link Level} to log at. - * @param format the message format. - * @param args the message arguments. - */ - public static void log(Object context, Level level, String format, Object... args) { - FsmLogging.Callback callback = CALLBACK.get(); - if (callback != null && callback.isEnabled(level)) { - callback.log(context, level, String.format(format, args)); - } - } - -} diff --git a/src/main/java/com/digitalpetri/fsm/StrictMachine.java b/src/main/java/com/digitalpetri/fsm/StrictMachine.java index e4c6b41..643ad83 100644 --- a/src/main/java/com/digitalpetri/fsm/StrictMachine.java +++ b/src/main/java/com/digitalpetri/fsm/StrictMachine.java @@ -10,7 +10,6 @@ package com.digitalpetri.fsm; -import com.digitalpetri.fsm.FsmLogging.Level; import com.digitalpetri.fsm.dsl.ActionContext; import com.digitalpetri.fsm.dsl.ActionProxy; import com.digitalpetri.fsm.dsl.Transition; @@ -26,6 +25,9 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; import java.util.function.Function; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; public class StrictMachine implements Fsm { @@ -38,23 +40,29 @@ public class StrictMachine implements Fsm { private final Map, Object> contextValues = new ConcurrentHashMap<>(); private final AtomicReference state = new AtomicReference<>(); - private final Object context; + private final Logger logger; + private final Map mdc; private final Executor executor; + private final Object userContext; private final ActionProxy actionProxy; private final List> transitions; private final List> transitionActions; public StrictMachine( - Object context, + String loggerName, + Map mdc, Executor executor, + Object userContext, ActionProxy actionProxy, S initialState, List> transitions, List> transitionActions ) { - this.context = context; + this.logger = LoggerFactory.getLogger(loggerName); + this.mdc = mdc; this.executor = executor; + this.userContext = userContext; this.actionProxy = actionProxy; this.transitions = transitions; this.transitionActions = transitionActions; @@ -171,14 +179,18 @@ public void run() { state.set(nextState); - if (Log.isLevelEnabled(Level.DEBUG)) { - Log.debug( - context, - "%s x %s = %s", - padRight(String.format("S(%s)", currState)), - padRight(String.format("E(%s)", event)), - padRight(String.format("S'(%s)", nextState)) - ); + if (logger.isDebugEnabled()) { + mdc.forEach(MDC::put); + try { + logger.debug( + "{} x {} = {}", + padRight(String.format("S(%s)", currState)), + padRight(String.format("E(%s)", event)), + padRight(String.format("S'(%s)", nextState)) + ); + } finally { + mdc.keySet().forEach(MDC::remove); + } } var actionContext = new ActionContextImpl( @@ -195,27 +207,49 @@ public void run() { } } - Log.trace(context, "found %d matching TransitionActions", matchingActions.size()); + if (logger.isTraceEnabled()) { + mdc.forEach(MDC::put); + try { + logger.trace("found {} matching TransitionActions", matchingActions.size()); + } finally { + mdc.keySet().forEach(MDC::remove); + } + } matchingActions.forEach(transitionAction -> { try { if (actionProxy == null) { - Log.trace(context, "executing TransitionAction: %s", transitionAction); + if (logger.isTraceEnabled()) { + mdc.forEach(MDC::put); + try { + logger.trace("executing TransitionAction: {}", transitionAction); + } finally { + mdc.keySet().forEach(MDC::remove); + } + } transitionAction.execute(actionContext); } else { - Log.trace( - context, - "executing (via proxy) TransitionAction: %s", transitionAction - ); + if (logger.isTraceEnabled()) { + mdc.forEach(MDC::put); + try { + logger.trace("executing (via proxy) TransitionAction: {}", transitionAction); + } finally { + mdc.keySet().forEach(MDC::remove); + } + } actionProxy.execute(actionContext, transitionAction::execute); } } catch (Throwable ex) { - Log.warn( - context, - "Uncaught Throwable executing TransitionAction: %s\n%s", transitionAction, ex - ); + + mdc.forEach(MDC::put); + try { + logger.warn("uncaught Throwable executing TransitionAction: {}", + transitionAction, ex); + } finally { + mdc.keySet().forEach(MDC::remove); + } } }); @@ -318,8 +352,8 @@ public void set(Key key, Object value) { } @Override - public Object getContext() { - return context; + public Object getUserContext() { + return userContext; } } diff --git a/src/main/java/com/digitalpetri/fsm/dsl/FsmBuilder.java b/src/main/java/com/digitalpetri/fsm/dsl/FsmBuilder.java index e39ae79..c794524 100644 --- a/src/main/java/com/digitalpetri/fsm/dsl/FsmBuilder.java +++ b/src/main/java/com/digitalpetri/fsm/dsl/FsmBuilder.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; @@ -34,18 +35,34 @@ public class FsmBuilder, E> { private ActionProxy actionProxy = null; - private final Object context; + private final String loggerName; + private final Map mdc; private final Executor executor; + private final Object userContext; public FsmBuilder() { - this(INSTANCE_ID.getAndIncrement(), EXECUTOR_SERVICE); + this( + StrictMachine.class.getName(), + Map.of(), + EXECUTOR_SERVICE, + null + ); } - public FsmBuilder(Object context, Executor executor) { - this.context = context != null ? context : INSTANCE_ID.getAndIncrement(); + public FsmBuilder( + String loggerName, + Map mdc, + Executor executor, + Object userContext + ) { + + this.loggerName = loggerName; + this.mdc = mdc; this.executor = executor; + this.userContext = userContext; } + /** * Start defining a {@link Transition} from state {@code state}. * @@ -145,8 +162,10 @@ public void setActionProxy(ActionProxy actionProxy) { public Fsm build(S initialState) { return new StrictMachine<>( - context, + loggerName, + mdc, executor, + userContext, actionProxy, initialState, new ArrayList<>(transitions),