--- a/src/zabbix_java/src/com/zabbix/gateway/JMXItemChecker.java	2017-12-27 14:04:35.000000000 +0000
+++ b/src/zabbix_java/src/com/zabbix/gateway/JMXItemChecker.java	2018-01-08 06:47:07.743745659 +0000
@@ -19,10 +19,14 @@
 
 package com.zabbix.gateway;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.HashSet;
 
+import javax.management.Attribute;
+import javax.management.AttributeList;
 import javax.management.MBeanAttributeInfo;
 import javax.management.MBeanServerConnection;
 import javax.management.ObjectName;
@@ -47,6 +51,17 @@
 	private String username;
 	private String password;
 
+	/** The items to retrieve, grouped by jmx-object and jmx-attribute */
+	private Map<ObjectName, ObjectAttributes> valueItems = new HashMap<ObjectName, ObjectAttributes>();
+	/** The items to be discovered, grouped by item key */
+	private Map<String, DiscoveryInfo> discoveryItems = new HashMap<String, DiscoveryInfo>();
+	/** The discovered items, keyed by item key */
+	private Map<String, List<DiscoveryResult>> discoveryResults = new HashMap<String, List<DiscoveryResult>>();
+	/** The retrieved JMX value, keyed by item key */
+	private Map<String, ValueInfo> valueResults = new HashMap<String, ValueInfo>();
+	/** Errors that occurred, keyed by item key */
+	private Map<String, Exception> errors = new HashMap<String, Exception>();
+
 	static final int DISCOVERY_MODE_ATTRIBUTES = 0;
 	static final int DISCOVERY_MODE_BEANS = 1;
 
@@ -91,6 +106,15 @@
 			mbsc = jmxc.getMBeanServerConnection();
 
 			for (String key : keys)
+				collectItemInfo(key);
+
+			for (Map.Entry<String, DiscoveryInfo> item : discoveryItems.entrySet())
+				discoverItems(item.getKey(), item.getValue());
+
+			for (Map.Entry<ObjectName, ObjectAttributes> object : valueItems.entrySet())
+				retrieveAttributeValues(object.getKey(), object.getValue());
+
+			for (String key : keys)
 				values.put(getJSONValue(key));
 		}
 		catch (Exception e)
@@ -108,17 +132,31 @@
 		return values;
 	}
 
-	@Override
-	protected String getStringValue(String key) throws Exception
+	/**
+	 * Add an entry to the valueItems or discoveryItems info for the given key.
+	 */
+	private void collectItemInfo(String key)
 	{
 		ZabbixItem item = new ZabbixItem(key);
 
 		if (item.getKeyId().equals("jmx"))
 		{
 			if (2 != item.getArgumentCount())
-				throw new ZabbixException("required key format: jmx[<object name>,<attribute name>]");
+			{
+				errors.put(key, new ZabbixException("required key format: jmx[<object name>,<attribute name>]"));
+				return;
+			}
 
-			ObjectName objectName = new ObjectName(item.getArgument(1));
+			ObjectName objectName;
+			try
+			{
+				objectName = new ObjectName(item.getArgument(1));
+			}
+			catch (Exception e)
+			{
+				errors.put(key, e);
+				return;
+			}
 			String attributeName = item.getArgument(2);
 			String realAttributeName;
 			String fieldNames = "";
@@ -146,17 +184,28 @@
 			logger.trace("attributeName:'{}'", realAttributeName);
 			logger.trace("fieldNames:'{}'", fieldNames);
 
-			return getPrimitiveAttributeValue(mbsc.getAttribute(objectName, realAttributeName), fieldNames);
+			recordValueItem(key, objectName, realAttributeName, fieldNames);
 		}
 		else if (item.getKeyId().equals("jmx.discovery"))
 		{
 			int argumentCount = item.getArgumentCount();
 
 			if (2 < argumentCount)
-				throw new ZabbixException("required key format: jmx.discovery[<discovery mode>,<object name>]");
+			{
+				errors.put(key, new ZabbixException("required key format: jmx.discovery[<discovery mode>,<object name>]"));
+				return;
+			}
 
-			JSONArray counters = new JSONArray();
-			ObjectName filter = (2 == argumentCount) ? new ObjectName(item.getArgument(2)) : null;
+			ObjectName filter;
+			try
+			{
+				filter = (2 == argumentCount) ? new ObjectName(item.getArgument(2)) : null;
+			}
+			catch (Exception e)
+			{
+				errors.put(key, e);
+				return;
+			}
 
 			int mode = DISCOVERY_MODE_ATTRIBUTES;
 			if (0 != argumentCount)
@@ -166,27 +215,187 @@
 				if (modeName.equals("beans"))
 					mode = DISCOVERY_MODE_BEANS;
 				else if (!modeName.equals("attributes"))
-					throw new ZabbixException("invalid discovery mode: " + modeName);
+				{
+					errors.put(key, new ZabbixException("invalid discovery mode: " + modeName));
+					return;
+				}
 			}
 
-			for (ObjectName name : mbsc.queryNames(filter, null))
+			discoveryItems.put(key, new DiscoveryInfo(filter, mode));
+		}
+		else
+			errors.put(key, new ZabbixException("Internal error: key '%s' not found in results", key));
+	}
+
+	/**
+	 * Discovers all matching items on the server. The retrieval of the values for the
+	 * items is not done here; instead, the items are added to the valueItems to be
+	 * retrieved along with the other requested items.
+	 */
+	private void discoverItems(String key, DiscoveryInfo info)
+	{
+		try
+		{
+			for (ObjectName name : mbsc.queryNames(info.filter, null))
 			{
 				logger.trace("discovered object '{}'", name);
 
-				if (DISCOVERY_MODE_ATTRIBUTES == mode)
-					discoverAttributes(counters, name);
+				List<DiscoveryResult> results = discoveryResults.get(key);
+				if (results == null)
+					discoveryResults.put(key, results = new ArrayList<DiscoveryResult>());
+
+				if (DISCOVERY_MODE_ATTRIBUTES == info.mode)
+					discoverAttributes(results, name);
 				else
-					discoverBeans(counters, name);
+					discoverBeans(results, name);
+			}
+		}
+		catch (Exception e)
+		{
+			errors.put(key, e);
+		}
+	}
+
+	private void recordValueItem(String key, ObjectName objectName, String realAttributeName, String fieldNames)
+	{
+		ObjectAttributes attrsMap = valueItems.get(objectName);
+		if (attrsMap == null)
+			valueItems.put(objectName, attrsMap = new ObjectAttributes());
+
+		attrsMap.addInfo(key, realAttributeName, fieldNames);
+	}
+
+	/**
+	 * Retrieve the desired attributes for the given object from the JMX server.
+	 */
+	private void retrieveAttributeValues(ObjectName objectName, ObjectAttributes attrsInfo)
+	{
+		try
+		{
+			logger.trace("Retrieving attributes {} for object {}", attrsInfo.keySet(), objectName);
+
+			String[] attributes = attrsInfo.keySet().toArray(new String[attrsInfo.size()]);
+			AttributeList attributeList = mbsc.getAttributes(objectName, attributes);
+
+			for (Attribute attribute : attributeList.asList())
+			{
+				List<ItemInfo> itemInfos = attrsInfo.remove(attribute.getName());
+				if (itemInfos == null)
+				{
+					logger.warn("server returned unknown item '{}'", attribute.getName());
+					continue;
+				}
+
+				logger.trace("received attribute {}", attribute);
+
+				for (ItemInfo itemInfo : itemInfos)
+					valueResults.put(itemInfo.key, new ValueInfo(itemInfo.fieldNames, attribute.getValue()));
+			}
+
+			for (List<ItemInfo> itemInfos : attrsInfo.values())
+				for (ItemInfo itemInfo : itemInfos)
+					valueResults.put(itemInfo.key, new ValueInfo(itemInfo.fieldNames, null));
+		}
+		catch (Exception e)
+		{
+			logger.debug("caught exception retrieving values for " + objectName, e);
+
+			for (List<ItemInfo> itemInfos : attrsInfo.values())
+				for (ItemInfo itemInfo : itemInfos)
+					errors.put(itemInfo.key, e);
+		}
+	}
+
+	@Override
+	protected String getStringValue(String key) throws Exception
+	{
+		Exception exc = errors.get(key);
+		if (exc != null)
+			throw exc;
+
+		ValueInfo valueInfo = valueResults.get(key);
+		if (valueInfo != null)
+			return getPrimitiveAttributeValue(valueInfo.value, valueInfo.fieldNames);
+
+		List<DiscoveryResult> discoveryResList = discoveryResults.get(key);
+		if (discoveryResList != null)
+		{
+			JSONArray counters = new JSONArray();
+
+			for (DiscoveryResult discoveryResult : discoveryResList)
+			{
+				logger.trace("looking for attributes of primitive types");
+
+				try
+				{
+					exc = errors.get(discoveryResult.getKey());
+					if (exc != null)
+						throw exc;
+
+					if (discoveryResult.attrInfo != null)
+					{
+						valueInfo = valueResults.get(discoveryResult.getKey());
+						MBeanAttributeInfo attrInfo = discoveryResult.attrInfo;
+						String descr = (attrInfo.getName().equals(attrInfo.getDescription()) ? null : attrInfo.getDescription());
+
+						findPrimitiveAttributes(counters, discoveryResult.name, descr, attrInfo.getName(), valueInfo.value);
+					}
+					else
+					{
+						addObjectNameDiscoveryFields(counters, discoveryResult.name);
+					}
+				}
+				catch (Exception e)
+				{
+					String attrName = (discoveryResult.attrInfo != null) ? discoveryResult.attrInfo.getName() : "";
+					Object[] logInfo = {discoveryResult.name, attrName, e};
+					logger.trace("processing '{},{}' failed", logInfo);
+				}
 			}
 
 			JSONObject mapping = new JSONObject();
 			mapping.put(ItemChecker.JSON_TAG_DATA, counters);
 			return mapping.toString();
 		}
-		else
-			throw new ZabbixException("key ID '%s' is not supported", item.getKeyId());
+
+		throw new ZabbixException("Internal error: key '%s' not found in results", key);
 	}
 
+	private void addObjectNameDiscoveryFields(JSONArray counters, ObjectName name) throws JSONException
+	{
+		try
+		{
+			HashSet<String> properties = new HashSet<String>();
+			JSONObject counter = new JSONObject();
+
+			// Default properties are added.
+			counter.put("{#JMXOBJ}", name);
+			counter.put("{#JMXDOMAIN}", name.getDomain());
+			properties.add("OBJ");
+			properties.add("DOMAIN");
+
+			for (Map.Entry<String, String> property : name.getKeyPropertyList().entrySet())
+			{
+				String key = property.getKey().toUpperCase();
+
+				// Property key should only contain valid characters and should not be already added to attribute list.
+				if (key.matches("^[A-Z0-9_\\.]+$") && !properties.contains(key))
+				{
+					counter.put("{#JMX" + key + "}" , property.getValue());
+					properties.add(key);
+				}
+				else
+					logger.trace("bean '{}' property '{}' was ignored", name, property.getKey());
+			}
+
+			counters.put(counter);
+		}
+		catch (Exception e)
+		{
+			logger.trace("bean processing '{}' failed", name);
+		}
+	}
+ 
 	private String getPrimitiveAttributeValue(Object dataObject, String fieldNames) throws ZabbixException
 	{
 		logger.trace("drilling down with data object '{}' and field names '{}'", dataObject, fieldNames);
@@ -230,7 +439,7 @@
 			throw new ZabbixException("unsupported data object type along the path: %s", dataObject.getClass());
 	}
 
-	private void discoverAttributes(JSONArray counters, ObjectName name) throws Exception
+	private void discoverAttributes(List<DiscoveryResult> results, ObjectName name) throws Exception
 	{
 		for (MBeanAttributeInfo attrInfo : mbsc.getMBeanInfo(name).getAttributes())
 		{
@@ -242,53 +451,16 @@
 				continue;
 			}
 
-			try
-			{
-				logger.trace("looking for attributes of primitive types");
-				String descr = (attrInfo.getName().equals(attrInfo.getDescription()) ? null : attrInfo.getDescription());
-				findPrimitiveAttributes(counters, name, descr, attrInfo.getName(), mbsc.getAttribute(name, attrInfo.getName()));
-			}
-			catch (Exception e)
-			{
-				Object[] logInfo = {name, attrInfo.getName(), e};
-				logger.trace("processing '{},{}' failed", logInfo);
-			}
+			DiscoveryResult result = new DiscoveryResult(name, attrInfo);
+			results.add(result);
+			recordValueItem(result.getKey(), name, attrInfo.getName(), "");
 		}
 	}
 
-	private void discoverBeans(JSONArray counters, ObjectName name)
+	private void discoverBeans(List<DiscoveryResult> results, ObjectName name)
 	{
-		try
-		{
-			HashSet<String> properties = new HashSet<String>();
-			JSONObject counter = new JSONObject();
-
-			// Default properties are added.
-			counter.put("{#JMXOBJ}", name);
-			counter.put("{#JMXDOMAIN}", name.getDomain());
-			properties.add("OBJ");
-			properties.add("DOMAIN");
-
-			for (Map.Entry<String, String> property : name.getKeyPropertyList().entrySet())
-			{
-				String key = property.getKey().toUpperCase();
-
-				// Property key should only contain valid characters and should not be already added to attribute list.
-				if (key.matches("^[A-Z0-9_\\.]+$") && !properties.contains(key))
-				{
-					counter.put("{#JMX" + key + "}" , property.getValue());
-					properties.add(key);
-				}
-				else
-					logger.trace("bean '{}' property '{}' was ignored", name, property.getKey());
-			}
-
-			counters.put(counter);
-		}
-		catch (Exception e)
-		{
-			logger.trace("bean processing '{}' failed", name);
-		}
+		DiscoveryResult result = new DiscoveryResult(name, null);
+		results.add(result);
 	}
 
 	private void findPrimitiveAttributes(JSONArray counters, ObjectName name, String descr, String attrPath, Object attribute) throws JSONException
@@ -335,4 +507,106 @@
 
 		return HelperFunctionChest.arrayContains(clazzez, clazz);
 	}
+
+	/**
+	 * This holds the information about the items to be retrieved for single
+	 * JMX-object. The keys are JMX-attributes, and the values are lists of
+	 * items for the attribute. For attributes with simple values the list
+	 * will contain only one entry; but for composite values the list will be
+	 * all the items that reference that same composite value.
+	 *
+	 * <p>Examples:
+	 * <pre>
+	 *   key="a", value=[ItemInfo{key="jmx[foo, a.b.c]", fieldNames="b.c"},
+	 *                   ItemInfo{key="jmx[foo, a.d]", fieldNames="d"}]
+	 *   key="bar", value=[ItemInfo{key="jmx[foo, bar]", fieldNames=""}]
+	 * </pre>
+	 */
+	private static class ObjectAttributes extends HashMap<String, List<ItemInfo>>
+	{
+		public void addInfo(String key, String realAttributeName, String fieldNames)
+		{
+			List<ItemInfo> items = get(realAttributeName);
+			if (items == null)
+				put(realAttributeName, items = new ArrayList<ItemInfo>());
+
+			items.add(new ItemInfo(key, fieldNames));
+		}
+	}
+
+	/**
+	 * The information about an item in the ObjectAttributes. The key is the
+	 * full zabbix item key; the fieldNames are the fields in the composite
+	 * object (if it is one), or the empty string if it's a simple value.
+	 *
+	 * <p>Examples:
+	 * <pre>
+	 *   key="jmx[foo, a.b.c]", fieldNames="b.c"
+	 *   key="jmx[foo, bar]", fieldNames=""
+	 * </pre>
+	 */
+	private static class ItemInfo
+	{
+		public final String key;
+		public final String fieldNames;
+
+		public ItemInfo(String key, String fieldNames)
+		{
+			this.key = key;
+			this.fieldNames = fieldNames;
+		}
+	}
+
+	/**
+	 * This holds the info for object discovery.
+	 */
+	private static class DiscoveryInfo
+	{
+		public final ObjectName filter;
+		public final int mode;
+
+		public DiscoveryInfo(ObjectName filter, int mode)
+		{
+			this.filter = filter;
+			this.mode = mode;
+		}
+	}
+
+	/**
+	 * This holds the result of discovered object.
+	 */
+	private static class DiscoveryResult
+	{
+		public final ObjectName name;
+		public final MBeanAttributeInfo attrInfo;
+
+		public DiscoveryResult(ObjectName name, MBeanAttributeInfo attrInfo)
+		{
+			this.name = name;
+			this.attrInfo = attrInfo;
+		}
+
+		public String getKey()
+		{
+			return name.toString() + (attrInfo != null ? ":" + attrInfo.getName() : "");
+		}
+	}
+
+	/**
+	 * This hold information about a value retrieved from the JMX server for a
+	 * given zabbix item. The value is the value received from the JMX server;
+	 * the fieldNames describe the field within that value (for composite values)
+	 * that the zabbix item refers to.
+	 */
+	private static class ValueInfo
+	{
+		public final String fieldNames;
+		public final Object value;
+
+		public ValueInfo(String fieldNames, Object value)
+		{
+			this.fieldNames = fieldNames;
+			this.value = value;
+		}
+	}
 }
