package com.spbsu.crawl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.spbsu.commons.io.StreamTools;
import com.spbsu.commons.seq.CharSequenceReader;
import com.spbsu.crawl.data.Message;
import com.spbsu.crawl.data.Protocol;
import javax.websocket.*;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetDecoder;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
/**
* Experts League
* Created by solar on 23/03/16.
*/
@ClientEndpoint
public class WSEndpoint {
private static final Logger log = Logger.getLogger(WSEndpoint.class.getName());
@SuppressWarnings("FieldCanBeLocal")
private final Thread outThread;
private BlockingQueue<Message> in = new LinkedBlockingQueue<>();
BlockingQueue<Message> out = new LinkedBlockingQueue<>();
private final Session session;
private Inflater inflater;
private CharsetDecoder decoder;
private final ObjectMapper mapper;
public WSEndpoint(URI uri) throws IOException, DeploymentException {
decoder = StreamTools.UTF.newDecoder();
mapper = new ObjectMapper();
inflater = new Inflater(true);
final WebSocketContainer container = ContainerProvider.getWebSocketContainer();
session = container.connectToServer(this, uri);
outThread = new Thread(() -> {
try {
//noinspection InfiniteLoopStatement
while (true) {
final Message poll = out.take();
final ObjectNode node = mapper.valueToTree(poll);
node.set("msg", new TextNode(poll.type().name().toLowerCase()));
final String clientJson = mapper.writeValueAsString(node);
session.getAsyncRemote().sendText(clientJson);
// log.info("[CLIENT]: " + clientJson);
}
} catch (InterruptedException | JsonProcessingException e) {
throw new RuntimeException(e);
}
}, "JSON Output thread");
outThread.setDaemon(true);
outThread.start();
}
@OnClose
public void close() {
log.info("Closed");
inflater.end();
}
@OnError
public void error(Throwable th) {
th.printStackTrace();
log.log(Level.WARNING, "Error", th);
inflater.end();
}
@OnMessage
public void onMessage(String msg) {
handleMessage(msg);
}
@OnMessage
public void onMessage(ByteBuffer buffer) {
try {
final ByteBuffer inBuffer = ByteBuffer.allocate(buffer.remaining() + 4);
inBuffer.put(buffer);
inBuffer.put((byte) 0);
inBuffer.put((byte) 0);
inBuffer.put((byte) -1);
inBuffer.put((byte) -1);
inBuffer.flip();
if (!inBuffer.hasArray()) {
System.out.println("Error: message with empty buffer");
return;
}
inflater.setInput(inBuffer.array(), inBuffer.position(), inBuffer.remaining());
final StringBuilder builder = new StringBuilder();
final ByteBuffer outBuffer = ByteBuffer.allocate(4096);
while (!inflater.needsInput()) {
final int inflate = inflater.inflate(outBuffer.array(), outBuffer.position(), outBuffer.remaining());
outBuffer.limit(outBuffer.position() + inflate);
builder.append(decoder.decode(outBuffer));
outBuffer.compact();
}
handleMessage(builder);
}
catch (DataFormatException | CharacterCodingException e) {
log.log(Level.WARNING, "WS message format exception: " + e.getMessage());
}
}
private void handleMessage(final CharSequence message) {
final JsonNode node;
try {
node = mapper.readTree(new CharSequenceReader(message));
}
catch (IOException e) {
// log.log(Level.WARNING, "JSON format exception. Message: '" + message + "'. Exception msg: " + e.getMessage() + ". Skipping the message.");
return;
}
final JsonNode msgs = node.get("msgs");
// log.info("[SERVER]: " + node.toString());
if (msgs != null) {
for (JsonNode msg : msgs) {
onItem(msg);
}
} else {
onItem(node);
}
}
public Message poll() {
try {
return in.poll(1, TimeUnit.HOURS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public void send(Message msg) {
try {
out.put(msg);
}
catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
private void onItem(JsonNode node) {
try {
final Protocol msg = Protocol.valueOf(node.get("msg").asText().toUpperCase());
in.put(mapper.treeToValue(node, msg.clazz()));
}
catch (JsonProcessingException | InterruptedException e) {
throw new RuntimeException(e);
}
catch (IllegalArgumentException iae) {
log.warning("Unknown message type: " + node.get("msg").asText().toUpperCase());
}
}
@OnOpen
public void open(Session wsSession) {
}
public BlockingQueue<Message> getMessagesQueue() {
return in;
}
}