View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.websocket.jsr356;
20  
21  import java.io.IOException;
22  import java.net.URI;
23  import java.util.HashSet;
24  import java.util.Map;
25  import java.util.Objects;
26  import java.util.Set;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.ExecutionException;
29  import java.util.concurrent.Executor;
30  import java.util.concurrent.Future;
31  
32  import javax.websocket.ClientEndpoint;
33  import javax.websocket.ClientEndpointConfig;
34  import javax.websocket.DeploymentException;
35  import javax.websocket.Endpoint;
36  import javax.websocket.Extension;
37  import javax.websocket.Session;
38  import javax.websocket.WebSocketContainer;
39  
40  import org.eclipse.jetty.util.component.ContainerLifeCycle;
41  import org.eclipse.jetty.util.thread.ShutdownThread;
42  import org.eclipse.jetty.websocket.api.InvalidWebSocketException;
43  import org.eclipse.jetty.websocket.api.extensions.ExtensionFactory;
44  import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
45  import org.eclipse.jetty.websocket.client.WebSocketClient;
46  import org.eclipse.jetty.websocket.client.io.UpgradeListener;
47  import org.eclipse.jetty.websocket.jsr356.annotations.AnnotatedEndpointScanner;
48  import org.eclipse.jetty.websocket.jsr356.client.AnnotatedClientEndpointMetadata;
49  import org.eclipse.jetty.websocket.jsr356.client.EmptyClientEndpointConfig;
50  import org.eclipse.jetty.websocket.jsr356.client.SimpleEndpointMetadata;
51  import org.eclipse.jetty.websocket.jsr356.decoders.PrimitiveDecoderMetadataSet;
52  import org.eclipse.jetty.websocket.jsr356.encoders.PrimitiveEncoderMetadataSet;
53  import org.eclipse.jetty.websocket.jsr356.endpoints.EndpointInstance;
54  import org.eclipse.jetty.websocket.jsr356.endpoints.JsrEventDriverFactory;
55  import org.eclipse.jetty.websocket.jsr356.metadata.EndpointMetadata;
56  
57  /**
58   * Container for Client use of the javax.websocket API.
59   * <p>
60   * This should be specific to a JVM if run in a standalone mode. or specific to a WebAppContext if running on the Jetty server.
61   */
62  public class ClientContainer extends ContainerLifeCycle implements WebSocketContainer
63  {
64      /** Tracking all primitive decoders for the container */
65      private final DecoderFactory decoderFactory;
66      /** Tracking all primitive encoders for the container */
67      private final EncoderFactory encoderFactory;
68  
69      /** Tracking for all declared Client endpoints */
70      private final Map<Class<?>, EndpointMetadata> endpointClientMetadataCache;
71      /** The jetty websocket client in use for this container */
72      private WebSocketClient client;
73  
74      public ClientContainer()
75      {
76          this(null);
77      }
78      
79      public ClientContainer(Executor executor)
80      {
81          endpointClientMetadataCache = new ConcurrentHashMap<>();
82          decoderFactory = new DecoderFactory(PrimitiveDecoderMetadataSet.INSTANCE);
83          encoderFactory = new EncoderFactory(PrimitiveEncoderMetadataSet.INSTANCE);
84  
85          EmptyClientEndpointConfig empty = new EmptyClientEndpointConfig();
86          decoderFactory.init(empty);
87          encoderFactory.init(empty);
88  
89          client = new WebSocketClient(executor);
90          client.setEventDriverFactory(new JsrEventDriverFactory(client.getPolicy()));
91          client.setSessionFactory(new JsrSessionFactory(this));
92          addBean(client);
93      }
94  
95      private Session connect(EndpointInstance instance, URI path) throws IOException
96      {
97          Objects.requireNonNull(instance,"EndpointInstance cannot be null");
98          Objects.requireNonNull(path,"Path cannot be null");
99  
100         ClientEndpointConfig config = (ClientEndpointConfig)instance.getConfig();
101         ClientUpgradeRequest req = new ClientUpgradeRequest();
102         UpgradeListener upgradeListener = null;
103 
104         for (Extension ext : config.getExtensions())
105         {
106             req.addExtensions(new JsrExtensionConfig(ext));
107         }
108 
109         if (config.getPreferredSubprotocols().size() > 0)
110         {
111             req.setSubProtocols(config.getPreferredSubprotocols());
112         }
113 
114         if (config.getConfigurator() != null)
115         {
116             upgradeListener = new JsrUpgradeListener(config.getConfigurator());
117         }
118 
119         Future<org.eclipse.jetty.websocket.api.Session> futSess = client.connect(instance,path,req,upgradeListener);
120         try
121         {
122             return (JsrSession)futSess.get();
123         }
124         catch (InterruptedException e)
125         {
126             throw new IOException("Connect failure",e);
127         }
128         catch (ExecutionException e)
129         {
130             // Unwrap Actual Cause
131             Throwable cause = e.getCause();
132 
133             if (cause instanceof IOException)
134             {
135                 // Just rethrow
136                 throw (IOException)cause;
137             }
138             else
139             {
140                 throw new IOException("Connect failure",cause);
141             }
142         }
143     }
144 
145     @Override
146     public Session connectToServer(Class<? extends Endpoint> endpointClass, ClientEndpointConfig config, URI path) throws DeploymentException, IOException
147     {
148         EndpointInstance instance = newClientEndpointInstance(endpointClass,config);
149         return connect(instance,path);
150     }
151 
152     @Override
153     public Session connectToServer(Class<?> annotatedEndpointClass, URI path) throws DeploymentException, IOException
154     {
155         EndpointInstance instance = newClientEndpointInstance(annotatedEndpointClass,null);
156         return connect(instance,path);
157     }
158 
159     @Override
160     public Session connectToServer(Endpoint endpoint, ClientEndpointConfig config, URI path) throws DeploymentException, IOException
161     {
162         EndpointInstance instance = newClientEndpointInstance(endpoint,config);
163         return connect(instance,path);
164     }
165 
166     @Override
167     public Session connectToServer(Object endpoint, URI path) throws DeploymentException, IOException
168     {
169         EndpointInstance instance = newClientEndpointInstance(endpoint,null);
170         return connect(instance,path);
171     }
172 
173     @Override
174     protected void doStart() throws Exception
175     {
176         super.doStart();
177         ShutdownThread.register(client);
178     }
179 
180     @Override
181     protected void doStop() throws Exception
182     {
183         endpointClientMetadataCache.clear();
184         ShutdownThread.deregister(client);
185         super.doStop();
186     }
187 
188     public WebSocketClient getClient()
189     {
190         return client;
191     }
192 
193     public EndpointMetadata getClientEndpointMetadata(Class<?> endpoint)
194     {
195         EndpointMetadata metadata = null;
196 
197         synchronized (endpointClientMetadataCache)
198         {
199             metadata = endpointClientMetadataCache.get(endpoint);
200 
201             if (metadata != null)
202             {
203                 return metadata;
204             }
205 
206             ClientEndpoint anno = endpoint.getAnnotation(ClientEndpoint.class);
207             if (anno != null)
208             {
209                 // Annotated takes precedence here
210                 AnnotatedClientEndpointMetadata annoMetadata = new AnnotatedClientEndpointMetadata(this,endpoint);
211                 AnnotatedEndpointScanner<ClientEndpoint, ClientEndpointConfig> scanner = new AnnotatedEndpointScanner<>(annoMetadata);
212                 scanner.scan();
213                 metadata = annoMetadata;
214             }
215             else if (Endpoint.class.isAssignableFrom(endpoint))
216             {
217                 // extends Endpoint
218                 @SuppressWarnings("unchecked")
219                 Class<? extends Endpoint> eendpoint = (Class<? extends Endpoint>)endpoint;
220                 metadata = new SimpleEndpointMetadata(eendpoint);
221             }
222             else
223             {
224                 StringBuilder err = new StringBuilder();
225                 err.append("Not a recognized websocket [");
226                 err.append(endpoint.getName());
227                 err.append("] does not extend @").append(ClientEndpoint.class.getName());
228                 err.append(" or extend from ").append(Endpoint.class.getName());
229                 throw new InvalidWebSocketException("Unable to identify as valid Endpoint: " + endpoint);
230             }
231 
232             endpointClientMetadataCache.put(endpoint,metadata);
233             return metadata;
234         }
235     }
236 
237     public DecoderFactory getDecoderFactory()
238     {
239         return decoderFactory;
240     }
241 
242     @Override
243     public long getDefaultAsyncSendTimeout()
244     {
245         return client.getAsyncWriteTimeout();
246     }
247 
248     @Override
249     public int getDefaultMaxBinaryMessageBufferSize()
250     {
251         return client.getMaxBinaryMessageBufferSize();
252     }
253 
254     @Override
255     public long getDefaultMaxSessionIdleTimeout()
256     {
257         return client.getMaxIdleTimeout();
258     }
259 
260     @Override
261     public int getDefaultMaxTextMessageBufferSize()
262     {
263         return client.getMaxTextMessageBufferSize();
264     }
265 
266     public EncoderFactory getEncoderFactory()
267     {
268         return encoderFactory;
269     }
270 
271     @Override
272     public Set<Extension> getInstalledExtensions()
273     {
274         Set<Extension> ret = new HashSet<>();
275         ExtensionFactory extensions = client.getExtensionFactory();
276 
277         for (String name : extensions.getExtensionNames())
278         {
279             ret.add(new JsrExtension(name));
280         }
281 
282         return ret;
283     }
284 
285     /**
286      * Used in {@link Session#getOpenSessions()}
287      */
288     public Set<Session> getOpenSessions()
289     {
290         // TODO Auto-generated method stub
291         return null;
292     }
293 
294     private EndpointInstance newClientEndpointInstance(Class<?> endpointClass, ClientEndpointConfig config)
295     {
296         try
297         {
298             return newClientEndpointInstance(endpointClass.newInstance(),config);
299         }
300         catch (InstantiationException | IllegalAccessException e)
301         {
302             throw new InvalidWebSocketException("Unable to instantiate websocket: " + endpointClass.getClass());
303         }
304     }
305 
306     public EndpointInstance newClientEndpointInstance(Object endpoint, ClientEndpointConfig config)
307     {
308         EndpointMetadata metadata = getClientEndpointMetadata(endpoint.getClass());
309         ClientEndpointConfig cec = config;
310         if (config == null)
311         {
312             if (metadata instanceof AnnotatedClientEndpointMetadata)
313             {
314                 cec = ((AnnotatedClientEndpointMetadata)metadata).getConfig();
315             }
316             else
317             {
318                 cec = new EmptyClientEndpointConfig();
319             }
320         }
321         return new EndpointInstance(endpoint,cec,metadata);
322     }
323 
324     @Override
325     public void setAsyncSendTimeout(long ms)
326     {
327         client.setAsyncWriteTimeout(ms);
328     }
329 
330     @Override
331     public void setDefaultMaxBinaryMessageBufferSize(int max)
332     {
333         // overall message limit (used in non-streaming)
334         client.getPolicy().setMaxBinaryMessageSize(max);
335         // incoming streaming buffer size
336         client.setMaxBinaryMessageBufferSize(max);
337     }
338 
339     @Override
340     public void setDefaultMaxSessionIdleTimeout(long ms)
341     {
342         client.setMaxIdleTimeout(ms);
343     }
344 
345     @Override
346     public void setDefaultMaxTextMessageBufferSize(int max)
347     {
348         // overall message limit (used in non-streaming)
349         client.getPolicy().setMaxTextMessageSize(max);
350         // incoming streaming buffer size
351         client.setMaxTextMessageBufferSize(max);
352     }
353 }