aboutsummaryrefslogblamecommitdiffstats
path: root/lib/jinterface/java_src/com/ericsson/otp/erlang/OtpNode.java
blob: 7ead0b9c5418cc39688b485c33f605d8994d59c9 (plain) (tree)
1
2
3
4
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807


                   
                                                        



















































































































































































                                                                                
                                  
































































                                                                                
                                  











































































































































































































































































































































































































































































































































































                                                                                    
/* 
 * %CopyrightBegin%
 * 
 * Copyright Ericsson AB 2000-2012. All Rights Reserved.
 * 
 * The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved online at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * %CopyrightEnd%
 */
package com.ericsson.otp.erlang;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;

/**
 * <p>
 * Represents a local OTP node. This class is used when you do not wish to
 * manage connections yourself - outgoing connections are established as needed,
 * and incoming connections accepted automatically. This class supports the use
 * of a mailbox API for communication, while management of the underlying
 * communication mechanism is automatic and hidden from the application
 * programmer.
 * </p>
 * 
 * <p>
 * Once an instance of this class has been created, obtain one or more mailboxes
 * in order to send or receive messages. The first message sent to a given node
 * will cause a connection to be set up to that node. Any messages received will
 * be delivered to the appropriate mailboxes.
 * </p>
 * 
 * <p>
 * To shut down the node, call {@link #close close()}. This will prevent the
 * node from accepting additional connections and it will cause all existing
 * connections to be closed. Any unread messages in existing mailboxes can still
 * be read, however no new messages will be delivered to the mailboxes.
 * </p>
 * 
 * <p>
 * Note that the use of this class requires that Epmd (Erlang Port Mapper
 * Daemon) is running on each cooperating host. This class does not start Epmd
 * automatically as Erlang does, you must start it manually or through some
 * other means. See the Erlang documentation for more information about this.
 * </p>
 */
public class OtpNode extends OtpLocalNode {
    private boolean initDone = false;

    // thread to manage incoming connections
    private Acceptor acceptor = null;

    // keep track of all connections
    Hashtable<String, OtpCookedConnection> connections = null;

    // keep track of all mailboxes
    Mailboxes mboxes = null;

    // handle status changes
    OtpNodeStatus handler;

    // flags
    private int flags = 0;

    /**
     * <p>
     * Create a node using the default cookie. The default cookie is found by
     * reading the first line of the .erlang.cookie file in the user's home
     * directory. The home directory is obtained from the System property
     * "user.home".
     * </p>
     * 
     * <p>
     * If the file does not exist, an empty string is used. This method makes no
     * attempt to create the file.
     * </p>
     * 
     * @param node
     *            the name of this node.
     * 
     * @exception IOException
     *                if communication could not be initialized.
     * 
     */
    public OtpNode(final String node) throws IOException {
	this(node, defaultCookie, 0);
    }

    /**
     * Create a node.
     * 
     * @param node
     *            the name of this node.
     * 
     * @param cookie
     *            the authorization cookie that will be used by this node when
     *            it communicates with other nodes.
     * 
     * @exception IOException
     *                if communication could not be initialized.
     * 
     */
    public OtpNode(final String node, final String cookie) throws IOException {
	this(node, cookie, 0);
    }

    /**
     * Create a node.
     * 
     * @param node
     *            the name of this node.
     * 
     * @param cookie
     *            the authorization cookie that will be used by this node when
     *            it communicates with other nodes.
     * 
     * @param port
     *            the port number you wish to use for incoming connections.
     *            Specifying 0 lets the system choose an available port.
     * 
     * @exception IOException
     *                if communication could not be initialized.
     * 
     */
    public OtpNode(final String node, final String cookie, final int port)
	    throws IOException {
	super(node, cookie);

	init(port);
    }

    private synchronized void init(final int port) throws IOException {
	if (!initDone) {
	    connections = new Hashtable<String, OtpCookedConnection>(17,
		    (float) 0.95);
	    mboxes = new Mailboxes();
	    acceptor = new Acceptor(port);
	    initDone = true;
	}
    }

    /**
     * Close the node. Unpublish the node from Epmd (preventing new connections)
     * and close all existing connections.
     */
    public synchronized void close() {
	acceptor.quit();
	OtpCookedConnection conn;
	final Collection<OtpCookedConnection> coll = connections.values();
	final Iterator<OtpCookedConnection> it = coll.iterator();

	mboxes.clear();

	while (it.hasNext()) {
	    conn = it.next();
	    it.remove();
	    conn.close();
	}
	initDone = false;
    }

    @Override
    protected void finalize() {
	close();
    }

    /**
     * Create an unnamed {@link OtpMbox mailbox} that can be used to send and
     * receive messages with other, similar mailboxes and with Erlang processes.
     * Messages can be sent to this mailbox by using its associated
     * {@link OtpMbox#self() pid}.
     * 
     * @return a mailbox.
     */
    public OtpMbox createMbox() {
	return mboxes.create();
    }

    /**
     * Close the specified mailbox with reason 'normal'.
     * 
     * @param mbox
     *            the mailbox to close.
     * 
     *            <p>
     *            After this operation, the mailbox will no longer be able to
     *            receive messages. Any delivered but as yet unretrieved
     *            messages can still be retrieved however.
     *            </p>
     * 
     *            <p>
     *            If there are links from the mailbox to other
     *            {@link OtpErlangPid pids}, they will be broken when this
     *            method is called and exit signals with reason 'normal' will be
     *            sent.
     *            </p>
     * 
     */
    public void closeMbox(final OtpMbox mbox) {
	closeMbox(mbox, new OtpErlangAtom("normal"));
    }

    /**
     * Close the specified mailbox with the given reason.
     * 
     * @param mbox
     *            the mailbox to close.
     * @param reason
     *            an Erlang term describing the reason for the termination.
     * 
     *            <p>
     *            After this operation, the mailbox will no longer be able to
     *            receive messages. Any delivered but as yet unretrieved
     *            messages can still be retrieved however.
     *            </p>
     * 
     *            <p>
     *            If there are links from the mailbox to other
     *            {@link OtpErlangPid pids}, they will be broken when this
     *            method is called and exit signals with the given reason will
     *            be sent.
     *            </p>
     * 
     */
    public void closeMbox(final OtpMbox mbox, final OtpErlangObject reason) {
	if (mbox != null) {
	    mboxes.remove(mbox);
	    mbox.name = null;
	    mbox.breakLinks(reason);
	}
    }

    /**
     * Create an named mailbox that can be used to send and receive messages
     * with other, similar mailboxes and with Erlang processes. Messages can be
     * sent to this mailbox by using its registered name or the associated
     * {@link OtpMbox#self() pid}.
     * 
     * @param name
     *            a name to register for this mailbox. The name must be unique
     *            within this OtpNode.
     * 
     * @return a mailbox, or null if the name was already in use.
     * 
     */
    public OtpMbox createMbox(final String name) {
	return mboxes.create(name);
    }

    /**
     * <p>
     * Register or remove a name for the given mailbox. Registering a name for a
     * mailbox enables others to send messages without knowing the
     * {@link OtpErlangPid pid} of the mailbox. A mailbox can have at most one
     * name; if the mailbox already had a name, calling this method will
     * supercede that name.
     * </p>
     * 
     * @param name
     *            the name to register for the mailbox. Specify null to
     *            unregister the existing name from this mailbox.
     * 
     * @param mbox
     *            the mailbox to associate with the name.
     * 
     * @return true if the name was available, or false otherwise.
     */
    public boolean registerName(final String name, final OtpMbox mbox) {
	return mboxes.register(name, mbox);
    }

    /**
     * Get a list of all known registered names on this node.
     * 
     * @return an array of Strings, containins all known registered names on
     *         this node.
     */

    public String[] getNames() {
	return mboxes.names();
    }

    /**
     * Determine the {@link OtpErlangPid pid} corresponding to a registered name
     * on this node.
     * 
     * @return the {@link OtpErlangPid pid} corresponding to the registered
     *         name, or null if the name is not known on this node.
     */
    public OtpErlangPid whereis(final String name) {
	final OtpMbox m = mboxes.get(name);
	if (m != null) {
	    return m.self();
	}
	return null;
    }

    /**
     * Register interest in certain system events. The {@link OtpNodeStatus
     * OtpNodeStatus} handler object contains callback methods, that will be
     * called when certain events occur.
     * 
     * @param handler
     *            the callback object to register. To clear the handler, specify
     *            null as the handler to use.
     * 
     */
    public synchronized void registerStatusHandler(final OtpNodeStatus handler) {
	this.handler = handler;
    }

    /**
     * <p>
     * Determine if another node is alive. This method has the side effect of
     * setting up a connection to the remote node (if possible). Only a single
     * outgoing message is sent; the timeout is how long to wait for a response.
     * </p>
     * 
     * <p>
     * Only a single attempt is made to connect to the remote node, so for
     * example it is not possible to specify an extremely long timeout and
     * expect to be notified when the node eventually comes up. If you wish to
     * wait for a remote node to be started, the following construction may be
     * useful:
     * </p>
     * 
     * <pre>
     * // ping every 2 seconds until positive response
     * while (!me.ping(him, 2000))
     *     ;
     * </pre>
     * 
     * @param node
     *            the name of the node to ping.
     * 
     * @param timeout
     *            the time, in milliseconds, to wait for response before
     *            returning false.
     * 
     * @return true if the node was alive and the correct ping response was
     *         returned. false if the correct response was not returned on time.
     */
    /*
     * internal info about the message formats...
     * 
     * the request: -> REG_SEND {6,#Pid<[email protected]>,'',net_kernel}
     * {'$gen_call',{#Pid<[email protected]>,#Ref<[email protected]>},{is_auth,bingo@aule}}
     * 
     * the reply: <- SEND {2,'',#Pid<[email protected]>} {#Ref<[email protected]>,yes}
     */
    public boolean ping(final String node, final long timeout) {
	if (node.equals(this.node)) {
	    return true;
	} else if (node.indexOf('@', 0) < 0
		&& node.equals(this.node
			.substring(0, this.node.indexOf('@', 0)))) {
	    return true;
	}

	// other node
	OtpMbox mbox = null;
	try {
	    mbox = createMbox();
	    mbox.send("net_kernel", node, getPingTuple(mbox));
	    final OtpErlangObject reply = mbox.receive(timeout);

	    final OtpErlangTuple t = (OtpErlangTuple) reply;
	    final OtpErlangAtom a = (OtpErlangAtom) t.elementAt(1);
	    return "yes".equals(a.atomValue());
	} catch (final Exception e) {
	} finally {
	    closeMbox(mbox);
	}
	return false;
    }

    /* create the outgoing ping message */
    private OtpErlangTuple getPingTuple(final OtpMbox mbox) {
	final OtpErlangObject[] ping = new OtpErlangObject[3];
	final OtpErlangObject[] pid = new OtpErlangObject[2];
	final OtpErlangObject[] node = new OtpErlangObject[2];

	pid[0] = mbox.self();
	pid[1] = createRef();

	node[0] = new OtpErlangAtom("is_auth");
	node[1] = new OtpErlangAtom(node());

	ping[0] = new OtpErlangAtom("$gen_call");
	ping[1] = new OtpErlangTuple(pid);
	ping[2] = new OtpErlangTuple(node);

	return new OtpErlangTuple(ping);
    }

    /*
     * this method simulates net_kernel only for the purpose of replying to
     * pings.
     */
    private boolean netKernel(final OtpMsg m) {
	OtpMbox mbox = null;
	try {
	    final OtpErlangTuple t = (OtpErlangTuple) m.getMsg();
	    final OtpErlangTuple req = (OtpErlangTuple) t.elementAt(1); // actual
	    // request

	    final OtpErlangPid pid = (OtpErlangPid) req.elementAt(0); // originating
	    // pid

	    final OtpErlangObject[] pong = new OtpErlangObject[2];
	    pong[0] = req.elementAt(1); // his #Ref
	    pong[1] = new OtpErlangAtom("yes");

	    mbox = createMbox();
	    mbox.send(pid, new OtpErlangTuple(pong));
	    return true;
	} catch (final Exception e) {
	} finally {
	    closeMbox(mbox);
	}
	return false;
    }

    /*
     * OtpCookedConnection delivers messages here return true if message was
     * delivered successfully, or false otherwise.
     */
    boolean deliver(final OtpMsg m) {
	OtpMbox mbox = null;

	try {
	    final int t = m.type();

	    if (t == OtpMsg.regSendTag) {
		final String name = m.getRecipientName();
		/* special case for netKernel requests */
		if (name.equals("net_kernel")) {
		    return netKernel(m);
		} else {
		    mbox = mboxes.get(name);
		}
	    } else {
		mbox = mboxes.get(m.getRecipientPid());
	    }

	    if (mbox == null) {
		return false;
	    }
	    mbox.deliver(m);
	} catch (final Exception e) {
	    return false;
	}

	return true;
    }

    /*
     * OtpCookedConnection delivers errors here, we send them on to the handler
     * specified by the application
     */
    void deliverError(final OtpCookedConnection conn, final Exception e) {
	removeConnection(conn);
	remoteStatus(conn.name, false, e);
    }

    /*
     * find or create a connection to the given node
     */
    OtpCookedConnection getConnection(final String node) {
	OtpPeer peer = null;
	OtpCookedConnection conn = null;

	synchronized (connections) {
	    // first just try looking up the name as-is
	    conn = connections.get(node);

	    if (conn == null) {
		// in case node had no '@' add localhost info and try again
		peer = new OtpPeer(node);
		conn = connections.get(peer.node());

		if (conn == null) {
		    try {
			conn = new OtpCookedConnection(this, peer);
			conn.setFlags(flags);
			addConnection(conn);
		    } catch (final Exception e) {
			/* false = outgoing */
			connAttempt(peer.node(), false, e);
		    }
		}
	    }
	    return conn;
	}
    }

    void addConnection(final OtpCookedConnection conn) {
	if (conn != null && conn.name != null) {
	    connections.put(conn.name, conn);
	    remoteStatus(conn.name, true, null);
	}
    }

    private void removeConnection(final OtpCookedConnection conn) {
	if (conn != null && conn.name != null) {
	    connections.remove(conn.name);
	}
    }

    /* use these wrappers to call handler functions */
    private synchronized void remoteStatus(final String node, final boolean up,
	    final Object info) {
	if (handler == null) {
	    return;
	}
	try {
	    handler.remoteStatus(node, up, info);
	} catch (final Exception e) {
	}
    }

    synchronized void localStatus(final String node, final boolean up,
	    final Object info) {
	if (handler == null) {
	    return;
	}
	try {
	    handler.localStatus(node, up, info);
	} catch (final Exception e) {
	}
    }

    synchronized void connAttempt(final String node, final boolean incoming,
	    final Object info) {
	if (handler == null) {
	    return;
	}
	try {
	    handler.connAttempt(node, incoming, info);
	} catch (final Exception e) {
	}
    }

    /*
     * this class used to wrap the mailbox hashtables so we can use weak
     * references
     */
    public class Mailboxes {
	// mbox pids here
	private Hashtable<OtpErlangPid, WeakReference<OtpMbox>> byPid = null;
	// mbox names here
	private Hashtable<String, WeakReference<OtpMbox>> byName = null;

	public Mailboxes() {
	    byPid = new Hashtable<OtpErlangPid, WeakReference<OtpMbox>>(17,
		    (float) 0.95);
	    byName = new Hashtable<String, WeakReference<OtpMbox>>(17,
		    (float) 0.95);
	}

	public OtpMbox create(final String name) {
	    OtpMbox m = null;

	    synchronized (byName) {
		if (get(name) != null) {
		    return null;
		}
		final OtpErlangPid pid = createPid();
		m = new OtpMbox(OtpNode.this, pid, name);
		byPid.put(pid, new WeakReference<OtpMbox>(m));
		byName.put(name, new WeakReference<OtpMbox>(m));
	    }
	    return m;
	}

	public OtpMbox create() {
	    final OtpErlangPid pid = createPid();
	    final OtpMbox m = new OtpMbox(OtpNode.this, pid);
	    byPid.put(pid, new WeakReference<OtpMbox>(m));
	    return m;
	}

	public void clear() {
	    byPid.clear();
	    byName.clear();
	}

	public String[] names() {
	    String allnames[] = null;

	    synchronized (byName) {
		final int n = byName.size();
		final Enumeration<String> keys = byName.keys();
		allnames = new String[n];

		int i = 0;
		while (keys.hasMoreElements()) {
		    allnames[i++] = keys.nextElement();
		}
	    }
	    return allnames;
	}

	public boolean register(final String name, final OtpMbox mbox) {
	    if (name == null) {
		if (mbox.name != null) {
		    byName.remove(mbox.name);
		    mbox.name = null;
		}
	    } else {
		synchronized (byName) {
		    if (get(name) != null) {
			return false;
		    }
		    byName.put(name, new WeakReference<OtpMbox>(mbox));
		    mbox.name = name;
		}
	    }
	    return true;
	}

	/*
	 * look up a mailbox based on its name. If the mailbox has gone out of
	 * scope we also remove the reference from the hashtable so we don't
	 * find it again.
	 */
	public OtpMbox get(final String name) {
	    final WeakReference<OtpMbox> wr = byName.get(name);

	    if (wr != null) {
		final OtpMbox m = wr.get();

		if (m != null) {
		    return m;
		}
		byName.remove(name);
	    }
	    return null;
	}

	/*
	 * look up a mailbox based on its pid. If the mailbox has gone out of
	 * scope we also remove the reference from the hashtable so we don't
	 * find it again.
	 */
	public OtpMbox get(final OtpErlangPid pid) {
	    final WeakReference<OtpMbox> wr = byPid.get(pid);

	    if (wr != null) {
		final OtpMbox m = wr.get();

		if (m != null) {
		    return m;
		}
		byPid.remove(pid);
	    }
	    return null;
	}

	public void remove(final OtpMbox mbox) {
	    byPid.remove(mbox.self);
	    if (mbox.name != null) {
		byName.remove(mbox.name);
	    }
	}
    }

    /*
     * this thread simply listens for incoming connections
     */
    public class Acceptor extends Thread {
	private final ServerSocket sock;
	private final int port;
	private volatile boolean done = false;

	Acceptor(final int port) throws IOException {
	    sock = new ServerSocket(port);
	    this.port = sock.getLocalPort();
	    OtpNode.this.port = this.port;

	    setDaemon(true);
	    setName("acceptor");
	    publishPort();
	    start();
	}

	private boolean publishPort() throws IOException {
	    if (getEpmd() != null) {
		return false; // already published
	    }
	    OtpEpmd.publishPort(OtpNode.this);
	    return true;
	}

	private void unPublishPort() {
	    // unregister with epmd
	    OtpEpmd.unPublishPort(OtpNode.this);

	    // close the local descriptor (if we have one)
	    closeSock(epmd);
	    epmd = null;
	}

	public void quit() {
	    unPublishPort();
	    done = true;
	    closeSock(sock);
	    localStatus(node, false, null);
	}

	private void closeSock(final ServerSocket s) {
	    try {
		if (s != null) {
		    s.close();
		}
	    } catch (final Exception e) {
	    }
	}

	private void closeSock(final Socket s) {
	    try {
		if (s != null) {
		    s.close();
		}
	    } catch (final Exception e) {
	    }
	}

	public int port() {
	    return port;
	}

	@Override
	public void run() {
	    Socket newsock = null;
	    OtpCookedConnection conn = null;

	    localStatus(node, true, null);

	    accept_loop: while (!done) {
		conn = null;

		try {
		    newsock = sock.accept();
		} catch (final Exception e) {
		    // Problem in java1.2.2: accept throws SocketException
		    // when socket is closed. This will happen when
		    // acceptor.quit()
		    // is called. acceptor.quit() will call localStatus(...), so
		    // we have to check if that's where we come from.
		    if (!done) {
			localStatus(node, false, e);
		    }
		    break accept_loop;
		}

		try {
		    synchronized (connections) {
			conn = new OtpCookedConnection(OtpNode.this, newsock);
			conn.setFlags(flags);
			addConnection(conn);
		    }
		} catch (final OtpAuthException e) {
		    if (conn != null && conn.name != null) {
			connAttempt(conn.name, true, e);
		    } else {
			connAttempt("unknown", true, e);
		    }
		    closeSock(newsock);
		} catch (final IOException e) {
		    if (conn != null && conn.name != null) {
			connAttempt(conn.name, true, e);
		    } else {
			connAttempt("unknown", true, e);
		    }
		    closeSock(newsock);
		} catch (final Exception e) {
		    closeSock(newsock);
		    closeSock(sock);
		    localStatus(node, false, e);
		    break accept_loop;
		}
	    } // while

	    // if we have exited loop we must do this too
	    unPublishPort();
	}
    }

    public void setFlags(final int flags) {
	this.flags = flags;
    }
}