blob: 4f96ff60cada7c66d6e8c8857cca9a8feabe460a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 Boeing.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Boeing - initial API and implementation
*******************************************************************************/
package org.eclipse.osee.ote.internal;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.codehaus.jackson.annotate.JsonProperty;
import org.eclipse.core.runtime.Platform;
import org.eclipse.osee.framework.jdk.core.util.Lib;
import org.eclipse.osee.framework.jdk.core.util.xml.XMLStreamWriterUtil;
import org.eclipse.osee.framework.plugin.core.util.ExportClassLoader;
import org.eclipse.osee.ote.Configuration;
import org.eclipse.osee.ote.ConfigurationItem;
import org.eclipse.osee.ote.ConfigurationStatus;
import org.eclipse.osee.ote.OTEServerRuntimeCache;
import org.eclipse.osee.ote.OTEStatusCallback;
import org.eclipse.osee.ote.core.OseeURLClassLoader;
import org.eclipse.osee.ote.core.ServiceUtility;
import org.eclipse.osee.ote.core.environment.interfaces.IRuntimeLibraryManager;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkUtil;
/**
* When starting bundles users can specify an OTE-StartLevel: <int value> in the manifest of a bundle, if there are issues
* with the order in which bundles need to be started. The list of bundles to start will simply be sorted by that value,
* the default value is 0. This was implemented to work around a deadlock issue that can happen when you call Bundle.start()
* and there is a conflict with 'component resolving thread'. By waiting till the end to load the bundles that can cause the issue
* we avoid the deadlock issue which has a default 5 second time limit.
*
* @author Andrew M. Finkbeiner
*
*/
public class OTEBundleLoader implements IRuntimeLibraryManager{
public static final String MANIFEST_ENTRY_OTE_STARTLEVEL = "OTE-StartLevel";
private final List<Bundle> installedBundles;
private final Collection<Bundle> runningBundles;
private final Map<String, String> bundleNameToMd5Map;
private final BundleContext context;
private URLClassLoader scriptClassLoader;
private OTEServerRuntimeCache serverRuntimeCache;
public void bindOTEServerRuntimeCache(OTEServerRuntimeCache serverRuntimeCache){
this.serverRuntimeCache = serverRuntimeCache;
}
public void unbindOTEServerRuntimeCache(OTEServerRuntimeCache serverRuntimeCache){
this.serverRuntimeCache = serverRuntimeCache;
}
public OTEBundleLoader() {
this.context = FrameworkUtil.getBundle(getClass()).getBundleContext();
this.installedBundles = new LinkedList<>();
this.runningBundles = new LinkedList<>();
this.bundleNameToMd5Map = new HashMap<>();
}
@Override
public boolean install(Configuration oteConfiguration, OTEStatusCallback<ConfigurationStatus> statusCallback) {
boolean pass = true;
for (ConfigurationItem bundleDescription : oteConfiguration.getItems()) {
String bundleName = bundleDescription.getSymbolicName();
try {
boolean exists = false;
for (Bundle bundle : runningBundles) {
if (bundle.getSymbolicName().equals(bundleName)) {
exists = true;
break;
}
}
if (!exists) {
Bundle bundle = Platform.getBundle(bundleDescription.getSymbolicName());
if (bundle == null) {
Bundle installedBundle;
File bundleData = acquireSystemLibraryStream(bundleDescription);
installedBundle = context.installBundle("reference:" + bundleData.toURI().toURL().toExternalForm());
bundleNameToMd5Map.put(bundleName, bundleDescription.getMd5Digest());
installedBundles.add(installedBundle);
}
}
statusCallback.log("installed " + bundleName);
} catch (BundleException ex){
if(ex.getType() == BundleException.DUPLICATE_BUNDLE_ERROR){
statusCallback.log(String.format("Duplicate bundle [%s].", bundleName));
} else {
statusCallback.error(String.format("Unable to load [%s].", bundleName), ex);
pass = false;
}
} catch (Throwable th) {
statusCallback.error(String.format("Unable to load [%s].", bundleName), th);
pass = false;
} finally {
statusCallback.incrememtUnitsWorked(1);
}
}
return pass;
}
@Override
public boolean acquireBundles(Configuration oteConfiguration, OTEStatusCallback<ConfigurationStatus> statusCallback) {
boolean pass = true;
for (ConfigurationItem bundleDescription : oteConfiguration.getItems()) {
String bundleName = bundleDescription.getSymbolicName();
File bundleData = null;
try{
bundleData = acquireSystemLibraryStream(bundleDescription);
} catch (Exception e) {
pass = false;
statusCallback.error("Failed to aquire bundle from client.", e);
}
finally {
if(bundleData == null){
pass = false;
statusCallback.error("Failed to downloaded bundle " + bundleName);
}
statusCallback.incrememtUnitsWorked(1);
}
statusCallback.log("downloaded " + bundleName);
}
return pass;
}
@Override
public boolean start(OTEStatusCallback<ConfigurationStatus> statusCallback) {
boolean pass = true;
sortBundles(installedBundles);
Iterator<Bundle> iter = installedBundles.iterator();
while (iter.hasNext()) {
Bundle bundle = iter.next();
try {
String entry = bundle.getHeaders().get("Fragment-Host");
if (entry == null) {
int bundleState = bundle.getState();
if(bundleState != Bundle.ACTIVE) {
bundle.start();
}
}
// We got here because bundle.start did not exception
runningBundles.add(bundle);
iter.remove();
statusCallback.log("started " + bundle.getSymbolicName());
} catch (BundleException ex) {
pass = false;
statusCallback.error("Failed to start " + bundle.getSymbolicName(), ex);
} finally {
statusCallback.incrememtUnitsWorked(1);
}
}
return pass;
}
private void sortBundles(List<Bundle> installedBundles2) {
Collections.sort(installedBundles, new Comparator<Bundle>() {
@Override
public int compare(Bundle arg0, Bundle arg1) {
int startLevel0 = getStartLevel(arg0);
int startLevel1 = getStartLevel(arg1);
if(startLevel0 != 0 || startLevel1 != 0){
return startLevel0 - startLevel1;
} else {
return arg0.getSymbolicName().compareTo(arg1.getSymbolicName());
}
}
private int getStartLevel(Bundle arg0) {
String level = arg0.getHeaders().get(MANIFEST_ENTRY_OTE_STARTLEVEL);
if(level == null){
return 0;
} else {
try {
return Integer.parseInt(level);
} catch (NumberFormatException ex) {
return 0;
}
}
}
});
}
@Override
public boolean uninstall(OTEStatusCallback<ConfigurationStatus> statusCallback) {
boolean result = true;
for (Bundle bundle : installedBundles) {
try {
bundle.uninstall();
} catch (BundleException ex) {
result = false;
statusCallback.error("Failed to uninstall " + bundle.getSymbolicName(), ex);
}
}
installedBundles.clear();
for (Bundle bundle : runningBundles) {
try {
String entry = bundle.getHeaders().get("Fragment-Host");
if (entry == null) {
bundle.stop();
}
bundle.uninstall();
} catch (BundleException ex) {
result = false;
statusCallback.error("Failed to stop and uninstall " + bundle.getSymbolicName(), ex);
}
}
runningBundles.clear();
return result;
}
private File acquireSystemLibraryStream(ConfigurationItem bundleDescription) throws Exception {
File file = serverRuntimeCache.get(bundleDescription.getSymbolicName(), bundleDescription.getMd5Digest());
if (file == null) {
InputStream servedBundleIn = new URL(bundleDescription.getLocationUrl()).openConnection().getInputStream();
file = serverRuntimeCache.save(bundleDescription.getSymbolicName(), bundleDescription.getMd5Digest(), servedBundleIn);
servedBundleIn.close();
}
return file;
}
public void dispose() {
bundleNameToMd5Map.clear();
installedBundles.clear();
runningBundles.clear();
}
@Override
public boolean installed() {
return installedBundles.size() > 0 || runningBundles.size() > 0;
}
@Override
public void clearJarCache() {
serverRuntimeCache.clearJarCache();
}
@Override
public void resetScriptLoader(Configuration oteConfiguration, String[] classPaths) throws Exception {
List<URL> urls = new ArrayList<>();
if(oteConfiguration != null){
for(ConfigurationItem item:oteConfiguration.getItems()){
File file = serverRuntimeCache.get(item.getSymbolicName(), item.getMd5Digest());
if(file != null){
urls.add(file.toURI().toURL());
}
}
}
URL[] urlArray = urls.toArray(new URL[urls.size()+classPaths.length]);
URL[] urlClassPaths = Lib.getUrlFromString(classPaths);
System.arraycopy(urlClassPaths, 0, urlArray, urls.size(), urlClassPaths.length);
scriptClassLoader = new OseeURLClassLoader("Script ClassLoader", urlArray, ExportClassLoader.getInstance());
}
@Override
public Class<?> loadFromScriptClassLoader(String path) throws ClassNotFoundException {
if (scriptClassLoader == null) {
return loadFromRuntimeLibraryLoader(path);
}
Class<?> scriptClass = scriptClassLoader.loadClass(path);
return scriptClass;
}
@Override
public Class<?> loadFromRuntimeLibraryLoader(String clazz) throws ClassNotFoundException {
return ExportClassLoader.getInstance().loadClass(clazz);
}
@Override
public void toXml(XMLStreamWriter writer) throws XMLStreamException {
writer.writeStartElement("RuntimeVersions");
for (String bundleVersion : getBundleVersions()) {
XMLStreamWriterUtil.writeElement(writer, "Version", bundleVersion);
}
writer.writeEndElement();
}
@JsonProperty
public List<String> getBundleVersions() {
Bundle[] bundles = ServiceUtility.getContext().getBundles();
List<String> versions = new ArrayList<>();
for(Bundle bundle: bundles ){
String version = bundle.getHeaders().get("Bundle-Version");
String implVersion = bundle.getHeaders().get("Implementation-Version");
if (version != null && implVersion != null) {
versions.add(String.format("%s_%s_%s", bundle.getSymbolicName(), version, implVersion));
} else if (version != null){
versions.add(String.format("%s_%s", bundle.getSymbolicName(), version));
} else {
versions.add(String.format("%s", bundle.getSymbolicName()));
}
}
Collections.sort(versions);
return versions;
}
@Override
public ClassLoader getClassLoader() {
if (scriptClassLoader == null) {
return ExportClassLoader.getInstance();
} else {
return scriptClassLoader;
}
}
}