001package org.apache.commons.jcs3.utils.discovery;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.ByteArrayOutputStream;
023import java.io.IOException;
024import java.net.DatagramPacket;
025import java.net.InetAddress;
026import java.net.MulticastSocket;
027import java.net.NetworkInterface;
028import java.util.ArrayList;
029
030import org.apache.commons.jcs3.engine.CacheInfo;
031import org.apache.commons.jcs3.engine.behavior.IElementSerializer;
032import org.apache.commons.jcs3.log.Log;
033import org.apache.commons.jcs3.log.LogManager;
034import org.apache.commons.jcs3.utils.discovery.UDPDiscoveryMessage.BroadcastType;
035import org.apache.commons.jcs3.utils.net.HostNameUtil;
036import org.apache.commons.jcs3.utils.serialization.StandardSerializer;
037
038/**
039 * This is a generic sender for the UDPDiscovery process.
040 */
041public class UDPDiscoverySender implements AutoCloseable
042{
043    /** The logger. */
044    private static final Log log = LogManager.getLog( UDPDiscoverySender.class );
045
046    /** The socket */
047    private final MulticastSocket localSocket;
048
049    /** The address */
050    private final InetAddress multicastAddress;
051
052    /** The port */
053    private final int multicastPort;
054
055    /** Used to serialize messages */
056    private final IElementSerializer serializer;
057
058    /**
059     * Constructor for the UDPDiscoverySender object
060     * <p>
061     * This sender can be used to send multiple messages.
062     * <p>
063     * When you are done sending, you should destroy the socket sender.
064     * <p>
065     * @param host
066     * @param port
067     * @param udpTTL the Datagram packet time-to-live
068     * @throws IOException
069     * @deprecated Specify serializer implementation explicitly
070     */
071    @Deprecated
072    public UDPDiscoverySender( final String host, final int port, final int udpTTL )
073        throws IOException
074    {
075        this(null, host, port, udpTTL, new StandardSerializer());
076    }
077
078    /**
079     * Constructor for the UDPDiscoverySender object
080     * <p>
081     * This sender can be used to send multiple messages.
082     * <p>
083     * When you are done sending, you should destroy the socket sender.
084     * <p>
085     * @param udpDiscoveryAttributes configuration object
086     * @param serializer the Serializer to use when sending messages
087     * @throws IOException
088     * @since 3.1
089     */
090    public UDPDiscoverySender(final UDPDiscoveryAttributes udpDiscoveryAttributes, final IElementSerializer serializer)
091        throws IOException
092    {
093        this(udpDiscoveryAttributes.getUdpDiscoveryInterface(),
094            udpDiscoveryAttributes.getUdpDiscoveryAddr(),
095            udpDiscoveryAttributes.getUdpDiscoveryPort(),
096            udpDiscoveryAttributes.getUdpTTL(),
097            serializer);
098    }
099
100    /**
101     * Constructor for the UDPDiscoverySender object
102     * <p>
103     * This sender can be used to send multiple messages.
104     * <p>
105     * When you are done sending, you should destroy the socket sender.
106     * <p>
107     * @param mcastInterface the Multicast interface name to use, if null, try to autodetect
108     * @param host
109     * @param port
110     * @param udpTTL the Datagram packet time-to-live
111     * @param serializer the Serializer to use when sending messages
112     * @throws IOException
113     * @since 3.1
114     */
115    public UDPDiscoverySender(final String mcastInterface, final String host,
116            final int port, final int udpTTL, IElementSerializer serializer)
117        throws IOException
118    {
119        try
120        {
121            log.debug( "Constructing socket for sender on port [{0}]", port );
122            localSocket = new MulticastSocket( port );
123
124            if (udpTTL > 0)
125            {
126                log.debug( "Setting datagram TTL to [{0}]", udpTTL );
127                localSocket.setTimeToLive(udpTTL);
128            }
129
130            // Remote address.
131            multicastAddress = InetAddress.getByName( host );
132
133            // Use dedicated interface if specified
134            NetworkInterface multicastInterface = null;
135            if (mcastInterface != null)
136            {
137                multicastInterface = NetworkInterface.getByName(mcastInterface);
138            }
139            else
140            {
141                multicastInterface = HostNameUtil.getMulticastNetworkInterface();
142            }
143            if (multicastInterface != null)
144            {
145                log.info("Sending multicast via network interface {0}",
146                        multicastInterface::getDisplayName);
147                localSocket.setNetworkInterface(multicastInterface);
148            }
149        }
150        catch ( final IOException e )
151        {
152            log.error( "Could not bind to multicast address [{0}]", host, e );
153            throw e;
154        }
155
156        this.multicastPort = port;
157        this.serializer = serializer;
158    }
159
160    /**
161     * Closes the socket connection.
162     */
163    @Override
164    public void close()
165    {
166        if ( this.localSocket != null && !this.localSocket.isClosed() )
167        {
168            this.localSocket.close();
169        }
170    }
171
172    /**
173     * Send messages.
174     * <p>
175     * @param message
176     * @throws IOException
177     */
178    public void send( final UDPDiscoveryMessage message )
179        throws IOException
180    {
181        if ( this.localSocket == null )
182        {
183            throw new IOException( "Socket is null, cannot send message." );
184        }
185
186        if ( this.localSocket.isClosed() )
187        {
188            throw new IOException( "Socket is closed, cannot send message." );
189        }
190
191        log.debug( "sending UDPDiscoveryMessage, address [{0}], port [{1}], "
192                + "message = {2}", multicastAddress, multicastPort, message );
193
194        final byte[] bytes = serializer.serialize( message );
195
196        // put the byte array in a packet
197        final DatagramPacket packet = new DatagramPacket( bytes, bytes.length, multicastAddress, multicastPort );
198
199        log.debug( "Sending DatagramPacket with {0} bytes to {1}:{2}",
200                bytes.length, multicastAddress, multicastPort );
201
202        localSocket.send( packet );
203    }
204
205    /**
206     * Ask other to broadcast their info the multicast address. If a lateral is non receiving it
207     * can use this. This is also called on startup so we can get info.
208     * <p>
209     * @throws IOException
210     */
211    public void requestBroadcast()
212        throws IOException
213    {
214        log.debug( "sending requestBroadcast" );
215
216        final UDPDiscoveryMessage message = new UDPDiscoveryMessage();
217        message.setHost(multicastAddress.getHostAddress());
218        message.setPort(multicastPort);
219        message.setRequesterId( CacheInfo.listenerId );
220        message.setMessageType( BroadcastType.REQUEST );
221        send( message );
222    }
223
224    /**
225     * This sends a message broadcasting out that the host and port is available for connections.
226     * <p>
227     * It uses the vmid as the requesterDI
228     * @param host
229     * @param port
230     * @param cacheNames
231     * @throws IOException
232     */
233    public void passiveBroadcast( final String host, final int port, final ArrayList<String> cacheNames )
234        throws IOException
235    {
236        passiveBroadcast( host, port, cacheNames, CacheInfo.listenerId );
237    }
238
239    /**
240     * This allows you to set the sender id. This is mainly for testing.
241     * <p>
242     * @param host
243     * @param port
244     * @param cacheNames names of the cache regions
245     * @param listenerId
246     * @throws IOException
247     */
248    protected void passiveBroadcast( final String host, final int port, final ArrayList<String> cacheNames, final long listenerId )
249        throws IOException
250    {
251        log.debug( "sending passiveBroadcast" );
252
253        final UDPDiscoveryMessage message = new UDPDiscoveryMessage();
254        message.setHost( host );
255        message.setPort( port );
256        message.setCacheNames( cacheNames );
257        message.setRequesterId( listenerId );
258        message.setMessageType( BroadcastType.PASSIVE );
259        send( message );
260    }
261
262    /**
263     * This sends a message broadcasting our that the host and port is no longer available.
264     * <p>
265     * It uses the vmid as the requesterID
266     * <p>
267     * @param host host
268     * @param port port
269     * @param cacheNames names of the cache regions
270     * @throws IOException on error
271     */
272    public void removeBroadcast( final String host, final int port, final ArrayList<String> cacheNames )
273        throws IOException
274    {
275        removeBroadcast( host, port, cacheNames, CacheInfo.listenerId );
276    }
277
278    /**
279     * This allows you to set the sender id. This is mainly for testing.
280     * <p>
281     * @param host host
282     * @param port port
283     * @param cacheNames names of the cache regions
284     * @param listenerId listener ID
285     * @throws IOException on error
286     */
287    protected void removeBroadcast( final String host, final int port, final ArrayList<String> cacheNames, final long listenerId )
288        throws IOException
289    {
290        log.debug( "sending removeBroadcast" );
291
292        final UDPDiscoveryMessage message = new UDPDiscoveryMessage();
293        message.setHost( host );
294        message.setPort( port );
295        message.setCacheNames( cacheNames );
296        message.setRequesterId( listenerId );
297        message.setMessageType( BroadcastType.REMOVE );
298        send( message );
299    }
300}
301
302/**
303 * This allows us to get the byte array from an output stream.
304 *
305 * @deprecated No longer used
306 */
307@Deprecated
308class MyByteArrayOutputStream
309    extends ByteArrayOutputStream
310{
311    /**
312     * Gets the bytes attribute of the MyByteArrayOutputStream object
313     * @return The bytes value
314     */
315    public byte[] getBytes()
316    {
317        return buf;
318    }
319}