/******************************************************************************* * Copyright (c) 2012, 2016 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.osgi.container; import java.security.AllPermission; import java.util.*; import org.eclipse.osgi.internal.framework.FilterImpl; import org.osgi.framework.*; import org.osgi.framework.namespace.HostNamespace; 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 String namespace; final Map<String, String> directives; final Map<String, Object> attributes; GenericInfo(String namespace, Map<String, String> directives, Map<String, Object> attributes) { this.namespace = namespace; this.directives = directives; this.attributes = attributes; } /** * 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 List<GenericInfo> capabilityInfos = new ArrayList<>(); private final List<GenericInfo> requirementInfos = new ArrayList<>(); /** * 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; } /** * 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 new ArrayList<>(capabilityInfos); } /** * 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 new ArrayList<>(requirementInfos); } /** * 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; } /** * 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) { 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); } } } 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 { 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"); //$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) { continue; } } } module.getContainer().checkAdminPermission(module.getBundle(), AdminPermission.LIFECYCLE); } catch (SecurityException e) { revisions.removeRevision(revision); throw e; } return revision; } private static void addGenericInfo(List<GenericInfo> infos, String namespace, Map<String, String> directives, Map<String, Object> attributes) { if (infos == null) { infos = new ArrayList<>(); } infos.add(new GenericInfo(namespace, copyUnmodifiableMap(directives), copyUnmodifiableMap(attributes))); } private static <K, V> Map<K, V> copyUnmodifiableMap(Map<K, V> map) { int size = map.size(); if (size == 0) { return Collections.emptyMap(); } if (size == 1) { Map.Entry<K, V> entry = map.entrySet().iterator().next(); return Collections.singletonMap(entry.getKey(), entry.getValue()); } return Collections.unmodifiableMap(new HashMap<>(map)); } 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(List<GenericInfo> infos, String namespace, Map<String, String> directives, Map<String, Object> attributes) { if (infos == null) { infos = new ArrayList<>(); } infos.add(new GenericInfo(namespace, unmodifiableMap(directives), unmodifiableMap(attributes))); } @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.<K, V> singletonMap(entry.getKey(), entry.getValue()); } } else { if (map.getClass() != UNMODIFIABLE_MAP_CLASS) { map = Collections.unmodifiableMap(map); } } return (Map<K, V>) map; } }