mirror of
https://github.com/2OOP/pism.git
synced 2026-02-04 19:04:49 +00:00
refactor
This commit is contained in:
202
framework/src/main/java/org/toop/Logging.java
Normal file
202
framework/src/main/java/org/toop/Logging.java
Normal file
@@ -0,0 +1,202 @@
|
||||
package org.toop.framework;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.core.LoggerContext;
|
||||
import org.apache.logging.log4j.core.config.Configuration;
|
||||
import org.apache.logging.log4j.core.config.LoggerConfig;
|
||||
|
||||
/**
|
||||
* Utility class for configuring logging levels dynamically at runtime using Log4j 2.
|
||||
*
|
||||
* <p>Provides methods to enable or disable logs globally or per class, with support for specifying
|
||||
* log levels either via {@link Level} enums or string names.
|
||||
*/
|
||||
// Todo: refactor
|
||||
public final class Logging {
|
||||
|
||||
/** Disables all logging globally by setting the root logger level to {@link Level#OFF}. */
|
||||
public static void disableAllLogs() {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
LoggerConfig rootLoggerConfig = config.getRootLogger();
|
||||
rootLoggerConfig.setLevel(Level.OFF);
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
|
||||
/** Enables all logging globally by setting the root logger level to {@link Level#ALL}. */
|
||||
public static void enableAllLogs() {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
LoggerConfig rootLoggerConfig = config.getRootLogger();
|
||||
rootLoggerConfig.setLevel(Level.ALL);
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables global logging at a specific level by setting the root logger.
|
||||
*
|
||||
* @param level the logging level to enable for all logs
|
||||
*/
|
||||
public static void enableAllLogs(Level level) {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
LoggerConfig rootLoggerConfig = config.getRootLogger();
|
||||
rootLoggerConfig.setLevel(level);
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies whether the provided string corresponds to a valid class name.
|
||||
*
|
||||
* @param className fully-qualified class name to check
|
||||
* @return true if the class exists, false otherwise
|
||||
*/
|
||||
private static boolean verifyStringIsActualClass(String className) {
|
||||
try {
|
||||
Class.forName(className);
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper to disable logs for a specific class by name.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
*/
|
||||
private static void disableLogsForClassInternal(String className) {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
config.removeLogger(className);
|
||||
LoggerConfig specificConfig = new LoggerConfig(className, Level.OFF, false);
|
||||
config.addLogger(className, specificConfig);
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables logs for a specific class.
|
||||
*
|
||||
* @param class_ the class for which logs should be disabled
|
||||
* @param <T> type of the class
|
||||
*/
|
||||
public static <T> void disableLogsForClass(Class<T> class_) {
|
||||
disableLogsForClassInternal(class_.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables logs for a class specified by fully-qualified name, if the class exists.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
*/
|
||||
public static void disableLogsForClass(String className) {
|
||||
if (verifyStringIsActualClass(className)) {
|
||||
disableLogsForClassInternal(className);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal helper to enable logs for a specific class at a specific level.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
* @param level logging level to set
|
||||
*/
|
||||
private static void enableLogsForClassInternal(String className, Level level) {
|
||||
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = ctx.getConfiguration();
|
||||
LoggerConfig loggerConfig = config.getLoggers().get(className);
|
||||
if (loggerConfig == null) {
|
||||
loggerConfig = new LoggerConfig(className, level, false);
|
||||
config.addLogger(className, loggerConfig);
|
||||
} else {
|
||||
loggerConfig.setLevel(level);
|
||||
}
|
||||
ctx.updateLoggers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables logging for a class at a specific level.
|
||||
*
|
||||
* @param class_ class to configure
|
||||
* @param levelToLog the logging level to set
|
||||
* @param <T> type of the class
|
||||
*/
|
||||
public static <T> void enableLogsForClass(Class<T> class_, Level levelToLog) {
|
||||
enableLogsForClassInternal(class_.getName(), levelToLog);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables logging for a class specified by name at a specific level, if the class exists.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
* @param levelToLog the logging level to set
|
||||
*/
|
||||
public static void enableLogsForClass(String className, Level levelToLog) {
|
||||
if (verifyStringIsActualClass(className)) {
|
||||
enableLogsForClassInternal(className, levelToLog);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables logging for a class specified by name at a specific level using a string.
|
||||
*
|
||||
* @param className fully-qualified class name
|
||||
* @param levelToLog name of the logging level (e.g., "DEBUG", "INFO")
|
||||
*/
|
||||
public static void enableLogsForClass(String className, String levelToLog) {
|
||||
Level level = Level.valueOf(levelToLog.trim().toUpperCase());
|
||||
if (level != null && verifyStringIsActualClass(className)) {
|
||||
enableLogsForClassInternal(className, level);
|
||||
}
|
||||
}
|
||||
|
||||
/** Convenience methods for enabling logs at specific levels for classes. */
|
||||
public static <T> void enableAllLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.ALL);
|
||||
}
|
||||
|
||||
public static void enableAllLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.ALL);
|
||||
}
|
||||
|
||||
public static <T> void enableDebugLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.DEBUG);
|
||||
}
|
||||
|
||||
public static void enableDebugLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.DEBUG);
|
||||
}
|
||||
|
||||
public static <T> void enableErrorLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.ERROR);
|
||||
}
|
||||
|
||||
public static void enableErrorLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.ERROR);
|
||||
}
|
||||
|
||||
public static <T> void enableFatalLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.FATAL);
|
||||
}
|
||||
|
||||
public static void enableFatalLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.FATAL);
|
||||
}
|
||||
|
||||
public static <T> void enableInfoLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.INFO);
|
||||
}
|
||||
|
||||
public static void enableInfoLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.INFO);
|
||||
}
|
||||
|
||||
public static <T> void enableTraceLogsForClass(Class<T> class_) {
|
||||
enableLogsForClass(class_, Level.TRACE);
|
||||
}
|
||||
|
||||
public static void enableTraceLogsForClass(String className) {
|
||||
enableLogsForClass(className, Level.TRACE);
|
||||
}
|
||||
}
|
||||
221
framework/src/main/java/org/toop/eventbus/EventFlow.java
Normal file
221
framework/src/main/java/org/toop/eventbus/EventFlow.java
Normal file
@@ -0,0 +1,221 @@
|
||||
package org.toop.framework.eventbus;
|
||||
|
||||
import org.toop.framework.eventbus.events.EventWithUuid;
|
||||
import org.toop.framework.eventbus.events.IEvent;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* EventFlow is a utility class for creating, posting, and optionally subscribing to events
|
||||
* in a type-safe and chainable manner. It is designed to work with the {@link GlobalEventBus}.
|
||||
*
|
||||
* <p>This class supports automatic UUID assignment for {@link EventWithUuid} events,
|
||||
* and allows filtering subscribers so they only respond to events with a specific UUID.
|
||||
* All subscription methods are chainable, and you can configure automatic unsubscription
|
||||
* after an event has been successfully handled.</p>
|
||||
*/
|
||||
public class EventFlow {
|
||||
|
||||
/** Lookup object used for dynamically invoking constructors via MethodHandles. */
|
||||
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
|
||||
|
||||
/** Cache of constructor handles for event classes to avoid repeated reflection lookups. */
|
||||
private static final Map<Class<?>, MethodHandle> CONSTRUCTOR_CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
/** Automatically assigned UUID for {@link EventWithUuid} events. */
|
||||
private String eventId = null;
|
||||
|
||||
/** The event instance created by this publisher. */
|
||||
private IEvent event = null;
|
||||
|
||||
/** The listener returned by GlobalEventBus subscription. Used for unsubscription. */
|
||||
private Object listener;
|
||||
|
||||
/** Flag indicating whether to automatically unsubscribe the listener after success. */
|
||||
private boolean unsubscribeAfterSuccess = false;
|
||||
|
||||
/** Holds the results returned from the subscribed event, if any. */
|
||||
private Map<String, Object> result = null;
|
||||
|
||||
/** Empty constructor (event must be added via {@link #addPostEvent}). */
|
||||
public EventFlow() {}
|
||||
|
||||
/**
|
||||
* Instantiate an event of the given class and store it in this publisher.
|
||||
*/
|
||||
public <T extends IEvent> EventFlow addPostEvent(Class<T> eventClass, Object... args) {
|
||||
try {
|
||||
boolean isUuidEvent = EventWithUuid.class.isAssignableFrom(eventClass);
|
||||
|
||||
MethodHandle ctorHandle = CONSTRUCTOR_CACHE.computeIfAbsent(eventClass, cls -> {
|
||||
try {
|
||||
Class<?>[] paramTypes = cls.getDeclaredConstructors()[0].getParameterTypes();
|
||||
MethodType mt = MethodType.methodType(void.class, paramTypes);
|
||||
return LOOKUP.findConstructor(cls, mt);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to find constructor handle for " + cls, e);
|
||||
}
|
||||
});
|
||||
|
||||
Object[] finalArgs;
|
||||
int expectedParamCount = ctorHandle.type().parameterCount();
|
||||
|
||||
if (isUuidEvent && args.length < expectedParamCount) {
|
||||
this.eventId = UUID.randomUUID().toString();
|
||||
finalArgs = new Object[args.length + 1];
|
||||
System.arraycopy(args, 0, finalArgs, 0, args.length);
|
||||
finalArgs[args.length] = this.eventId;
|
||||
} else if (isUuidEvent) {
|
||||
this.eventId = (String) args[args.length - 1];
|
||||
finalArgs = args;
|
||||
} else {
|
||||
finalArgs = args;
|
||||
}
|
||||
|
||||
this.event = (IEvent) ctorHandle.invokeWithArguments(finalArgs);
|
||||
return this;
|
||||
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException("Failed to instantiate event", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start listening for a response event type, chainable with perform().
|
||||
*/
|
||||
public <TT extends IEvent> ResponseBuilder<TT> onResponse(Class<TT> eventClass) {
|
||||
return new ResponseBuilder<>(this, eventClass);
|
||||
}
|
||||
|
||||
public static class ResponseBuilder<R extends IEvent> {
|
||||
private final EventFlow parent;
|
||||
private final Class<R> responseClass;
|
||||
|
||||
ResponseBuilder(EventFlow parent, Class<R> responseClass) {
|
||||
this.parent = parent;
|
||||
this.responseClass = responseClass;
|
||||
}
|
||||
|
||||
/** Finalize the subscription */
|
||||
public EventFlow perform(Consumer<R> action) {
|
||||
parent.listener = GlobalEventBus.subscribe(responseClass, event -> {
|
||||
action.accept(responseClass.cast(event));
|
||||
if (parent.unsubscribeAfterSuccess && parent.listener != null) {
|
||||
GlobalEventBus.unsubscribe(parent.listener);
|
||||
}
|
||||
});
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe by ID: only fires if UUID matches this publisher's eventId.
|
||||
*/
|
||||
public <TT extends EventWithUuid> EventFlow onResponse(Class<TT> eventClass, Consumer<TT> action) {
|
||||
this.listener = GlobalEventBus.subscribe(eventClass, event -> {
|
||||
if (event.eventId().equals(this.eventId)) {
|
||||
action.accept(event);
|
||||
if (unsubscribeAfterSuccess && listener != null) {
|
||||
GlobalEventBus.unsubscribe(listener);
|
||||
}
|
||||
this.result = event.result();
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe by ID without explicit class.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <TT extends EventWithUuid> EventFlow onResponse(Consumer<TT> action) {
|
||||
this.listener = GlobalEventBus.subscribe(event -> {
|
||||
if (event instanceof EventWithUuid uuidEvent) {
|
||||
if (uuidEvent.eventId().equals(this.eventId)) {
|
||||
try {
|
||||
TT typedEvent = (TT) uuidEvent;
|
||||
action.accept(typedEvent);
|
||||
if (unsubscribeAfterSuccess && listener != null) {
|
||||
GlobalEventBus.unsubscribe(listener);
|
||||
}
|
||||
this.result = typedEvent.result();
|
||||
} catch (ClassCastException ignored) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
// choose event type
|
||||
public <TT extends IEvent> EventSubscriberBuilder<TT> onEvent(Class<TT> eventClass) {
|
||||
return new EventSubscriberBuilder<>(this, eventClass);
|
||||
}
|
||||
|
||||
// One-liner shorthand
|
||||
public <TT extends IEvent> EventFlow listen(Class<TT> eventClass, Consumer<TT> action) {
|
||||
return this.onEvent(eventClass).perform(action);
|
||||
}
|
||||
|
||||
// Builder for chaining .onEvent(...).perform(...)
|
||||
public static class EventSubscriberBuilder<TT extends IEvent> {
|
||||
private final EventFlow publisher;
|
||||
private final Class<TT> eventClass;
|
||||
|
||||
EventSubscriberBuilder(EventFlow publisher, Class<TT> eventClass) {
|
||||
this.publisher = publisher;
|
||||
this.eventClass = eventClass;
|
||||
}
|
||||
|
||||
public EventFlow perform(Consumer<TT> action) {
|
||||
publisher.listener = GlobalEventBus.subscribe(eventClass, event -> {
|
||||
action.accept(eventClass.cast(event));
|
||||
if (publisher.unsubscribeAfterSuccess && publisher.listener != null) {
|
||||
GlobalEventBus.unsubscribe(publisher.listener);
|
||||
}
|
||||
});
|
||||
return publisher;
|
||||
}
|
||||
}
|
||||
|
||||
/** Post synchronously */
|
||||
public EventFlow postEvent() {
|
||||
GlobalEventBus.post(event);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Post asynchronously */
|
||||
public EventFlow asyncPostEvent() {
|
||||
GlobalEventBus.postAsync(event);
|
||||
return this;
|
||||
}
|
||||
|
||||
public EventFlow unsubscribeAfterSuccess() {
|
||||
this.unsubscribeAfterSuccess = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public EventFlow unsubscribeNow() {
|
||||
if (unsubscribeAfterSuccess && listener != null) {
|
||||
GlobalEventBus.unsubscribe(listener);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<String, Object> getResult() {
|
||||
return this.result;
|
||||
}
|
||||
|
||||
public IEvent getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
public String getEventId() {
|
||||
return eventId;
|
||||
}
|
||||
}
|
||||
203
framework/src/main/java/org/toop/eventbus/GlobalEventBus.java
Normal file
203
framework/src/main/java/org/toop/eventbus/GlobalEventBus.java
Normal file
@@ -0,0 +1,203 @@
|
||||
package org.toop.framework.eventbus;
|
||||
|
||||
import org.toop.framework.eventbus.events.EventWithUuid;
|
||||
import org.toop.framework.eventbus.events.IEvent;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* GlobalEventBus is a high-throughput, thread-safe event bus for publishing and subscribing
|
||||
* to events within the application.
|
||||
*
|
||||
* <p>It supports:</p>
|
||||
* <ul>
|
||||
* <li>Type-specific subscriptions via {@link #subscribe(Class, Consumer)}</li>
|
||||
* <li>UUID-specific subscriptions via {@link #subscribeById(Class, String, Consumer)}</li>
|
||||
* <li>Asynchronous posting of events with automatic queueing and fallback</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>Performance note:</b> Directly using {@link GlobalEventBus} is possible,
|
||||
* but for safer type handling, automatic UUID management, and easier unsubscription,
|
||||
* it is recommended to use {@link EventPublisher} whenever possible.</p>
|
||||
*
|
||||
* <p>The bus maintains a fixed pool of worker threads that continuously process queued events.</p>
|
||||
*/
|
||||
public final class GlobalEventBus {
|
||||
|
||||
/** Number of worker threads, set to the number of available CPU cores. */
|
||||
private static final int WORKERS = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
/** Queue for asynchronous event processing. */
|
||||
private static final BlockingQueue<IEvent> EVENT_QUEUE = new LinkedBlockingQueue<>(WORKERS * 1024);
|
||||
|
||||
/** Map of event class to type-specific listeners. */
|
||||
private static final Map<Class<?>, CopyOnWriteArrayList<Consumer<? super IEvent>>> LISTENERS = new ConcurrentHashMap<>();
|
||||
|
||||
/** Map of event class to UUID-specific listeners. */
|
||||
private static final Map<Class<?>, ConcurrentHashMap<String, Consumer<? extends EventWithUuid>>> UUID_LISTENERS = new ConcurrentHashMap<>();
|
||||
|
||||
/** Thread pool for worker threads processing queued events. */
|
||||
private static final ExecutorService WORKER_POOL = Executors.newFixedThreadPool(WORKERS, r -> {
|
||||
Thread t = new Thread(r, "EventBus-Worker-" + r.hashCode());
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
|
||||
// Initialize worker threads
|
||||
static {
|
||||
for (int i = 0; i < WORKERS; i++) {
|
||||
WORKER_POOL.submit(GlobalEventBus::workerLoop);
|
||||
}
|
||||
}
|
||||
|
||||
/** Private constructor to prevent instantiation. */
|
||||
private GlobalEventBus() {}
|
||||
|
||||
/** Continuously processes events from the queue and dispatches them to listeners. */
|
||||
private static void workerLoop() {
|
||||
try {
|
||||
while (true) {
|
||||
IEvent event = EVENT_QUEUE.take();
|
||||
dispatchEvent(event);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes a type-specific listener for all events of a given class.
|
||||
*
|
||||
* @param eventClass the class of events to subscribe to
|
||||
* @param listener the action to execute when the event is posted
|
||||
* @param <T> the event type
|
||||
* @return the provided listener for possible unsubscription
|
||||
*/
|
||||
public static <T extends IEvent> Consumer<T> subscribe(Class<T> eventClass, Consumer<T> listener) {
|
||||
CopyOnWriteArrayList<Consumer<? super IEvent>> list =
|
||||
LISTENERS.computeIfAbsent(eventClass, k -> new CopyOnWriteArrayList<>());
|
||||
list.add(event -> listener.accept(eventClass.cast(event)));
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes a generic listener for all events (no type filtering).
|
||||
*
|
||||
* @param listener the action to execute on any event
|
||||
* @return the provided listener for possible unsubscription
|
||||
*/
|
||||
public static Consumer<Object> subscribe(Consumer<Object> listener) {
|
||||
LISTENERS.computeIfAbsent(Object.class, _ -> new CopyOnWriteArrayList<>())
|
||||
.add(listener);
|
||||
return listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes a listener for a specific {@link EventWithUuid} identified by its UUID.
|
||||
*
|
||||
* @param eventClass the class of the UUID event
|
||||
* @param eventId the UUID of the event to listen for
|
||||
* @param listener the action to execute when the event with the matching UUID is posted
|
||||
* @param <T> the event type extending EventWithUuid
|
||||
*/
|
||||
public static <T extends EventWithUuid> void subscribeById(Class<T> eventClass, String eventId, Consumer<T> listener) {
|
||||
UUID_LISTENERS
|
||||
.computeIfAbsent(eventClass, _ -> new ConcurrentHashMap<>())
|
||||
.put(eventId, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes a previously registered listener.
|
||||
*
|
||||
* @param listener the listener to remove
|
||||
*/
|
||||
public static void unsubscribe(Object listener) {
|
||||
LISTENERS.values().forEach(list -> list.remove(listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes a UUID-specific listener.
|
||||
*
|
||||
* @param eventClass the class of the UUID event
|
||||
* @param eventId the UUID of the listener to remove
|
||||
* @param <T> the event type extending EventWithUuid
|
||||
*/
|
||||
public static <T extends EventWithUuid> void unsubscribeById(Class<T> eventClass, String eventId) {
|
||||
Map<String, Consumer<? extends EventWithUuid>> map = UUID_LISTENERS.get(eventClass);
|
||||
if (map != null) map.remove(eventId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts an event synchronously to all subscribed listeners.
|
||||
*
|
||||
* @param event the event instance to post
|
||||
* @param <T> the event type
|
||||
*/
|
||||
public static <T extends IEvent> void post(T event) {
|
||||
dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Posts an event asynchronously by adding it to the internal queue.
|
||||
* If the queue is full, the event is dispatched synchronously.
|
||||
*
|
||||
* @param event the event instance to post
|
||||
* @param <T> the event type
|
||||
*/
|
||||
public static <T extends IEvent> void postAsync(T event) {
|
||||
if (!EVENT_QUEUE.offer(event)) {
|
||||
dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
/** Dispatches an event to all type-specific, generic, and UUID-specific listeners. */
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void dispatchEvent(IEvent event) {
|
||||
Class<?> clazz = event.getClass();
|
||||
|
||||
CopyOnWriteArrayList<Consumer<? super IEvent>> classListeners = LISTENERS.get(clazz);
|
||||
if (classListeners != null) {
|
||||
for (Consumer<? super IEvent> listener : classListeners) {
|
||||
try { listener.accept(event); } catch (Throwable ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
CopyOnWriteArrayList<Consumer<? super IEvent>> genericListeners = LISTENERS.get(Object.class);
|
||||
if (genericListeners != null) {
|
||||
for (Consumer<? super IEvent> listener : genericListeners) {
|
||||
try { listener.accept(event); } catch (Throwable ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (event instanceof EventWithUuid uuidEvent) {
|
||||
Map<String, Consumer<? extends EventWithUuid>> map = UUID_LISTENERS.get(clazz);
|
||||
if (map != null) {
|
||||
Consumer<EventWithUuid> listener = (Consumer<EventWithUuid>) map.remove(uuidEvent.eventId());
|
||||
if (listener != null) {
|
||||
try { listener.accept(uuidEvent); } catch (Throwable ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the bus immediately, clearing all listeners and queued events.
|
||||
* Worker threads are stopped.
|
||||
*/
|
||||
public static void shutdown() {
|
||||
WORKER_POOL.shutdownNow();
|
||||
LISTENERS.clear();
|
||||
UUID_LISTENERS.clear();
|
||||
EVENT_QUEUE.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all listeners and UUID-specific subscriptions without stopping worker threads.
|
||||
*/
|
||||
public static void reset() {
|
||||
LISTENERS.clear();
|
||||
UUID_LISTENERS.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.toop.framework.eventbus.events;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface EventWithUuid extends IEvent {
|
||||
Map<String, Object> result();
|
||||
String eventId();
|
||||
}
|
||||
184
framework/src/main/java/org/toop/eventbus/events/Events.java
Normal file
184
framework/src/main/java/org/toop/eventbus/events/Events.java
Normal file
@@ -0,0 +1,184 @@
|
||||
package org.toop.framework.eventbus.events;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/** Events that are used in the GlobalEventBus class. */
|
||||
public class Events implements IEvent {
|
||||
|
||||
/**
|
||||
* WIP, DO NOT USE!
|
||||
*
|
||||
* @param eventName
|
||||
* @param args
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static Object get(String eventName, Object... args) throws Exception {
|
||||
Class<?> clazz = Class.forName("org.toop.eventbus.events.Events$ServerEvents$" + eventName);
|
||||
Class<?>[] paramTypes = Arrays.stream(args).map(Object::getClass).toArray(Class<?>[]::new);
|
||||
Constructor<?> constructor = clazz.getConstructor(paramTypes);
|
||||
return constructor.newInstance(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* WIP, DO NOT USE!
|
||||
*
|
||||
* @param eventCategory
|
||||
* @param eventName
|
||||
* @param args
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static Object get(String eventCategory, String eventName, Object... args)
|
||||
throws Exception {
|
||||
Class<?> clazz =
|
||||
Class.forName("org.toop.eventbus.events.Events$" + eventCategory + "$" + eventName);
|
||||
Class<?>[] paramTypes = Arrays.stream(args).map(Object::getClass).toArray(Class<?>[]::new);
|
||||
Constructor<?> constructor = clazz.getConstructor(paramTypes);
|
||||
return constructor.newInstance(args);
|
||||
}
|
||||
|
||||
/**
|
||||
* WIP, DO NOT USE!
|
||||
*
|
||||
* @param eventName
|
||||
* @param args
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static Object get2(String eventName, Object... args) throws Exception {
|
||||
// Fully qualified class name
|
||||
String className = "org.toop.server.backend.Events$ServerEvents$" + eventName;
|
||||
|
||||
// Load the class
|
||||
Class<?> clazz = Class.forName(className);
|
||||
|
||||
// Build array of argument types
|
||||
Class<?>[] paramTypes = new Class[args.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
paramTypes[i] = args[i].getClass();
|
||||
}
|
||||
|
||||
// Get the constructor
|
||||
Constructor<?> constructor = clazz.getConstructor(paramTypes);
|
||||
|
||||
// Create a new instance
|
||||
return constructor.newInstance(args);
|
||||
}
|
||||
|
||||
public static class ServerEvents {
|
||||
|
||||
/**
|
||||
* BLOCKING Requests all active servers. The result is returned via the provided
|
||||
* CompletableFuture.
|
||||
*
|
||||
* @param future List of all servers in string form.
|
||||
*/
|
||||
public record RequestsAllServers(CompletableFuture<String> future) {}
|
||||
|
||||
/** Forces closing all active servers immediately. */
|
||||
public record ForceCloseAllServers() implements IEvent {}
|
||||
|
||||
/**
|
||||
* Requests starting a server with a specific port and game type.
|
||||
*
|
||||
* @param port The port to open the server.
|
||||
* @param gameType Either "tictactoe" or ...
|
||||
*/
|
||||
public record StartServer(int port, String gameType) implements IEvent {}
|
||||
|
||||
/**
|
||||
* BLOCKING Requests starting a server with a specific port and game type, and returns a
|
||||
* CompletableFuture that completes when the server has started.
|
||||
*
|
||||
* @param port The port to open the server.
|
||||
* @param gameType Either "tictactoe" or ...
|
||||
* @param future The uuid of the server.
|
||||
*/
|
||||
public record StartServerRequest(
|
||||
int port, String gameType, CompletableFuture<String> future) implements IEvent{}
|
||||
|
||||
/**
|
||||
* Represents a server that has successfully started.
|
||||
*
|
||||
* @param uuid The unique identifier of the server.
|
||||
* @param port The port the server is listening on.
|
||||
*/
|
||||
public record ServerStarted(String uuid, int port) implements IEvent {}
|
||||
|
||||
/**
|
||||
* BLOCKING Requests creation of a TicTacToe game on a specific server.
|
||||
*
|
||||
* @param serverUuid The unique identifier of the server where the game will be created.
|
||||
* @param playerA The name of the first player.
|
||||
* @param playerB The name of the second player.
|
||||
* @param future The game UUID when the game is created.
|
||||
*/
|
||||
public record CreateTicTacToeGameRequest(
|
||||
String serverUuid,
|
||||
String playerA,
|
||||
String playerB,
|
||||
CompletableFuture<String> future) implements IEvent {}
|
||||
|
||||
/**
|
||||
* Requests running a TicTacToe game on a specific server.
|
||||
*
|
||||
* @param serverUuid The unique identifier of the server.
|
||||
* @param gameUuid The UUID of the game to run.
|
||||
*/
|
||||
public record RunTicTacToeGame(String serverUuid, String gameUuid) implements IEvent {}
|
||||
|
||||
/**
|
||||
* Requests ending a TicTacToe game on a specific server.
|
||||
*
|
||||
* @param serverUuid The UUID of the server the game is running on.
|
||||
* @param gameUuid The UUID of the game to end.
|
||||
*/
|
||||
public record EndTicTacToeGame(String serverUuid, String gameUuid) implements IEvent {}
|
||||
|
||||
// public record StartGameConnectionRequest(String ip, String port,
|
||||
// CompletableFuture<String> future) {}
|
||||
|
||||
/**
|
||||
* Triggers on changing the server IP.
|
||||
*
|
||||
* @param ip The new IP address.
|
||||
*/
|
||||
public record OnChangingServerIp(String ip) {}
|
||||
|
||||
/**
|
||||
* Triggers on changing the server port.
|
||||
*
|
||||
* @param port The new port.
|
||||
*/
|
||||
public record OnChangingServerPort(int port) {}
|
||||
|
||||
/** Triggers when a cell is clicked in one of the game boards. */
|
||||
public record CellClicked(int cell) {}
|
||||
}
|
||||
|
||||
public static class EventBusEvents {}
|
||||
|
||||
public static class WindowEvents {
|
||||
/** Triggers when the window wants to quit. */
|
||||
public record OnQuitRequested() implements IEvent {}
|
||||
|
||||
/** Triggers when the window is resized. */
|
||||
// public record OnResize(Window.Size size) {}
|
||||
|
||||
/** Triggers when the mouse is moved within the window. */
|
||||
public record OnMouseMove(int x, int y) implements IEvent {}
|
||||
|
||||
/** Triggers when the mouse is clicked within the window. */
|
||||
public record OnMouseClick(int button) {}
|
||||
|
||||
/** Triggers when the mouse is released within the window. */
|
||||
public record OnMouseRelease(int button) {}
|
||||
}
|
||||
|
||||
public static class TttEvents {}
|
||||
|
||||
public static class AiTttEvents {}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package org.toop.framework.eventbus.events;
|
||||
|
||||
public interface IEvent {}
|
||||
@@ -0,0 +1,189 @@
|
||||
package org.toop.framework.eventbus.events;
|
||||
|
||||
import org.toop.backend.tictactoe.TicTacToeServer;
|
||||
import org.toop.framework.networking.NetworkingGameClientHandler;
|
||||
|
||||
import java.lang.reflect.RecordComponent;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class NetworkEvents extends Events {
|
||||
|
||||
/**
|
||||
* BLOCKING Requests all active connections. The result is returned via the provided
|
||||
* CompletableFuture.
|
||||
*
|
||||
* @param future List of all connections in string form.
|
||||
*/
|
||||
public record RequestsAllClients(CompletableFuture<String> future) implements IEvent {}
|
||||
|
||||
/** Forces closing all active connections immediately. */
|
||||
public record ForceCloseAllClients() implements IEvent {}
|
||||
|
||||
public record CloseClientRequest(CompletableFuture<String> future) {}
|
||||
|
||||
public record CloseClient(String connectionId) implements IEvent {}
|
||||
|
||||
/**
|
||||
* Event to start a new client connection to a server.
|
||||
* <p>
|
||||
* This event is typically posted to the {@code GlobalEventBus} to initiate the creation of
|
||||
* a client connection, and carries all information needed to establish that connection:
|
||||
* <br>
|
||||
* - A factory for creating the Netty handler that will manage the connection
|
||||
* <br>
|
||||
* - The server's IP address and port
|
||||
* <br>
|
||||
* - A unique event identifier for correlation with follow-up events
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* The {@link #eventId()} allows callers to correlate the {@code StartClient} event
|
||||
* with subsequent success/failure events. For example, a {@code StartClientSuccess}
|
||||
* or {@code StartClientFailure} event may carry the same {@code eventId}.
|
||||
* </p>
|
||||
*
|
||||
* @param handlerFactory Factory for constructing a {@link NetworkingGameClientHandler}.
|
||||
* @param ip The IP address of the server to connect to.
|
||||
* @param port The port number of the server to connect to.
|
||||
* @param eventId A unique identifier for this event, typically injected
|
||||
* automatically by the {@link org.toop.eventbus.EventPublisher}.
|
||||
*/
|
||||
public record StartClient(
|
||||
Supplier<? extends NetworkingGameClientHandler> handlerFactory,
|
||||
String ip,
|
||||
int port,
|
||||
String eventId
|
||||
) implements EventWithUuid {
|
||||
|
||||
/**
|
||||
* Returns a map representation of this event, where keys are record component names
|
||||
* and values are their corresponding values. Useful for generic logging, debugging,
|
||||
* or serializing events without hardcoding field names.
|
||||
*
|
||||
* @return a {@code Map<String, Object>} containing field names and values
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> result() {
|
||||
return Stream.of(this.getClass().getRecordComponents())
|
||||
.collect(Collectors.toMap(
|
||||
RecordComponent::getName,
|
||||
rc -> {
|
||||
try {
|
||||
return rc.getAccessor().invoke(this);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unique event identifier used for correlating this event.
|
||||
*
|
||||
* @return the event ID string
|
||||
*/
|
||||
@Override
|
||||
public String eventId() {
|
||||
return this.eventId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Update docs new input.
|
||||
* BLOCKING Triggers starting a server connection and returns a future.
|
||||
*
|
||||
* @param ip The IP address of the server to connect to.
|
||||
* @param port The port of the server to connect to.
|
||||
* @param future Returns the UUID of the connection, when connection is established.
|
||||
*/
|
||||
public record StartClientRequest(
|
||||
Supplier<? extends NetworkingGameClientHandler> handlerFactory,
|
||||
String ip, int port, CompletableFuture<String> future) implements IEvent {}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param clientId The ID of the client to be used in requests.
|
||||
* @param eventId The eventID used in checking if event is for you.
|
||||
*/
|
||||
public record StartClientSuccess(String clientId, String eventId)
|
||||
implements EventWithUuid {
|
||||
@Override
|
||||
public Map<String, Object> result() {
|
||||
return Stream.of(this.getClass().getRecordComponents())
|
||||
.collect(Collectors.toMap(
|
||||
RecordComponent::getName,
|
||||
rc -> {
|
||||
try {
|
||||
return rc.getAccessor().invoke(this);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String eventId() {
|
||||
return this.eventId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers sending a command to a server.
|
||||
*
|
||||
* @param connectionId The UUID of the connection to send the command on.
|
||||
* @param args The command arguments.
|
||||
*/
|
||||
public record SendCommand(String connectionId, String... args) implements IEvent {}
|
||||
|
||||
/**
|
||||
* WIP Triggers when a command is sent to a server.
|
||||
*
|
||||
* @param command The TicTacToeServer instance that executed the command.
|
||||
* @param args The command arguments.
|
||||
* @param result The result returned from executing the command.
|
||||
*/
|
||||
public record OnCommand(
|
||||
TicTacToeServer command, String[] args, String result) {} // TODO old
|
||||
|
||||
/**
|
||||
* Triggers reconnecting to a previous address.
|
||||
*
|
||||
* @param connectionId The identifier of the connection being reconnected.
|
||||
*/
|
||||
public record Reconnect(Object connectionId) {}
|
||||
|
||||
|
||||
/**
|
||||
* Triggers when the server client receives a message.
|
||||
*
|
||||
* @param ConnectionUuid The UUID of the connection that received the message.
|
||||
* @param message The message received.
|
||||
*/
|
||||
public record ReceivedMessage(String ConnectionUuid, String message) implements IEvent {}
|
||||
|
||||
/**
|
||||
* Triggers changing connection to a new address.
|
||||
*
|
||||
* @param connectionId The identifier of the connection being changed.
|
||||
* @param ip The new IP address.
|
||||
* @param port The new port.
|
||||
*/
|
||||
public record ChangeClient(Object connectionId, String ip, int port) {}
|
||||
|
||||
|
||||
/**
|
||||
* Triggers when the server couldn't connect to the desired address.
|
||||
*
|
||||
* @param connectionId The identifier of the connection that failed.
|
||||
*/
|
||||
public record CouldNotConnect(Object connectionId) {}
|
||||
|
||||
/** WIP Triggers when a connection closes. */
|
||||
public record ClosedConnection() {}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package org.toop.framework.eventbus.events;
|
||||
|
||||
public class ServerEvents {
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package org.toop.framework.networking;
|
||||
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioIoHandler;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||
import io.netty.handler.codec.LineBasedFrameDecoder;
|
||||
import io.netty.handler.codec.string.StringDecoder;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class NetworkingClient {
|
||||
private static final Logger logger = LogManager.getLogger(NetworkingClient.class);
|
||||
|
||||
final Bootstrap bootstrap = new Bootstrap();
|
||||
final EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
|
||||
|
||||
private String connectionUuid;
|
||||
private Channel channel;
|
||||
private NetworkingGameClientHandler handler;
|
||||
|
||||
public NetworkingClient(
|
||||
Supplier<? extends NetworkingGameClientHandler> handlerFactory,
|
||||
String host,
|
||||
int port) {
|
||||
try {
|
||||
this.bootstrap.group(this.workerGroup);
|
||||
this.bootstrap.channel(NioSocketChannel.class);
|
||||
this.bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
|
||||
this.bootstrap.handler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) {
|
||||
handler = handlerFactory.get();
|
||||
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
pipeline.addLast(new LineBasedFrameDecoder(1024)); // split at \n
|
||||
pipeline.addLast(new StringDecoder()); // bytes -> String
|
||||
pipeline.addLast(handler);
|
||||
}
|
||||
});
|
||||
ChannelFuture channelFuture = this.bootstrap.connect(host, port).sync();
|
||||
this.channel = channelFuture.channel();
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to create networking client instance", e);
|
||||
}
|
||||
}
|
||||
|
||||
public NetworkingGameClientHandler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
public void setConnectionUuid(String connectionUuid) {
|
||||
this.connectionUuid = connectionUuid;
|
||||
}
|
||||
|
||||
public boolean isChannelActive() {
|
||||
return this.channel != null && this.channel.isActive();
|
||||
}
|
||||
|
||||
public void writeAndFlush(String msg) {
|
||||
String literalMsg = msg.replace("\n", "\\n").replace("\r", "\\r");
|
||||
if (isChannelActive()) {
|
||||
this.channel.writeAndFlush(msg);
|
||||
logger.info("Connection {} sent message: {}", this.channel.remoteAddress(), literalMsg);
|
||||
} else {
|
||||
logger.warn("Cannot send message: {}, connection inactive.", literalMsg);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeAndFlushnl(String msg) {
|
||||
if (isChannelActive()) {
|
||||
this.channel.writeAndFlush(msg + "\n");
|
||||
logger.info("Connection {} sent message: {}", this.channel.remoteAddress(), msg);
|
||||
} else {
|
||||
logger.warn("Cannot send message: {}, connection inactive.", msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void login(String username) {
|
||||
this.writeAndFlush("login " + username + "\n");
|
||||
}
|
||||
|
||||
public void logout() {
|
||||
this.writeAndFlush("logout\n");
|
||||
}
|
||||
|
||||
public void sendMove(int move) {
|
||||
this.writeAndFlush("move " + move + "\n"); // append \n so server receives a full line
|
||||
}
|
||||
|
||||
public void getGamelist() {
|
||||
this.writeAndFlush("get gamelist\n");
|
||||
}
|
||||
|
||||
public void getPlayerlist() {
|
||||
this.writeAndFlush("get playerlist\n");
|
||||
}
|
||||
|
||||
public void subscribe(String gameType) {
|
||||
this.writeAndFlush("subscribe " + gameType + "\n");
|
||||
}
|
||||
|
||||
public void forfeit() {
|
||||
this.writeAndFlush("forfeit\n");
|
||||
}
|
||||
|
||||
public void challenge(String playerName, String gameType) {
|
||||
this.writeAndFlush("challenge " + playerName + " " + gameType + "\n");
|
||||
}
|
||||
|
||||
public void acceptChallenge(String challengeNumber) {
|
||||
this.writeAndFlush("challenge accept " + challengeNumber + "\n");
|
||||
}
|
||||
|
||||
public void sendChatMessage(String message) {
|
||||
this.writeAndFlush("message " + "\"" + message + "\"" + "\n");
|
||||
}
|
||||
|
||||
public void help(String command) {
|
||||
this.writeAndFlush("help " + command + "\n");
|
||||
}
|
||||
|
||||
public void closeConnection() {
|
||||
if (this.channel != null && this.channel.isActive()) {
|
||||
this.channel.close().addListener(future -> {
|
||||
if (future.isSuccess()) {
|
||||
logger.info("Connection {} closed successfully", this.channel.remoteAddress());
|
||||
} else {
|
||||
logger.error("Error closing connection {}. Error: {}",
|
||||
this.channel.remoteAddress(),
|
||||
future.cause().getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package org.toop.framework.networking;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.toop.eventbus.EventPublisher;
|
||||
import org.toop.framework.eventbus.events.NetworkEvents;
|
||||
|
||||
public class NetworkingClientManager {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(NetworkingClientManager.class);
|
||||
|
||||
/** Map of serverId -> Server instances */
|
||||
private final Map<String, NetworkingClient> networkClients = new ConcurrentHashMap<>();
|
||||
|
||||
/** Starts a connection manager, to manage, connections. */
|
||||
public NetworkingClientManager() {
|
||||
new EventPublisher<>(NetworkEvents.StartClientRequest.class, this::handleStartClientRequest);
|
||||
new EventPublisher<>(NetworkEvents.StartClient.class, this::handleStartClient);
|
||||
new EventPublisher<>(NetworkEvents.SendCommand.class, this::handleCommand);
|
||||
new EventPublisher<>(NetworkEvents.CloseClient.class, this::handleCloseClient);
|
||||
new EventPublisher<>(NetworkEvents.RequestsAllClients.class, this::getAllConnections);
|
||||
new EventPublisher<>(NetworkEvents.ForceCloseAllClients.class, this::shutdownAll);
|
||||
}
|
||||
|
||||
private String startClientRequest(Supplier<? extends NetworkingGameClientHandler> handlerFactory,
|
||||
String ip,
|
||||
int port) {
|
||||
String connectionUuid = UUID.randomUUID().toString();
|
||||
try {
|
||||
NetworkingClient client = new NetworkingClient(
|
||||
handlerFactory,
|
||||
ip,
|
||||
port);
|
||||
this.networkClients.put(connectionUuid, client);
|
||||
} catch (Exception e) {
|
||||
logger.error(e);
|
||||
}
|
||||
logger.info("Client {} started", connectionUuid);
|
||||
return connectionUuid;
|
||||
}
|
||||
|
||||
private void handleStartClientRequest(NetworkEvents.StartClientRequest request) {
|
||||
request.future()
|
||||
.complete(
|
||||
this.startClientRequest(
|
||||
request.handlerFactory(),
|
||||
request.ip(),
|
||||
request.port())); // TODO: Maybe post ConnectionEstablished event.
|
||||
}
|
||||
|
||||
private void handleStartClient(NetworkEvents.StartClient event) {
|
||||
String uuid = this.startClientRequest(event.handlerFactory(), event.ip(), event.port());
|
||||
new EventPublisher<>(NetworkEvents.StartClientSuccess.class,
|
||||
uuid, event.eventId()
|
||||
).asyncPostEvent();
|
||||
}
|
||||
|
||||
private void handleCommand(
|
||||
NetworkEvents.SendCommand
|
||||
event) { // TODO: Move this to ServerConnection class, keep it internal.
|
||||
NetworkingClient client = this.networkClients.get(event.connectionId());
|
||||
logger.info("Preparing to send command: {} to server: {}", event.args(), client);
|
||||
if (client != null) {
|
||||
String args = String.join(" ", event.args()) + "\n";
|
||||
client.writeAndFlush(args);
|
||||
} else {
|
||||
logger.warn("Server {} not found for command '{}'", event.connectionId(), event.args());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCloseClient(NetworkEvents.CloseClient event) {
|
||||
NetworkingClient client = this.networkClients.get(event.connectionId());
|
||||
client.closeConnection(); // TODO: Check if not blocking, what if error, mb not remove?
|
||||
this.networkClients.remove(event.connectionId());
|
||||
logger.info("Client {} closed successfully.", event.connectionId());
|
||||
}
|
||||
|
||||
// private void handleReconnect(Events.ServerEvents.Reconnect event) {
|
||||
// NetworkingClient client = this.networkClients.get(event.connectionId());
|
||||
// if (client != null) {
|
||||
// try {
|
||||
// client;
|
||||
// logger.info("Server {} reconnected", event.connectionId());
|
||||
// } catch (Exception e) {
|
||||
// logger.error("Server {} failed to reconnect", event.connectionId(), e);
|
||||
// GlobalEventBus.post(new Events.ServerEvents.CouldNotConnect(event.connectionId()));
|
||||
// }
|
||||
// }
|
||||
// } // TODO: Reconnect on disconnect
|
||||
|
||||
// private void handleChangeConnection(Events.ServerEvents.ChangeConnection event) {
|
||||
// ServerConnection serverConnection = this.serverConnections.get(event.connectionId());
|
||||
// if (serverConnection != null) {
|
||||
// try {
|
||||
// serverConnection.connect(event.ip(), event.port());
|
||||
// logger.info("Server {} changed connection to {}:{}", event.connectionId(),
|
||||
// event.ip(), event.port());
|
||||
// } catch (Exception e) {
|
||||
// logger.error("Server {} failed to change connection", event.connectionId(),
|
||||
// e);
|
||||
// GlobalEventBus.post(new
|
||||
// Events.ServerEvents.CouldNotConnect(event.connectionId()));
|
||||
// }
|
||||
// }
|
||||
// } TODO
|
||||
|
||||
private void getAllConnections(NetworkEvents.RequestsAllClients request) {
|
||||
List<NetworkingClient> a = new ArrayList<>(this.networkClients.values());
|
||||
request.future().complete(a.toString());
|
||||
}
|
||||
|
||||
public void shutdownAll(NetworkEvents.ForceCloseAllClients request) {
|
||||
this.networkClients.values().forEach(NetworkingClient::closeConnection);
|
||||
this.networkClients.clear();
|
||||
logger.info("All servers shut down");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.toop.framework.networking;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
public class NetworkingGameClientHandler extends ChannelInboundHandlerAdapter {
|
||||
private static final Logger logger = LogManager.getLogger(NetworkingGameClientHandler.class);
|
||||
|
||||
public NetworkingGameClientHandler() {}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
logger.debug("Received message from server-{}, data: {}", ctx.channel().remoteAddress(), msg);
|
||||
|
||||
// TODO: Handle server messages
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
logger.error(cause.getMessage(), cause);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//package org.toop.frontend.networking.handlers;
|
||||
//
|
||||
//import io.netty.channel.ChannelHandlerContext;
|
||||
//import org.apache.logging.log4j.LogManager;
|
||||
//import org.apache.logging.log4j.Logger;
|
||||
//import org.toop.frontend.networking.NetworkingGameClientHandler;
|
||||
//
|
||||
//public class NetworkingTicTacToeClientHandler extends NetworkingGameClientHandler {
|
||||
// static final Logger logger = LogManager.getLogger(NetworkingTicTacToeClientHandler.class);
|
||||
//
|
||||
//
|
||||
//}
|
||||
13
framework/src/main/resources/log4j2.xml
Normal file
13
framework/src/main/resources/log4j2.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="debug" name="AppConfig">
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT">
|
||||
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%17.17t] %-5level %logger{36} - %msg%n"/>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="info">
|
||||
<AppenderRef ref="Console"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
Reference in New Issue
Block a user