Система событий
This commit is contained in:
@@ -22,7 +22,7 @@ public class Main {
|
|||||||
/**
|
/**
|
||||||
* Запуск сервера на порту 8881
|
* Запуск сервера на порту 8881
|
||||||
*/
|
*/
|
||||||
Server server = new Server(settings, manager, Configuration.class);
|
Server server = new Server(settings, manager);
|
||||||
server.start();
|
server.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
17
src/main/java/com/rosetta/im/event/Cancelable.java
Normal file
17
src/main/java/com/rosetta/im/event/Cancelable.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package com.rosetta.im.event;
|
||||||
|
|
||||||
|
public interface Cancelable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отменено ли событие
|
||||||
|
* @return true, если событие отменено
|
||||||
|
*/
|
||||||
|
boolean isCanceled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Установить отмену события
|
||||||
|
* @param canceled true, если событие должно быть отменено
|
||||||
|
*/
|
||||||
|
void setCanceled(boolean canceled);
|
||||||
|
|
||||||
|
}
|
||||||
18
src/main/java/com/rosetta/im/event/Event.java
Normal file
18
src/main/java/com/rosetta/im/event/Event.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package com.rosetta.im.event;
|
||||||
|
|
||||||
|
public class Event {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
public Event() {}
|
||||||
|
|
||||||
|
public String getEventName() {
|
||||||
|
if(this.name == null) {
|
||||||
|
this.name = this.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
13
src/main/java/com/rosetta/im/event/EventException.java
Normal file
13
src/main/java/com/rosetta/im/event/EventException.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package com.rosetta.im.event;
|
||||||
|
|
||||||
|
public class EventException extends Exception {
|
||||||
|
|
||||||
|
public EventException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EventException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
12
src/main/java/com/rosetta/im/event/EventHandler.java
Normal file
12
src/main/java/com/rosetta/im/event/EventHandler.java
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package com.rosetta.im.event;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface EventHandler {
|
||||||
|
|
||||||
|
}
|
||||||
120
src/main/java/com/rosetta/im/event/EventManager.java
Normal file
120
src/main/java/com/rosetta/im/event/EventManager.java
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package com.rosetta.im.event;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Менеджер событий
|
||||||
|
*/
|
||||||
|
public class EventManager {
|
||||||
|
|
||||||
|
private Set<Listener> listeners;
|
||||||
|
|
||||||
|
public EventManager() {
|
||||||
|
this.listeners = new HashSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Регистрация слушателя событий
|
||||||
|
* @param listener Слушатель событий
|
||||||
|
*/
|
||||||
|
public void registerListener(Listener listener) {
|
||||||
|
this.listeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Удаление слушателя событий
|
||||||
|
* @param listener Слушатель событий
|
||||||
|
*/
|
||||||
|
public void unregisterListener(Listener listener) {
|
||||||
|
this.listeners.remove(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить все зарегистрированные слушатели событий
|
||||||
|
* @return Множество слушателей событий
|
||||||
|
*/
|
||||||
|
public Set<Listener> getListeners() {
|
||||||
|
return this.listeners;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Вызывает событие и обрабатывает исключения
|
||||||
|
* @param event Событие для вызова
|
||||||
|
* @return true, если событие отменено
|
||||||
|
*/
|
||||||
|
public boolean callEvent(Event event) {
|
||||||
|
try {
|
||||||
|
return fireEvent(event);
|
||||||
|
} catch (EventException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Запускает событие, уведомляя всех зарегистрированных слушателей, возвращает true если событие отменено
|
||||||
|
* @param event Событие для запуска
|
||||||
|
* @return true, если событие отменено
|
||||||
|
* @throws EventException Ошибка при обработке события
|
||||||
|
* @throws Exception Общая ошибка при вызове метода
|
||||||
|
*/
|
||||||
|
private boolean fireEvent(Event event) throws EventException, Exception {
|
||||||
|
for(Listener listener : this.listeners) {
|
||||||
|
/**
|
||||||
|
* Получаем все методы в Listener с аннотацией @EventHandler
|
||||||
|
*/
|
||||||
|
List<Method> methods = getMethodsWithAnnotation(listener.getClass(), EventHandler.class);
|
||||||
|
for(Method method : methods) {
|
||||||
|
/**
|
||||||
|
* Проверяем, что метод принимает один параметр того же типа, что и событие
|
||||||
|
*/
|
||||||
|
if(method.getParameterCount() == 1
|
||||||
|
&& method.getParameterTypes()[0].isAssignableFrom(event.getClass())) {
|
||||||
|
/**
|
||||||
|
* Если параметры совпадают и они одного типа - вызываем событие
|
||||||
|
*/
|
||||||
|
method.setAccessible(true);
|
||||||
|
method.invoke(listener, event);
|
||||||
|
/**
|
||||||
|
* Если событие отменяемое - проверяем его статус
|
||||||
|
*/
|
||||||
|
if(event instanceof Cancelable) {
|
||||||
|
Cancelable cancelableEvent = (Cancelable) event;
|
||||||
|
if(cancelableEvent.isCanceled()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Если метод обрабатывающий событие
|
||||||
|
* реализован неправильно - выбрасываем исключение
|
||||||
|
*/
|
||||||
|
throw new EventException("Invalid event params");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private List<Method> getMethodsWithAnnotation(Class<?> clazz, Class<? extends Annotation> annotation) {
|
||||||
|
List<Method> methods = new ArrayList<>();
|
||||||
|
for (Method method : clazz.getDeclaredMethods()) {
|
||||||
|
if (method.isAnnotationPresent(annotation)) {
|
||||||
|
methods.add(method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
25
src/main/java/com/rosetta/im/event/EventPriority.java
Normal file
25
src/main/java/com/rosetta/im/event/EventPriority.java
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package com.rosetta.im.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Приоритет события
|
||||||
|
* Указывает на то, в каком порядке обработаются два одинаковых события
|
||||||
|
*/
|
||||||
|
public enum EventPriority {
|
||||||
|
LOW(0),
|
||||||
|
MEDIUM(1),
|
||||||
|
HIGH(2);
|
||||||
|
|
||||||
|
private final int priority;
|
||||||
|
|
||||||
|
EventPriority(int priority) {
|
||||||
|
this.priority = priority;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить приоритет
|
||||||
|
* @return Приоритет события
|
||||||
|
*/
|
||||||
|
public int getPriority() {
|
||||||
|
return priority;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/main/java/com/rosetta/im/event/Listener.java
Normal file
10
src/main/java/com/rosetta/im/event/Listener.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package com.rosetta.im.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Слушатель событий
|
||||||
|
*/
|
||||||
|
public interface Listener {
|
||||||
|
/**
|
||||||
|
* Пустой интерфейс для обозначения слушателя событий
|
||||||
|
*/
|
||||||
|
}
|
||||||
3
src/main/java/io/orprotocol/Context.java
Normal file
3
src/main/java/io/orprotocol/Context.java
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package io.orprotocol;
|
||||||
|
|
||||||
|
public class Context extends Object {}
|
||||||
@@ -20,23 +20,56 @@ public class Server extends WebSocketServer {
|
|||||||
private PacketManager packetManager;
|
private PacketManager packetManager;
|
||||||
private Settings settings;
|
private Settings settings;
|
||||||
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||||
private Object attachment;
|
private Context context;
|
||||||
|
private ServerListener listener;
|
||||||
|
|
||||||
public Server(Settings settings, PacketManager packetManager, Object attachment) {
|
/**
|
||||||
|
* Конструктор сервера
|
||||||
|
* @param settings базовые настройки серверера
|
||||||
|
* @param packetManager менеджер пакетов (обработчиков и зарегистрированных пакетов)
|
||||||
|
*/
|
||||||
|
public Server(Settings settings, PacketManager packetManager) {
|
||||||
super(new InetSocketAddress(settings.port));
|
super(new InetSocketAddress(settings.port));
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
this.packetManager = packetManager;
|
this.packetManager = packetManager;
|
||||||
this.attachment = attachment;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Конструктор сервера с объектом прикрепления
|
||||||
|
* @param settings базовые настройки серверера
|
||||||
|
* @param packetManager менеджер пакетов (обработчиков и зарегистрированных пакетов)
|
||||||
|
* @param context вложение которое будет передаваться всем серрверным обработчикам пакетов,
|
||||||
|
* может быть использовано для передачи контекста приложения
|
||||||
|
*/
|
||||||
|
public Server(Settings settings, PacketManager packetManager, Context context) {
|
||||||
|
super(new InetSocketAddress(settings.port));
|
||||||
|
this.settings = settings;
|
||||||
|
this.packetManager = packetManager;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Server(Settings settings, PacketManager packetManager, Context context, ServerListener listener) {
|
||||||
|
super(new InetSocketAddress(settings.port));
|
||||||
|
this.settings = settings;
|
||||||
|
this.packetManager = packetManager;
|
||||||
|
this.context = context;
|
||||||
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClose(WebSocket arg0, int arg1, String arg2, boolean arg3) {
|
public void onClose(WebSocket socket, int arg1, String arg2, boolean arg3) {
|
||||||
|
if(this.listener == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.listener.onClientDisconnect(this, socket.getAttachment());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(WebSocket arg0, Exception arg1) {
|
public void onError(WebSocket arg0, Exception arg1) {
|
||||||
|
if(this.listener == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.listener.onError(this, arg1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -47,6 +80,12 @@ public class Server extends WebSocketServer {
|
|||||||
*/
|
*/
|
||||||
Client client = socket.getAttachment();
|
Client client = socket.getAttachment();
|
||||||
client.updateHeartbeat();
|
client.updateHeartbeat();
|
||||||
|
if(this.listener != null) {
|
||||||
|
/**
|
||||||
|
* Сообщение от клиента, но пакет не сформирован, передаем null
|
||||||
|
*/
|
||||||
|
this.listener.onPacketReceived(this, client, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -90,7 +129,13 @@ public class Server extends WebSocketServer {
|
|||||||
Class<? extends PacketExecutor> executorClass = this.packetManager.getExecutors().get(packetId);
|
Class<? extends PacketExecutor> executorClass = this.packetManager.getExecutors().get(packetId);
|
||||||
PacketExecutor executor = executorClass.getConstructor().newInstance();
|
PacketExecutor executor = executorClass.getConstructor().newInstance();
|
||||||
executor.settings = this.settings;
|
executor.settings = this.settings;
|
||||||
executor.attachment = this.attachment;
|
executor.context = this.context;
|
||||||
|
if(listener != null && listener.onPacketReceived(this, client, packet)) {
|
||||||
|
/**
|
||||||
|
* Если слушатель сервера вернул true, пакет не обрабатываем.
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
executor.onPacketReceived(packet, client);
|
executor.onPacketReceived(packet, client);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
System.out.println("Error while processing packet " + packetClass.getName());
|
System.out.println("Error while processing packet " + packetClass.getName());
|
||||||
@@ -107,6 +152,13 @@ public class Server extends WebSocketServer {
|
|||||||
*/
|
*/
|
||||||
Client client = new Client(socket, this.settings.heartbeatInterval);
|
Client client = new Client(socket, this.settings.heartbeatInterval);
|
||||||
socket.setAttachment(client);
|
socket.setAttachment(client);
|
||||||
|
if(this.listener == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(this.listener.onClientConnect(this, client)) {
|
||||||
|
client.disconnect(ServerFailures.SERVER_NOT_ACCEPT_CLIENT);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -118,6 +170,10 @@ public class Server extends WebSocketServer {
|
|||||||
|
|
||||||
int port = this.getPort();
|
int port = this.getPort();
|
||||||
System.out.println("\u001B[32mServer started at x.x.x.x:" + port + "\u001B[0m");
|
System.out.println("\u001B[32mServer started at x.x.x.x:" + port + "\u001B[0m");
|
||||||
|
if(this.listener == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.listener.onServerStart(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -24,6 +24,11 @@ public enum ServerFailures implements BaseFailures {
|
|||||||
* Код ошибки, указывающий на тайм-аут бездействия.
|
* Код ошибки, указывающий на тайм-аут бездействия.
|
||||||
*/
|
*/
|
||||||
INACTIVITY_TIMEOUT(3004),
|
INACTIVITY_TIMEOUT(3004),
|
||||||
|
/**
|
||||||
|
* Сервер по какой-то причине не принимает подключение клиента
|
||||||
|
* Выбрасывается в методе onClientConnect при отмене события например защитой
|
||||||
|
*/
|
||||||
|
SERVER_NOT_ACCEPT_CLIENT(3005),
|
||||||
/**
|
/**
|
||||||
* Код ошибки, указывающий на неизвестный тип пакета.
|
* Код ошибки, указывающий на неизвестный тип пакета.
|
||||||
*/
|
*/
|
||||||
|
|||||||
45
src/main/java/io/orprotocol/ServerListener.java
Normal file
45
src/main/java/io/orprotocol/ServerListener.java
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package io.orprotocol;
|
||||||
|
|
||||||
|
import io.orprotocol.client.Client;
|
||||||
|
import io.orprotocol.packet.Packet;
|
||||||
|
|
||||||
|
public interface ServerListener {
|
||||||
|
/**
|
||||||
|
* Сервер запущен
|
||||||
|
* @param server
|
||||||
|
*/
|
||||||
|
void onServerStart(Server server);
|
||||||
|
/**
|
||||||
|
* Сервер остановлен
|
||||||
|
* @param server
|
||||||
|
*/
|
||||||
|
void onServerStop(Server server);
|
||||||
|
/**
|
||||||
|
* Клиент подключился
|
||||||
|
* @param server
|
||||||
|
* @param client
|
||||||
|
* @return если возвращено true значит клиент не будет подключен к серверу выбросится
|
||||||
|
* ошибка SERVER_NOT_ACCEPT_CLIENT
|
||||||
|
*/
|
||||||
|
boolean onClientConnect(Server server, Client client);
|
||||||
|
/**
|
||||||
|
* Клиент отключился
|
||||||
|
* @param server
|
||||||
|
* @param client
|
||||||
|
*/
|
||||||
|
void onClientDisconnect(Server server, Client client);
|
||||||
|
/**
|
||||||
|
* Произошла ошибка сервера
|
||||||
|
* @param server
|
||||||
|
* @param exception
|
||||||
|
*/
|
||||||
|
void onError(Server server, Exception exception);
|
||||||
|
/**
|
||||||
|
* Пакет получен от клиента
|
||||||
|
* @param server
|
||||||
|
* @param client
|
||||||
|
* @param packet
|
||||||
|
* @return если возвращено true значит пакет не будет обработан дальше
|
||||||
|
*/
|
||||||
|
boolean onPacketReceived(Server server, Client client, Packet packet);
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.orprotocol.packet;
|
package io.orprotocol.packet;
|
||||||
|
|
||||||
|
import io.orprotocol.Context;
|
||||||
import io.orprotocol.Settings;
|
import io.orprotocol.Settings;
|
||||||
import io.orprotocol.client.Client;
|
import io.orprotocol.client.Client;
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ import io.orprotocol.client.Client;
|
|||||||
*/
|
*/
|
||||||
public abstract class PacketExecutor {
|
public abstract class PacketExecutor {
|
||||||
public Settings settings;
|
public Settings settings;
|
||||||
public Object attachment;
|
public Context context;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Настройки сервера.
|
* Настройки сервера.
|
||||||
@@ -19,11 +20,11 @@ public abstract class PacketExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Вложенный обьект, который был передан при создании сервера.
|
* Контекст приложения переданный при создании сервера.
|
||||||
* @return вложенный обьект
|
* @return контекст
|
||||||
*/
|
*/
|
||||||
public Object getAttachment() {
|
public Context getContext() {
|
||||||
return attachment;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user