/*
* 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 com.sun.org.apache.xerces.internal.impl.io.MalformedByteSequenceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.MBeanOperationInfo;
import javax.management.ReflectionException;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLInputFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* Exposes the version of all maven built dependencies onto jmx. It's a dynamic bean so the attributes are named <groupId>:<artifactId> with values
* as the version of the library.
*/
public class LibraryVersions implements DynamicMBean, InitializingBean {
private Logger logger = LoggerFactory.getLogger(getClass());
private Map<String, String> artifacts = new HashMap<String, String>();
@Override
public void afterPropertiesSet() throws Exception {
try {
Enumeration<URL> mavenDirs = LibraryVersions.class.getClassLoader().getResources("META-INF/maven");
findMavenArtifacts(mavenDirs);
} 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
void findMavenArtifacts(Enumeration<URL> mavenDirs) throws IOException, SAXException, XPathExpressionException, ParserConfigurationException {
XPath xpath = XPathFactory.newInstance().newXPath();
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
// don't allow external entity refs in the poms.
builderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
builderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
builderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
DocumentBuilder builder = builderFactory.newDocumentBuilder();
while (mavenDirs.hasMoreElements()) {
URL u = mavenDirs.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");
try (JarFile jarFile = new JarFile(file)) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
try {
if (entry.getName().endsWith("pom.xml")) {
try (InputStream is = jarFile.getInputStream(entry)) {
Document doc = builder.parse(is);
String groupId = xpath.evaluate("/project/groupId", doc);
if (groupId == null || "".equals(groupId)) {
groupId = xpath.evaluate("/project/parent/groupId", doc);
if (groupId == null || "".equals(groupId)) {
logger.warn("Can't work out groupId for pom: "+file+"!"+entry);
break;
}
}
String artifactId = xpath.evaluate("/project/artifactId", doc);
if (artifactId == null || "".equals(artifactId)) {
artifactId = xpath.evaluate("/project/parent/artifactId", doc);
if (artifactId == null || "".equals(artifactId)) {
logger.warn("Can't work out artifactId for pom: "+file+"!"+entry);
break;
}
}
String version = xpath.evaluate("/project/version", doc);
if (version == null || "".equals(version)) {
version = xpath.evaluate("/project/parent/version", doc);
if (version == null || "".equals(version)) {
logger.warn("Can't work out version for pom: "+file+"!"+entry);
break;
}
}
artifacts.put(groupId + ":" + artifactId, version);
}
}
}
catch (IOException e) {
logger.error("Can't resolve "+entry.getName());
throw e;
}
}
}
}
else {
logger.info("Can't examine maven artifact in non jar location: "+u);
}
}
}
@Override
public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {
return artifacts.get(attribute);
}
@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, artifacts.get(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() {
MBeanAttributeInfo[] attributes = new MBeanAttributeInfo[artifacts.size()];
int i=0;
for (String key : artifacts.keySet()) {
attributes[i++] = new MBeanAttributeInfo(key, "java.lang.String", "", true, false, false);
}
return new MBeanInfo(getClass().getName(), "", attributes, new MBeanConstructorInfo[0], new MBeanOperationInfo[0], new MBeanNotificationInfo[0]);
}
}