package com.revolsys.websocket.json;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.UUID;
import javax.websocket.RemoteEndpoint.Async;
import javax.websocket.SendHandler;
import javax.websocket.SendResult;
import javax.websocket.Session;
import com.revolsys.collection.map.Maps;
import com.revolsys.logging.Logs;
import com.revolsys.util.Property;
import com.revolsys.websocket.AsyncResult;
public class JsonAsyncSender implements SendHandler {
private final Async async;
private final Map<String, AsyncResult<Map<String, Object>>> messageCallbackById = new HashMap<>();
private final Map<String, Map<String, Object>> messageResultById = new HashMap<>();
private final LinkedList<Map<String, Object>> messages = new LinkedList<>();
private final Session session;
public JsonAsyncSender(final Session session) {
this.session = session;
this.async = session.getAsyncRemote();
}
public void close() {
synchronized (this.messageCallbackById) {
for (final AsyncResult<Map<String, Object>> messageProcessor : this.messageCallbackById
.values()) {
synchronized (messageProcessor) {
messageProcessor.notify();
}
}
this.messageCallbackById.clear();
}
}
private void doSendMessage(final Map<String, Object> message) {
this.async.sendObject(message, this);
}
@Override
public void onResult(final SendResult result) {
synchronized (this.messages) {
if (this.session.isOpen()) {
if (!result.isOK()) {
Logs.error(this, "Error sending message", result.getException());
}
this.messages.removeFirst();
if (!this.messages.isEmpty()) {
final Map<String, Object> message = this.messages.getFirst();
doSendMessage(message);
} else {
this.messages.clear();
}
}
}
}
public <V> V sendAndWait(final Map<String, Object> message,
final AsyncResult<Map<String, Object>> messageProcessor) {
final String messageId = UUID.randomUUID().toString();
message.put("messageId", messageId);
synchronized (this.messageCallbackById) {
this.messageCallbackById.put(messageId, messageProcessor);
}
synchronized (messageProcessor) {
try {
sendMessage(message);
messageProcessor.wait();
final Map<String, Object> result = this.messageResultById.remove(messageId);
if (result == null) {
throw new RuntimeException("No result returned: " + message);
} else {
return messageProcessor.getResult(result);
}
} catch (final Throwable e) {
throw new RuntimeException("Error getting result: " + message, e);
} finally {
this.messageCallbackById.remove(messageId);
this.messageResultById.remove(messageId);
}
}
}
public synchronized void sendMessage(final Map<String, Object> message) {
final boolean hasMessage = !this.messages.isEmpty();
if (this.session.isOpen()) {
this.messages.addLast(message);
if (!hasMessage) {
doSendMessage(message);
}
}
}
public boolean setResult(final Map<String, Object> message) {
if (this.session.isOpen()) {
final String messageId = Maps.getString(message, "messageId");
if (Property.hasValue(messageId)) {
synchronized (this.messageCallbackById) {
final AsyncResult<Map<String, Object>> resultCallback = this.messageCallbackById
.get(messageId);
if (resultCallback != null) {
synchronized (resultCallback) {
this.messageResultById.put(messageId, message);
resultCallback.notify();
return true;
}
}
}
}
}
return false;
}
}