/** * This file is part of Erjang - A JVM-based Erlang VM * * Copyright (c) 2009 by Trifork * * 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 erjang; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; import erjang.m.erlang.ErlDist; import kilim.Pausable; /** * */ public abstract class EAbstractNode { /** * */ public static final EAtom am_nonode_at_nohost = EAtom.intern("nonode@nohost"); public static final EAtom am_nodedown = EAtom.intern("nodedown"); public static final EAtom am_nodeup = EAtom.intern("nodeup"); public static final EAtom am_nodedown_reason = EAtom.intern("nodedown_reason"); public static final EAtom am_node_type = EAtom.intern("node_type"); public static final EAtom am_visible = EAtom.intern("visible"); public static final EAtom am_hidden = EAtom.intern("hidden"); public static final EAtom am_all = EAtom.intern("all"); static String localHost = null; EAtom node = am_nonode_at_nohost; String host; String alive; EAtom cookie; static EAtom defaultCookie = null; // Node types static final int NTYPE_R6 = 110; // 'n' post-r5, all nodes static final int NTYPE_R4_ERLANG = 109; // 'm' Only for source compatibility static final int NTYPE_R4_HIDDEN = 104; // 'h' Only for source compatibility public static final int ERTS_NODES_MON_OPT_TYPE_VISIBLE = 1<<0; public static final int ERTS_NODES_MON_OPT_TYPE_HIDDEN = 1<<1; public static final int ERTS_NODES_MON_OPT_DOWN_REASON = 1<<2; public static final int ERTS_NODES_MON_OPT_TYPES = ERTS_NODES_MON_OPT_TYPE_VISIBLE | ERTS_NODES_MON_OPT_TYPE_HIDDEN; // Node capability flags static final int dFlagPublished = 1; static final int dFlagAtomCache = 2; static final int dFlagExtendedReferences = 4; static final int dFlagDistMonitor = 8; static final int dFlagFunTags = 0x10; static final int dFlagDistMonitorName = 0x20; // NOT USED static final int dFlagHiddenAtomCache = 0x40; // NOT SUPPORTED static final int dflagNewFunTags = 0x80; static final int dFlagExtendedPidsPorts = 0x100; static final int dFlagExportPtrTag = 0x200; // NOT SUPPORTED static final int dFlagBitBinaries = 0x400; static final int dFlagNewFloats = 0x800; static final int dFUnicodeIO = 0x1000; static final int dFDistHdrAtomCache = 0x2000; static final int dFlagSmallAtoms = 0x4000; int ntype = NTYPE_R6; int proto = 0; // tcp/ip int distHigh = 5; // Cannot talk to nodes before R6 int distLow = 5; // Cannot talk to nodes before R6 int creation = 0; int flags = dFlagExtendedReferences | dFlagExtendedPidsPorts | dFlagBitBinaries | dFlagNewFloats | dFlagFunTags | dflagNewFunTags; /* initialize hostname and default cookie */ static { try { localHost = InetAddress.getLocalHost().getHostName(); /* * Make sure it's a short name, i.e. strip of everything after first * '.' */ final int dot = localHost.indexOf("."); if (dot != -1) { localHost = localHost.substring(0, dot); } } catch (final UnknownHostException e) { localHost = "localhost"; } final String dotCookieFilename = System.getProperty("user.home") + File.separator + ".erlang.cookie"; BufferedReader br = null; try { final File dotCookieFile = ERT.newFile(dotCookieFilename); br = new BufferedReader(new FileReader(dotCookieFile)); defaultCookie = EAtom.intern( br.readLine().trim() ); } catch (final IOException e) { defaultCookie = EAtom.intern(""); } finally { try { if (br != null) { br.close(); } } catch (final IOException e) { } } } /** * @param node */ public EAbstractNode(EAtom node) { this(node, defaultCookie); } /** * */ public EAbstractNode() { this(am_nonode_at_nohost); } /** * @param node * @param cookie */ public EAbstractNode(EAtom node, EAtom cookie) { this.cookie = cookie; set(node, 0); } public void set(EAtom node, int cr) { String name = node.getName(); final int i = name.indexOf('@', 0); if (i < 0) { alive = name; host = localHost; } else { alive = name.substring(0, i); host = name.substring(i + 1, name.length()); } if (alive.length() > 0xff) { alive = alive.substring(0, 0xff); } this.node = EAtom.intern( alive + "@" + host ); this.creation = cr; } /** * Get the name of this node. * * @return the name of the node represented by this object. */ public EAtom node() { return node; } /** * Get the hostname part of the nodename. Nodenames are composed of two * parts, an alivename and a hostname, separated by '@'. This method returns * the part of the nodename following the '@'. * * @return the hostname component of the nodename. */ public String host() { return host; } /** * Get the alivename part of the hostname. Nodenames are composed of two * parts, an alivename and a hostname, separated by '@'. This method returns * the part of the nodename preceding the '@'. * * @return the alivename component of the nodename. */ public String alive() { return alive; } /** * Get the authorization cookie used by this node. * * @return the authorization cookie used by this node. */ public EAtom cookie() { return cookie; } // package scope int type() { return ntype; } // package scope int distHigh() { return distHigh; } // package scope int distLow() { return distLow; } // package scope: useless information? int proto() { return proto; } // package scope int creation() { return creation; } /** * Set the authorization cookie used by this node. * * @return the previous authorization cookie used by this node. */ public EAtom setCookie(final EAtom cookie) { final EAtom prev = this.cookie; this.cookie = cookie; return prev; } /*==================== Monitoring of a specific node ====================*/ ConcurrentHashMap<EHandle,AtomicInteger> node_monitors = new ConcurrentHashMap<EHandle,AtomicInteger>(); public void monitor_node(EHandle caller, boolean on) { node_monitors.putIfAbsent(caller, new AtomicInteger()); AtomicInteger ami = node_monitors.get(caller); if (on) { ami.incrementAndGet(); } else { ami.decrementAndGet(); } } /*==================== Monitoring of node changes in general ==========*/ static ConcurrentHashMap<EHandle,ConcurrentHashMap<Integer,AtomicInteger> > nodes_monitors = new ConcurrentHashMap(); /** @return whether monitoring was already active for the given opts_list, * or null if the opts_list was invalid. */ public static Boolean monitor_nodes(EHandle caller, boolean on, ESeq opts_list) { boolean all = false, visible = false, hidden = false; int opts = 0; for (; !opts_list.isNil(); opts_list = opts_list.tail()) { EObject opt = opts_list.head(); ETuple2 tp; if (opt == am_nodedown_reason) { opts |= ERTS_NODES_MON_OPT_DOWN_REASON; } else if ((tp = ETuple2.cast(opt)) != null) { if (tp.elem1 == am_node_type) { if (tp.elem2 == am_visible) { if (hidden || all) return null; opts |= ERTS_NODES_MON_OPT_TYPE_VISIBLE; visible = true; } else if (tp.elem2 == am_hidden) { if (visible || all) return null; opts |= ERTS_NODES_MON_OPT_TYPE_HIDDEN; hidden = true; } else if (tp.elem2 == am_all) { if (visible || hidden) return null; opts |= ERTS_NODES_MON_OPT_TYPES; } else { return null; } } else { return null; } } else { return null; } } return Boolean.valueOf(monitor_nodes(caller, on, opts)); } public static boolean monitor_nodes(EHandle caller, boolean on, int opts) { if (on) nodes_monitors.putIfAbsent(caller, new ConcurrentHashMap()); ConcurrentHashMap<Integer,AtomicInteger> forHandle = nodes_monitors.get(caller); if (forHandle==null) return false; // We're disabling but entry wasn't there. if (on) { forHandle.putIfAbsent(opts, new AtomicInteger(0)); AtomicInteger ami = forHandle.get(opts); if (ami==null) return false; // We're disabling but entry wasn't there. int old = ami.getAndIncrement(); return old > 0; } else { AtomicInteger old = forHandle.remove(opts); return old != null && old.get() > 0; } } /** network driver exited! * @throws Pausable */ public void node_going_down(EHandle sender, EObject reason) throws Pausable { for (Map.Entry<EHandle,AtomicInteger> ent : node_monitors.entrySet()) { EHandle handle = ent.getKey(); AtomicInteger howmany = ent.getValue(); ETuple nd = ETuple.make(am_nodedown, this.node()); while (howmany.decrementAndGet() >= 0) { handle.send(sender, nd); } } node_monitors.clear(); } public void node_up(EHandle sender, EObject reason) throws Pausable { // TODO: Send only to those which subscribe to the kind of node in question (visible/hidden). for (Map.Entry<EHandle,ConcurrentHashMap<Integer,AtomicInteger> > ent : nodes_monitors.entrySet()) { EHandle handle = ent.getKey(); ConcurrentHashMap<Integer,AtomicInteger> submap = ent.getValue(); // TODO: Add exit hook under the right circumstances. for (Map.Entry<Integer,AtomicInteger> subent : submap.entrySet()) { int opts = subent.getKey().intValue(); int howmany = subent.getValue().get(); final ETuple msg; if (opts == 0) { msg = ETuple.make(am_nodeup, this.node()); } else { /* TODO: Respect opts. * (1) Don't send if opts and type don't agree * (2) Add to the property list in the third element: * optionally with {'node_type', Type}, * optionally with {'nodedown_reason', Reason}. */ ECons info = ERT.NIL; // TODO: construct correct info value msg = ETuple.make(am_nodeup, this.node(), info); } for (int i=0; i < howmany; i++) { handle.send(sender, msg); } } } } public abstract EObject dsig_reg_send(EInternalPID caller, EAtom name, EObject msg) throws Pausable; public abstract void dsig_demonitor(EHandle sender, ERef ref, EObject to_pid_or_name) throws Pausable; public static EAbstractNode get_or_connect(ETask<?> proc, EAtom n) throws Pausable { EAbstractNode res = EPeer.get(n); if (res == null && (proc instanceof EProc)) { if (ErlDist.net_kernel__connect__1.invoke((EProc) proc, new EObject[] { n }) == ERT.TRUE) { return EPeer.get(n); } } return res; } }