/***************************************************************************** * Copyright 2013 Olivier Croquette <ocroquette@free.fr> * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * ****************************************************************************/ package org.magnum.soda.server.wamp; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.magnum.soda.server.wamp.common.Channel; import org.magnum.soda.server.wamp.messages.CallMessage; import org.magnum.soda.server.wamp.messages.EventMessage; import org.magnum.soda.server.wamp.messages.Message; import org.magnum.soda.server.wamp.messages.MessageMapper; import org.magnum.soda.server.wamp.messages.PublishMessage; import org.magnum.soda.server.wamp.messages.SubscribeMessage; import org.magnum.soda.server.wamp.messages.UnsubscribeMessage; import org.magnum.soda.server.wamp.messages.WelcomeMessage; public class WampServer { private List<WampServerListener> listeners_ = new ArrayList<WampServerListener>(); private List<PubSubFilter> publishFilters_ = new ArrayList<PubSubFilter>(); public WampServer() { init(); } public WampServer(String serverIdent) { init(); this.serverIdent = serverIdent; } public Subscriptions getSubscriptions() { return subscriptions; } public void setSubscriptions(Subscriptions subscriptions) { this.subscriptions = subscriptions; } protected void init() { outgoingClientChannels = new ConcurrentHashMap<ClientId, Channel>(); rpcHandlers = new ConcurrentHashMap<String, RpcHandler>(); serverIdent = "<UNIDENTIFIED SERVER>"; subscriptions = new Subscriptions(); clientIdFactory = new ClientIdFactory(); } public ClientId addClient(Channel outgoingChannel) { System.out.println("adding client"); ClientId clientId = clientIdFactory.getNext(); outgoingClientChannels.put(clientId, outgoingChannel); try { outgoingChannel.handle(MessageMapper.toJson(new WelcomeMessage( clientId.toString(), serverIdent))); } catch (IOException e) { // Could not even greet this client. How sad is that? return null; } for (WampServerListener l : listeners_) { l.clientConnected(clientId); } return clientId; } public void handleIncomingMessage(ClientId clientId, String jsonText) throws IOException { Message message = MessageMapper.fromJson(jsonText); switch (message.getType()) { case CALL: handleIncomingCallMessage(clientId, (CallMessage) message); break; case SUBSCRIBE: handleIncomingSubscribeMessage(clientId, (SubscribeMessage) message); break; case UNSUBSCRIBE: handleIncomingUnsubscribeMessage(clientId, (UnsubscribeMessage) message); break; case PUBLISH: handleIncomingPublishMessage(clientId, (PublishMessage) message); break; default: // TODO logging System.err .println("ERROR: handleIncomingMessage doesn't know how to handle message type " + message.getType()); } } private void handleIncomingSubscribeMessage(ClientId clientId, SubscribeMessage message) { for (PubSubFilter f : publishFilters_) { message = f.filterSubscribe(clientId, message); } if (message != null) { subscriptions.subscribe(clientId, message.topicUri); for (WampServerListener l : listeners_) { l.clientSubscribedToTopic(clientId, message.topicUri); } } } private void handleIncomingUnsubscribeMessage(ClientId clientId, UnsubscribeMessage message) { for (PubSubFilter f : publishFilters_) { message = f.filterUnsubscribe(clientId, message); } if (message != null) { subscriptions.unsubscribe(clientId, message.topicUri); for (WampServerListener l : listeners_) { l.clientUnSubscribedFromTopic(clientId, message.topicUri); } } } private void handleIncomingCallMessage(ClientId clientId, CallMessage message) throws IOException { String procedureId = message.procedureId; RpcHandler handler = rpcHandlers.get(procedureId); if (handler != null) { RpcCall rpcCall = new RpcCall(message); handler.execute(rpcCall); sendMessageToClient(clientId, rpcCall.getResultingJson()); } else // TODO System.out.println("No handler registered for " + procedureId); } private void handleIncomingPublishMessage(final ClientId clientId, PublishMessage msg) throws IOException { for (PubSubFilter f : publishFilters_) { msg = f.filterPublish(clientId, msg); } if (msg != null) { final PublishMessage message = msg; final EventMessage eventMessage = new EventMessage(message.topicUri); eventMessage.setPayload(message.payload); Subscriptions.ActionOnSubscriber action = new Subscriptions.ActionOnSubscriber() { @Override public void execute(ClientId subscriberClientId) { if (shallSendPublish(message.excludeMe, clientId, subscriberClientId)) sendMessageToClient(subscriberClientId, MessageMapper.toJson(eventMessage)); } }; subscriptions.forAllSubscribers(message.topicUri, action); } } private boolean shallSendPublish(Boolean excludeMe, ClientId from, ClientId to) { return excludeMe == null || !excludeMe.booleanValue() || from != to; } protected void sendMessageToClient(ClientId clientId, String message) { Channel channel = outgoingClientChannels.get(clientId); if (channel != null) { try { channel.handle(message); } catch (IOException e) { // TODO System.out.println("Looks like client " + clientId + "is disconnecting. Discarding."); deleteClient(clientId); } } else // TODO System.out.println("Cannot send to client, client ID unknown: " + clientId); } private void deleteClient(ClientId clientId) { outgoingClientChannels.remove(clientId); for (WampServerListener l : listeners_) { l.clientDisconnected(clientId); } } public void cancelAllSubscriptions(ClientId clientId) { } public void registerRpcHandler(String procedureId, RpcHandler rpcHandler) { rpcHandlers.put(procedureId, rpcHandler); } public void addListener(WampServerListener l) { listeners_.add(l); } public void removeListener(WampServerListener l) { listeners_.remove(l); } public void addPublishFilter(PubSubFilter f) { publishFilters_.add(f); } public void removePublishFilter(PubSubFilter f) { publishFilters_.remove(f); } protected Map<ClientId, Channel> outgoingClientChannels; protected Map<String, RpcHandler> rpcHandlers; protected Subscriptions subscriptions; protected String serverIdent; protected ClientIdFactory clientIdFactory; }