org.eclipse.osgi.container.ModuleRevisionBuilder Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2012, 2017 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.
*
* 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 directives;
final Map attributes;
GenericInfo(String namespace, Map directives, Map 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 getDirectives() {
return directives;
}
/**
* Returns the attributes of this generic info
* @return the attributes
*/
public Map getAttributes() {
return attributes;
}
}
private String symbolicName = null;
private Version version = Version.emptyVersion;
private int types = 0;
private final List capabilityInfos = new ArrayList<>();
private final List requirementInfos = new ArrayList<>();
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.
*
* 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.
*
* 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 directives, Map attributes) {
addGenericInfo(capabilityInfos, namespace, directives, attributes);
}
/**
* Returns a snapshot of the capabilities for this builder
* @return the capabilities
*/
public List 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 directives, Map attributes) {
addGenericInfo(requirementInfos, namespace, directives, attributes);
}
/**
* Returns a snapshot of the requirements for this builder
* @return the requirements
*/
public List 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;
}
/**
* 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) {
Collection> systemNames = Collections.emptyList();
Module systemModule = module.getContainer().getModule(0);
if (systemModule != null) {
ModuleRevision systemRevision = systemModule.getCurrentRevision();
List 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 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 infos, String namespace, Map directives, Map attributes) {
if (infos == null) {
infos = new ArrayList<>();
}
infos.add(new GenericInfo(namespace, copyUnmodifiableMap(directives), copyUnmodifiableMap(attributes)));
}
private static Map copyUnmodifiableMap(Map map) {
int size = map.size();
if (size == 0) {
return Collections.emptyMap();
}
if (size == 1) {
Map.Entry entry = map.entrySet().iterator().next();
return Collections.singletonMap(entry.getKey(), entry.getValue());
}
return Collections.unmodifiableMap(new HashMap<>(map));
}
void basicAddCapability(String namespace, Map directives, Map attributes) {
basicAddGenericInfo(capabilityInfos, namespace, directives, attributes);
}
void basicAddRequirement(String namespace, Map directives, Map attributes) {
basicAddGenericInfo(requirementInfos, namespace, directives, attributes);
}
private static void basicAddGenericInfo(List infos, String namespace, Map directives, Map attributes) {
if (infos == null) {
infos = new ArrayList<>();
}
infos.add(new GenericInfo(namespace, unmodifiableMap(directives), unmodifiableMap(attributes)));
}
@SuppressWarnings("unchecked")
static Map 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) map;
}
}