1 /* 2 ** Zabbix 3 ** Copyright (C) 2001-2021 Zabbix SIA 4 ** 5 ** This program is free software; you can redistribute it and/or modify 6 ** it under the terms of the GNU General Public License as published by 7 ** the Free Software Foundation; either version 2 of the License, or 8 ** (at your option) any later version. 9 ** 10 ** This program is distributed in the hope that it will be useful, 11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of 12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 ** GNU General Public License for more details. 14 ** 15 ** You should have received a copy of the GNU General Public License 16 ** along with this program; if not, write to the Free Software 17 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 **/ 19 20 package com.zabbix.gateway; 21 22 import java.util.HashMap; 23 24 import javax.management.MBeanAttributeInfo; 25 import javax.management.MBeanInfo; 26 import javax.management.MBeanServerConnection; 27 import javax.management.ObjectName; 28 import javax.management.openmbean.CompositeData; 29 import javax.management.openmbean.TabularDataSupport; 30 import javax.management.remote.JMXConnector; 31 import javax.management.remote.JMXServiceURL; 32 33 import org.json.*; 34 35 import org.slf4j.Logger; 36 import org.slf4j.LoggerFactory; 37 38 class JMXItemChecker extends ItemChecker 39 { 40 private static final Logger logger = LoggerFactory.getLogger(JMXItemChecker.class); 41 42 private JMXServiceURL url; 43 private JMXConnector jmxc; 44 private MBeanServerConnection mbsc; 45 46 private String username; 47 private String password; 48 JMXItemChecker(JSONObject request)49 public JMXItemChecker(JSONObject request) throws ZabbixException 50 { 51 super(request); 52 53 try 54 { 55 String conn = request.getString(JSON_TAG_CONN); 56 int port = request.getInt(JSON_TAG_PORT); 57 58 url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://[" + conn + "]:" + port + "/jmxrmi"); 59 jmxc = null; 60 mbsc = null; 61 62 username = request.optString(JSON_TAG_USERNAME, null); 63 password = request.optString(JSON_TAG_PASSWORD, null); 64 65 if (null != username && null == password || null == username && null != password) 66 throw new IllegalArgumentException("invalid username and password nullness combination"); 67 } 68 catch (Exception e) 69 { 70 throw new ZabbixException(e); 71 } 72 } 73 74 @Override getValues()75 public JSONArray getValues() throws ZabbixException 76 { 77 JSONArray values = new JSONArray(); 78 79 try 80 { 81 HashMap<String, String[]> env = null; 82 83 if (null != username && null != password) 84 { 85 env = new HashMap<String, String[]>(); 86 env.put(JMXConnector.CREDENTIALS, new String[] {username, password}); 87 } 88 89 jmxc = ZabbixJMXConnectorFactory.connect(url, env); 90 mbsc = jmxc.getMBeanServerConnection(); 91 92 for (String key : keys) 93 values.put(getJSONValue(key)); 94 } 95 catch (Exception e) 96 { 97 throw new ZabbixException(e); 98 } 99 finally 100 { 101 try { if (null != jmxc) jmxc.close(); } catch (java.io.IOException exception) { } 102 103 jmxc = null; 104 mbsc = null; 105 } 106 107 return values; 108 } 109 110 @Override getStringValue(String key)111 protected String getStringValue(String key) throws Exception 112 { 113 ZabbixItem item = new ZabbixItem(key); 114 115 if (item.getKeyId().equals("jmx")) 116 { 117 if (2 != item.getArgumentCount()) 118 throw new ZabbixException("required key format: jmx[<object name>,<attribute name>]"); 119 120 ObjectName objectName = new ObjectName(item.getArgument(1)); 121 String attributeName = item.getArgument(2); 122 String realAttributeName; 123 String fieldNames = ""; 124 125 // Attribute name and composite data field names are separated by dots. On the other hand the 126 // name may contain a dot too. In this case user needs to escape it with a backslash. Also the 127 // backslash symbols in the name must be escaped. So a real separator is unescaped dot and 128 // separatorIndex() is used to locate it. 129 130 int sep = HelperFunctionChest.separatorIndex(attributeName); 131 132 if (-1 != sep) 133 { 134 logger.trace("'{}' contains composite data", attributeName); 135 136 realAttributeName = attributeName.substring(0, sep); 137 fieldNames = attributeName.substring(sep + 1); 138 } 139 else 140 realAttributeName = attributeName; 141 142 // unescape possible dots or backslashes that were escaped by user 143 realAttributeName = HelperFunctionChest.unescapeUserInput(realAttributeName); 144 145 logger.trace("attributeName:'{}'", realAttributeName); 146 logger.trace("fieldNames:'{}'", fieldNames); 147 148 return getPrimitiveAttributeValue(mbsc.getAttribute(objectName, realAttributeName), fieldNames); 149 } 150 else if (item.getKeyId().equals("jmx.discovery")) 151 { 152 if (0 != item.getArgumentCount()) 153 throw new ZabbixException("required key format: jmx.discovery"); 154 155 JSONArray counters = new JSONArray(); 156 157 for (ObjectName name : mbsc.queryNames(null, null)) 158 { 159 logger.trace("discovered object '{}'", name); 160 161 for (MBeanAttributeInfo attrInfo : mbsc.getMBeanInfo(name).getAttributes()) 162 { 163 logger.trace("discovered attribute '{}'", attrInfo.getName()); 164 165 if (!attrInfo.isReadable()) 166 { 167 logger.trace("attribute not readable, skipping"); 168 continue; 169 } 170 171 try 172 { 173 logger.trace("looking for attributes of primitive types"); 174 String descr = (attrInfo.getName().equals(attrInfo.getDescription()) ? null : attrInfo.getDescription()); 175 findPrimitiveAttributes(counters, name, descr, attrInfo.getName(), mbsc.getAttribute(name, attrInfo.getName())); 176 } 177 catch (Exception e) 178 { 179 Object[] logInfo = {name, attrInfo.getName(), e}; 180 logger.trace("processing '{},{}' failed", logInfo); 181 } 182 } 183 } 184 185 JSONObject mapping = new JSONObject(); 186 mapping.put(ItemChecker.JSON_TAG_DATA, counters); 187 return mapping.toString(); 188 } 189 else 190 throw new ZabbixException("key ID '%s' is not supported", item.getKeyId()); 191 } 192 getPrimitiveAttributeValue(Object dataObject, String fieldNames)193 private String getPrimitiveAttributeValue(Object dataObject, String fieldNames) throws ZabbixException 194 { 195 logger.trace("drilling down with data object '{}' and field names '{}'", dataObject, fieldNames); 196 197 if (null == dataObject) 198 throw new ZabbixException("data object is null"); 199 200 if (fieldNames.equals("")) 201 { 202 if (isPrimitiveAttributeType(dataObject.getClass())) 203 return dataObject.toString(); 204 else 205 throw new ZabbixException("data object type is not primitive: %s", dataObject.getClass()); 206 } 207 208 if (dataObject instanceof CompositeData) 209 { 210 logger.trace("'{}' contains composite data", dataObject); 211 212 CompositeData comp = (CompositeData)dataObject; 213 214 String dataObjectName; 215 String newFieldNames = ""; 216 217 int sep = HelperFunctionChest.separatorIndex(fieldNames); 218 219 if (-1 != sep) 220 { 221 dataObjectName = fieldNames.substring(0, sep); 222 newFieldNames = fieldNames.substring(sep + 1); 223 } 224 else 225 dataObjectName = fieldNames; 226 227 // unescape possible dots or backslashes that were escaped by user 228 dataObjectName = HelperFunctionChest.unescapeUserInput(dataObjectName); 229 230 return getPrimitiveAttributeValue(comp.get(dataObjectName), newFieldNames); 231 } 232 else 233 throw new ZabbixException("unsupported data object type along the path: %s", dataObject.getClass()); 234 } 235 findPrimitiveAttributes(JSONArray counters, ObjectName name, String descr, String attrPath, Object attribute)236 private void findPrimitiveAttributes(JSONArray counters, ObjectName name, String descr, String attrPath, Object attribute) throws JSONException 237 { 238 logger.trace("drilling down with attribute path '{}'", attrPath); 239 240 if (isPrimitiveAttributeType(attribute.getClass())) 241 { 242 logger.trace("found attribute of a primitive type: {}", attribute.getClass()); 243 244 JSONObject counter = new JSONObject(); 245 246 counter.put("{#JMXDESC}", null == descr ? name + "," + attrPath : descr); 247 counter.put("{#JMXOBJ}", name); 248 counter.put("{#JMXATTR}", attrPath); 249 counter.put("{#JMXTYPE}", attribute.getClass().getName()); 250 counter.put("{#JMXVALUE}", attribute.toString()); 251 252 counters.put(counter); 253 } 254 else if (attribute instanceof CompositeData) 255 { 256 logger.trace("found attribute of a composite type: {}", attribute.getClass()); 257 258 CompositeData comp = (CompositeData)attribute; 259 260 for (String key : comp.getCompositeType().keySet()) 261 findPrimitiveAttributes(counters, name, descr, attrPath + "." + key, comp.get(key)); 262 } 263 else if (attribute instanceof TabularDataSupport || attribute.getClass().isArray()) 264 { 265 logger.trace("found attribute of a known, unsupported type: {}", attribute.getClass()); 266 } 267 else 268 logger.trace("found attribute of an unknown, unsupported type: {}", attribute.getClass()); 269 } 270 isPrimitiveAttributeType(Class<?> clazz)271 private boolean isPrimitiveAttributeType(Class<?> clazz) 272 { 273 Class<?>[] clazzez = {Boolean.class, Character.class, Byte.class, Short.class, Integer.class, Long.class, 274 Float.class, Double.class, String.class, java.math.BigDecimal.class, java.math.BigInteger.class, 275 java.util.Date.class, javax.management.ObjectName.class}; 276 277 return HelperFunctionChest.arrayContains(clazzez, clazz); 278 } 279 } 280