/*
* Copyright (C) 2011 Everit Kft. (http://everit.org)
*
* 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 org.everit.osgi.dev.maven.upgrade.jmx;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.JMX;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
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.apache.maven.plugin.logging.Log;
import org.everit.osgi.dev.maven.upgrade.BundleSNV;
import org.everit.osgi.dev.maven.upgrade.RemoteOSGiManager;
import org.everit.osgi.dev.maven.upgrade.RuntimeBundleInfo;
import org.osgi.framework.Bundle;
import org.osgi.jmx.framework.BundleStateMBean;
import org.osgi.jmx.framework.FrameworkMBean;
/**
* The JMX implementation of the {@link RemoteOSGiManager}.
*/
public class JMXOSGiManager implements RemoteOSGiManager {
private static final ObjectName BUNDLE_STATE_MBEAN_FILTER;
private static final ObjectName FRAMEWORK_MBEAN_MBEAN_FILTER;
static {
try {
FRAMEWORK_MBEAN_MBEAN_FILTER = new ObjectName(FrameworkMBean.OBJECTNAME + ",*");
BUNDLE_STATE_MBEAN_FILTER = new ObjectName(BundleStateMBean.OBJECTNAME + ",*");
} catch (MalformedObjectNameException e) {
throw new IllegalStateException(e);
}
}
private final UniqueIdBundleIdMap<BundleSNV> bundleIdByLocation;
private final BundleStateMBean bundleStateMBean;
private final FrameworkMBean frameworkMBean;
private final JMXConnector jmxConnector;
private final Log log;
/**
* Constructor.
*/
public JMXOSGiManager(final String jmxServiceURL, final Log log)
throws IOException, InstanceNotFoundException, IntrospectionException, ReflectionException {
this.log = log;
JMXServiceURL url = new JMXServiceURL(jmxServiceURL);
jmxConnector = JMXConnectorFactory.connect(url, null);
MBeanServerConnection mbsc = jmxConnector.getMBeanServerConnection();
Set<ObjectName> frameworkMBeanONs = mbsc.queryNames(FRAMEWORK_MBEAN_MBEAN_FILTER, null);
Set<ObjectName> bundleStateMBeanONs = mbsc.queryNames(BUNDLE_STATE_MBEAN_FILTER, null);
if (frameworkMBeanONs.size() == 0) {
throw new InstanceNotFoundException("Found the JVM that runs the OSGi container of the"
+ " environment, but no live incremental distribution is available. "
+ "Either stop the JVM before running distribution command, or install bundle(s) that"
+ " implement the OSGi Remote Service Admin specification. One solution can be if"
+ " you add org.apache.aries.jmx:org.apache.aries.jmx.core and"
+ " org.everit.osgi.jmx:org.everit.osgi.jmx.activator maven artifacts with "
+ "their dependencies to your project.");
}
if (bundleStateMBeanONs.size() > 1) {
throw new InstanceNotFoundException("Found the JVM for the environment, but live"
+ " distribution upgrade is not possible as multiple FrameworkMBean found on it. Please"
+ " be sure that only one bundle is installed on the system that implements the "
+ "OSGi Remote Admin Service specification or stop the JVM before running the "
+ "distribution upgrade.");
}
ObjectName framewotkMBeanON = frameworkMBeanONs.iterator().next();
ObjectName bundleStateMBeanON = bundleStateMBeanONs.iterator().next();
mbsc.getMBeanInfo(framewotkMBeanON);
mbsc.getMBeanInfo(bundleStateMBeanON);
frameworkMBean = JMX.newMBeanProxy(mbsc, framewotkMBeanON, FrameworkMBean.class);
bundleStateMBean = JMX.newMBeanProxy(mbsc, bundleStateMBeanON, BundleStateMBean.class);
bundleIdByLocation = getBundleIdByLocationMap();
}
@Override
public void close() {
try {
jmxConnector.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private boolean contains(final long[] dependencyClosure, final long bundleId) {
int searchResult = Arrays.binarySearch(dependencyClosure, bundleId);
return searchResult > 0;
}
private RuntimeBundleInfo convertCompositeDataToRuntimeBundleInfo(final Object value) {
CompositeData compositeData = (CompositeData) value;
Long bundleId = (Long) compositeData.get(BundleStateMBean.IDENTIFIER);
String symbolicName = (String) compositeData.get(BundleStateMBean.SYMBOLIC_NAME);
String version = (String) compositeData.get(BundleStateMBean.VERSION);
String location = (String) compositeData.get(BundleStateMBean.LOCATION);
String state = (String) compositeData.get(BundleStateMBean.STATE);
RuntimeBundleInfo runtimeBundleInfo =
new RuntimeBundleInfo(bundleId, symbolicName, version, location,
convertStringToIntState(state));
return runtimeBundleInfo;
}
private int convertStringToIntState(final String state) {
int result;
switch (state) {
case BundleStateMBean.ACTIVE:
result = Bundle.ACTIVE;
break;
case BundleStateMBean.INSTALLED:
result = Bundle.INSTALLED;
break;
case BundleStateMBean.RESOLVED:
result = Bundle.RESOLVED;
break;
case BundleStateMBean.STARTING:
result = Bundle.STARTING;
break;
case BundleStateMBean.STOPPING:
result = Bundle.STOPPING;
break;
case BundleStateMBean.UNINSTALLED:
result = Bundle.UNINSTALLED;
break;
default:
throw new RuntimeException("Unknown state: " + state);
}
return result;
}
private UniqueIdBundleIdMap<BundleSNV> getBundleIdByLocationMap() {
UniqueIdBundleIdMap<BundleSNV> result = new UniqueIdBundleIdMap<>();
TabularData tabularData;
try {
tabularData = bundleStateMBean.listBundles();
} catch (IOException e) {
throw new RuntimeException(e);
}
Collection<?> values = tabularData.values();
for (Object value : values) {
CompositeData compositeData = (CompositeData) value;
String symbolicName = (String) compositeData.get(BundleStateMBean.SYMBOLIC_NAME);
String version = (String) compositeData.get(BundleStateMBean.VERSION);
Long bundleIdentifier = (Long) compositeData.get(BundleStateMBean.IDENTIFIER);
result.put(new BundleSNV(symbolicName, version), bundleIdentifier);
}
return result;
}
@Override
public RuntimeBundleInfo[] getDependencyClosure(final Collection<BundleSNV> bundleSNVs) {
long[] bundleIds = resolveBundleIdentifiers(bundleSNVs, "getDependencyClosure");
try {
long[] dependencyClosure = frameworkMBean.getDependencyClosure(bundleIds);
if (dependencyClosure.length == 0) {
return new RuntimeBundleInfo[0];
}
Arrays.sort(dependencyClosure);
Collection<RuntimeBundleInfo> result = getRuntimeBundleInfoCollection();
for (Iterator<RuntimeBundleInfo> iterator = result.iterator(); iterator.hasNext();) {
RuntimeBundleInfo runtimeBundleInfo = iterator.next();
if (!contains(dependencyClosure, runtimeBundleInfo.bundleId)) {
iterator.remove();
}
}
return result.toArray(new RuntimeBundleInfo[0]);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public int getFrameworkStartLevel() {
try {
return frameworkMBean.getFrameworkStartLevel();
} catch (IOException e) {
throw new RuntimeException();
}
}
@Override
public int getInitialBundleStartLevel() {
try {
return frameworkMBean.getInitialBundleStartLevel();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public RuntimeBundleInfo[] getRuntimeBundleInfoArray() {
return getRuntimeBundleInfoCollection().toArray(new RuntimeBundleInfo[0]);
}
private Collection<RuntimeBundleInfo> getRuntimeBundleInfoCollection() {
TabularData tabularData;
try {
tabularData =
bundleStateMBean.listBundles(BundleStateMBean.IDENTIFIER, BundleStateMBean.SYMBOLIC_NAME,
BundleStateMBean.VERSION, BundleStateMBean.STATE);
} catch (IOException e) {
throw new RuntimeException(e);
}
List<RuntimeBundleInfo> result = new ArrayList<>();
Collection<?> values = tabularData.values();
for (Object value : values) {
RuntimeBundleInfo runtimeBundleInfo = convertCompositeDataToRuntimeBundleInfo(value);
result.add(runtimeBundleInfo);
}
return result;
}
@Override
public void installBundles(final Map<BundleSNV, String> bundleSNVLocationMap) {
if ((bundleSNVLocationMap == null) || (bundleSNVLocationMap.size() == 0)) {
return;
}
try {
for (Entry<BundleSNV, String> bundleSNVAndLocation : bundleSNVLocationMap.entrySet()) {
long bundleIdentifier = frameworkMBean.installBundle(bundleSNVAndLocation.getValue());
bundleIdByLocation.put(bundleSNVAndLocation.getKey(), bundleIdentifier);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void refresh() {
try {
frameworkMBean.refreshBundlesAndWait(null);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void resolveAll() {
try {
frameworkMBean.resolveBundles(null);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private long[] resolveBundleIdentifiers(final Collection<BundleSNV> bundleSNVs,
final String actionNameForMessages) {
long[] bundleIdentifierArray = new long[bundleSNVs.size()];
int i = 0;
for (BundleSNV bundleSNV : bundleSNVs) {
Long bundleIdentifier =
bundleIdByLocation.getBundleId(bundleSNV);
if (bundleIdentifier != null) {
bundleIdentifierArray[i] = bundleIdentifier;
i++;
} else {
log.warn("'" + actionNameForMessages
+ "' action cannot be executed on bundle as it is not found on the container: "
+ bundleSNV);
}
}
return (i == bundleSNVs.size()) ? bundleIdentifierArray
: Arrays.copyOf(bundleIdentifierArray, i);
}
@Override
public void setBundleStartLevel(final BundleSNV bundleSNV, final int newlevel) {
Long bundleIdentifier = bundleIdByLocation.getBundleId(bundleSNV);
if (bundleIdentifier == null) {
log.warn("Cannot set bundle start level as it is not found in the container: "
+ bundleSNV);
} else {
try {
frameworkMBean.setBundleStartLevel(bundleIdentifier, newlevel);
} catch (IOException e) {
throw new RuntimeException();
}
}
}
@Override
public void setFrameworkStartLevel(final int newlevel) {
try {
frameworkMBean.setFrameworkStartLevel(newlevel);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void setInitialBundleStartLevel(final int startLevel) {
try {
frameworkMBean.setInitialBundleStartLevel(startLevel);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void startBundles(final Collection<BundleSNV> bundleSNVs) {
long[] bundleIdentifiers = resolveBundleIdentifiers(bundleSNVs, "start");
if (bundleIdentifiers.length > 0) {
try {
frameworkMBean.startBundles(bundleIdentifiers);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void stopBundles(final Collection<BundleSNV> bundleSNVs) {
long[] bundleIdentifiers = resolveBundleIdentifiers(bundleSNVs, "stop");
if (bundleIdentifiers.length > 0) {
try {
frameworkMBean.stopBundles(bundleIdentifiers);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void uninstallBundles(final Collection<BundleSNV> bundleSNVs) {
if ((bundleSNVs == null) || (bundleSNVs.size() == 0)) {
return;
}
try {
for (BundleSNV bundleSNV : bundleSNVs) {
Long bundleIdentifier = bundleIdByLocation.getBundleId(bundleSNV);
if (bundleIdentifier == null) {
throw new RuntimeException("Could not uninstall bundle as it does not exist: "
+ bundleSNV);
}
frameworkMBean.uninstallBundle(bundleIdentifier);
bundleIdByLocation.removeByBundleId(bundleIdentifier);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void updateBundles(final Collection<BundleSNV> bundleSNVs) {
long[] bundleIdentifiers = resolveBundleIdentifiers(bundleSNVs, "stop");
if (bundleIdentifiers.length > 0) {
try {
frameworkMBean.updateBundles(bundleIdentifiers);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}