com.netxforge.oss2.config.AmiPeerFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.netxforge.oss2.config.AmiPeerFactory.java

Source

/*******************************************************************************
 * This file is part of OpenNMS(R).
 *
 * Copyright (C) 2012 The OpenNMS Group, Inc.
 * OpenNMS(R) is Copyright (C) 1999-2012 The OpenNMS Group, Inc.
 *
 * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc.
 *
 * OpenNMS(R) 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.
 *
 * OpenNMS(R) 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 OpenNMS(R).  If not, see:
 *      http://www.gnu.org/licenses/
 *
 * For more information contact:
 *     OpenNMS(R) Licensing <license@opennms.org>
 *     http://www.opennms.org/
 *     http://www.opennms.com/
 *******************************************************************************/

package com.netxforge.oss2.config;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.commons.io.IOUtils;
import org.exolab.castor.xml.MarshalException;
import org.exolab.castor.xml.Marshaller;
import org.exolab.castor.xml.ValidationException;
import org.opennms.core.utils.ConfigFileConstants;
import org.opennms.core.utils.IPLike;
import org.opennms.core.utils.InetAddressComparator;
import org.opennms.core.utils.InetAddressUtils;
import org.opennms.core.utils.LogUtils;
import org.opennms.core.xml.CastorUtils;
import org.opennms.netmgt.config.ami.AmiAgentConfig;
import org.opennms.netmgt.config.ami.AmiConfig;
import org.opennms.netmgt.config.ami.Definition;
import org.opennms.netmgt.config.ami.Range;

/**
 * This class is the main repository for AMI configuration information used by
 * the capabilities daemon. When this class is loaded it reads the AMI
 * configuration into memory, and uses the configuration to find the
 * {@link org.opennms.protocols.ami.AmiAgentConfig AmiAgentConfig} objects for specific
 * addresses. If an address cannot be located in the configuration then a
 * default peer instance is returned to the caller.
 *
 * <strong>Note: </strong>Users of this class should make sure the
 * <em>init()</em> is called before calling any other method to ensure the
 * config is loaded before accessing other convenience methods.
 *
 * @author <a href="mailto:jeffg@opennms.org">Jeff Gehlbach</a>
 * @author <a href="http://www.opennms.org/">OpenNMS </a>
 */
public class AmiPeerFactory {
    private final ReadWriteLock m_globalLock = new ReentrantReadWriteLock();
    private final Lock m_readLock = m_globalLock.readLock();
    private final Lock m_writeLock = m_globalLock.writeLock();

    /**
     * The singleton instance of this factory
     */
    private static AmiPeerFactory m_singleton = null;

    /**
     * The config class loaded from the config file
     */
    private static AmiConfig m_config;

    /**
     * This member is set to true if the configuration file has been loaded.
     */
    private static boolean m_loaded = false;

    /**
     * Private constructor
     * 
     * @exception java.io.IOException
     *                Thrown if the specified config file cannot be read
     * @exception org.exolab.castor.xml.MarshalException
     *                Thrown if the file does not conform to the schema.
     * @exception org.exolab.castor.xml.ValidationException
     *                Thrown if the contents do not match the required schema.
     *
     * @param configFile the path to the config file to load in.
     */
    private AmiPeerFactory(final String configFile) throws IOException, MarshalException, ValidationException {
        super();
        final InputStream cfgIn = new FileInputStream(configFile);
        m_config = CastorUtils.unmarshal(AmiConfig.class, cfgIn);
        IOUtils.closeQuietly(cfgIn);
    }

    public Lock getReadLock() {
        return m_readLock;
    }

    public Lock getWriteLock() {
        return m_writeLock;
    }

    /**
     * Load the config from the default config file and create the singleton
     * instance of this factory.
     *
     * @exception java.io.IOException
     *                Thrown if the specified config file cannot be read
     * @exception org.exolab.castor.xml.MarshalException
     *                Thrown if the file does not conform to the schema.
     * @exception org.exolab.castor.xml.ValidationException
     *                Thrown if the contents do not match the required schema.
     * @throws java.io.IOException if any.
     * @throws org.exolab.castor.xml.MarshalException if any.
     * @throws org.exolab.castor.xml.ValidationException if any.
     */
    public static synchronized void init() throws IOException, MarshalException, ValidationException {
        if (m_loaded) {
            // init already called - return
            // to reload, reload() will need to be called
            return;
        }

        final File cfgFile = ConfigFileConstants.getFile(ConfigFileConstants.AMI_CONFIG_FILE_NAME);
        LogUtils.debugf(AmiPeerFactory.class, "init: config file path: %s", cfgFile.getPath());
        m_singleton = new AmiPeerFactory(cfgFile.getPath());
        m_loaded = true;
    }

    /**
     * Reload the config from the default config file
     *
     * @exception java.io.IOException
     *                Thrown if the specified config file cannot be read/loaded
     * @exception org.exolab.castor.xml.MarshalException
     *                Thrown if the file does not conform to the schema.
     * @exception org.exolab.castor.xml.ValidationException
     *                Thrown if the contents do not match the required schema.
     * @throws java.io.IOException if any.
     * @throws org.exolab.castor.xml.MarshalException if any.
     * @throws org.exolab.castor.xml.ValidationException if any.
     */
    public static synchronized void reload() throws IOException, MarshalException, ValidationException {
        m_singleton = null;
        m_loaded = false;

        init();
    }

    /**
     * Return the singleton instance of this factory.
     *
     * @return The current factory instance.
     * @throws java.lang.IllegalStateException
     *             Thrown if the factory has not yet been initialized.
     */
    public static synchronized AmiPeerFactory getInstance() {
        if (!m_loaded)
            throw new IllegalStateException("The AmiPeerFactory has not been initialized");

        return m_singleton;
    }

    /**
     * <p>setInstance</p>
     *
     * @param singleton a {@link org.opennms.netmgt.config.AmiPeerFactory} object.
     */
    public static synchronized void setInstance(final AmiPeerFactory singleton) {
        m_singleton = singleton;
        m_loaded = true;
    }

    /**
     * <p>setAmiConfig</p>
     *
     * @param m_config a {@link org.opennms.netmgt.config.ami.AmiConfig} object.
     */
    public static synchronized void setAmiConfig(AmiConfig m_config) {
        AmiPeerFactory.m_config = m_config;
    }

    /**
     * Saves the current settings to disk
     *
     * @throws java.lang.Exception if saving settings to disk fails.
     */
    public void saveCurrent() throws Exception {
        getWriteLock().lock();

        try {
            optimize();

            // Marshal to a string first, then write the string to the file. This
            // way the original config
            // isn't lost if the XML from the marshal is hosed.
            final StringWriter stringWriter = new StringWriter();
            Marshaller.marshal(m_config, stringWriter);
            if (stringWriter.toString() != null) {
                final Writer fileWriter = new OutputStreamWriter(
                        new FileOutputStream(ConfigFileConstants.getFile(ConfigFileConstants.AMI_CONFIG_FILE_NAME)),
                        "UTF-8");
                fileWriter.write(stringWriter.toString());
                fileWriter.flush();
                fileWriter.close();
            }

            reload();
        } finally {
            getWriteLock().unlock();
        }
    }

    /**
     * Combine specific and range elements so that AMIPeerFactory has to spend
     * less time iterating all these elements.
     * TODO This really should be pulled up into PeerFactory somehow, but I'm not sure how (given that "Definition" is different for both
     * SNMP and AMI.  Maybe some sort of visitor methodology would work.  The basic logic should be fine as it's all IP address manipulation
     *
     * @throws UnknownHostException
     */
    void optimize() throws UnknownHostException {
        getWriteLock().lock();

        try {
            // First pass: Remove empty definition elements
            for (final Iterator<Definition> definitionsIterator = m_config.getDefinitionCollection()
                    .iterator(); definitionsIterator.hasNext();) {
                final Definition definition = definitionsIterator.next();
                if (definition.getSpecificCount() == 0 && definition.getRangeCount() == 0) {
                    LogUtils.debugf(this, "optimize: Removing empty definition element");
                    definitionsIterator.remove();
                }
            }

            // Second pass: Replace single IP range elements with specific elements
            for (Definition definition : m_config.getDefinitionCollection()) {
                for (Iterator<Range> rangesIterator = definition.getRangeCollection().iterator(); rangesIterator
                        .hasNext();) {
                    Range range = rangesIterator.next();
                    if (range.getBegin().equals(range.getEnd())) {
                        definition.addSpecific(range.getBegin());
                        rangesIterator.remove();
                    }
                }
            }

            // Third pass: Sort specific and range elements for improved XML
            // readability and then combine them into fewer elements where possible
            for (final Definition definition : m_config.getDefinitionCollection()) {
                // Sort specifics
                final TreeMap<InetAddress, String> specificsMap = new TreeMap<InetAddress, String>(
                        new InetAddressComparator());
                for (final String specific : definition.getSpecificCollection()) {
                    specificsMap.put(InetAddressUtils.getInetAddress(specific), specific.trim());
                }

                // Sort ranges
                final TreeMap<InetAddress, Range> rangesMap = new TreeMap<InetAddress, Range>(
                        new InetAddressComparator());
                for (final Range range : definition.getRangeCollection()) {
                    rangesMap.put(InetAddressUtils.getInetAddress(range.getBegin()), range);
                }

                // Combine consecutive specifics into ranges
                InetAddress priorSpecific = null;
                Range addedRange = null;
                for (final InetAddress specific : specificsMap.keySet()) {
                    if (priorSpecific == null) {
                        priorSpecific = specific;
                        continue;
                    }

                    if (BigInteger.ONE.equals(InetAddressUtils.difference(specific, priorSpecific))
                            && InetAddressUtils.inSameScope(specific, priorSpecific)) {
                        if (addedRange == null) {
                            addedRange = new Range();
                            addedRange.setBegin(InetAddressUtils.toIpAddrString(priorSpecific));
                            rangesMap.put(priorSpecific, addedRange);
                            specificsMap.remove(priorSpecific);
                        }

                        addedRange.setEnd(InetAddressUtils.toIpAddrString(specific));
                        specificsMap.remove(specific);
                    } else {
                        addedRange = null;
                    }

                    priorSpecific = specific;
                }

                // Move specifics to ranges
                for (final InetAddress specific : new ArrayList<InetAddress>(specificsMap.keySet())) {
                    for (final InetAddress begin : new ArrayList<InetAddress>(rangesMap.keySet())) {

                        if (!InetAddressUtils.inSameScope(begin, specific)) {
                            continue;
                        }

                        if (InetAddressUtils.toInteger(begin).subtract(BigInteger.ONE)
                                .compareTo(InetAddressUtils.toInteger(specific)) > 0) {
                            continue;
                        }

                        final Range range = rangesMap.get(begin);

                        final InetAddress end = InetAddressUtils.getInetAddress(range.getEnd());

                        if (InetAddressUtils.toInteger(end).add(BigInteger.ONE)
                                .compareTo(InetAddressUtils.toInteger(specific)) < 0) {
                            continue;
                        }

                        if (InetAddressUtils.toInteger(specific).compareTo(InetAddressUtils.toInteger(begin)) >= 0
                                && InetAddressUtils.toInteger(specific)
                                        .compareTo(InetAddressUtils.toInteger(end)) <= 0) {
                            specificsMap.remove(specific);
                            break;
                        }

                        if (InetAddressUtils.toInteger(begin).subtract(BigInteger.ONE)
                                .equals(InetAddressUtils.toInteger(specific))) {
                            rangesMap.remove(begin);
                            rangesMap.put(specific, range);
                            range.setBegin(InetAddressUtils.toIpAddrString(specific));
                            specificsMap.remove(specific);
                            break;
                        }

                        if (InetAddressUtils.toInteger(end).add(BigInteger.ONE)
                                .equals(InetAddressUtils.toInteger(specific))) {
                            range.setEnd(InetAddressUtils.toIpAddrString(specific));
                            specificsMap.remove(specific);
                            break;
                        }
                    }
                }

                // Combine consecutive ranges
                Range priorRange = null;
                InetAddress priorBegin = null;
                InetAddress priorEnd = null;
                for (final Iterator<InetAddress> rangesIterator = rangesMap.keySet().iterator(); rangesIterator
                        .hasNext();) {
                    final InetAddress beginAddress = rangesIterator.next();
                    final Range range = rangesMap.get(beginAddress);
                    final InetAddress endAddress = InetAddressUtils.getInetAddress(range.getEnd());

                    if (priorRange != null) {
                        if (InetAddressUtils.inSameScope(beginAddress, priorEnd) && InetAddressUtils
                                .difference(beginAddress, priorEnd).compareTo(BigInteger.ONE) <= 0) {
                            priorBegin = new InetAddressComparator().compare(priorBegin, beginAddress) < 0
                                    ? priorBegin
                                    : beginAddress;
                            priorRange.setBegin(InetAddressUtils.toIpAddrString(priorBegin));
                            priorEnd = new InetAddressComparator().compare(priorEnd, endAddress) > 0 ? priorEnd
                                    : endAddress;
                            priorRange.setEnd(InetAddressUtils.toIpAddrString(priorEnd));

                            rangesIterator.remove();
                            continue;
                        }
                    }

                    priorRange = range;
                    priorBegin = beginAddress;
                    priorEnd = endAddress;
                }

                // Update changes made to sorted maps
                definition.setSpecific(specificsMap.values().toArray(new String[0]));
                definition.setRange(rangesMap.values().toArray(new Range[0]));
            }
        } finally {
            getWriteLock().unlock();
        }
    }

    /**
     * <p>getAgentConfig</p>
     *
     * @param agentInetAddress a {@link java.net.InetAddress} object.
     * @return a {@link org.opennms.protocols.ami.AmiAgentConfig} object.
     */
    public AmiAgentConfig getAgentConfig(final InetAddress agentInetAddress) {
        getReadLock().lock();

        try {
            if (m_config == null)
                return new AmiAgentConfig(agentInetAddress);

            final AmiAgentConfig agentConfig = new AmiAgentConfig(agentInetAddress);

            //Now set the defaults from the m_config
            setAmiAgentConfig(agentConfig, new Definition());

            // Attempt to locate the node
            DEFLOOP: for (final Definition def : m_config.getDefinitionCollection()) {
                // check the specifics first
                for (String saddr : def.getSpecificCollection()) {
                    saddr = saddr.trim();
                    final InetAddress addr = InetAddressUtils.addr(saddr);
                    if (addr.equals(agentConfig.getAddress())) {
                        setAmiAgentConfig(agentConfig, def);
                        break DEFLOOP;
                    }
                }

                // check the ranges
                for (final Range rng : def.getRangeCollection()) {
                    if (InetAddressUtils.isInetAddressInRange(InetAddressUtils.str(agentConfig.getAddress()),
                            rng.getBegin(), rng.getEnd())) {
                        setAmiAgentConfig(agentConfig, def);
                        break DEFLOOP;
                    }
                }

                // check the matching IP expressions
                for (final String ipMatch : def.getIpMatchCollection()) {
                    if (IPLike.matches(InetAddressUtils.str(agentInetAddress), ipMatch)) {
                        setAmiAgentConfig(agentConfig, def);
                        break DEFLOOP;
                    }
                }

            } // end DEFLOOP

            if (agentConfig == null)
                setAmiAgentConfig(agentConfig, new Definition());

            return agentConfig;
        } finally {
            getReadLock().unlock();
        }
    }

    private void setAmiAgentConfig(final AmiAgentConfig agentConfig, final Definition def) {
        setCommonAttributes(agentConfig, def);
        agentConfig.setPassword(determinePassword(def));
    }

    /**
     * This is a helper method to set all the common attributes in the agentConfig.
     * 
     * @param agentConfig
     * @param def
     */
    private void setCommonAttributes(final AmiAgentConfig agentConfig, final Definition def) {
        agentConfig.setRetries(determineRetries(def));
        agentConfig.setTimeout((int) determineTimeout(def));
        agentConfig.setUsername(determineUsername(def));
        agentConfig.setPassword(determinePassword(def));
    }

    /**
     * Helper method to search the ami-config for the appropriate username
     * @param def
     * @return a string containing the username. will return the default if none is set.
     */
    private String determineUsername(final Definition def) {
        return (def.getPassword() == null
                ? (m_config.getUsername() == null ? AmiAgentConfig.DEFAULT_USERNAME : m_config.getUsername())
                : def.getUsername());
    }

    /**
    * Helper method to search the ami-config for the appropriate password
    * @param def
    * @return a string containing the password. will return the default if none is set.
    */
    private String determinePassword(final Definition def) {
        return (def.getPassword() == null
                ? (m_config.getPassword() == null ? AmiAgentConfig.DEFAULT_PASSWORD : m_config.getPassword())
                : def.getPassword());
    }

    /**
     * Helper method to search the ami-config 
     * @param def
     * @return a long containing the timeout, AmiAgentConfig.DEFAULT_TIMEOUT if not specified.
     */
    private long determineTimeout(final Definition def) {
        final long timeout = AmiAgentConfig.DEFAULT_TIMEOUT;
        return (long) (def.getTimeout() == 0 ? (m_config.getTimeout() == 0 ? timeout : m_config.getTimeout())
                : def.getTimeout());
    }

    private int determineRetries(final Definition def) {
        final int retries = AmiAgentConfig.DEFAULT_RETRIES;
        return (def.getRetry() == 0 ? (m_config.getRetry() == 0 ? retries : m_config.getRetry()) : def.getRetry());
    }

    /**
     * <p>getAmiConfig</p>
     *
     * @return a {@link org.opennms.netmgt.config.ami.AmiConfig} object.
     */
    public static AmiConfig getAmiConfig() {
        return m_config;
    }
}