001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.protocol.ws.api.registry;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.Map;
023import java.util.concurrent.ConcurrentMap;
024
025import org.apache.wicket.Application;
026import org.apache.wicket.MetaDataKey;
027import org.apache.wicket.protocol.ws.api.IWebSocketConnection;
028import org.apache.wicket.util.lang.Args;
029import org.apache.wicket.util.lang.Generics;
030
031/**
032 * A registry that keeps all currently opened web socket connections in
033 * maps in Application's meta data.
034 *
035 * @since 6.0
036 */
037public class SimpleWebSocketConnectionRegistry implements IWebSocketConnectionRegistry
038{
039        private static final MetaDataKey<ConcurrentMap<String, ConcurrentMap<IKey, IWebSocketConnection>>> KEY =
040                        new MetaDataKey<>()
041        {
042        };
043
044        @Override
045        public IWebSocketConnection getConnection(Application application, String sessionId, IKey key)
046        {
047                Args.notNull(application, "application");
048                Args.notNull(sessionId, "sessionId");
049                Args.notNull(key, "key");
050
051                IWebSocketConnection connection = null;
052                ConcurrentMap<String, ConcurrentMap<IKey, IWebSocketConnection>> connectionsBySession = application.getMetaData(KEY);
053                if (connectionsBySession != null)
054                {
055                        ConcurrentMap<IKey, IWebSocketConnection> connectionsByPage = connectionsBySession.get(sessionId);
056                        if (connectionsByPage != null)
057                        {
058                                connection = connectionsByPage.get(key);
059                        }
060                }
061                return connection;
062        }
063
064        @Override
065        public Collection<IWebSocketConnection> getConnections(Application application, String sessionId)
066        {
067                Args.notNull(application, "application");
068                Args.notNull(sessionId, "sessionId");
069
070                Collection<IWebSocketConnection> connections = Collections.emptyList();
071                ConcurrentMap<String, ConcurrentMap<IKey, IWebSocketConnection>> connectionsBySession = application.getMetaData(KEY);
072                if (connectionsBySession != null)
073                {
074                        ConcurrentMap<IKey, IWebSocketConnection> connectionsByPage = connectionsBySession.get(sessionId);
075                        if (connectionsByPage != null)
076                        {
077                                connections = connectionsByPage.values();
078                        }
079                }
080                return connections;
081        }
082
083        @Override
084        public Collection<IWebSocketConnection> getConnections(Application application, IConnectionsFilter connectionsFilter)
085        {
086                Args.notNull(application, "application");
087                Args.notNull(connectionsFilter, "connectionsFilter");
088
089                Collection<IWebSocketConnection> connections = new ArrayList<>();
090                ConcurrentMap<String, ConcurrentMap<IKey, IWebSocketConnection>> connectionsBySession = application.getMetaData(KEY);
091                if (connectionsBySession != null)
092                {
093                        for (Map.Entry<String, ConcurrentMap<IKey, IWebSocketConnection>> connectionsByPage : connectionsBySession.entrySet())
094                        {
095                                for (Map.Entry<IKey, IWebSocketConnection> connectionEntry: connectionsByPage.getValue().entrySet())
096                                {
097                                        if (connectionsFilter.accept(connectionsByPage.getKey(), connectionEntry.getKey()))
098                                        {
099                                                connections.add(connectionEntry.getValue());
100                                        }
101                                }
102
103                        }
104                }
105                return connections;
106        }
107
108        /**
109         * Returns a collection of currently active websockets. The connections might close at any time.
110         *
111         * @param application
112         *          The application
113         * @return a collection of currently active websockets
114         */
115        @Override
116        public Collection<IWebSocketConnection> getConnections(Application application)
117        {
118                Args.notNull(application, "application");
119
120                Collection<IWebSocketConnection> connections = new ArrayList<>();
121                ConcurrentMap<String, ConcurrentMap<IKey, IWebSocketConnection>> connectionsBySession = application.getMetaData(KEY);
122                if (connectionsBySession != null)
123                {
124                        for (ConcurrentMap<IKey, IWebSocketConnection> connectionsByPage : connectionsBySession.values())
125                        {
126                                connections.addAll(connectionsByPage.values());
127                        }
128                }
129                return connections;
130        }
131
132        @Override
133        public void setConnection(Application application, String sessionId, IKey key, IWebSocketConnection connection)
134        {
135                Args.notNull(application, "application");
136                Args.notNull(sessionId, "sessionId");
137                Args.notNull(key, "key");
138
139                ConcurrentMap<String, ConcurrentMap<IKey, IWebSocketConnection>> connectionsBySession = application.getMetaData(KEY);
140                if (connectionsBySession == null)
141                {
142                        synchronized (KEY)
143                        {
144                                connectionsBySession = application.getMetaData(KEY);
145                                if (connectionsBySession == null)
146                                {
147                                        connectionsBySession = Generics.newConcurrentHashMap();
148                                        application.setMetaData(KEY, connectionsBySession);
149                                }
150                        }
151                }
152
153                ConcurrentMap<IKey, IWebSocketConnection> connectionsByPage = connectionsBySession.get(sessionId);
154                if (connectionsByPage == null && connection != null)
155                {
156                        connectionsByPage = connectionsBySession.get(sessionId);
157                        if (connectionsByPage == null)
158                        {
159                                connectionsByPage = Generics.newConcurrentHashMap();
160                                ConcurrentMap<IKey, IWebSocketConnection> old = connectionsBySession.putIfAbsent(sessionId, connectionsByPage);
161                                if (old != null)
162                                {
163                                        connectionsByPage = old;
164                                }
165                        }
166                }
167
168                if (connection != null)
169                {
170                        connectionsByPage.put(key, connection);
171                }
172                else if (connectionsByPage != null)
173                {
174                        connectionsByPage.remove(key);
175                        if (connectionsByPage.isEmpty())
176                        {
177                                connectionsBySession.remove(sessionId);
178                        }
179                }
180        }
181
182        @Override
183        public void removeConnection(Application application, String sessionId, IKey key)
184        {
185                setConnection(application, sessionId, key, null);
186        }
187}