Resolves checkstyle errors for event-* (#1070)
* Reduces checkstyle errors in event-aggregator * Reduces checkstyle errors in event-asynchronous * Reduces checkstyle errors in event-driven-architecture * Reduces checkstyle errors in event-queue * Reduces checkstyle errors in event-sourcing
This commit is contained in:
parent
7c888e8886
commit
5ae2ce6e2e
@ -27,24 +27,22 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* A system with lots of objects can lead to complexities when a client wants to subscribe to
|
||||
* events. The client has to find and register for each object individually, if each object has
|
||||
* multiple events then each event requires a separate subscription.
|
||||
* <p>
|
||||
* An Event Aggregator acts as a single source of events for many objects. It registers for all the
|
||||
* events of the many objects allowing clients to register with just the aggregator.
|
||||
* <p>
|
||||
* In the example {@link LordBaelish}, {@link LordVarys} and {@link Scout} deliver events to
|
||||
* {@link KingsHand}. {@link KingsHand}, the event aggregator, then delivers the events to
|
||||
* {@link KingJoffrey}.
|
||||
*
|
||||
* <p>An Event Aggregator acts as a single source of events for many objects. It registers for all
|
||||
* the events of the many objects allowing clients to register with just the aggregator.
|
||||
*
|
||||
* <p>In the example {@link LordBaelish}, {@link LordVarys} and {@link Scout} deliver events to
|
||||
* {@link KingsHand}. {@link KingsHand}, the event aggregator, then delivers the events to {@link
|
||||
* KingJoffrey}.
|
||||
*/
|
||||
public class App {
|
||||
|
||||
/**
|
||||
* Program entry point
|
||||
*
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args command line args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
@ -24,9 +24,7 @@
|
||||
package com.iluwatar.event.aggregator;
|
||||
|
||||
/**
|
||||
*
|
||||
* Event enumeration.
|
||||
*
|
||||
*/
|
||||
public enum Event {
|
||||
|
||||
|
@ -27,9 +27,7 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* EventEmitter is the base class for event producers that can be observed.
|
||||
*
|
||||
*/
|
||||
public abstract class EventEmitter {
|
||||
|
||||
|
@ -24,9 +24,7 @@
|
||||
package com.iluwatar.event.aggregator;
|
||||
|
||||
/**
|
||||
*
|
||||
* Observers of events implement this interface.
|
||||
*
|
||||
*/
|
||||
public interface EventObserver {
|
||||
|
||||
|
@ -27,9 +27,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* KingJoffrey observes events from {@link KingsHand}.
|
||||
*
|
||||
*/
|
||||
public class KingJoffrey implements EventObserver {
|
||||
|
||||
|
@ -24,9 +24,7 @@
|
||||
package com.iluwatar.event.aggregator;
|
||||
|
||||
/**
|
||||
*
|
||||
* KingsHand observes events from multiple sources and delivers them to listeners.
|
||||
*
|
||||
*/
|
||||
public class KingsHand extends EventEmitter implements EventObserver {
|
||||
|
||||
|
@ -24,9 +24,7 @@
|
||||
package com.iluwatar.event.aggregator;
|
||||
|
||||
/**
|
||||
*
|
||||
* LordBaelish produces events.
|
||||
*
|
||||
*/
|
||||
public class LordBaelish extends EventEmitter {
|
||||
|
||||
|
@ -24,9 +24,7 @@
|
||||
package com.iluwatar.event.aggregator;
|
||||
|
||||
/**
|
||||
*
|
||||
* LordVarys produces events.
|
||||
*
|
||||
*/
|
||||
public class LordVarys extends EventEmitter {
|
||||
|
||||
|
@ -24,9 +24,7 @@
|
||||
package com.iluwatar.event.aggregator;
|
||||
|
||||
/**
|
||||
*
|
||||
* Scout produces events.
|
||||
*
|
||||
*/
|
||||
public class Scout extends EventEmitter {
|
||||
|
||||
|
@ -24,9 +24,7 @@
|
||||
package com.iluwatar.event.aggregator;
|
||||
|
||||
/**
|
||||
*
|
||||
* Weekday enumeration
|
||||
*
|
||||
* Weekday enumeration.
|
||||
*/
|
||||
public enum Weekday {
|
||||
|
||||
|
@ -23,38 +23,38 @@
|
||||
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Properties;
|
||||
import java.util.Scanner;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This application demonstrates the <b>Event-based Asynchronous</b> pattern. Essentially, users (of
|
||||
* the pattern) may choose to run events in an Asynchronous or Synchronous mode. There can be
|
||||
* multiple Asynchronous events running at once but only one Synchronous event can run at a time.
|
||||
* Asynchronous events are synonymous to multi-threads. The key point here is that the threads run
|
||||
* in the background and the user is free to carry on with other processes. Once an event is
|
||||
* complete, the appropriate listener/callback method will be called. The listener then proceeds to
|
||||
* carry out further processing depending on the needs of the user.
|
||||
*
|
||||
* This application demonstrates the <b>Event-based Asynchronous</b> pattern. Essentially, users (of the pattern) may
|
||||
* choose to run events in an Asynchronous or Synchronous mode. There can be multiple Asynchronous events running at
|
||||
* once but only one Synchronous event can run at a time. Asynchronous events are synonymous to multi-threads. The key
|
||||
* point here is that the threads run in the background and the user is free to carry on with other processes. Once an
|
||||
* event is complete, the appropriate listener/callback method will be called. The listener then proceeds to carry out
|
||||
* further processing depending on the needs of the user.
|
||||
* <p>The {@link EventManager} manages the events/threads that the user creates. Currently, the
|
||||
* supported event operations are: <code>start</code>, <code>stop</code>, <code>getStatus</code>.
|
||||
* For Synchronous events, the user is unable to start another (Synchronous) event if one is already
|
||||
* running at the time. The running event would have to either be stopped or completed before a new
|
||||
* event can be started.
|
||||
*
|
||||
* The {@link EventManager} manages the events/threads that the user creates. Currently, the supported event operations
|
||||
* are: <code>start</code>, <code>stop</code>, <code>getStatus</code>. For Synchronous events, the user is unable to
|
||||
* start another (Synchronous) event if one is already running at the time. The running event would have to either be
|
||||
* stopped or completed before a new event can be started.
|
||||
*
|
||||
* The Event-based Asynchronous Pattern makes available the advantages of multithreaded applications while hiding many
|
||||
* of the complex issues inherent in multithreaded design. Using a class that supports this pattern can allow you to:-
|
||||
* (1) Perform time-consuming tasks, such as downloads and database operations, "in the background," without
|
||||
* interrupting your application. (2) Execute multiple operations simultaneously, receiving notifications when each
|
||||
* completes. (3) Wait for resources to become available without stopping ("hanging") your application. (4) Communicate
|
||||
* with pending asynchronous operations using the familiar events-and-delegates model.
|
||||
* <p>The Event-based Asynchronous Pattern makes available the advantages of multithreaded
|
||||
* applications while hiding many of the complex issues inherent in multithreaded design. Using a
|
||||
* class that supports this pattern can allow you to:- (1) Perform time-consuming tasks, such as
|
||||
* downloads and database operations, "in the background," without interrupting your application.
|
||||
* (2) Execute multiple operations simultaneously, receiving notifications when each completes. (3)
|
||||
* Wait for resources to become available without stopping ("hanging") your application. (4)
|
||||
* Communicate with pending asynchronous operations using the familiar events-and-delegates model.
|
||||
*
|
||||
* @see EventManager
|
||||
* @see Event
|
||||
*
|
||||
*/
|
||||
public class App {
|
||||
|
||||
@ -67,8 +67,7 @@ public class App {
|
||||
/**
|
||||
* Program entry point.
|
||||
*
|
||||
* @param args
|
||||
* command line args
|
||||
* @param args command line args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
App app = new App();
|
||||
@ -78,8 +77,9 @@ public class App {
|
||||
}
|
||||
|
||||
/**
|
||||
* App can run in interactive mode or not. Interactive mode == Allow user interaction with command line.
|
||||
* Non-interactive is a quick sequential run through the available {@link EventManager} operations.
|
||||
* App can run in interactive mode or not. Interactive mode == Allow user interaction with command
|
||||
* line. Non-interactive is a quick sequential run through the available {@link EventManager}
|
||||
* operations.
|
||||
*/
|
||||
public void setUp() {
|
||||
Properties prop = new Properties();
|
||||
@ -118,24 +118,24 @@ public class App {
|
||||
|
||||
try {
|
||||
// Create an Asynchronous event.
|
||||
int aEventId = eventManager.createAsync(60);
|
||||
LOGGER.info("Async Event [{}] has been created.", aEventId);
|
||||
eventManager.start(aEventId);
|
||||
LOGGER.info("Async Event [{}] has been started.", aEventId);
|
||||
int asyncEventId = eventManager.createAsync(60);
|
||||
LOGGER.info("Async Event [{}] has been created.", asyncEventId);
|
||||
eventManager.start(asyncEventId);
|
||||
LOGGER.info("Async Event [{}] has been started.", asyncEventId);
|
||||
|
||||
// Create a Synchronous event.
|
||||
int sEventId = eventManager.create(60);
|
||||
LOGGER.info("Sync Event [{}] has been created.", sEventId);
|
||||
eventManager.start(sEventId);
|
||||
LOGGER.info("Sync Event [{}] has been started.", sEventId);
|
||||
int syncEventId = eventManager.create(60);
|
||||
LOGGER.info("Sync Event [{}] has been created.", syncEventId);
|
||||
eventManager.start(syncEventId);
|
||||
LOGGER.info("Sync Event [{}] has been started.", syncEventId);
|
||||
|
||||
eventManager.status(aEventId);
|
||||
eventManager.status(sEventId);
|
||||
eventManager.status(asyncEventId);
|
||||
eventManager.status(syncEventId);
|
||||
|
||||
eventManager.cancel(aEventId);
|
||||
LOGGER.info("Async Event [{}] has been stopped.", aEventId);
|
||||
eventManager.cancel(sEventId);
|
||||
LOGGER.info("Sync Event [{}] has been stopped.", sEventId);
|
||||
eventManager.cancel(asyncEventId);
|
||||
LOGGER.info("Async Event [{}] has been stopped.", asyncEventId);
|
||||
eventManager.cancel(syncEventId);
|
||||
LOGGER.info("Sync Event [{}] has been stopped.", syncEventId);
|
||||
|
||||
} catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException
|
||||
| InvalidOperationException e) {
|
||||
@ -211,7 +211,8 @@ public class App {
|
||||
int eventId = eventManager.createAsync(eventTime);
|
||||
eventManager.start(eventId);
|
||||
LOGGER.info("Egg [{}] is being boiled.", eventId);
|
||||
} catch (MaxNumOfEventsAllowedException | LongRunningEventException | EventDoesNotExistException e) {
|
||||
} catch (MaxNumOfEventsAllowedException | LongRunningEventException
|
||||
| EventDoesNotExistException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
} else if (eventType.equalsIgnoreCase("S")) {
|
||||
@ -219,8 +220,8 @@ public class App {
|
||||
int eventId = eventManager.create(eventTime);
|
||||
eventManager.start(eventId);
|
||||
LOGGER.info("Egg [{}] is being boiled.", eventId);
|
||||
} catch (MaxNumOfEventsAllowedException | InvalidOperationException | LongRunningEventException
|
||||
| EventDoesNotExistException e) {
|
||||
} catch (MaxNumOfEventsAllowedException | InvalidOperationException
|
||||
| LongRunningEventException | EventDoesNotExistException e) {
|
||||
LOGGER.error(e.getMessage());
|
||||
}
|
||||
} else {
|
||||
|
@ -27,9 +27,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* Each Event runs as a separate/individual thread.
|
||||
*
|
||||
*/
|
||||
public class Event implements IEvent, Runnable {
|
||||
|
||||
@ -43,9 +41,10 @@ public class Event implements IEvent, Runnable {
|
||||
private ThreadCompleteListener eventListener;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param eventId event ID
|
||||
* @param eventTime event time
|
||||
* @param eventId event ID
|
||||
* @param eventTime event time
|
||||
* @param isSynchronous is of synchronous type
|
||||
*/
|
||||
public Event(final int eventId, final int eventTime, final boolean isSynchronous) {
|
||||
|
@ -24,7 +24,7 @@
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
/**
|
||||
* Custom Exception Class for Non Existent Event
|
||||
* Custom Exception Class for Non Existent Event.
|
||||
*/
|
||||
public class EventDoesNotExistException extends Exception {
|
||||
|
||||
|
@ -29,29 +29,28 @@ import java.util.Random;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
*
|
||||
* EventManager handles and maintains a pool of event threads. {@link Event} threads are created upon user request. Thre
|
||||
* are two types of events; Asynchronous and Synchronous. There can be multiple Asynchronous events running at once but
|
||||
* only one Synchronous event running at a time. Currently supported event operations are: start, stop, and getStatus.
|
||||
* Once an event is complete, it then notifies EventManager through a listener. The EventManager then takes the event
|
||||
* out of the pool.
|
||||
*
|
||||
* EventManager handles and maintains a pool of event threads. {@link Event} threads are created
|
||||
* upon user request. Thre are two types of events; Asynchronous and Synchronous. There can be
|
||||
* multiple Asynchronous events running at once but only one Synchronous event running at a time.
|
||||
* Currently supported event operations are: start, stop, and getStatus. Once an event is complete,
|
||||
* it then notifies EventManager through a listener. The EventManager then takes the event out of
|
||||
* the pool.
|
||||
*/
|
||||
public class EventManager implements ThreadCompleteListener {
|
||||
|
||||
public static final int MAX_RUNNING_EVENTS = 1000; // Just don't wanna have too many running events. :)
|
||||
public static final int MAX_RUNNING_EVENTS = 1000;
|
||||
// Just don't wanna have too many running events. :)
|
||||
public static final int MIN_ID = 1;
|
||||
public static final int MAX_ID = MAX_RUNNING_EVENTS;
|
||||
public static final int MAX_EVENT_TIME = 1800; // in seconds / 30 minutes.
|
||||
private int currentlyRunningSyncEvent = -1;
|
||||
private Random rand;
|
||||
private Map<Integer, Event> eventPool;
|
||||
|
||||
|
||||
private static final String DOES_NOT_EXIST = " does not exist.";
|
||||
|
||||
/**
|
||||
* EventManager constructor.
|
||||
*
|
||||
*/
|
||||
public EventManager() {
|
||||
rand = new Random(1);
|
||||
@ -65,14 +64,15 @@ public class EventManager implements ThreadCompleteListener {
|
||||
* @param eventTime Time an event should run for.
|
||||
* @return eventId
|
||||
* @throws MaxNumOfEventsAllowedException When too many events are running at a time.
|
||||
* @throws InvalidOperationException No new synchronous events can be created when one is already running.
|
||||
* @throws LongRunningEventException Long running events are not allowed in the app.
|
||||
* @throws InvalidOperationException No new synchronous events can be created when one is
|
||||
* already running.
|
||||
* @throws LongRunningEventException Long running events are not allowed in the app.
|
||||
*/
|
||||
public int create(int eventTime)
|
||||
throws MaxNumOfEventsAllowedException, InvalidOperationException, LongRunningEventException {
|
||||
if (currentlyRunningSyncEvent != -1) {
|
||||
throw new InvalidOperationException(
|
||||
"Event [" + currentlyRunningSyncEvent + "] is still running. Please wait until it finishes and try again.");
|
||||
throw new InvalidOperationException("Event [" + currentlyRunningSyncEvent + "] is still"
|
||||
+ " running. Please wait until it finishes and try again.");
|
||||
}
|
||||
|
||||
int eventId = createEvent(eventTime, true);
|
||||
@ -87,16 +87,18 @@ public class EventManager implements ThreadCompleteListener {
|
||||
* @param eventTime Time an event should run for.
|
||||
* @return eventId
|
||||
* @throws MaxNumOfEventsAllowedException When too many events are running at a time.
|
||||
* @throws LongRunningEventException Long running events are not allowed in the app.
|
||||
* @throws LongRunningEventException Long running events are not allowed in the app.
|
||||
*/
|
||||
public int createAsync(int eventTime) throws MaxNumOfEventsAllowedException, LongRunningEventException {
|
||||
public int createAsync(int eventTime) throws MaxNumOfEventsAllowedException,
|
||||
LongRunningEventException {
|
||||
return createEvent(eventTime, false);
|
||||
}
|
||||
|
||||
private int createEvent(int eventTime, boolean isSynchronous)
|
||||
throws MaxNumOfEventsAllowedException, LongRunningEventException {
|
||||
if (eventPool.size() == MAX_RUNNING_EVENTS) {
|
||||
throw new MaxNumOfEventsAllowedException("Too many events are running at the moment. Please try again later.");
|
||||
throw new MaxNumOfEventsAllowedException("Too many events are running at the moment."
|
||||
+ " Please try again later.");
|
||||
}
|
||||
|
||||
if (eventTime >= MAX_EVENT_TIME) {
|
||||
@ -185,7 +187,8 @@ public class EventManager implements ThreadCompleteListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pseudo-random number between min and max, inclusive. The difference between min and max can be at most
|
||||
* Returns a pseudo-random number between min and max, inclusive. The difference between min and
|
||||
* max can be at most
|
||||
* <code>Integer.MAX_VALUE - 1</code>.
|
||||
*/
|
||||
private int generateId() {
|
||||
|
@ -24,8 +24,7 @@
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
/**
|
||||
* Events that fulfill the start stop and list out current status behaviour
|
||||
* follow this interface
|
||||
* Events that fulfill the start stop and list out current status behaviour follow this interface.
|
||||
*/
|
||||
public interface IEvent {
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
/**
|
||||
* Type of Exception raised when the Operation being invoked is Invalid
|
||||
* Type of Exception raised when the Operation being invoked is Invalid.
|
||||
*/
|
||||
public class InvalidOperationException extends Exception {
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
/**
|
||||
* Type of Exception raised when the Operation being invoked is Long Running
|
||||
* Type of Exception raised when the Operation being invoked is Long Running.
|
||||
*/
|
||||
public class LongRunningEventException extends Exception {
|
||||
|
||||
|
@ -24,7 +24,7 @@
|
||||
package com.iluwatar.event.asynchronous;
|
||||
|
||||
/**
|
||||
* Type of Exception raised when the max number of allowed events is exceeded
|
||||
* Type of Exception raised when the max number of allowed events is exceeded.
|
||||
*/
|
||||
public class MaxNumOfEventsAllowedException extends Exception {
|
||||
|
||||
|
@ -34,11 +34,11 @@ import com.iluwatar.eda.model.User;
|
||||
/**
|
||||
* An event-driven architecture (EDA) is a framework that orchestrates behavior around the
|
||||
* production, detection and consumption of events as well as the responses they evoke. An event is
|
||||
* any identifiable occurrence that has significance for system hardware or software. <p> The
|
||||
* example below uses an {@link EventDispatcher} to link/register {@link Event} objects to their
|
||||
* respective handlers once an {@link Event} is dispatched, it's respective handler is invoked and
|
||||
* the {@link Event} is handled accordingly.
|
||||
* any identifiable occurrence that has significance for system hardware or software.
|
||||
*
|
||||
* <p>The example below uses an {@link EventDispatcher} to link/register {@link Event} objects to
|
||||
* their respective handlers once an {@link Event} is dispatched, it's respective handler is invoked
|
||||
* and the {@link Event} is handled accordingly.
|
||||
*/
|
||||
public class App {
|
||||
|
||||
@ -47,9 +47,8 @@ public class App {
|
||||
* made known to the dispatcher by registering them. In this case the {@link UserCreatedEvent} is
|
||||
* bound to the UserCreatedEventHandler, whilst the {@link UserUpdatedEvent} is bound to the
|
||||
* {@link UserUpdatedEventHandler}. The dispatcher can now be called to dispatch specific events.
|
||||
* When a user is saved, the {@link UserCreatedEvent} can be dispatched.
|
||||
* On the other hand, when a user is updated, {@link UserUpdatedEvent} can be dispatched.
|
||||
*
|
||||
* When a user is saved, the {@link UserCreatedEvent} can be dispatched. On the other hand, when a
|
||||
* user is updated, {@link UserUpdatedEvent} can be dispatched.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
|
@ -23,12 +23,12 @@
|
||||
|
||||
package com.iluwatar.eda.event;
|
||||
|
||||
import com.iluwatar.eda.framework.EventDispatcher;
|
||||
import com.iluwatar.eda.framework.Event;
|
||||
import com.iluwatar.eda.framework.EventDispatcher;
|
||||
|
||||
/**
|
||||
* The {@link AbstractEvent} class serves as a base class for defining custom events happening with your
|
||||
* system. In this example we have two types of events defined.
|
||||
* The {@link AbstractEvent} class serves as a base class for defining custom events happening with
|
||||
* your system. In this example we have two types of events defined.
|
||||
* <ul>
|
||||
* <li>{@link UserCreatedEvent} - used when a user is created</li>
|
||||
* <li>{@link UserUpdatedEvent} - used when a user is updated</li>
|
||||
@ -38,9 +38,8 @@ import com.iluwatar.eda.framework.Event;
|
||||
public abstract class AbstractEvent implements Event {
|
||||
|
||||
/**
|
||||
* Returns the event type as a {@link Class} object
|
||||
* In this example, this method is used by the {@link EventDispatcher} to
|
||||
* dispatch events depending on their type.
|
||||
* Returns the event type as a {@link Class} object In this example, this method is used by the
|
||||
* {@link EventDispatcher} to dispatch events depending on their type.
|
||||
*
|
||||
* @return the AbstractEvent type as a {@link Class}.
|
||||
*/
|
||||
|
@ -26,9 +26,9 @@ package com.iluwatar.eda.event;
|
||||
import com.iluwatar.eda.model.User;
|
||||
|
||||
/**
|
||||
* The {@link UserCreatedEvent} should should be dispatched whenever a user has been created.
|
||||
* This class can be extended to contain details about the user has been created. In this example,
|
||||
* the entire {@link User} object is passed on as data with the event.
|
||||
* The {@link UserCreatedEvent} should should be dispatched whenever a user has been created. This
|
||||
* class can be extended to contain details about the user has been created. In this example, the
|
||||
* entire {@link User} object is passed on as data with the event.
|
||||
*/
|
||||
public class UserCreatedEvent extends AbstractEvent {
|
||||
|
||||
|
@ -26,9 +26,9 @@ package com.iluwatar.eda.event;
|
||||
import com.iluwatar.eda.model.User;
|
||||
|
||||
/**
|
||||
* The {@link UserUpdatedEvent} should should be dispatched whenever a user has been updated.
|
||||
* This class can be extended to contain details about the user has been updated. In this example,
|
||||
* the entire {@link User} object is passed on as data with the event.
|
||||
* The {@link UserUpdatedEvent} should should be dispatched whenever a user has been updated. This
|
||||
* class can be extended to contain details about the user has been updated. In this example, the
|
||||
* entire {@link User} object is passed on as data with the event.
|
||||
*/
|
||||
public class UserUpdatedEvent extends AbstractEvent {
|
||||
|
||||
|
@ -24,14 +24,15 @@
|
||||
package com.iluwatar.eda.framework;
|
||||
|
||||
/**
|
||||
* A {@link Event} is an object with a specific type that is associated
|
||||
* to a specific {@link Handler}.
|
||||
* A {@link Event} is an object with a specific type that is associated to a specific {@link
|
||||
* Handler}.
|
||||
*/
|
||||
public interface Event {
|
||||
|
||||
/**
|
||||
* Returns the message type as a {@link Class} object. In this example the message type is
|
||||
* used to handle events by their type.
|
||||
* Returns the message type as a {@link Class} object. In this example the message type is used to
|
||||
* handle events by their type.
|
||||
*
|
||||
* @return the message type as a {@link Class}.
|
||||
*/
|
||||
Class<? extends Event> getType();
|
||||
|
@ -27,8 +27,8 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Handles the routing of {@link Event} messages to associated handlers.
|
||||
* A {@link HashMap} is used to store the association between events and their respective handlers.
|
||||
* Handles the routing of {@link Event} messages to associated handlers. A {@link HashMap} is used
|
||||
* to store the association between events and their respective handlers.
|
||||
*/
|
||||
public class EventDispatcher {
|
||||
|
||||
|
@ -24,16 +24,18 @@
|
||||
package com.iluwatar.eda.framework;
|
||||
|
||||
/**
|
||||
* This interface can be implemented to handle different types of messages.
|
||||
* Every handler is responsible for a single of type message
|
||||
* This interface can be implemented to handle different types of messages. Every handler is
|
||||
* responsible for a single of type message
|
||||
*
|
||||
* @param <E> Handler can handle events of type E
|
||||
*/
|
||||
public interface Handler<E extends Event> {
|
||||
|
||||
/**
|
||||
* The onEvent method should implement and handle behavior related to the event.
|
||||
* This can be as simple as calling another service to handle the event on publishing the event on
|
||||
* a queue to be consumed by other sub systems.
|
||||
* The onEvent method should implement and handle behavior related to the event. This can be as
|
||||
* simple as calling another service to handle the event on publishing the event on a queue to be
|
||||
* consumed by other sub systems.
|
||||
*
|
||||
* @param event the {@link Event} object to be handled.
|
||||
*/
|
||||
void onEvent(E event);
|
||||
|
@ -27,8 +27,8 @@ import com.iluwatar.eda.event.UserCreatedEvent;
|
||||
import com.iluwatar.eda.event.UserUpdatedEvent;
|
||||
|
||||
/**
|
||||
* This {@link User} class is a basic pojo used to demonstrate user data sent along with
|
||||
* the {@link UserCreatedEvent} and {@link UserUpdatedEvent} events.
|
||||
* This {@link User} class is a basic pojo used to demonstrate user data sent along with the {@link
|
||||
* UserCreatedEvent} and {@link UserUpdatedEvent} events.
|
||||
*/
|
||||
public class User {
|
||||
|
||||
|
@ -23,40 +23,40 @@
|
||||
|
||||
package com.iluwatar.event.queue;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Event or message queues provide an asynchronous communications protocol, meaning that the sender
|
||||
* and receiver of the message do not need to interact with the message queue at the same time.
|
||||
* Events or messages placed onto the queue are stored until the recipient retrieves them. Event
|
||||
* or message queues have implicit or explicit limits on the size of data that may be transmitted
|
||||
* in a single message and the number of messages that may remain outstanding on the queue.
|
||||
* A queue stores a series of notifications or requests in first-in, first-out order.
|
||||
* Sending a notification enqueues the request and returns. The request processor then processes
|
||||
* items from the queue at a later time.
|
||||
* and receiver of the message do not need to interact with the message queue at the same time.
|
||||
* Events or messages placed onto the queue are stored until the recipient retrieves them. Event or
|
||||
* message queues have implicit or explicit limits on the size of data that may be transmitted in a
|
||||
* single message and the number of messages that may remain outstanding on the queue. A queue
|
||||
* stores a series of notifications or requests in first-in, first-out order. Sending a notification
|
||||
* enqueues the request and returns. The request processor then processes items from the queue at a
|
||||
* later time.
|
||||
*/
|
||||
public class App {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(App.class);
|
||||
|
||||
/**
|
||||
* Program entry point.
|
||||
*
|
||||
*
|
||||
* @param args command line args
|
||||
* @throws IOException when there is a problem with the audio file loading
|
||||
* @throws UnsupportedAudioFileException when the loaded audio file is unsupported
|
||||
* @throws IOException when there is a problem with the audio file loading
|
||||
* @throws UnsupportedAudioFileException when the loaded audio file is unsupported
|
||||
*/
|
||||
public static void main(String[] args) throws UnsupportedAudioFileException, IOException, InterruptedException {
|
||||
public static void main(String[] args) throws UnsupportedAudioFileException, IOException,
|
||||
InterruptedException {
|
||||
Audio audio = Audio.getInstance();
|
||||
audio.playSound(audio.getAudioStream("./etc/Bass-Drum-1.wav"), -10.0f);
|
||||
audio.playSound(audio.getAudioStream("./etc/Closed-Hi-Hat-1.wav"), -8.0f);
|
||||
|
||||
|
||||
LOGGER.info("Press Enter key to stop the program...");
|
||||
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
|
||||
br.read();
|
||||
|
@ -23,22 +23,20 @@
|
||||
|
||||
package com.iluwatar.event.queue;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.Clip;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This class implements the Event Queue pattern.
|
||||
* @author mkuprivecz
|
||||
*
|
||||
* @author mkuprivecz
|
||||
*/
|
||||
public class Audio {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Audio.class);
|
||||
@ -73,9 +71,10 @@ public class Audio {
|
||||
updateThread.join();
|
||||
updateThread = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method check the Update Method's thread is started.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public synchronized boolean isServiceRunning() {
|
||||
@ -83,8 +82,8 @@ public class Audio {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the thread for the Update Method pattern if it was not started previously.
|
||||
* Also when the thread is is ready initializes the indexes of the queue
|
||||
* Starts the thread for the Update Method pattern if it was not started previously. Also when the
|
||||
* thread is is ready initializes the indexes of the queue
|
||||
*/
|
||||
public void init() {
|
||||
if (updateThread == null) {
|
||||
@ -96,9 +95,9 @@ public class Audio {
|
||||
}
|
||||
startThread();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is a synchronized thread starter
|
||||
* This is a synchronized thread starter.
|
||||
*/
|
||||
private synchronized void startThread() {
|
||||
if (!updateThread.isAlive()) {
|
||||
@ -110,8 +109,9 @@ public class Audio {
|
||||
|
||||
/**
|
||||
* This method adds a new audio into the queue.
|
||||
*
|
||||
* @param stream is the AudioInputStream for the method
|
||||
* @param volume is the level of the audio's volume
|
||||
* @param volume is the level of the audio's volume
|
||||
*/
|
||||
public void playSound(AudioInputStream stream, float volume) {
|
||||
init();
|
||||
@ -128,10 +128,9 @@ public class Audio {
|
||||
getPendingAudio()[tailIndex] = new PlayMessage(stream, volume);
|
||||
tailIndex = (tailIndex + 1) % MAX_PENDING;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method uses the Update Method pattern.
|
||||
* It takes the audio from the queue and plays it
|
||||
* This method uses the Update Method pattern. It takes the audio from the queue and plays it
|
||||
*/
|
||||
private void update() {
|
||||
// If there are no pending requests, do nothing.
|
||||
@ -155,11 +154,12 @@ public class Audio {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the AudioInputStream of a file
|
||||
* Returns the AudioInputStream of a file.
|
||||
*
|
||||
* @param filePath is the path of the audio file
|
||||
* @return AudioInputStream
|
||||
* @throws UnsupportedAudioFileException when the audio file is not supported
|
||||
* @throws IOException when the file is not readable
|
||||
* @throws UnsupportedAudioFileException when the audio file is not supported
|
||||
* @throws IOException when the file is not readable
|
||||
*/
|
||||
public AudioInputStream getAudioStream(String filePath)
|
||||
throws UnsupportedAudioFileException, IOException {
|
||||
@ -167,7 +167,8 @@ public class Audio {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns with the message array of the queue
|
||||
* Returns with the message array of the queue.
|
||||
*
|
||||
* @return PlayMessage[]
|
||||
*/
|
||||
public PlayMessage[] getPendingAudio() {
|
||||
|
@ -27,15 +27,15 @@ import javax.sound.sampled.AudioInputStream;
|
||||
|
||||
/**
|
||||
* The Event Queue's queue will store the instances of this class.
|
||||
* @author mkuprivecz
|
||||
*
|
||||
* @author mkuprivecz
|
||||
*/
|
||||
public class PlayMessage {
|
||||
|
||||
|
||||
private AudioInputStream stream;
|
||||
|
||||
|
||||
private float volume;
|
||||
|
||||
|
||||
public PlayMessage(AudioInputStream stream, float volume) {
|
||||
setStream(stream);
|
||||
setVolume(volume);
|
||||
|
@ -42,13 +42,13 @@ import org.slf4j.LoggerFactory;
|
||||
* transactional data, and maintain full audit trails and history that can enable compensating
|
||||
* actions.
|
||||
*
|
||||
* This App class is an example usage of Event Sourcing pattern. As an example, two bank account is
|
||||
* created, then some money deposit and transfer actions are taken so a new state of accounts is
|
||||
* <p>This App class is an example usage of Event Sourcing pattern. As an example, two bank account
|
||||
* is created, then some money deposit and transfer actions are taken so a new state of accounts is
|
||||
* created. At that point, state is cleared in order to represent a system shot down. After the shot
|
||||
* down, system state is recovered by re-creating the past events from event journal. Then state is
|
||||
* printed so a user can view the last state is same with the state before system shot down.
|
||||
*
|
||||
* Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
*/
|
||||
public class App {
|
||||
|
||||
@ -86,10 +86,10 @@ public class App {
|
||||
LOGGER.info("Do some money operations............");
|
||||
|
||||
eventProcessor.process(new MoneyDepositEvent(
|
||||
2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000")));
|
||||
2, new Date().getTime(), ACCOUNT_OF_DAENERYS, new BigDecimal("100000")));
|
||||
|
||||
eventProcessor.process(new MoneyDepositEvent(
|
||||
3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100")));
|
||||
3, new Date().getTime(), ACCOUNT_OF_JON, new BigDecimal("100")));
|
||||
|
||||
eventProcessor.process(new MoneyTransferEvent(
|
||||
4, new Date().getTime(), new BigDecimal("10000"), ACCOUNT_OF_DAENERYS,
|
||||
|
@ -32,11 +32,11 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* This is the Account class that holds the account info, the account number,
|
||||
* account owner name and money of the account. Account class also have the business logic of events
|
||||
* that effects this account.
|
||||
* This is the Account class that holds the account info, the account number, account owner name and
|
||||
* money of the account. Account class also have the business logic of events that effects this
|
||||
* account.
|
||||
*
|
||||
* Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
*/
|
||||
public class Account {
|
||||
|
||||
@ -45,14 +45,15 @@ public class Account {
|
||||
private final int accountNo;
|
||||
private final String owner;
|
||||
private BigDecimal money;
|
||||
|
||||
private static final String MSG = "Some external api for only realtime execution could be called here.";
|
||||
|
||||
private static final String MSG =
|
||||
"Some external api for only realtime execution could be called here.";
|
||||
|
||||
/**
|
||||
* Instantiates a new Account.
|
||||
*
|
||||
* @param accountNo the account no
|
||||
* @param owner the owner
|
||||
* @param owner the owner
|
||||
*/
|
||||
public Account(int accountNo, String owner) {
|
||||
this.accountNo = accountNo;
|
||||
|
@ -27,12 +27,11 @@ import com.iluwatar.event.sourcing.domain.Account;
|
||||
import com.iluwatar.event.sourcing.state.AccountAggregate;
|
||||
|
||||
/**
|
||||
* This is the class that implements account create event.
|
||||
* Holds the necessary info for an account create event.
|
||||
* Implements the process function that finds the event related
|
||||
* domain objects and calls the related domain object's handle event functions
|
||||
* This is the class that implements account create event. Holds the necessary info for an account
|
||||
* create event. Implements the process function that finds the event related domain objects and
|
||||
* calls the related domain object's handle event functions
|
||||
*
|
||||
* Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
*/
|
||||
public class AccountCreateEvent extends DomainEvent {
|
||||
|
||||
@ -42,10 +41,10 @@ public class AccountCreateEvent extends DomainEvent {
|
||||
/**
|
||||
* Instantiates a new Account create event.
|
||||
*
|
||||
* @param sequenceId the sequence id
|
||||
* @param sequenceId the sequence id
|
||||
* @param createdTime the created time
|
||||
* @param accountNo the account no
|
||||
* @param owner the owner
|
||||
* @param accountNo the account no
|
||||
* @param owner the owner
|
||||
*/
|
||||
public AccountCreateEvent(long sequenceId, long createdTime, int accountNo, String owner) {
|
||||
super(sequenceId, createdTime, "AccountCreateEvent");
|
||||
|
@ -28,7 +28,7 @@ import java.io.Serializable;
|
||||
/**
|
||||
* This is the base class for domain events. All events must extend this class.
|
||||
*
|
||||
* Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
*/
|
||||
public abstract class DomainEvent implements Serializable {
|
||||
|
||||
@ -40,8 +40,8 @@ public abstract class DomainEvent implements Serializable {
|
||||
/**
|
||||
* Instantiates a new Domain event.
|
||||
*
|
||||
* @param sequenceId the sequence id
|
||||
* @param createdTime the created time
|
||||
* @param sequenceId the sequence id
|
||||
* @param createdTime the created time
|
||||
* @param eventClassName the event class name
|
||||
*/
|
||||
public DomainEvent(long sequenceId, long createdTime, String eventClassName) {
|
||||
|
@ -28,12 +28,11 @@ import com.iluwatar.event.sourcing.state.AccountAggregate;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* This is the class that implements money deposit event.
|
||||
* Holds the necessary info for a money deposit event.
|
||||
* Implements the process function that finds the event related
|
||||
* domain objects and calls the related domain object's handle event functions
|
||||
* This is the class that implements money deposit event. Holds the necessary info for a money
|
||||
* deposit event. Implements the process function that finds the event related domain objects and
|
||||
* calls the related domain object's handle event functions
|
||||
*
|
||||
* Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
*/
|
||||
public class MoneyDepositEvent extends DomainEvent {
|
||||
|
||||
@ -43,10 +42,10 @@ public class MoneyDepositEvent extends DomainEvent {
|
||||
/**
|
||||
* Instantiates a new Money deposit event.
|
||||
*
|
||||
* @param sequenceId the sequence id
|
||||
* @param sequenceId the sequence id
|
||||
* @param createdTime the created time
|
||||
* @param accountNo the account no
|
||||
* @param money the money
|
||||
* @param accountNo the account no
|
||||
* @param money the money
|
||||
*/
|
||||
public MoneyDepositEvent(long sequenceId, long createdTime, int accountNo, BigDecimal money) {
|
||||
super(sequenceId, createdTime, "MoneyDepositEvent");
|
||||
|
@ -28,12 +28,11 @@ import com.iluwatar.event.sourcing.state.AccountAggregate;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* This is the class that implements money transfer event.
|
||||
* Holds the necessary info for a money transfer event.
|
||||
* Implements the process function that finds the event related
|
||||
* domain objects and calls the related domain object's handle event functions
|
||||
* This is the class that implements money transfer event. Holds the necessary info for a money
|
||||
* transfer event. Implements the process function that finds the event related domain objects and
|
||||
* calls the related domain object's handle event functions
|
||||
*
|
||||
* Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
*/
|
||||
public class MoneyTransferEvent extends DomainEvent {
|
||||
|
||||
@ -44,14 +43,14 @@ public class MoneyTransferEvent extends DomainEvent {
|
||||
/**
|
||||
* Instantiates a new Money transfer event.
|
||||
*
|
||||
* @param sequenceId the sequence id
|
||||
* @param createdTime the created time
|
||||
* @param money the money
|
||||
* @param sequenceId the sequence id
|
||||
* @param createdTime the created time
|
||||
* @param money the money
|
||||
* @param accountNoFrom the account no from
|
||||
* @param accountNoTo the account no to
|
||||
* @param accountNoTo the account no to
|
||||
*/
|
||||
public MoneyTransferEvent(long sequenceId, long createdTime, BigDecimal money, int accountNoFrom,
|
||||
int accountNoTo) {
|
||||
int accountNoTo) {
|
||||
super(sequenceId, createdTime, "MoneyTransferEvent");
|
||||
this.money = money;
|
||||
this.accountNoFrom = accountNoFrom;
|
||||
|
@ -26,11 +26,10 @@ package com.iluwatar.event.sourcing.processor;
|
||||
import com.iluwatar.event.sourcing.event.DomainEvent;
|
||||
|
||||
/**
|
||||
* This is the implementation of event processor.
|
||||
* All events are processed by this class.
|
||||
* This processor uses processorJournal to persist and recover events.
|
||||
* This is the implementation of event processor. All events are processed by this class. This
|
||||
* processor uses processorJournal to persist and recover events.
|
||||
*
|
||||
* Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
*/
|
||||
public class DomainEventProcessor {
|
||||
|
||||
|
@ -43,15 +43,14 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This is the implementation of event journal.
|
||||
* This implementation serialize/deserialize the events with JSON
|
||||
* and writes/reads them on a Journal.json file at the working directory.
|
||||
* This is the implementation of event journal. This implementation serialize/deserialize the events
|
||||
* with JSON and writes/reads them on a Journal.json file at the working directory.
|
||||
*
|
||||
* Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
*/
|
||||
public class JsonFileJournal {
|
||||
|
||||
private final File aFile;
|
||||
private final File file;
|
||||
private final List<String> events = new ArrayList<>();
|
||||
private int index = 0;
|
||||
|
||||
@ -59,10 +58,10 @@ public class JsonFileJournal {
|
||||
* Instantiates a new Json file journal.
|
||||
*/
|
||||
public JsonFileJournal() {
|
||||
aFile = new File("Journal.json");
|
||||
if (aFile.exists()) {
|
||||
file = new File("Journal.json");
|
||||
if (file.exists()) {
|
||||
try (BufferedReader input = new BufferedReader(
|
||||
new InputStreamReader(new FileInputStream(aFile), "UTF-8"))) {
|
||||
new InputStreamReader(new FileInputStream(file), "UTF-8"))) {
|
||||
String line;
|
||||
while ((line = input.readLine()) != null) {
|
||||
events.add(line);
|
||||
@ -88,14 +87,14 @@ public class JsonFileJournal {
|
||||
jsonElement = gson.toJsonTree(domainEvent, AccountCreateEvent.class);
|
||||
} else if (domainEvent instanceof MoneyDepositEvent) {
|
||||
jsonElement = gson.toJsonTree(domainEvent, MoneyDepositEvent.class);
|
||||
} else if (domainEvent instanceof MoneyTransferEvent) {
|
||||
} else if (domainEvent instanceof MoneyTransferEvent) {
|
||||
jsonElement = gson.toJsonTree(domainEvent, MoneyTransferEvent.class);
|
||||
} else {
|
||||
throw new RuntimeException("Journal Event not recegnized");
|
||||
}
|
||||
|
||||
try (Writer output = new BufferedWriter(
|
||||
new OutputStreamWriter(new FileOutputStream(aFile, true), "UTF-8"))) {
|
||||
new OutputStreamWriter(new FileOutputStream(file, true), "UTF-8"))) {
|
||||
String eventString = jsonElement.toString();
|
||||
output.write(eventString + "\r\n");
|
||||
} catch (IOException e) {
|
||||
@ -108,7 +107,7 @@ public class JsonFileJournal {
|
||||
* Reset.
|
||||
*/
|
||||
public void reset() {
|
||||
aFile.delete();
|
||||
file.delete();
|
||||
}
|
||||
|
||||
|
||||
@ -135,7 +134,7 @@ public class JsonFileJournal {
|
||||
domainEvent = gson.fromJson(jsonElement, MoneyDepositEvent.class);
|
||||
} else if (eventClassName.equals("MoneyTransferEvent")) {
|
||||
domainEvent = gson.fromJson(jsonElement, MoneyTransferEvent.class);
|
||||
} else {
|
||||
} else {
|
||||
throw new RuntimeException("Journal Event not recegnized");
|
||||
}
|
||||
|
||||
|
@ -28,10 +28,9 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This is the static accounts map holder class.
|
||||
* This class holds the state of the accounts.
|
||||
* This is the static accounts map holder class. This class holds the state of the accounts.
|
||||
*
|
||||
* Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
* <p>Created by Serdar Hamzaogullari on 06.08.2017.
|
||||
*/
|
||||
public class AccountAggregate {
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user