Java tutorial
/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2006-2014 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2014 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 Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero 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 org.opennms.poller.remote; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.swing.JOptionPane; import javax.swing.SwingUtilities; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.cli.PosixParser; import org.opennms.netmgt.icmp.NullPinger; import org.opennms.netmgt.icmp.Pinger; import org.opennms.netmgt.icmp.PingerFactory; import org.opennms.netmgt.poller.remote.PollerFrontEnd; import org.opennms.netmgt.poller.remote.PollerFrontEnd.PollerFrontEndStates; import org.quartz.Scheduler; import org.quartz.SchedulerException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; /** * This class launches the Remote Poller by reading command-line arguments, * collecting authentication information so that the system can connect to the * OpenNMS system, and instantiating a Spring context containing beans for the * {@link PollerFrontEnd} and (optionally) the UI. The context initialization will * launch the Remote Poller process. * * @author <a href="mailto:ranger@opennms.org">Benjamin Reed</a> * @author <a href="mailto:brozow@opennms.org">Mathew Brozowski</a> */ public class Main implements Runnable { private static final Logger LOG = LoggerFactory.getLogger(Main.class); protected final String[] m_args; protected URI m_uri = null; protected String m_locationName; protected String m_username = null; protected String m_password = null; protected boolean m_gui = false; protected boolean m_disableIcmp = false; protected boolean m_scanReport = false; private static enum SpringExportSchemes { rmi, http, https } /** * This {@link PropertyChangeListener} listens for {@link PollerFrontEnd} * events and if the {@link PollerFrontEnd} is ready to exit, it shuts the * Spring context down. */ private static class ShouldExitPropertyChangeListener implements PropertyChangeListener { private final AbstractApplicationContext m_context; public ShouldExitPropertyChangeListener(AbstractApplicationContext context) { m_context = context; } /** * Examine the event to see if it should trigger a shutdown. */ private static boolean shouldExit(PropertyChangeEvent e) { LOG.info("shouldExit: received property change event for property: {}; oldvalue: {}; newvalue: {}", e.getPropertyName(), e.getOldValue(), e.getNewValue()); String propName = e.getPropertyName(); Object newValue = e.getNewValue(); // if exitNecessary becomes true.. then return true if (PollerFrontEndStates.exitNecessary.toString().equals(propName) && Boolean.TRUE.equals(newValue)) { LOG.info("shouldExit: Exiting because exitNecessary is TRUE"); return true; } // if started becomes false the we should exit if (PollerFrontEndStates.started.toString().equals(propName) && Boolean.FALSE.equals(newValue)) { LOG.info("shouldExit: Exiting because started is now false"); return true; } LOG.info("shouldExit: not exiting"); return false; } @Override public void propertyChange(PropertyChangeEvent e) { if (shouldExit(e)) { shutdownContextAndExit(m_context); } } } private Main(String[] args) { // Give us some time to attach a debugger if necessary //try { Thread.sleep(20000); } catch (InterruptedException e) {} m_args = Arrays.copyOf(args, args.length); } public void initializePinger() { if (m_disableIcmp) { LOG.info("Disabling ICMP by user request."); System.setProperty("org.opennms.netmgt.icmp.pingerClass", "org.opennms.netmgt.icmp.NullPinger"); PingerFactory.setInstance(new NullPinger()); return; } final String pingerClass = System.getProperty("org.opennms.netmgt.icmp.pingerClass"); if (pingerClass == null) { LOG.info("System property org.opennms.netmgt.icmp.pingerClass is not set; using JnaPinger by default"); System.setProperty("org.opennms.netmgt.icmp.pingerClass", "org.opennms.netmgt.icmp.jna.JnaPinger"); } LOG.info("Pinger class: {}", System.getProperty("org.opennms.netmgt.icmp.pingerClass")); try { final Pinger pinger = PingerFactory.getInstance(); pinger.ping(InetAddress.getLoopbackAddress()); } catch (final Throwable t) { LOG.warn( "Unable to get pinger instance. Setting pingerClass to NullPinger. For details, see: http://www.opennms.org/wiki/ICMP_could_not_be_initialized"); System.setProperty("org.opennms.netmgt.icmp.pingerClass", "org.opennms.netmgt.icmp.NullPinger"); PingerFactory.setInstance(new NullPinger()); if (m_gui) { final String message = "ICMP (ping) could not be initialized: " + t.getMessage() + "\nDisabling ICMP and using the NullPinger instead." + "\nFor details, see: http://www.opennms.org/wiki/ICMP_could_not_be_initialized"; JOptionPane.showMessageDialog(null, message, "ICMP Not Available", JOptionPane.WARNING_MESSAGE); } } } private void getAuthenticationInfo() throws InvocationTargetException, InterruptedException { if (m_uri == null) { throw new IllegalArgumentException("no URI specified!"); } else if (m_uri.getScheme() == null) { throw new IllegalArgumentException("no URI scheme specified!"); } // Make sure that the URI is a valid value SpringExportSchemes.valueOf(m_uri.getScheme()); if (SpringExportSchemes.rmi.toString().equals(m_uri.getScheme())) { // RMI doesn't have authentication return; } if (m_username == null) { // Display a screen where the username and password are entered final AuthenticationGui gui = createGui(); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { gui.createAndShowGui(); } }); /* * This call pauses on a {@link CountDownLatch} that is * signaled when the user hits the 'OK' button on the GroovyGui * screen. */ AuthenticationBean auth = gui.getAuthenticationBean(); m_username = auth.getUsername(); m_password = auth.getPassword(); } if (m_username != null) { SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_GLOBAL); SecurityContextHolder.getContext() .setAuthentication(new UsernamePasswordAuthenticationToken(m_username, m_password)); } } /** * Create the username and password form GUI so that the credentials can be input * by the user. * * @return GroovyGui that will display a username and password form. */ private static AuthenticationGui createGui() { try { return (AuthenticationGui) Class.forName("org.opennms.groovy.poller.remote.LoginGui").newInstance(); } catch (Throwable e) { throw new RuntimeException("Unable to find Configuration GUI!", e); } } @Override public void run() { try { // Parse arguments to initialize the configuration fields. parseArguments(m_args); // Test to make sure that we can use ICMP. If not, replace the ICMP // implementation with NullPinger. initializePinger(); // If we didn't get authentication information from the command line or // system properties, then use an AWT window to prompt the user. // Initialize a Spring {@link SecurityContext} that contains the // credentials. getAuthenticationInfo(); AbstractApplicationContext context = createAppContext(); PollerFrontEnd frontEnd = getPollerFrontEnd(context); if (!m_gui && !m_scanReport) { if (!frontEnd.isRegistered()) { if (m_locationName == null) { LOG.error( "No location name provided. You must pass a location name the first time you start the Remote Poller!"); System.exit(27); } else { frontEnd.register(m_locationName); } } } } catch (Throwable e) { // a fatal exception occurred LOG.error("Exception occurred during registration!", e); System.exit(27); } } private void parseArguments(String[] args) throws ParseException { Options options = new Options(); options.addOption("h", "help", false, "this help"); options.addOption("d", "debug", false, "write debug messages to the log"); options.addOption("g", "gui", false, "start a GUI (default: false)"); options.addOption("i", "disable-icmp", false, "disable ICMP/ping (overrides -Dorg.opennms.netmgt.icmp.pingerClass=)"); options.addOption("l", "location", true, "the location name of this remote poller"); options.addOption("u", "url", true, "the URL for OpenNMS (example: https://server-name/opennms-remoting)"); options.addOption("n", "name", true, "the name of the user to connect as"); options.addOption("p", "password", true, "the password to use when connecting"); options.addOption("s", "scan-report", false, "perform a single scan report instead of running the polling engine"); CommandLineParser parser = new PosixParser(); CommandLine cl = parser.parse(options, args); if (cl.hasOption("h")) { usage(options); System.exit(1); } if (cl.hasOption("d")) { } if (cl.hasOption("i")) { m_disableIcmp = true; } if (cl.hasOption("l")) { m_locationName = cl.getOptionValue("l"); } if (cl.hasOption("u")) { String arg = cl.getOptionValue("u").toLowerCase(); try { m_uri = new URI(arg); } catch (URISyntaxException e) { usage(options); e.printStackTrace(); System.exit(2); } } else { usage(options); System.exit(3); } if (cl.hasOption("g")) { m_gui = true; } if (cl.hasOption("s")) { m_scanReport = true; } if (cl.hasOption("n")) { m_username = cl.getOptionValue("n"); m_password = cl.getOptionValue("p"); if (m_password == null) { m_password = ""; } } // If we cannot obtain the username/password from the command line, attempt // to optionally get it from system properties if (m_username == null) { m_username = System.getProperty("opennms.poller.server.username"); if (m_username != null) { m_password = System.getProperty("opennms.poller.server.password"); if (m_password == null) { m_password = ""; } } } } private static void usage(Options o) { HelpFormatter formatter = new HelpFormatter(); formatter.printHelp(Main.class.getName() + " -u [URL] [options]", o); } private AbstractApplicationContext createAppContext() { /* * Set a system property called user.home.url so that the * Spring contexts can reference resources that are stored * in the user's home directory. */ File homeDir = new File(System.getProperty("user.home")); String homeUrl = homeDir.toURI().toString(); // Trim the trailing file separator off of the end of the URI if (homeUrl.endsWith("/")) { homeUrl = homeUrl.substring(0, homeUrl.length() - 1); } LOG.info("user.home.url = {}", homeUrl); System.setProperty("user.home.url", homeUrl); String serverURI = m_uri.toString().replaceAll("/*$", ""); LOG.info("opennms.poller.server.url = {}", serverURI); System.setProperty("opennms.poller.server.url", serverURI); LOG.info("location name = {}", m_locationName); List<String> configs = new ArrayList<String>(); configs.add( "classpath:/META-INF/opennms/applicationContext-remotePollerBackEnd-" + m_uri.getScheme() + ".xml"); configs.add("classpath:/META-INF/opennms/applicationContext-pollerFrontEnd.xml"); // Choose a PollerFrontEnd implementation if (m_scanReport) { configs.add("classpath:/META-INF/opennms/applicationContext-pollerFrontEnd-scanReport.xml"); } else { configs.add("classpath:/META-INF/opennms/applicationContext-pollerFrontEnd.xml"); } // Choose a GUI implementation (if in scan-report or gui mode) if (m_scanReport) { configs.add("classpath:/META-INF/opennms/applicationContext-scan-gui.xml"); } else if (m_gui) { configs.add("classpath:/META-INF/opennms/applicationContext-ws-gui.xml"); } final ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext( configs.toArray(new String[0])); // Add a shutdown hook so that even if the user CTRL-Cs the app or closes its JNLP GUI window, // it will still perform the same clean shutdown as stopping the poller via messaging. Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { shutdownContextAndExit(context); } }); return context; } private PollerFrontEnd getPollerFrontEnd(final AbstractApplicationContext context) { PollerFrontEnd frontEnd = (PollerFrontEnd) context.getBean("pollerFrontEnd"); frontEnd.addPropertyChangeListener(new ShouldExitPropertyChangeListener(context)); return frontEnd; } private static void shutdownContextAndExit(AbstractApplicationContext context) { int returnCode = 0; // MVR: gracefully shutdown scheduler, otherwise context.close() will raise // an exception. See #NMS-6966 for more details. if (context.isActive()) { // If there is a scheduler in the context, then shut it down Scheduler scheduler = (Scheduler) context.getBean("scheduler"); if (scheduler != null) { try { LOG.info("Shutting down PollerFrontEnd scheduler"); scheduler.shutdown(); LOG.info("PollerFrontEnd scheduler shutdown complete"); } catch (SchedulerException ex) { LOG.warn("Shutting down PollerFrontEnd scheduler failed", ex); returnCode = 10; } } // Now close the application context. This will invoke // {@link DefaultPollerFrontEnd#destroy()} which will mark the // remote poller as "Stopped" during shutdown instead of letting // it remain in the "Disconnected" state. context.close(); } final int returnCodeValue = returnCode; new Thread() { public void run() { // Sleep for a couple of seconds so that the other // PropertyChangeListeners get a chance to fire try { Thread.sleep(5000); } catch (InterruptedException e) { } // Exit System.exit(returnCodeValue); } }.start(); } /** * <p>main</p> * * @param args an array of {@link java.lang.String} objects. * @throws java.lang.Exception if any. */ public static void main(String[] args) { try { String killSwitchFileName = System.getProperty("opennms.poller.killSwitch.resource"); File killSwitch = null; if (!"".equals(killSwitchFileName) && killSwitchFileName != null) { killSwitch = new File(System.getProperty("opennms.poller.killSwitch.resource")); if (!killSwitch.exists()) { try { killSwitch.createNewFile(); } catch (IOException ioe) { // We'll just do without one } } } new Main(args).run(); } catch (Throwable e) { e.printStackTrace(); } } }