diff options
Diffstat (limited to 'src/main/java/kr/syeyoung/dungeonsguide/stomp')
8 files changed, 260 insertions, 0 deletions
diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/CloseListener.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/CloseListener.java new file mode 100644 index 00000000..a9b21cb7 --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/CloseListener.java @@ -0,0 +1,5 @@ +package kr.syeyoung.dungeonsguide.stomp; + +public interface CloseListener { + void onClose(int code, String reason, boolean remote); +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompClient.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompClient.java new file mode 100644 index 00000000..248bf04b --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompClient.java @@ -0,0 +1,136 @@ +package kr.syeyoung.dungeonsguide.stomp; + +import lombok.Getter; +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.server.DefaultSSLWebSocketServerFactory; +import sun.security.ssl.SSLSocketFactoryImpl; + +import javax.net.ssl.SSLContext; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +public class StompClient extends WebSocketClient implements StompInterface { + public StompClient(URI serverUri, final String token, CloseListener closeListener) throws Exception { + super(serverUri); + this.closeListener = closeListener; + addHeader("Authorization", token); + + connectBlocking(); + while(this.stompClientStatus == StompClientStatus.CONNECTING); + } + private CloseListener closeListener; + + @Getter + private volatile StompClientStatus stompClientStatus = StompClientStatus.CONNECTING; + + @Getter + private StompPayload errorPayload; + + @Override + public void onOpen(ServerHandshake handshakedata) { + send(new StompPayload().method(StompHeader.CONNECT) + .header("accept-version","1.2") + .header("host",uri.getHost()).getBuilt() + ); + } + + @Override + public void onMessage(String message) { + try { + StompPayload payload = StompPayload.parse(message); + if (payload.method() == StompHeader.CONNECTED) { + stompClientStatus = StompClientStatus.CONNECTED; + } else if (payload.method() == StompHeader.ERROR) { + errorPayload = payload; + stompClientStatus = StompClientStatus.ERROR; + this.close(); + } else if (payload.method() == StompHeader.MESSAGE) { + // mesage + StompSubscription stompSubscription = stompSubscriptionMap.get(Integer.parseInt(payload.headers().get("subscription"))); + try { + stompSubscription.getStompMessageHandler().handle(this, payload); + if (stompSubscription.getAckMode() != StompSubscription.AckMode.AUTO) { + send(new StompPayload().method(StompHeader.ACK) + .header("id",payload.headers().get("ack")).getBuilt() + ); + } + } catch (Exception e) { + send(new StompPayload().method(StompHeader.NACK) + .header("id",payload.headers().get("ack")).getBuilt() + ); + e.printStackTrace(); + } + } else if (payload.method() == StompHeader.RECEIPT) { + String receipt_id = payload.headers().get("receipt-id"); + StompPayload payload1 = receiptMap.remove(Integer.parseInt(receipt_id)); + if (payload1.method() == StompHeader.DISCONNECT) { + stompClientStatus = StompClientStatus.DISCONNECTED; + close(); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onClose(int code, String reason, boolean remote) { + closeListener.onClose(code, reason, remote); + } + + @Override + public void onError(Exception ex) { + + } + + private Map<Integer, StompSubscription> stompSubscriptionMap = new HashMap<Integer, StompSubscription>(); + private Map<Integer, StompPayload> receiptMap = new HashMap<Integer, StompPayload>(); + + private int idIncrement = 0; + + @Override + public void send(StompPayload payload) { + if (stompClientStatus != StompClientStatus.CONNECTED) throw new IllegalStateException("not connected"); + payload.method(StompHeader.SEND); + if (payload.headers().get("receipt") != null) + receiptMap.put(Integer.parseInt(payload.headers().get("receipt")), payload); + send(payload); + } + + @Override + public void subscribe(StompSubscription stompSubscription) { + if (stompClientStatus != StompClientStatus.CONNECTED) throw new IllegalStateException("not connected"); + stompSubscription.setId(++idIncrement); + + send(new StompPayload().method(StompHeader.SUBSCRIBE) + .header("id",String.valueOf(stompSubscription.getId())) + .header("destination", stompSubscription.getDestination()) + .header("ack", stompSubscription.getAckMode().getValue()).getBuilt() + ); + + stompSubscriptionMap.put(stompSubscription.getId(), stompSubscription); + } + + @Override + public void unsubscribe(StompSubscription stompSubscription) { + if (stompClientStatus != StompClientStatus.CONNECTED) throw new IllegalStateException("not connected"); + send(new StompPayload().method(StompHeader.UNSUBSCRIBE) + .header("id",String.valueOf(stompSubscription.getId())).getBuilt() + ); + stompSubscriptionMap.remove(stompSubscription.getId()); + } + + @Override + public void disconnect() { + if (stompClientStatus != StompClientStatus.CONNECTED) throw new IllegalStateException("not connected"); + StompPayload stompPayload; + stompClientStatus =StompClientStatus.DISCONNECTING; + send((stompPayload = new StompPayload().method(StompHeader.DISCONNECT) + .header("receipt", String.valueOf(++idIncrement))) + .getBuilt() + ); + receiptMap.put(idIncrement, stompPayload); + } +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompClientStatus.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompClientStatus.java new file mode 100644 index 00000000..b3f5cbc2 --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompClientStatus.java @@ -0,0 +1,5 @@ +package kr.syeyoung.dungeonsguide.stomp; + +public enum StompClientStatus { + CONNECTING, CONNECTED, ERROR, DISCONNECTING, DISCONNECTED; +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompHeader.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompHeader.java new file mode 100644 index 00000000..0e9c4311 --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompHeader.java @@ -0,0 +1,9 @@ +package kr.syeyoung.dungeonsguide.stomp; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; + +public enum StompHeader { + SEND, SUBSCRIBE, UNSUBSCRIBE, BEGIN, COMMIT, ABORT, ACK, NACK, DISCONNECT, CONNECT, STOMP, CONNECTED, MESSAGE, RECEIPT, ERROR +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompInterface.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompInterface.java new file mode 100644 index 00000000..e78f8ceb --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompInterface.java @@ -0,0 +1,8 @@ +package kr.syeyoung.dungeonsguide.stomp; + +public interface StompInterface { + void send(StompPayload payload); + void subscribe(StompSubscription stompSubscription); + void unsubscribe(StompSubscription stompSubscription); + void disconnect(); +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompMessageHandler.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompMessageHandler.java new file mode 100644 index 00000000..7b13c1fd --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompMessageHandler.java @@ -0,0 +1,5 @@ +package kr.syeyoung.dungeonsguide.stomp; + +public interface StompMessageHandler { + void handle(StompInterface stompInterface, StompPayload stompPayload); +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompPayload.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompPayload.java new file mode 100644 index 00000000..9476241c --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompPayload.java @@ -0,0 +1,69 @@ +package kr.syeyoung.dungeonsguide.stomp; + +import lombok.Data; +import lombok.Singular; +import lombok.experimental.Accessors; + +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +@Data +@Accessors(chain = true, fluent = true) +public class StompPayload { + private StompHeader method; + private Map<String, String> headers = new HashMap<String, String>(); + private String payload; + + public StompPayload header(String key, String value) { + headers.put(key, value); + return this; + } + + public String getBuilt() { + StringBuilder sb = new StringBuilder(); + sb.append(method.name()); + sb.append("\n"); + for (Map.Entry<String, String> stringStringEntry : headers.entrySet()) { + sb.append(stringStringEntry.getKey()); + sb.append(":"); + sb.append(stringStringEntry.getValue()); + sb.append("\n"); + if (stringStringEntry.getKey().contains(":")) throw new IllegalStateException("Illegal Character : inside headers"); + if (stringStringEntry.getValue().contains(":")) throw new IllegalStateException("Illegal Character : inside headers"); + } + sb.append("\n"); + if (payload != null) + sb.append(payload); + sb.append((char) 0); + System.out.println("Probably sending "+sb.toString()); + return sb.toString(); + } + + public static StompPayload parse(String payload) { + System.out.println("Parsing "+payload); + Scanner scanner = new Scanner(payload); + StompPayload stompPayload = new StompPayload(); + stompPayload.method = StompHeader.valueOf(scanner.nextLine()); + String line = ""; + while (!(line = scanner.nextLine()).isEmpty()) { + int index = line.indexOf(":"); + if (index == -1) throw new IllegalArgumentException("No : found in headers section"); + String name = line.substring(0, index); + String value; + if (index == line.length() - 1) + value = ""; + else + value = line.substring(index+1); + stompPayload.headers.put(name, value); + } + + StringBuilder payloadBuilder = new StringBuilder(); + while (scanner.hasNextLine() && !(line = scanner.nextLine()).equals("\0")) { + payloadBuilder.append(line); + payloadBuilder.append("\n"); + } + stompPayload.payload = payloadBuilder.toString(); + return stompPayload; + } +} diff --git a/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompSubscription.java b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompSubscription.java new file mode 100644 index 00000000..5ecbeb06 --- /dev/null +++ b/src/main/java/kr/syeyoung/dungeonsguide/stomp/StompSubscription.java @@ -0,0 +1,23 @@ +package kr.syeyoung.dungeonsguide.stomp; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; + +@Data +@Builder +public class StompSubscription { + private int id; + private String destination; + private StompMessageHandler stompMessageHandler; + private AckMode ackMode; + + @AllArgsConstructor + public static enum AckMode { + AUTO("auto"), CLIENT("client"), CLIENT_INDIVIDUAL("client-individual"); + + @Getter + private String value; + } +} |