All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.glassfish.tyrus.client.ClientManager Maven / Gradle / Ivy

There is a newer version: 3.1.1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2011-2013 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * http://glassfish.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package org.glassfish.tyrus.client;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import javax.websocket.ClientEndpoint;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.DeploymentException;
import javax.websocket.Endpoint;
import javax.websocket.Extension;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;

import org.glassfish.tyrus.core.AnnotatedEndpoint;
import org.glassfish.tyrus.core.BaseContainer;
import org.glassfish.tyrus.core.ComponentProviderService;
import org.glassfish.tyrus.core.ErrorCollector;
import org.glassfish.tyrus.core.ReflectionHelper;
import org.glassfish.tyrus.core.TyrusEndpointWrapper;
import org.glassfish.tyrus.core.TyrusFuture;
import org.glassfish.tyrus.spi.ClientContainer;
import org.glassfish.tyrus.spi.ClientEngine;

/**
 * ClientManager implementation.
 *
 * @author Stepan Kopriva (stepan.kopriva at oracle.com)
 * @author Pavel Bucek (pavel.bucek at oracle.com)
 */
public class ClientManager extends BaseContainer implements WebSocketContainer {

    /**
     * Property usable in {@link #getProperties()}.
     * 

* Value must be {@code int} and represents handshake timeout in milliseconds. Default value is 30000 (30 seconds). */ public static final String HANDSHAKE_TIMEOUT = "org.glassfish.tyrus.client.ClientManager.ContainerTimeout"; /** * Default {@link org.glassfish.tyrus.spi.ServerContainerFactory} class name. *

* Uses Grizzly as transport implementation. */ private static final String CONTAINER_PROVIDER_CLASSNAME = "org.glassfish.tyrus.container.grizzly.client.GrizzlyClientContainer"; private static final Logger LOGGER = Logger.getLogger(ClientManager.class.getName()); private final WebSocketContainer webSocketContainer; private final ClientContainer container; private final ComponentProviderService componentProvider; private final Map properties = new HashMap(); private volatile long defaultAsyncSendTimeout; private volatile long defaultMaxSessionIdleTimeout; private volatile int maxBinaryMessageBufferSize = Integer.MAX_VALUE; private volatile int maxTextMessageBufferSize = Integer.MAX_VALUE; /** * Create new {@link ClientManager} instance. *

* Uses {@link ClientManager#CONTAINER_PROVIDER_CLASSNAME} as container implementation, thus relevant module needs to * be on classpath. Setting different container is possible via {@link ClientManager#createClient(String)}. * * @see ClientManager#createClient(String) */ public static ClientManager createClient() { return createClient(CONTAINER_PROVIDER_CLASSNAME); } /** * Create new ClientManager instance on top of provided {@link WebSocketContainer} instance. *

* Uses {@link ClientManager#CONTAINER_PROVIDER_CLASSNAME} as container implementation, thus relevant module needs to * be on classpath. Setting different container is possible via {@link ClientManager#createClient(String)}. * * @see ClientManager#createClient(String) */ public static ClientManager createClient(WebSocketContainer webSocketContainer) { return createClient(CONTAINER_PROVIDER_CLASSNAME, webSocketContainer); } /** * Create new ClientManager instance. * * @return new ClientManager instance. */ public static ClientManager createClient(String containerProviderClassName) { return new ClientManager(containerProviderClassName, null); } /** * Create new ClientManager instance on top of provided {@link WebSocketContainer} instance. * * @return new ClientManager instance. */ public static ClientManager createClient(String containerProviderClassName, WebSocketContainer webSocketContainer) { return new ClientManager(containerProviderClassName, webSocketContainer); } /** * Create new {@link ClientManager} instance. *

* Uses {@link ClientManager#CONTAINER_PROVIDER_CLASSNAME} as container implementation, thus relevant module needs to * be on classpath. Setting different container is possible via {@link ClientManager#createClient(String)}}. * * @see ClientManager#createClient(String) */ public ClientManager() { this(CONTAINER_PROVIDER_CLASSNAME, null); } private ClientManager(String containerProviderClassName, WebSocketContainer webSocketContainer) { final ErrorCollector collector = new ErrorCollector(); componentProvider = ComponentProviderService.createClient(); Class engineProviderClazz; try { engineProviderClazz = ReflectionHelper.classForNameWithException(containerProviderClassName); } catch (ClassNotFoundException e) { collector.addException(e); throw new RuntimeException(collector.composeComprehensiveException()); } LOGGER.config(String.format("Provider class loaded: %s", containerProviderClassName)); this.container = (ClientContainer) ReflectionHelper.getInstance(engineProviderClazz, collector); if (!collector.isEmpty()) { throw new RuntimeException(collector.composeComprehensiveException()); } this.webSocketContainer = webSocketContainer; } @Override public Session connectToServer(Class annotatedEndpointClass, URI path) throws DeploymentException, IOException { if (annotatedEndpointClass.getAnnotation(ClientEndpoint.class) == null) { throw new DeploymentException(String.format("Class argument in connectToServer(Class, URI) is to be annotated endpoint class." + "Class %s does not have @ClientEndpoint", annotatedEndpointClass.getName())); } try { return connectToServer(annotatedEndpointClass, null, path.toString(), new SameThreadExecutorService()).get(); } catch (InterruptedException e) { throw new DeploymentException(e.getMessage(), e); } catch (ExecutionException e) { final Throwable cause = e.getCause(); if (cause instanceof DeploymentException) { throw (DeploymentException) cause; } else if (cause instanceof IOException) { throw (IOException) cause; } else { throw new DeploymentException(cause.getMessage(), cause); } } } @Override public Session connectToServer(Class endpointClass, ClientEndpointConfig cec, URI path) throws DeploymentException, IOException { try { return connectToServer(endpointClass, cec, path.toString(), new SameThreadExecutorService()).get(); } catch (InterruptedException e) { throw new DeploymentException(e.getMessage(), e); } catch (ExecutionException e) { final Throwable cause = e.getCause(); if (cause instanceof DeploymentException) { throw (DeploymentException) cause; } else if (cause instanceof IOException) { throw (IOException) cause; } else { throw new DeploymentException(cause.getMessage(), cause); } } } @Override public Session connectToServer(Endpoint endpointInstance, ClientEndpointConfig cec, URI path) throws DeploymentException, IOException { try { return connectToServer(endpointInstance, cec, path.toString(), new SameThreadExecutorService()).get(); } catch (InterruptedException e) { throw new DeploymentException(e.getMessage(), e); } catch (ExecutionException e) { final Throwable cause = e.getCause(); if (cause instanceof DeploymentException) { throw (DeploymentException) cause; } else if (cause instanceof IOException) { throw (IOException) cause; } else { throw new DeploymentException(cause.getMessage(), cause); } } } @Override public Session connectToServer(Object obj, URI path) throws DeploymentException, IOException { try { return connectToServer(obj, null, path.toString(), new SameThreadExecutorService()).get(); } catch (InterruptedException e) { throw new DeploymentException(e.getMessage(), e); } catch (ExecutionException e) { final Throwable cause = e.getCause(); if (cause instanceof DeploymentException) { throw (DeploymentException) cause; } else if (cause instanceof IOException) { throw (IOException) cause; } else { throw new DeploymentException(cause.getMessage(), cause); } } } /** * Non-blocking version of {@link WebSocketContainer#connectToServer(Class, java.net.URI)}. *

* Only simple checks are performed in the main thread; client container is created in different thread, same * applies to connecting etc. * * @param annotatedEndpointClass the annotated websocket client endpoint. * @param path the complete path to the server endpoint. * @return Future for the Session created if the connection is successful. * @throws DeploymentException if the class is not a valid annotated endpoint class. */ public Future asyncConnectToServer(Class annotatedEndpointClass, URI path) throws DeploymentException { if (annotatedEndpointClass.getAnnotation(ClientEndpoint.class) == null) { throw new DeploymentException(String.format("Class argument in connectToServer(Class, URI) is to be annotated endpoint class." + "Class %s does not have @ClientEndpoint", annotatedEndpointClass.getName())); } return connectToServer(annotatedEndpointClass, null, path.toString(), getExecutorService()); } /** * Non-blocking version of {@link WebSocketContainer#connectToServer(Class, javax.websocket.ClientEndpointConfig, java.net.URI)}. *

* Only simple checks are performed in the main thread; client container is created in different thread, same * applies to connecting etc. * * @param endpointClass the programmatic client endpoint class {@link Endpoint}. * @param path the complete path to the server endpoint. * @param cec the configuration used to configure the programmatic endpoint. * @return the Session created if the connection is successful. * @throws DeploymentException if the configuration is not valid * @see WebSocketContainer#connectToServer(Class, javax.websocket.ClientEndpointConfig, java.net.URI) */ public Future asyncConnectToServer(Class endpointClass, ClientEndpointConfig cec, URI path) throws DeploymentException { return connectToServer(endpointClass, cec, path.toString(), getExecutorService()); } /** * Non-blocking version of {@link WebSocketContainer#connectToServer(javax.websocket.Endpoint, javax.websocket.ClientEndpointConfig, java.net.URI)}. *

* Only simple checks are performed in the main thread; client container is created in different thread, same * applies to connecting etc. * * @param endpointInstance the programmatic client endpoint instance {@link Endpoint}. * @param path the complete path to the server endpoint. * @param cec the configuration used to configure the programmatic endpoint. * @return the Session created if the connection is successful. * @throws DeploymentException if the configuration is not valid * @see WebSocketContainer#connectToServer(javax.websocket.Endpoint, javax.websocket.ClientEndpointConfig, java.net.URI) */ public Future asyncConnectToServer(Endpoint endpointInstance, ClientEndpointConfig cec, URI path) throws DeploymentException { return connectToServer(endpointInstance, cec, path.toString(), getExecutorService()); } /** * Non-blocking version of {@link WebSocketContainer#connectToServer(Object, java.net.URI)}. *

* Only simple checks are performed in the main thread; client container is created in different thread, same * applies to connecting etc. * * @param obj the annotated websocket client endpoint * instance. * @param path the complete path to the server endpoint. * @return the Session created if the connection is successful. * @throws DeploymentException if the annotated endpoint instance is not valid. * @see WebSocketContainer#connectToServer(Object, java.net.URI) */ public Future asyncConnectToServer(Object obj, URI path) throws DeploymentException { return connectToServer(obj, null, path.toString(), getExecutorService()); } /** * Connects client endpoint o to the specified url. * * @param o the endpoint. * @param configuration of the endpoint. * @param url to which the client will connect. * @return Future which will return {@link Session} instance when available. * @throws DeploymentException if the endpoint or provided URL is not valid. */ Future connectToServer(final Object o, final ClientEndpointConfig configuration, final String url, final ExecutorService executorService) throws DeploymentException { final Map copiedProperties = new HashMap(properties); final TyrusFuture future = new TyrusFuture(); try { URI uri = new URI(url); String scheme = uri.getScheme(); if (scheme == null || !(scheme.equals("ws") || scheme.equals("wss"))) { throw new DeploymentException("Incorrect scheme in WebSocket endpoint URI=" + url); } } catch (URISyntaxException e) { throw new DeploymentException("Incorrect WebSocket endpoint URI=" + url, e); } final int handshakeTimeout = getHandshakeTimeout(); executorService.submit(new Runnable() { @Override public void run() { ClientEndpointConfig config; Endpoint endpoint; final ErrorCollector collector = new ErrorCollector(); TyrusEndpointWrapper clientEndpoint; final CountDownLatch responseLatch = new CountDownLatch(1); ClientManagerHandshakeListener listener; TyrusClientEngine clientEngine; try { if (o instanceof Endpoint) { endpoint = (Endpoint) o; config = configuration == null ? ClientEndpointConfig.Builder.create().build() : configuration; } else if (o instanceof Class) { if (Endpoint.class.isAssignableFrom((Class) o)) { //noinspection unchecked endpoint = ReflectionHelper.getInstance(((Class) o), collector); config = configuration == null ? ClientEndpointConfig.Builder.create().build() : configuration; } else if ((((Class) o).getAnnotation(ClientEndpoint.class) != null)) { endpoint = AnnotatedEndpoint.fromClass((Class) o, componentProvider, false, collector); config = (ClientEndpointConfig) ((AnnotatedEndpoint) endpoint).getEndpointConfig(); } else { collector.addException(new DeploymentException(String.format("Class %s in not Endpoint descendant and does not have @ClientEndpoint", ((Class) o).getName()))); endpoint = null; config = null; } } else { endpoint = AnnotatedEndpoint.fromInstance(o, componentProvider, false, collector); config = (ClientEndpointConfig) ((AnnotatedEndpoint) endpoint).getEndpointConfig(); } clientEndpoint = new TyrusEndpointWrapper(endpoint, config, componentProvider, webSocketContainer == null ? ClientManager.this : webSocketContainer, url, null); // fail fast when there is some issue with client endpoint. if (!collector.isEmpty()) { future.setFailure(collector.composeComprehensiveException()); return; } listener = new ClientManagerHandshakeListener() { private volatile Session session; private volatile Throwable throwable; @Override public void onSessionCreated(Session session) { this.session = session; responseLatch.countDown(); } @Override public void onError(Throwable exception) { throwable = exception; responseLatch.countDown(); } @Override public Session getSession() { return session; } @Override public Throwable getThrowable() { return throwable; } }; clientEngine = new TyrusClientEngine(clientEndpoint, listener, copiedProperties); container.openClientSocket(url, config, copiedProperties, clientEngine); } catch (IOException e) { future.setFailure(e); return; } catch (DeploymentException e) { future.setFailure(e); return; } if (!collector.isEmpty()) { future.setFailure(collector.composeComprehensiveException()); return; } try { final boolean countedDown = responseLatch.await(handshakeTimeout, TimeUnit.MILLISECONDS); if (countedDown) { assert config != null; final Throwable exception = listener.getThrowable(); if (exception != null) { future.setFailure(new DeploymentException("Handshake error.", exception)); return; } final Session session = listener.getSession(); if (session.isOpen()) { session.setMaxBinaryMessageBufferSize(maxBinaryMessageBufferSize); session.setMaxTextMessageBufferSize(maxTextMessageBufferSize); session.setMaxIdleTimeout(defaultMaxSessionIdleTimeout); } future.setResult(session); return; } else { // timeout! final ClientEngine.TimeoutHandler timeoutHandler = clientEngine.getTimeoutHandler(); if (timeoutHandler != null) { timeoutHandler.handleTimeout(); } } } catch (Exception e) { future.setFailure(new DeploymentException("Handshake response not received.", e)); } future.setFailure(new DeploymentException("Handshake response not received.")); } }); return future; } private int getHandshakeTimeout() { final Object o = properties.get(HANDSHAKE_TIMEOUT); if (o != null && o instanceof Integer) { return (Integer) o; } else { // default value return 30000; } } private interface ClientManagerHandshakeListener extends TyrusClientEngine.ClientHandshakeListener { Session getSession(); Throwable getThrowable(); } @Override public int getDefaultMaxBinaryMessageBufferSize() { if (webSocketContainer == null) { return maxBinaryMessageBufferSize; } else { return webSocketContainer.getDefaultMaxBinaryMessageBufferSize(); } } @Override public void setDefaultMaxBinaryMessageBufferSize(int i) { if (webSocketContainer == null) { maxBinaryMessageBufferSize = i; } else { webSocketContainer.setDefaultMaxBinaryMessageBufferSize(i); } } @Override public int getDefaultMaxTextMessageBufferSize() { if (webSocketContainer == null) { return maxTextMessageBufferSize; } else { return webSocketContainer.getDefaultMaxTextMessageBufferSize(); } } @Override public void setDefaultMaxTextMessageBufferSize(int i) { if (webSocketContainer == null) { maxTextMessageBufferSize = i; } else { webSocketContainer.setDefaultMaxTextMessageBufferSize(i); } } @Override public Set getInstalledExtensions() { if (webSocketContainer == null) { // TODO return Collections.emptySet(); } else { return webSocketContainer.getInstalledExtensions(); } } @Override public long getDefaultAsyncSendTimeout() { if (webSocketContainer == null) { return defaultAsyncSendTimeout; } else { return webSocketContainer.getDefaultAsyncSendTimeout(); } } @Override public void setAsyncSendTimeout(long timeoutmillis) { if (webSocketContainer == null) { defaultAsyncSendTimeout = timeoutmillis; } else { webSocketContainer.setAsyncSendTimeout(timeoutmillis); } } @Override public long getDefaultMaxSessionIdleTimeout() { if (webSocketContainer == null) { return defaultMaxSessionIdleTimeout; } else { return webSocketContainer.getDefaultMaxSessionIdleTimeout(); } } @Override public void setDefaultMaxSessionIdleTimeout(long defaultMaxSessionIdleTimeout) { if (webSocketContainer == null) { this.defaultMaxSessionIdleTimeout = defaultMaxSessionIdleTimeout; } else { webSocketContainer.setDefaultMaxSessionIdleTimeout(defaultMaxSessionIdleTimeout); } } /** * Container properties. *

* Used to set container specific configuration as SSL truststore and keystore, HTTP Proxy configuration and * maximum incoming buffer size. These properties cannot be shared among various containers due to constraints in * WebSocket API, so if you need to have multiple configurations, you will need to create multiple ClientManager * instances or synchronize connectToServer method invocations. * * @return map containing container properties. */ public Map getProperties() { return properties; } /** * Executor service which just executes provided {@link Runnable} in the very same thread. */ private static class SameThreadExecutorService extends AbstractExecutorService { @Override public void shutdown() { // do nothing. } @Override public List shutdownNow() { // do nothing. return Collections.emptyList(); } @Override public boolean isShutdown() { return false; } @Override public boolean isTerminated() { return false; } @Override public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { return false; } @Override public void execute(Runnable command) { command.run(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy