
org.glassfish.tyrus.ext.client.java8.SessionBuilder Maven / Gradle / Ivy
/*
* Copyright (c) 2015, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.tyrus.ext.client.java8;
import java.io.IOException;
import java.net.URI;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.function.BiConsumer;
import jakarta.websocket.ClientEndpointConfig;
import jakarta.websocket.CloseReason;
import jakarta.websocket.DeploymentException;
import jakarta.websocket.Endpoint;
import jakarta.websocket.EndpointConfig;
import jakarta.websocket.MessageHandler;
import jakarta.websocket.Session;
import jakarta.websocket.WebSocketContainer;
import org.glassfish.tyrus.client.ClientManager;
import org.glassfish.tyrus.core.Beta;
import org.glassfish.tyrus.core.MessageHandlerManager;
/**
* Session builder removed the need for having client endpoint.
*
* Client endpoint is replaced by references to onOpen, onError and onClose methods plus message handlers. Same rules
* which do apply or limit message handlers are forced here as well. None of the methods is required, so {@link
* Session}
* can be opened even without it and used only for sending messages.
*
* {@link jakarta.websocket.Encoder Encoders} and {@link jakarta.websocket.Decoder decoders} can be registered by creating
* {@link jakarta.websocket.ClientEndpointConfig} and registering it to SessionBuilder via
* {@link org.glassfish.tyrus.ext.client.java8.SessionBuilder#clientEndpointConfig} method call.
*
* Code example:
*
Session session = new SessionBuilder()
* .uri(getURI(SessionBuilderEncDecTestEndpoint.class))
* .clientEndpointConfig(clientEndpointConfig)
* .messageHandler(AClass.class,aClass -> messageLatch.countDown())
* .onOpen((session1, endpointConfig) -> onOpenLatch.countDown())
* .onError((session1, throwable) -> onErrorLatch.countDown())
* .onClose((session1, closeReason) -> onCloseLatch.countDown())
* .connect();
*
* @author Pavel Bucek
*/
@Beta
public class SessionBuilder {
private static final BiConsumer NO_OP_BI_CONSUMER = (o, o2) -> {
// no-op
};
private final WebSocketContainer container;
private final List, MessageHandler.Whole>>> wholeMessageHandlers = new ArrayList<>();
private final List, MessageHandler.Partial>>> partialMessageHandlers = new ArrayList<>();
private URI uri;
private ClientEndpointConfig clientEndpointConfig;
private BiConsumer onOpen;
private BiConsumer onError;
private BiConsumer onClose;
/**
* Create SessionBuilder with provided {@link jakarta.websocket.WebSocketContainer}.
*
* @param container provided websocket container.
*/
public SessionBuilder(WebSocketContainer container) {
this.container = container;
}
/**
* Create SessionBuilder with provided container provider class name.
*
* Generally, this is used only when you want to have fine-grained control about used container.
*
* @param containerProviderClassName container provider class name.
*/
public SessionBuilder(String containerProviderClassName) {
this(ClientManager.createClient(containerProviderClassName));
}
/**
* Create new SessionBuilder instance.
*/
public SessionBuilder() {
this(ClientManager.createClient());
}
/**
* Set {@link jakarta.websocket.ClientEndpointConfig}.
*
* @param clientEndpointConfig {@link jakarta.websocket.ClientEndpointConfig} to be set.
* @return updated SessionBuilder instance.
*/
public SessionBuilder clientEndpointConfig(ClientEndpointConfig clientEndpointConfig) {
this.clientEndpointConfig = clientEndpointConfig;
return this;
}
/**
* Set {@link java.net.URI} of the server endpoint.
*
* @param uri server endpoint address.
* @return updated SessionBuilder instance.
*/
public SessionBuilder uri(URI uri) {
this.uri = uri;
return this;
}
/**
* Add whole message handler.
*
* @param clazz handled message type class.
* @param messageHandler message handler.
* @param handled message type.
* @return updated SessionBuilder instance.
*/
public SessionBuilder messageHandler(Class clazz, MessageHandler.Whole messageHandler) {
wholeMessageHandlers.add(new AbstractMap.SimpleEntry<>(clazz, messageHandler));
return this;
}
/**
* Add partial message handler.
*
* @param clazz handled message type class.
* @param messageHandler message handler.
* @param handled message type.
* @return updated SessionBuilder instance.
*/
public SessionBuilder messageHandlerPartial(Class clazz, MessageHandler.Partial messageHandler) {
partialMessageHandlers.add(new AbstractMap.SimpleEntry<>(clazz, messageHandler));
return this;
}
/**
* Set method reference which will be invoked when a {@link Session} is opened.
*
* @param onOpen method invoked when a {@link Session} is opened.
* @return updated SessionBuilder instance.
* @see jakarta.websocket.OnOpen
*/
public SessionBuilder onOpen(BiConsumer onOpen) {
this.onOpen = onOpen;
return this;
}
/**
* Set method reference which will be invoked when {@link jakarta.websocket.OnError} method is invoked.
*
* @param onError method invoked when {@link jakarta.websocket.OnError} method is invoked.
* @return updated SessionBuilder instance.
* @see jakarta.websocket.OnError
*/
public SessionBuilder onError(BiConsumer onError) {
this.onError = onError;
return this;
}
/**
* Set method reference which will be invoked when a {@link Session} is closed.
*
* @param onClose method invoked when a {@link Session} is closed.
* @return updated SessionBuilder instance.
* @see jakarta.websocket.OnClose
*/
public SessionBuilder onClose(BiConsumer onClose) {
this.onClose = onClose;
return this;
}
/**
* Connect to the remote (server) endpoint.
*
* This method can be called multiple times, each invocation will result in new {@link Session} (new TCP connection
* to the server).
*
* @return created session.
* @throws IOException when there is a problem with connecting to the server endpoint.
* @throws DeploymentException when there is a problem with provided settings or there is other, non IO connection
* issue.
*/
public Session connect() throws IOException, DeploymentException {
// default values
if (clientEndpointConfig == null) {
clientEndpointConfig = ClientEndpointConfig.Builder.create().build();
}
//noinspection unchecked
onOpen = onOpen != null ? onOpen : (BiConsumer) NO_OP_BI_CONSUMER;
//noinspection unchecked
onClose = onClose != null ? onClose : (BiConsumer) NO_OP_BI_CONSUMER;
//noinspection unchecked
onError = onError != null ? onError : (BiConsumer) NO_OP_BI_CONSUMER;
// validation
MessageHandlerManager messageHandlerManager =
MessageHandlerManager.fromDecoderClasses(clientEndpointConfig.getDecoders());
try {
for (Map.Entry, MessageHandler.Whole>> entry : wholeMessageHandlers) {
messageHandlerManager
.addMessageHandler((Class) entry.getKey(), (MessageHandler.Whole) entry.getValue());
}
for (Map.Entry, MessageHandler.Partial>> entry : partialMessageHandlers) {
messageHandlerManager
.addMessageHandler((Class) entry.getKey(), (MessageHandler.Partial) entry.getValue());
}
} catch (IllegalStateException ise) {
throw new DeploymentException(ise.getMessage(), ise);
}
// validation end
final URI path = this.uri;
final ClientEndpointConfig clientEndpointConfig = this.clientEndpointConfig;
final BiConsumer onOpen = this.onOpen;
final BiConsumer onError = this.onError;
final BiConsumer onClose = this.onClose;
final Endpoint endpoint = new Endpoint() {
@Override
public void onOpen(Session session, EndpointConfig config) {
for (Map.Entry, MessageHandler.Whole>> entry : wholeMessageHandlers) {
session.addMessageHandler((Class) entry.getKey(), (MessageHandler.Whole) entry.getValue());
}
for (Map.Entry, MessageHandler.Partial>> entry : partialMessageHandlers) {
session.addMessageHandler((Class) entry.getKey(), (MessageHandler.Partial) entry.getValue());
}
onOpen.accept(session, config);
}
@Override
public void onClose(Session session, CloseReason closeReason) {
onClose.accept(session, closeReason);
}
@Override
public void onError(Session session, Throwable thr) {
onError.accept(session, thr);
}
};
return container.connectToServer(endpoint, clientEndpointConfig, path);
}
/**
* Connect to the remote (server) endpoint asynchronously.
*
* Same statements as at {@link SessionBuilder#connect()} do apply here, only the returned value and possible
* exceptions are returned as {@link java.util.concurrent.CompletableFuture}.
*
* {@link ForkJoinPool#commonPool()} is used for executing the connection phase.
*
* @return completable future returning {@link Session} when created.
*/
public CompletableFuture connectAsync() {
final CompletableFuture completableFuture = new CompletableFuture<>();
final ForkJoinTask forkJoinTask = new ForkJoinTask() {
@Override
public final Void getRawResult() {
return null;
}
@Override
public final void setRawResult(Void v) {
}
@Override
protected boolean exec() {
try {
completableFuture.complete(connect());
return true;
} catch (Exception e) {
completableFuture.completeExceptionally(e);
}
return false;
}
};
// TODO: Can we use ForkJoinPool#commonPool?
ForkJoinPool.commonPool().execute(forkJoinTask);
//noinspection unchecked
return completableFuture;
}
/**
* Connect to the remote (server) endpoint asynchronously.
*
* Same statements as at {@link SessionBuilder#connect()} do apply here, only the returned value and possible
* exceptions are returned as {@link java.util.concurrent.CompletableFuture}.
*
* Provided {@link java.util.concurrent.ExecutorService} is used for executing the connection phase.
*
* @param executorService executor service used for executing the {@link #connect()} method.
* @return completable future returning {@link Session} when created.
*/
public CompletableFuture connectAsync(ExecutorService executorService) {
final CompletableFuture completableFuture = new CompletableFuture<>();
Runnable runnable = () -> {
try {
completableFuture.complete(connect());
} catch (Exception e) {
completableFuture.completeExceptionally(e);
}
};
executorService.execute(runnable);
return completableFuture;
}
}