package com.liferay.ide.server.core.portal;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.TabularData;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import org.osgi.framework.Bundle;
import org.osgi.framework.dto.BundleDTO;
/**
* This class will try to connect to a remote OSGi framework using JMX and will
* deploy a bundle for you, by deploy, that means install the bundle if it
* doesn't existing in the remote runtime or update the bundle if it already
* exists. For the actual JMX connection it will use a port if you tell it to,
* or if not, it will try to use the JDK's attach API and search for the OSGi
* framework JMX beans. For the JDK attach API, beware, assumptions about the
* Oracle JDK directory layout have been made.
*/
public class JMXBundleDeployer {
private final static String OBJECTNAME = "osgi.core";
protected MBeanServerConnection mBeanServerConnection;
public JMXBundleDeployer() {
this(getLocalConnectorAddress());
}
public JMXBundleDeployer(int port) {
this("service:jmx:rmi:///jndi/rmi://:" + port + "/jmxrmi");
}
public JMXBundleDeployer(String serviceURL) {
try {
final JMXServiceURL jmxServiceUrl = new JMXServiceURL(serviceURL);
final JMXConnector jmxConnector = connectWithTimeout( jmxServiceUrl, 5, TimeUnit.SECONDS );
mBeanServerConnection = jmxConnector.getMBeanServerConnection();
} catch (Exception e) {
throw new IllegalArgumentException(
"Unable to get JMX connection", e);
}
}
JMXConnector connectWithTimeout( final JMXServiceURL url, long timeout, TimeUnit unit ) throws Exception
{
final BlockingQueue<Object> queue = new ArrayBlockingQueue<Object>( 1 );
final ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit( new Runnable()
{
public void run()
{
try
{
JMXConnector connector = JMXConnectorFactory.connect( url );
if( !queue.offer( connector ) )
{
connector.close();
}
}
catch( IOException e )
{
}
}
});
Object result = queue.poll( timeout, unit );
if( result == null )
{
if( !queue.offer( "" ) )
{
result = queue.take();
}
}
return (JMXConnector) result;
}
/**
* Gets the current list of installed bsns, compares it to the bsn provided.
* If bsn doesn't exist, then install it. If it does exist then update it.
*
* @param bsn
* Bundle-SymbolicName of bundle you are wanting to deploy
* @param bundle
* the bundle
* @return the id of the updated or installed bundle
* @throws Exception
*/
public long deploy(String bsn, String bundleUrl) throws Exception {
final ObjectName framework = getFramework(mBeanServerConnection);
long bundleId = -1;
for (BundleDTO osgiBundle : listBundles()) {
if (osgiBundle.symbolicName.equals(bsn)) {
bundleId = osgiBundle.id;
break;
}
}
// TODO serve bundle url over http so it works for non file:// urls
if (bundleId > -1) {
mBeanServerConnection.invoke(framework, "stopBundle",
new Object[] { bundleId }, new String[] { "long" });
mBeanServerConnection.invoke(framework, "updateBundleFromURL",
new Object[] { bundleId,
bundleUrl },
new String[] { "long", String.class.getName() });
mBeanServerConnection.invoke(framework, "refreshBundle",
new Object[] { bundleId }, new String[] { "long" });
} else {
Object installed = mBeanServerConnection.invoke(
framework,
"installBundleFromURL",
new Object[] { bundleUrl, bundleUrl },
new String[] { String.class.getName(),
String.class.getName() });
bundleId = Long.parseLong(installed.toString());
}
mBeanServerConnection.invoke(framework, "startBundle",
new Object[] { bundleId }, new String[] { "long" });
return bundleId;
}
private ObjectName getBundleState() throws MalformedObjectNameException, IOException {
return mBeanServerConnection.queryNames(new ObjectName(OBJECTNAME + ":type=bundleState,*"), null).iterator()
.next();
}
private static ObjectName getFramework(MBeanServerConnection mBeanServerConnection)
throws MalformedObjectNameException, IOException {
final ObjectName objectName = new ObjectName(OBJECTNAME + ":type=framework,*");
final Set<ObjectName> objectNames = mBeanServerConnection.queryNames(objectName, null);
if (objectNames != null && objectNames.size() > 0) {
return objectNames.iterator().next();
}
return null;
}
/**
* Calls osgi.core bundleState MBean listBundles operation
*
* @return array of bundles in framework
*/
public BundleDTO[] listBundles() {
final List<BundleDTO> retval = new ArrayList<BundleDTO>();
try {
final ObjectName bundleState = getBundleState();
final Object[] params = new Object[] {
new String[] {
"Identifier", "SymbolicName", "State", "Version",
}
};
final String[] signature = new String[] {
String[].class.getName()
};
final TabularData data = (TabularData) mBeanServerConnection.invoke(bundleState, "listBundles", params,
signature);
for (Object value : data.values()) {
final CompositeData cd = (CompositeData) value;
try {
retval.add(newFromData(cd));
}
catch (Exception e) {
e.printStackTrace();
}
}
}
catch (Exception e) {
e.printStackTrace();
}
return retval.toArray(new BundleDTO[0]);
}
private static BundleDTO newFromData(CompositeData cd) {
final BundleDTO dto = new BundleDTO();
dto.id = Long.parseLong(cd.get("Identifier").toString());
dto.symbolicName = cd.get("SymbolicName").toString();
String state = cd.get("State").toString();
if ("UNINSTALLED".equals(state)) {
dto.state = Bundle.UNINSTALLED;
} else if ("INSTALLED".equals(state)) {
dto.state = Bundle.INSTALLED;
} else if ("RESOLVED".equals(state)) {
dto.state = Bundle.RESOLVED;
} else if ("STARTING".equals(state)) {
dto.state = Bundle.STARTING;
} else if ("STOPPING".equals(state)) {
dto.state = Bundle.STOPPING;
} else if ("ACTIVE".equals(state)) {
dto.state = Bundle.ACTIVE;
}
dto.version = cd.get("Version").toString();
return dto;
}
/**
* Uninstall a bundle by passing in its Bundle-SymbolicName. If bundle
* doesn't exist, this is a NOP.
*
* @param bsn
* bundle symbolic name
* @throws Exception
*/
public void uninstall(String bsn) throws Exception {
for (BundleDTO osgiBundle : listBundles()) {
if (osgiBundle.symbolicName.equals(bsn)) {
uninstall(osgiBundle.id);
return;
}
}
throw new IllegalStateException("Unable to uninstall " + bsn);
}
/**
* Calls through directly to the OSGi frameworks MBean uninstallBundle
* operation
*
* @param id
* id of bundle to uninstall
* @throws Exception
*/
public void uninstall(long id) throws Exception {
final ObjectName framework = getFramework(mBeanServerConnection);
Object[] objects = new Object[] {
id
};
String[] params = new String[] {
"long"
};
mBeanServerConnection.invoke(framework, "uninstallBundle", objects, params);
}
/**
* Uses Oracle JDK's Attach API to try to search VMs on this machine looking
* for the osgi.core MBeans. This will stop searching for VMs once the
* MBeans are found. Beware if you have multiple JVMs with osgi.core MBeans
* published.
*
* @return
*/
@SuppressWarnings("unchecked")
static String getLocalConnectorAddress() {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
ClassLoader toolsClassloader = null;
try {
toolsClassloader = getToolsClassLoader(cl);
if (toolsClassloader != null) {
Thread.currentThread().setContextClassLoader(toolsClassloader);
Class< ? > vmClass = toolsClassloader.loadClass("com.sun.tools.attach.VirtualMachine");
Method listMethod = vmClass.getMethod("list");
List<Object> vmds = (List<Object>) listMethod.invoke(null);
for (Object vmd : vmds) {
try {
Class< ? > vmdClass = toolsClassloader
.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
Method idMethod = vmdClass.getMethod("id");
String id = (String) idMethod.invoke(vmd);
Method attachMethod = vmClass.getMethod("attach", String.class);
Object vm = attachMethod.invoke(null, id);
try {
Method getAgentPropertiesMethod = vmClass.getMethod("getAgentProperties");
Properties agentProperties = (Properties) getAgentPropertiesMethod.invoke(vm);
String localConnectorAddress = agentProperties
.getProperty("com.sun.management.jmxremote.localConnectorAddress");
if (localConnectorAddress == null) {
File agentJar = findJdkJar("management-agent.jar");
if (agentJar != null) {
Method loadAgent = vmClass.getMethod("loadAgent", String.class);
loadAgent.invoke(vm, agentJar.getCanonicalPath());
agentProperties = (Properties) getAgentPropertiesMethod.invoke(vm);
localConnectorAddress = agentProperties
.getProperty("com.sun.management.jmxremote.localConnectorAddress");
}
}
if (localConnectorAddress != null) {
final JMXServiceURL jmxServiceUrl = new JMXServiceURL(localConnectorAddress);
final JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxServiceUrl, null);
final MBeanServerConnection mBeanServerConnection = jmxConnector
.getMBeanServerConnection();
if (mBeanServerConnection != null) {
final ObjectName framework = getFramework(mBeanServerConnection);
if (framework != null) {
return localConnectorAddress;
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
Method detachMethod = vmClass.getMethod("detach");
detachMethod.invoke(vm);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
finally {
Thread.currentThread().setContextClassLoader(cl);
// try to get custom classloader to unload native libs
try {
if (toolsClassloader != null) {
Field nl = ClassLoader.class.getDeclaredField("nativeLibraries");
nl.setAccessible(true);
Vector< ? > nativeLibs = (Vector< ? >) nl.get(toolsClassloader);
for (Object nativeLib : nativeLibs) {
Field nameField = nativeLib.getClass().getDeclaredField("name");
nameField.setAccessible(true);
String name = (String) nameField.get(nativeLib);
if (new File(name).getName().contains("attach")) {
Method f = nativeLib.getClass().getDeclaredMethod("finalize");
f.setAccessible(true);
f.invoke(nativeLib);
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
private static ClassLoader getToolsClassLoader(ClassLoader parent) throws IOException {
File toolsJar = findJdkJar("tools.jar");
if (toolsJar != null && toolsJar.exists()) {
URL toolsUrl = null;
try {
toolsUrl = toolsJar.toURI().toURL();
}
catch (MalformedURLException e) {
//
}
URL[] urls = new URL[] {
toolsUrl
};
return new URLClassLoader(urls, parent);
}
return null;
}
static File findJdkJar(String jar) throws IOException {
File retval = null;
final String jarPath = File.separator + "lib" + File.separator + jar;
final String javaHome = System.getProperty("java.home");
File jarFile = new File(javaHome + jarPath);
if (jarFile.exists()) {
retval = jarFile;
} else {
jarFile = new File(javaHome + "/.." + jarPath);
if (jarFile.exists()) {
retval = jarFile.getCanonicalFile();
}
}
return retval;
}
}