1   package com.mccrory.scott.spumoni;
2   
3   ////////////////////////////////////////////////////////////////////////////////
4   // Copyright (C) 2002  Scott McCrory
5   //
6   // This program is free software; you can redistribute it and/or
7   // modify it under the terms of the GNU General Public License
8   // as published by the Free Software Foundation; either version 2
9   // of the License, or (at your option) any later version.
10  //
11  // This program is distributed in the hope that it will be useful,
12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  // GNU General Public License for more details.
15  //
16  // You should have received a copy of the GNU General Public License
17  // along with this program; if not, write to the Free Software
18  // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19  ////////////////////////////////////////////////////////////////////////////////
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.io.ObjectInputStream;
24  import java.io.ObjectOutputStream;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.NoSuchElementException;
28  import java.util.StringTokenizer;
29  import java.util.Vector;
30  
31  import org.apache.log4j.Category;
32  import org.apache.log4j.NDC;
33  import org.apache.log4j.PropertyConfigurator;
34  import org.apache.oro.text.regex.MalformedPatternException;
35  import org.apache.oro.text.regex.MatchResult;
36  import org.apache.oro.text.regex.Pattern;
37  import org.apache.oro.text.regex.PatternCompiler;
38  import org.apache.oro.text.regex.PatternMatcher;
39  import org.apache.oro.text.regex.PatternMatcherInput;
40  import org.apache.oro.text.regex.Perl5Compiler;
41  import org.apache.oro.text.regex.Perl5Matcher;
42  import org.dom4j.Attribute;
43  import org.dom4j.Document;
44  import org.dom4j.DocumentException;
45  import org.dom4j.Element;
46  import org.dom4j.io.SAXReader;
47  import org.opennms.protocols.snmp.SnmpAgentSession;
48  import org.opennms.protocols.snmp.SnmpInt32;
49  import org.opennms.protocols.snmp.SnmpOctetString;
50  import org.opennms.protocols.snmp.SnmpVarBind;
51  
52  import com.mccrory.scott.base.ConcreteFilenameFilter;
53  import com.mccrory.scott.base.Dom4jHelper;
54  import com.mccrory.scott.base.ExecRunner;
55  import fr.dyade.jdring.AlarmEntry;
56  import fr.dyade.jdring.AlarmListener;
57  import fr.dyade.jdring.AlarmManager;
58  import fr.dyade.jdring.PastDateException;
59  
60  /**
61   * <P>Responsible for obtaining system statistics and storing them in a data file.
62   * This is typically done using our main() entry point or manually as such:
63   * <pre>
64   * try {
65   *     StatsCollector sc = new StatsCollector(args);
66   *     sc.scheduleRuns();
67   * }
68   * catch (Exception e) {
69   *     e.printStackTrace();
70   *     System.exit(1);
71   * }
72   * </pre></P>
73   *
74   * @author <a href="mailto:smccrory@users.sourceforge.net">Scott McCrory</a>.
75   * @version CVS $Id: StatsCollector.java,v 1.30 2002/08/04 22:04:53 smccrory Exp $
76   */
77  public class StatsCollector implements AlarmListener, OidHandler {
78  
79      /** The name of this class to log context without introspection **/
80      private static final String CLASS_NAME = "StatsCollector";
81  
82      /** The version of this class (filled in by CVS) **/
83      private static final String VERSION = "CVS $Revision: 1.30 $";
84  
85      /** Default preferences directory **/
86      private static final String DEFAULT_PREFS_DIR = "conf/";
87  
88      /** Default run mode **/
89      private static final String DEFAULT_RUN_MODE = "once";
90  
91      /** Default global preferences filename **/
92      private static final String DEFAULT_GLOBAL_PREFS_FILENAME = "global.xml";
93  
94      /** Default output data filename **/
95      private static final String DEFAULT_DATA_FILENAME = "spumoni.dat";
96  
97      /** Default log4j configuration filename **/
98      private static final String DEFAULT_LOG4J_CONFIG_FILENAME = "log4j.properties";
99  
100     /** Default module list **/
101     private static final String DEFAULT_MODULE_LIST = "linux";
102 
103     /** Default run interval in minutes **/
104     private static final String PREFS_DIR_CMD_SWITCH = "--prefsdir";
105 
106     /** Output data file header message **/
107     private static final String STATS_HEADER_MESSAGE =
108         "This is the Spumoni stats data file";
109 
110     /** Key for last run date string in data file **/
111     private static final String LAST_RUN_DATE_STRING_KEY = "lastRunDateString";
112 
113     /** End of line terminator (system dependant) **/
114     private static final String CRLF =
115         System.getProperties().getProperty("line.separator");
116 
117     /** Who wrote Spumoni **/
118     private static final String AUTHOR =
119         "Scott McCrory and the Spumoni development team";
120 
121     /** Contact info for author **/
122     private static final String CONTACT_INFO = "smccrory@users.sourceforge.net";
123 
124     /** Some more program info for the help screen **/
125     private static final String SPUMONI_INFO =
126         "Distributed as part of the Spumoni project - http://spumoni.sourceforge.net";
127 
128     /** Some more program info for the help screen **/
129     private static final String CONFIG_INFO =
130         "If you haven't done so, you need to configure Spumoni before running this!";
131 
132     /** Header string for help screen **/
133     private static final String HEADER =
134         "StatsCollector.java, " + VERSION + ", part of the Spumoni project";
135 
136     /** The usage message string built up from the other constants **/
137     private static final String USAGE =
138         "Obtains statistics from the host system and outputs them for easy lookup."
139             + CRLF
140             + "Written by "
141             + AUTHOR
142             + ", "
143             + CONTACT_INFO
144             + CRLF
145             + SPUMONI_INFO
146             + CRLF
147             + CONFIG_INFO
148             + CRLF
149             + "usage: java com.mccrory.scott.spumoni.StatsCollector [options]"
150             + CRLF
151             + "  "
152             + PREFS_DIR_CMD_SWITCH
153             + "=dir       : defaults to `conf/'"
154             + CRLF;
155 
156     /** Where we hold arguments passed to us from the command line **/
157     private HashMap argsMap = new HashMap();
158 
159     /** Where we hold the stats program XML document objects prior to parsing **/
160     private HashMap statsProgDocMap = new HashMap();
161 
162     /** Where we hold the StatsProgram objects **/
163     private StatsProgramList statsProgramList = new StatsProgramList();
164 
165     /** The final preferences directory after being modified by command-line args **/
166     private String prefsDir = new String();
167 
168     /** The final run interval; defaults to 5 **/
169     private int interval = 5;
170 
171     /** The final run mode after being modified by global.xml settings **/
172     private boolean runMode = false;
173 
174     /** A Vector of modules we're allowed to run **/
175     private Vector moduleList = new Vector();
176 
177     /** Whether SNMP is enabled at all or not **/
178     private boolean snmpReceiveEnabled = true;
179 
180     /** What version of SNMP to use if enabled **/
181     private int snmpReceiveVersion = 2;
182 
183     /** What SNMP community string to receive if enabled **/
184     private String snmpReceiveCommunityString = "public";
185 
186     /** What port to run the SNMP listener on if enabled **/
187     private int snmpReceivePort = 161;
188 
189     /** The final run mode after being modified by global.xml settings **/
190     private boolean snmpTrapEnabled = true;
191 
192     /** What version of SNMP to use for traps if enabled **/
193     private int snmpTrapVersion = 2;
194 
195     /** What SNMP target hostname to send traps to if enabled **/
196     private String snmpTrapTargetIp = "localhost";
197 
198     /** What SNMP community name to use for traps if enabled **/
199     private String snmpTrapCommunityString = "public";
200 
201     /** What port to send SNMP traps to if enabled **/
202     private int snmpTrapTargetPort = 162;
203 
204     /** Our log4j object.  I think "log" is more intuitive than "cat"! **/
205     private static Category log;
206 
207     /**
208      * StatsCollector constructor.
209      * We do the following things because they have to only be performed once
210      * and are tightly coupled to the creation of the object.
211      *
212      * @param args an array of command-line arguments
213      * @throws ExceptionInInitializerError thrown if a problem occurs
214      */
215     public StatsCollector(java.lang.String[] args)
216         throws ExceptionInInitializerError {
217 
218         super();
219 
220         final String METHOD_NAME = "StatsCollector(S)";
221         final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
222 
223         try {
224 
225             // Tell the User that we're Spumoni.  We use System.out instead of
226             // log() so that the user knows what failed if the log creation goes kaput.
227             System.out.println(
228                 "--------------------------------------------------------------------------");
229             System.out.println(HEADER);
230             System.out.println(
231                 "--------------------------------------------------------------------------");
232 
233             // Parse args into the form of a HashMap for quick lookup
234             boolean parseError = parseArgs(args);
235             if (parseError) {
236                 System.out.println(USAGE);
237                 System.exit(1);
238             }
239 
240             // Set up log4j using the referenced config file
241             log = Category.getInstance(CLASS_NAME);
242             PropertyConfigurator.configure(prefsDir + "/" + DEFAULT_LOG4J_CONFIG_FILENAME);
243 
244             // Parse the preferences files
245             parsePrefsXmlFiles();
246 
247         }
248         catch (Exception e) {
249             e.printStackTrace();
250             throw new ExceptionInInitializerError("Couldn't initialize: " + e.getMessage());
251         }
252 
253     }
254 
255     /**
256      * We override the <code>clone</code> method here to prevent cloning of our class.
257      *
258      * @throws CloneNotSupportedException To indicate cloning is not allowed
259      * @return Nothing ever really returned since we throw a CloneNotSupportedException
260      **/
261     public final Object clone() throws CloneNotSupportedException {
262 
263         throw new CloneNotSupportedException();
264 
265     }
266 
267     /**
268      * Collect all of the stats and write them to the data file.
269      * This is typically performed by our main() method, either once or
270      * periodically as specified by global.xml.  See main() for usage.
271      */
272     public synchronized void collectStats() {
273 
274         // Set up our log source using log4j's nested diagnostic context
275         final String METHOD_NAME = "collectStats()";
276         final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
277         NDC.push(LOG_SOURCE);
278         log.debug("-------------------------------------");
279 
280         // Unset our collected stats to keep from retaining the old values
281         // when the current run doesn't get new results (i.e. let the old
282         // results fall out like they should).  THIS MAY BE BEHAVIOR WHICH
283         // SHOULD BE LEFT UP TO THE USER AS DEFINED IN EACH STATS PROGRAM(?)
284         //statsProgramList.unsetValues();
285 
286         // Step through all of the stats programs
287         for (int i = 0; i < statsProgramList.size(); i++) {
288 
289             // Get the current StatsProgram and its attributes, including
290             // its Vector of StatsValue objects
291             StatsProgram sProg = (StatsProgram) statsProgramList.get(i);
292             String program = sProg.getProgram();
293             String regexp = sProg.getRegexp();
294             int maxRunTime = sProg.getMaxRunTime();
295             Vector requiredModules = sProg.getRequiredModules();
296 
297             ///////////////////////////////////////////////////////////////////////////
298             // OK, we're now ready to run the program and obtain the results
299             // (provided the required modules are in the modules list).
300             if (moduleList.containsAll(requiredModules)) {
301 
302                 // We'll be modifying this...
303                 StatsValueList statsValueList = sProg.getStatsValueList();
304 
305                 // Execute the program and grab the results
306                 String out = "";
307                 String err = "";
308                 try {
309 
310                     ExecRunner er = new ExecRunner();
311                     if (maxRunTime > 0) {
312                         er.setMaxRunTimeSecs(maxRunTime);
313                     }
314 
315                     log.info("Running '" + program + "' for max of " + maxRunTime + " seconds...");
316 
317                     er.exec(program);
318                     if (!er.getMaxRunTimeExceeded()) {
319                         out = er.getOutString();
320                         err = er.getErrString();
321                     }
322                     else {
323                         log.error("Maximum run time exceeded!");
324                         continue;
325                     }
326 
327                 }
328                 catch (Exception e) {
329                     log.error("Error executing stats program " + program + ": " + e.getMessage());
330                     continue;
331                 }
332 
333                 /////////////////////////////////////////////////////////////
334                 // Parse the results using the specified regular expression
335                 MatchResult mr = matchResult(regexp, out);
336 
337                 // Retrieve the number of matched groups. A group corresponds to a
338                 // parenthesized set in a pattern, or in other words, each one of
339                 // the values we're extracting from the stats program's output.
340                 if (mr != null) {
341                     int valuesMatched = mr.groups();
342                     for (int group = 1; group < valuesMatched; group++) {
343 
344                         // Save the data extracted
345                         StatsValue sValue = (StatsValue) statsValueList.get(group - 1);
346                         String valueString = mr.group(group);
347                         sValue.setValue(valueString);
348 
349                         // Check to see if the min or max values were exceeded,
350                         // and if so, send an SNMP Trap
351                         try {
352 
353                             String message = "";
354                             int min = sValue.getSnmpTrapMin();
355                             int max = sValue.getSnmpTrapMax();
356                             boolean sendIt = false;
357                             int value = Integer.parseInt(valueString);
358                             if (min != -2147483648 && value < min) {
359                                 message = "Collected value " + value + " was below threshold of " + min;
360                                 sendIt = true;
361                             }
362                             if (max != 2147483647 && value > max) {
363                                 message = "Collected value " + value + " was above threshold of " + max;
364                                 sendIt = true;
365                             }
366                             if (sendIt) {
367                                 sendSnmpTrap(sValue.getSnmpOid(), message);
368                             }
369                         }
370                         catch (NumberFormatException e) {
371                             // Something wrong happened while parsing
372                             // the value string (it must not be a number)
373                             // So we silently ignore it and carry on
374                         }
375                     }
376                 }
377                 else {
378                     log.warn("No matches with regexp '" + regexp + "'");
379                 }
380 
381             }
382 
383         }
384 
385         // Report the information collected to the log
386         log.info(getCollectedStats());
387 
388         // Pop our nested diagnostic context off the stack
389         NDC.pop();
390 
391     }
392 
393     /**
394      * Returns an object with only the COLLECTED statistics.
395      * This is done by returning a StatsProgramList object
396      * which in turn contains StatsProgram (containing StatsValue) objects.
397      *
398      * @return a StatsProgramList object containing the collected statistics.
399      */
400     public StatsProgramList getCollectedStats() {
401 
402         // Our Vector of COLLECTED StatsProgram objects
403         StatsProgramList collected = new StatsProgramList();
404 
405         // Iterate through every stats program
406         for (int i = 0; i < statsProgramList.size(); i++) {
407 
408             StatsProgram sProg = (StatsProgram) statsProgramList.get(i);
409             StatsValueList statsValueList = sProg.getStatsValueList();
410             boolean isSet = false;
411 
412             // Go through each StatsValue and see if it is in a "set" state
413             for (int j = 0; j < statsValueList.size(); j++) {
414                 StatsValue sValue = (StatsValue) statsValueList.get(j);
415                 if (!sValue.isUnset()) {
416                     isSet = true;
417                     break;
418                 }
419             }
420 
421             // If this program's values were set, then add it to the
422             // Vector that we'll be returning to our caller.
423             if (isSet) {
424                 collected.add(sProg);
425             }
426 
427         }
428 
429         return collected;
430     }
431 
432     /**
433     * Invoked when a JDring alarm is triggered.
434     *
435     * @param entry The JDring AlarmEntry which has been triggered.
436     */
437     public void handleAlarm(AlarmEntry entry) {
438 
439         collectStats();
440 
441     }
442 
443     /**
444      * Starts the Spumoni stats collection according to the prefs files' specs.
445      *
446      * @param args an array of command-line arguments
447      */
448     public static void main(java.lang.String[] args) {
449 
450         try {
451 
452             StatsCollector sc = new StatsCollector(args);
453             sc.scheduleRuns();
454 
455         }
456         catch (Exception e) {
457             e.printStackTrace();
458             System.exit(1);
459         }
460 
461     }
462 
463     /**
464      * Performs regexp pattern matching and returns results.
465      * Code was taken from Apache's ORO examples; please see
466      * http://jakarta.apache.org/oro/index.html for more info.
467      *
468      * @param patternString The regexp pattern string
469      * @param inputString The string to throw at the regexp pattern
470      * @return The regexp match result
471      */
472     private static MatchResult matchResult(
473         String patternString,
474         String inputString) {
475 
476         int groups = 0;
477         PatternMatcher matcher = null;
478         PatternCompiler compiler = null;
479         Pattern pattern = null;
480         PatternMatcherInput input = null;
481         MatchResult result = null;
482 
483         // Must have at least two arguments, else exit.
484         if (patternString == null
485             || patternString.length() < 1
486             || inputString == null
487             || inputString.length() < 1) {
488             return null;
489         }
490 
491         // Create Perl5Compiler and Perl5Matcher instances.
492         compiler = new Perl5Compiler();
493         matcher = new Perl5Matcher();
494 
495         // Attempt to compile the pattern.  If the pattern is not valid,
496         // report the error and exit.
497         try {
498             pattern = compiler.compile(patternString);
499         }
500         catch (MalformedPatternException e) {
501             System.out.println("Bad pattern.");
502             System.out.println(e.getMessage());
503             return null;
504         }
505 
506         // Create a PatternMatcherInput instance to keep track of the position
507         // where the last match finished, so that the next match search will
508         // start from there.  You always create a PatternMatcherInput instance
509         // when you want to search a string for all of the matches it contains,
510         // and not just the first one.
511         input = new PatternMatcherInput(inputString);
512 
513         // Loop until there are no more matches left.
514         if (matcher.contains(input, pattern)) {
515             // Since we're still in the loop, fetch match that was found.
516             result = matcher.getMatch();
517             return result;
518         }
519         else {
520             return null;
521         }
522 
523     }
524 
525     /**
526      * Parses the command-line args into our static HashMap called argsMap.
527      *
528      * @param args an array of command-line arguments
529      * @return true is there was a problem parsing the args
530      * @throws IllegalArgumentException thrown if a problem occurs
531      */
532     private boolean parseArgs(java.lang.String[] args)
533         throws IllegalArgumentException {
534 
535         // Set up our log source using log4j's nested diagnostic context
536         final String METHOD_NAME = "parseArgs(S[])";
537         final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
538         NDC.push(LOG_SOURCE);
539 
540         if (args == null || args.length < 1) {
541             System.out.println("No args detected - running with defaults");
542         }
543         else {
544             for (int i = 0; i < args.length; i++) {
545 
546                 // Do we hear cries for help?
547                 String arg = args[i];
548                 if (arg == null
549                     || arg.length() < 1
550                     || arg.equals("--help")
551                     || arg.equals("--h")
552                     || arg.equals("help")
553                     || arg.equals("--?")
554                     || arg.equals("-?")
555                     || arg.equals("?")
556                     || arg.equals("help")
557                     || arg.equals("--version")
558                     || arg.equals("-version")
559                     || arg.equals("-v")) {
560                     NDC.pop();
561                     return true;
562                 }
563 
564                 // All of the arguments should either be switches or key/value pairs
565                 if (arg.indexOf("=") > 0) {
566 
567                     // One or more "=" was found, so let's try to parse the key/value pair
568                     Vector pair = new Vector();
569                     StringTokenizer st = new StringTokenizer(arg, "=");
570                     while (st.hasMoreTokens()) {
571                         pair.add(st.nextToken());
572                     }
573 
574                     // There should only ever be one "=", which is to say
575                     // two strings: the key and the value.  If there's more
576                     // than one "=" then return null to indicate a problem.
577                     if (pair.size() == 2) {
578                         argsMap.put((String) pair.get(0), (String) pair.get(1));
579                     }
580                     else {
581                         NDC.pop();
582                         return true;
583                     }
584 
585                 }
586                 else {
587                     // It's just a switch - store it on the HashMap with a null value
588                     argsMap.put(arg, null);
589                 }
590             }
591 
592             /////////////////////////////////////////////////////////////////////////////
593             // All done parsing, now set the object's values based on what we found
594 
595             // Grab our default interval if it wasn't specified on the command line
596             if (argsMap.containsKey(PREFS_DIR_CMD_SWITCH)) {
597                 prefsDir = (String) argsMap.get(PREFS_DIR_CMD_SWITCH);
598             }
599             else {
600                 prefsDir = DEFAULT_PREFS_DIR;
601                 System.out.println(
602                     "No \"" + PREFS_DIR_CMD_SWITCH + "\", running with defaults");
603             }
604         }
605 
606         NDC.pop();
607         return false;
608 
609     }
610 
611     /**
612      * Parses a module list and returns them as individual elements in a Vector.
613      * We assume that the comma (",") is the delimiter
614      *
615      * @param moduleList A list of modules separated by commas
616      * @return The list of modules parsed from the comma-delimited string
617      */
618     private static Vector parseModuleList(String moduleList) {
619 
620         if (moduleList == null || moduleList.length() < 1) {
621             return null;
622         }
623 
624         Vector tempModuleList = new Vector();
625 
626         StringTokenizer st = new StringTokenizer(moduleList, ",");
627         while (st.hasMoreTokens()) {
628             tempModuleList.add(st.nextElement());
629         }
630 
631         return tempModuleList;
632 
633     }
634 
635     /**
636      * Parses all of the XML preferences files.
637      * Once parsed, we process all of global.xml's contents here but don't
638      * actually process what is in the stats program XML files until each time
639      * the schedule kicks off (or once right after parsing if specified).
640      *
641      * @throws IOException thrown if a problem occurs
642      * @throws DocumentException thrown if a parsing problem occurs
643      */
644     private void parsePrefsXmlFiles() throws IOException, DocumentException {
645 
646         // Set up our log source using log4j's nested diagnostic context
647         final String METHOD_NAME = "parsePrefsXmlFiles()";
648         final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
649         NDC.push(LOG_SOURCE);
650         log.debug("Begins");
651 
652         // Get the user's prefs directory or else the default one
653         String prefsDirString;
654         if (argsMap.containsKey(PREFS_DIR_CMD_SWITCH)) {
655             prefsDirString = (String) argsMap.get(PREFS_DIR_CMD_SWITCH);
656         }
657         else {
658             prefsDirString = DEFAULT_PREFS_DIR;
659         }
660 
661         // Create a handle to the directory and make sure it is legit
662         File prefsDir = new File(prefsDirString);
663         if (!prefsDir.isDirectory()) {
664             NDC.pop();
665             throw new IOException("'" + prefsDirString + "' is not a valid directory!");
666         }
667 
668         // Get a list of XML files in that directory
669         ConcreteFilenameFilter xmlFilter = new ConcreteFilenameFilter(".xml");
670         File fileList[] = prefsDir.listFiles(xmlFilter);
671         if (fileList == null || fileList.length < 1) {
672             NDC.pop();
673             throw new IOException("'" + prefsDirString + "' has no .xml files in it!");
674         }
675 
676         ///////////////////////////////////////////////////////////////////////////////
677         // Now iterate through each XML file and parse it into a separate Document, then
678         // attach each to a master HashMap.  This makes it easier to process each's
679         // content later.
680         for (int i = 0; i < fileList.length; i++) {
681 
682             String fileName = fileList[i].getName();
683             log.debug("Parsing: " + fileName);
684 
685             SAXReader reader = new SAXReader();
686             Document prefsDocument = reader.read(fileList[i]);
687             Element root = prefsDocument.getRootElement();
688 
689             // If we're working with the global.xml file, then we process it here,
690             // otherwise it contains stats program information (see 'else' below)
691             if (fileName.equalsIgnoreCase(DEFAULT_GLOBAL_PREFS_FILENAME)) {
692 
693                 // It is the global.xml file, so process it.
694                 processGlobalXmlFile(root);
695 
696             }
697             else {
698 
699                 // It wasn't the global.xml file, which means it contains
700                 // stats program information, so process the data and store
701                 // it into a StatsProgram object then accumulate it onto our
702                 // Vector for later handling.
703                 processStatsProgramFile(root, fileName);
704 
705             }
706 
707         }
708 
709         // Check for OID conflicts.  This is critical because we're going to
710         // be using OID as a primary key to report the collected values via SNMP
711         StatsProgramList oidConflicts = statsProgramList.getOidConflicts();
712 /*      if (oidConflicts.size() > 0) {
713             log.error(
714                 "OID Conflicts detected!  Please correct the problems below before running Spumoni again:");
715             for (int col = 0; col < oidConflicts.size(); col++) {
716                 log.error(oidConflicts.get(col));
717             }
718             NDC.pop();
719             throw new IOException("OID Conflicts detected!");
720         }
721 */
722         // Pop our nested diagnostic context off the stack
723         log.debug("Ends");
724         NDC.pop();
725 
726     }
727 
728     /**
729      * Processes the parsed global.xml document and saves its data into
730      * our class variables.
731      * @param root org.dom4j.Element - The root element of our global.xml document
732      */
733     private void processGlobalXmlFile(Element root) {
734 
735         for (Iterator rootAttributesIterator = root.attributeIterator();
736             rootAttributesIterator.hasNext();
737             ) {
738 
739             Attribute attribute = (Attribute) rootAttributesIterator.next();
740             String attributeName = attribute.getName();
741 
742             //////// Check for the fundamental elements ////////
743             if (attributeName.equals("runMode")) {
744                 String runModeString = (String) attribute.getData();
745                 if (!runModeString.equalsIgnoreCase("once")) {
746                     runMode = true;
747                 }
748             }
749             if (attributeName.equals("interval")) {
750                 interval = Integer.parseInt((String) attribute.getData());
751             }
752             if (attributeName.equals("moduleList")) {
753                 String moduleListString = (String) attribute.getData();
754                 moduleList = parseModuleList(moduleListString);
755             }
756 
757             //////// Check for SNMP Receive elements ////////
758             if (attributeName.equals("snmpReceiveEnabled")) {
759                 String s = (String) attribute.getData();
760                 if (s.equalsIgnoreCase("true")) {
761                     snmpReceiveEnabled = true;
762                 }
763                 else {
764                     snmpReceiveEnabled = false;
765                 }
766             }
767             if (attributeName.equals("snmpReceiveVersion")) {
768                 snmpReceiveVersion = Integer.parseInt((String) attribute.getData());
769             }
770             if (attributeName.equals("snmpReceiveCommunityString")) {
771                 snmpReceiveCommunityString = (String) attribute.getData();
772             }
773             if (attributeName.equals("snmpReceivePort")) {
774                 snmpReceivePort = Integer.parseInt((String) attribute.getData());
775             }
776 
777             //////// Check for SNMP Trap elements ////////
778             if (attributeName.equals("snmpTrapEnabled")) {
779                 String s = (String) attribute.getData();
780                 if (s.equalsIgnoreCase("true")) {
781                     snmpTrapEnabled = true;
782                 }
783                 else {
784                     snmpTrapEnabled = false;
785                 }
786             }
787             if (attributeName.equals("snmpTrapVersion")) {
788                 snmpTrapVersion = Integer.parseInt((String) attribute.getData());
789             }
790             if (attributeName.equals("snmpTrapTargetIp")) {
791                 snmpTrapTargetIp = (String) attribute.getData();
792             }
793             if (attributeName.equals("snmpTrapCommunityString")) {
794                 snmpTrapCommunityString = (String) attribute.getData();
795             }
796             if (attributeName.equals("snmpTrapTargetPort")) {
797                 snmpTrapTargetPort = Integer.parseInt((String) attribute.getData());
798             }
799 
800         }
801 
802     }
803 
804     /**
805      * Processes a single parsed stats program document and stores the data in
806      * the form of a StatsProgram objects on our class-wide Vector.
807      * @param root org.dom4j.Element - The root element of our document
808      * @param filename The filename of the file we're parsing
809      */
810     private void processStatsProgramFile(Element root, String filename) {
811 
812         // Iterate through all stats programs defined in this file
813         for (Iterator statsProgIterator = root.elementIterator();
814             statsProgIterator.hasNext();
815             ) {
816 
817             Element statsProgElement = (Element) statsProgIterator.next();
818             HashMap statsProgAttribs = Dom4jHelper.getAttributes(statsProgElement);
819 
820             // Build our StatsProgram with what we know so far
821             StatsProgram program = new StatsProgram();
822             program.setXmlFilename(filename);
823             program.setProgram((String) statsProgAttribs.get("program"));
824             program.setRegexp((String) statsProgAttribs.get("regexp"));
825             String requiredModulesString = (String) statsProgAttribs.get("requiredModules");
826             program.setRequiredModules(parseModuleList(requiredModulesString));
827             program.setMaxRunTime(
828                 Integer.parseInt((String) statsProgAttribs.get("maxRunTime")));
829 
830             // Iterate through all the child elements, which will be
831             // the value names and attributes.
832             for (Iterator valueIterator = statsProgElement.elementIterator();
833                 valueIterator.hasNext();
834                 ) {
835 
836                 Element valueElement = (Element) valueIterator.next();
837                 String valueName = (String) valueElement.getData();
838                 HashMap valueAttribs = Dom4jHelper.getAttributes(valueElement);
839 
840                 // Build our StatsValue with the details
841                 StatsValue value = new StatsValue();
842                 value.setValueName(valueName);
843 
844                 // Get the oid
845                 String oid = (String) valueAttribs.get("snmpOid");
846                 if (oid.startsWith(".")) {
847                     oid = oid.substring(1);
848                 }
849                 value.setSnmpOid(oid);
850 
851                 String snmpTrapMin = (String) valueAttribs.get("snmpTrapMin");
852                 if (snmpTrapMin != null && snmpTrapMin.length() > 0) {
853                     value.setSnmpTrapMin(Integer.parseInt(snmpTrapMin));
854                 }
855                 else {
856                     value.setSnmpTrapMin(0);
857                 }
858 
859                 String snmpTrapMax = (String) valueAttribs.get("snmpTrapMax");
860                 if (snmpTrapMax != null && snmpTrapMax.length() > 0) {
861                     value.setSnmpTrapMax(Integer.parseInt(snmpTrapMax));
862                 }
863                 else {
864                     value.setSnmpTrapMax(0);
865                 }
866 
867                 // Add the completed StatsValue object to our StatsProgram
868                 program.addStatsValue(value);
869 
870             }
871 
872             // Add the completed StatsProgram object to our class-wide Vector
873             statsProgramList.add(program);
874 
875         }
876 
877     }
878 
879     /**
880      * We override the <code>readObject</code> method here to prevent
881      * deserialization of our class for security reasons.
882      *
883      * @param in java.io.ObjectInputStream
884      * @throws IOException thrown if a problem occurs
885      **/
886     private final void readObject(ObjectInputStream in) throws IOException {
887 
888         throw new IOException("Object cannot be deserialized");
889 
890     }
891 
892     /**
893      * Run once or schedule ourself on a new alarm manager to run periodically.
894      */
895     public void scheduleRuns() {
896 
897         // Set up our log source using log4j's nested diagnostic context
898         final String METHOD_NAME = "scheduleRuns()";
899         final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
900 
901         // Create a new alarm manager that will hold our "cronned" events
902         AlarmManager mgr = new AlarmManager();
903 
904         if (runMode && interval > 0) {
905 
906             // Set up alarms to occur every INTERVAL minutes
907             try {
908 
909                 for (int i = 0; i < 60; i += interval) {
910                     mgr.addAlarm(i, -1, -1, -1, -1, -1, this);
911                 }
912 
913             }
914             catch (PastDateException e) {
915 
916                 NDC.push(LOG_SOURCE);
917                 log.fatal("Couldn't schedule ourself: " + e.getMessage());
918                 e.printStackTrace();
919                 NDC.pop();
920 
921                 return;
922             }
923 
924             // Spawn the SNMP agent daemon and register our known OIDs
925             // if the user wants this )native SNMP agent) functionality.
926             if (snmpReceiveEnabled
927                 && snmpReceiveCommunityString != null
928                 && snmpReceiveCommunityString.length() > 0) {
929 
930                 try {
931 
932                     NDC.push(LOG_SOURCE);
933 
934                     // Initialize the daemon and register our OIDs
935                     SnmpD snmpd = new SnmpD();
936                     Vector oids = statsProgramList.getOids();
937                     for (int i = 0; i < oids.size(); i++) {
938                         String thisOid = (String) oids.get(i);
939                         snmpd.registerOidHandler(thisOid, this);
940                     }
941 
942                     SnmpAgentSession daemonSession = new SnmpAgentSession(snmpd, snmpReceivePort);
943                     log.debug("SNMP Agent Started");
944 
945                     // Wait for daemon to complete
946                     synchronized (daemonSession) {
947                         //daemonSession.wait();
948                     }
949 
950                     //User wants to exit...
951                     //log.debug("SNMP Agent Exiting");
952                     //daemonSession.close();
953 
954                     NDC.pop();
955 
956                 }
957                 catch (Exception e) {
958 
959                     NDC.push(LOG_SOURCE);
960                     log.fatal("Problem with SNMP daemon: " + e.getMessage());
961                     e.printStackTrace();
962                     NDC.pop();
963 
964                     System.exit(0);
965                 }
966 
967             }
968 
969             // Give a brief report to the user to let them know what's coming
970             NDC.push(LOG_SOURCE);
971             log.info("Collecting stats every " + interval + " min(s)");
972             log.debug("-------------------------------------");
973             NDC.pop();
974 
975         }
976         else {
977 
978             // Run the stats collector once (useful for diagnostics)
979             collectStats();
980             System.exit(0);
981         }
982 
983     }
984 
985     /**
986      * Sends an SNMP trap if all of the required data and conditions are OK.
987      *
988      * @param oid       The SNMP OID of the triggering value
989      * @param message   A message about the trap
990      */
991     public void sendSnmpTrap(String oid, String message) {
992 
993         final String METHOD_NAME = "sendSnmpTrap()";
994         final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
995         NDC.push(LOG_SOURCE);
996 
997         if (// Check parameters
998         oid != null
999             && oid.length() > 0
1000            && message != null
1001            && message.length() > 0
1002            && // Check object conditions
1003        snmpTrapEnabled == true
1004            && snmpTrapTargetIp != null
1005            && snmpTrapTargetIp.length() > 0
1006            && snmpTrapCommunityString != null
1007            && snmpTrapCommunityString.length() > 0) {
1008
1009            try {
1010
1011                SnmpTrapAgent sa = new SnmpTrapAgent(snmpTrapTargetIp, snmpTrapTargetPort, oid);
1012
1013                if (snmpTrapVersion == 1) {
1014                    sa.sendV1Trap(message);
1015                }
1016                else {
1017                    sa.sendV2Trap(message);
1018                }
1019                sa.close();
1020
1021                log.info(
1022                    "Trap sent to '"
1023                        + snmpTrapTargetIp
1024                        + ":"
1025                        + snmpTrapTargetPort
1026                        + "' "
1027                        + "for OID '"
1028                        + oid
1029                        + "' "
1030                        + "with message '"
1031                        + message
1032                        + "' ");
1033
1034            }
1035            catch (Exception e) {
1036                log.warn(
1037                    "Trouble sending trap to '"
1038                        + snmpTrapTargetIp
1039                        + ":"
1040                        + snmpTrapTargetPort
1041                        + "' "
1042                        + "for OID '"
1043                        + oid
1044                        + "' "
1045                        + "with message '"
1046                        + message
1047                        + "': "
1048                        + e.getMessage());
1049            }
1050
1051        }
1052
1053        // Pop our nested diagnostic context off the stack
1054        NDC.pop();
1055
1056    }
1057
1058    /**
1059     * We override the <code>writeObject</code> method here to prevent
1060     * serialization of our class for security reasons.
1061     *
1062     * @param out java.io.ObjectOutputStream
1063     * @throws IOException thrown if a problem occurs
1064     **/
1065    private final void writeObject(ObjectOutputStream out) throws IOException {
1066
1067        throw new IOException("Object cannot be serialized");
1068
1069    }
1070
1071    /**
1072    * Handles SNMP GET PDUs.
1073    *
1074    * @param varBind The request SnmpVarBind
1075    * @return The response SnmpVarBind.
1076    *
1077    */
1078    public SnmpVarBind handleGet(SnmpVarBind varBind) {
1079
1080        // Set up our log source using log4j's nested diagnostic context
1081        final String METHOD_NAME = "handleGet()";
1082        final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1083        NDC.push(LOG_SOURCE);
1084
1085        // Get the oid
1086        String oid = varBind.getName().toString();
1087        if (oid.startsWith(".")) {
1088            oid = oid.substring(1);
1089        }
1090
1091        // Start assembling the response value
1092        String value = null;
1093        try {
1094            value = statsProgramList.getValueByOid(oid);
1095        }
1096        catch (NoSuchElementException e) {
1097            log.info(oid + " NOT FOUND!");
1098            value = oid + " NOT FOUND!";
1099        }
1100
1101        // If we can turn this into an integer then do so, otherwise use a String
1102        try {
1103            SnmpInt32 si = new SnmpInt32(Integer.parseInt(value));
1104            varBind.setValue(si);
1105            log.debug("Setting oid " + oid + " with integer " + value);
1106        }
1107        catch (NumberFormatException e) {
1108            SnmpOctetString sos = new SnmpOctetString();
1109            sos.setString(value);
1110            varBind.setValue(sos);
1111            log.debug("Setting oid " + oid + " with String " + value);
1112        }
1113
1114        NDC.pop();
1115        
1116        return varBind;
1117
1118    }
1119
1120    /**
1121    * Handles SNMP GETBULK PDUs.
1122    *
1123    * @param varBind The request SnmpVarBind
1124    * @return The response SnmpVarBind.
1125    *
1126    */
1127    public SnmpVarBind handleGetbulk(SnmpVarBind varBind) {
1128
1129        // Set up our log source using log4j's nested diagnostic context
1130        final String METHOD_NAME = "handleGetbulk()";
1131        final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1132        NDC.push(LOG_SOURCE);
1133
1134        log.debug("GETBULK received");
1135
1136        NDC.pop();
1137        
1138        return varBind;
1139
1140    }
1141
1142    /**
1143    * Handles SNMP GETNEXT PDUs.
1144    *
1145    * @param varBind The request SnmpVarBind
1146    * @return The response SnmpVarBind.
1147    *
1148    */
1149    public SnmpVarBind handleGetnext(SnmpVarBind varBind) {
1150
1151        // Set up our log source using log4j's nested diagnostic context
1152        final String METHOD_NAME = "handleGetnext()";
1153        final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1154        NDC.push(LOG_SOURCE);
1155
1156        log.debug("GETNEXT received");
1157
1158        NDC.pop();
1159        
1160        return varBind;
1161
1162    }
1163
1164    /**
1165    * Handles SNMP INFORM PDUs.
1166    *
1167    * @param varBind The request SnmpVarBind
1168    * @return The response SnmpVarBind.
1169    *
1170    */
1171    public SnmpVarBind handleInform(SnmpVarBind varBind) {
1172
1173        // Set up our log source using log4j's nested diagnostic context
1174        final String METHOD_NAME = "handleInform()";
1175        final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1176        NDC.push(LOG_SOURCE);
1177
1178        log.debug("INFORM received");
1179
1180        NDC.pop();
1181        
1182        return varBind;
1183
1184    }
1185
1186    /**
1187    * Handles SNMP REPORT PDUs.
1188    *
1189    * @param varBind The request SnmpVarBind
1190    * @return The response SnmpVarBind.
1191    *
1192    */
1193    public SnmpVarBind handleReport(SnmpVarBind varBind) {
1194
1195        // Set up our log source using log4j's nested diagnostic context
1196        final String METHOD_NAME = "handleReport()";
1197        final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1198        NDC.push(LOG_SOURCE);
1199
1200        log.debug("REPORT received");
1201
1202        NDC.pop();
1203        
1204        return varBind;
1205
1206    }
1207
1208    /**
1209    * Handles SNMP RESPONSE PDUs.
1210    *
1211    * @param varBind The request SnmpVarBind
1212    * @return The response SnmpVarBind.
1213    *
1214    */
1215    public SnmpVarBind handleResponse(SnmpVarBind varBind) {
1216
1217        // Set up our log source using log4j's nested diagnostic context
1218        final String METHOD_NAME = "handleResponse()";
1219        final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1220        NDC.push(LOG_SOURCE);
1221
1222        log.debug("RESPONSE received");
1223
1224        NDC.pop();
1225        
1226        return varBind;
1227
1228    }
1229
1230    /**
1231    * Handles SNMP SET PDUs.
1232    *
1233    * @param varBind The request SnmpVarBind
1234    * @return The response SnmpVarBind.
1235    *
1236    */
1237    public SnmpVarBind handleSet(SnmpVarBind varBind) {
1238
1239        // Set up our log source using log4j's nested diagnostic context
1240        final String METHOD_NAME = "handleSet()";
1241        final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1242        NDC.push(LOG_SOURCE);
1243
1244        log.debug("SET received");
1245
1246        NDC.pop();
1247        
1248        return varBind;
1249
1250    }
1251
1252    /**
1253    * Handles SNMP TRAP PDUs.
1254    *
1255    * @param varBind The request SnmpVarBind
1256    * @return The response SnmpVarBind.
1257    *
1258    */
1259    public SnmpVarBind handleTrap(SnmpVarBind varBind) {
1260
1261        // Set up our log source using log4j's nested diagnostic context
1262        final String METHOD_NAME = "handleTrap()";
1263        final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1264        NDC.push(LOG_SOURCE);
1265
1266        log.debug("TRAP received");
1267
1268        NDC.pop();
1269        
1270        return varBind;
1271
1272    }
1273
1274    /**
1275    * Handles SNMP V2TRAP PDUs.
1276    *
1277    * @param varBind The request SnmpVarBind
1278    * @return The response SnmpVarBind.
1279    *
1280    */
1281    public SnmpVarBind handleV2Trap(SnmpVarBind varBind) {
1282
1283        // Set up our log source using log4j's nested diagnostic context
1284        final String METHOD_NAME = "handleV2Trap()";
1285        final String LOG_SOURCE = CLASS_NAME + "." + METHOD_NAME;
1286        NDC.push(LOG_SOURCE);
1287
1288        log.debug("V2TRAP received");
1289
1290        NDC.pop();
1291        
1292        return varBind;
1293
1294    }
1295
1296}