org.hyperic.snmp.MIBTree.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperic.snmp.MIBTree.java

Source

/*
 * 'MIBTree.java' NOTE: This copyright does *not* cover user programs that use
 * HQ program services by normal system calls through the application program
 * interfaces provided as part of the Hyperic Plug-in Development Kit or the
 * Hyperic Client Development Kit - this is merely considered normal use of the
 * program, and does *not* fall under the heading of "derived work". Copyright
 * (C) [2004, 2005, 2006, 2007, 2008, 2009], Hyperic, Inc. This file is part of
 * HQ. HQ is free software; you can redistribute it and/or modify it under the
 * terms version 2 of the GNU General Public License as published by the Free
 * Software Foundation. This program 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, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA.
 */

package org.hyperic.snmp;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.net.URL;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.StringTokenizer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.snmp4j.smi.OctetString;

/*
 * MIB file parser intended ONLY for OBJECT-TYPE name -> OID conversion For
 * example, converting: "wwwServiceDescription" to: "1.3.6.1.2.1.65.1.1.1.1.2"
 * IMPORTS are ignored and parse() order of MIBs does not matter, provided all
 * MIB files required to lookup a given name have been parsed prior to calling
 * getOID(name).
 */
public class MIBTree {
    public static final String PROP_MIBS_DIR = "snmp.mibs.dir";

    private static Log log = LogFactory.getLog(MIBTree.class.getName());

    private static final int INDEX = 1;
    private static final int IDENTIFIER = 2;
    private static final int NO_ACCESS = 4;

    private static final int MAX_OID_LEN = 127;
    private static final String QUOTE = "\"";
    private static final String ASSIGN = "::=";

    private static MIBTree instance = null;

    private HashMap parsedFiles = new HashMap();
    private HashMap table = new HashMap(); // parsed MIBs
    private HashMap oids = new HashMap(); // cache OID conversion

    private LineNumberReader reader;

    private List tokens = new ArrayList(); // lexx / yakk / hakk
    private List previous = new ArrayList();

    private StringTokenizer tokenizer;

    private String currentMIB;
    private String lastLookupFailure;

    private boolean inDescription = false;

    private static final int T_NAME = 0;
    private static final int T_PARENT = 1;
    private static final int T_OID = 2;

    // Every MIB depends on SNMPv2-SMI...
    private static final String[][] SNMPv2_SMI = { { "org", "iso", "3" }, { "dod", "org", "6" },
            { "internet", "dod", "1" }, { "directory", "internet", "1" }, { "mgmt", "internet", "2" },
            { "mib-2", "mgmt", "1" }, { "transmission", "mib-2", "10" }, { "experimental", "internet", "3" },
            { "private", "internet", "4" }, { "enterprises", "private", "1" }, { "security", "internet", "5" },
            { "snmpV2", "internet", "6" }, { "snmpDomains", "snmpV2", "1" }, { "snmpProxys", "snmpV2", "2" },
            { "snmpModules", "snmpV2", "3" }, };

    // Commonly used objects from SNMPv2-MIB...
    private static final String[][] SNMPv2_MIB = { { "sysDescr", "system", "1" }, { "sysObjectID", "system", "2" },
            { "sysUpTime", "system", "3" }, { "sysContact", "system", "4" }, { "sysName", "system", "5" },
            { "sysLocation", "system", "6" }, { "sysServices", "system", "7" }, };

    class MIBNode {
        String parent;
        String oid;

        int flags = 0;

        MIBNode(String oid, String parent) {
            this.oid = oid.intern();
            this.parent = parent;
        }

        MIBNode getParent() {
            return lookup(this.parent);
        }

        String getMIB() {
            return "unknown";
        }

        boolean hasFlag(int flag) {
            return (this.flags & flag) != 0;
        }

        // To keep the MIBNode objects as small as possible,
        // we work backwards here to compose the full oid
        // only when they are asked for. Parser will cache
        // so this is a one-time expense...
        int[] getOID(String name) {
            int[] scratch = new int[MAX_OID_LEN];
            int ix = scratch.length;

            MIBNode node = this;

            boolean indexApplies = !hasFlag(IDENTIFIER) && !hasFlag(NO_ACCESS);
            boolean hasIndex = false;
            boolean isDebug = log.isDebugEnabled();

            while ((node != null) && (ix > 0)) {
                scratch[--ix] = Integer.parseInt(node.oid);

                MIBNode parent = node.getParent();

                if (parent == null) {
                    if (node.getClass() != ISONode.class) {
                        lastLookupFailure = node.parent;

                        return null;
                    } else {
                        break;
                    }
                }

                if (parent.hasFlag(INDEX)) {
                    hasIndex = true;
                }

                node = parent;
            }

            boolean addIndex = indexApplies && !hasIndex;

            int len = scratch.length - ix;
            int alloc = addIndex ? len + 1 : len;
            int[] oid = new int[alloc];

            System.arraycopy(scratch, ix, oid, 0, len);

            // sysUpTime.0...
            if (addIndex) {
                oid[len] = 0;

                if (isDebug) {
                    log.debug(getMIB() + "." + name + " has no index, appending .0");
                }
            }

            return oid;
        }
    }

    class ISONode extends MIBNode {
        ISONode() {
            super("1", null);

            this.flags = IDENTIFIER;
        }
    }

    class DebugMIBNode extends MIBNode {
        String mib; // only useful if log.isDebugEnabled

        DebugMIBNode(String oid, String parent) {
            super(oid, parent);

            if (instance != null) {
                this.mib = instance.currentMIB;
            }
        }

        String getMIB() {
            return this.mib;
        }
    }

    public MIBTree() {
        this.table.put("iso", new ISONode());
    }

    public void init() {
        this.currentMIB = "SNMPv2-SMI";

        add(SNMPv2_SMI, IDENTIFIER);

        this.currentMIB = "SNMPv2-MIB";

        add("system", "mib-2", "1", IDENTIFIER);
        add(SNMPv2_MIB, 0);

        this.currentMIB = null;

        String dir = System.getProperty(PROP_MIBS_DIR);

        if (dir != null) {
            File mibs = new File(dir);

            if (!mibs.exists()) {
                log.debug(mibs + " MIB dir does not exist");
                return;
            }
            try {
                parse(mibs);
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            }
        }
    }

    public synchronized static MIBTree getInstance() {
        if (instance == null) {
            instance = new MIBTree();

            instance.init();
        }

        return instance;
    }

    MIBNode lookup(String name) {
        return (MIBNode) this.table.get(name);
    }

    public static void setMibDir(String dir) {
        System.setProperty(PROP_MIBS_DIR, dir);
    }

    public static String toString(int[] oid) {
        StringBuffer buffer = new StringBuffer(oid.length * 2);

        buffer.append(oid[0]);

        for (int i = 1; i < oid.length; i++) {
            buffer.append('.').append(oid[i]);
        }

        return buffer.toString();
    }

    public int[] getOID(String name) {
        int[] oid = (int[]) this.oids.get(name);

        if (oid != null) {
            return oid;
        }

        if (name.indexOf('.') != -1) {
            // Handle "cpmCPUTotal5sec.1"...
            StringTokenizer tok = new StringTokenizer(name, ".");

            int[] scratch = new int[MAX_OID_LEN];
            int ix = 0;

            while (tok.hasMoreTokens()) {
                String node = tok.nextToken();

                if (Character.isDigit(node.charAt(0))) {
                    scratch[ix++] = Integer.parseInt(node);
                } else {
                    int[] subnode;
                    int len = node.length() - 1;

                    // See:
                    // http://www.snmp4j.org/doc/org/snmp4j/smi/OID.html#OID(java.lang.String)...
                    final char quote = '\'';

                    if ((node.charAt(0) == quote) && (node.charAt(len) == quote)) {
                        node = node.substring(1, len);
                        subnode = new OctetString(node).toSubIndex(false).getValue();
                    } else {
                        subnode = getOID(node);
                    }

                    if (subnode == null) {
                        return null;
                    }

                    System.arraycopy(subnode, 0, scratch, ix, subnode.length);

                    ix += subnode.length;
                }
            }

            oid = new int[ix];

            System.arraycopy(scratch, 0, oid, 0, ix);
        } else {
            MIBNode mibnode = lookup(name);

            if (mibnode == null) {
                return null;
            }

            oid = mibnode.getOID(name);

            if (oid == null) {
                log.warn(name + " found in tree but unable to resolve OID." + " lastLookupFailure="
                        + this.lastLookupFailure);

                return null;
            }
        }

        if (log.isDebugEnabled()) {
            log.debug(name + " resolved to: " + toString(oid));
        }

        this.oids.put(name, oid); // Cache result...

        return oid;
    }

    private void add(String[][] table, int flags) {
        for (int i = 0; i < table.length; i++) {
            String[] entry = table[i];

            add(entry[T_NAME], entry[T_PARENT], entry[T_OID], flags);
        }
    }

    private void add(String name, String parent, String oid, int flags) {
        boolean isDebug = log.isDebugEnabled();

        MIBNode node = (MIBNode) this.table.get(name);

        if (node != null) {
            if (isDebug) {
                log.debug(this.currentMIB + "." + name + " already added by " + node.getMIB());
            }

            return;
        }

        node = isDebug ? new DebugMIBNode(oid, parent) : new MIBNode(oid, parent);

        node.flags = flags;

        this.table.put(name, node);
    }

    // On-demand StringTokenizer.nextToken ( )...
    private String token(int ix) {
        if (ix < this.tokens.size()) {
            return (String) this.tokens.get(ix);
        }

        while (this.tokenizer.hasMoreTokens()) {
            String token = this.tokenizer.nextToken();

            this.tokens.add(token);

            if (ix + 1 == this.tokens.size()) {
                return token;
            }
        }

        return ""; // Avoid NPE and goofy "CONST".equals ( val )...
    }

    private String where(int start) {
        return " at " + this.currentMIB + ":" + ((start == 0) ? "" : start + "..") + this.reader.getLineNumber();
    }

    private void tokenize(String line) {
        this.previous.clear();
        this.previous.addAll(this.tokens);
        this.tokens.clear();

        if (line == null) {
            line = "";
        }

        this.tokenizer = new StringTokenizer(line);
    }

    private String readToLine(String contains) throws IOException {
        int start = this.reader.getLineNumber();

        String line;

        while ((line = readLine()) != null) {
            if (line.indexOf(contains) != -1) {
                return line;
            }
        }

        throw new IOException("Expecting '" + contains + "'" + " not found" + where(start));
    }

    // Skip all text within DESCRIPTION "..."
    // since certain MIBs have text which we would
    // otherwise get parsed, which we dont want...
    private String skipDescription(String line) throws IOException {
        // Flag to prevent recursing on ourselves...
        this.inDescription = true;

        try {
            if (line.indexOf(QUOTE) == -1) {
                line = readToLine(QUOTE);
            }

            if (!line.endsWith(QUOTE)) {
                line = readToLine(QUOTE);
            }

            return readLine();
        } finally {
            this.inDescription = false;
        }
    }

    private String readLine() throws IOException {
        String line;

        while ((line = this.reader.readLine()) != null) {
            line = line.trim();

            if ((line.length() == 0) || line.startsWith("--")) // Skip
                                                               // comments...
            {
                continue;
            }

            int ix = line.indexOf("--");

            if (ix != -1) {
                line = line.substring(0, ix).trim();
            }

            if (line.length() != 0) {
                if (!this.inDescription && line.startsWith("DESCRIPTION")) {
                    // This will recurse...
                    return skipDescription(line);
                } else {
                    return line;
                }
            }
        }

        return null;
    }

    private void parseId(String name, String line, int flags) throws IOException {
        if (line.endsWith(ASSIGN)) {
            line = readLine();
        }

        int start = line.indexOf('{');
        int end = line.indexOf('}');

        if ((start != -1) && (end == -1)) {
            // e.g. cisco LAN-EMULATION-CLIENT-MIB.my
            // atmfLanEmulation OBJECT IDENTIFIER ::= {
            // enterprises
            // atmForum(353)
            // atmForumNetworkManagement(5)
            // 3 }
            String nextLine;

            do {
                nextLine = readLine();

                line += " " + nextLine;
            } while ((end = line.indexOf('}')) == -1);
        }

        if ((start == -1) || (end == -1)) {
            throw new IOException("Expecting ::= {...} " + " in " + line + where(0));
        }

        line = line.substring(start + 1, end).trim();

        StringTokenizer tok = new StringTokenizer(line);

        int numTokens = tok.countTokens();

        if (numTokens < 2) {
            throw new IOException("Invalid ID in " + line + where(0));
        }

        if (numTokens == 2) {
            // Common case ::= { wwwServiceEntry 4 }...
            String parent = tok.nextToken();
            String number = tok.nextToken();

            add(name, parent, number, flags);
        } else {
            // ::= { iso org(3) dod(6) 1 }
            // atmfLanEmulation ... ::= (above)
            String parent = tok.nextToken();

            while (tok.hasMoreTokens()) {
                String next = tok.nextToken();

                int openParen = next.indexOf('(');

                if (openParen != -1) {
                    int closeParen = next.indexOf(')');

                    if (closeParen == -1) {
                        throw new IOException("Expecting ')' in " + line + where(0));
                    }

                    String subName = next.substring(0, openParen);
                    String subNum = next.substring(openParen + 1, closeParen);

                    add(subName, parent, subNum, IDENTIFIER);

                    parent = subName;
                } else {
                    add(name, parent, next, flags);
                }
            }
        }
    }

    private void parseObjectType() throws IOException {
        // :: = { wwwService 65 }
        String name = token(0);
        String line;

        int flags = NO_ACCESS;

        while ((line = readLine()) != null) {
            if (line.indexOf(ASSIGN) != -1) {
                break;
            }

            if (line.startsWith("INDEX")) {
                flags |= INDEX;
            } else if (line.startsWith("SYNTAX")) {
                if (line.indexOf("SEQUENCE") != -1) {
                    flags |= INDEX;
                }
            } else if (line.startsWith("ACCESS") || line.startsWith("MAX-ACCESS")) {
                if (line.indexOf("not-accessible") == -1) {
                    flags &= ~NO_ACCESS;
                }
            }
        }

        parseId(name, line, flags);
    }

    private void parseObjectIdentifier(String line) throws IOException {
        // wwwMIBObjects OBJECT IDENTIFIER ::= { wwwMIB 1 }
        String name = token(0);

        if (line.indexOf(ASSIGN) == -1) {
            line = readToLine(ASSIGN);
        }

        parseId(name, line, IDENTIFIER);
    }

    private boolean hasParsedFile(File file) {
        String name = file.getName();

        if (this.parsedFiles.get(name) != null) {
            return true;
        } else {
            this.parsedFiles.put(name, Boolean.TRUE);

            return false;
        }
    }

    private boolean parseFile(File file) throws IOException {
        return parse(file.toString(), new FileInputStream(file));
    }

    private class AcceptFilter {
        List filter = null;

        AcceptFilter(String[] accept) {
            if ((accept != null) && (accept.length != 0)) {
                filter = Arrays.asList(accept);
            }
        }

        boolean accept(String name) {
            if (filter != null) {
                return filter.contains(name);
            } else {
                return true;
            }
        }
    }

    public boolean parse(JarFile jar) throws IOException {
        return parse(jar, null);
    }

    public boolean parse(JarFile jar, String[] accept) throws IOException {
        AcceptFilter filter = new AcceptFilter(accept);

        for (Enumeration e = jar.entries(); e.hasMoreElements();) {
            JarEntry entry = (JarEntry) e.nextElement();

            if (entry.isDirectory()) {
                continue;
            }

            if (!entry.getName().startsWith("mibs/")) {
                continue;
            }

            String name = entry.getName().substring(5);

            if (!filter.accept(name)) {
                continue;
            }

            if (hasParsedFile(new File(name))) {
                continue;
            }

            String where = jar.getName() + "!" + entry.getName();

            parse(where, jar.getInputStream(entry));
        }

        return true;
    }

    public boolean parse(File file) throws IOException {
        return parse(file, null);
    }

    public boolean parse(File file, String[] accept) throws IOException {
        if (hasParsedFile(file)) {
            return true;
        }

        if (file.isDirectory()) {
            File[] mibs = file.listFiles();

            if ((mibs == null) || (mibs.length == 0)) {
                log.debug("No MIBs in directory: " + file);

                return false;
            }

            AcceptFilter filter = new AcceptFilter(accept);

            log.debug("Loading MIBs in directory: " + file);

            for (int i = 0; i < mibs.length; i++) {
                File mib = mibs[i];

                if (mib.isDirectory()) {
                    continue;
                }

                if (!filter.accept(mib.getName())) {
                    continue;
                }

                parseFile(mib);
            }

            return true;
        } else if (file.getName().endsWith(".jar")) {
            JarFile jar = new JarFile(file);

            try {
                return parse(jar, accept);
            } finally {
                jar.close();
            }
        } else {
            return parseFile(file);
        }
    }

    public boolean parse(URL url) throws IOException {
        if (hasParsedFile(new File(url.getFile()))) {
            return true;
        }

        return parse(url.toString(), url.openStream());
    }

    public boolean parse(String name, InputStream is) throws IOException {
        boolean isSuccess = false;

        try {
            isSuccess = parse(is);
        } catch (IOException e) {
            throw new IOException("Failed to load MIB: '" + name + "': " + e);
        }

        log.debug("Loading MIB: '" + name + "': " + (isSuccess ? "success" : "skipped"));

        return isSuccess;
    }

    public boolean parse(InputStream is) throws IOException {
        this.lastLookupFailure = null;

        try {
            return parseMIB(is);
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new IOException(e + where(0));
        } finally {
            this.tokens.clear();
            this.previous.clear();

            try {
                is.close();
            } catch (IOException e) {
            }
        }
    }

    public String getLastLookupFailure() {
        return this.lastLookupFailure;
    }

    private boolean parseMIB(InputStream is) throws IOException {
        String line;

        this.reader = new LineNumberReader(new InputStreamReader(is));

        this.currentMIB = "";

        tokenize(readLine());

        if (!token(1).equals("DEFINITIONS")) {
            return false;
        }

        this.currentMIB = token(0);

        int size = this.table.size();

        while ((line = readLine()) != null) {
            tokenize(line);

            String first = token(0);

            if (first.equals("IMPORTS") || first.equals("EXPORTS")) {
                readToLine(";");

                continue;
            }

            if (first.equals("SYNTAX")) {
                continue;
            }

            String second = token(1);

            if (second == null) {
                continue;
            }

            if ((line.indexOf("SEQUENCE {") != -1) || (line.indexOf("CHOICE {") != -1)) {
                readToLine("}");
            } else if (second.equals("OBJECT") && token(2).equals("IDENTIFIER")) {
                parseObjectIdentifier(line);
            } else if ((this.previous.size() == 1) && first.equals("OBJECT") && second.equals("IDENTIFIER")) {
                // snmpFrameworkAdmin
                // OBJECT IDENTIFIER ::= { snmpFrameworkMIB 1 }
                Object name = this.previous.get(0);

                line = name + " " + line;

                this.tokens.add(0, name);

                parseObjectIdentifier(line);
            } else if (second.equals("OBJECT-TYPE") || second.equals("MODULE-IDENTITY")
                    || second.equals("OBJECT-IDENTITY")) {
                parseObjectType();
            }
        }

        if (log.isDebugEnabled()) {
            log.debug(this.currentMIB + " added " + (this.table.size() - size) + " entries");
        }

        return true;
    }

    public static void main(String[] args) throws Exception {
        ArrayList names = new ArrayList();

        MIBTree tree = MIBTree.getInstance();

        for (int i = 0; i < args.length; i++) {
            File file = new File(args[i]);

            if (file.exists()) {
                if (!tree.parse(file)) {
                    System.out.println(args[i] + " is not valid MIB");
                } else {
                    System.out.println(args[i] + " parsed");
                }
            } else {
                names.add(args[i]);
            }
        }

        if (names.size() == 0) {
            names.addAll(tree.table.keySet());
        }

        for (int i = 0; i < names.size(); i++) {
            String name = (String) names.get(i);

            int[] oid = tree.getOID(name);

            if (oid == null) {
                System.out.println(
                        "Failed to get oid for: " + name + " (lastLookupFailure=" + tree.lastLookupFailure + ")");
            } else {
                System.out.println(name + "=" + toString(oid));
            }
        }
    }
}