/* * Copyright 2014, The Sporting Exchange Limited * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.betfair.cougar.util.jmx; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.xml.sax.SAXException; import javax.management.*; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPathExpressionException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.URL; import java.net.URLDecoder; import java.security.MessageDigest; import java.util.*; /** * * */ public class ApplicationChecksums implements DynamicMBean, InitializingBean { public static final String FILES_INCLUDED_ATTRIBUTE = "FILES_INCLUDED_IN_CHECKSUM"; public static final String APPLICATION_CHECKSUM = "ApplicationChecksum"; public static final String SEP = "::"; private Logger logger = LoggerFactory.getLogger(getClass()); private String[] algorithms; private Map<String, Map<String, String>> fileCheckSumsByAlgorithm; private Map<String, String> appCheckSumsByAlgorithm; private String filesIncluded; @Override public void afterPropertiesSet() throws Exception { logger.info("Calculating application checksums using algorithms: " + Arrays.toString(algorithms)); try { List<File> jars = findNonJdkJars(); // make sure they're always in the same order Collections.sort(jars); String[] files = new String[jars.size()]; Map<String, MessageDigest> appDigests = new HashMap<String, MessageDigest>(); Map<String, MessageDigest> fileDigests = new HashMap<String, MessageDigest>(); Map<String, Map<String, String>> results = new HashMap<String, Map<String, String>>(); for (String alg : algorithms) { fileDigests.put(alg, MessageDigest.getInstance(alg)); appDigests.put(alg, MessageDigest.getInstance(alg)); results.put(alg, new HashMap()); } int i = 0; for (File j : jars) { if (logger.isDebugEnabled()) { logger.debug("Including: " + j); } files[i++] = j.getCanonicalPath(); try (FileInputStream fis = new FileInputStream(j)) { byte[] dataBytes = new byte[1024]; int nread = 0; while ((nread = fis.read(dataBytes)) != -1) { for (Map.Entry<String, MessageDigest> entry : fileDigests.entrySet()) { final MessageDigest appDigest = appDigests.get(entry.getKey()); final MessageDigest fileDigest = entry.getValue(); fileDigest.update(dataBytes, 0, nread); appDigest.update(dataBytes, 0, nread); } } for (Map.Entry<String, MessageDigest> entry : fileDigests.entrySet()) { MessageDigest md = entry.getValue(); String algorithm = entry.getKey(); byte[] mdbytes = md.digest(); //convert the byte to hex format StringBuilder sb = new StringBuilder(); for (byte mdbyte : mdbytes) { sb.append(Integer.toString((mdbyte & 0xff) + 0x100, 16).substring(1)); } results.get(algorithm).put(j.getName(), sb.toString()); } } } Map<String, String> appCheckSums = new HashMap<String, String>(); for (Map.Entry<String, MessageDigest> entry : appDigests.entrySet()) { MessageDigest md = entry.getValue(); String algorithm = entry.getKey(); byte[] mdbytes = md.digest(); //convert the byte to hex format StringBuilder sb = new StringBuilder(); for (byte mdbyte : mdbytes) { sb.append(Integer.toString((mdbyte & 0xff) + 0x100, 16).substring(1)); } appCheckSums.put(algorithm, sb.toString()); } this.fileCheckSumsByAlgorithm = results; this.appCheckSumsByAlgorithm = appCheckSums; StringBuilder sb = new StringBuilder(); String sep = ""; for (String s : files) { sb.append(sep).append(s); sep = "\n"; } filesIncluded = sb.toString(); } catch (IOException ioe) { logger.error("Unable to discover maven artifacts", ioe); } catch (SAXException e) { logger.error("Unable to discover maven artifacts", e); } catch (XPathExpressionException e) { logger.error("Unable to discover maven artifacts", e); } } // package private for testing purposes List<File> findNonJdkJars() throws IOException, SAXException, XPathExpressionException, ParserConfigurationException { Enumeration<URL> manifests = LibraryVersions.class.getClassLoader().getResources("META-INF/MANIFEST.MF"); String javaHome = System.getProperty("java.home"); List<File> ret = new LinkedList<File>(); while (manifests.hasMoreElements()) { URL u = manifests.nextElement(); // jar:file:/home/username/.m2/repository/org/slf4j/slf4j-jdk14/1.5.0/slf4j-jdk14-1.5.0.jar!/META-INF/MANIFEST.MF // we're not interested if it's not a jar, although how you'd have a MANIFEST.MF which wasn't in a jar? if (u.getProtocol().equals("jar")) { // file:/home/username/.m2/repository/com/betfair/tornjak/kpi/3.0-SNAPSHOT/kpi-3.0-SNAPSHOT.jar!/META-INF/MANIFEST.MF String file = u.getFile(); // file:/home/username/.m2/repository/com/betfair/tornjak/kpi/3.0-SNAPSHOT/kpi-3.0-SNAPSHOT.jar file = file.substring(0, file.indexOf("!")); // /home/username/.m2/repository/com/betfair/tornjak/kpi/3.0-SNAPSHOT/kpi-3.0-SNAPSHOT.jar file = file.substring(5); // remove %20 etc file = URLDecoder.decode(file, "UTF-8"); File f = new File(file); String canonicalPath = f.getCanonicalPath(); if (!canonicalPath.startsWith(javaHome)) { ret.add(f); } } else { logger.info("Can't include code in checksum which isn't in a JAR: " + u); } } return ret; } public void setAlgorithms(String algorithms) { this.algorithms = algorithms.split(","); } @ManagedAttribute public String[] getAlgorithmsArray() { return algorithms; } public Map<String, Map<String, String>> getFileCheckSumsByAlgorithm() { return fileCheckSumsByAlgorithm; } @Override public Object getAttribute(String attribute) { if (FILES_INCLUDED_ATTRIBUTE.equals(attribute)) { return filesIncluded; } final int index = attribute.indexOf(SEP); if (index == -1) { return "UNKNOWN"; } String algorithm = attribute.substring(index + SEP.length()); if (attribute.startsWith(APPLICATION_CHECKSUM)) { return appCheckSumsByAlgorithm.get(algorithm); } String fileName = attribute.substring(0, index); String value = fileCheckSumsByAlgorithm.get(algorithm).get(fileName); if (value != null) { return value; } return "UNKNOWN"; } @Override public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException { // not supported } @Override public AttributeList getAttributes(String[] attributes) { AttributeList ret = new AttributeList(); for (String s : attributes) { ret.add(new Attribute(s, getAttribute(s))); } return ret; } @Override public AttributeList setAttributes(AttributeList attributes) { return attributes; } @Override public Object invoke(String actionName, Object[] params, String[] signature) throws MBeanException, ReflectionException { return null; } @Override public MBeanInfo getMBeanInfo() { List<MBeanAttributeInfo> attributes = new ArrayList(); for (Map.Entry<String, Map<String, String>> entry : fileCheckSumsByAlgorithm.entrySet()) { String algorithm = entry.getKey(); final Map<String, String> fileCheckSums = entry.getValue(); for (String fileName : fileCheckSums.keySet()) { attributes.add(new MBeanAttributeInfo(getKey(algorithm, fileName), "java.lang.String", "", true, false, false)); } } for (Map.Entry<String, String> entry : appCheckSumsByAlgorithm.entrySet()) { attributes.add(new MBeanAttributeInfo(getKey(entry.getKey(), APPLICATION_CHECKSUM), "java.lang.String", "", true, false, false)); } attributes.add(new MBeanAttributeInfo(FILES_INCLUDED_ATTRIBUTE, "java.lang.String", "", true, false, false)); return new MBeanInfo(getClass().getName(), "", attributes.toArray(new MBeanAttributeInfo[]{}), new MBeanConstructorInfo[0], new MBeanOperationInfo[0], new MBeanNotificationInfo[0]); } private String getKey(String algorithm, String fileName) { return fileName + SEP + algorithm; } public Map<String, String> getAppCheckSumsByAlgorithm() { return appCheckSumsByAlgorithm; } }