All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.welen.jmole.JMole Maven / Gradle / Ivy

There is a newer version: 1.5.4
Show newest version
package net.welen.jmole;

/*
 * #%L
 * JMole, https://bitbucket.org/awelen/jmole
 * %%
 * Copyright (C) 2015 - 2019 Anders Welén, [email protected]
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 * 
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import java.util.Map;
import java.util.HashMap;
import java.util.Properties;

import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanRegistrationException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;
import javax.management.NotificationListener;
import javax.management.MBeanServerNotification;
import javax.management.relation.MBeanServerNotificationFilter;
import javax.management.MBeanServerDelegate;
import javax.management.ListenerNotFoundException;
import javax.management.MBeanException;
import javax.management.AttributeNotFoundException;
import javax.management.ReflectionException;
import javax.management.IntrospectionException;
import javax.xml.XMLConstants;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import com.google.gson.Gson;

import net.welen.jmole.presentation.PresentationInformation;
import net.welen.jmole.threshold.Threshold;

import java.util.List;
import java.util.Collections;
import java.util.ArrayList;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.beans.XMLDecoder;
import java.beans.ExceptionListener;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * The MBean implementation of JMole and more or less the main class
 */
public class JMole extends NotificationBroadcasterSupport implements NotificationListener, JMoleMBean {

	private final static Logger LOG = Logger.getLogger(JMole.class.getName());

	static public final String OBJECT_NAME = "net.welen.jmole:service=jmole";
	static public final String CONFIG_FILENAMES_PROPERTY = "jmole.config.filenames";
	static public final String CONFIG_FILENAME_PROPERTY_PREFIX = "jmole.config.filename.";
	static public final String CONFIG_FILENAME_XSLT_PROPERTY_PREFIX = "jmole.config.xslt.";
	static public final String CONFIG_LEVEL_PROPERTY = "jmole.config.level";

	public static final String NOTIFICATION_TYPE = "jmole.reconfigured";

	private MBeanServer server = Utils.getMBeanServer();
	private List configurations = new ArrayList();

	private DiscoveryThread discoveryThread = new DiscoveryThread(this);
	private long numOfDiscoveries = 0;

	private List thresholdThreads = new ArrayList();		
	
	// Inner class that is used by XML libs
	private static class SimpleExceptionListener implements ExceptionListener {
		private Exception e = null;

		public void exceptionThrown(Exception e) {
			this.e = e;
		}

		Exception getException() {
			return e;
		}
	}
	
	/**
	 * Register the JMole MBean and a listener (that react to all new and
	 * removed MBeans)
	 * 
	 * @throws MalformedObjectNameException
	 * @throws InstanceAlreadyExistsException
	 * @throws MBeanRegistrationException
	 * @throws NotCompliantMBeanException
	 * @throws InstanceNotFoundException
	 */
	public void register() throws InstanceAlreadyExistsException, MBeanRegistrationException,
			NotCompliantMBeanException, MalformedObjectNameException, InstanceNotFoundException {
		LOG.log(Level.FINE, "Registering JMole MBean");
		server.registerMBean(this, new ObjectName(OBJECT_NAME));
		try {
			MBeanServerNotificationFilter filter = new MBeanServerNotificationFilter();
			filter.enableAllObjectNames();
			 
			Thread t = new Thread(discoveryThread, "JMole discovery thread");
			t.setDaemon(true);
			t.start();
			
			server.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, this, filter, null);
		} catch (Exception e) {
			LOG.log(Level.SEVERE, e.getMessage(), e);
			server.unregisterMBean(new ObjectName(OBJECT_NAME));
			throw new RuntimeException(e);
		}
	}

	/**
	 * Unregister the JMole MBean and remove the listener
	 * 
	 * @throws MalformedObjectNameException
	 * @throws InstanceNotFoundException
	 * @throws MBeanRegistrationException
	 * @throws ListenerNotFoundException
	 */
	public void unregister() throws MalformedObjectNameException, InstanceNotFoundException, MBeanRegistrationException,
			ListenerNotFoundException {
		LOG.log(Level.FINE, "Removing JMole MBean");
		deactivateThresholds();
		discoveryThread.stop();
		try {
			server.removeNotificationListener(MBeanServerDelegate.DELEGATE_NAME, this);
		} finally {
			server.unregisterMBean(new ObjectName(OBJECT_NAME));
		}
	}

	@Override
	public synchronized void configure() throws MalformedObjectNameException, FileNotFoundException, MBeanException,
			AttributeNotFoundException, InstanceNotFoundException, ReflectionException, IntrospectionException {

		List newConfigurations = Collections.synchronizedList(new ArrayList());

		String fileNames = getConfigFileNames();

		for (String fileName : fileNames.split("\\|")) {
			LOG.log(Level.INFO, "Configuring JMole with config file: " + fileName);

			XMLDecoder decoder = null;
			InputStream configStream = null;
			SimpleExceptionListener myListener = new SimpleExceptionListener();
			byte[] data = null;
			try {
				// Get inputstream
				if (new File(fileName).exists()) {
					configStream = new BufferedInputStream(new FileInputStream(fileName));
				} else {
					configStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
				}

				if (configStream == null) {
					LOG.log(Level.SEVERE, String.format("Unable to load config from '%s'", fileName));
					continue;
				}

				// Put all in memory
				try {
					data = readData(configStream);
				} catch (IOException e) {
					LOG.log(Level.SEVERE, String.format("Unable to load config from '%s'", fileName), e);
					continue;
				}

				// Replace all ${systemProperty} syntax
				data = replaceAllSystemProperties(data);


				// Find matching XSLT (if any)
				String xslt = null;
				for (Entry entry : System.getProperties().entrySet()) {				
					if (entry.getKey() instanceof String && entry.getValue() instanceof String) {
						String key = (String) entry.getKey();
						String value = (String) entry.getValue();
						if (key.startsWith(CONFIG_FILENAME_PROPERTY_PREFIX) && value.equals(fileName)) {
							String part = key.substring(CONFIG_FILENAME_PROPERTY_PREFIX.length());
							xslt = System.getProperty(CONFIG_FILENAME_XSLT_PROPERTY_PREFIX + part);
						}
					}
				}					
					
				if (xslt != null) {
					try {
						LOG.log(Level.INFO,
								String.format("Applying XSLT: '%s' on config file: '%s'", xslt, fileName));
						data = transformData(data, xslt);
					} catch (Exception e) {
						LOG.log(Level.SEVERE, String.format(
								"Unable to apply specified XSLT transformation to config file '%s'", fileName), e);
						continue;
					}
				}

				try {
					validateData(data);
				} catch (Exception e) {
					LOG.log(Level.SEVERE, String.format("Unable to validate config file '%s'", fileName), e);
					continue;
				}

				try {
					data = transformData(data, "META-INF/JMole.xsl");
				} catch (Exception e) {
					LOG.log(Level.SEVERE, String.format("Unable to transform config file '%s'", fileName), e);
					continue;
				}

				// Parse data
				decoder = new XMLDecoder(new ByteArrayInputStream(data));
				decoder.setExceptionListener(myListener);

				Object newDecodedConfigurations = null;
				try {
					newDecodedConfigurations = decoder.readObject();
				} catch (ArrayIndexOutOfBoundsException e) {
					LOG.log(Level.SEVERE,
							String.format("Config file is corrupt '%s'%n== CONFIG START ==%n%s%n== CONFIG STOP ==",
									fileName, new String(data)), e);
					continue;
				}

				newConfigurations.addAll((List) newDecodedConfigurations);
			} finally {
				Exception exception = myListener.getException();
				if (exception != null) {
					LOG.log(Level.SEVERE,
							String.format("Config file is corrupt '%s'%n== CONFIG START ==%n%s%n== CONFIG STOP ==",
									fileName, new String(data)), exception);
				}

				if (decoder != null) {
					decoder.close();
				}
				if (configStream != null) {
					try {
						configStream.close();
					} catch (IOException e) {
						LOG.log(Level.WARNING, "Couldn't close configStream", e);
					}
				}
			}
		}

		// Remove all configurations that don't meet the level criteria
		int level = Integer.getInteger(CONFIG_LEVEL_PROPERTY, 3);
		if (level < 1 || level > 5) {
			throw new RuntimeException(CONFIG_LEVEL_PROPERTY + " must be 1-5");
		}
		List levelFilteredConfigurations = new ArrayList();
		for (Configuration configuration : newConfigurations) {
			if (configuration.getLevel() <= level) {
				levelFilteredConfigurations.add(configuration);
			}
		}

		deactivateThresholds(); // Even if discover() calls deactivateThresholds() we need to do it as we otherwise
								// loose track of the threhold threads kept in the configuration
		configurations = levelFilteredConfigurations;
		discover();
	}

	private String getConfigFileNames() {
		String fileNames = "JMole_JVM.xml";
		if (System.getProperty(CONFIG_FILENAMES_PROPERTY) != null) {
			LOG.log(Level.WARNING, "JMole config property " + CONFIG_FILENAMES_PROPERTY + " is deprecated. Use " + CONFIG_FILENAME_PROPERTY_PREFIX + "* instead");
			fileNames = System.getProperty(CONFIG_FILENAMES_PROPERTY);
		} else {
			StringBuilder configs = new StringBuilder();
			for (Object prop : System.getProperties().keySet()) {				
				if (prop instanceof String) {
					String propString = (String) prop;					
					if (propString.startsWith(CONFIG_FILENAME_PROPERTY_PREFIX)) {
						if (configs.length() > 0) {
							configs.append("|");
						}
						configs.append(System.getProperty(propString));
					}
				}
			}
			if (configs.length() > 0) {
				fileNames = configs.toString();
			} else {			
				LOG.log(Level.INFO, "No config file(s) configured. Using: JMole_JVM.xml");
			}
		}
		return fileNames;
	}

	private byte[] replaceAllSystemProperties(byte[] data) {
		String stringData = new String(data);

		Properties clone = new Properties();
		clone.putAll(System.getProperties());
		for (Entry propEntry : clone.entrySet()) {
			String key = (String) propEntry.getKey();
			String value = (String) propEntry.getValue();
			stringData = stringData.replaceAll("\\$\\{" + key + ":.*\\}", value);
			stringData = stringData.replaceAll("\\$\\{" + key + "\\}", value);
		}

		stringData = stringData.replaceAll("\\$\\{.*:|\\}", "");

		return stringData.getBytes();
	}

	private byte[] readData(InputStream inputStream) throws IOException {
		ByteArrayOutputStream data = new ByteArrayOutputStream();

		try {
			byte[] buffer = new byte[1024];
			int read = 0;
			while ((read = inputStream.read(buffer)) != -1) {
				data.write(buffer, 0, read);
			}
		} finally {
			try {
				data.close();
			} catch (IOException e) {
				LOG.log(Level.SEVERE, e.getMessage(), e);
			}
		}
		return data.toByteArray();
	}

	private void validateData(byte[] data) throws SAXException, IOException {
		SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
		Schema schema = null;
		try {
			schema = factory.newSchema(new StreamSource(
					Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/JMole.xsd")));
		} catch (SAXParseException e) {
			ClassLoader tccl = Thread.currentThread().getContextClassLoader();
			try {
				Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
				schema = factory.newSchema(new StreamSource(
						Thread.currentThread().getContextClassLoader().getResourceAsStream("META-INF/JMole.xsd")));
			} finally {
				Thread.currentThread().setContextClassLoader(tccl);
			}
		}
		Validator validator = schema.newValidator();
		validator.validate(new StreamSource(new ByteArrayInputStream(data)));
	}

	private byte[] transformData(byte[] data, String xsltFile) throws TransformerException {
		ByteArrayOutputStream answer = new ByteArrayOutputStream();
		StreamResult outputStream = new StreamResult(answer);

		TransformerFactory factory = TransformerFactory.newInstance();
		factory.setErrorListener(new ErrorListener() {		
			@Override
			public void warning(TransformerException exception) throws TransformerException {
				LOG.log(Level.FINE, exception.getMessage(), exception);
			}			
			@Override
			public void fatalError(TransformerException exception) throws TransformerException {
				LOG.log(Level.FINE, exception.getMessage(), exception);
			}			
			@Override
			public void error(TransformerException exception) throws TransformerException {
				LOG.log(Level.FINE, exception.getMessage(), exception);
			}
		});		
		
		if (new File(xsltFile).exists()) {
			Source xslt;
			try {
				xslt = new StreamSource(new BufferedInputStream(new FileInputStream(xsltFile)));
				Transformer transformer = factory.newTransformer(xslt);
				transformer.transform(new StreamSource(new ByteArrayInputStream(data)), outputStream);
			} catch (FileNotFoundException e) {
				LOG.log(Level.SEVERE, "XSLT file: " + xsltFile + " not found.", e);
				throw new TransformerException(e.getMessage(), e);
			}
		} else {
			try {
				LOG.log(Level.FINE, "Getting the XSLT file from Thread.currentThread().getContextClassLoader()");
				Source xslt = new StreamSource(Thread.currentThread().getContextClassLoader().getResourceAsStream(xsltFile));
				Transformer transformer = factory.newTransformer(xslt);
				transformer.transform(new StreamSource(new ByteArrayInputStream(data)), outputStream);
			} catch (Exception e) {
				ClassLoader tccl = Thread.currentThread().getContextClassLoader();
				try {
					LOG.log(Level.FINE, "Getting the XSLT file from getClass().getClassLoader()");
					Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
					Source xslt = new StreamSource(Thread.currentThread().getContextClassLoader().getResourceAsStream(xsltFile));
					Transformer transformer = factory.newTransformer(xslt);
					transformer.transform(new StreamSource(new ByteArrayInputStream(data)), outputStream);
				} finally {
					Thread.currentThread().setContextClassLoader(tccl);
				}
			}
		}
		return answer.toByteArray();
	}

	@Override
	public synchronized void discover() throws MBeanException, AttributeNotFoundException, InstanceNotFoundException,
			ReflectionException, IntrospectionException {
		LOG.log(Level.FINE, "Running JMole discovery #" + ++numOfDiscoveries);
		deactivateThresholds();
		for (Configuration configuration : configurations) {
			configuration.getMBeanFinder().updateMatchingObjectNames();
		}
		activateThresholds();
		
		// Send MBean notification
		Notification notification = new Notification(NOTIFICATION_TYPE, OBJECT_NAME, numOfDiscoveries-1);
		LOG.log(Level.FINE, "Sending JMX notification: " + notification);
		sendNotification(notification);
	}

	/**
	 * Get the current configuration
	 * 
	 * @return The configuration in for av classes
	 */
	public List getConfiguration() {
		return configurations;
	}

	@Override
	public Map>>> collectMeasurements()
			throws InstanceNotFoundException, ReflectionException, MBeanException, AttributeNotFoundException {
		return collectMeasurements((Map) null);
	}

	public Map>>> collectMeasurements(Map presentationInformation)
			throws InstanceNotFoundException, ReflectionException, MBeanException, AttributeNotFoundException {

		Map>>> answer = new HashMap>>>();
		for (Configuration configuration : configurations) {
			String category = configuration.getPresentationInformation().getCategory();

			if (answer.containsKey(category)) {
				continue;
			}

			Map> data = collectMeasurements(category, presentationInformation);

			if (!data.isEmpty()) {
				if (answer.get(category) == null) {
					answer.put(category, new ArrayList>>());
				}
				answer.get(category).add(data);
			}

		}
		return answer;
	}

	@Override
	public String collectMeasurementsAsJSON()
			throws InstanceNotFoundException, ReflectionException, MBeanException, AttributeNotFoundException {
		return new Gson().toJson(collectMeasurements());
	}

	@Override
	public Map> collectMeasurements(String category)
			throws InstanceNotFoundException, ReflectionException, MBeanException, AttributeNotFoundException {		
		return collectMeasurements(category, (Map) null);
	}

	public Map> collectMeasurements(String category, Map presentationInformation)
			throws InstanceNotFoundException, ReflectionException, MBeanException, AttributeNotFoundException {

		Map> answer = new HashMap>();
		for (Configuration configuration : configurations) {
			if (configuration.getPresentationInformation().getCategory().equals(category)) {
				for (ObjectName objectName : configuration.getMBeanFinder().getMatchingObjectNames()) {
					if (presentationInformation != null) {
						LOG.log(Level.FINE, "PresentationInformation saved for: " + category + configuration.getMBeanCollector().getConstructedName(objectName));
						presentationInformation.put(category + configuration.getMBeanCollector().getConstructedName(objectName), configuration.getPresentationInformation());
					}
					try {
						answer.put(configuration.getMBeanCollector().getConstructedName(objectName),
								configuration.getMBeanCollector().getValues(objectName));
					} catch (Throwable t) {
						LOG.log(Level.SEVERE, t.getMessage(), t);
						continue;
					}					
				}
			}
		}
		return answer;
	}

	@Override
	public String collectMeasurementsAsJSON(String category)
			throws InstanceNotFoundException, ReflectionException, MBeanException, AttributeNotFoundException {
		return new Gson().toJson(collectMeasurements(category));
	}

	@Override
	public Map collectMeasurements(String category, String name)
			throws InstanceNotFoundException, ReflectionException, MBeanException, AttributeNotFoundException {

		for (Configuration configuration : configurations) {
			if (configuration.getPresentationInformation().getCategory().equals(category)) {
				for (ObjectName objectName : configuration.getMBeanFinder().getMatchingObjectNames()) {
					if (configuration.getMBeanCollector().getConstructedName(objectName).equals(name)) {
						return configuration.getMBeanCollector().getValues(objectName);
					}
				}
			}
		}
		return null;
	}

	@Override
	public String collectMeasurementsAsJSON(String category, String name)
			throws InstanceNotFoundException, ReflectionException, MBeanException, AttributeNotFoundException {
		return new Gson().toJson(collectMeasurements(category, name));
	}

	@Override
	public Object collectMeasurement(String category, String name, String attribute)
			throws InstanceNotFoundException, ReflectionException, MBeanException, AttributeNotFoundException {

		return collectMeasurements(category, name).get(attribute);
	}

	public String collectMeasurementAsJSON(String category, String name, String attribute)
			throws InstanceNotFoundException, ReflectionException, MBeanException, AttributeNotFoundException {
		return new Gson().toJson(collectMeasurement(category, name, attribute));
	}

	/**
	 * Call back method used by the MBean Server notification mechanism. May
	 * trigger a re-configuration if new MBeans are registred (or unregistred(
	 */
	public void handleNotification(Notification notification, Object handback) {
		LOG.log(Level.FINE, "MBean notification recieved", notification);
		if (notification instanceof MBeanServerNotification &&
				(notification.getType().equals(MBeanServerNotification.REGISTRATION_NOTIFICATION)
					|| notification.getType().equals(MBeanServerNotification.UNREGISTRATION_NOTIFICATION))) {
			LOG.log(Level.FINE, "MBean notication recieved. Reconfiguring");
			try {
				discoveryThread.performDiscovery();
			} catch (Exception e) {
				LOG.log(Level.SEVERE, "Reconfiguring of JMole failed.", e);
			}
		}
	}

	private void deactivateThresholds() {
		LOG.log(Level.FINE, "Stopping threshold threads");

		for (Configuration configuration : configurations) {
			for (Threshold threshold : configuration.getThresholds().values()) {
				threshold.stopThread();
			}
		}

		for (Thread t : thresholdThreads) {
			t.interrupt();
		}		
		thresholdThreads.clear();
	}

	private void activateThresholds() {
		for (Configuration configuration : configurations) {
			for (Entry entry : configuration.getThresholds().entrySet()) {
				String key = entry.getKey();
				Threshold threshold = entry.getValue();

				StringBuffer threadName = new StringBuffer(
						"JMole Threshold Thread: " + configuration.getPresentationInformation().getCategory() + "->"
								+ configuration.getMBeanCollector().getName() + "->" + key);
				if (!threshold.getWarningLowThreshold().isEmpty()) {
					threadName.append("  WarningLow");
				}
				if (!threshold.getWarningHighThreshold().isEmpty()) {
					threadName.append("  WarningHigh");
				}
				if (!threshold.getCriticalLowThreshold().isEmpty()) {
					threadName.append("  CriticalLow");
				}
				if (!threshold.getCriticalHighThreshold().isEmpty()) {
					threadName.append("  CriticalHigh");
				}

				threshold.setMBeanFinder(configuration.getMBeanFinder());
				threshold.setMBeanCollector(configuration.getMBeanCollector());
				threshold.setAttribute(key);
				threshold.setLabel(configuration.getPresentationInformation().getAttributeLabel(key));

				LOG.log(Level.FINE, "Starting thread: " + threadName);
				Thread thread = new Thread(threshold, threadName.toString());
				thread.start();
				thresholdThreads.add(thread);
			}
		}
	}

	@Override
	public Map> warningMessages()
			throws AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
		return constructMessageData(true);
	}

	@Override
	public String warningMessagesAsJSON()
			throws AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
		return new Gson().toJson(constructMessageData(true));
	}

	@Override
	public Map> criticalMessages()
			throws AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
		return constructMessageData(false);
	}

	@Override
	public String criticalMessagesAsJSON()
			throws AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
		return new Gson().toJson(constructMessageData(false));
	}

	private Map> constructMessageData(boolean warning)
			throws AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {
		Map> answer = new HashMap>();
		for (Configuration configuration : configurations) {
			for (Threshold threshold : configuration.getThresholds().values()) {
				Map thresholdMap = new HashMap();
				Set> entrySet = null;
				if (warning) {
					entrySet = threshold.getWarningMessages().entrySet();
				} else {
					entrySet = threshold.getCriticalMessages().entrySet();
				}
				for (Entry entry : entrySet) {
					ObjectName objectName = entry.getKey();
					String msg = entry.getValue();
					thresholdMap.put(configuration.getMBeanCollector().getConstructedName(objectName), msg);
				}
				if (!thresholdMap.isEmpty()) {
					answer.put(configuration.getPresentationInformation().getCategory(), thresholdMap);
				}
			}
		}
		return answer;
	}

	@Override
	public long getNumberOfExecutedDiscoveries() {
		return numOfDiscoveries;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy