/** * GRANITE DATA SERVICES * Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S. * * This file is part of the Granite Data Services Platform. * * *** * * Community License: GPL 3.0 * * This file is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * This file is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * *** * * Available Commercial License: GraniteDS SLA 1.0 * * This is the appropriate option if you are creating proprietary * applications and you are not prepared to distribute and share the * source code of your application under the GPL v3 license. * * Please visit http://www.granitedataservices.com/license for more * details. */ package org.granite.client.messaging.udp; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.URI; import java.net.UnknownHostException; import java.nio.channels.DatagramChannel; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.granite.client.messaging.Consumer; import org.granite.client.messaging.channel.amf.BaseAMFMessagingChannel; import org.granite.client.messaging.codec.MessagingCodec; import org.granite.client.messaging.messages.ResponseMessage; import org.granite.client.messaging.messages.responses.ResultMessage; import org.granite.client.messaging.transport.DefaultTransportMessage; import org.granite.client.messaging.transport.Transport; import org.granite.logging.Logger; import org.granite.util.UUIDUtil; import flex.messaging.messages.AsyncMessage; import flex.messaging.messages.CommandMessage; import flex.messaging.messages.Message; /** * @author Franck WOLFF */ public class UdpMessagingChannelImpl extends BaseAMFMessagingChannel implements UdpMessagingChannel, UpdMessageListener { private static final Logger log = Logger.getLogger(UdpMessagingChannelImpl.class); private static final String GDS_CLIENT_UPD_PORT = "GDS_CLIENT_UDP_PORT"; private static final String GDS_SERVER_UDP_PORT = "GDS_SERVER_UDP_PORT"; private final ConcurrentMap<UdpChannelListener, Boolean> listeners = new ConcurrentHashMap<UdpChannelListener, Boolean>(); private final InetAddress remoteHost; private SocketAddress defaultLocalAddress = null; private DatagramChannel channel = null; private Thread channelReader = null; public UdpMessagingChannelImpl(MessagingCodec<Message[]> codec, Transport transport, String id, URI uri) { super(codec, transport, id, uri); try { remoteHost = InetAddress.getByName(uri.getHost()); } catch (UnknownHostException e) { throw new RuntimeException("Could not get remote host address from: " + uri, e); } } public void addListener(UdpChannelListener listener) { listeners.put(listener, Boolean.TRUE); } public boolean removeListener(UdpChannelListener listener) { return listeners.remove(listener) != null; } @Override public void setDefaultLocalAddress(SocketAddress address) { defaultLocalAddress = address; } @Override public SocketAddress getDefaultLocalAddress() { return defaultLocalAddress; } @Override public SocketAddress getLocalAddress() throws IOException { return (channel != null ? channel.socket().getLocalSocketAddress() : null); } @Override public SocketAddress getRemoteAddress() throws IOException { return (channel != null ? channel.socket().getRemoteSocketAddress() : null); } @Override protected boolean connect() { // No subscriptions... if (consumersMap.isEmpty()) return false; // We are already waiting for a connection/answer. final String id = UUIDUtil.randomUUID(); if (!connectMessageId.compareAndSet(null, id)) return false; log.debug("Connecting UDP channel with clientId %s", clientId); try { channel = DatagramChannel.open(); channel.socket().bind(defaultLocalAddress); } catch (Exception e) { channel = null; return false; } for (UdpChannelListener listener : listeners.keySet()) { try { listener.onBound(this); } catch (Exception e) { log.error(e, "Error while calling listener %s", listener); } } int port = channel.socket().getLocalPort(); CommandMessage connectMessage = new CommandMessage(); connectMessage.setOperation(CommandMessage.CONNECT_OPERATION); connectMessage.setMessageId(id); connectMessage.setTimestamp(System.currentTimeMillis()); connectMessage.setClientId(clientId); connectMessage.setHeader(GDS_CLIENT_UPD_PORT, Integer.valueOf(port)); try { transport.send(this, new DefaultTransportMessage<Message[]>(id, true, false, clientId, sessionId, new Message[]{connectMessage}, codec)); return true; } catch (Exception e) { connectMessageId.set(null); return false; } } @Override protected ResponseMessage decodeResponse(InputStream is) throws IOException { ResponseMessage response = super.decodeResponse(is); if (response instanceof ResultMessage && response.getHeader(GDS_SERVER_UDP_PORT) != null) { ResultMessage result = (ResultMessage)response; String id = connectMessageId.getAndSet(null); if (id == null || !id.equals(result.getCorrelationId())) log.warn("Bad correlation id: %s != %s", id, result.getCorrelationId()); Number port = (Number)result.getHeader(GDS_SERVER_UDP_PORT); if (port == null) throw new RuntimeException("Server didn't return an UDP port"); channel.connect(new InetSocketAddress(remoteHost, port.intValue())); channel.configureBlocking(true); channelReader = new Thread(new UpdMessageReader(channel, this)); channelReader.start(); for (UdpChannelListener listener : listeners.keySet()) { try { listener.onConnected(this); } catch (Exception e) { log.error(e, "Error while calling listener %s", listener); } } return null; } return response; } public void onUdpMessage(byte[] data, int off, int len) { try { Message[] messages = codec.decode(new ByteArrayInputStream(data, off, len)); for (Message message : messages) { if (!(message instanceof AsyncMessage)) throw new RuntimeException("Message should be an AsyncMessage: " + message); String subscriptionId = (String)message.getHeader(AsyncMessage.DESTINATION_CLIENT_ID_HEADER); Consumer consumer = consumersMap.get(subscriptionId); if (consumer != null) consumer.onMessage(convertFromAmf((AsyncMessage)message)); else log.warn("No consumer for subscriptionId: %s", subscriptionId); } } catch (Exception e) { log.error(e, "Error while reading UDP message"); } } @Override public void onStop(Transport transport) { if (channelReader != null) { try { channelReader.interrupt(); } catch (Exception e) { log.error(e, "Could not close UDP channel %s", channel); } finally { channelReader = null; channel = null; } } for (UdpChannelListener listener : listeners.keySet()) { try { listener.onClosed(this); } catch (Exception e) { log.error(e, "Error while calling listener %s", listener); } } super.onStop(transport); } }