/*******************************************************************************
* Copyright (c) 2012-2016 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.everrest.websockets.message;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.websocket.CloseReason;
import javax.websocket.EncodeException;
import javax.websocket.SendHandler;
import javax.websocket.SendResult;
import javax.websocket.Session;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.LinkedList;
import static javax.websocket.CloseReason.CloseCodes.VIOLATED_POLICY;
import static javax.websocket.RemoteEndpoint.Async;
/**
* @author andrew00x
*/
public class MessageSender {
private static final Logger LOG = LoggerFactory.getLogger(MessageSender.class);
// todo: make configurable
private final int maxNumberOfMessageInQueue = 1_000_000_000;
private final Session session;
private final Async async;
private final LinkedList<MessageWrapper> sendQueue;
private final SendHandler sendHandler;
private final Object lock = new Object();
private volatile boolean sendingInProgress = false;
public MessageSender(Session session) {
this.session = session;
async = session.getAsyncRemote();
sendQueue = new LinkedList<>();
sendHandler = new MessageSendHandler();
}
public void send(Message message) throws IOException, EncodeException {
send(new MessageWrapper(message));
}
public void send(String text) throws IOException {
send(new MessageWrapper(text));
}
public void send(byte[] bytes) throws IOException {
send(new MessageWrapper(bytes));
}
private void send(MessageWrapper message) throws IOException {
synchronized (lock) {
if (sendingInProgress) {
if (isMaxQueueCapacityExceeded()) {
final String error = "Max size of message queue exceeded";
session.close(new CloseReason(VIOLATED_POLICY, error));
throw new IOException(error);
} else {
sendQueue.add(message);
}
} else {
sendingInProgress = true;
doSend(message);
}
}
}
private boolean isMaxQueueCapacityExceeded() {
final int newSize = sendQueue.size() + 1;
LOG.debug(" SendQueue size {} , maxNumberOfMessageInQueue {}", newSize, maxNumberOfMessageInQueue);
return newSize > maxNumberOfMessageInQueue;
}
private void doSend(MessageWrapper messageForSending) {
if (messageForSending.isText()) {
async.sendText(messageForSending.getText(), sendHandler);
} else if (messageForSending.isBinary()) {
async.sendBinary(messageForSending.getBinary(), sendHandler);
} else if (messageForSending.isMessage()) {
async.sendObject(messageForSending.getMessage(), sendHandler);
}
}
private static final class MessageWrapper {
private final Message message;
private final byte[] bytes;
private final String text;
MessageWrapper(String text) {
this.text = text;
this.message = null;
this.bytes = null;
}
MessageWrapper(byte[] bytes) {
this.bytes = bytes;
this.message = null;
this.text = null;
}
MessageWrapper(Message message) {
this.message = message;
this.bytes = null;
this.text = null;
}
boolean isText() {
return text != null;
}
boolean isBinary() {
return bytes != null;
}
boolean isMessage() {
return message != null;
}
Message getMessage() {
return message;
}
ByteBuffer getBinary() {
return ByteBuffer.wrap(bytes);
}
String getText() {
return text;
}
}
private class MessageSendHandler implements SendHandler {
@Override
public void onResult(SendResult result) {
LOG.debug(" SendQueue size {} , maxNumberOfMessageInQueue {} result {}",
sendQueue.size(),
maxNumberOfMessageInQueue,
result.isOK());
if (!result.isOK()) {
try {
session.close();
} catch (IOException ignored) {
} finally {
sendQueue.clear();
}
}
synchronized (lock) {
if (sendQueue.isEmpty()) {
sendingInProgress = false;
} else {
MessageWrapper message = sendQueue.remove();
doSend(message);
}
}
}
}
}