View Javadoc

1   /*
2    *  Copyright 2009-2011 Felix Roethenbacher
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   */
16  package ch.syabru.nagios;
17  
18  import java.io.IOException;
19  import java.io.InputStreamReader;
20  import java.io.PrintStream;
21  import java.io.Reader;
22  import java.math.BigDecimal;
23  import java.math.BigInteger;
24  import java.net.MalformedURLException;
25  import java.net.URL;
26  import java.rmi.ConnectException;
27  import java.util.HashMap;
28  import java.util.Set;
29  import java.util.Properties;
30  import java.util.regex.Matcher;
31  import java.util.regex.Pattern;
32  import java.util.regex.PatternSyntaxException;
33  
34  import javax.management.AttributeNotFoundException;
35  import javax.management.InstanceNotFoundException;
36  import javax.management.IntrospectionException;
37  import javax.management.MBeanException;
38  import javax.management.MBeanServerConnection;
39  import javax.management.ObjectInstance;
40  import javax.management.MalformedObjectNameException;
41  import javax.management.ObjectName;
42  import javax.management.ReflectionException;
43  import javax.management.openmbean.CompositeDataSupport;
44  import javax.management.openmbean.InvalidKeyException;
45  import javax.management.remote.JMXConnector;
46  import javax.management.remote.JMXConnectorFactory;
47  import javax.management.remote.JMXServiceURL;
48  
49  /**
50   * Nagios JMX plugin.
51   *
52   * @author Felix Roethenbacher
53   *
54   */
55  public class NagiosJmxPlugin {
56  
57      /**
58       * Nagios status codes and messages.
59       */
60      enum Status {
61          /**
62           * Status code NAGIOS OK.
63           */
64          OK (0, "JMX OK - "),
65          /**
66           * Status code NAGIOS WARNING.
67           */
68          WARNING (1, "JMX WARNING - "),
69          /**
70           * Status code NAGIOS CRITICAL.
71           */
72          CRITICAL (2, "JMX CRITICAL - "),
73          /**
74           * Status code NAGIOS UNKNOWN.
75           */
76          UNKNOWN (3, "JMX UNKNOWN - ");
77  
78          private int exitCode;
79          private String messagePrefix;
80  
81          /**
82           * C'tor.
83           * @param exitCode Exit code.
84           * @param messagePrefix Message prefix.
85           */
86          private Status(int exitCode, String messagePrefix) {
87              this.exitCode = exitCode;
88              this.messagePrefix = messagePrefix;
89          }
90  
91          public int getExitCode() {
92              return exitCode;
93          }
94  
95          public String getMessagePrefix() {
96              return messagePrefix;
97          }
98      }
99  
100     /**
101      * Unit enumeration.
102      */
103     enum Unit {
104         /**
105          * Unit bytes.
106          */
107         BYTES ("B"),
108         /**
109          * Unit kilobytes.
110          */
111         KILOBYTES ("KB"),
112         /**
113          * Unit megabytes.
114          */
115         MEGABYTES ("MB"),
116         /**
117          * Unit terabytes.
118          */
119         TERABYTES ("TB"),
120         /**
121          * Unit seconds.
122          */
123         SECONDS ("s"),
124         /**
125          * Unit microseconds.
126          */
127         MICROSECONDS ("us"),
128         /**
129          * Unit milliseconds.
130          */
131         MILLISECONDS ("ms"),
132         /**
133          * Unit counter.
134          */
135         COUNTER ("c");
136 
137         private String abbreviation;
138 
139         /**
140          * C'tor.
141          * @param abbreviation Abbreviation.
142          */
143         private Unit(String abbreviation) {
144             this.abbreviation = abbreviation;
145         }
146 
147         public String getAbbreviation() {
148             return abbreviation;
149         }
150 
151         /**
152          * Parse abbreviation and return matching unit.
153          * @param abbr Abbreviation.
154          * @return Matching unit, null if not found.
155          */
156         public static Unit parse(String abbr) {
157             for (Unit unit : Unit.values()) {
158                 if (unit.getAbbreviation().equals(abbr))
159                     return unit;
160             }
161             return null;
162         }
163     }
164 
165     /**
166      * Username system property.
167      */
168     public static final String PROP_USERNAME = "username";
169     /**
170      * Password system property.
171      */
172     public static final String PROP_PASSWORD = "password";
173     /**
174      * Object name system property.
175      */
176     public static final String PROP_OBJECT_NAME = "objectName";
177     /**
178      * Attribute name system property.
179      */
180     public static final String PROP_ATTRIBUTE_NAME = "attributeName";
181     /**
182      * Attribute key system property.
183      */
184     public static final String PROP_ATTRIBUTE_KEY = "attributeKey";
185     /**
186      * Service URL system property.
187      */
188     public static final String PROP_SERVICE_URL = "serviceUrl";
189     /**
190      * Threshold warning level system property.
191      * The number format of this property has to correspond to the type of
192      * the attribute object.
193      */
194     public static final String PROP_THRESHOLD_WARNING = "thresholdWarning";
195     /**
196      * Threshold critical level system property.
197      * The number format of this property has to correspond the type of
198      * the attribute object.
199      */
200     public static final String PROP_THRESHOLD_CRITICAL = "thresholdCritical";
201     /**
202      * Units system property.
203      */
204     public static final String PROP_UNITS = "units";
205     /**
206      * Operation to invoke on MBean.
207      */
208     public static final String PROP_OPERATION = "operation";
209     /**
210      * Verbose output.
211      */
212     public static final String PROP_VERBOSE = "verbose";
213     /**
214      * Help output.
215      */
216     public static final String PROP_HELP = "help";
217 
218     private HashMap<MBeanServerConnection, JMXConnector> connections =
219         new HashMap<MBeanServerConnection, JMXConnector>();
220 
221     /**
222      * Open a connection to a MBean server.
223      * @param serviceUrl Service URL,
224      *     e.g. service:jmx:rmi://HOST:PORT/jndi/rmi://HOST:PORT/jmxrmi
225      * @param username Username
226      * @param password Password
227      * @return MBeanServerConnection if succesfull.
228      * @throws IOException XX
229      */
230     public MBeanServerConnection openConnection(
231             JMXServiceURL serviceUrl, String username, String password)
232     throws IOException
233     {
234         JMXConnector connector;
235         HashMap<String, Object> environment = new HashMap<String, Object>();
236         // Add environment variable to check for dead connections.
237         environment.put("jmx.remote.x.client.connection.check.period", 5000);
238         if (username != null && password != null) {
239             environment = new HashMap<String, Object>();
240             environment.put(JMXConnector.CREDENTIALS,
241                     new String[] {username, password});
242             connector = JMXConnectorFactory.connect(serviceUrl, environment);
243         } else {
244             connector = JMXConnectorFactory.connect(serviceUrl, environment);
245         }
246         MBeanServerConnection connection = connector.getMBeanServerConnection();
247         connections.put(connection, connector);
248         return connection;
249     }
250 
251     /**
252      * Close JMX connection.
253      * @param connection Connection.
254      * @throws IOException XX.
255      */
256     public void closeConnection(MBeanServerConnection connection)
257     throws IOException
258     {
259         JMXConnector connector = connections.remove(connection);
260         if (connector != null)
261             connector.close();
262     }
263 
264     /**
265      * Get object name object.
266      * @param connection MBean server connection.
267      * @param objectName Object name string.
268      * @return Object name object.
269      * @throws InstanceNotFoundException If object not found.
270      * @throws MalformedObjectNameException If object name is malformed.
271      * @throws NagiosJmxPluginException If object name is not unqiue.
272      * @throws IOException In case of a communication error.
273      */
274     public ObjectName getObjectName(MBeanServerConnection connection,
275             String objectName)
276     throws InstanceNotFoundException, MalformedObjectNameException,
277             NagiosJmxPluginException, IOException
278     {
279         ObjectName objName = new ObjectName(objectName);
280         if (objName.isPropertyPattern() || objName.isDomainPattern()) {
281             Set<ObjectInstance> mBeans = connection.queryMBeans(objName, null);
282 
283             if (mBeans.size() == 0) {
284                 throw new InstanceNotFoundException();
285             } else if (mBeans.size() > 1) {
286                 throw new NagiosJmxPluginException(
287                         "Object name not unique: objectName pattern matches " +
288                         mBeans.size() + " MBeans.");
289             } else {
290                 objName = mBeans.iterator().next().getObjectName();
291             }
292         }
293         return objName;
294     }
295 
296     /**
297      * Query MBean object.
298      * @param connection MBean server connection.
299      * @param objectName Object name.
300      * @param attributeName Attribute name.
301      * @param attributeKey Attribute key.
302      * @return Value.
303      * @throws InstanceNotFoundException XX
304      * @throws IntrospectionException XX
305      * @throws ReflectionException XX
306      * @throws IOException XX
307      * @throws AttributeNotFoundException XX
308      * @throws MBeanException XX
309      * @throws MalformedObjectNameException XX
310      * @throws NagiosJmxPluginException XX
311      */
312     public Object query(MBeanServerConnection connection, String objectName,
313             String attributeName, String attributeKey)
314     throws InstanceNotFoundException, IntrospectionException,
315             ReflectionException, IOException, AttributeNotFoundException,
316             MBeanException, MalformedObjectNameException,
317             NagiosJmxPluginException
318     {
319         Object value = null;
320         ObjectName objName = getObjectName(connection, objectName);
321 
322         Object attribute = connection.getAttribute(objName, attributeName);
323         if (attribute instanceof CompositeDataSupport) {
324             CompositeDataSupport compositeAttr =
325                 (CompositeDataSupport) attribute;
326             value = compositeAttr.get(attributeKey);
327         } else {
328             value = attribute;
329         }
330         return value;
331     }
332 
333     /**
334      * Invoke an operation on MBean.
335      * @param connection MBean server connection.
336      * @param objectName Object name.
337      * @param operationName Operation name.
338      * @throws InstanceNotFoundException XX
339      * @throws IOException XX
340      * @throws MalformedObjectNameException XX
341      * @throws MBeanException XX
342      * @throws ReflectionException XX
343      * @throws NagiosJmxPluginException XX
344      */
345     public void invoke(MBeanServerConnection connection, String objectName,
346             String operationName)
347     throws InstanceNotFoundException, IOException,
348             MalformedObjectNameException, MBeanException, ReflectionException,
349             NagiosJmxPluginException
350     {
351         ObjectName objName = getObjectName(connection, objectName);
352         connection.invoke(objName, operationName, null, null);
353     }
354 
355     /**
356      * Get system properties and execute query.
357      * @param args Arguments as properties.
358      * @return Nagios exit code.
359      * @throws NagiosJmxPluginException XX
360      */
361     public int execute(Properties args) throws NagiosJmxPluginException {
362         String username = args.getProperty(PROP_USERNAME);
363         String password = args.getProperty(PROP_PASSWORD);
364         String objectName = args.getProperty(PROP_OBJECT_NAME);
365         String attributeName = args.getProperty(PROP_ATTRIBUTE_NAME);
366         String attributeKey = args.getProperty(PROP_ATTRIBUTE_KEY);
367         String serviceUrl = args.getProperty(PROP_SERVICE_URL);
368         String thresholdWarning = args.getProperty(PROP_THRESHOLD_WARNING);
369         String thresholdCritical = args.getProperty(PROP_THRESHOLD_CRITICAL);
370         String operation = args.getProperty(PROP_OPERATION);
371         String units = args.getProperty(PROP_UNITS);
372         String help = args.getProperty(PROP_HELP);
373 
374         PrintStream out = System.out;
375 
376         if (help != null) {
377             showHelp();
378             return Status.OK.getExitCode();
379         }
380 
381         if (objectName == null || attributeName == null || serviceUrl == null)
382         {
383             showUsage();
384             return Status.CRITICAL.getExitCode();
385         }
386 
387         Unit unit = null;
388         if (units != null && Unit.parse(units) == null) {
389             throw new NagiosJmxPluginException("Unknown unit [" + units + "]");
390         } else {
391             unit = Unit.parse(units);
392         }
393 
394         JMXServiceURL url = null;
395         try {
396             url = new JMXServiceURL(serviceUrl);
397         } catch (MalformedURLException e) {
398             throw new NagiosJmxPluginException("Malformed service URL [" +
399                     serviceUrl + "]", e);
400         }
401         // Connect to MBean server.
402         MBeanServerConnection connection = null;
403         Object value = null;
404         try {
405             try {
406                 connection = openConnection(url, username, password);
407             } catch (ConnectException ce) {
408                 throw new NagiosJmxPluginException(
409                         "Error opening RMI connection: " + ce.getMessage(), ce);
410             } catch (Exception e) {
411                 throw new NagiosJmxPluginException(
412                         "Error opening connection: " + e.getMessage(), e);
413             }
414             // Query attribute.
415             try {
416                 value = query(connection, objectName, attributeName,
417                         attributeKey);
418             } catch (MalformedObjectNameException e) {
419                 throw new NagiosJmxPluginException(
420                         "Malformed objectName [" + objectName + "]", e);
421             } catch (InstanceNotFoundException e) {
422                 throw new NagiosJmxPluginException(
423                         "objectName not found [" + objectName + "]", e);
424             } catch (AttributeNotFoundException e) {
425                 throw new NagiosJmxPluginException(
426                         "attributeName not found [" + attributeName + "]", e);
427             } catch (InvalidKeyException e) {
428                 throw new NagiosJmxPluginException(
429                         "attributeKey not found [" + attributeKey + "]", e);
430             } catch (Exception e) {
431                 throw new NagiosJmxPluginException(
432                         "Error querying server: " + e.getMessage(), e);
433             }
434             // Invoke operation if defined.
435             if (operation != null) {
436                 try {
437                     invoke(connection, objectName, operation);
438                 } catch (Exception e) {
439                     throw new NagiosJmxPluginException(
440                             "Error invoking operation [" + operation + "]: " +
441                             e.getMessage(), e);
442                 }
443             }
444         } finally {
445             if (connection != null) {
446                 try {
447                     closeConnection(connection);
448                 } catch (Exception e) {
449                     throw new NagiosJmxPluginException(
450                             "Error closing JMX connection", e);
451                 }
452             }
453         }
454         int exitCode;
455         if (value != null) {
456             Status status;
457             if (value instanceof Number) {
458                 Number numValue = (Number) value;
459                 if (isOutsideThreshold(numValue, thresholdCritical)) {
460                     status = Status.CRITICAL;
461                 } else if (isOutsideThreshold(numValue, thresholdWarning)) {
462                     status = Status.WARNING;
463                 } else {
464                     status = Status.OK;
465                 }
466             } else if (value instanceof String) {
467                 String strValue = (String) value;
468                 if (matchesThreshold(strValue, thresholdCritical)) {
469                     status = Status.CRITICAL;
470                 } else if (matchesThreshold(strValue, thresholdWarning)) {
471                     status = Status.WARNING;
472                 } else {
473                     status = Status.OK;
474                 }
475             } else {
476                 throw new NagiosJmxPluginException(
477                         "Type of return value not supported [" +
478                         value.getClass().getName() + "]. Must be either a " +
479                         "Number or String object.");
480             }
481             outputStatus(out, status, objectName, attributeName, attributeKey,
482                     value, unit);
483             if (value instanceof Number)
484                 outputPerformanceData(out, objectName, attributeName,
485                         attributeKey, (Number) value, thresholdWarning,
486                         thresholdCritical, unit);
487             out.println();
488             exitCode = status.getExitCode();
489         } else {
490             out.print(Status.WARNING.getMessagePrefix());
491             out.println("Value not set. JMX query returned null value.");
492             exitCode = Status.WARNING.getExitCode();
493         }
494         return exitCode;
495     }
496 
497     /**
498      * Get threshold limits.
499      * The array returned contains two values, min and max.
500      * If value is +/- inifinity, value is set to null.
501      * @param clazz Class threshold gets parsed as.
502      * @param threshold Threshold range.
503      * @return Array with two elements containing min, max.
504      * @throws NagiosJmxPluginException If threshold can't be parsed as
505      *      clazz or threshold format is not supported.
506      * @see http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT
507      */
508     Number[] getThresholdLimits(
509             Class<? extends Number> clazz, String threshold)
510     throws NagiosJmxPluginException
511     {
512         // 10
513         Matcher matcher1 = Pattern.compile(
514                 "^(\\d+\\.?\\d*)$").matcher(threshold);
515         // 10:
516         Matcher matcher2 = Pattern.compile(
517                 "^(\\d+\\.?\\d*):$").matcher(threshold);
518         // ~:10
519         Matcher matcher3 = Pattern.compile(
520                 "^~:(\\d+\\.?\\d*)$").matcher(threshold);
521         // 10:20
522         Matcher matcher4 = Pattern.compile(
523                 "^(\\d+\\.?\\d*):(\\d+\\.?\\d*)$").matcher(threshold);
524         // @10:20
525         Matcher matcher5 = Pattern.compile(
526                 "^@(\\d+\\.?\\d*):(\\d+\\.?\\d*)$").matcher(threshold);
527 
528         Number[] limits = new Number[2];
529         if (matcher1.matches()) {
530             limits[0] = parseAsNumber(clazz, "0");
531             limits[1] = parseAsNumber(clazz, matcher1.group(1));
532         } else if (matcher2.matches()) {
533             limits[0] = parseAsNumber(clazz, matcher2.group(1));
534             limits[1] = null;
535         } else if (matcher3.matches()) {
536             limits[0] = null;
537             limits[1] = parseAsNumber(clazz, matcher3.group(1));
538         } else if (matcher4.matches()) {
539             limits[0] = parseAsNumber(clazz, matcher4.group(1));
540             limits[1] = parseAsNumber(clazz, matcher4.group(2));
541         } else if (matcher5.matches()) {
542             limits[0] = parseAsNumber(clazz, matcher5.group(2));
543             limits[1] = parseAsNumber(clazz, matcher5.group(1));
544         } else {
545             throw new NagiosJmxPluginException("Error parsing threshold. " +
546                     "Unknown threshold range format [" + threshold + "]");
547         }
548         return limits;
549     }
550 
551     /**
552      * Parse value as clazz.
553      * @param clazz Class.
554      * @param value Value.
555      * @return Value parsed as Number of type clazz.
556      * @throws NagiosJmxPluginException If clazz is not supported or value
557      *     can't be parsed.
558      */
559     Number parseAsNumber(Class<? extends Number> clazz, String value)
560     throws NagiosJmxPluginException
561     {
562         Number result;
563         try {
564             if (Double.class.equals(clazz)) {
565                 result = Double.valueOf(value);
566             } else if (Integer.class.equals(clazz)) {
567                 result = Integer.valueOf(value);
568             } else if (Long.class.equals(clazz)) {
569                 result = Long.valueOf(value);
570             } else if (Short.class.equals(clazz)) {
571                 result = Short.valueOf(value);
572             } else if (Byte.class.equals(clazz)) {
573                 result = Byte.valueOf(value);
574             } else if (Float.class.equals(clazz)) {
575                 result = Float.valueOf(value);
576             } else if (BigInteger.class.equals(clazz)) {
577                 result = new BigInteger(value);
578             } else if (BigDecimal.class.equals(clazz)) {
579                 result = new BigDecimal(value);
580             } else {
581                 throw new NumberFormatException("Can't handle object type [" +
582                         value.getClass().getName() + "]");
583             }
584         } catch (NumberFormatException e) {
585             throw new NagiosJmxPluginException("Error parsing threshold " +
586                     "value [" + value + "]. Expected [" + clazz.getName() +
587                     "]", e);
588         }
589         return result;
590     }
591 
592     /**
593      * Output status.
594      * @param out Print stream.
595      * @param status Status.
596      * @param objectName Object name.
597      * @param attributeName Attribute name.
598      * @param attributeKey Attribute key, or null
599      * @param value Value
600      * @param unit Unit.
601      */
602     private void outputStatus(PrintStream out, Status status, String objectName,
603             String attributeName, String attributeKey, Object value,
604             Unit unit)
605     {
606         StringBuilder output = new StringBuilder(status.getMessagePrefix());
607         output.append(attributeName);
608         if (attributeKey != null) {
609             output.append(".").append(attributeKey);
610         }
611         output.append(" = ").append(value);
612         if (unit != null)
613             output.append(unit.getAbbreviation());
614         out.print(output.toString());
615     }
616 
617     /**
618      * Get performance data output.
619      * @param out Print stream.
620      * @param objectName Object name.
621      * @param attributeName Attribute name.
622      * @param attributeKey Attribute key, or null
623      * @param value Value
624      * @param thresholdWarning Warning threshold.
625      * @param thresholdCritical Critical threshold.
626      * @param unit Unit, null if not defined.
627      */
628     private void outputPerformanceData(PrintStream out, String objectName,
629             String attributeName, String attributeKey, Number value,
630             String thresholdWarning, String thresholdCritical,
631             Unit unit)
632     {
633         StringBuilder output = new StringBuilder();
634         output.append(" | '");
635         output.append(attributeName);
636         if (attributeKey != null) {
637             output.append(" ").append(attributeKey);
638         }
639         output.append("'=").append(value);
640         if (unit != null)
641             output.append(unit.getAbbreviation());
642         output.append(";");
643         if (thresholdWarning != null)
644             output.append(thresholdWarning);
645         output.append(";");
646         if (thresholdCritical != null)
647             output.append(thresholdCritical);
648         output.append(";;");
649         out.print(output.toString());
650     }
651 
652     /**
653      * Check if value is outside threshold range.
654      * @param value Value, which is either Double, Long, Integer, Short, Byte,
655      *        or Float.
656      * @param threshold Threshold range, which must be parsable in same number
657      *        format as value, can be null
658      * @return true if value is outside threshold, false otherwise.
659      * @throws NagiosJmxPluginException If number format is not parseable.
660      */
661     @SuppressWarnings({ "rawtypes", "unchecked" })
662     private boolean isOutsideThreshold(Number value, String threshold)
663     throws NagiosJmxPluginException
664     {
665         boolean outsideThreshold = false;
666         if (threshold != null) {
667             Number[] limits = getThresholdLimits(value.getClass(), threshold);
668             Number min = limits[0];
669             Number max = limits[1];
670             if (value instanceof Double || value instanceof Float) {
671                 outsideThreshold =
672                     (min != null &&
673                             value.doubleValue() < min.doubleValue()) ||
674                     (max != null &&
675                             value.doubleValue() > max.doubleValue());
676             } else if (value instanceof Long || value instanceof Integer ||
677                     value instanceof Short || value instanceof Byte)
678             {
679                 outsideThreshold =
680                     (min != null &&
681                             value.longValue() < min.longValue()) ||
682                     (max != null &&
683                             value.longValue() > max.longValue());
684             } else if (value instanceof BigInteger ||
685                     value instanceof BigDecimal)
686             {
687                 Comparable compVal = (Comparable) value;
688                 outsideThreshold =
689                     (min != null &&
690                             compVal.compareTo(min) < 0) ||
691                     (max != null &&
692                             compVal.compareTo(max) > 0);
693             } else {
694                 throw new NumberFormatException("Can't handle object type [" +
695                         value.getClass().getName() + "]");
696             }
697         } else {
698             outsideThreshold = false;
699         }
700         return outsideThreshold;
701     }
702 
703     /**
704      * Check if value matches threshold regular expression.
705      * A threshold starting with @ means that the threshold must
706      * not match to return true.
707      * @param value Value.
708      * @param threshold Threshold regular expression.
709      * @return true if value matches threshold regex, otherwise false.
710      * @throws NagiosJmxPluginException If threshold regex is not parseable.
711      */
712     private boolean matchesThreshold(String value, String threshold)
713     throws NagiosJmxPluginException
714     {
715         boolean matchesThreshold = false;
716         try {
717             if (threshold == null) {
718                 matchesThreshold = false;
719             } else {
720                 if (threshold.startsWith("@")) {
721                     matchesThreshold = Pattern.matches(threshold.substring(1),
722                             value);
723                 } else {
724                     matchesThreshold = !Pattern.matches(threshold, value);
725                 }
726             }
727             return matchesThreshold;
728         } catch (PatternSyntaxException e) {
729             throw new NagiosJmxPluginException("Error parsing threshold " +
730                     "regex [" + threshold + "]", e);
731         }
732     }
733 
734     /**
735      * Main method.
736      * @param args Command line arguments.
737      */
738     public static void main(String[] args) {
739         PrintStream out = System.out;
740         NagiosJmxPlugin plugin = new NagiosJmxPlugin();
741         int exitCode;
742         Properties props = parseArguments(args);
743         String verbose = props.getProperty(PROP_VERBOSE);
744         try {
745             exitCode = plugin.execute(props);
746         } catch (NagiosJmxPluginException e) {
747             out.println(Status.CRITICAL.getMessagePrefix() + e.getMessage());
748             if (verbose != null)
749                 e.printStackTrace(System.out);
750             exitCode = Status.CRITICAL.getExitCode();
751         } catch (Exception e) {
752             out.println(Status.UNKNOWN.getMessagePrefix() + e.getMessage());
753             if (verbose != null)
754                 e.printStackTrace(System.out);
755             exitCode = Status.UNKNOWN.getExitCode();
756         }
757         System.exit(exitCode);
758     }
759 
760     /**
761      * Show usage.
762      * @throws NagiosJmxPluginException XX
763      */
764     private void showUsage() throws NagiosJmxPluginException {
765         outputResource(getClass().getResource("usage.txt"));
766     }
767 
768     /**
769      * Show help.
770      * @throws NagiosJmxPluginException XX
771      */
772     private void showHelp() throws NagiosJmxPluginException {
773         outputResource(getClass().getResource("help.txt"));
774     }
775 
776     /**
777      * Output resource.
778      * @param url Resource URL.
779      * @throws NagiosJmxPluginException XX
780      */
781     private void outputResource(URL url) throws NagiosJmxPluginException {
782         PrintStream out = System.out;
783         try {
784             Reader r = new InputStreamReader(url.openStream());
785             StringBuilder sbHelp = new StringBuilder();
786             char[] buffer = new char[1024];
787             for (int len = r.read(buffer); len != -1; len = r.read(buffer)) {
788                 sbHelp.append(buffer, 0, len);
789             }
790             out.println(sbHelp.toString());
791         } catch (IOException e) {
792             throw new NagiosJmxPluginException(e);
793         }
794     }
795 
796     /**
797      * Parse command line arguments.
798      * @param args Command line arguments.
799      * @return Command line arguments as properties.
800      */
801     private static Properties parseArguments(String[] args) {
802         Properties props = new Properties();
803         int i = 0;
804         while (i < args.length) {
805             if ("-h".equals(args[i]))
806                 props.put(PROP_HELP, "");
807             else if ("-U".equals(args[i]))
808                 props.put(PROP_SERVICE_URL, args[++i]);
809             else if ("-O".equals(args[i]))
810                 props.put(PROP_OBJECT_NAME, args[++i]);
811             else if ("-A".equals(args[i]))
812                 props.put(PROP_ATTRIBUTE_NAME, args[++i]);
813             else if ("-K".equals(args[i]))
814                 props.put(PROP_ATTRIBUTE_KEY, args[++i]);
815             else if ("-v".equals(args[i]))
816                 props.put(PROP_VERBOSE, "true");
817             else if ("-w".equals(args[i]))
818                 props.put(PROP_THRESHOLD_WARNING, args[++i]);
819             else if ("-c".equals(args[i]))
820                 props.put(PROP_THRESHOLD_CRITICAL, args[++i]);
821             else if ("--username".equals(args[i]))
822                 props.put(PROP_USERNAME, args[++i]);
823             else if ("--password".equals(args[i]))
824                 props.put(PROP_PASSWORD, args[++i]);
825             else if ("-u".equals(args[i]))
826                 props.put(PROP_UNITS, args[++i]);
827             else if ("-o".equals(args[i]))
828                 props.put(PROP_OPERATION, args[++i]);
829             i++;
830         }
831         return props;
832     }
833 }