| /* |
| * 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; |
| } |
| |
| @Override |
| public boolean isDirectory(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. |
| } |
| } |
| } |