| /******************************************************************************* |
| * Copyright (c) 2012, 2021 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.osgi.container; |
| |
| import java.security.AllPermission; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.function.Function; |
| import org.eclipse.osgi.internal.container.NamespaceList; |
| import org.eclipse.osgi.internal.container.NamespaceList.Builder; |
| import org.eclipse.osgi.internal.framework.FilterImpl; |
| import org.osgi.framework.AdminPermission; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleException; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.InvalidSyntaxException; |
| import org.osgi.framework.Version; |
| import org.osgi.framework.namespace.HostNamespace; |
| import org.osgi.framework.wiring.BundleRevision; |
| import org.osgi.resource.Namespace; |
| |
| /** |
| * A builder for creating module {@link ModuleRevision} objects. A builder can only be used by |
| * the module {@link ModuleContainer container} to build revisions when |
| * {@link ModuleContainer#install(Module, String, ModuleRevisionBuilder, Object) |
| * installing} or {@link ModuleContainer#update(Module, ModuleRevisionBuilder, Object) updating} a module. |
| * <p> |
| * The builder provides the instructions to the container for creating a {@link ModuleRevision}. |
| * They are not thread-safe; in the absence of external synchronization, they do not support concurrent access by multiple threads. |
| * @since 3.10 |
| */ |
| public final class ModuleRevisionBuilder { |
| private final static Class<?> SINGLETON_MAP_CLASS = Collections.singletonMap(null, null).getClass(); |
| private final static Class<?> UNMODIFIABLE_MAP_CLASS = Collections.unmodifiableMap(Collections.emptyMap()).getClass(); |
| |
| /** |
| * Provides information about a capability or requirement |
| */ |
| public static class GenericInfo { |
| final static Function<GenericInfo, String> GETNAMESPACE = new Function<GenericInfo, String>() { |
| public String apply(GenericInfo info) { |
| return info.getNamespace(); |
| } |
| }; |
| final String namespace; |
| final Map<String, String> directives; |
| final Map<String, Object> attributes; |
| final boolean mutable; |
| |
| GenericInfo(String namespace, Map<String, String> directives, Map<String, Object> attributes, boolean mutable) { |
| this.namespace = namespace; |
| this.directives = directives; |
| this.attributes = attributes; |
| this.mutable = mutable; |
| } |
| |
| /** |
| * Returns the namespace of this generic info |
| * @return the namespace |
| */ |
| public String getNamespace() { |
| return namespace; |
| } |
| |
| /** |
| * Returns the directives of this generic info |
| * @return the directives |
| */ |
| public Map<String, String> getDirectives() { |
| return directives; |
| } |
| |
| /** |
| * Returns the attributes of this generic info |
| * @return the attributes |
| */ |
| public Map<String, Object> getAttributes() { |
| return attributes; |
| } |
| } |
| |
| private String symbolicName = null; |
| private Version version = Version.emptyVersion; |
| private int types = 0; |
| private final NamespaceList.Builder<GenericInfo> capabilityInfos = Builder.create(GenericInfo.GETNAMESPACE); |
| private final NamespaceList.Builder<GenericInfo> requirementInfos = Builder.create(GenericInfo.GETNAMESPACE); |
| private long id = -1; |
| |
| /** |
| * Constructs a new module builder |
| */ |
| public ModuleRevisionBuilder() { |
| // nothing |
| } |
| |
| /** |
| * Sets the symbolic name for the builder |
| * @param symbolicName the symbolic name |
| */ |
| public void setSymbolicName(String symbolicName) { |
| this.symbolicName = symbolicName; |
| } |
| |
| /** |
| * Sets the module version for the builder. |
| * @param version the version |
| */ |
| public void setVersion(Version version) { |
| this.version = version; |
| } |
| |
| /** |
| * Sets the module types for the builder. |
| * @param types the module types |
| */ |
| public void setTypes(int types) { |
| this.types = types; |
| } |
| |
| /** |
| * Sets the module ID for the builder. |
| * <p> |
| * This module ID will be used if this builder is used to |
| * {@link ModuleContainer#install(Module, String, ModuleRevisionBuilder, Object) install} |
| * a module. If the ID is not set then a module ID will be generated by the module |
| * container at install time. If a module already exists with the specified ID |
| * then an error will occur when attempting to install a new module with this |
| * builder. |
| * <p> |
| * Note that the system module with location {@link Constants#SYSTEM_BUNDLE_LOCATION} |
| * always gets module ID of zero. The builder for the system module is not |
| * asked to provide the module ID for the system module at install time. |
| * @param id the module ID to use. Must be >= 1. |
| * @since 3.13 |
| */ |
| public void setId(long id) { |
| if (id < 1) { |
| throw new IllegalArgumentException("ID must be >=1."); //$NON-NLS-1$ |
| } |
| this.id = id; |
| } |
| |
| void setInternalId(long id) { |
| this.id = id; |
| } |
| |
| /** |
| * Adds a capability to this builder using the specified namespace, directives and attributes |
| * @param namespace the namespace of the capability |
| * @param directives the directives of the capability |
| * @param attributes the attributes of the capability |
| */ |
| public void addCapability(String namespace, Map<String, String> directives, Map<String, Object> attributes) { |
| addGenericInfo(capabilityInfos, namespace, directives, attributes); |
| } |
| |
| /** |
| * Returns a snapshot of the capabilities for this builder |
| * @return the capabilities |
| */ |
| public List<GenericInfo> getCapabilities() { |
| return getCapabilities(null); |
| } |
| |
| /** |
| * Returns a snapshot of the capabilities in the given namespace for this |
| * builder |
| * |
| * @param namespace The namespace of the capabilities to return or null to |
| * return the capabilities from all namespaces. |
| * @return the capabilities |
| * @since 3.17 |
| */ |
| public List<GenericInfo> getCapabilities(String namespace) { |
| return capabilityInfos.getNamespaceElements(namespace); |
| } |
| |
| /** |
| * Adds a requirement to this builder using the specified namespace, directives and attributes |
| * @param namespace the namespace of the requirement |
| * @param directives the directives of the requirement |
| * @param attributes the attributes of the requirement |
| */ |
| public void addRequirement(String namespace, Map<String, String> directives, Map<String, Object> attributes) { |
| addGenericInfo(requirementInfos, namespace, directives, attributes); |
| } |
| |
| /** |
| * Returns a snapshot of the requirements for this builder |
| * @return the requirements |
| */ |
| public List<GenericInfo> getRequirements() { |
| return getRequirements(null); |
| } |
| |
| NamespaceList.Builder<GenericInfo> getRequirementsBuilder() { |
| return requirementInfos; |
| } |
| |
| /** |
| * Returns a snapshot of the requirements in the given namespace for this |
| * builder |
| * |
| * @param namespace The namespace of the requirements to return or null to |
| * return the requirements from all namespaces. |
| * @return the requirements |
| * @since 3.17 |
| */ |
| public List<GenericInfo> getRequirements(String namespace) { |
| return requirementInfos.getNamespaceElements(namespace); |
| } |
| |
| /** |
| * Returns the symbolic name for this builder. |
| * @return the symbolic name for this builder. |
| */ |
| public String getSymbolicName() { |
| return symbolicName; |
| } |
| |
| /** |
| * Returns the module version for this builder. |
| * @return the module version for this builder. |
| */ |
| public Version getVersion() { |
| return version; |
| } |
| |
| /** |
| * Returns the module type for this builder. |
| * @return the module type for this builder. |
| */ |
| public int getTypes() { |
| return types; |
| } |
| |
| /** |
| * Returns the module id for this builder. A value of -1 |
| * indicates that the module ID will be generated by the |
| * module container at {@link ModuleContainer#install(Module, String, ModuleRevisionBuilder, Object) install} |
| * time. |
| * @return the module id for this builder. |
| * @since 3.13 |
| */ |
| public long getId() { |
| return id; |
| } |
| |
| /** |
| * Used by the container to build a new revision for a module. |
| * This builder is used to build a new {@link Module#getCurrentRevision() current} |
| * revision for the specified module. |
| * @param module the module to build a new revision for |
| * @param revisionInfo the revision info for the new revision, may be {@code null} |
| * @return the new new {@link Module#getCurrentRevision() current} revision. |
| */ |
| ModuleRevision addRevision(Module module, Object revisionInfo) { |
| ModuleRevisions revisions = module.getRevisions(); |
| ModuleRevision revision = new ModuleRevision(symbolicName, version, types, capabilityInfos, requirementInfos, revisions, revisionInfo); |
| |
| revisions.addRevision(revision); |
| module.getContainer().getAdaptor().associateRevision(revision, revisionInfo); |
| |
| try { |
| checkFrameworkExtensionPermission(module, revision); |
| module.getContainer().checkAdminPermission(module.getBundle(), AdminPermission.LIFECYCLE); |
| } catch (SecurityException e) { |
| revisions.removeRevision(revision); |
| throw e; |
| } |
| return revision; |
| } |
| |
| private void checkFrameworkExtensionPermission(Module module, ModuleRevision revision) { |
| if (System.getSecurityManager() == null) { |
| return; |
| } |
| if ((revision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) { |
| Collection<?> systemNames = Collections.emptyList(); |
| Module systemModule = module.getContainer().getModule(0); |
| if (systemModule != null) { |
| ModuleRevision systemRevision = systemModule.getCurrentRevision(); |
| List<ModuleCapability> hostCapabilities = systemRevision.getModuleCapabilities(HostNamespace.HOST_NAMESPACE); |
| for (ModuleCapability hostCapability : hostCapabilities) { |
| Object hostNames = hostCapability.getAttributes().get(HostNamespace.HOST_NAMESPACE); |
| if (hostNames instanceof Collection) { |
| systemNames = (Collection<?>) hostNames; |
| } else if (hostNames instanceof String) { |
| systemNames = Arrays.asList(hostNames); |
| } |
| } |
| } |
| List<ModuleRequirement> hostRequirements = revision.getModuleRequirements(HostNamespace.HOST_NAMESPACE); |
| for (ModuleRequirement hostRequirement : hostRequirements) { |
| FilterImpl f = null; |
| String filterSpec = hostRequirement.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE); |
| if (filterSpec != null) { |
| try { |
| f = FilterImpl.newInstance(filterSpec); |
| String hostName = f.getPrimaryKeyValue(HostNamespace.HOST_NAMESPACE); |
| if (hostName != null) { |
| if (systemNames.contains(hostName)) { |
| Bundle b = module.getBundle(); |
| if (b != null && !b.hasPermission(new AllPermission())) { |
| SecurityException se = new SecurityException( |
| "Must have AllPermission granted to install an extension bundle: " + b); //$NON-NLS-1$ |
| // TODO this is such a hack: making the cause a bundle exception so we can throw the right one later |
| BundleException be = new BundleException(se.getMessage(), BundleException.SECURITY_ERROR, se); |
| se.initCause(be); |
| throw se; |
| } |
| module.getContainer().checkAdminPermission(module.getBundle(), AdminPermission.EXTENSIONLIFECYCLE); |
| } |
| } |
| } catch (InvalidSyntaxException e) { // ignore |
| } |
| } |
| } |
| } |
| } |
| |
| private void addGenericInfo(NamespaceList.Builder<GenericInfo> infos, String namespace, Map<String, String> directives, Map<String, Object> attributes) { |
| infos.add(new GenericInfo(namespace, directives, attributes, true)); |
| } |
| |
| void basicAddCapability(String namespace, Map<String, String> directives, Map<String, Object> attributes) { |
| basicAddGenericInfo(capabilityInfos, namespace, directives, attributes); |
| } |
| |
| void basicAddRequirement(String namespace, Map<String, String> directives, Map<String, Object> attributes) { |
| basicAddGenericInfo(requirementInfos, namespace, directives, attributes); |
| } |
| |
| private static void basicAddGenericInfo(NamespaceList.Builder<GenericInfo> infos, String namespace, Map<String, String> directives, Map<String, Object> attributes) { |
| infos.add(new GenericInfo(namespace, unmodifiableMap(directives), unmodifiableMap(attributes), false)); |
| } |
| |
| @SuppressWarnings("unchecked") |
| static <K, V> Map<K, V> unmodifiableMap(Map<? extends K, ? extends V> map) { |
| int size = map.size(); |
| if (size == 0) { |
| return Collections.emptyMap(); |
| } |
| if (size == 1) { |
| if (map.getClass() != SINGLETON_MAP_CLASS) { |
| Map.Entry<? extends K, ? extends V> entry = map.entrySet().iterator().next(); |
| map = Collections.singletonMap(entry.getKey(), entry.getValue()); |
| } |
| } else { |
| if (map.getClass() != UNMODIFIABLE_MAP_CLASS) { |
| map = Collections.unmodifiableMap(map); |
| } |
| } |
| return (Map<K, V>) map; |
| } |
| |
| void clear() { |
| capabilityInfos.clear(); |
| requirementInfos.clear(); |
| id = -1; |
| symbolicName = null; |
| version = Version.emptyVersion; |
| types = 0; |
| } |
| } |