View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.push.cdi;
21  
22  import java.io.Serializable;
23  import java.util.ArrayList;
24  import java.util.Collections;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.concurrent.ConcurrentHashMap;
29  import javax.annotation.PreDestroy;
30  import javax.enterprise.context.SessionScoped;
31  
32  /**
33   * The purpose of this bean is to keep track of the active tokens and Session instances in the current session,
34   * so it can be possible to decide if the token is valid or not for the current session. If the token is not in
35   * application scope and is present in session, it means there was a server restart, so the connection must be
36   * updated (added to application scope).
37   * 
38   */
39  @SessionScoped
40  public class WebsocketSessionBean implements Serializable
41  {
42      
43      /**
44       * This map hold all tokens that are related to the current scope. 
45       * This map use as key channel and as value channelTokens
46       */
47      private Map<String, List<WebsocketChannel> > channelTokenListMap = 
48              new ConcurrentHashMap<String, List<WebsocketChannel> >(2);    
49      
50      /**
51       * This map holds all tokens related to the current session and its associated metadata, that will
52       * be used in the websocket handshake to validate if the incoming request is valid and to store
53       * the user object into the Session object.
54       */
55      private Map<String, WebsocketChannelMetadata> tokenMap = 
56          new ConcurrentHashMap<String, WebsocketChannelMetadata>();
57      
58      public WebsocketSessionBean()
59      {
60      }
61      
62      public void registerToken(String token, WebsocketChannelMetadata metadata)
63      {
64          tokenMap.put(token, metadata);
65      }
66  
67      public void registerWebsocketSession(String token, WebsocketChannelMetadata metadata)
68      {
69          if ("session".equals(metadata.getScope()))
70          {
71              channelTokenListMap.putIfAbsent(metadata.getChannel(), new ArrayList<WebsocketChannel>(1));
72              channelTokenListMap.get(metadata.getChannel()).add(new WebsocketChannel(
73                      token, metadata));
74          }
75      }
76      
77      public boolean isTokenValid(String token)
78      {
79          return tokenMap.containsKey(token);
80      }
81      
82      public Serializable getUserFromChannelToken(String channelToken)
83      {
84          if (tokenMap != null)
85          {
86              WebsocketChannelMetadata metadata = tokenMap.get(channelToken);
87              if (metadata != null)
88              {
89                  return metadata.getUser();
90              }
91          }
92          return null;
93      }
94      
95      /**
96       * Indicate if the channel mentioned is valid for view scope.
97       * 
98       * A channel is valid if there is at least one token that represents a valid connection to this channel.
99       * 
100      * @param channel
101      * @return 
102      */
103     public boolean isChannelAvailable(String channel)
104     {
105         return channelTokenListMap.containsKey(channel);
106     }
107     
108     public List<String> getChannelTokensFor(String channel)
109     {
110         List<WebsocketChannel> list = channelTokenListMap.get(channel);
111         if (list != null && !list.isEmpty())
112         {
113             List<String> value = new ArrayList<String>(list.size());
114             for (WebsocketChannel md : list)
115             {
116                 value.add(md.getChannelToken());
117             }
118             return value;
119         }
120         return Collections.emptyList();
121     }
122     
123     public <S extends Serializable> List<String> getChannelTokensFor(String channel, S user)
124     {
125         List<WebsocketChannel> list = channelTokenListMap.get(channel);
126         if (list != null && !list.isEmpty())
127         {
128             List<String> value = new ArrayList<String>(list.size());
129             for (WebsocketChannel md : list)
130             {
131                 if (user.equals(md.getUser()))
132                 {
133                     value.add(md.getChannelToken());
134                 }
135             }
136             return value;
137         }
138         return null;
139     }
140 
141     @PreDestroy
142     public void destroy()
143     {
144         // Since there is an algorithm in place for @PreDestroy and @ViewScoped beans using a session
145         // scope bean and @PreDestroy, there is nothing else to do here. But on session expiration
146         // it is easier to just clear the map. At the end it will not cause any side effects.
147         channelTokenListMap.clear();
148         tokenMap.clear();
149     }
150     
151     public void destroyChannelToken(String channelToken)
152     {
153         String channel = null;
154         for (Map.Entry<String, List<WebsocketChannel>> entry : channelTokenListMap.entrySet())
155         {
156             for (Iterator<WebsocketChannel> it = entry.getValue().iterator(); it.hasNext();)
157             {
158                 WebsocketChannel wschannel = it.next();
159                 if (channelToken.equals(wschannel.getChannelToken()))
160                 {
161                     it.remove();
162                     break;
163                 }
164             }
165             if (entry.getValue().isEmpty())
166             {
167                 channel = entry.getKey();
168             }
169         }
170         if (channel != null)
171         {
172             channelTokenListMap.remove(channel);
173         }
174         tokenMap.remove(channelToken);
175     }
176 }