
org.everrest.websockets.client.WSClient Maven / Gradle / Ivy
/*******************************************************************************
* 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.client;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.base.Throwables.propagateIfPossible;
import static java.util.concurrent.Executors.newCachedThreadPool;
import static java.util.concurrent.TimeUnit.SECONDS;
import static javax.websocket.ContainerProvider.getWebSocketContainer;
import static javax.websocket.RemoteEndpoint.Basic;
import static org.everrest.core.impl.uri.UriComponent.parseQueryString;
import static org.everrest.websockets.message.RestInputMessage.newSubscribeChannelMessage;
import static org.everrest.websockets.message.RestInputMessage.newUnsubscribeChannelMessage;
import org.everrest.core.impl.provider.json.JsonException;
import org.everrest.websockets.message.BaseTextEncoder;
import org.everrest.websockets.message.InputMessage;
import org.everrest.websockets.message.JsonMessageConverter;
import org.everrest.websockets.message.MessageSender;
import org.slf4j.LoggerFactory;
import javax.websocket.ClientEndpoint;
import javax.websocket.CloseReason;
import javax.websocket.DeploymentException;
import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
/**
* @author andrew00x
*/
@ClientEndpoint(encoders = {WSClient.InputMessageEncoder.class})
public class WSClient {
private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(WSClient.class);
private static final AtomicLong sequence = new AtomicLong(1);
private static ExecutorService executor = newCachedThreadPool(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
final Thread t = new Thread(r, "everrest.WSClient" + sequence.getAndIncrement());
t.setDaemon(true);
return t;
}
});
private final URI serverUri;
private final List listeners;
private final List channels;
private Session session;
private MessageSender messageSender;
public WSClient(URI serverUri, ClientMessageListener... listeners) {
this(Builder.create(serverUri).listeners(listeners));
}
public WSClient(Builder builder) {
this.serverUri = builder.serverUri;
this.listeners = builder.listeners;
this.channels = builder.channels;
}
/**
* Connect to server endpoint.
*
* @param timeout
* connection timeout value in seconds
* @throws IOException
* if connection failed
* @throws SocketTimeoutException
* if timeout occurs while try to connect to server endpoint
* @throws IllegalArgumentException
* if {@code timeout} zero or negative
*/
public void connect(int timeout) throws IOException, DeploymentException {
if (timeout < 1) {
throw new IllegalArgumentException(String.format("Invalid timeout: %d", timeout));
}
final WebSocketContainer container = getWebSocketContainer();
container.setAsyncSendTimeout(1);
try {
executor.submit(new Callable() {
@Override
public Void call() throws Exception {
final Session session = container.connectToServer(WSClient.this, serverUri);
final Basic remoteEndpoint = session.getBasicRemote();
for (String channel : channels) {
remoteEndpoint.sendObject(newSubscribeChannelMessage(uuid(), channel));
}
return null;
}
}).get(timeout, SECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (TimeoutException e) {
throw new SocketTimeoutException("Connection timeout");
} catch (ExecutionException e) {
propagateIfPossible(e.getCause(), IOException.class, DeploymentException.class);
propagate(e);
}
}
public void disconnect() throws IOException {
if (isConnected()) {
session.close();
}
}
public URI getServerUri() {
return serverUri;
}
public boolean isConnected() {
return session != null && session.isOpen();
}
private void checkIsConnected() {
if (!isConnected()) {
throw new IllegalStateException("Unable send message because the WebSocket connection has been closed");
}
}
public void send(String message) throws IOException {
checkIsConnected();
messageSender.send(message);
}
public void send(byte[] message) throws IOException {
checkIsConnected();
messageSender.send(message);
}
public void send(InputMessage message) throws IOException, EncodeException {
checkIsConnected();
messageSender.send(message);
}
public void subscribeToChannel(String channel) throws IOException {
try {
send(newSubscribeChannelMessage(uuid(), channel));
} catch (EncodeException e) {
propagate(e);
}
LOG.debug("Subscribed to channel {}", channel);
}
public void unsubscribeFromChannel(String channel) throws IOException {
try {
send(newUnsubscribeChannelMessage(uuid(), channel));
} catch (EncodeException e) {
propagate(e);
}
LOG.debug("Unsubscribed from channel {}", channel);
}
private static String uuid() {
return UUID.randomUUID().toString();
}
@OnOpen
public void onOpen(Session session) {
LOG.debug("WS session {} started", session.getId());
this.session = session;
messageSender = new MessageSender(session);
for (ClientMessageListener listener : listeners) {
listener.onOpen(this);
}
}
@OnMessage
public void processTextMessage(String message) {
for (ClientMessageListener listener : listeners) {
listener.onMessage(message);
}
}
@OnMessage
public void processBinaryMessage(byte[] message) {
for (ClientMessageListener listener : listeners) {
listener.onMessage(message);
}
}
@OnClose
public void onClose(Session session, CloseReason closeReason) {
LOG.debug("WS session {} about to be closed, {}", session.getId(), closeReason);
for (ClientMessageListener listener : listeners) {
listener.onClose(closeReason.getCloseCode().getCode(), closeReason.getReasonPhrase());
}
}
@OnError
public void onError(Throwable error) {
LOG.warn(error.getMessage(), error);
}
public static class InputMessageEncoder extends BaseTextEncoder {
private final JsonMessageConverter jsonMessageConverter = new JsonMessageConverter();
@Override
public String encode(InputMessage output) throws EncodeException {
try {
return jsonMessageConverter.toString(output);
} catch (JsonException e) {
throw new EncodeException(output, e.getMessage(), e);
}
}
}
public static class Builder {
private final URI serverUri;
private final List listeners;
private final List channels;
public static Builder create(URI serverUri) {
return new Builder(serverUri);
}
public Builder(URI serverUri) {
if (serverUri == null) {
throw new IllegalArgumentException("Connection URI may not be null");
}
this.serverUri = serverUri;
listeners = new LinkedList<>();
channels = new LinkedList<>();
final List channelsFromUri = parseQueryString(serverUri.getRawQuery(), true).get("channel");
if (channelsFromUri != null) {
channels.addAll(channelsFromUri);
}
}
public Builder listeners(ClientMessageListener... listeners) {
Collections.addAll(this.listeners, listeners);
return this;
}
public Builder listeners(Collection listeners) {
this.listeners.addAll(listeners);
return this;
}
public Builder channels(String... channels) {
Collections.addAll(this.channels, channels);
return this;
}
public Builder channels(Collection channels) {
this.channels.addAll(channels);
return this;
}
public WSClient build() {
return new WSClient(this);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy