blob: 5ed99559be06f095a0df38a5cab7e96f5bf907be [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.felix.framework;
import org.apache.felix.framework.cache.ConnectContentContent;
import org.apache.felix.framework.cache.Content;
import org.apache.felix.framework.cache.DirectoryContent;
import org.apache.felix.framework.cache.JarContent;
import org.apache.felix.framework.ext.ClassPathExtenderFactory;
import org.apache.felix.framework.util.ClassParser;
import org.apache.felix.framework.util.FelixConstants;
import org.apache.felix.framework.util.StringMap;
import org.apache.felix.framework.util.Util;
import org.apache.felix.framework.util.manifestparser.ManifestParser;
import org.apache.felix.framework.util.manifestparser.NativeLibrary;
import org.apache.felix.framework.util.manifestparser.NativeLibraryClause;
import org.apache.felix.framework.wiring.BundleCapabilityImpl;
import org.apache.felix.framework.wiring.BundleRequirementImpl;
import org.apache.felix.framework.wiring.BundleWireImpl;
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
import org.osgi.framework.AdminPermission;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkEvent;
import org.osgi.framework.Version;
import org.osgi.framework.namespace.BundleNamespace;
import org.osgi.framework.namespace.ExecutionEnvironmentNamespace;
import org.osgi.framework.namespace.HostNamespace;
import org.osgi.framework.namespace.IdentityNamespace;
import org.osgi.framework.namespace.NativeNamespace;
import org.osgi.framework.wiring.BundleCapability;
import org.osgi.framework.wiring.BundleRequirement;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWire;
import org.osgi.framework.wiring.BundleWiring;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URI;
import java.net.URL;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* The ExtensionManager class is used as content loader of the systembundle. Added extension
* bundles exports will be available via this loader.
*/
class ExtensionManager implements Content
{
static final ClassPathExtenderFactory.ClassPathExtender m_extenderFramework;
static final ClassPathExtenderFactory.ClassPathExtender m_extenderBoot;
private static final Set<String> IDENTITY = new HashSet<String>(Arrays.asList(
BundleNamespace.BUNDLE_NAMESPACE,
HostNamespace.HOST_NAMESPACE,
IdentityNamespace.IDENTITY_NAMESPACE));
static
{
ClassPathExtenderFactory.ClassPathExtender extenderFramework = null;
ClassPathExtenderFactory.ClassPathExtender extenderBoot = null;
if (!"true".equalsIgnoreCase(Felix.m_secureAction.getSystemProperty(FelixConstants.FELIX_EXTENSIONS_DISABLE, "false")))
{
ServiceLoader<ClassPathExtenderFactory> loader = ServiceLoader.load(ClassPathExtenderFactory.class,
ExtensionManager.class.getClassLoader());
for (Iterator<ClassPathExtenderFactory> iter = loader.iterator();
iter.hasNext() && (extenderFramework == null || extenderBoot == null); )
{
try
{
ClassPathExtenderFactory factory = iter.next();
if (extenderFramework == null)
{
try
{
extenderFramework = factory.getExtender(ExtensionManager.class.getClassLoader());
}
catch (Throwable t)
{
// Ignore
}
}
if (extenderBoot == null)
{
try
{
extenderBoot = factory.getExtender(null);
}
catch (Throwable t)
{
// Ignore
}
}
}
catch (Throwable t)
{
// Ignore
}
}
try
{
if (extenderFramework == null)
{
extenderFramework = new ClassPathExtenderFactory.DefaultClassLoaderExtender()
.getExtender(ExtensionManager.class.getClassLoader());
}
}
catch (Throwable t) {
// Ignore
}
}
m_extenderFramework = extenderFramework;
m_extenderBoot = extenderBoot;
}
private final Logger m_logger;
private volatile ExtensionManagerRevision m_systemBundleRevision;
private final List<ExtensionTuple> m_extensionTuples = Collections.synchronizedList(new ArrayList<ExtensionTuple>());
private final List<BundleRevisionImpl> m_resolvedExtensions = new CopyOnWriteArrayList<BundleRevisionImpl>();
private final List<BundleRevisionImpl> m_unresolvedExtensions = new CopyOnWriteArrayList<BundleRevisionImpl>();
private final List<BundleRevisionImpl> m_failedExtensions = new CopyOnWriteArrayList<BundleRevisionImpl>();
private static class ExtensionTuple
{
private final BundleActivator m_activator;
private final Bundle m_bundle;
private volatile boolean m_failed;
private volatile boolean m_started;
public ExtensionTuple(BundleActivator activator, Bundle bundle)
{
m_activator = activator;
m_bundle = bundle;
}
}
/**
* This constructor is used to create one instance per framework instance.
* The general approach is to have one private static instance that we register
* with the parent classloader and one instance per framework instance that
* keeps track of extension bundles and systembundle exports for that framework
* instance.
*
* @param logger the logger to use.
*/
ExtensionManager(Logger logger, Map configMap, Felix felix)
{
m_logger = logger;
m_systemBundleRevision = new ExtensionManagerRevision(configMap, felix);
}
protected BundleCapability buildNativeCapabilites(BundleRevisionImpl revision, Map configMap) {
String osArchitecture = (String) configMap.get(FelixConstants.FRAMEWORK_PROCESSOR);
String osName = (String) configMap.get(FelixConstants.FRAMEWORK_OS_NAME);
String osVersion = (String) configMap.get(FelixConstants.FRAMEWORK_OS_VERSION);
String userLang = (String) configMap.get(FelixConstants.FRAMEWORK_LANGUAGE);
Map<String, Object> attributes = new HashMap<String, Object>();
//Add all startup properties so we can match selection-filters
attributes.putAll(configMap);
if( osArchitecture != null )
{
attributes.put(NativeNamespace.CAPABILITY_PROCESSOR_ATTRIBUTE, NativeLibraryClause.getProcessorWithAliases(osArchitecture));
}
if( osName != null)
{
attributes.put(NativeNamespace.CAPABILITY_OSNAME_ATTRIBUTE, NativeLibraryClause.getOsNameWithAliases(osName));
}
if( osVersion != null)
{
attributes.put(NativeNamespace.CAPABILITY_OSVERSION_ATTRIBUTE, new Version(NativeLibraryClause.normalizeOSVersion(osVersion)));
}
if( userLang != null)
{
attributes.put(NativeNamespace.CAPABILITY_LANGUAGE_ATTRIBUTE, userLang);
}
return new BundleCapabilityImpl(revision, NativeNamespace.NATIVE_NAMESPACE, Collections.<String, String> emptyMap(), attributes);
}
@IgnoreJRERequirement
void updateRevision(Felix felix, Map configMap)
{
Map config = new HashMap(configMap);
Properties defaultProperties = Util.loadDefaultProperties(m_logger);
Util.initializeJPMSEE(felix._getProperty("java.specification.version"), defaultProperties, m_logger);
String sysprops = felix._getProperty(Constants.FRAMEWORK_SYSTEMPACKAGES);
boolean subst = "true".equalsIgnoreCase(felix._getProperty(FelixConstants.USE_PROPERTY_SUBSTITUTION_IN_SYSTEMPACKAGES));
if (sysprops != null && sysprops.isEmpty())
{
if (felix.hasConnectFramework())
{
subst = true;
sysprops = "${osgi-exports}";
config.put(Constants.FRAMEWORK_SYSTEMPACKAGES, sysprops);
}
}
final Map<String, Set<String>> exports = Util.initializeJPMS(defaultProperties);
if (exports != null && (sysprops == null || "true".equalsIgnoreCase(felix._getProperty(FelixConstants.USE_PROPERTY_SUBSTITUTION_IN_SYSTEMPACKAGES))))
{
final ClassParser classParser = new ClassParser();
final Set<String> imports = new HashSet<String>();
for (Set<String> moduleImport : exports.values())
{
for (String pkg : moduleImport)
{
if (!pkg.startsWith("java."))
{
imports.add(pkg);
}
}
}
for (final String moduleKey : exports.keySet())
{
int idx = moduleKey.indexOf("@");
String module = idx == -1 ? moduleKey : moduleKey.substring(0, idx);
if (felix._getProperty(module) == null && !exports.get(moduleKey).isEmpty() && defaultProperties.getProperty(module) == null)
{
final SortedMap<String, SortedSet<String>> referred = new TreeMap<String, SortedSet<String>>();
if ("true".equalsIgnoreCase(felix._getProperty(FelixConstants.CALCULATE_SYSTEMPACKAGES_USES)))
{
java.nio.file.FileSystem fs = java.nio.file.FileSystems.getFileSystem(URI.create("jrt:/"));
try
{
Properties cachedProps = new Properties();
File modulesDir = felix.getDataFile(felix, "modules");
Felix.m_secureAction.mkdirs(modulesDir);
File cached = new File(modulesDir, moduleKey + ".properties");
if (Felix.m_secureAction.isFile(cached))
{
InputStream input = Felix.m_secureAction.getInputStream(cached);
cachedProps.load(new InputStreamReader(input, "UTF-8"));
input.close();
for (Enumeration<?> keys = cachedProps.propertyNames(); keys.hasMoreElements();)
{
String pkg = (String) keys.nextElement();
referred.put(pkg, new TreeSet<String>(Arrays.asList(cachedProps.getProperty(pkg).split(","))));
}
}
else
{
java.nio.file.Path path = fs.getPath("modules", module.substring("felix.jpms.".length()));
java.nio.file.Files.walkFileTree(path, (java.nio.file.FileVisitor) Felix.class.getClassLoader().loadClass("org.apache.felix.framework.util.ClassFileVisitor")
.getConstructor(Set.class, Set.class, ClassParser.class, SortedMap.class).newInstance(imports, exports.get(moduleKey), classParser, referred));
for (String pkg : referred.keySet())
{
SortedSet<String> uses = referred.get(pkg);
if (uses != null && !uses.isEmpty())
{
cachedProps.setProperty(pkg, String.join(",", uses));
}
}
OutputStream output = Felix.m_secureAction.getOutputStream(cached);
cachedProps.store(new OutputStreamWriter(output, "UTF-8"), null);
output.close();
}
}
catch (Throwable e)
{
m_logger.log(Logger.LOG_WARNING, "Exception calculating JPMS module exports", e);
}
}
String pkgs = "";
for (String pkg : exports.get(moduleKey))
{
pkgs += "," + pkg;
SortedSet<String> uses = referred.get(pkg);
if (uses != null && !uses.isEmpty())
{
pkgs += ";uses:=\"";
String sep = "";
for (String u : uses)
{
pkgs += sep + u;
sep = ",";
}
pkgs += "\"";
}
pkgs += ";version=\"" + defaultProperties.getProperty("felix.detect.java.version") + "\"";
}
defaultProperties.put(module, pkgs);
}
}
}
for (Map.Entry entry : defaultProperties.entrySet())
{
if (!config.containsKey(entry.getKey()))
{
config.put(entry.getKey(), entry.getValue());
}
}
if(sysprops != null && subst)
{
config.put(Constants.FRAMEWORK_SYSTEMPACKAGES, Util.getPropertyWithSubs(Util.toProperties(config), Constants.FRAMEWORK_SYSTEMPACKAGES));
}
else if (sysprops == null)
{
config.put(Constants.FRAMEWORK_SYSTEMPACKAGES, Util.getPropertyWithSubs(Util.toProperties(config), Constants.FRAMEWORK_SYSTEMPACKAGES));
}
String syspropsExtra = felix._getProperty(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA);
if (syspropsExtra != null && "true".equalsIgnoreCase(felix._getProperty(FelixConstants.USE_PROPERTY_SUBSTITUTION_IN_SYSTEMPACKAGES)))
{
config.put(Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA, Util.getPropertyWithSubs(Util.toProperties(config), Constants.FRAMEWORK_SYSTEMPACKAGES_EXTRA));
}
String syscaps = felix._getProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES);
if(syscaps != null && "true".equalsIgnoreCase(felix._getProperty(FelixConstants.USE_PROPERTY_SUBSTITUTION_IN_SYSTEMPACKAGES)))
{
config.put(Constants.FRAMEWORK_SYSTEMCAPABILITIES, Util.getPropertyWithSubs(Util.toProperties(config), Constants.FRAMEWORK_SYSTEMCAPABILITIES));
}
else if(syscaps == null)
{
config.put(Constants.FRAMEWORK_SYSTEMCAPABILITIES, Util.getPropertyWithSubs(Util.toProperties(config), Constants.FRAMEWORK_SYSTEMCAPABILITIES));
}
String syscapsExtra = felix._getProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES_EXTRA);
if (syscapsExtra != null && "true".equalsIgnoreCase(felix._getProperty(FelixConstants.USE_PROPERTY_SUBSTITUTION_IN_SYSTEMPACKAGES)))
{
config.put(Constants.FRAMEWORK_SYSTEMCAPABILITIES_EXTRA, Util.getPropertyWithSubs(Util.toProperties(config), Constants.FRAMEWORK_SYSTEMCAPABILITIES_EXTRA));
}
m_systemBundleRevision.update(config);
}
public BundleRevisionImpl getRevision()
{
return m_systemBundleRevision;
}
/**
* Add an extension bundle. The bundle will be added to the parent classloader
* and it's exported packages will be added to the module definition
* exports of this instance. Subsequently, they are available form the
* instance in it's role as content loader.
*
* @param bundle the extension bundle to add.
* @throws BundleException if extension bundles are not supported or this is
* not a framework extension.
* @throws SecurityException if the caller does not have the needed
* AdminPermission.EXTENSIONLIFECYCLE and security is enabled.
* @throws Exception in case something goes wrong.
*/
void addExtensionBundle(BundleImpl bundle) throws Exception
{
Object sm = System.getSecurityManager();
if (sm != null)
{
((SecurityManager) sm).checkPermission(
new AdminPermission(bundle, AdminPermission.EXTENSIONLIFECYCLE));
if (!((BundleProtectionDomain) bundle.getProtectionDomain()).impliesDirect(new AllPermission()))
{
throw new SecurityException("Extension Bundles must have AllPermission");
}
}
String directive = ManifestParser.parseExtensionBundleHeader((String)
((BundleRevisionImpl) bundle.adapt(BundleRevision.class))
.getHeaders().get(Constants.FRAGMENT_HOST));
Content content = bundle.adapt(BundleRevisionImpl.class).getContent();
final File file;
if (content instanceof JarContent)
{
file = ((JarContent) content).getFile();
}
else if (content instanceof DirectoryContent)
{
file = ((DirectoryContent) content).getFile();
}
else
{
file = null;
}
if (file == null && !(content instanceof ConnectContentContent))
{
// We don't support revision type for extension
m_logger.log(bundle, Logger.LOG_WARNING,
"Unable to add extension bundle - wrong revision type?");
throw new UnsupportedOperationException(
"Unable to add extension bundle.");
}
if (!Constants.EXTENSION_FRAMEWORK.equals(directive))
{
throw new BundleException("Unsupported Extension Bundle type: " +
directive, new UnsupportedOperationException(
"Unsupported Extension Bundle type!"));
}
else if (m_extenderFramework == null && file != null)
{
// We don't support extensions
m_logger.log(bundle, Logger.LOG_WARNING,
"Unable to add extension bundle - Maybe ClassLoader is not supported " +
"(on java9, try --add-opens=java.base/jdk.internal.loader=ALL-UNNAMED)?");
throw new UnsupportedOperationException(
"Unable to add extension bundle.");
}
BundleRevisionImpl bri = bundle.adapt(BundleRevisionImpl.class);
bri.resolve(null);
// we have to try again for all previously failed extensions because maybe they can now resolve.
m_unresolvedExtensions.addAll(m_failedExtensions);
m_failedExtensions.clear();
m_unresolvedExtensions.add(bri);
}
public synchronized List<Bundle> resolveExtensionBundles(Felix felix)
{
if (m_unresolvedExtensions.isEmpty())
{
return Collections.emptyList();
}
// Collect the highest version of unresolved that are not already resolved by bsn
List<BundleRevisionImpl> extensions = new ArrayList<BundleRevisionImpl>();
// Collect the unresolved that where filtered out as alternatives in case the highest version doesn't resolve
List<BundleRevisionImpl> alt = new ArrayList<BundleRevisionImpl>();
outer : for (BundleRevisionImpl revision : m_unresolvedExtensions)
{
// Already resolved by bsn?
for (BundleRevisionImpl existing : m_resolvedExtensions)
{
if (existing.getSymbolicName().equals(revision.getSymbolicName()))
{
// Then ignore it
continue outer;
}
}
// Otherwise, does a higher version exist by bsn?
for (BundleRevisionImpl other : m_unresolvedExtensions)
{
if ((revision != other) && (revision.getSymbolicName().equals(other.getSymbolicName())) &&
revision.getVersion().compareTo(other.getVersion()) < 0)
{
// Add this one to alternatives and filter it
alt.add(revision);
continue outer;
}
}
// no higher version and not resolved yet by bsn - try to resolve it
extensions.add(revision);
}
// This will return all resolvable revisions with the wires they need
Map<BundleRevisionImpl, List<BundleWire>> wirings = findResolvableExtensions(extensions, alt);
List<Bundle> result = new ArrayList<Bundle>();
for (Map.Entry<BundleRevisionImpl, List<BundleWire>> entry : wirings.entrySet())
{
BundleRevisionImpl revision = entry.getKey();
// move this revision from unresolved to resolved
m_unresolvedExtensions.remove(revision);
m_resolvedExtensions.add(revision);
BundleWire wire = new BundleWireImpl(revision,
revision.getDeclaredRequirements(BundleRevision.HOST_NAMESPACE).get(0),
m_systemBundleRevision, m_systemBundleRevision.getWiring().getCapabilities(BundleRevision.HOST_NAMESPACE).get(0));
try
{
revision.resolve(new BundleWiringImpl(m_logger, m_systemBundleRevision.m_configMap, null, revision, null,
Collections.singletonList(wire), Collections.EMPTY_MAP, Collections.EMPTY_MAP));
}
catch (Exception ex)
{
m_logger.log(revision.getBundle(), Logger.LOG_ERROR,
"Error resolving extension bundle : " + revision.getBundle(), ex);
}
felix.getDependencies().addDependent(wire);
List<BundleCapability> caps = new ArrayList<BundleCapability>();
for (BundleCapability cap : entry.getKey().getDeclaredCapabilities(null))
{
if (!IDENTITY.contains(cap.getNamespace()))
{
caps.add(cap);
}
}
m_systemBundleRevision.appendCapabilities(caps);
for (BundleWire w : entry.getValue())
{
if (!w.getRequirement().getNamespace().equals(BundleRevision.HOST_NAMESPACE) &&
!w.getRequirement().getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
((BundleWiringImpl) w.getRequirer().getWiring()).addDynamicWire(w);
felix.getDependencies().addDependent(w);
}
}
final File f;
Content revisionContent = revision.getContent();
if (revisionContent instanceof JarContent)
{
f = ((JarContent) revisionContent).getFile();
}
else if (revisionContent instanceof DirectoryContent)
{
f = ((DirectoryContent) revisionContent).getFile();
}
else
{
f = null;
}
if (f != null)
{
try
{
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>()
{
@Override
public Void run() throws Exception
{
m_extenderFramework.add(f);
return null;
}
});
}
catch (Exception ex)
{
m_logger.log(revision.getBundle(), Logger.LOG_ERROR,
"Error adding extension bundle to framework classloader: " + revision.getBundle(), ex);
}
}
felix.setBundleStateAndNotify(revision.getBundle(), Bundle.RESOLVED);
result.add(revision.getBundle());
}
// at this point, all revisions left in unresolved are not resolvable
m_failedExtensions.addAll(m_unresolvedExtensions);
m_unresolvedExtensions.clear();
return result;
}
/**
* Start extension bundle if it has an activator
*
* @param felix the framework instance the extension bundle is installed in.
* @param bundle the extension bundle to start if it has a an extension bundle activator.
*/
void startExtensionBundle(Felix felix, BundleImpl bundle)
{
Map<?,?> headers = bundle.adapt(BundleRevisionImpl.class).getHeaders();
String activatorClass = (String) headers.get(Constants.EXTENSION_BUNDLE_ACTIVATOR);
boolean felixExtension = false;
if (activatorClass == null)
{
felixExtension = true;
activatorClass = (String) headers.get(FelixConstants.FELIX_EXTENSION_ACTIVATOR);
}
if (activatorClass != null)
{
ExtensionTuple tuple = null;
try
{
// TODO: SECURITY - Should this consider security?
BundleActivator activator = (BundleActivator)
Felix.m_secureAction.getClassLoader(felix.getClass()).loadClass(
activatorClass.trim()).newInstance();
BundleContext context = felix._getBundleContext();
bundle.setBundleContext(context);
// TODO: EXTENSIONMANAGER - This is kind of hacky, can we improve it?
if (!felixExtension)
{
tuple = new ExtensionTuple(activator, bundle);
m_extensionTuples.add(tuple);
}
else
{
felix.m_activatorList.add(activator);
}
if ((felix.getState() == Bundle.ACTIVE) || (felix.getState() == Bundle.STARTING))
{
if (tuple != null)
{
tuple.m_started = true;
}
Felix.m_secureAction.startActivator(activator, context);
}
}
catch (Throwable ex)
{
if (tuple != null)
{
tuple.m_failed = true;
}
felix.fireFrameworkEvent(FrameworkEvent.ERROR, bundle,
new BundleException("Unable to start Bundle", ex));
m_logger.log(bundle, Logger.LOG_WARNING,
"Unable to start Extension Activator", ex);
}
}
}
void startPendingExtensionBundles(Felix felix)
{
for (int i = 0;i < m_extensionTuples.size();i++)
{
if (!m_extensionTuples.get(i).m_started)
{
m_extensionTuples.get(i).m_started = true;
try
{
Felix.m_secureAction.startActivator(m_extensionTuples.get(i).m_activator, felix._getBundleContext());
}
catch (Throwable ex)
{
m_extensionTuples.get(i).m_failed = true;
felix.fireFrameworkEvent(FrameworkEvent.ERROR, m_extensionTuples.get(i).m_bundle,
new BundleException("Unable to start Bundle", BundleException.ACTIVATOR_ERROR, ex));
m_logger.log(m_extensionTuples.get(i).m_bundle, Logger.LOG_WARNING,
"Unable to start Extension Activator", ex);
}
}
}
}
void stopExtensionBundles(Felix felix)
{
for (int i = m_extensionTuples.size() - 1; i >= 0;i--)
{
if (m_extensionTuples.get(i).m_started && !m_extensionTuples.get(i).m_failed)
{
try
{
Felix.m_secureAction.stopActivator(m_extensionTuples.get(i).m_activator, felix._getBundleContext());
}
catch (Throwable ex)
{
felix.fireFrameworkEvent(FrameworkEvent.ERROR, m_extensionTuples.get(i).m_bundle,
new BundleException("Unable to stop Bundle", BundleException.ACTIVATOR_ERROR, ex));
m_logger.log(m_extensionTuples.get(i).m_bundle, Logger.LOG_WARNING,
"Unable to stop Extension Activator", ex);
}
}
}
m_extensionTuples.clear();
}
public synchronized void removeExtensionBundles()
{
m_resolvedExtensions.clear();
m_unresolvedExtensions.clear();
m_failedExtensions.clear();
}
private Map<BundleRevisionImpl, List<BundleWire>> findResolvableExtensions(List<BundleRevisionImpl> extensions, List<BundleRevisionImpl> alt)
{
// The idea is to loop through the extensions and try to resolve all unresolved extension. If we can't resolve
// a given extension, we will call the method again with the extension in question removed or replaced if there
// is a replacement for it in alt.
// This resolve doesn't take into account that maybe a revision could be resolved with a revision from alt but
// not with the current extensions. In that case, it will be removed (assuming the current extension can be resolved)
// in other words, it will prefer to resolve the highest version of each extension over install order
Map<BundleRevisionImpl, List<BundleWire>> wires = new LinkedHashMap<BundleRevisionImpl, List<BundleWire>>();
for (BundleRevisionImpl bri : extensions)
{
List<BundleWire> wi = new ArrayList<BundleWire>();
boolean resolved = true;
outer: for (BundleRequirement req : bri.getDeclaredRequirements(null))
{
// first see if we can resolve from the system bundle
for (BundleCapability cap : m_systemBundleRevision.getWiring().getCapabilities(req.getNamespace()))
{
if (req.matches(cap))
{
// we can, create the wire but in the case of an ee requirement, make it from the extension
wi.add(new BundleWireImpl(
req.getNamespace().equals(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE) ?
bri : m_systemBundleRevision, req, m_systemBundleRevision, cap));
continue outer;
}
}
// now loop through the resolved extensions
for (BundleRevisionImpl extension : m_resolvedExtensions)
{
// and check the caps that will not be lifted (i.e., identity)
for (BundleCapability cap : extension.getDeclaredCapabilities(req.getNamespace()))
{
if (req.matches(cap))
{
// it was identity - hence, use the extension itself as provider
wi.add(new BundleWireImpl(m_systemBundleRevision, req, extension, cap));
continue outer;
}
}
}
// now loop through the other extensions
for (BundleRevisionImpl extension : extensions)
{
for (BundleCapability cap : extension.getDeclaredCapabilities(req.getNamespace()))
{
if (req.matches(cap))
{
// we can use a yet unresolved extension (resolved one are implicitly checked by the
// system bundle loop above as they would be attached.
wi.add(new BundleWireImpl(m_systemBundleRevision, req,
// lift identity
IDENTITY.contains(cap.getNamespace()) ?
extension : m_systemBundleRevision, cap));
continue outer;
}
}
// and check the caps that will not be lifted (i.e., identity)
for (BundleCapability cap : extension.getDeclaredCapabilities(req.getNamespace()))
{
if (req.matches(cap))
{
// it was identity - hence, use the extension itself as provider
wi.add(new BundleWireImpl(m_systemBundleRevision, req, extension, cap));
continue outer;
}
}
}
// we couldn't find a provider - was it optional?
if (!((BundleRequirementImpl)req).isOptional())
{
resolved = false;
break;
}
}
if(resolved)
{
wires.put(bri, wi);
}
else
{
// we failed to resolve this extension - try again without it. Yes, this throws away the work done
// up to this point
List<BundleRevisionImpl> next = new ArrayList<BundleRevisionImpl>(extensions);
List<BundleRevisionImpl> nextAlt = new ArrayList<BundleRevisionImpl>();
outer : for (BundleRevisionImpl replacement : alt)
{
if (bri.getSymbolicName().equals(replacement.getSymbolicName()))
{
for (BundleRevisionImpl other : alt)
{
if ((replacement != other) && (replacement.getSymbolicName().equals(other.getSymbolicName())) &&
replacement.getVersion().compareTo(other.getVersion()) < 0)
{
nextAlt.add(replacement);
continue outer;
}
}
next.set(next.indexOf(bri), replacement);
break;
}
nextAlt.add(replacement);
}
next.remove(bri);
return next.isEmpty() ? Collections.EMPTY_MAP : findResolvableExtensions(next, nextAlt);
}
}
return wires;
}
public void close()
{
// Do nothing on close, since we have nothing open.
}
public Enumeration getEntries()
{
return new Enumeration()
{
public boolean hasMoreElements()
{
return false;
}
public Object nextElement() throws NoSuchElementException
{
throw new NoSuchElementException();
}
};
}
public boolean hasEntry(String name) {
return false;
}
public byte[] getEntryAsBytes(String name)
{
return null;
}
public InputStream getEntryAsStream(String name) throws IOException
{
return null;
}
public Content getEntryAsContent(String name)
{
return null;
}
public String getEntryAsNativeLibrary(String name)
{
return null;
}
public URL getEntryAsURL(String name)
{
return null;
}
@Override
public long getContentTime(String name)
{
return -1L;
}
//
// Utility methods.
//
class ExtensionManagerRevision extends BundleRevisionImpl
{
private volatile Map m_configMap;
private final Map m_headerMap = new StringMap();
private volatile List<BundleCapability> m_capabilities = Collections.EMPTY_LIST;
private volatile Version m_version;
private volatile BundleWiring m_wiring;
ExtensionManagerRevision(Map configMap, Felix felix)
{
super(felix, "0");
m_configMap = configMap;
// TODO: FRAMEWORK - Not all of this stuff really belongs here
// Populate system bundle header map.
m_headerMap.put(FelixConstants.BUNDLE_VERSION,
m_configMap.get(FelixConstants.FELIX_VERSION_PROPERTY));
m_headerMap.put(FelixConstants.BUNDLE_SYMBOLICNAME,
FelixConstants.SYSTEM_BUNDLE_SYMBOLICNAME);
m_headerMap.put(FelixConstants.BUNDLE_NAME, "System Bundle");
m_headerMap.put(FelixConstants.BUNDLE_DESCRIPTION,
"This bundle is system specific; it implements various system services.");
m_headerMap.put(FelixConstants.EXPORT_SERVICE,
"org.osgi.service.packageadmin.PackageAdmin," +
"org.osgi.service.startlevel.StartLevel," +
"org.osgi.service.url.URLHandlers");
m_headerMap.put(FelixConstants.BUNDLE_MANIFESTVERSION, "2");
m_version = new Version((String) m_configMap.get(FelixConstants.FELIX_VERSION_PROPERTY));
try
{
ManifestParser mp = new ManifestParser(
m_logger, m_configMap, this, m_headerMap);
List<BundleCapability> caps = ManifestParser.aliasSymbolicName(mp.getCapabilities(), this);
caps.add(buildNativeCapabilites(this, m_configMap));
appendCapabilities(caps);
m_headerMap.put(Constants.EXPORT_PACKAGE, convertCapabilitiesToHeaders(caps));
}
catch (Exception ex)
{
m_capabilities = Collections.EMPTY_LIST;
m_logger.log(
Logger.LOG_ERROR,
"Error parsing system bundle statement", ex);
}
}
private void update(Map configMap)
{
Properties configProps = Util.toProperties(configMap);
// The system bundle exports framework packages as well as
// arbitrary user-defined packages from the system class path.
// We must construct the system bundle's export metadata.
// Get configuration property that specifies which class path
// packages should be exported by the system bundle.
String syspkgs = configProps.getProperty(FelixConstants.FRAMEWORK_SYSTEMPACKAGES);
syspkgs = (syspkgs == null) ? "" : syspkgs;
// If any extra packages are specified, then append them.
String pkgextra = configProps.getProperty(FelixConstants.FRAMEWORK_SYSTEMPACKAGES_EXTRA);
syspkgs = ((pkgextra == null) || (pkgextra.trim().length() == 0))
? syspkgs : syspkgs + (pkgextra.trim().startsWith(",") ? pkgextra : "," + pkgextra);
if (syspkgs.startsWith(","))
{
syspkgs = syspkgs.substring(1);
}
m_headerMap.put(FelixConstants.EXPORT_PACKAGE, syspkgs);
// The system bundle alsp provides framework generic capabilities
// as well as arbitrary user-defined generic capabilities. We must
// construct the system bundle's capabilities metadata. Get the
// configuration property that specifies which capabilities should
// be provided by the system bundle.
String syscaps = configProps.getProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES);
syscaps = (syscaps == null) ? "" : syscaps;
// If any extra capabilities are specified, then append them.
String capextra = configProps.getProperty(Constants.FRAMEWORK_SYSTEMCAPABILITIES_EXTRA);
syscaps = ((capextra == null) || (capextra.trim().length() == 0))
? syscaps : syscaps + (capextra.trim().startsWith(",") ? capextra : "," + capextra);
m_headerMap.put(FelixConstants.PROVIDE_CAPABILITY, syscaps);
try
{
ManifestParser mp = new ManifestParser(
m_logger, m_configMap, this, m_headerMap);
List<BundleCapability> caps = ManifestParser.aliasSymbolicName(mp.getCapabilities(), this);
caps.add(buildNativeCapabilites(this, m_configMap));
m_capabilities = Collections.EMPTY_LIST;
appendCapabilities(caps);
m_headerMap.put(Constants.EXPORT_PACKAGE, convertCapabilitiesToHeaders(caps));
}
catch (Exception ex)
{
m_capabilities = Collections.EMPTY_LIST;
m_logger.log(
Logger.LOG_ERROR,
"Error parsing system bundle statement.", ex);
}
}
private void appendCapabilities(List<BundleCapability> caps)
{
List<BundleCapability> newCaps = new ArrayList<BundleCapability>(m_capabilities.size() + caps.size());
newCaps.addAll(m_capabilities);
newCaps.addAll(caps);
m_capabilities = Util.newImmutableList(newCaps);
}
private String convertCapabilitiesToHeaders(List<BundleCapability> caps)
{
StringBuilder exportSB = new StringBuilder();
for (BundleCapability cap : caps)
{
if (cap.getNamespace().equals(BundleRevision.PACKAGE_NAMESPACE))
{
// Add a comma separate if there is an existing package.
if (exportSB.length() > 0)
{
exportSB.append(", ");
}
// Append exported package information.
exportSB.append(cap.getAttributes().get(BundleRevision.PACKAGE_NAMESPACE));
for (Entry<String, String> entry : cap.getDirectives().entrySet())
{
exportSB.append("; ");
exportSB.append(entry.getKey());
exportSB.append(":=\"");
exportSB.append(entry.getValue());
exportSB.append("\"");
}
for (Entry<String, Object> entry : cap.getAttributes().entrySet())
{
if (!entry.getKey().equals(BundleRevision.PACKAGE_NAMESPACE)
&& !entry.getKey().equals(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE)
&& !entry.getKey().equals(Constants.BUNDLE_VERSION_ATTRIBUTE))
{
exportSB.append("; ");
exportSB.append(entry.getKey());
exportSB.append("=\"");
exportSB.append(entry.getValue());
exportSB.append("\"");
}
}
}
}
return exportSB.toString();
}
@Override
public Map getHeaders()
{
return Util.newImmutableMap(m_headerMap);
}
@Override
public List<BundleCapability> getDeclaredCapabilities(String namespace)
{
List<BundleCapability> caps = m_capabilities;
List<BundleCapability> result;
if (namespace != null)
{
result = new ArrayList<BundleCapability>();
for (BundleCapability cap : caps)
{
if (cap.getNamespace().equals(namespace))
{
result.add(cap);
}
}
}
else
{
result = new ArrayList<BundleCapability>(m_capabilities);
}
return result;
}
@Override
public String getSymbolicName()
{
return FelixConstants.SYSTEM_BUNDLE_SYMBOLICNAME;
}
@Override
public Version getVersion()
{
return m_version;
}
@Override
public void close()
{
// Nothing needed here.
}
@Override
public Content getContent()
{
return ExtensionManager.this;
}
@Override
public URL getEntry(String name)
{
// There is no content for the system bundle, so return null.
return null;
}
@Override
public boolean hasInputStream(int index, String urlPath)
{
return (getClass().getClassLoader().getResource(urlPath) != null);
}
@Override
public InputStream getInputStream(int index, String urlPath)
{
return getClass().getClassLoader().getResourceAsStream(urlPath);
}
@Override
public URL getLocalURL(int index, String urlPath)
{
return getClass().getClassLoader().getResource(urlPath);
}
@Override
public void resolve(BundleWiringImpl wire)
{
try
{
m_wiring = new ExtensionManagerWiring(
m_logger, m_configMap, this);
}
catch (Exception ex)
{
// This should never happen.
}
}
@Override
public BundleWiring getWiring()
{
return m_wiring;
}
}
class ExtensionManagerWiring extends BundleWiringImpl
{
ExtensionManagerWiring(
Logger logger, Map configMap, BundleRevisionImpl revision)
throws Exception
{
super(logger, configMap, null, revision,
null, Collections.EMPTY_LIST, null, null);
}
@Override
public ClassLoader getClassLoader()
{
return getClass().getClassLoader();
}
@Override
public List<BundleCapability> getCapabilities(String namespace)
{
return m_systemBundleRevision.getDeclaredCapabilities(namespace);
}
@Override
public List<NativeLibrary> getNativeLibraries()
{
return Collections.EMPTY_LIST;
}
@Override
public Class getClassByDelegation(String name) throws ClassNotFoundException
{
return getClass().getClassLoader().loadClass(name);
}
@Override
public URL getResourceByDelegation(String name)
{
return getClass().getClassLoader().getResource(name);
}
@Override
public Enumeration getResourcesByDelegation(String name)
{
try
{
return getClass().getClassLoader().getResources(name);
}
catch (IOException ex)
{
return null;
}
}
@Override
public void dispose()
{
// Nothing needed here.
}
}
}