diff options
author | Thomas Watson | 2012-06-07 17:14:24 +0000 |
---|---|---|
committer | Thomas Watson | 2012-06-07 17:14:24 +0000 |
commit | 883f1251263debbff303100af87e8b5e5ce872ac (patch) | |
tree | cdf46f9a380638008904aa02422b6af19ab2d98d /bundles/org.eclipse.osgi/container/src/org | |
parent | 499ac70cc96fd37279f31bb8b700f9c6b7778391 (diff) | |
download | rt.equinox.framework-883f1251263debbff303100af87e8b5e5ce872ac.tar.gz rt.equinox.framework-883f1251263debbff303100af87e8b5e5ce872ac.tar.xz rt.equinox.framework-883f1251263debbff303100af87e8b5e5ce872ac.zip |
Move the container impl to org.eclipse.osgi
Diffstat (limited to 'bundles/org.eclipse.osgi/container/src/org')
21 files changed, 6394 insertions, 0 deletions
diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/Module.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/Module.java new file mode 100644 index 000000000..b950a49a0 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/Module.java @@ -0,0 +1,645 @@ +/******************************************************************************* + * Copyright (c) 2012 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.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; +import org.eclipse.osgi.container.namespaces.EquinoxModuleDataNamespace; +import org.osgi.framework.*; +import org.osgi.framework.startlevel.BundleStartLevel; +import org.osgi.resource.Capability; +import org.osgi.service.resolver.ResolutionException; + +/** + * A module represents a set of revisions installed in a + * module {@link ModuleContainer container}. + */ +public abstract class Module implements BundleReference, BundleStartLevel, Comparable<Module> { + /** + * The possible start options for a module + */ + public static enum StartOptions { + /** + * The module start operation is transient and the persistent + * autostart or activation policy setting of the module is not modified. + */ + TRANSIENT, + /** + * The module start operation must activate the module according to the module's declared + * activation policy. + */ + USE_ACTIVATION_POLICY, + /** + * The module start operation is transient and the persistent activation policy + * setting will be used. + */ + TRANSIENT_RESUME, + /** + * The module start operation is transient and will only happen if {@link Settings#AUTO_START auto start} + * setting is persistent. + */ + TRANSIENT_IF_AUTO_START, + /** + * The module start operation that indicates the module is being started because of a + * lazy start trigger class load. This option must be used with the + * {@link StartOptions#TRANSIENT transient} options. + */ + LAZY_TRIGGER; + + /** + * Tests if this option is contained in the specified options + */ + public boolean isContained(StartOptions... options) { + for (StartOptions option : options) { + if (equals(option)) { + return true; + } + } + return false; + } + } + + /** + * The possible start options for a module + */ + public static enum StopOptions { + /** + * The module stop operation is transient and the persistent + * autostart setting of the module is not modified. + */ + TRANSIENT; + + /** + * Tests if this option is contained in the specified options + */ + public boolean isContained(StopOptions... options) { + for (StopOptions option : options) { + if (equals(option)) { + return true; + } + } + return false; + } + } + + /** + * An enumeration of the possible {@link Module#getState() states} a module may be in. + */ + public static enum State { + /** + * The module is installed but not yet resolved. + */ + INSTALLED, + /** + * The module is resolved and able to be started. + */ + RESOLVED, + /** + * The module is waiting for a {@link StartOptions#LAZY_TRIGGER trigger} + * class load to proceed with starting. + */ + LAZY_STARTING, + /** + * The module is in the process of starting. + */ + STARTING, + /** + * The module is now running. + */ + ACTIVE, + /** + * The module is in the process of stopping + */ + STOPPING, + /** + * The module is uninstalled and may not be used. + */ + UNINSTALLED + } + + /** + * Event types that may be {@link Module#publishEvent(Event) published} for a module + * indicating a {@link Module#getState() state} change has occurred for a module. + */ + public static enum Event { + /** + * The module has been installed + */ + INSTALLED, + /** + * The module has been activated with the lazy activation policy and + * is waiting a {@link StartOptions#LAZY_TRIGGER trigger} class load. + */ + LAZY_ACTIVATION, + /** + * The module has been resolved. + */ + RESOLVED, + /** + * The module has beens started. + */ + STARTED, + /** + * The module is about to be activated. + */ + STARTING, + /** + * The module has been stopped. + */ + STOPPED, + /** + * The module is about to be deactivated. + */ + STOPPING, + /** + * The module has been uninstalled. + */ + UNINSTALLED, + /** + * The module has been unresolved. + */ + UNRESOLVED, + /** + * The module has been updated. + */ + UPDATED + } + + /** + * An enumeration of persistent settings for a module + */ + public static enum Settings { + /** + * The module has been set to auto start. + */ + AUTO_START, + /** + * The module has been set to use its activation policy + */ + USE_ACTIVATION_POLICY, + } + + /** + * A set of {@link State states} that indicate a module is active. + */ + public static final EnumSet<State> ACTIVE_SET = EnumSet.of(State.STARTING, State.LAZY_STARTING, State.ACTIVE, State.STOPPING); + /** + * A set of {@link State states} that indicate a module is resolved. + */ + public static final EnumSet<State> RESOLVED_SET = EnumSet.of(State.RESOLVED, State.STARTING, State.LAZY_STARTING, State.ACTIVE, State.STOPPING); + + private final Long id; + private final String location; + private final ModuleRevisions revisions; + private final ReentrantLock stateChangeLock = new ReentrantLock(); + private final EnumSet<Event> stateTransitionEvents = EnumSet.noneOf(Event.class); + private final EnumSet<Settings> settings; + private volatile State state = State.INSTALLED; + private volatile int startlevel; + private volatile long lastModified; + + /** + * Constructs a new module with the specified id, location and + * container. + * @param id the new module id + * @param location the new module location + * @param container the container for the new module + * @param settings the persisted settings. May be {@code null} if there are no settings. + * @param startlevel the persisted start level or initial start level. + */ + public Module(Long id, String location, ModuleContainer container, EnumSet<Settings> settings, int startlevel) { + this.id = id; + this.location = location; + this.revisions = new ModuleRevisions(this, container); + this.settings = settings == null ? EnumSet.noneOf(Settings.class) : settings; + this.startlevel = startlevel; + } + + /** + * Returns the module id. + * @return the module id. + */ + public Long getId() { + return id; + } + + /** Returns the module location + * @return the module location + */ + public String getLocation() { + return location; + } + + /** + * Returns the {@link ModuleRevisions} associated with this module. + * @return the {@link ModuleRevisions} associated with this module + */ + public final ModuleRevisions getRevisions() { + return revisions; + } + + /** + * Returns the current {@link ModuleRevision revision} associated with this module. + * If the module is uninstalled then {@code null} is returned. + * @return the current {@link ModuleRevision revision} associated with this module. + */ + public final ModuleRevision getCurrentRevision() { + return revisions.getCurrentRevision(); + } + + /** + * Returns the current {@link State state} of this module. + * @return the current state of this module. + */ + public State getState() { + return state; + } + + void setState(State state) { + this.state = state; + } + + @Override + public int getStartLevel() { + return this.startlevel; + } + + @Override + public void setStartLevel(int startLevel) { + revisions.getContainer().setStartLevel(this, startLevel); + } + + @Override + public boolean isPersistentlyStarted() { + return settings.contains(Settings.AUTO_START); + } + + @Override + public boolean isActivationPolicyUsed() { + return settings.contains(Settings.USE_ACTIVATION_POLICY); + } + + void storeStartLevel(int newStartLevel) { + this.startlevel = newStartLevel; + } + + /** + * Returns the time when this module was last modified. A module is considered + * to be modified when it is installed, updated or uninstalled. + * <p> + * The time value is a the number of milliseconds since January 1, 1970, 00:00:00 UTC. + * @return the time when this bundle was last modified. + */ + public long getLastModified() { + return this.lastModified; + } + + void setlastModified(long lastModified) { + this.lastModified = lastModified; + } + + private static final EnumSet<Event> VALID_RESOLVED_TRANSITION = EnumSet.of(Event.STARTED); + private static final EnumSet<Event> VALID_STOPPED_TRANSITION = EnumSet.of(Event.UPDATED, Event.UNRESOLVED, Event.UNINSTALLED); + + /** + * Acquires the module lock for state changes by the current thread for the specified + * transition event. Certain transition events locks may be nested within other + * transition event locks. For example, a resolved transition event lock may be + * nested within a started transition event lock. A stopped transition lock + * may be nested within an updated, unresolved or uninstalled transition lock. + * @param transitionEvent the transition event to acquire the lock for. + * @throws BundleException + */ + protected void lockStateChange(Event transitionEvent) throws BundleException { + try { + boolean acquired = stateChangeLock.tryLock(5, TimeUnit.SECONDS); + if (acquired) { + boolean isValidTransition = true; + switch (transitionEvent) { + case STARTED : + case UPDATED : + case UNINSTALLED : + case UNRESOLVED : + // These states must be initiating transition states + // no other transition state is allowed when these are kicked off + isValidTransition = stateTransitionEvents.isEmpty(); + break; + case RESOLVED : + isValidTransition = VALID_RESOLVED_TRANSITION.containsAll(stateTransitionEvents); + break; + case STOPPED : + isValidTransition = VALID_STOPPED_TRANSITION.containsAll(stateTransitionEvents); + break; + default : + isValidTransition = false; + break; + } + if (!isValidTransition) { + stateChangeLock.unlock(); + } else { + stateTransitionEvents.add(transitionEvent); + return; + } + } + throw new BundleException("Unable to acquire the state change lock for the module: " + transitionEvent, BundleException.STATECHANGE_ERROR); + } catch (InterruptedException e) { + throw new BundleException("Unable to acquire the state change lock for the module.", BundleException.STATECHANGE_ERROR, e); + } + } + + /** + * Releases the lock for state changes for the specified transition event. + * @param transitionEvent + */ + protected void unlockStateChange(Event transitionEvent) { + if (stateChangeLock.getHoldCount() == 0 || !stateTransitionEvents.contains(transitionEvent)) + throw new IllegalMonitorStateException("Current thread does not hold the state change lock for: " + transitionEvent); + stateTransitionEvents.remove(transitionEvent); + stateChangeLock.unlock(); + } + + /** + * Returns true if the current thread holds the state change lock for the specified transition event. + * @param transitionEvent + * @return true if the current thread holds the state change lock for the specified transition event. + */ + protected boolean holdsTransitionEventLock(Event transitionEvent) { + return stateChangeLock.getHoldCount() > 0 && stateTransitionEvents.contains(transitionEvent); + } + + /** + * Starts this module + * @param options the options for starting + * @throws BundleException if an errors occurs while starting + */ + public void start(StartOptions... options) throws BundleException { + revisions.getContainer().checkAdminPermission(getBundle(), AdminPermission.EXECUTE); + if (options == null) { + options = new StartOptions[0]; + } + Event event; + if (StartOptions.LAZY_TRIGGER.isContained(options)) { + if (stateChangeLock.getHoldCount() > 0 && stateTransitionEvents.contains(Event.STARTED)) { + // nothing to do here; the current thread is activating the bundle. + } + } + BundleException startError = null; + lockStateChange(Event.STARTED); + try { + checkValid(); + if (StartOptions.TRANSIENT_IF_AUTO_START.isContained(options) && !settings.contains(Settings.AUTO_START)) { + // Do nothing + return; + } + persistStartOptions(options); + if (getStartLevel() > getRevisions().getContainer().getStartLevel()) { + if (StartOptions.TRANSIENT.isContained(options)) { + throw new BundleException("Cannot transiently start a module whose start level is not met.", BundleException.START_TRANSIENT_ERROR); + } + // DO nothing + return; + } + // TODO need a check to see if the current revision is valid for start (e.g. is fragment). + if (State.ACTIVE.equals(getState())) + return; + if (getState().equals(State.INSTALLED)) { + try { + getRevisions().getContainer().resolve(Arrays.asList(this), true); + } catch (ResolutionException e) { + throw new BundleException("Could not resolve module.", BundleException.RESOLVE_ERROR, e); + } + } + if (getState().equals(State.INSTALLED)) { + throw new BundleException("Could not resolve module.", BundleException.RESOLVE_ERROR); + } + try { + event = doStart(options); + } catch (BundleException e) { + // must return state to resolved + setState(State.RESOLVED); + startError = e; + // must always publish the STOPPED event on error + event = Event.STOPPED; + } + } finally { + unlockStateChange(Event.STARTED); + } + + if (event != null) { + if (!EnumSet.of(Event.STARTED, Event.LAZY_ACTIVATION, Event.STOPPED).contains(event)) + throw new IllegalStateException("Wrong event type: " + event); + publishEvent(event); + } + + if (startError != null) { + throw startError; + } + } + + /** + * Stops this module. + * @param options options for stopping + * @throws BundleException if an error occurs while stopping + */ + public void stop(StopOptions... options) throws BundleException { + revisions.getContainer().checkAdminPermission(getBundle(), AdminPermission.EXECUTE); + if (options == null) + options = new StopOptions[0]; + Event event; + BundleException stopError = null; + lockStateChange(Event.STOPPED); + try { + checkValid(); + persistStopOptions(options); + if (!Module.ACTIVE_SET.contains(getState())) + return; + try { + event = doStop(); + } catch (BundleException e) { + stopError = e; + // must always publish the STOPPED event + event = Event.STOPPED; + } + } finally { + unlockStateChange(Event.STOPPED); + } + + if (event != null) { + if (!Event.STOPPED.equals(event)) + throw new IllegalStateException("Wrong event type: " + event); + publishEvent(event); + } + if (stopError != null) + throw stopError; + } + + @Override + public int compareTo(Module o) { + int slcomp = getStartLevel() - o.getStartLevel(); + if (slcomp != 0) { + return slcomp; + } + long idcomp = getId() - o.getId(); + return (idcomp < 0L) ? -1 : ((idcomp > 0L) ? 1 : 0); + } + + void checkValid() { + if (getState().equals(State.UNINSTALLED)) + throw new IllegalStateException("Module has been uninstalled."); + } + + private Event doStart(StartOptions... options) throws BundleException { + boolean isLazyTrigger = StartOptions.LAZY_TRIGGER.isContained(options); + if (isLazyTrigger) { + if (!State.LAZY_STARTING.equals(getState())) { + // need to make sure we transition through the lazy starting state + setState(State.LAZY_STARTING); + // need to publish the lazy event + unlockStateChange(Event.STARTED); + try { + publishEvent(Event.LAZY_ACTIVATION); + } finally { + lockStateChange(Event.STARTED); + } + if (State.ACTIVE.equals(getState())) { + // A sync listener must have caused the bundle to activate + return null; + } + // continue on to normal starting + } + } else { + if (settings.contains(Settings.USE_ACTIVATION_POLICY) && isLazyActivate()) { + if (State.LAZY_STARTING.equals(getState())) { + // a sync listener must have tried to start this module again with the lazy option + return null; // no event to publish; nothing to do + } + // set the lazy starting state and return lazy activation event for firing + setState(State.LAZY_STARTING); + return Event.LAZY_ACTIVATION; + } + } + + // time to actual start the module + if (!State.STARTING.equals(getState())) { + // TODO this starting state check should not be needed + // but we do it because of the way the system module init works + setState(State.STARTING); + publishEvent(Event.STARTING); + } + try { + startWorker(); + setState(State.ACTIVE); + return Event.STARTED; + } catch (Throwable t) { + // must fire stopping event + setState(State.STOPPING); + publishEvent(Event.STOPPING); + if (t instanceof BundleException) + throw (BundleException) t; + throw new BundleException("Error starting module.", BundleException.ACTIVATOR_ERROR, t); + } + } + + /** + * Performs any work associated with starting a module. For example, + * loading and calling start on an activator. + * @throws BundleException + */ + protected void startWorker() throws BundleException { + // Do nothing + } + + private Event doStop() throws BundleException { + setState(State.STOPPING); + publishEvent(Event.STOPPING); + try { + stopWorker(); + return Event.STOPPED; + } catch (Throwable t) { + if (t instanceof BundleException) + throw (BundleException) t; + throw new BundleException("Error stopping module.", BundleException.ACTIVATOR_ERROR, t); + } finally { + // must always set the state to stopped + setState(State.RESOLVED); + } + } + + /** + * @throws BundleException + */ + protected void stopWorker() throws BundleException { + // Do nothing + } + + /** + * @throws BundleException + */ + protected void updateWorker(ModuleRevisionBuilder builder) throws BundleException { + // do nothing + } + + @Override + public String toString() { + return "[id=" + id + "]"; + } + + /** + * Publishes the specified event for this module. + * @param event the event type to publish + */ + abstract protected void publishEvent(Event event); + + private void persistStartOptions(StartOptions... options) { + if (StartOptions.TRANSIENT_RESUME.isContained(options) || StartOptions.LAZY_TRIGGER.isContained(options)) { + return; + } + + // Always set the use acivation policy setting + if (StartOptions.USE_ACTIVATION_POLICY.isContained(options)) { + settings.add(Settings.USE_ACTIVATION_POLICY); + } else { + settings.remove(Settings.USE_ACTIVATION_POLICY); + } + + if (StartOptions.TRANSIENT.isContained(options)) { + return; + } + settings.add(Settings.AUTO_START); + revisions.getContainer().moduleDataBase.persistSettings(settings, this); + } + + private void persistStopOptions(StopOptions... options) { + if (StopOptions.TRANSIENT.isContained(options)) + return; + settings.clear(); + revisions.getContainer().moduleDataBase.persistSettings(settings, this); + } + + /** + * The container is done with the revision and it has been complete removed. + * This method allows the resources behind the revision to be cleaned up. + * @param revision the revision to clean up + */ + abstract protected void cleanup(ModuleRevision revision); + + boolean isLazyActivate() { + ModuleRevision current = getCurrentRevision(); + if (current == null) + return false; + List<Capability> capabilities = current.getCapabilities(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE); + if (capabilities.isEmpty()) + return false; + Capability moduleData = capabilities.get(0); + return EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY_LAZY.equals(moduleData.getAttributes().get(EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY)); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleCapability.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleCapability.java new file mode 100644 index 000000000..83e0f67c8 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleCapability.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * Copyright (c) 2012 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.util.Map; +import org.osgi.framework.wiring.BundleCapability; + +/** + * An implementation of {@link BundleCapability}. + */ +public class ModuleCapability implements BundleCapability { + private final String namespace; + private final Map<String, String> directives; + private final Map<String, Object> attributes; + private final ModuleRevision revision; + + ModuleCapability(String namespace, Map<String, String> directives, Map<String, Object> attributes, ModuleRevision revision) { + this.namespace = namespace; + this.directives = directives; + this.attributes = attributes; + this.revision = revision; + } + + @Override + public ModuleRevision getRevision() { + return revision; + } + + @Override + public String getNamespace() { + return namespace; + } + + @Override + public Map<String, String> getDirectives() { + return directives; + } + + @Override + public Map<String, Object> getAttributes() { + return attributes; + } + + @Override + public ModuleRevision getResource() { + return revision; + } + + @Override + public String toString() { + return namespace + ModuleRevision.toString(attributes, false) + ModuleRevision.toString(directives, true); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleClassLoader.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleClassLoader.java new file mode 100644 index 000000000..475eb40ab --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleClassLoader.java @@ -0,0 +1,42 @@ +/******************************************************************************* + * Copyright (c) 2012 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.net.URL; +import java.util.Collection; +import java.util.List; + +public interface ModuleClassLoader { + /** + * + * @param path + * @param filePattern + * @param options + * @return TODO + * @see ModuleWiring#findEntries(String, String, int) + */ + public List<URL> findEntries(String path, String filePattern, int options); + + /** + * + * @param path + * @param filePattern + * @param options + * @return TODO + * @see ModuleWiring#listResources(String, String, int) + */ + public Collection<String> listResources(String path, String filePattern, int options); + + /** + * Closes this module class loader and all of its associated resources + */ + public void close(); +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleCollisionHook.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleCollisionHook.java new file mode 100644 index 000000000..94b562c9f --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleCollisionHook.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2012 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.util.Collection; + +/** + * Hook used to determine if a module revision being installed or updated will cause a collision + + */ +public interface ModuleCollisionHook { + + /** + * Specifies a module install operation is being performed. + */ + int INSTALLING = 1; + + /** + * Specifies a module update operation is being performed. + */ + int UPDATING = 2; + + /** + * Filter bundle collisions hook method. This method is called during the + * install or update operation. The operation type will be + * {@link #INSTALLING installing} or {@link #UPDATING updating}. Depending + * on the operation type the target module and the collision candidate + * collection are the following: + * <ul> + * <li> {@link #INSTALLING installing} - The target is the module associated + * which is performing the install operation. The + * collision candidate collection contains the existing modules installed + * which have a current revision with the same symbolic name and version as the + * module being installed. + * <li> {@link #UPDATING updating} - The target is the module being updated. + * The collision candidate collection contains the existing modules installed which have + * a current revision with the same symbolic name and version as the content the target + * module is being updated to. + * </ul> + * This method can filter the collection of collision candidates by removing + * potential collisions. For the specified operation to succeed, the + * collection of collision candidates must be empty when this method returns. + * + * @param operationType The operation type. Must be the value of + * {@link #INSTALLING installing} or {@link #UPDATING updating}. + * @param target The target module used to determine what collision + * candidates to filter. + * @param collisionCandidates The collection of collision candidates. + */ + void filterCollisions(int operationType, Module target, Collection<Module> collisionCandidates); +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java new file mode 100644 index 000000000..c7e0abc25 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainer.java @@ -0,0 +1,1156 @@ +/******************************************************************************* + * Copyright (c) 2012 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.AccessController; +import java.security.PrivilegedAction; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import org.eclipse.osgi.container.Module.Event; +import org.eclipse.osgi.container.Module.StartOptions; +import org.eclipse.osgi.container.Module.State; +import org.eclipse.osgi.container.Module.StopOptions; +import org.eclipse.osgi.container.ModuleContainerAdaptor.ContainerEvent; +import org.eclipse.osgi.container.ModuleDataBase.Sort; +import org.eclipse.osgi.container.ModuleRequirement.DynamicModuleRequirement; +import org.eclipse.osgi.framework.eventmgr.*; +import org.eclipse.osgi.internal.container.LockSet; +import org.osgi.framework.*; +import org.osgi.framework.namespace.HostNamespace; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.framework.startlevel.FrameworkStartLevel; +import org.osgi.framework.wiring.BundleRevision; +import org.osgi.framework.wiring.FrameworkWiring; +import org.osgi.service.resolver.ResolutionException; + +/** + * A container for installing, updating, uninstalling and resolve modules. + * + */ +public final class ModuleContainer { + + /** + * Used by install operations to establish a write lock on an install location + */ + private final LockSet<String> locationLocks = new LockSet<String>(false); + + /** + * Used by install and update operations to establish a write lock for a name + */ + private final LockSet<String> nameLocks = new LockSet<String>(false); + + /** + * An implementation of FrameworkWiring for this container + */ + private final ContainerWiring frameworkWiring; + + /** + * An implementation of FrameworkStartLevel for this container + */ + private final ContainerStartLevel frameworkStartLevel; + + /** + * The module database for this container. All access to this database MUST + * be guarded by the database lock + */ + /* @GuardedBy("moduleDataBase") */ + final ModuleDataBase moduleDataBase; + + final ModuleContainerAdaptor adaptor; + + /** + * The module resolver which implements the ResolverContext and handles calling the + * resolver service. + */ + private final ModuleResolver moduleResolver; + + /** + * Constructs a new container with the specified collision hook, resolver hook, resolver and module database. + * @param adaptor the adaptor for the container + * @param moduleDataBase the module database + */ + public ModuleContainer(ModuleContainerAdaptor adaptor, ModuleDataBase moduleDataBase) { + this.adaptor = adaptor; + this.moduleResolver = new ModuleResolver(adaptor); + this.moduleDataBase = moduleDataBase; + this.frameworkWiring = new ContainerWiring(); + this.frameworkStartLevel = new ContainerStartLevel(); + } + + /** + * Returns the list of currently installed modules sorted by module id. + * @return the list of currently installed modules sorted by module id. + */ + public List<Module> getModules() { + return moduleDataBase.getModules(); + } + + /** + * Returns the module installed with the specified id, or null if no + * such module is installed. + * @param id the id of the module + * @return the module with the specified id, or null of no such module is installed. + */ + public Module getModule(long id) { + return moduleDataBase.getModule(id); + } + + /** + * Returns the module installed with the specified location, or null if no + * such module is installed. + * @param location the location of the module + * @return the module with the specified location, or null of no such module is installed. + */ + public Module getModule(String location) { + return moduleDataBase.getModule(location); + } + + /** + * Installs a new module using the specified location. The specified + * builder is used to create a new {@link ModuleRevision revision} + * which will become the {@link Module#getCurrentRevision() current} + * revision of the new module. + * <p> + * If a module already exists with the specified location then the + * existing module is returned and the builder is not used. + * @param origin the module performing the install, may be {@code null}. + * @param location The location identifier of the module to install. + * @param builder the builder used to create the revision to install. + * @return a new module or a existing module if one exists at the + * specified location. + * @throws BundleException if some error occurs installing the module + */ + public Module install(Module origin, String location, ModuleRevisionBuilder builder) throws BundleException { + String name = builder.getSymbolicName(); + boolean locationLocked = false; + boolean nameLocked = false; + try { + // Attempt to lock the location and name + try { + locationLocked = locationLocks.tryLock(location, 5, TimeUnit.SECONDS); + nameLocked = name != null && nameLocks.tryLock(name, 5, TimeUnit.SECONDS); + if (!locationLocked) { + throw new BundleException("Failed to obtain location lock for installation: " + location, BundleException.STATECHANGE_ERROR); + } + if (name != null && !nameLocked) { + throw new BundleException("Failed to obtain symbolic name lock for installation: " + name, BundleException.STATECHANGE_ERROR); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new BundleException("Failed to obtain id locks for installation.", BundleException.STATECHANGE_ERROR, e); + } + + Module existingLocation = null; + Collection<Module> collisionCandidates = Collections.emptyList(); + moduleDataBase.lockRead(); + try { + existingLocation = moduleDataBase.getModule(location); + if (existingLocation == null) { + // Collect existing current revisions with the same name and version as the revision we want to install + // This is to perform the collision check below + Collection<ModuleRevision> existingRevisionNames = moduleDataBase.getRevisions(name, builder.getVersion()); + if (!existingRevisionNames.isEmpty()) { + collisionCandidates = new ArrayList<Module>(1); + for (ModuleRevision equinoxRevision : existingRevisionNames) { + if (!equinoxRevision.isCurrent()) + continue; // only pay attention to current revisions + // need to prevent duplicates here; this is in case a revisions object contains multiple revision objects. + if (!collisionCandidates.contains(equinoxRevision.getRevisions().getModule())) + collisionCandidates.add(equinoxRevision.getRevisions().getModule()); + } + } + } + } finally { + moduleDataBase.unlockRead(); + } + // Check that the existing location is visible from the origin module + if (existingLocation != null) { + if (origin != null) { + Bundle bundle = origin.getBundle(); + BundleContext context = bundle == null ? null : bundle.getBundleContext(); + if (context != null && context.getBundle(existingLocation.getId()) == null) { + Bundle b = existingLocation.getBundle(); + throw new BundleException("Bundle \"" + b.getSymbolicName() + "\" version \"" + b.getVersion() + "\" is already installed at location: " + location, BundleException.REJECTED_BY_HOOK); + } + } + return existingLocation; + } + // Check that the bundle does not collide with other bundles with the same name and version + // This is from the perspective of the origin bundle + if (origin != null && !collisionCandidates.isEmpty()) { + adaptor.getModuleCollisionHook().filterCollisions(ModuleCollisionHook.INSTALLING, origin, collisionCandidates); + } + if (!collisionCandidates.isEmpty()) { + throw new BundleException("A bundle is already installed with name \"" + name + "\" and version \"" + builder.getVersion(), BundleException.DUPLICATE_BUNDLE_ERROR); + } + + Module result = moduleDataBase.install(location, builder); + + result.publishEvent(Event.INSTALLED); + + return result; + } finally { + if (locationLocked) + locationLocks.unlock(location); + if (nameLocked) + nameLocks.unlock(name); + } + } + + /** + * Updates the specified module with a new revision. The specified + * builder is used to create a new {@link ModuleRevision revision} + * which will become the {@link Module#getCurrentRevision() current} + * revision of the new module. + * @param module the module to update + * @param builder the builder used to create the revision for the update. + * @throws BundleException if some error occurs updating the module + */ + public void update(Module module, ModuleRevisionBuilder builder) throws BundleException { + String name = builder.getSymbolicName(); + boolean nameLocked = false; + try { + // Attempt to lock the name + try { + nameLocked = name != null && nameLocks.tryLock(name, 5, TimeUnit.SECONDS); + if (!nameLocked) { + throw new BundleException("Failed to obtain id locks for installation.", BundleException.STATECHANGE_ERROR); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new BundleException("Failed to obtain id locks for installation.", BundleException.STATECHANGE_ERROR, e); + } + + Collection<Module> collisionCandidates = Collections.emptyList(); + moduleDataBase.lockRead(); + try { + // Collect existing bundles with the same name and version as the bundle we want to install + // This is to perform the collision check below + Collection<ModuleRevision> existingRevisionNames = moduleDataBase.getRevisions(name, builder.getVersion()); + if (!existingRevisionNames.isEmpty()) { + collisionCandidates = new ArrayList<Module>(1); + for (ModuleRevision equinoxRevision : existingRevisionNames) { + if (!equinoxRevision.isCurrent()) + continue; + Module m = equinoxRevision.getRevisions().getModule(); + if (m.equals(module)) + continue; // don't worry about the updating modules revisions + // need to prevent duplicates here; this is in case a revisions object contains multiple revision objects. + if (!collisionCandidates.contains(m)) + collisionCandidates.add(m); + } + } + + } finally { + moduleDataBase.unlockRead(); + } + + // Check that the module does not collide with other modules with the same name and version + // This is from the perspective of the module being updated + if (module != null && !collisionCandidates.isEmpty()) { + adaptor.getModuleCollisionHook().filterCollisions(ModuleCollisionHook.UPDATING, module, collisionCandidates); + } + + if (!collisionCandidates.isEmpty()) { + throw new BundleException("A bundle is already installed with name \"" + name + "\" and version \"" + builder.getVersion(), BundleException.DUPLICATE_BUNDLE_ERROR); + } + + module.lockStateChange(Event.UPDATED); + State previousState = module.getState(); + BundleException updateError = null; + try { + // throwing an exception from stop terminates update + module.stop(StopOptions.TRANSIENT); + try { + // throwing an exception from updateWorker keeps the previous revision + module.updateWorker(builder); + if (Module.RESOLVED_SET.contains(previousState)) { + // set the state to installed and publish unresolved event + module.setState(State.INSTALLED); + module.publishEvent(Event.UNRESOLVED); + } + moduleDataBase.update(module, builder); + } catch (BundleException e) { + updateError = e; + } + + } finally { + module.unlockStateChange(Event.UPDATED); + } + if (updateError == null) { + // only publish updated event on success + module.publishEvent(Event.UPDATED); + } + if (Module.ACTIVE_SET.contains(previousState)) { + // restart the module if necessary + module.start(StartOptions.TRANSIENT_RESUME); + } + if (updateError != null) { + // throw cause of update error + throw updateError; + } + } finally { + if (nameLocked) + nameLocks.unlock(name); + } + } + + /** + * Uninstalls the specified module. + * @param module the module to uninstall + * @throws BundleException if some error occurs uninstalling the module + */ + public void uninstall(Module module) throws BundleException { + module.lockStateChange(Event.UNINSTALLED); + try { + if (Module.ACTIVE_SET.equals(module.getState())) { + try { + module.stop(StopOptions.TRANSIENT); + } catch (BundleException e) { + adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e); + } + } + moduleDataBase.uninstall(module); + module.setState(State.UNINSTALLED); + } finally { + module.unlockStateChange(Event.UNINSTALLED); + } + module.publishEvent(Event.UNINSTALLED); + } + + ModuleWiring getWiring(ModuleRevision revision) { + return moduleDataBase.getWiring(revision); + } + + /** + * Returns the {@link FrameworkWiring} for this container + * @return the framework wiring for this container. + */ + public FrameworkWiring getFrameworkWiring() { + return frameworkWiring; + } + + /** + * Returns the {@link FrameworkStartLevel} for this container + * @return the framework start level for this container + */ + public FrameworkStartLevel getFrameworkStartLevel() { + return frameworkStartLevel; + } + + /** + * Attempts to resolve the current revisions of the specified modules. + * @param triggers the modules to resolve or {@code null} to resolve all unresolved + * current revisions. + * @param triggersMandatory true if the triggers must be resolved. This will result in + * a {@link ResolutionException} if set to true and one of the triggers could not be resolved. + * @throws ResolutionException if a resolution error occurs + * @see FrameworkWiring#resolveBundles(Collection) + */ + public void resolve(Collection<Module> triggers, boolean triggersMandatory) throws ResolutionException { + while (!resolve0(triggers, triggersMandatory)) { + // nothing + } + } + + private boolean resolve0(Collection<Module> triggers, boolean triggersMandatory) throws ResolutionException { + if (triggers == null) + triggers = new ArrayList<Module>(0); + Collection<ModuleRevision> triggerRevisions = new ArrayList<ModuleRevision>(triggers.size()); + Collection<ModuleRevision> unresolved = new ArrayList<ModuleRevision>(); + Map<ModuleRevision, ModuleWiring> wiringClone; + long timestamp; + moduleDataBase.lockRead(); + try { + timestamp = moduleDataBase.getTimestamp(); + wiringClone = moduleDataBase.getWiringsClone(); + for (Module module : triggers) { + ModuleRevision current = module.getCurrentRevision(); + if (current != null) + triggerRevisions.add(current); + } + Collection<Module> allModules = moduleDataBase.getModules(); + for (Module module : allModules) { + ModuleRevision revision = module.getCurrentRevision(); + if (revision != null && !wiringClone.containsKey(revision)) + unresolved.add(revision); + } + } finally { + moduleDataBase.unlockRead(); + } + + Map<ModuleRevision, ModuleWiring> deltaWiring = moduleResolver.resolveDelta(triggerRevisions, triggersMandatory, unresolved, wiringClone, moduleDataBase); + if (deltaWiring.isEmpty()) + return true; // nothing to do + + Collection<Module> modulesResolved = new ArrayList<Module>(); + for (ModuleRevision deltaRevision : deltaWiring.keySet()) { + if (!wiringClone.containsKey(deltaRevision)) + modulesResolved.add(deltaRevision.getRevisions().getModule()); + } + + return applyDelta(deltaWiring, modulesResolved, timestamp); + } + + public ModuleWire resolveDynamic(String dynamicPkgName, ModuleRevision revision) throws ResolutionException { + ModuleWire result; + Map<ModuleRevision, ModuleWiring> deltaWiring; + Collection<Module> modulesResolved; + long timestamp; + do { + result = null; + Map<ModuleRevision, ModuleWiring> wiringClone = null; + List<DynamicModuleRequirement> dynamicReqs = null; + Collection<ModuleRevision> unresolved = new ArrayList<ModuleRevision>(); + moduleDataBase.lockRead(); + try { + dynamicReqs = getDynamicRequirements(dynamicPkgName, revision); + if (dynamicReqs.isEmpty()) { + // do nothing + return null; + } + timestamp = moduleDataBase.getTimestamp(); + wiringClone = moduleDataBase.getWiringsClone(); + Collection<Module> allModules = moduleDataBase.getModules(); + for (Module module : allModules) { + ModuleRevision current = module.getCurrentRevision(); + if (current != null && !wiringClone.containsKey(current)) + unresolved.add(current); + } + } finally { + moduleDataBase.unlockRead(); + } + + deltaWiring = null; + for (DynamicModuleRequirement dynamicReq : dynamicReqs) { + deltaWiring = moduleResolver.resolveDynamicDelta(dynamicReq, unresolved, wiringClone, moduleDataBase); + if (deltaWiring.get(revision) != null) { + break; + } + } + if (deltaWiring == null || deltaWiring.get(revision) == null) + return null; // nothing to do + + modulesResolved = new ArrayList<Module>(); + for (ModuleRevision deltaRevision : deltaWiring.keySet()) { + if (!wiringClone.containsKey(deltaRevision)) + modulesResolved.add(deltaRevision.getRevisions().getModule()); + } + + // Save the result + ModuleWiring wiring = deltaWiring.get(revision); + if (wiring != null) { + List<ModuleWire> wires = wiring.getRequiredModuleWires(null); + result = wires.isEmpty() ? null : wires.get(wires.size() - 1); + // Doing a sanity check, may not be necessary + if (result != null) { + if (!PackageNamespace.PACKAGE_NAMESPACE.equals(result.getCapability().getNamespace()) || !dynamicPkgName.equals(result.getCapability().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE))) { + throw new ResolutionException("Resolver provided an invalid dynamic wire: " + result); + } + } + } + } while (!applyDelta(deltaWiring, modulesResolved, timestamp)); + + return result; + } + + private boolean applyDelta(Map<ModuleRevision, ModuleWiring> deltaWiring, Collection<Module> modulesResolved, long timestamp) { + Collection<Module> modulesLocked = new ArrayList<Module>(modulesResolved.size()); + // now attempt to apply the delta + try { + // acquire the necessary RESOLVED state change lock + for (Module module : modulesResolved) { + try { + module.lockStateChange(Event.RESOLVED); + modulesLocked.add(module); + } catch (BundleException e) { + // TODO throw some appropriate exception + throw new IllegalStateException("Could not acquire state change lock.", e); + } + } + moduleDataBase.lockWrite(); + try { + if (timestamp != moduleDataBase.getTimestamp()) + return false; // need to try again + Map<ModuleRevision, ModuleWiring> wiringCopy = moduleDataBase.getWiringsCopy(); + for (Map.Entry<ModuleRevision, ModuleWiring> deltaEntry : deltaWiring.entrySet()) { + ModuleWiring current = wiringCopy.get(deltaEntry.getKey()); + if (current != null) { + // only need to update the provided and required wires for currently resolved + current.setProvidedWires(deltaEntry.getValue().getProvidedModuleWires(null)); + current.setRequiredWires(deltaEntry.getValue().getRequiredModuleWires(null)); + deltaEntry.setValue(current); // set the real wiring into the delta + } else { + modulesResolved.add(deltaEntry.getValue().getRevision().getRevisions().getModule()); + } + } + moduleDataBase.mergeWiring(deltaWiring); + } finally { + moduleDataBase.unlockWrite(); + } + // set the modules state to resolved + for (Module module : modulesLocked) { + module.setState(State.RESOLVED); + } + } finally { + for (Module module : modulesLocked) { + module.unlockStateChange(Event.RESOLVED); + } + } + + for (Module module : modulesLocked) { + module.publishEvent(Event.RESOLVED); + } + return true; + } + + private List<DynamicModuleRequirement> getDynamicRequirements(String dynamicPkgName, ModuleRevision revision) { + // TODO Will likely need to optimize this + if ((revision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0) { + // only do this for hosts + return null; + } + ModuleWiring wiring = revision.getWiring(); + if (wiring == null) { + // not resolved! + return null; + } + List<DynamicModuleRequirement> result = new ArrayList<ModuleRequirement.DynamicModuleRequirement>(1); + // check the dynamic import packages + DynamicModuleRequirement dynamicRequirement; + for (ModuleRequirement requirement : wiring.getModuleRequirements(PackageNamespace.PACKAGE_NAMESPACE)) { + dynamicRequirement = requirement.getDynamicPackageRequirement(revision, dynamicPkgName); + if (dynamicRequirement != null) { + result.add(dynamicRequirement); + } + } + + return result; + } + + private Collection<Module> unresolve(Collection<Module> initial) { + Collection<Module> refreshTriggers = null; + while (refreshTriggers == null) { + refreshTriggers = unresolve0(initial); + } + return refreshTriggers; + } + + private Collection<Module> unresolve0(Collection<Module> initial) { + Map<ModuleRevision, ModuleWiring> wiringCopy; + Collection<Module> refreshTriggers; + Collection<ModuleRevision> toRemoveRevisions; + Collection<ModuleWiring> toRemoveWirings; + Map<ModuleWiring, Collection<ModuleWire>> toRemoveWireLists; + long timestamp; + moduleDataBase.lockRead(); + try { + timestamp = moduleDataBase.getTimestamp(); + wiringCopy = moduleDataBase.getWiringsCopy(); + refreshTriggers = getRefreshClosure(initial, wiringCopy); + toRemoveRevisions = new ArrayList<ModuleRevision>(); + toRemoveWirings = new ArrayList<ModuleWiring>(); + toRemoveWireLists = new HashMap<ModuleWiring, Collection<ModuleWire>>(); + for (Module module : refreshTriggers) { + boolean first = true; + for (ModuleRevision revision : module.getRevisions().getModuleRevisions()) { + ModuleWiring removedWiring = wiringCopy.remove(revision); + if (removedWiring != null) { + toRemoveWirings.add(removedWiring); + List<ModuleWire> removedWires = removedWiring.getRequiredModuleWires(null); + for (ModuleWire wire : removedWires) { + Collection<ModuleWire> providerWires = toRemoveWireLists.get(wire.getProviderWiring()); + if (providerWires == null) { + providerWires = new ArrayList<ModuleWire>(); + toRemoveWireLists.put(wire.getProviderWiring(), providerWires); + } + providerWires.add(wire); + } + } + if (!first || revision.getRevisions().isUninstalled()) { + toRemoveRevisions.add(revision); + } + first = false; + } + } + } finally { + moduleDataBase.unlockRead(); + } + Collection<Module> modulesLocked = new ArrayList<Module>(refreshTriggers.size()); + Collection<Module> modulesUnresolved = new ArrayList<Module>(); + try { + // acquire module state change locks + try { + for (Module refreshModule : refreshTriggers) { + refreshModule.lockStateChange(Event.UNRESOLVED); + modulesLocked.add(refreshModule); + } + } catch (BundleException e) { + // TODO throw some appropriate exception + throw new IllegalStateException("Could not acquire state change lock.", e); + } + // Stop any active bundles and remove non-active modules from the refreshTriggers + for (Iterator<Module> iTriggers = refreshTriggers.iterator(); iTriggers.hasNext();) { + Module refreshModule = iTriggers.next(); + if (Module.ACTIVE_SET.contains(refreshModule.getState())) { + try { + refreshModule.stop(StopOptions.TRANSIENT); + } catch (BundleException e) { + adaptor.publishContainerEvent(ContainerEvent.ERROR, refreshModule, e); + } + } else { + iTriggers.remove(); + } + } + + // do a sanity check on states of the modules, they must be INSTALLED, RESOLVED or UNINSTALLED + for (Module module : modulesLocked) { + if (Module.ACTIVE_SET.contains(module.getState())) { + throw new IllegalStateException("Module is in the wrong state: " + module + ": " + module.getState()); + } + } + + // finally apply the unresolve to the database + moduleDataBase.lockWrite(); + try { + if (timestamp != moduleDataBase.getTimestamp()) + return null; // need to try again + // remove any wires from unresolved wirings that got removed + for (Map.Entry<ModuleWiring, Collection<ModuleWire>> entry : toRemoveWireLists.entrySet()) { + List<ModuleWire> provided = entry.getKey().getProvidedModuleWires(null); + provided.removeAll(entry.getValue()); + entry.getKey().setProvidedWires(provided); + for (ModuleWire removedWire : entry.getValue()) { + // invalidate the wire + removedWire.invalidate(); + } + + } + // remove any revisions that got removed as part of the refresh + for (ModuleRevision removed : toRemoveRevisions) { + removed.getRevisions().removeRevision(removed); + moduleDataBase.removeCapabilities(removed); + } + // invalidate any removed wiring objects + for (ModuleWiring moduleWiring : toRemoveWirings) { + moduleWiring.invalidate(); + } + moduleDataBase.setWiring(wiringCopy); + } finally { + moduleDataBase.unlockWrite(); + } + // set the state of modules to unresolved + for (Module module : modulesLocked) { + if (State.RESOLVED.equals(module.getState())) { + module.setState(State.INSTALLED); + modulesUnresolved.add(module); + } + } + } finally { + for (Module module : modulesLocked) { + module.unlockStateChange(Event.UNRESOLVED); + } + } + + // publish unresolved events after giving up all locks + for (Module module : modulesUnresolved) { + module.publishEvent(Event.UNRESOLVED); + } + return refreshTriggers; + } + + /** + * Refreshes the specified collection of modules. + * @param initial the modules to refresh or {@code null} to refresh the + * removal pending. + * @throws ResolutionException + * @see FrameworkWiring#refreshBundles(Collection, FrameworkListener...) + */ + public void refresh(Collection<Module> initial) throws ResolutionException { + Collection<Module> refreshTriggers = unresolve(initial); + resolve(refreshTriggers, false); + for (Module module : refreshTriggers) { + try { + module.start(StartOptions.TRANSIENT_RESUME); + } catch (BundleException e) { + adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e); + } + } + } + + /** + * Returns the dependency closure of for the specified modules. + * @param initial The initial modules for which to generate the dependency closure + * @return A collection containing a snapshot of the dependency closure of the specified + * modules, or an empty collection if there were no specified modules. + */ + public Collection<Module> getDependencyClosure(Collection<Module> initial) { + moduleDataBase.lockRead(); + try { + return getRefreshClosure(initial, moduleDataBase.getWiringsCopy()); + } finally { + moduleDataBase.unlockRead(); + } + } + + /** + * Returns the revisions that have {@link ModuleWiring#isCurrent() non-current}, {@link ModuleWiring#isInUse() in use} module wirings. + * @return A collection containing a snapshot of the revisions which have non-current, in use ModuleWirings, + * or an empty collection if there are no such revisions. + */ + public Collection<ModuleRevision> getRemovalPending() { + return moduleDataBase.getRemovalPending(); + } + + /** + * Return the active start level value of this container. + * + * If the container is in the process of changing the start level this + * method must return the active start level if this differs from the + * requested start level. + * + * @return The active start level value of the Framework. + */ + public int getStartLevel() { + return frameworkStartLevel.getStartLevel(); + } + + void setStartLevel(Module module, int startlevel) { + frameworkStartLevel.setStartLevel(module, startlevel); + } + + void open() { + frameworkStartLevel.open(); + frameworkWiring.open(); + } + + void close() { + frameworkStartLevel.close(); + frameworkWiring.close(); + } + + Collection<Module> getRefreshClosure(Collection<Module> initial, Map<ModuleRevision, ModuleWiring> wiringCopy) { + Set<Module> refreshClosure = new HashSet<Module>(); + if (initial == null) { + initial = new HashSet<Module>(); + Collection<ModuleRevision> removalPending = moduleDataBase.getRemovalPending(); + for (ModuleRevision revision : removalPending) { + initial.add(revision.getRevisions().getModule()); + } + } + for (Module module : initial) + addDependents(module, wiringCopy, refreshClosure); + return refreshClosure; + } + + private static void addDependents(Module module, Map<ModuleRevision, ModuleWiring> wiringCopy, Set<Module> refreshClosure) { + if (refreshClosure.contains(module)) + return; + refreshClosure.add(module); + List<ModuleRevision> revisions = module.getRevisions().getModuleRevisions(); + for (ModuleRevision revision : revisions) { + ModuleWiring wiring = wiringCopy.get(revision); + if (wiring == null) + continue; + List<ModuleWire> provided = wiring.getProvidedModuleWires(null); + // add all requirers of the provided wires + for (ModuleWire providedWire : provided) { + addDependents(providedWire.getRequirer().getRevisions().getModule(), wiringCopy, refreshClosure); + } + // add all hosts of a fragment + if (revision.getTypes() == BundleRevision.TYPE_FRAGMENT) { + List<ModuleWire> hosts = wiring.getRequiredModuleWires(HostNamespace.HOST_NAMESPACE); + for (ModuleWire hostWire : hosts) { + addDependents(hostWire.getProvider().getRevisions().getModule(), wiringCopy, refreshClosure); + } + } + } + } + + static Collection<ModuleRevision> getDependencyClosure(ModuleRevision initial, Map<ModuleRevision, ModuleWiring> wiringCopy) { + Set<ModuleRevision> dependencyClosure = new HashSet<ModuleRevision>(); + addDependents(initial, wiringCopy, dependencyClosure); + return dependencyClosure; + } + + private static void addDependents(ModuleRevision revision, Map<ModuleRevision, ModuleWiring> wiringCopy, Set<ModuleRevision> dependencyClosure) { + if (dependencyClosure.contains(revision)) + return; + dependencyClosure.add(revision); + ModuleWiring wiring = wiringCopy.get(revision); + if (wiring == null) + return; + List<ModuleWire> provided = wiring.getProvidedModuleWires(null); + // add all requirers of the provided wires + for (ModuleWire providedWire : provided) { + addDependents(providedWire.getRequirer(), wiringCopy, dependencyClosure); + } + // add all hosts of a fragment + if (revision.getTypes() == BundleRevision.TYPE_FRAGMENT) { + List<ModuleWire> hosts = wiring.getRequiredModuleWires(HostNamespace.HOST_NAMESPACE); + for (ModuleWire hostWire : hosts) { + addDependents(hostWire.getProvider(), wiringCopy, dependencyClosure); + } + } + } + + Bundle getSystemBundle() { + Module systemModule = moduleDataBase.getModule(0); + return systemModule == null ? null : systemModule.getBundle(); + } + + void checkAdminPermission(Bundle bundle, String action) { + if (bundle == null) + return; + SecurityManager sm = System.getSecurityManager(); + if (sm != null) + sm.checkPermission(new AdminPermission(bundle, action)); + } + + class ContainerWiring implements FrameworkWiring, EventDispatcher<ContainerWiring, FrameworkListener[], Collection<Module>> { + private final Object monitor = new Object(); + private EventManager refreshThread = null; + + @Override + public Bundle getBundle() { + return getSystemBundle(); + } + + @Override + public void refreshBundles(Collection<Bundle> bundles, FrameworkListener... listeners) { + checkAdminPermission(getBundle(), AdminPermission.RESOLVE); + Collection<Module> modules = getModules(bundles); + + // queue to refresh in the background + // notice that we only do one refresh operation at a time + CopyOnWriteIdentityMap<ContainerWiring, FrameworkListener[]> dispatchListeners = new CopyOnWriteIdentityMap<ModuleContainer.ContainerWiring, FrameworkListener[]>(); + dispatchListeners.put(this, listeners); + ListenerQueue<ContainerWiring, FrameworkListener[], Collection<Module>> queue = new ListenerQueue<ModuleContainer.ContainerWiring, FrameworkListener[], Collection<Module>>(refreshThread); + queue.queueListeners(dispatchListeners.entrySet(), this); + + // dispatch the refresh job + queue.dispatchEventAsynchronous(0, modules); + } + + @Override + public boolean resolveBundles(Collection<Bundle> bundles) { + checkAdminPermission(getBundle(), AdminPermission.RESOLVE); + Collection<Module> modules = getModules(bundles); + try { + resolve(modules, false); + } catch (ResolutionException e) { + return false; + } + for (Module module : modules) { + if (getWiring(module.getCurrentRevision()) == null) + return false; + } + return true; + } + + @Override + public Collection<Bundle> getRemovalPendingBundles() { + moduleDataBase.lockRead(); + try { + Collection<Bundle> removalPendingBundles = new HashSet<Bundle>(); + Collection<ModuleRevision> removalPending = moduleDataBase.getRemovalPending(); + for (ModuleRevision moduleRevision : removalPending) { + removalPendingBundles.add(moduleRevision.getBundle()); + } + return removalPendingBundles; + } finally { + moduleDataBase.unlockRead(); + } + } + + @Override + public Collection<Bundle> getDependencyClosure(Collection<Bundle> bundles) { + Collection<Module> modules = getModules(bundles); + moduleDataBase.lockRead(); + try { + Collection<Module> closure = getRefreshClosure(modules, moduleDataBase.getWiringsCopy()); + Collection<Bundle> result = new ArrayList<Bundle>(closure.size()); + for (Module module : closure) { + result.add(module.getBundle()); + } + return result; + } finally { + moduleDataBase.unlockRead(); + } + } + + private Collection<Module> getModules(final Collection<Bundle> bundles) { + if (bundles == null) + return null; + return AccessController.doPrivileged(new PrivilegedAction<Collection<Module>>() { + @Override + public Collection<Module> run() { + Collection<Module> result = new ArrayList<Module>(bundles.size()); + for (Bundle bundle : bundles) { + Module module = bundle.adapt(Module.class); + if (module == null) + throw new IllegalStateException("Could not adapt a bundle to a module."); //$NON-NLS-1$ + result.add(module); + } + return result; + } + }); + } + + @Override + public void dispatchEvent(ContainerWiring eventListener, FrameworkListener[] frameworkListeners, int eventAction, Collection<Module> eventObject) { + try { + refresh(eventObject); + } catch (ResolutionException e) { + adaptor.publishContainerEvent(ContainerEvent.ERROR, moduleDataBase.getModule(0), e); + } finally { + adaptor.publishContainerEvent(ContainerEvent.REFRESH, moduleDataBase.getModule(0), null, frameworkListeners); + } + } + + private EventManager getManager() { + synchronized (monitor) { + if (refreshThread == null) { + refreshThread = new EventManager("Start Level: " + adaptor.toString()); + } + return refreshThread; + } + } + + // because of bug 378491 we have to synchronize access to the manager + // so we can close and re-open ourselves + void close() { + synchronized (monitor) { + // force a manager to be created if it did not exist + EventManager manager = getManager(); + // this prevents any operations until open is called + manager.close(); + } + } + + void open() { + synchronized (monitor) { + if (refreshThread != null) { + // Make sure it is closed just incase + refreshThread.close(); + // a new one will be constructed on demand + refreshThread = null; + } + } + } + } + + class ContainerStartLevel implements FrameworkStartLevel, EventDispatcher<Module, FrameworkListener[], Integer> { + static final int USE_BEGINNING_START_LEVEL = Integer.MIN_VALUE; + private static final int FRAMEWORK_STARTLEVEL = 1; + private static final int MODULE_STARTLEVEL = 2; + private final AtomicInteger activeStartLevel = new AtomicInteger(0); + private final Object monitor = new Object(); + private EventManager startLevelThread = null; + + @Override + public Bundle getBundle() { + return getSystemBundle(); + } + + @Override + public int getStartLevel() { + return activeStartLevel.get(); + } + + void setStartLevel(Module module, int startlevel) { + checkAdminPermission(module.getBundle(), AdminPermission.EXECUTE); + if (module.getId() == 0) { + throw new IllegalArgumentException("Cannot set the start level of the system bundle."); + } + if (startlevel < 1) { + throw new IllegalArgumentException("Cannot set the start level to less than 1: " + startlevel); + } + if (module.getStartLevel() == startlevel) { + return; // do nothing + } + moduleDataBase.setStartLevel(module, startlevel); + // queue start level operation in the background + // notice that we only do one start level operation at a time + CopyOnWriteIdentityMap<Module, FrameworkListener[]> dispatchListeners = new CopyOnWriteIdentityMap<Module, FrameworkListener[]>(); + dispatchListeners.put(module, new FrameworkListener[0]); + ListenerQueue<Module, FrameworkListener[], Integer> queue = new ListenerQueue<Module, FrameworkListener[], Integer>(getManager()); + queue.queueListeners(dispatchListeners.entrySet(), this); + + // dispatch the start level job + queue.dispatchEventAsynchronous(MODULE_STARTLEVEL, startlevel); + } + + @Override + public void setStartLevel(int startlevel, FrameworkListener... listeners) { + checkAdminPermission(getBundle(), AdminPermission.STARTLEVEL); + if (startlevel < 1) { + throw new IllegalArgumentException("Cannot set the start level to less than 1: " + startlevel); + } + + if (activeStartLevel.get() == 0) { + throw new IllegalStateException("The system has not be activated yet."); + } + // queue start level operation in the background + // notice that we only do one start level operation at a time + CopyOnWriteIdentityMap<Module, FrameworkListener[]> dispatchListeners = new CopyOnWriteIdentityMap<Module, FrameworkListener[]>(); + dispatchListeners.put(moduleDataBase.getModule(0), listeners); + ListenerQueue<Module, FrameworkListener[], Integer> queue = new ListenerQueue<Module, FrameworkListener[], Integer>(getManager()); + queue.queueListeners(dispatchListeners.entrySet(), this); + + // dispatch the start level job + queue.dispatchEventAsynchronous(FRAMEWORK_STARTLEVEL, startlevel); + } + + @Override + public int getInitialBundleStartLevel() { + return moduleDataBase.getInitialModuleStartLevel(); + } + + @Override + public void setInitialBundleStartLevel(int startlevel) { + checkAdminPermission(getBundle(), AdminPermission.STARTLEVEL); + moduleDataBase.setInitialModuleStartLevel(startlevel); + } + + @Override + public void dispatchEvent(Module module, FrameworkListener[] listeners, int eventAction, Integer startlevel) { + switch (eventAction) { + case FRAMEWORK_STARTLEVEL : + doContainerStartLevel(module, startlevel, listeners); + break; + case MODULE_STARTLEVEL : + try { + if (getStartLevel() < startlevel) { + module.stop(StopOptions.TRANSIENT); + } else { + module.start(StartOptions.TRANSIENT_IF_AUTO_START, StartOptions.TRANSIENT_RESUME); + } + } catch (BundleException e) { + adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e); + } + break; + default : + break; + } + } + + void doContainerStartLevel(Module module, int newStartLevel, FrameworkListener... listeners) { + if (newStartLevel == USE_BEGINNING_START_LEVEL) { + String beginningSL = (String) adaptor.getConfiguration().get(Constants.FRAMEWORK_BEGINNING_STARTLEVEL); + newStartLevel = beginningSL == null ? 1 : Integer.parseInt(beginningSL); + } + try { + int currentSL = getStartLevel(); + // Note that we must get a new list of modules each time; + // this is because additional modules could have been installed from the previous start-level + if (newStartLevel > currentSL) { + for (int i = currentSL; i < newStartLevel; i++) { + int toStartLevel = i + 1; + activeStartLevel.set(toStartLevel); + incStartLevel(toStartLevel, moduleDataBase.getSortedModules(Sort.BY_START_LEVEL)); + } + } else { + for (int i = currentSL; i > newStartLevel; i--) { + int toStartLevel = i - 1; + activeStartLevel.set(toStartLevel); + decStartLevel(toStartLevel, moduleDataBase.getSortedModules(Sort.BY_START_LEVEL, Sort.BY_DEPENDENCY)); + } + } + adaptor.publishContainerEvent(ContainerEvent.START_LEVEL, module, null, listeners); + } catch (Error e) { + adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e, listeners); + throw e; + } catch (RuntimeException e) { + adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e, listeners); + throw e; + } + } + + private void incStartLevel(int toStartLevel, List<Module> sortedModules) { + incStartLevel(toStartLevel, sortedModules, true); + incStartLevel(toStartLevel, sortedModules, false); + } + + private void incStartLevel(int toStartLevel, List<Module> sortedModules, boolean lazyOnly) { + for (Module module : sortedModules) { + if (module.getStartLevel() < toStartLevel) { + // skip modules who should have already been started + continue; + } else if (module.getStartLevel() == toStartLevel) { + boolean isLazyStart = module.isLazyActivate(); + if (lazyOnly ? isLazyStart : !isLazyStart) { + try { + module.start(StartOptions.TRANSIENT_IF_AUTO_START, StartOptions.TRANSIENT_RESUME); + } catch (BundleException e) { + adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e); + } + } + } else { + // can stop resumin since any remaining modules have a greater startlevel than the active startlevel + break; + } + } + } + + private void decStartLevel(int toStartLevel, List<Module> sortedModules) { + ListIterator<Module> iModules = sortedModules.listIterator(sortedModules.size()); + while (iModules.hasPrevious()) { + Module module = iModules.previous(); + + if (module.getStartLevel() > toStartLevel + 1) { + // skip modules who should have already been stopped + continue; + } else if (module.getStartLevel() <= toStartLevel) { + // stopped all modules we are going to for this start level + break; + } + try { + module.stop(StopOptions.TRANSIENT); + } catch (BundleException e) { + adaptor.publishContainerEvent(ContainerEvent.ERROR, module, e); + } + } + } + + private EventManager getManager() { + synchronized (monitor) { + if (startLevelThread == null) { + startLevelThread = new EventManager("Start Level: " + adaptor.toString()); + } + return startLevelThread; + } + } + + // because of bug 378491 we have to synchronize access to the manager + // so we can close and re-open ourselves + void close() { + synchronized (monitor) { + // force a manager to be created if it did not exist + EventManager manager = getManager(); + // this prevents any operations until open is called + manager.close(); + } + } + + void open() { + synchronized (monitor) { + if (startLevelThread != null) { + // Make sure it is closed just incase + startLevelThread.close(); + // a new one will be constructed on demand + startLevelThread = null; + } + } + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainerAdaptor.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainerAdaptor.java new file mode 100644 index 000000000..14dc4642b --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleContainerAdaptor.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2012 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.util.Collections; +import java.util.Map; +import org.apache.felix.resolver.Logger; +import org.apache.felix.resolver.ResolverImpl; +import org.osgi.framework.FrameworkListener; +import org.osgi.framework.hooks.resolver.ResolverHookFactory; +import org.osgi.service.resolver.Resolver; + +/** + * Adapts the behavior of a container. + */ +public abstract class ModuleContainerAdaptor { + public enum ContainerEvent { + REFRESH, START_LEVEL, STARTED, STOPPED, STOPPED_UPDATE, STOPPED_REFRESH, ERROR, WARNING, INFO + } + + /** + * Returns the resolver the container will use. This implementation will + * return the default implementation of the resolver. Override this method + * to provide an alternative resolver implementation for the container. + * @return the resolver the container will use. + */ + public Resolver getResolver() { + return new ResolverImpl(new Logger(4)); + } + + /** + * Returns the collision hook the container will use. + * @return the collision hook the container will use. + */ + public abstract ModuleCollisionHook getModuleCollisionHook(); + + /** + * Returns the resolver hook factory the container will use. + * @return the resolver hook factory the container will use. + */ + public abstract ResolverHookFactory getResolverHookFactory(); + + /** + * Publishes the specified container event. + * @param type the type of event + * @param module the module associated with the event + * @param error the error associated with the event, may be {@code null} + * @param listeners additional listeners to publish the event to synchronously + */ + public abstract void publishContainerEvent(ContainerEvent type, Module module, Throwable error, FrameworkListener... listeners); + + /** + * Returns an unmodifiable map of the configuration for the container + * @return an unmodifiable map of the configuration for the container + */ + public Map<String, Object> getConfiguration() { + return Collections.emptyMap(); + } + + /** + * Creates a new {@link ModuleClassLoader} for the specified wiring. + * @param wiring the module wiring to create a module class loader for + * @return a new {@link ModuleClassLoader} for the specified wiring. + */ + public ModuleClassLoader createClassLoader(ModuleWiring wiring) { + throw new UnsupportedOperationException("Container adaptor does not support module class loaders."); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleDataBase.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleDataBase.java new file mode 100644 index 000000000..95f42d643 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleDataBase.java @@ -0,0 +1,1523 @@ +/******************************************************************************* + * Copyright (c) 2012 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.io.*; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.*; +import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; +import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; +import org.eclipse.osgi.container.Module.Settings; +import org.eclipse.osgi.container.Module.State; +import org.eclipse.osgi.framework.util.ObjectPool; +import org.eclipse.osgi.internal.container.Capabilities; +import org.eclipse.osgi.internal.resolver.ComputeNodeOrder; +import org.osgi.framework.Constants; +import org.osgi.framework.Version; +import org.osgi.framework.wiring.BundleRevision; +import org.osgi.resource.*; +import org.osgi.service.resolver.Resolver; + +/** + * A database for storing modules, their revisions and wiring states. The + * database is responsible for assigning ids and providing access to the + * capabilities provided by the revisions currently installed as well as + * the wiring states. + * <p> + * <strong>Concurrent Semantics</strong><br /> + * + * Implementations must be thread safe. The database allows for concurrent + * read operations and all read operations are protected by the + * {@link #lockRead() read} lock. All write operations are + * protected by the {@link #lockWrite() write} lock. The read and write + * locks are reentrant and follow the semantics of the + * {@link ReentrantReadWriteLock}. Just like the {@code ReentrantReadWriteLock} + * the lock on a database can not be upgraded from a read to a write. Doing so will result in an + * {@link IllegalMonitorStateException} being thrown. This is behavior is different from + * the {@code ReentrantReadWriteLock} which results in a deadlock if an attempt is made + * to upgrade from a read to a write lock. + * <p> + * A database is associated with a {@link ModuleContainer container}. The container + * associated with a database provides public API for manipulating the modules + * and their wiring states. For example, installing, updating, uninstalling, + * resolving and unresolving modules. Except for the {@link #load(DataInputStream)}, + * all other methods that perform write operations are intended to be used by + * the associated container. + */ +public abstract class ModuleDataBase { + /** + * The container this database is associated with + */ + protected ModuleContainer container = null; + + /** + * A map of modules by location. + */ + private final Map<String, Module> modulesByLocations; + + /** + * A map of modules by id. + */ + private final Map<Long, Module> modulesById; + + /** + * A map of revision collections by symbolic name + */ + private final Map<String, Collection<ModuleRevision>> revisionByName; + + /** + * A map of revision wiring objects. + */ + private final Map<ModuleRevision, ModuleWiring> wirings; + + /** + * Holds the next id to be assigned to a module when it is installed + */ + final AtomicLong nextId; + + /** + * Holds the current timestamp of this database. + */ + final AtomicLong timeStamp; + + private final Capabilities capabilities; + + /** + * A map of module settings keyed by module id. + */ + private final Map<Long, EnumSet<Settings>> moduleSettings; + + /** + * The initial module start level. + */ + private int initialModuleStartLevel = 1; + + /** + * Monitors read and write access to this database + */ + private final ReentrantReadWriteLock monitor = new ReentrantReadWriteLock(true); + + static enum Sort { + BY_DEPENDENCY, BY_START_LEVEL, BY_ID; + /** + * Tests if this option is contained in the specified options + */ + public boolean isContained(Sort... options) { + for (Sort option : options) { + if (equals(option)) { + return true; + } + } + return false; + } + } + + /** + * Constructs a new empty database. + */ + public ModuleDataBase() { + this.modulesByLocations = new HashMap<String, Module>(); + this.modulesById = new HashMap<Long, Module>(); + this.revisionByName = new HashMap<String, Collection<ModuleRevision>>(); + this.wirings = new HashMap<ModuleRevision, ModuleWiring>(); + // Start at id 1 because 0 is reserved for the system bundle + this.nextId = new AtomicLong(1); + this.timeStamp = new AtomicLong(0); + this.moduleSettings = new HashMap<Long, EnumSet<Settings>>(); + this.capabilities = new Capabilities(); + } + + /** + * Sets the container for this database. A database can only + * be associated with a single container and that container must + * have been constructed with this database. + * <p> + * This method modifies this database and is considered a write operation. + * This method acquires the {@link #lockWrite() write} lock while setting + * the container for this database. + * @param container the container to associate this database with. + */ + public final void setContainer(ModuleContainer container) { + lockWrite(); + try { + if (this.container != null) + throw new IllegalStateException("The container is already set."); //$NON-NLS-1$ + if (container.moduleDataBase != this) { + throw new IllegalArgumentException("Container is already using a different database."); //$NON-NLS-1$ + } + this.container = container; + } finally { + unlockWrite(); + } + } + + /** + * Returns the module at the given location or null if no module exists + * at the given location. + * <p> + * A read operation protected by the {@link #lockRead() read} lock. + * @param location the location of the module. + * @return the module at the given location or null. + */ + final Module getModule(String location) { + lockRead(); + try { + return modulesByLocations.get(location); + } finally { + unlockRead(); + } + } + + /** + * Returns the module at the given id or null if no module exists + * at the given location. + * <p> + * A read operation protected by the {@link #lockRead() read} lock. + * @param id the id of the module. + * @return the module at the given id or null. + */ + final Module getModule(long id) { + lockRead(); + try { + return modulesById.get(id); + } finally { + unlockRead(); + } + } + + /** + * Returns a snapshot collection of revisions with the specified name + * and version. If version is {@code null} then all revisions with + * the specified name are returned. + * <p> + * A read operation protected by the {@link #lockRead() read} lock. + * @param name the name of the modules + * @param version the version of the modules or {@code null} + * @return a snapshot collection of revisions with the specified name + * and version. + */ + final Collection<ModuleRevision> getRevisions(String name, Version version) { + lockRead(); + try { + if (version == null) + return new ArrayList<ModuleRevision>(revisionByName.get(name)); + + Collection<ModuleRevision> existingRevisions = revisionByName.get(name); + if (existingRevisions == null) { + return Collections.emptyList(); + } + Collection<ModuleRevision> sameVersion = new ArrayList<ModuleRevision>(1); + for (ModuleRevision revision : existingRevisions) { + if (revision.getVersion().equals(version)) { + sameVersion.add(revision); + } + } + return sameVersion; + } finally { + unlockRead(); + } + } + + /** + * Returns a snapshot collection of current revisions which are fragments + + * @return a snapshot collection of current revisions which are fragments + */ + final Collection<ModuleRevision> getFragmentRevisions() { + Collection<ModuleRevision> fragments = new ArrayList<ModuleRevision>(); + lockRead(); + try { + for (Module module : modulesById.values()) { + ModuleRevision revision = module.getCurrentRevision(); + if (revision != null && ((revision.getTypes() & BundleRevision.TYPE_FRAGMENT) != 0)) { + fragments.add(revision); + } + } + return fragments; + } finally { + unlockRead(); + } + } + + /** + * Installs a new revision using the specified builder, location and module. + * <p> + * A write operation protected by the {@link #lockWrite() write} lock. + * @param location the location to use for the installation + * @param builder the builder to use to create the new revision + * @return the installed module + */ + final Module install(String location, ModuleRevisionBuilder builder) { + lockWrite(); + try { + int startlevel = Constants.SYSTEM_BUNDLE_LOCATION.equals(location) ? 0 : getInitialModuleStartLevel(); + long id = Constants.SYSTEM_BUNDLE_LOCATION.equals(location) ? 0 : getNextIdAndIncrement(); + Module module = load(location, builder, id, null, startlevel); + module.setlastModified(System.currentTimeMillis()); + incrementTimestamp(); + return module; + } finally { + unlockWrite(); + } + } + + final Module load(String location, ModuleRevisionBuilder builder, long id, EnumSet<Settings> settings, int startlevel) { + // sanity check + checkWrite(); + if (container == null) + throw new IllegalStateException("Container is not set."); //$NON-NLS-1$ + if (modulesByLocations.containsKey(location)) + throw new IllegalArgumentException("Location is already used: " + location); //$NON-NLS-1$ + if (modulesById.containsKey(id)) + throw new IllegalArgumentException("Id is already used: " + id); //$NON-NLS-1$ + Module module; + if (id == 0) { + module = createSystemModule(); + } else { + module = createModule(location, id, settings, startlevel); + } + builder.addRevision(module); + modulesByLocations.put(location, module); + modulesById.put(id, module); + if (settings != null) + moduleSettings.put(id, settings); + ModuleRevision newRevision = module.getCurrentRevision(); + addToRevisionByName(newRevision); + addCapabilities(newRevision); + return module; + } + + private void addToRevisionByName(ModuleRevision revision) { + // sanity check + checkWrite(); + + String name = revision.getSymbolicName(); + Collection<ModuleRevision> sameName = revisionByName.get(name); + if (sameName == null) { + sameName = new ArrayList<ModuleRevision>(1); + revisionByName.put(name, sameName); + } + sameName.add(revision); + } + + /** + * Uninstalls the specified module from this database. + * Uninstalling a module will attempt to clean up any removal pending + * revisions possible. + * <p> + * A write operation protected by the {@link #lockWrite() write} lock. + * @param module the module to uninstall + */ + final void uninstall(Module module) { + lockWrite(); + try { + ModuleRevisions uninstalling = module.getRevisions(); + // remove the location + modulesByLocations.remove(module.getLocation()); + modulesById.remove(module.getId()); + moduleSettings.remove(module.getId()); + // remove the revisions by name + List<ModuleRevision> revisions = uninstalling.getModuleRevisions(); + for (ModuleRevision revision : revisions) { + removeCapabilities(revision); + String name = revision.getSymbolicName(); + if (name != null) { + Collection<ModuleRevision> sameName = revisionByName.get(name); + if (sameName != null) { + sameName.remove(revision); + } + } + // if the revision does not have a wiring it can safely be removed + // from the revisions for the module + ModuleWiring oldWiring = wirings.get(revision); + if (oldWiring == null) { + module.getRevisions().removeRevision(revision); + } + } + // marke the revisions as uninstalled + uninstalling.uninstall(); + // attempt to cleanup any removal pendings + cleanupRemovalPending(); + + module.setlastModified(System.currentTimeMillis()); + incrementTimestamp(); + } finally { + unlockWrite(); + } + } + + /** + * Updates the specified module with anew revision using the specified builder. + * <p> + * A write operation protected by the {@link #lockWrite() write} lock. + * @param module the module for which the revision provides an update for + * @param builder the builder to use to create the new revision + */ + final void update(Module module, ModuleRevisionBuilder builder) { + lockWrite(); + try { + ModuleRevision oldRevision = module.getCurrentRevision(); + ModuleRevision newRevision = builder.addRevision(module); + String name = newRevision.getSymbolicName(); + Collection<ModuleRevision> sameName = revisionByName.get(name); + if (sameName == null) { + sameName = new ArrayList<ModuleRevision>(1); + revisionByName.put(name, sameName); + } + sameName.add(newRevision); + addCapabilities(newRevision); + + // remove the old revision by name + String oldName = oldRevision.getSymbolicName(); + if (oldName != null) { + Collection<ModuleRevision> oldSameName = revisionByName.get(oldName); + if (oldSameName != null) { + oldSameName.remove(oldRevision); + } + } + + // if the old revision does not have a wiring it can safely be removed + ModuleWiring oldWiring = wirings.get(oldRevision); + if (oldWiring == null) { + module.getRevisions().removeRevision(oldRevision); + removeCapabilities(oldRevision); + } + // attempt to clean up removal pendings + cleanupRemovalPending(); + + module.setlastModified(System.currentTimeMillis()); + incrementTimestamp(); + } finally { + unlockWrite(); + } + } + + /** + * Examines the wirings to determine if there are any removal + * pending wiring objects that can be removed. We consider + * a removal pending wiring as removable if all dependent + * wiring are also removal pending. + */ + private void cleanupRemovalPending() { + // sanity check + checkWrite(); + Collection<ModuleRevision> removalPending = getRemovalPending(); + for (ModuleRevision removed : removalPending) { + if (wirings.get(removed) == null) + continue; + Collection<ModuleRevision> dependencyClosure = ModuleContainer.getDependencyClosure(removed, wirings); + boolean allPendingRemoval = true; + for (ModuleRevision pendingRemoval : dependencyClosure) { + if (pendingRemoval.isCurrent()) { + allPendingRemoval = false; + break; + } + } + if (allPendingRemoval) { + for (ModuleRevision pendingRemoval : dependencyClosure) { + pendingRemoval.getRevisions().removeRevision(pendingRemoval); + removeCapabilities(pendingRemoval); + wirings.remove(pendingRemoval); + } + } + } + } + + /** + * Gets all revisions with a removal pending wiring. + * <p> + * A read operation protected by the {@link #lockRead() read} lock. + * @return all revisions with a removal pending wiring. + */ + final Collection<ModuleRevision> getRemovalPending() { + Collection<ModuleRevision> removalPending = new ArrayList<ModuleRevision>(); + lockRead(); + try { + for (ModuleWiring wiring : wirings.values()) { + if (!wiring.isCurrent()) + removalPending.add(wiring.getRevision()); + } + } finally { + unlockRead(); + } + return removalPending; + } + + /** + * Returns the current wiring for the specified revision or + * null of no wiring exists for the revision. + * <p> + * A read operation protected by the {@link #lockRead() read} lock. + * @param revision the revision to get the wiring for + * @return the current wiring for the specified revision. + */ + final ModuleWiring getWiring(ModuleRevision revision) { + lockRead(); + try { + return wirings.get(revision); + } finally { + unlockRead(); + } + } + + /** + * Returns a snapshot of the wirings for all revisions. This + * performs a shallow copy of each entry in the wirings map. + * <p> + * A read operation protected by the {@link #lockRead() read} lock. + * @return a snapshot of the wirings for all revisions. + */ + final Map<ModuleRevision, ModuleWiring> getWiringsCopy() { + lockRead(); + try { + return new HashMap<ModuleRevision, ModuleWiring>(wirings); + } finally { + unlockRead(); + } + } + + /** + * Returns a cloned snapshot of the wirings of all revisions. This + * performs a clone of each {@link ModuleWiring}. The + * {@link ModuleWiring#getRevision() revision}, + * {@link ModuleWiring#getModuleCapabilities(String) capabilities}, + * {@link ModuleWiring#getModuleRequirements(String) requirements}, + * {@link ModuleWiring#getProvidedModuleWires(String) provided wires}, and + * {@link ModuleWiring#getRequiredModuleWires(String) required wires} of + * each wiring are copied into a cloned copy of the wiring. + * <p> + * The returned map of wirings may be safely read from while not holding + * any read or write locks on this database. This is useful for doing + * {@link Resolver#resolve(org.osgi.service.resolver.ResolveContext) resolve} + * operations without holding the read or write lock on this database. + * <p> + * A read operation protected by the {@link #lockRead() read} lock. + * @return a cloned snapshot of the wirings of all revisions. + */ + final Map<ModuleRevision, ModuleWiring> getWiringsClone() { + lockRead(); + try { + Map<ModuleRevision, ModuleWiring> clonedWirings = new HashMap<ModuleRevision, ModuleWiring>(); + for (Map.Entry<ModuleRevision, ModuleWiring> entry : wirings.entrySet()) { + ModuleWiring wiring = new ModuleWiring(entry.getKey(), entry.getValue().getModuleCapabilities(null), entry.getValue().getModuleRequirements(null), entry.getValue().getProvidedModuleWires(null), entry.getValue().getRequiredModuleWires(null), entry.getValue().getSubstitutedNames()); + clonedWirings.put(entry.getKey(), wiring); + } + return clonedWirings; + } finally { + unlockRead(); + } + } + + /** + * Replaces the complete wiring map with the specified wiring + * <p> + * A write operation protected by the {@link #lockWrite() write} lock. + * @param newWiring the new wiring to take effect. The values + * from the new wiring are copied. + */ + final void setWiring(Map<ModuleRevision, ModuleWiring> newWiring) { + lockWrite(); + try { + wirings.clear(); + wirings.putAll(newWiring); + incrementTimestamp(); + } finally { + unlockWrite(); + } + } + + /** + * Adds all the values from the specified delta wirings to the + * wirings current wirings + * <p> + * A write operation protected by the {@link #lockWrite() write} lock. + * @param deltaWiring the new wiring values to take effect. + * The values from the delta wiring are copied. + */ + final void mergeWiring(Map<ModuleRevision, ModuleWiring> deltaWiring) { + lockWrite(); + try { + wirings.putAll(deltaWiring); + incrementTimestamp(); + } finally { + unlockWrite(); + } + } + + /** + * Returns a snapshot of all modules ordered by module ID. + * <p> + * A read operation protected by the {@link #lockRead() read} lock. + * @return a snapshot of all modules. + */ + final List<Module> getModules() { + return getSortedModules(); + } + + /** + * Returns a snapshot of all modules ordered according to the sort options + * @param sortOptions options for sorting + * @return a snapshot of all modules ordered according to the sort options + */ + final List<Module> getSortedModules(Sort... sortOptions) { + lockRead(); + try { + List<Module> modules = new ArrayList<Module>(modulesByLocations.values()); + sortModules(modules, sortOptions); + return modules; + } finally { + unlockRead(); + } + } + + final void sortModules(List<Module> modules, Sort... sortOptions) { + if (modules.size() < 2) + return; + if (sortOptions == null || Sort.BY_ID.isContained(sortOptions) || sortOptions.length == 0) { + Collections.sort(modules, new Comparator<Module>() { + @Override + public int compare(Module m1, Module m2) { + return m1.getId().compareTo(m2.getId()); + } + }); + return; + } + // first sort by start-level + if (Sort.BY_START_LEVEL.isContained(sortOptions)) { + Collections.sort(modules); + } + if (Sort.BY_DEPENDENCY.isContained(sortOptions)) { + if (Sort.BY_START_LEVEL.isContained(sortOptions)) { + // sort each sublist that has modules of the same start level + int currentSL = modules.get(0).getStartLevel(); + int currentSLindex = 0; + boolean lazy = false; + for (int i = 0; i < modules.size(); i++) { + Module module = modules.get(i); + if (currentSL != module.getStartLevel()) { + if (lazy) + sortByDependencies(modules.subList(currentSLindex, i)); + currentSL = module.getStartLevel(); + currentSLindex = i; + lazy = false; + } + lazy |= module.isLazyActivate(); + } + // sort the last set of bundles + if (lazy) + sortByDependencies(modules.subList(currentSLindex, modules.size())); + } else { + // sort the whole list by dependency + sortByDependencies(modules); + } + } + } + + private Collection<List<Module>> sortByDependencies(List<Module> toSort) { + // Build references so we can sort + List<Module[]> references = new ArrayList<Module[]>(toSort.size()); + for (Module module : toSort) { + ModuleRevision current = module.getCurrentRevision(); + if (current == null) { + continue; + } + ModuleWiring wiring = current.getWiring(); + if (wiring == null) { + continue; + } + for (ModuleWire wire : wiring.getRequiredModuleWires(null)) { + references.add(new Module[] {wire.getRequirer().getRevisions().getModule(), wire.getProvider().getRevisions().getModule()}); + } + } + + // Sort an array using the references + Module[] sorted = toSort.toArray(new Module[toSort.size()]); + Object[][] cycles = ComputeNodeOrder.computeNodeOrder(sorted, references.toArray(new Module[references.size()][])); + + // Apply the sorted array to the list + toSort.clear(); + toSort.addAll(Arrays.asList(sorted)); + + if (cycles.length == 0) + return Collections.emptyList(); + + Collection<List<Module>> moduleCycles = new ArrayList<List<Module>>(cycles.length); + for (Object[] cycle : cycles) { + List<Module> moduleCycle = new ArrayList<Module>(cycle.length); + for (Object module : cycle) { + moduleCycle.add((Module) module); + } + moduleCycles.add(moduleCycle); + } + return moduleCycles; + } + + /** + * Increments by one the next module ID + */ + private long getNextIdAndIncrement() { + // sanity check + checkWrite(); + return nextId.getAndIncrement(); + + } + + private void checkWrite() { + if (monitor.getWriteHoldCount() == 0) + throw new IllegalMonitorStateException("Must hold the write lock."); + } + + /** + * returns the next module ID + * <p> + * A read operation protected by the {@link #lockRead() read} lock. + * @return the next module ID + */ + public final long getNextId() { + lockRead(); + try { + return nextId.get(); + } finally { + unlockRead(); + } + } + + /** + * Returns the current timestamp of this database. + * The timestamp is incremented any time a modification + * is made to this database. For example: + * <ul> + * <li> installing a module + * <li> updating a module + * <li> uninstalling a module + * <li> modifying the wirings + * </ul> + * <p> + * A read operation protected by the {@link #lockRead() read} lock. + * @return the current timestamp of this database. + */ + final public long getTimestamp() { + lockRead(); + try { + return timeStamp.get(); + } finally { + unlockRead(); + } + } + + /** + * Increments the timestamp of this database. + */ + private void incrementTimestamp() { + // sanity check + checkWrite(); + timeStamp.incrementAndGet(); + } + + /** + * @see ReadLock#lock() + */ + public final void lockRead() { + monitor.readLock().lock(); + } + + /** + * Same as {@link WriteLock#lock()} except an illegal + * state exception is thrown if the current thread holds + * one or more read locks. + * @see WriteLock#lock() + * @throws IllegalMonitorStateException if the current thread holds + * one or more read locks. + */ + public final void lockWrite() { + if (monitor.getReadHoldCount() > 0) { + // this is not supported and will cause deadlock if allowed to proceed. + // fail fast instead of deadlocking + throw new IllegalMonitorStateException("Requesting upgrade to write lock."); //$NON-NLS-1$ + } + monitor.writeLock().lock(); + } + + /** + * @see ReadLock#unlock() + */ + public final void unlockRead() { + monitor.readLock().unlock(); + } + + /** + * @see WriteLock#unlock() + */ + public final void unlockWrite() { + monitor.writeLock().unlock(); + } + + /** + * Adds the {@link ModuleRevision#getModuleCapabilities(String) capabilities} + * provided by the specified revision to this database. These capabilities must + * become available for lookup with the {@link ModuleDataBase#findCapabilities(ModuleRequirement)} + * method. + * <p> + * This method must be called while holding the {@link #lockWrite() write} lock. + * @param revision the revision which has capabilities to add + */ + protected void addCapabilities(ModuleRevision revision) { + checkWrite(); + capabilities.addCapabilities(revision); + } + + /** + * Removes the {@link ModuleRevision#getModuleCapabilities(String) capabilities} + * provided by the specified revision from this database. These capabilities + * must no longer be available for lookup with the + * {@link ModuleDataBase#findCapabilities(ModuleRequirement)} method. + * <p> + * This method must be called while holding the {@link #lockWrite() write} lock. + * @param revision + */ + protected void removeCapabilities(ModuleRevision revision) { + checkWrite(); + capabilities.removeCapabilities(revision); + } + + /** + * Returns a mutable snapshot of capabilities that are candidates for + * satisfying the specified requirement. + * <p> + * A read operation protected by the {@link #lockRead() read} lock. + * Implementers of this method should acquire the read lock while + * finding capabilities. + * @param requirement the requirement + * @return the candidates for the requirement + */ + protected List<ModuleCapability> findCapabilities(ModuleRequirement requirement) { + lockRead(); + try { + return capabilities.findCapabilities(requirement); + } finally { + unlockRead(); + } + } + + /** + * Creates a new module. This gets called when a new module is installed + * or when {@link #load(DataInputStream) loading} persistent data into this + * database. + * @param location the location for the module + * @param id the id for the module + * @param settings the settings for the module. May be {@code null} if there are no settings. + * @param startlevel the start level for the module + * @return the Module + */ + protected abstract Module createModule(String location, long id, EnumSet<Settings> settings, int startlevel); + + /** + * Creates the system module. This gets called when the system module is installed + * or when {@link #load(DataInputStream) loading} persistent data into this + * database. + * <p> + * The returned system module must have an {@link Module#getId() id} of zero and a location + * of {@link Constants#SYSTEM_BUNDLE_LOCATION System Bundle}. + * @return the system module + */ + protected abstract SystemModule createSystemModule(); + + /** + * Writes this database in a format suitable for using the {@link #load(DataInputStream)} + * method. All modules are stored which have a current {@link ModuleRevision revision}. + * Only the current revision of each module is stored (no removal pending revisions + * are stored). Optionally the {@link ModuleWiring wiring} of each current revision + * may be stored. Wiring can only be stored if there are no {@link #getRemovalPending() + * removal pending} revisions. + * <p> + * This method acquires the {@link #lockRead() read} lock while writing this + * database. + * <p> + * After this database have been written, the output stream is flushed. + * The output stream remains open after this method returns. + * @param out the data output steam. + * @param persistWirings true if wirings should be persisted. This option will be ignored + * if there are {@link #getRemovalPending() removal pending} revisions. + * @throws IOException if writing this database to the specified output stream throws an IOException + */ + public final void store(DataOutputStream out, boolean persistWirings) throws IOException { + lockRead(); + try { + Persistence.store(this, out, persistWirings); + } finally { + unlockRead(); + } + } + + /** + * Loads information into this database from the input data stream. This data + * base must be empty and never been modified (the {@link #getTimestamp() timestamp} is zero. + * All stored modules are loaded into this database. If the input stream contains + * wiring then it will also be loaded into this database. + * <p> + * Since this method modifies this database it is considered a write operation. + * This method acquires the {@link #lockWrite() write} lock while loading + * the information into this database. + * <p> + * The specified stream remains open after this method returns. + * @param in the data input stream. + * @throws IOException if an error occurred when reading from the input stream. + * @throws IllegalStateException if this database is not empty. + */ + public final void load(DataInputStream in) throws IOException { + lockWrite(); + try { + if (timeStamp.get() != 0) + throw new IllegalStateException("Can only load into a empty database."); //$NON-NLS-1$ + Persistence.load(this, in); + } finally { + unlockWrite(); + } + } + + void persistSettings(EnumSet<Settings> settings, Module module) { + lockWrite(); + try { + moduleSettings.put(module.getId(), EnumSet.copyOf(settings)); + } finally { + unlockWrite(); + } + } + + void setStartLevel(Module module, int startlevel) { + lockWrite(); + try { + module.storeStartLevel(startlevel); + } finally { + unlockWrite(); + } + } + + int getInitialModuleStartLevel() { + lockRead(); + try { + return this.initialModuleStartLevel; + } finally { + unlockRead(); + } + } + + void setInitialModuleStartLevel(int initialStartlevel) { + lockWrite(); + try { + this.initialModuleStartLevel = initialStartlevel; + } finally { + unlockWrite(); + } + } + + private static class Persistence { + private static final int VERSION = 1; + private static final byte NULL = 0; + private static final byte OBJECT = 1; + private static final byte LONG_STRING = 3; + private static final String UTF_8 = "UTF-8"; //$NON-NLS-1$ + + private static final byte VALUE_STRING = 0; + private static final byte VALUE_STRING_ARRAY = 1; + private static final byte VAlUE_BOOLEAN = 2; + private static final byte VALUE_INTEGER = 3; + private static final byte VALUE_LONG = 4; + private static final byte VALUE_DOUBLE = 5; + private static final byte VALUE_VERSION = 6; + private static final byte VALUE_URI = 7; + private static final byte VALUE_LIST = 8; + + private static int addToWriteTable(Object object, Map<Object, Integer> objectTable) { + if (object == null) + throw new NullPointerException(); + Integer cur = objectTable.get(object); + if (cur != null) + throw new IllegalStateException("Object is already in the write table: " + object); //$NON-NLS-1$ + objectTable.put(object, new Integer(objectTable.size())); + // return the index of the object just added (i.e. size - 1) + return (objectTable.size() - 1); + } + + private static void addToReadTable(Object object, int index, Map<Integer, Object> objectTable) { + objectTable.put(new Integer(index), object); + } + + public static void store(ModuleDataBase moduleDataBase, DataOutputStream out, boolean persistWirings) throws IOException { + out.writeInt(VERSION); + out.writeLong(moduleDataBase.getTimestamp()); + out.writeLong(moduleDataBase.getNextId()); + out.writeInt(moduleDataBase.getInitialModuleStartLevel()); + + List<Module> modules = moduleDataBase.getModules(); + out.writeInt(modules.size()); + + Map<Object, Integer> objectTable = new HashMap<Object, Integer>(); + for (Module module : modules) { + writeModule(module, moduleDataBase, out, objectTable); + } + + Collection<ModuleRevision> removalPendings = moduleDataBase.getRemovalPending(); + // only persist wirings if there are no removals pending + persistWirings &= removalPendings.isEmpty(); + out.writeBoolean(persistWirings); + if (!persistWirings) { + return; + } + + Map<ModuleRevision, ModuleWiring> wirings = moduleDataBase.wirings; + // prime the object table with all the required wires + out.writeInt(wirings.size()); + for (ModuleWiring wiring : wirings.values()) { + List<ModuleWire> requiredWires = wiring.getRequiredModuleWires(null); + out.writeInt(requiredWires.size()); + for (ModuleWire wire : requiredWires) { + writeWire(wire, out, objectTable); + } + } + + // now write all the info about each wiring using only indexes + for (ModuleWiring wiring : wirings.values()) { + writeWiring(wiring, out, objectTable); + } + + out.flush(); + } + + public static void load(ModuleDataBase moduleDataBase, DataInputStream in) throws IOException { + int version = in.readInt(); + if (version < VERSION) + throw new UnsupportedOperationException("Perstence version is not correct for loading: " + version + " expecting: " + VERSION); //$NON-NLS-1$ //$NON-NLS-2$ + long timeStamp = in.readLong(); + moduleDataBase.nextId.set(in.readLong()); + moduleDataBase.setInitialModuleStartLevel(in.readInt()); + + int numModules = in.readInt(); + + Map<Integer, Object> objectTable = new HashMap<Integer, Object>(); + for (int i = 0; i < numModules; i++) { + readModule(moduleDataBase, in, objectTable); + } + if (!in.readBoolean()) + return; // no wires persisted + + int numWirings = in.readInt(); + // prime the table with all the required wires + for (int i = 0; i < numWirings; i++) { + int numWires = in.readInt(); + for (int j = 0; j < numWires; j++) { + readWire(in, objectTable); + } + } + + // now read all the info about each wiring using only indexes + Map<ModuleRevision, ModuleWiring> wirings = new HashMap<ModuleRevision, ModuleWiring>(); + for (int i = 0; i < numWirings; i++) { + ModuleWiring wiring = readWiring(in, objectTable); + wirings.put(wiring.getRevision(), wiring); + } + // TODO need to do this without incrementing the timestamp + moduleDataBase.setWiring(wirings); + + // need to set the resolution state of the modules + for (ModuleWiring wiring : wirings.values()) { + wiring.getRevision().getRevisions().getModule().setState(State.RESOLVED); + } + + // Setting the timestamp at the end since some operations increment it + moduleDataBase.timeStamp.set(timeStamp); + } + + private static void writeModule(Module module, ModuleDataBase moduleDataBase, DataOutputStream out, Map<Object, Integer> objectTable) throws IOException { + ModuleRevision current = module.getCurrentRevision(); + if (current == null) + return; + out.writeInt(addToWriteTable(current, objectTable)); + + writeString(module.getLocation(), out); + out.writeLong(module.getId()); + + writeString(current.getSymbolicName(), out); + writeVersion(current.getVersion(), out); + out.writeInt(current.getTypes()); + + List<Capability> capabilities = current.getCapabilities(null); + out.writeInt(capabilities.size()); + for (Capability capability : capabilities) { + out.writeInt(addToWriteTable(capability, objectTable)); + writeGenericInfo(capability.getNamespace(), capability.getAttributes(), capability.getDirectives(), out); + } + + List<Requirement> requirements = current.getRequirements(null); + out.writeInt(requirements.size()); + for (Requirement requirement : requirements) { + out.writeInt(addToWriteTable(requirement, objectTable)); + writeGenericInfo(requirement.getNamespace(), requirement.getAttributes(), requirement.getDirectives(), out); + } + + // settings + EnumSet<Settings> settings = moduleDataBase.moduleSettings.get(module.getId()); + out.writeInt(settings == null ? 0 : settings.size()); + if (settings != null) { + for (Settings setting : settings) { + writeString(setting.name(), out); + } + } + + // startlevel + out.writeInt(module.getStartLevel()); + + // last modified + out.writeLong(module.getLastModified()); + } + + private static void readModule(ModuleDataBase moduleDataBase, DataInputStream in, Map<Integer, Object> objectTable) throws IOException { + ModuleRevisionBuilder builder = new ModuleRevisionBuilder(); + int moduleIndex = in.readInt(); + String location = readString(in); + long id = in.readLong(); + builder.setSymbolicName(readString(in)); + builder.setVersion(readVersion(in)); + builder.setTypes(in.readInt()); + + int numCapabilities = in.readInt(); + int[] capabilityIndexes = new int[numCapabilities]; + for (int i = 0; i < numCapabilities; i++) { + capabilityIndexes[i] = in.readInt(); + readGenericInfo(true, in, builder); + } + + int numRequirements = in.readInt(); + int[] requirementIndexes = new int[numRequirements]; + for (int i = 0; i < numRequirements; i++) { + requirementIndexes[i] = in.readInt(); + readGenericInfo(false, in, builder); + } + + // settings + EnumSet<Settings> settings = null; + int numSettings = in.readInt(); + if (numSettings > 0) { + settings = EnumSet.noneOf(Settings.class); + for (int i = 0; i < numSettings; i++) { + settings.add(Settings.valueOf(readString(in))); + } + } + + // startlevel + int startlevel = in.readInt(); + Module module = moduleDataBase.load(location, builder, id, settings, startlevel); + + // last modified + module.setlastModified(in.readLong()); + + ModuleRevision current = module.getCurrentRevision(); + addToReadTable(current, moduleIndex, objectTable); + + List<ModuleCapability> capabilities = current.getModuleCapabilities(null); + for (int i = 0; i < capabilities.size(); i++) { + addToReadTable(capabilities.get(i), capabilityIndexes[i], objectTable); + } + + List<ModuleRequirement> requirements = current.getModuleRequirements(null); + for (int i = 0; i < requirements.size(); i++) { + addToReadTable(requirements.get(i), requirementIndexes[i], objectTable); + } + } + + private static void writeWire(ModuleWire wire, DataOutputStream out, Map<Object, Integer> objectTable) throws IOException { + Wire w = wire; + Integer capability = objectTable.get(w.getCapability()); + Integer provider = objectTable.get(w.getProvider()); + Integer requirement = objectTable.get(w.getRequirement()); + Integer requirer = objectTable.get(w.getRequirer()); + + if (capability == null || provider == null || requirement == null || requirer == null) + throw new NullPointerException("Could not find the expected indexes"); //$NON-NLS-1$ + + out.writeInt(addToWriteTable(wire, objectTable)); + + out.writeInt(capability); + out.writeInt(provider); + out.writeInt(requirement); + out.writeInt(requirer); + } + + private static void readWire(DataInputStream in, Map<Integer, Object> objectTable) throws IOException { + int wireIndex = in.readInt(); + + ModuleCapability capability = (ModuleCapability) objectTable.get(in.readInt()); + ModuleRevision provider = (ModuleRevision) objectTable.get(in.readInt()); + ModuleRequirement requirement = (ModuleRequirement) objectTable.get(in.readInt()); + ModuleRevision requirer = (ModuleRevision) objectTable.get(in.readInt()); + + if (capability == null || provider == null || requirement == null || requirer == null) + throw new NullPointerException("Could not find the expected indexes"); //$NON-NLS-1$ + + ModuleWire result = new ModuleWire(capability, provider, requirement, requirer); + + addToReadTable(result, wireIndex, objectTable); + } + + private static void writeWiring(ModuleWiring wiring, DataOutputStream out, Map<Object, Integer> objectTable) throws IOException { + Integer revisionIndex = objectTable.get(wiring.getRevision()); + if (revisionIndex == null) + throw new NullPointerException("Could not find revision for wiring."); //$NON-NLS-1$ + out.writeInt(revisionIndex); + + List<ModuleCapability> capabilities = wiring.getModuleCapabilities(null); + out.writeInt(capabilities.size()); + for (ModuleCapability capability : capabilities) { + Integer capabilityIndex = objectTable.get(capability); + if (capabilityIndex == null) + throw new NullPointerException("Could not find capability for wiring."); //$NON-NLS-1$ + out.writeInt(capabilityIndex); + } + + List<ModuleRequirement> requirements = wiring.getModuleRequirements(null); + out.writeInt(requirements.size()); + for (ModuleRequirement requirement : requirements) { + Integer requirementIndex = objectTable.get(requirement); + if (requirementIndex == null) + throw new NullPointerException("Could not find requirement for wiring."); //$NON-NLS-1$ + out.writeInt(requirementIndex); + } + + List<ModuleWire> providedWires = wiring.getProvidedModuleWires(null); + out.writeInt(providedWires.size()); + for (ModuleWire wire : providedWires) { + Integer wireIndex = objectTable.get(wire); + if (wireIndex == null) + throw new NullPointerException("Could not find provided wire for wiring."); //$NON-NLS-1$ + out.writeInt(wireIndex); + } + + List<ModuleWire> requiredWires = wiring.getRequiredModuleWires(null); + out.writeInt(requiredWires.size()); + for (ModuleWire wire : requiredWires) { + Integer wireIndex = objectTable.get(wire); + if (wireIndex == null) + throw new NullPointerException("Could not find required wire for wiring."); //$NON-NLS-1$ + out.writeInt(wireIndex); + } + + Collection<String> substituted = wiring.getSubstitutedNames(); + out.writeInt(substituted.size()); + for (String pkgName : substituted) { + writeString(pkgName, out); + } + } + + private static ModuleWiring readWiring(DataInputStream in, Map<Integer, Object> objectTable) throws IOException { + ModuleRevision revision = (ModuleRevision) objectTable.get(in.readInt()); + if (revision == null) + throw new NullPointerException("Could not find revision for wiring."); //$NON-NLS-1$ + + int numCapabilities = in.readInt(); + List<ModuleCapability> capabilities = new ArrayList<ModuleCapability>(numCapabilities); + for (int i = 0; i < numCapabilities; i++) { + capabilities.add((ModuleCapability) objectTable.get(in.readInt())); + } + + int numRequirements = in.readInt(); + List<ModuleRequirement> requirements = new ArrayList<ModuleRequirement>(numRequirements); + for (int i = 0; i < numRequirements; i++) { + requirements.add((ModuleRequirement) objectTable.get(in.readInt())); + } + + int numProvidedWires = in.readInt(); + List<ModuleWire> providedWires = new ArrayList<ModuleWire>(numProvidedWires); + for (int i = 0; i < numProvidedWires; i++) { + providedWires.add((ModuleWire) objectTable.get(in.readInt())); + } + + int numRequiredWires = in.readInt(); + List<ModuleWire> requiredWires = new ArrayList<ModuleWire>(numRequiredWires); + for (int i = 0; i < numRequiredWires; i++) { + requiredWires.add((ModuleWire) objectTable.get(in.readInt())); + } + + int numSubstitutedNames = in.readInt(); + Collection<String> substituted = new ArrayList<String>(numSubstitutedNames); + for (int i = 0; i < numSubstitutedNames; i++) { + substituted.add(readString(in)); + } + + return new ModuleWiring(revision, capabilities, requirements, providedWires, requiredWires, substituted); + } + + private static void writeGenericInfo(String namespace, Map<String, ?> attributes, Map<String, String> directives, DataOutputStream out) throws IOException { + writeString(namespace, out); + writeMap(attributes, out); + writeMap(directives, out); + } + + @SuppressWarnings("unchecked") + private static void readGenericInfo(boolean isCapability, DataInputStream in, ModuleRevisionBuilder builder) throws IOException { + String namespace = readString(in); + Map<String, Object> attributes = readMap(in); + Map<String, ?> directives = readMap(in); + if (isCapability) { + builder.addCapability(namespace, (Map<String, String>) directives, attributes); + } else { + builder.addRequirement(namespace, (Map<String, String>) directives, attributes); + } + + } + + private static void writeMap(Map<String, ?> source, DataOutputStream out) throws IOException { + if (source == null) { + out.writeInt(0); + } else { + out.writeInt(source.size()); + Iterator<String> iter = source.keySet().iterator(); + while (iter.hasNext()) { + String key = iter.next(); + Object value = source.get(key); + writeString(key, out); + if (value instanceof String) { + out.writeByte(VALUE_STRING); + writeString((String) value, out); + } else if (value instanceof String[]) { + out.writeByte(VALUE_STRING_ARRAY); + writeStringArray(out, (String[]) value); + } else if (value instanceof Boolean) { + out.writeByte(VAlUE_BOOLEAN); + out.writeBoolean(((Boolean) value).booleanValue()); + } else if (value instanceof Integer) { + out.writeByte(VALUE_INTEGER); + out.writeInt(((Integer) value).intValue()); + } else if (value instanceof Long) { + out.writeByte(VALUE_LONG); + out.writeLong(((Long) value).longValue()); + } else if (value instanceof Double) { + out.writeByte(VALUE_DOUBLE); + out.writeDouble(((Double) value).doubleValue()); + } else if (value instanceof Version) { + out.writeByte(VALUE_VERSION); + writeVersion((Version) value, out); + } else if (value instanceof URI) { + out.writeByte(VALUE_URI); + writeString(value.toString(), out); + } else if (value instanceof List) { + out.writeByte(VALUE_LIST); + writeList(out, (List<?>) value); + } + } + } + } + + private static Map<String, Object> readMap(DataInputStream in) throws IOException { + int count = in.readInt(); + HashMap<String, Object> result = new HashMap<String, Object>(count); + for (int i = 0; i < count; i++) { + String key = readString(in); + Object value = null; + byte type = in.readByte(); + if (type == VALUE_STRING) + value = readString(in); + else if (type == VALUE_STRING_ARRAY) + value = readStringArray(in); + else if (type == VAlUE_BOOLEAN) + value = in.readBoolean() ? Boolean.TRUE : Boolean.FALSE; + else if (type == VALUE_INTEGER) + value = new Integer(in.readInt()); + else if (type == VALUE_LONG) + value = new Long(in.readLong()); + else if (type == VALUE_DOUBLE) + value = new Double(in.readDouble()); + else if (type == VALUE_VERSION) + value = readVersion(in); + else if (type == VALUE_URI) + try { + value = new URI(readString(in)); + } catch (URISyntaxException e) { + value = null; + } + else if (type == VALUE_LIST) + value = readList(in); + + result.put(key, value); + } + return result; + } + + private static void writeStringArray(DataOutputStream out, String[] value) throws IOException { + if (value == null) { + out.writeInt(0); + } else { + out.writeInt(value.length); + for (int i = 0; i < value.length; i++) + writeString(value[i], out); + } + + } + + private static String[] readStringArray(DataInputStream in) throws IOException { + int count = in.readInt(); + if (count == 0) + return null; + String[] result = new String[count]; + for (int i = 0; i < count; i++) + result[i] = readString(in); + return result; + } + + private static void writeList(DataOutputStream out, List<?> list) throws IOException { + if (list.isEmpty()) { + out.writeInt(0); + return; + } + byte type = getListType(list); + if (type < 0) { + out.writeInt(0); + return; // don't understand the list type + } + out.writeInt(list.size()); + out.writeByte(type); + for (Object value : list) { + switch (type) { + case VALUE_STRING : + writeString((String) value, out); + break; + case VALUE_INTEGER : + out.writeInt(((Integer) value).intValue()); + break; + case VALUE_LONG : + out.writeLong(((Long) value).longValue()); + break; + case VALUE_DOUBLE : + out.writeDouble(((Double) value).doubleValue()); + break; + case VALUE_VERSION : + writeVersion((Version) value, out); + break; + default : + break; + } + } + } + + private static byte getListType(List<?> list) { + if (list.size() == 0) + return -1; + Object type = list.get(0); + if (type instanceof String) + return VALUE_STRING; + if (type instanceof Integer) + return VALUE_INTEGER; + if (type instanceof Long) + return VALUE_LONG; + if (type instanceof Double) + return VALUE_DOUBLE; + if (type instanceof Version) + return VALUE_VERSION; + return -2; + } + + private static List<?> readList(DataInputStream in) throws IOException { + + int size = in.readInt(); + if (size == 0) + return new ArrayList<Object>(0); + byte listType = in.readByte(); + List<Object> list = new ArrayList<Object>(size); + for (int i = 0; i < size; i++) { + switch (listType) { + case VALUE_STRING : + list.add(readString(in)); + break; + case VALUE_INTEGER : + list.add(new Integer(in.readInt())); + break; + case VALUE_LONG : + list.add(new Long(in.readLong())); + break; + case VALUE_DOUBLE : + list.add(new Double(in.readDouble())); + break; + case VALUE_VERSION : + list.add(readVersion(in)); + break; + default : + throw new IOException("Invalid type: " + listType); //$NON-NLS-1$ + } + } + return list; + } + + private static void writeVersion(Version version, DataOutputStream out) throws IOException { + if (version == null || version.equals(Version.emptyVersion)) { + out.writeByte(NULL); + return; + } + out.writeByte(OBJECT); + out.writeInt(version.getMajor()); + out.writeInt(version.getMinor()); + out.writeInt(version.getMicro()); + writeQualifier(version.getQualifier(), out); + } + + private static void writeQualifier(String string, DataOutputStream out) throws IOException { + if (string != null && string.length() == 0) + string = null; + writeString(string, out); + } + + private static Version readVersion(DataInputStream in) throws IOException { + byte tag = in.readByte(); + if (tag == NULL) + return Version.emptyVersion; + int majorComponent = in.readInt(); + int minorComponent = in.readInt(); + int serviceComponent = in.readInt(); + String qualifierComponent = readString(in); + return (Version) ObjectPool.intern(new Version(majorComponent, minorComponent, serviceComponent, qualifierComponent)); + } + + private static void writeString(String string, DataOutputStream out) throws IOException { + if (string == null) + out.writeByte(NULL); + else { + byte[] data = string.getBytes(UTF_8); + + if (data.length > 65535) { + out.writeByte(LONG_STRING); + out.writeInt(data.length); + out.write(data); + } else { + out.writeByte(OBJECT); + out.writeUTF(string); + } + } + } + + static private String readString(DataInputStream in) throws IOException { + byte type = in.readByte(); + if (type == NULL) + return null; + + if (type == LONG_STRING) { + int length = in.readInt(); + byte[] data = new byte[length]; + in.readFully(data); + String string = new String(data, UTF_8); + + return (String) ObjectPool.intern(string); + } + + return (String) ObjectPool.intern(in.readUTF()); + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRequirement.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRequirement.java new file mode 100644 index 000000000..886630625 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRequirement.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2012 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.util.*; +import org.eclipse.osgi.framework.internal.core.FilterImpl; +import org.eclipse.osgi.internal.container.Capabilities; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.namespace.*; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleRequirement; +import org.osgi.resource.Namespace; + +/** + * An implementation of {@link BundleRequirement}. This requirement implements + * the matches method according to the OSGi specification which includes + * implementing the mandatory directive for the osgi.wiring.* namespaces. + */ +public class ModuleRequirement implements BundleRequirement { + private final String namespace; + private final Map<String, String> directives; + private final Map<String, Object> attributes; + private final ModuleRevision revision; + + ModuleRequirement(String namespace, Map<String, String> directives, Map<String, Object> attributes, ModuleRevision revision) { + this.namespace = namespace; + this.directives = Collections.unmodifiableMap(directives); + this.attributes = Collections.unmodifiableMap(attributes); + this.revision = revision; + } + + @Override + public ModuleRevision getRevision() { + return revision; + } + + @Override + public boolean matches(BundleCapability capability) { + if (!namespace.equals(capability.getNamespace())) + return false; + String filterSpec = directives.get(Namespace.REQUIREMENT_FILTER_DIRECTIVE); + FilterImpl f = null; + if (filterSpec != null) { + try { + f = FilterImpl.newInstance(filterSpec); + } catch (InvalidSyntaxException e) { + return false; + } + } + boolean matchMandatory = PackageNamespace.PACKAGE_NAMESPACE.equals(namespace) || BundleNamespace.BUNDLE_NAMESPACE.equals(namespace) || HostNamespace.HOST_NAMESPACE.equals(namespace); + return Capabilities.matches(f, capability, matchMandatory); + } + + @Override + public String getNamespace() { + return namespace; + } + + @Override + public Map<String, String> getDirectives() { + return directives; + } + + @Override + public Map<String, Object> getAttributes() { + return attributes; + } + + @Override + public ModuleRevision getResource() { + return revision; + } + + public String toString() { + return namespace + ModuleRevision.toString(attributes, false) + ModuleRevision.toString(directives, true); + } + + private static final String PACKAGENAME_FILTER_COMPONENT = PackageNamespace.PACKAGE_NAMESPACE + "="; + + DynamicModuleRequirement getDynamicPackageRequirement(ModuleRevision host, String dynamicPkgName) { + if (!PackageNamespace.PACKAGE_NAMESPACE.equals(namespace)) { + return null; + } + if (!PackageNamespace.RESOLUTION_DYNAMIC.equals(directives.get(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE))) { + // not dynamic + return null; + } + String dynamicFilter = directives.get(PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE); + // TODO we make some assumptions here on the format of the filter string + int packageNameBegin = dynamicFilter.indexOf(PACKAGENAME_FILTER_COMPONENT); + if (packageNameBegin == -1) { + // not much we can do + return null; + } + packageNameBegin += PACKAGENAME_FILTER_COMPONENT.length(); + int packageNameEnd = dynamicFilter.indexOf(')', packageNameBegin); + if (packageNameEnd == -1) { + // not much we can do + return null; + } + String filterPackageName = dynamicFilter.substring(packageNameBegin, packageNameEnd); + String specificPackageFilter = null; + if ("*".equals(filterPackageName)) { + // matches all + specificPackageFilter = dynamicFilter.replace(PACKAGENAME_FILTER_COMPONENT + filterPackageName, PACKAGENAME_FILTER_COMPONENT + dynamicPkgName); + } else if (filterPackageName.endsWith(".*")) { + if (dynamicPkgName.startsWith(filterPackageName.substring(0, filterPackageName.length() - 1))) { + specificPackageFilter = dynamicFilter.replace(PACKAGENAME_FILTER_COMPONENT + filterPackageName, PACKAGENAME_FILTER_COMPONENT + dynamicPkgName); + } + } else if (dynamicPkgName.equals(filterPackageName)) { + specificPackageFilter = dynamicFilter; + } + + if (specificPackageFilter != null) { + Map<String, String> dynamicDirectives = new HashMap<String, String>(directives); + dynamicDirectives.put(PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE, specificPackageFilter); + return new DynamicModuleRequirement(host, dynamicDirectives); + } + return null; + } + + class DynamicModuleRequirement extends ModuleRequirement { + + DynamicModuleRequirement(ModuleRevision host, Map<String, String> directives) { + super(ModuleRequirement.this.getNamespace(), directives, ModuleRequirement.this.getAttributes(), host); + } + + ModuleRequirement getOriginal() { + return ModuleRequirement.this; + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java new file mode 100644 index 000000000..f2b1b3b3b --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleResolver.java @@ -0,0 +1,663 @@ +/******************************************************************************* + * Copyright (c) 2012 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.util.*; +import org.apache.felix.resolver.ResolverImpl; +import org.eclipse.osgi.container.ModuleRequirement.DynamicModuleRequirement; +import org.eclipse.osgi.internal.container.Converters; +import org.osgi.framework.Version; +import org.osgi.framework.hooks.resolver.ResolverHook; +import org.osgi.framework.namespace.*; +import org.osgi.framework.wiring.*; +import org.osgi.resource.*; +import org.osgi.service.resolver.*; + +/** + * The module resolver handles calls to the {@link Resolver} service for resolving modules + * in a module {@link ModuleContainer container}. + */ +class ModuleResolver { + final ModuleContainerAdaptor adaptor; + + /** + * Constructs the module resolver with the specified resolver hook factory + * and resolver. + * @param adaptor the container adaptor + */ + ModuleResolver(ModuleContainerAdaptor adaptor) { + this.adaptor = adaptor; + } + + /** + * Attempts to resolve all unresolved modules installed in the specified module database. + * returns a delta containing the new wirings or modified wirings that should be + * merged into the specified moduleDatabase. + * <p> + * This method only does read operations on the database no wirings are modified + * directly by this method. The returned wirings need to be merged into + * the database. + * @param triggers the triggers that caused the resolver operation to occur + * @param triggersMandatory true if the triggers must be resolved by the resolve process + * @param unresolved a snapshot of unresolved revisions + * @param wiringCopy the wirings snapshot of the currently resolved revisions + * @param moduleDataBase the module database. + * @return a delta container the new wirings or modified wirings that should be + * merged into the moduleDatabase + * @throws ResolutionException + */ + Map<ModuleRevision, ModuleWiring> resolveDelta(Collection<ModuleRevision> triggers, boolean triggersMandatory, Collection<ModuleRevision> unresolved, Map<ModuleRevision, ModuleWiring> wiringCopy, ModuleDataBase moduleDataBase) throws ResolutionException { + ResolveProcess resolveProcess = new ResolveProcess(unresolved, triggers, triggersMandatory, wiringCopy, moduleDataBase); + Map<Resource, List<Wire>> result = resolveProcess.resolve(); + return generateDelta(result, wiringCopy); + } + + Map<ModuleRevision, ModuleWiring> resolveDynamicDelta(DynamicModuleRequirement dynamicReq, Collection<ModuleRevision> unresolved, Map<ModuleRevision, ModuleWiring> wiringCopy, ModuleDataBase moduleDataBase) throws ResolutionException { + ResolveProcess resolveProcess = new ResolveProcess(unresolved, dynamicReq, wiringCopy, moduleDataBase); + Map<Resource, List<Wire>> result = resolveProcess.resolve(); + return generateDelta(result, wiringCopy); + } + + private static Map<ModuleRevision, ModuleWiring> generateDelta(Map<Resource, List<Wire>> result, Map<ModuleRevision, ModuleWiring> wiringCopy) { + Map<ModuleRevision, Map<ModuleCapability, List<ModuleWire>>> provided = new HashMap<ModuleRevision, Map<ModuleCapability, List<ModuleWire>>>(); + Map<ModuleRevision, List<ModuleWire>> required = new HashMap<ModuleRevision, List<ModuleWire>>(); + // First populate the list of provided and required wires for revision + // This is done this way to share the wire object between both the provider and requirer + for (Map.Entry<Resource, List<Wire>> resultEntry : result.entrySet()) { + ModuleRevision revision = (ModuleRevision) resultEntry.getKey(); + List<ModuleWire> requiredWires = new ArrayList<ModuleWire>(resultEntry.getValue().size()); + for (Wire wire : resultEntry.getValue()) { + ModuleWire moduleWire = new ModuleWire((ModuleCapability) wire.getCapability(), (ModuleRevision) wire.getProvider(), (ModuleRequirement) wire.getRequirement(), (ModuleRevision) wire.getRequirer()); + requiredWires.add(moduleWire); + Map<ModuleCapability, List<ModuleWire>> providedWiresMap = provided.get(moduleWire.getProvider()); + if (providedWiresMap == null) { + providedWiresMap = new HashMap<ModuleCapability, List<ModuleWire>>(); + provided.put(moduleWire.getProvider(), providedWiresMap); + } + List<ModuleWire> providedWires = providedWiresMap.get(moduleWire.getCapability()); + if (providedWires == null) { + providedWires = new ArrayList<ModuleWire>(); + providedWiresMap.put(moduleWire.getCapability(), providedWires); + } + providedWires.add(moduleWire); + } + required.put(revision, requiredWires); + } + + Map<ModuleRevision, ModuleWiring> delta = new HashMap<ModuleRevision, ModuleWiring>(); + // now create the ModuleWiring for the newly resolved revisions + for (ModuleRevision revision : required.keySet()) { + ModuleWiring existingWiring = wiringCopy.get(revision); + if (existingWiring == null) { + delta.put(revision, createNewWiring(revision, provided, required)); + } else { + // this is to handle dynamic imports + delta.put(revision, createWiringDelta(revision, existingWiring, provided.get(revision), required.get(revision))); + } + } + // Also need to create the wiring deltas for already resolved bundles + // This should only include updating provided wires and + // for fragments it may include new hosts + for (ModuleRevision revision : provided.keySet()) { + ModuleWiring existingWiring = wiringCopy.get(revision); + if (existingWiring != null && !delta.containsKey(revision)) { + delta.put(revision, createWiringDelta(revision, existingWiring, provided.get(revision), required.get(revision))); + } + } + return delta; + } + + private static ModuleWiring createNewWiring(ModuleRevision revision, Map<ModuleRevision, Map<ModuleCapability, List<ModuleWire>>> provided, Map<ModuleRevision, List<ModuleWire>> required) { + Map<ModuleCapability, List<ModuleWire>> providedWireMap = provided.get(revision); + if (providedWireMap == null) + providedWireMap = Collections.emptyMap(); + List<ModuleWire> requiredWires = required.get(revision); + if (requiredWires == null) + requiredWires = Collections.emptyList(); + + List<ModuleCapability> capabilities = new ArrayList<ModuleCapability>(revision.getModuleCapabilities(null)); + ListIterator<ModuleCapability> iCapabilities = capabilities.listIterator(capabilities.size()); + List<ModuleRequirement> requirements = new ArrayList<ModuleRequirement>(revision.getModuleRequirements(null)); + ListIterator<ModuleRequirement> iRequirements = requirements.listIterator(requirements.size()); + + // add fragment capabilities and requirements + List<ModuleCapability> hostCapabilities = revision.getModuleCapabilities(HostNamespace.HOST_NAMESPACE); + ModuleCapability hostCapability = hostCapabilities.isEmpty() ? null : hostCapabilities.get(0); + if (hostCapability != null) { + addFragmentContent(providedWireMap.get(hostCapability), iCapabilities, iRequirements); + } + + removeNonEffectiveCapabilities(iCapabilities); + removeNonEffectiveRequirements(iRequirements, requiredWires); + Collection<String> substituted = removeSubstitutedCapabilities(iCapabilities, requiredWires); + + List<ModuleWire> providedWires = new ArrayList<ModuleWire>(); + addProvidedWires(providedWireMap, providedWires, capabilities); + + return new ModuleWiring(revision, capabilities, requirements, providedWires, requiredWires, substituted); + } + + private static Collection<String> removeSubstitutedCapabilities(ListIterator<ModuleCapability> iCapabilities, List<ModuleWire> requiredWires) { + Collection<String> substituted = null; + for (ModuleWire moduleWire : requiredWires) { + if (!PackageNamespace.PACKAGE_NAMESPACE.equals(moduleWire.getCapability().getNamespace())) + continue; + String packageName = (String) moduleWire.getCapability().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE); + rewind(iCapabilities); + while (iCapabilities.hasNext()) { + ModuleCapability capability = iCapabilities.next(); + if (PackageNamespace.PACKAGE_NAMESPACE.equals(capability.getNamespace())) { + if (packageName.equals(capability.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE))) { + // found a package capability with the same name as a package that got imported + // this indicates a substitution + iCapabilities.remove(); + if (substituted == null) { + substituted = new ArrayList<String>(); + } + substituted.add(packageName); + if (!substituted.contains(packageName)) { + substituted.add(packageName); + } + } + } + } + } + return substituted == null ? Collections.<String> emptyList() : substituted; + } + + private static void removeNonEffectiveRequirements(ListIterator<ModuleRequirement> iRequirements, List<ModuleWire> requiredWires) { + rewind(iRequirements); + requirements: while (iRequirements.hasNext()) { + ModuleRequirement requirement = iRequirements.next(); + // check the effective directive; + Object effective = requirement.getAttributes().get(Namespace.REQUIREMENT_EFFECTIVE_DIRECTIVE); + if (effective != null && !Namespace.EFFECTIVE_RESOLVE.equals(effective)) { + iRequirements.remove(); + break requirements; + } + // check the resolution directive + Object resolution = requirement.getAttributes().get(Namespace.REQUIREMENT_RESOLUTION_DIRECTIVE); + if (resolution != null && !Namespace.RESOLUTION_MANDATORY.equals(resolution)) { + boolean found = false; + // need to check the wires to see if the optional requirement is resolved + wires: for (ModuleWire wire : requiredWires) { + if (wire.getRequirement().equals(requirement)) { + found = true; + break wires; + } + } + if (!found) { + // optional requirement is not resolved + iRequirements.remove(); + break requirements; + } + } + } + } + + static void removeNonEffectiveCapabilities(ListIterator<ModuleCapability> iCapabilities) { + rewind(iCapabilities); + while (iCapabilities.hasNext()) { + Object effective = iCapabilities.next().getAttributes().get(Namespace.CAPABILITY_EFFECTIVE_DIRECTIVE); + if (effective != null && !Namespace.EFFECTIVE_RESOLVE.equals(effective)) + iCapabilities.remove(); + } + } + + private static void addFragmentContent(List<ModuleWire> hostWires, ListIterator<ModuleCapability> iCapabilities, ListIterator<ModuleRequirement> iRequirements) { + if (hostWires == null) + return; + for (ModuleWire hostWire : hostWires) { + // add fragment capabilities + String currentNamespace = null; + List<ModuleCapability> fragmentCapabilities = hostWire.getRequirer().getModuleCapabilities(null); + for (ModuleCapability fragmentCapability : fragmentCapabilities) { + if (IdentityNamespace.IDENTITY_NAMESPACE.equals(fragmentCapability.getNamespace())) + continue; // identity is not a payload + if (!fragmentCapability.getNamespace().equals(currentNamespace)) { + currentNamespace = fragmentCapability.getNamespace(); + fastForward(iCapabilities); + while (iCapabilities.hasPrevious()) { + if (iCapabilities.previous().getNamespace().equals(currentNamespace)) { + iCapabilities.next(); // put position after the last one + break; + } + } + } + iCapabilities.add(fragmentCapability); + } + // add fragment requirements + currentNamespace = null; + List<ModuleRequirement> fragmentRequriements = hostWire.getRequirer().getModuleRequirements(null); + for (ModuleRequirement fragmentRequirement : fragmentRequriements) { + String fragNamespace = fragmentRequirement.getNamespace(); + if (HostNamespace.HOST_NAMESPACE.equals(fragNamespace) || ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE.equals(fragNamespace)) + continue; // host and osgi.ee is not a payload + if (!fragmentRequirement.getNamespace().equals(currentNamespace)) { + currentNamespace = fragmentRequirement.getNamespace(); + fastForward(iRequirements); + while (iRequirements.hasPrevious()) { + if (iRequirements.previous().getNamespace().equals(currentNamespace)) { + iRequirements.next(); // put position after the last one + break; + } + } + } + iRequirements.add(fragmentRequirement); + } + } + } + + private static void addProvidedWires(Map<ModuleCapability, List<ModuleWire>> toAdd, List<ModuleWire> existing, final List<ModuleCapability> orderedCapabilities) { + if (toAdd == null) + return; + int originalSize = existing.size(); + for (ModuleCapability capability : orderedCapabilities) { + List<ModuleWire> newWires = toAdd.get(capability); + if (newWires != null) { + existing.addAll(newWires); + } + } + if (originalSize != 0) { + Collections.sort(existing, new Comparator<ModuleWire>() { + @Override + public int compare(ModuleWire w1, ModuleWire w2) { + int index1 = orderedCapabilities.indexOf(w1.getCapability()); + int index2 = orderedCapabilities.indexOf(w2.getCapability()); + return index1 - index2; + } + }); + } + } + + private static void addRequiredWires(List<ModuleWire> toAdd, List<ModuleWire> existing, final List<ModuleRequirement> orderedRequirements) { + if (toAdd == null) + return; + int originalSize = existing.size(); + existing.addAll(toAdd); + if (originalSize != 0) { + Collections.sort(existing, new Comparator<ModuleWire>() { + @Override + public int compare(ModuleWire w1, ModuleWire w2) { + int index1 = orderedRequirements.indexOf(w1.getRequirement()); + int index2 = orderedRequirements.indexOf(w2.getRequirement()); + return index1 - index2; + } + }); + } + } + + private static void fastForward(ListIterator<?> listIterator) { + while (listIterator.hasNext()) + listIterator.next(); + } + + static void rewind(ListIterator<?> listIterator) { + while (listIterator.hasPrevious()) + listIterator.previous(); + } + + @SuppressWarnings("unchecked") + private static ModuleWiring createWiringDelta(ModuleRevision revision, ModuleWiring existingWiring, Map<ModuleCapability, List<ModuleWire>> providedWireMap, List<ModuleWire> requiredWires) { + // Create a ModuleWiring that only contains the new ordered list of provided wires + List<ModuleWire> existingProvided = existingWiring.getProvidedModuleWires(null); + addProvidedWires(providedWireMap, existingProvided, existingWiring.getModuleCapabilities(null)); + + // Also need to include any new required wires that may have be added for fragment hosts + // Also will be needed for dynamic imports + List<ModuleWire> existingRequired = existingWiring.getRequiredModuleWires(null); + addRequiredWires(requiredWires, existingRequired, existingWiring.getModuleRequirements(null)); + return new ModuleWiring(revision, Collections.EMPTY_LIST, Collections.EMPTY_LIST, existingProvided, existingRequired, Collections.EMPTY_LIST); + } + + static boolean isSingleton(ModuleRevision revision) { + List<Capability> identities = revision.getCapabilities(IdentityNamespace.IDENTITY_NAMESPACE); + if (identities.isEmpty()) + return false; + return "true".equals(identities.get(0).getDirectives().get(IdentityNamespace.CAPABILITY_SINGLETON_DIRECTIVE)); //$NON-NLS-1$ + } + + static Version getVersion(Capability c) { + String versionAttr = null; + String namespace = c.getNamespace(); + if (IdentityNamespace.IDENTITY_NAMESPACE.equals(namespace)) { + versionAttr = IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE; + } else if (PackageNamespace.PACKAGE_NAMESPACE.equals(namespace)) { + versionAttr = PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE; + } else if (BundleNamespace.BUNDLE_NAMESPACE.equals(namespace)) { + versionAttr = BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE; + } else if (HostNamespace.HOST_NAMESPACE.equals(namespace)) { + versionAttr = HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE; + } else { + // Just default to version attribute + versionAttr = IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE; + } + Object version = c.getAttributes().get(versionAttr); + return version instanceof Version ? (Version) version : Version.emptyVersion; + } + + class ResolveProcess extends ResolveContext implements Comparator<Capability> { + private final Collection<ModuleRevision> unresolved; + private final Collection<ModuleRevision> disabled; + private final Collection<ModuleRevision> triggers; + private final Collection<ModuleRevision> optionals; + private final boolean triggersMandatory; + private final ModuleDataBase moduleDataBase; + private final Map<ModuleRevision, ModuleWiring> wirings; + private final DynamicModuleRequirement dynamicReq; + private volatile ResolverHook hook = null; + private volatile Map<String, Collection<ModuleRevision>> byName = null; + + ResolveProcess(Collection<ModuleRevision> unresolved, Collection<ModuleRevision> triggers, boolean triggersMandatory, Map<ModuleRevision, ModuleWiring> wirings, ModuleDataBase moduleDataBase) { + this.unresolved = unresolved; + this.disabled = new HashSet<ModuleRevision>(unresolved); + this.triggers = triggers; + this.triggersMandatory = triggersMandatory; + this.optionals = new ArrayList<ModuleRevision>(unresolved); + if (this.triggersMandatory) { + this.optionals.removeAll(triggers); + } + this.wirings = wirings; + this.moduleDataBase = moduleDataBase; + this.dynamicReq = null; + } + + ResolveProcess(Collection<ModuleRevision> unresolved, DynamicModuleRequirement dynamicReq, Map<ModuleRevision, ModuleWiring> wirings, ModuleDataBase moduleDataBase) { + this.unresolved = unresolved; + this.disabled = new HashSet<ModuleRevision>(unresolved); + ModuleRevision revision = dynamicReq.getRevision(); + this.triggers = new ArrayList<ModuleRevision>(1); + this.triggers.add(revision); + this.triggersMandatory = false; + this.optionals = new ArrayList<ModuleRevision>(unresolved); + this.wirings = wirings; + this.moduleDataBase = moduleDataBase; + this.dynamicReq = dynamicReq; + } + + @Override + public List<Capability> findProviders(Requirement requirement) { + List<ModuleCapability> candidates = moduleDataBase.findCapabilities((ModuleRequirement) requirement); + return filterProviders(requirement, candidates); + } + + private List<Capability> filterProviders(Requirement requirement, List<ModuleCapability> candidates) { + ListIterator<ModuleCapability> iCandidates = candidates.listIterator(); + filterDisabled(iCandidates); + removeNonEffectiveCapabilities(iCandidates); + removeSubstituted(iCandidates); + hook.filterMatches((BundleRequirement) requirement, Converters.asListBundleCapability(candidates)); + Collections.sort(candidates, this); + return Converters.asListCapability(candidates); + } + + private void filterDisabled(ListIterator<ModuleCapability> iCandidates) { + rewind(iCandidates); + while (iCandidates.hasNext()) { + if (disabled.contains(iCandidates.next().getResource())) + iCandidates.remove(); + } + } + + private void removeSubstituted(ListIterator<ModuleCapability> iCapabilities) { + rewind(iCapabilities); + while (iCapabilities.hasNext()) { + ModuleCapability capability = iCapabilities.next(); + ModuleWiring wiring = wirings.get(capability.getRevision()); + if (wiring != null && wiring.isSubtituted(capability)) { + iCapabilities.remove(); + } + } + } + + @Override + public int insertHostedCapability(List<Capability> capabilities, HostedCapability hostedCapability) { + int index = Collections.binarySearch(capabilities, hostedCapability, this); + if (index < 0) + index = -index - 1; + capabilities.add(index, hostedCapability); + return index; + } + + @Override + public boolean isEffective(Requirement requirement) { + String effective = requirement.getDirectives().get(Namespace.REQUIREMENT_EFFECTIVE_DIRECTIVE); + return effective == null || Namespace.EFFECTIVE_RESOLVE.equals(effective); + } + + @SuppressWarnings("unchecked") + @Override + public Map<Resource, Wiring> getWirings() { + Map<?, ?> raw = wirings; + return Collections.unmodifiableMap((Map<Resource, Wiring>) raw); + } + + @Override + public Collection<Resource> getMandatoryResources() { + if (triggersMandatory) { + return Converters.asCollectionResource(triggers); + } + return super.getMandatoryResources(); + } + + @Override + public Collection<Resource> getOptionalResources() { + return Converters.asCollectionResource(optionals); + } + + Map<Resource, List<Wire>> resolve() throws ResolutionException { + hook = adaptor.getResolverHookFactory().begin(Converters.asListBundleRevision((List<? extends BundleRevision>) triggers)); + try { + filterResolvable(); + selectSingletons(); + // remove disabled from optional and triggers to prevent the resolver from resolving them + optionals.removeAll(disabled); + if (triggers.removeAll(disabled) && triggersMandatory) { + throw new ResolutionException("Could not resolve mandatory modules because another singleton was selected or the module was disabled: " + disabled); + } + if (dynamicReq != null) { + return resolveDynamic(); + } + return adaptor.getResolver().resolve(this); + } finally { + hook.end(); + } + } + + private Map<Resource, List<Wire>> resolveDynamic() throws ResolutionException { + Resolver resolver = adaptor.getResolver(); + if (!(resolver instanceof ResolverImpl)) { + throw new ResolutionException("Dynamic import resolution not supported by the resolver: " + resolver.getClass()); + } + List<Capability> dynamicMatches = filterProviders(dynamicReq.getOriginal(), moduleDataBase.findCapabilities(dynamicReq)); + Collection<Resource> ondemandFragments = Converters.asCollectionResource(moduleDataBase.getFragmentRevisions()); + + return ((ResolverImpl) resolver).resolve(this, dynamicReq.getRevision(), dynamicReq.getOriginal(), dynamicMatches, ondemandFragments); + + } + + private void filterResolvable() { + Collection<ModuleRevision> enabledCandidates = new ArrayList<ModuleRevision>(unresolved); + hook.filterResolvable(Converters.asListBundleRevision((List<? extends BundleRevision>) enabledCandidates)); + disabled.removeAll(enabledCandidates); + } + + private void selectSingletons() { + Map<String, Collection<ModuleRevision>> selectedSingletons = new HashMap<String, Collection<ModuleRevision>>(); + for (ModuleRevision revision : unresolved) { + if (!isSingleton(revision) || disabled.contains(revision)) + continue; + String bsn = revision.getSymbolicName(); + Collection<ModuleRevision> selected = selectedSingletons.get(bsn); + if (selected != null) + continue; // already processed the bsn + selected = new ArrayList<ModuleRevision>(1); + selectedSingletons.put(bsn, selected); + + // TODO out of band call that obtains the read lock + // Should generate our own copy Map<String, Collection<ModuleRevision>> + Collection<ModuleRevision> sameBSN = getRevisions(bsn); + if (sameBSN.size() < 2) { + selected.add(revision); + continue; + } + // prime selected with resolved singleton bundles + for (ModuleRevision singleton : sameBSN) { + if (isSingleton(singleton) && wirings.containsKey(singleton)) + selected.add(singleton); + } + // get the collision map for the BSN + Map<ModuleRevision, Collection<ModuleRevision>> collisionMap = getCollisionMap(sameBSN); + // process the collision map + for (ModuleRevision singleton : sameBSN) { + if (selected.contains(singleton)) + continue; // no need to process resolved bundles + Collection<ModuleRevision> collisions = collisionMap.get(singleton); + if (collisions == null || disabled.contains(singleton)) + continue; // not a singleton or not resolvable + Collection<ModuleRevision> pickOneToResolve = new ArrayList<ModuleRevision>(); + for (ModuleRevision collision : collisions) { + if (selected.contains(collision)) { + // Must fail since there is already a selected bundle which is a collision of the singleton bundle + disabled.add(singleton); + // TODO add resolver diagnostics here + //state.addResolverError(singleton.getBundleDescription(), ResolverError.SINGLETON_SELECTION, collision.getBundleDescription().toString(), null); + break; + } + if (!pickOneToResolve.contains(collision)) + pickOneToResolve.add(collision); + } + // need to make sure the bundle does not collide from the POV of another entry + for (Map.Entry<ModuleRevision, Collection<ModuleRevision>> collisionEntry : collisionMap.entrySet()) { + if (collisionEntry.getKey() != singleton && collisionEntry.getValue().contains(singleton)) { + if (selected.contains(collisionEntry.getKey())) { + // Must fail since there is already a selected bundle for which the singleton bundle is a collision + disabled.add(singleton); + // TODO add resolver diagnostics here + // state.addResolverError(singleton.getBundleDescription(), ResolverError.SINGLETON_SELECTION, collisionEntry.getKey().getBundleDescription().toString(), null); + break; + } + if (!pickOneToResolve.contains(collisionEntry.getKey())) + pickOneToResolve.add(collisionEntry.getKey()); + } + } + if (!disabled.contains(singleton)) { + pickOneToResolve.add(singleton); + selected.add(pickOneToResolve(pickOneToResolve)); + } + } + } + } + + private Collection<ModuleRevision> getRevisions(String name) { + Map<String, Collection<ModuleRevision>> current = byName; + if (current == null) { + Set<ModuleRevision> revisions = new HashSet<ModuleRevision>(); + revisions.addAll(unresolved); + revisions.addAll(wirings.keySet()); + current = new HashMap<String, Collection<ModuleRevision>>(); + for (ModuleRevision revision : revisions) { + Collection<ModuleRevision> sameName = current.get(revision.getSymbolicName()); + if (sameName == null) { + sameName = new ArrayList<ModuleRevision>(); + current.put(revision.getSymbolicName(), sameName); + } + sameName.add(revision); + } + byName = current; + } + Collection<ModuleRevision> result = current.get(name); + if (result == null) { + return Collections.emptyList(); + } + return result; + } + + private ModuleRevision pickOneToResolve(Collection<ModuleRevision> pickOneToResolve) { + ModuleRevision selectedVersion = null; + for (ModuleRevision singleton : pickOneToResolve) { + if (selectedVersion == null) + selectedVersion = singleton; + boolean higherVersion = selectedVersion.getVersion().compareTo(singleton.getVersion()) < 0; + if (higherVersion) + selectedVersion = singleton; + } + + for (ModuleRevision singleton : pickOneToResolve) { + if (singleton != selectedVersion) { + disabled.add(singleton); + // TODO add resolver diagnostic here. + // state.addResolverError(singleton.getBundleDescription(), ResolverError.SINGLETON_SELECTION, selectedVersion.getBundleDescription().toString(), null); + } + } + return selectedVersion; + } + + private Map<ModuleRevision, Collection<ModuleRevision>> getCollisionMap(Collection<ModuleRevision> sameBSN) { + Map<ModuleRevision, Collection<ModuleRevision>> result = new HashMap<ModuleRevision, Collection<ModuleRevision>>(); + for (ModuleRevision singleton : sameBSN) { + if (!isSingleton(singleton) || disabled.contains(singleton)) + continue; // ignore non-singleton and non-resolvable + List<BundleCapability> capabilities = new ArrayList<BundleCapability>(sameBSN.size() - 1); + for (ModuleRevision collision : sameBSN) { + if (collision == singleton || !isSingleton(collision) || disabled.contains(collision)) + continue; // Ignore the bundle we are checking and non-singletons and non-resolvable + capabilities.add(getIdentity(collision)); + } + hook.filterSingletonCollisions(getIdentity(singleton), capabilities); + Collection<ModuleRevision> collisionCandidates = new ArrayList<ModuleRevision>(capabilities.size()); + for (BundleCapability identity : capabilities) { + collisionCandidates.add((ModuleRevision) identity.getRevision()); + } + result.put(singleton, collisionCandidates); + } + return result; + } + + private BundleCapability getIdentity(ModuleRevision bundle) { + List<BundleCapability> identities = bundle.getDeclaredCapabilities(IdentityNamespace.IDENTITY_NAMESPACE); + return identities.isEmpty() ? null : identities.get(0); + } + + @Override + public int compare(Capability c1, Capability c2) { + // TODO Ideally this policy should be handled by the ModuleDataBase. + // To do that the wirings would have to be provided since the wirings may + // be a subset of the current wirings provided by the ModuleDataBase + boolean resolved1 = wirings.get(c1.getResource()) != null; + boolean resolved2 = wirings.get(c2.getResource()) != null; + if (resolved1 != resolved2) + return resolved1 ? -1 : 1; + + Version v1 = getVersion(c1); + Version v2 = getVersion(c2); + int versionCompare = -(v1.compareTo(v2)); + if (versionCompare != 0) + return versionCompare; + + // We assume all resources here come from us and are ModuleRevision objects + ModuleRevision m1 = (ModuleRevision) c1.getResource(); + ModuleRevision m2 = (ModuleRevision) c2.getResource(); + Long id1 = m1.getRevisions().getModule().getId(); + Long id2 = m2.getRevisions().getModule().getId(); + + if (id1.equals(id2) && !m1.equals(m2)) { + // sort based on revision ordering + List<ModuleRevision> revisions = m1.getRevisions().getModuleRevisions(); + int index1 = revisions.indexOf(m1); + int index2 = revisions.indexOf(m2); + // we want to sort the indexes from highest to lowest + return index2 - index1; + } + return id1.compareTo(id2); + } + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevision.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevision.java new file mode 100644 index 000000000..7610b3f9a --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevision.java @@ -0,0 +1,199 @@ +/******************************************************************************* + * Copyright (c) 2012 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.util.*; +import java.util.Map.Entry; +import org.eclipse.osgi.container.ModuleRevisionBuilder.GenericInfo; +import org.eclipse.osgi.internal.container.Converters; +import org.osgi.framework.Bundle; +import org.osgi.framework.Version; +import org.osgi.framework.namespace.IdentityNamespace; +import org.osgi.framework.wiring.*; +import org.osgi.resource.Capability; +import org.osgi.resource.Requirement; + +/** + * An implementation of {@link BundleRevision}. + */ +public class ModuleRevision implements BundleRevision { + private final String symbolicName; + private final Version version; + private final int types; + private final List<ModuleCapability> capabilities; + private final List<ModuleRequirement> requirements; + private final ModuleRevisions revisions; + + ModuleRevision(String symbolicName, Version version, int types, List<GenericInfo> capabilityInfos, List<GenericInfo> requirementInfos, ModuleRevisions revisions) { + this.symbolicName = symbolicName; + this.version = version; + this.types = types; + this.capabilities = createCapabilities(capabilityInfos); + this.requirements = createRequirements(requirementInfos); + this.revisions = revisions; + } + + private List<ModuleCapability> createCapabilities(List<GenericInfo> capabilityInfos) { + if (capabilityInfos == null || capabilityInfos.isEmpty()) + return Collections.emptyList(); + List<ModuleCapability> result = new ArrayList<ModuleCapability>(capabilityInfos.size()); + for (GenericInfo info : capabilityInfos) { + result.add(new ModuleCapability(info.namespace, info.directives, info.attributes, this)); + } + return result; + } + + private List<ModuleRequirement> createRequirements(List<GenericInfo> requirementInfos) { + if (requirementInfos == null || requirementInfos.isEmpty()) + return Collections.emptyList(); + List<ModuleRequirement> result = new ArrayList<ModuleRequirement>(requirementInfos.size()); + for (GenericInfo info : requirementInfos) { + result.add(new ModuleRequirement(info.namespace, info.directives, info.attributes, this)); + } + return result; + } + + @Override + public Bundle getBundle() { + return revisions.getBundle(); + } + + @Override + public String getSymbolicName() { + return symbolicName; + } + + @Override + public Version getVersion() { + return version; + } + + @Override + public List<BundleCapability> getDeclaredCapabilities(String namespace) { + return Converters.asListBundleCapability(getModuleCapabilities(namespace)); + } + + @Override + public List<BundleRequirement> getDeclaredRequirements(String namespace) { + return Converters.asListBundleRequirement(getModuleRequirements(namespace)); + } + + /** + * Returns the capabilities declared by this revision + * @param namespace The namespace of the declared capabilities to return or + * {@code null} to return the declared capabilities from all namespaces. + * @return An unmodifiable list containing the declared capabilities. + */ + public List<ModuleCapability> getModuleCapabilities(String namespace) { + if (namespace == null) + return Collections.unmodifiableList(capabilities); + List<ModuleCapability> result = new ArrayList<ModuleCapability>(); + for (ModuleCapability capability : capabilities) { + if (namespace.equals(capability.getNamespace())) { + result.add(capability); + } + } + return Collections.unmodifiableList(result); + } + + /** + * Returns the requirements declared by this revision + * @param namespace The namespace of the declared requirements to return or + * {@code null} to return the declared requirements from all namespaces. + * @return An unmodifiable list containing the declared requirements. + */ + public List<ModuleRequirement> getModuleRequirements(String namespace) { + if (namespace == null) + return Collections.unmodifiableList(requirements); + List<ModuleRequirement> result = new ArrayList<ModuleRequirement>(); + for (ModuleRequirement requirement : requirements) { + if (namespace.equals(requirement.getNamespace())) { + result.add(requirement); + } + } + return Collections.unmodifiableList(result); + } + + @Override + public int getTypes() { + return types; + } + + @Override + public ModuleWiring getWiring() { + return revisions.getContainer().getWiring(this); + } + + @Override + public List<Capability> getCapabilities(String namespace) { + return Converters.asListCapability(getDeclaredCapabilities(namespace)); + } + + @Override + public List<Requirement> getRequirements(String namespace) { + return Converters.asListRequirement(getDeclaredRequirements(namespace)); + } + + /** + * Returns the {@link ModuleRevisions revisions} for this revision. + * @return the {@link ModuleRevisions revisions} for this revision. + */ + public ModuleRevisions getRevisions() { + return revisions; + } + + boolean isCurrent() { + return this.equals(revisions.getCurrentRevision()); + } + + @Override + public String toString() { + List<ModuleCapability> identities = getModuleCapabilities(IdentityNamespace.IDENTITY_NAMESPACE); + if (identities.isEmpty()) + return super.toString(); + return identities.get(0).toString(); + } + + static <V> String toString(Map<String, V> map, boolean directives) { + if (map.size() == 0) + return ""; //$NON-NLS-1$ + String assignment = directives ? ":=" : "="; //$NON-NLS-1$ //$NON-NLS-2$ + Set<Entry<String, V>> set = map.entrySet(); + StringBuffer sb = new StringBuffer(); + for (Entry<String, V> entry : set) { + sb.append("; "); //$NON-NLS-1$ + String key = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof List) { + @SuppressWarnings("unchecked") + List<Object> list = (List<Object>) value; + if (list.size() == 0) + continue; + Object component = list.get(0); + String className = component.getClass().getName(); + String type = className.substring(className.lastIndexOf('.') + 1); + sb.append(key).append(':').append("List<").append(type).append(">").append(assignment).append('"'); //$NON-NLS-1$ //$NON-NLS-2$ + for (Object object : list) + sb.append(object).append(','); + sb.setLength(sb.length() - 1); + sb.append('"'); + } else { + String type = ""; //$NON-NLS-1$ + if (!(value instanceof String)) { + String className = value.getClass().getName(); + type = ":" + className.substring(className.lastIndexOf('.') + 1); //$NON-NLS-1$ + } + sb.append(key).append(type).append(assignment).append('"').append(value).append('"'); + } + } + return sb.toString(); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisionBuilder.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisionBuilder.java new file mode 100644 index 000000000..941337523 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisionBuilder.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2012 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.util.*; +import org.osgi.framework.Version; + +/** + * 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) + * installing} or {@link ModuleContainer#update(Module, ModuleRevisionBuilder) updating} a module. + * <p> + * The builder provides the instructions to the container for creating a {@link ModuleRevision}. + */ +public class ModuleRevisionBuilder { + /** + * Provides information about a capability or requirement + */ + static class GenericInfo { + GenericInfo(String namespace, Map<String, String> directives, Map<String, Object> attributes) { + this.namespace = namespace; + this.directives = directives; + this.attributes = attributes; + } + + final String namespace; + final Map<String, String> directives; + final Map<String, Object> attributes; + } + + private String symbolicName = null; + private Version version = Version.emptyVersion; + private int types = 0; + private List<GenericInfo> capabilityInfos = null; + private List<GenericInfo> requirementInfos = null; + + /** + * 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) { + capabilityInfos = addGenericInfo(capabilityInfos, namespace, directives, attributes); + } + + /** + * 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) { + requirementInfos = addGenericInfo(requirementInfos, namespace, directives, attributes); + } + + /** + * 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 + * @return the new new {@link Module#getCurrentRevision() current} revision. + */ + ModuleRevision addRevision(Module module) { + ModuleRevisions revisions = module.getRevisions(); + ModuleRevision revision = new ModuleRevision(symbolicName, version, types, capabilityInfos, requirementInfos, revisions); + revisions.addRevision(revision); + return revision; + } + + private static List<GenericInfo> addGenericInfo(List<GenericInfo> infos, String namespace, Map<String, String> directives, Map<String, Object> attributes) { + if (infos == null) { + infos = new ArrayList<GenericInfo>(); + } + infos.add(new GenericInfo(namespace, directives, attributes)); + return infos; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisions.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisions.java new file mode 100644 index 000000000..06e4a3767 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleRevisions.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2012 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.util.ArrayList; +import java.util.List; +import org.osgi.framework.Bundle; +import org.osgi.framework.wiring.BundleRevision; +import org.osgi.framework.wiring.BundleRevisions; + +/** + * An implementation of {@link BundleRevisions} which represent a + * {@link Module} installed in a {@link ModuleContainer container}. + * The ModuleRevisions provides a bridge between the revisions, the + * module and the container they are associated with. The + * ModuleRevisions holds the information about the installation of + * a module in a container such as the module id and location. + */ +public class ModuleRevisions implements BundleRevisions { + private final Object monitor = new Object(); + private final Module module; + private final ModuleContainer container; + /* @GuardedBy("monitor") */ + private final List<ModuleRevision> revisions = new ArrayList<ModuleRevision>(1); + private volatile boolean uninstalled = false; + + ModuleRevisions(Module module, ModuleContainer container) { + this.module = module; + this.container = container; + } + + Module getModule() { + return module; + } + + ModuleContainer getContainer() { + return container; + } + + @Override + public Bundle getBundle() { + return module.getBundle(); + } + + @Override + public List<BundleRevision> getRevisions() { + synchronized (monitor) { + return new ArrayList<BundleRevision>(revisions); + } + } + + List<ModuleRevision> getModuleRevisions() { + synchronized (monitor) { + return new ArrayList<ModuleRevision>(revisions); + } + } + + /** + * Returns the current {@link ModuleRevision revision} associated with this revisions. + * @return the current {@link ModuleRevision revision} associated with this revisions + * or {@code null} if the current revision does not exist. + */ + ModuleRevision getCurrentRevision() { + synchronized (monitor) { + if (revisions.isEmpty() || uninstalled) { + return null; + } + return revisions.get(0); + } + } + + ModuleRevision addRevision(ModuleRevision revision) { + synchronized (monitor) { + revisions.add(0, revision); + } + return revision; + } + + boolean removeRevision(ModuleRevision revision) { + try { + synchronized (monitor) { + return revisions.remove(revision); + } + } finally { + module.cleanup(revision); + } + } + + boolean isUninstalled() { + return uninstalled; + } + + void uninstall() { + uninstalled = true; + } + + public String toString() { + return "moduleID=" + module.getId(); //$NON-NLS-1$ + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWire.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWire.java new file mode 100644 index 000000000..085f1dd9e --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWire.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2012 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 org.osgi.framework.wiring.BundleWire; + +/** + * An implementation of {@link BundleWire}. + */ +public class ModuleWire implements BundleWire { + private final ModuleCapability capability; + private final ModuleRevision hostingProvider; + private final ModuleRequirement requirement; + private final ModuleRevision hostingRequirer; + // indicates that the wire points to valid wirings + // technically this should be a separate flag for requirer vs provider but that seems like overkill + private volatile boolean isValid = true; + + ModuleWire(ModuleCapability capability, ModuleRevision hostingProvider, ModuleRequirement requirement, ModuleRevision hostingRequirer) { + super(); + this.capability = capability; + this.hostingProvider = hostingProvider; + this.requirement = requirement; + this.hostingRequirer = hostingRequirer; + } + + @Override + public ModuleCapability getCapability() { + return capability; + } + + @Override + public ModuleRequirement getRequirement() { + return requirement; + } + + @Override + public ModuleWiring getProviderWiring() { + if (!isValid) { + return null; + } + return hostingProvider.getWiring(); + } + + @Override + public ModuleWiring getRequirerWiring() { + if (!isValid) { + return null; + } + return hostingRequirer.getWiring(); + } + + @Override + public ModuleRevision getProvider() { + return hostingProvider; + } + + @Override + public ModuleRevision getRequirer() { + return hostingRequirer; + } + + public String toString() { + return getRequirement() + " -> " + getCapability(); //$NON-NLS-1$ + } + + void invalidate() { + this.isValid = false; + } + +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWiring.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWiring.java new file mode 100644 index 000000000..883ac3856 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/ModuleWiring.java @@ -0,0 +1,255 @@ +/******************************************************************************* + * Copyright (c) 2012 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.net.URL; +import java.util.*; +import org.eclipse.osgi.internal.container.Converters; +import org.osgi.framework.AdminPermission; +import org.osgi.framework.Bundle; +import org.osgi.framework.namespace.HostNamespace; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.framework.wiring.*; +import org.osgi.resource.*; + +/** + * An implementation of {@link BundleWiring}. + */ +public class ModuleWiring implements BundleWiring { + private static final RuntimePermission GET_CLASSLOADER_PERM = new RuntimePermission("getClassLoader"); //$NON-NLS-1$ + private final ModuleRevision revision; + private final List<ModuleCapability> capabilities; + private final List<ModuleRequirement> requirements; + private final Collection<String> substitutedPkgNames; + private final Object monitor = new Object(); + private ModuleClassLoader loader = null; + private volatile List<ModuleWire> providedWires; + private volatile List<ModuleWire> requiredWires; + private volatile boolean isValid = true; + + ModuleWiring(ModuleRevision revision, List<ModuleCapability> capabilities, List<ModuleRequirement> requirements, List<ModuleWire> providedWires, List<ModuleWire> requiredWires, Collection<String> substitutedPkgNames) { + super(); + this.revision = revision; + this.capabilities = capabilities; + this.requirements = requirements; + this.providedWires = providedWires; + this.requiredWires = requiredWires; + this.substitutedPkgNames = substitutedPkgNames; + } + + @Override + public Bundle getBundle() { + return revision.getBundle(); + } + + @Override + public boolean isCurrent() { + return isValid && revision.isCurrent(); + } + + @Override + public boolean isInUse() { + return isCurrent() || !providedWires.isEmpty() || isFragmentInUse(); + } + + private boolean isFragmentInUse() { + // A fragment is considered in use if it has any required host wires + return ((BundleRevision.TYPE_FRAGMENT & revision.getTypes()) != 0) && !getRequiredWires(HostNamespace.HOST_NAMESPACE).isEmpty(); + } + + public List<ModuleCapability> getModuleCapabilities(String namespace) { + if (!isValid) + return null; + if (namespace == null) + return new ArrayList<ModuleCapability>(capabilities); + List<ModuleCapability> result = new ArrayList<ModuleCapability>(); + for (ModuleCapability capability : capabilities) { + if (namespace.equals(capability.getNamespace())) { + result.add(capability); + } + } + return result; + } + + public List<ModuleRequirement> getModuleRequirements(String namespace) { + if (!isValid) + return null; + if (namespace == null) + return new ArrayList<ModuleRequirement>(requirements); + List<ModuleRequirement> result = new ArrayList<ModuleRequirement>(); + for (ModuleRequirement requirement : requirements) { + if (namespace.equals(requirement.getNamespace())) { + result.add(requirement); + } + } + return result; + } + + @Override + public List<BundleCapability> getCapabilities(String namespace) { + return Converters.asListBundleCapability(getModuleCapabilities(namespace)); + + } + + @Override + public List<BundleRequirement> getRequirements(String namespace) { + return Converters.asListBundleRequirement(getModuleRequirements(namespace)); + } + + public List<ModuleWire> getProvidedModuleWires(String namespace) { + return getWires(namespace, providedWires); + } + + public List<ModuleWire> getRequiredModuleWires(String namespace) { + return getWires(namespace, requiredWires); + } + + @Override + public List<BundleWire> getProvidedWires(String namespace) { + return Converters.asListBundleWire(getWires(namespace, providedWires)); + } + + @Override + public List<BundleWire> getRequiredWires(String namespace) { + return Converters.asListBundleWire(getWires(namespace, requiredWires)); + } + + private List<ModuleWire> getWires(String namespace, List<ModuleWire> allWires) { + if (!isValid) + return null; + if (namespace == null) + return new ArrayList<ModuleWire>(allWires); + List<ModuleWire> result = new ArrayList<ModuleWire>(); + for (ModuleWire moduleWire : allWires) { + if (namespace.equals(moduleWire.getCapability().getNamespace())) { + result.add(moduleWire); + } + } + return result; + } + + @Override + public ModuleRevision getRevision() { + return revision; + } + + @Override + public ClassLoader getClassLoader() { + return (ClassLoader) getModuleClassLoader(); + } + + public ModuleClassLoader getModuleClassLoader() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + sm.checkPermission(GET_CLASSLOADER_PERM); + } + synchronized (monitor) { + if (!isValid) { + return null; + } + if (loader == null) { + loader = revision.getRevisions().getContainer().adaptor.createClassLoader(this); + } + return loader; + } + + } + + @Override + public List<URL> findEntries(String path, String filePattern, int options) { + if (!hasResourcePermission()) + return Collections.emptyList(); + ModuleClassLoader current = getModuleClassLoader(); + if (current == null) { + // must not be valid + return null; + } + return current.findEntries(path, filePattern, options); + } + + @Override + public Collection<String> listResources(String path, String filePattern, int options) { + if (!hasResourcePermission()) + return Collections.emptyList(); + ModuleClassLoader current = getModuleClassLoader(); + if (current == null) { + // must not be valid + return null; + } + return current.listResources(path, filePattern, options); + } + + @Override + public List<Capability> getResourceCapabilities(String namespace) { + return Converters.asListCapability(getCapabilities(namespace)); + } + + @Override + public List<Requirement> getResourceRequirements(String namespace) { + return Converters.asListRequirement(getRequirements(namespace)); + } + + @Override + public List<Wire> getProvidedResourceWires(String namespace) { + return Converters.asListWire(getWires(namespace, providedWires)); + } + + @Override + public List<Wire> getRequiredResourceWires(String namespace) { + return Converters.asListWire(getWires(namespace, requiredWires)); + } + + @Override + public ModuleRevision getResource() { + return revision; + } + + void setProvidedWires(List<ModuleWire> providedWires) { + this.providedWires = providedWires; + } + + void setRequiredWires(List<ModuleWire> requiredWires) { + this.requiredWires = requiredWires; + } + + void invalidate() { + synchronized (monitor) { + this.isValid = false; + if (loader != null) { + loader.close(); + loader = null; + } + } + } + + boolean isSubtituted(ModuleCapability capability) { + if (!PackageNamespace.PACKAGE_NAMESPACE.equals(capability.getNamespace())) { + return false; + } + return substitutedPkgNames.contains(capability.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE)); + } + + Collection<String> getSubstitutedNames() { + return substitutedPkgNames; + } + + private boolean hasResourcePermission() { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + try { + sm.checkPermission(new AdminPermission(getBundle(), AdminPermission.RESOURCE)); + } catch (SecurityException e) { + return false; + } + } + return true; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/SystemModule.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/SystemModule.java new file mode 100644 index 000000000..28a69b8c5 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/SystemModule.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2012 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.util.Arrays; +import java.util.EnumSet; +import org.eclipse.osgi.container.ModuleContainer.ContainerStartLevel; +import org.eclipse.osgi.container.ModuleContainerAdaptor.ContainerEvent; +import org.osgi.framework.BundleException; +import org.osgi.framework.Constants; +import org.osgi.service.resolver.ResolutionException; + +public abstract class SystemModule extends Module { + + public SystemModule(ModuleContainer container) { + super(new Long(0), Constants.SYSTEM_BUNDLE_LOCATION, container, EnumSet.of(Settings.AUTO_START, Settings.USE_ACTIVATION_POLICY), new Integer(0)); + } + + public void init() throws BundleException { + getRevisions().getContainer().open(); + lockStateChange(Event.STARTED); + try { + checkValid(); + if (ACTIVE_SET.contains(getState())) + return; + if (getState().equals(State.INSTALLED)) { + try { + getRevisions().getContainer().resolve(Arrays.asList((Module) this), true); + } catch (ResolutionException e) { + throw new BundleException("Could not resolve module.", BundleException.RESOLVE_ERROR, e); + } + } + if (getState().equals(State.INSTALLED)) { + throw new BundleException("Could not resolve module.", BundleException.RESOLVE_ERROR); + } + setState(State.STARTING); + publishEvent(Event.STARTING); + try { + initWorker(); + } catch (Throwable t) { + setState(State.STOPPING); + publishEvent(Event.STOPPING); + setState(State.RESOLVED); + publishEvent(Event.STOPPED); + getRevisions().getContainer().close(); + if (t instanceof BundleException) { + throw (BundleException) t; + } + throw new BundleException("Error initializing container.", BundleException.ACTIVATOR_ERROR, t); + } + } finally { + unlockStateChange(Event.STARTED); + } + + } + + /** + * @throws BundleException + */ + public void initWorker() throws BundleException { + // Do nothing + } + + @Override + public void start(StartOptions... options) throws BundleException { + // make sure to init if needed + init(); + // Always transient + super.start(StartOptions.TRANSIENT, StartOptions.USE_ACTIVATION_POLICY); + getRevisions().getContainer().adaptor.publishContainerEvent(ContainerEvent.STARTED, this, null); + } + + @Override + public void stop(StopOptions... options) throws BundleException { + // Always transient + super.stop(StopOptions.TRANSIENT); + ContainerEvent containerEvent; + if (holdsTransitionEventLock(Event.UPDATED)) { + containerEvent = ContainerEvent.STOPPED_UPDATE; + } else if (holdsTransitionEventLock(Event.UNRESOLVED)) { + containerEvent = ContainerEvent.STOPPED_REFRESH; + } else { + containerEvent = ContainerEvent.STOPPED; + } + getRevisions().getContainer().adaptor.publishContainerEvent(containerEvent, this, null); + getRevisions().getContainer().close(); + } + + @Override + protected void startWorker() throws BundleException { + super.startWorker(); + if (getId() != 0) + return; + ((ContainerStartLevel) getRevisions().getContainer().getFrameworkStartLevel()).doContainerStartLevel(this, ContainerStartLevel.USE_BEGINNING_START_LEVEL); + } + + @Override + protected void stopWorker() throws BundleException { + super.stopWorker(); + if (getId() != 0) + return; + ((ContainerStartLevel) getRevisions().getContainer().getFrameworkStartLevel()).doContainerStartLevel(this, 0); + } + +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/builders/OSGiManifestBuilderFactory.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/builders/OSGiManifestBuilderFactory.java new file mode 100644 index 000000000..6e8355cc7 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/builders/OSGiManifestBuilderFactory.java @@ -0,0 +1,557 @@ +/******************************************************************************* + * Copyright (c) 2012 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.builders; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import org.eclipse.osgi.container.ModuleRevisionBuilder; +import org.eclipse.osgi.container.namespaces.EclipsePlatformNamespace; +import org.eclipse.osgi.container.namespaces.EquinoxModuleDataNamespace; +import org.eclipse.osgi.framework.internal.core.FilterImpl; +import org.eclipse.osgi.framework.internal.core.Tokenizer; +import org.eclipse.osgi.util.ManifestElement; +import org.eclipse.osgi.util.NLS; +import org.osgi.framework.*; +import org.osgi.framework.namespace.*; +import org.osgi.framework.wiring.BundleRevision; + +public class OSGiManifestBuilderFactory { + private static final String ATTR_TYPE_STRING = "string"; //$NON-NLS-1$ + private static final String ATTR_TYPE_VERSION = "version"; //$NON-NLS-1$ + private static final String ATTR_TYPE_URI = "uri"; //$NON-NLS-1$ + private static final String ATTR_TYPE_LONG = "long"; //$NON-NLS-1$ + private static final String ATTR_TYPE_DOUBLE = "double"; //$NON-NLS-1$ + private static final String ATTR_TYPE_SET = "set"; //$NON-NLS-1$ + private static final String ATTR_TYPE_LIST = "List"; //$NON-NLS-1$ + + public static ModuleRevisionBuilder createBuilder(Map<String, String> manifest) throws BundleException { + return createBuilder(manifest, null, null, null); + } + + public static ModuleRevisionBuilder createBuilder(Map<String, String> manifest, String symbolicNameAlias, String extraExports, String extraCapabilities) throws BundleException { + ModuleRevisionBuilder builder = new ModuleRevisionBuilder(); + + int manifestVersion = getManifestVersion(manifest); + + Object symbolicName = getSymbolicNameAndVersion(builder, manifest, symbolicNameAlias, manifestVersion); + + Collection<Map<String, Object>> exportedPackages = new ArrayList<Map<String, Object>>(); + getPackageExports(builder, ManifestElement.parseHeader(Constants.EXPORT_PACKAGE, manifest.get(Constants.EXPORT_PACKAGE)), symbolicName, exportedPackages); + if (extraExports != null) { + getPackageExports(builder, ManifestElement.parseHeader(Constants.EXPORT_PACKAGE, extraExports), symbolicName, exportedPackages); + } + getPackageImports(builder, manifest, exportedPackages, manifestVersion); + + getRequireBundle(builder, ManifestElement.parseHeader(Constants.REQUIRE_BUNDLE, manifest.get(Constants.REQUIRE_BUNDLE))); + + getProvideCapabilities(builder, ManifestElement.parseHeader(Constants.PROVIDE_CAPABILITY, manifest.get(Constants.PROVIDE_CAPABILITY))); + if (extraCapabilities != null) { + getProvideCapabilities(builder, ManifestElement.parseHeader(Constants.PROVIDE_CAPABILITY, extraCapabilities)); + } + getRequireCapabilities(builder, ManifestElement.parseHeader(Constants.REQUIRE_CAPABILITY, manifest.get(Constants.REQUIRE_CAPABILITY))); + + addRequireEclipsePlatform(builder, manifest); + + getEquinoxDataCapability(builder, manifest); + + getFragmentHost(builder, ManifestElement.parseHeader(Constants.FRAGMENT_HOST, manifest.get(Constants.FRAGMENT_HOST))); + + convertBREEs(builder, manifest); + return builder; + } + + private static int getManifestVersion(Map<String, String> manifest) { + String manifestVersionHeader = manifest.get(Constants.BUNDLE_MANIFESTVERSION); + return manifestVersionHeader == null ? 1 : Integer.parseInt(manifestVersionHeader); + } + + private static Object getSymbolicNameAndVersion(ModuleRevisionBuilder builder, Map<String, String> manifest, String symbolicNameAlias, int manifestVersion) throws BundleException { + boolean isFragment = manifest.get(Constants.FRAGMENT_HOST) != null; + builder.setTypes(isFragment ? BundleRevision.TYPE_FRAGMENT : 0); + String version = manifest.get(Constants.BUNDLE_VERSION); + try { + builder.setVersion((version != null) ? Version.parseVersion(version) : Version.emptyVersion); + } catch (IllegalArgumentException ex) { + if (manifestVersion >= 2) { + String message = NLS.bind("Invalid Manifest header \"{0}\": {1}", Constants.BUNDLE_VERSION, version); + throw new BundleException(message, BundleException.MANIFEST_ERROR, ex); + } + // prior to R4 the Bundle-Version header was not interpreted by the Framework; + // must not fail for old R3 style bundles + } + + Object symbolicName = null; + String symbolicNameHeader = manifest.get(Constants.BUNDLE_SYMBOLICNAME); + if (symbolicNameHeader != null) { + ManifestElement[] symbolicNameElements = ManifestElement.parseHeader(Constants.BUNDLE_SYMBOLICNAME, symbolicNameHeader); + if (symbolicNameElements.length > 0) { + ManifestElement bsnElement = symbolicNameElements[0]; + builder.setSymbolicName(bsnElement.getValue()); + if (symbolicNameAlias != null) { + List<String> result = new ArrayList<String>(); + result.add(builder.getSymbolicName()); + result.add(symbolicNameAlias); + symbolicName = result; + } else { + symbolicName = builder.getSymbolicName(); + } + Map<String, String> directives = getDirectives(bsnElement); + directives.remove(BundleNamespace.CAPABILITY_USES_DIRECTIVE); + directives.remove(BundleNamespace.CAPABILITY_EFFECTIVE_DIRECTIVE); + Map<String, Object> attributes = getAttributes(bsnElement); + if (!isFragment) { + // create the bundle namespace + Map<String, Object> bundleAttributes = new HashMap<String, Object>(attributes); + bundleAttributes.put(BundleNamespace.BUNDLE_NAMESPACE, symbolicName); + bundleAttributes.put(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, builder.getVersion()); + builder.addCapability(BundleNamespace.BUNDLE_NAMESPACE, directives, bundleAttributes); + + // create the host namespace + Map<String, Object> hostAttributes = new HashMap<String, Object>(attributes); + hostAttributes.put(HostNamespace.HOST_NAMESPACE, symbolicName); + hostAttributes.put(HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, builder.getVersion()); + builder.addCapability(HostNamespace.HOST_NAMESPACE, directives, hostAttributes); + } + // every bundle that has a symbolic name gets an identity; + // never use the symbolic name alias for the identity namespace + Map<String, Object> identityAttributes = new HashMap<String, Object>(attributes); + identityAttributes.put(IdentityNamespace.IDENTITY_NAMESPACE, builder.getSymbolicName()); + identityAttributes.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, builder.getVersion()); + identityAttributes.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, isFragment ? IdentityNamespace.TYPE_FRAGMENT : IdentityNamespace.TYPE_BUNDLE); + builder.addCapability(IdentityNamespace.IDENTITY_NAMESPACE, directives, identityAttributes); + } + } + + return symbolicName == null ? symbolicNameAlias : symbolicName; + } + + private static void getPackageExports(ModuleRevisionBuilder builder, ManifestElement[] exportElements, Object symbolicName, Collection<Map<String, Object>> exportedPackages) throws BundleException { + if (exportElements == null) + return; + for (ManifestElement exportElement : exportElements) { + String[] packageNames = exportElement.getValueComponents(); + Map<String, Object> attributes = getAttributes(exportElement); + Map<String, String> directives = getDirectives(exportElement); + directives.remove(PackageNamespace.CAPABILITY_EFFECTIVE_DIRECTIVE); + String versionAttr = (String) attributes.remove(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE); + @SuppressWarnings("deprecation") + String specVersionAttr = (String) attributes.remove(Constants.PACKAGE_SPECIFICATION_VERSION); + Version version = versionAttr == null ? (specVersionAttr == null ? Version.parseVersion(specVersionAttr) : Version.emptyVersion) : Version.parseVersion(versionAttr); + attributes.put(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE, version); + if (symbolicName != null) { + attributes.put(PackageNamespace.CAPABILITY_BUNDLE_SYMBOLICNAME_ATTRIBUTE, symbolicName); + } + attributes.put(PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE, builder.getVersion()); + for (String packageName : packageNames) { + Map<String, Object> packageAttrs = new HashMap<String, Object>(attributes); + packageAttrs.put(PackageNamespace.PACKAGE_NAMESPACE, packageName); + builder.addCapability(PackageNamespace.PACKAGE_NAMESPACE, directives, packageAttrs); + exportedPackages.add(packageAttrs); + } + } + } + + private static void getPackageImports(ModuleRevisionBuilder builder, Map<String, String> manifest, Collection<Map<String, Object>> exportedPackages, int manifestVersion) throws BundleException { + Collection<String> importPackageNames = new ArrayList<String>(); + ManifestElement[] importElements = ManifestElement.parseHeader(Constants.IMPORT_PACKAGE, manifest.get(Constants.IMPORT_PACKAGE)); + ManifestElement[] dynamicImportElements = ManifestElement.parseHeader(Constants.DYNAMICIMPORT_PACKAGE, manifest.get(Constants.DYNAMICIMPORT_PACKAGE)); + addPackageImports(builder, importElements, importPackageNames, false); + addPackageImports(builder, dynamicImportElements, importPackageNames, true); + if (manifestVersion < 2) + addImplicitImports(builder, exportedPackages, importPackageNames); + } + + private static void addPackageImports(ModuleRevisionBuilder builder, ManifestElement[] importElements, Collection<String> importPackageNames, boolean dynamic) { + if (importElements == null) + return; + for (ManifestElement importElement : importElements) { + String[] packageNames = importElement.getValueComponents(); + Map<String, Object> attributes = getAttributes(importElement); + Map<String, String> directives = getDirectives(importElement); + directives.remove(PackageNamespace.REQUIREMENT_EFFECTIVE_DIRECTIVE); + directives.remove(PackageNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE); + if (dynamic) { + directives.put(PackageNamespace.REQUIREMENT_RESOLUTION_DIRECTIVE, PackageNamespace.RESOLUTION_DYNAMIC); + } + String versionRangeAttr = (String) attributes.remove(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE); + @SuppressWarnings("deprecation") + String specVersionRangeAttr = (String) attributes.remove(Constants.PACKAGE_SPECIFICATION_VERSION); + VersionRange versionRange = versionRangeAttr == null ? (specVersionRangeAttr == null ? null : new VersionRange(specVersionRangeAttr)) : new VersionRange(versionRangeAttr); + String bundleVersionRangeAttr = (String) attributes.remove(PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); + VersionRange bundleVersionRange = bundleVersionRangeAttr == null ? null : new VersionRange(bundleVersionRangeAttr); + for (String packageName : packageNames) { + if (dynamic && importPackageNames.contains(packageName)) + continue; // already importing this package, don't add a dynamic import for it + importPackageNames.add(packageName); + + // fill in the filter directive based on the attributes + Map<String, String> packageDirectives = new HashMap<String, String>(directives); + StringBuilder filter = new StringBuilder(); + filter.append('(').append(PackageNamespace.PACKAGE_NAMESPACE).append('=').append(packageName).append(')'); + int size = filter.length(); + for (Map.Entry<String, Object> attribute : attributes.entrySet()) + filter.append('(').append(attribute.getKey()).append('=').append(attribute.getValue()).append(')'); + if (versionRange != null) + filter.append(versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE)); + if (bundleVersionRange != null) + filter.append(bundleVersionRange.toFilterString(PackageNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE)); + if (size != filter.length()) + // need to add (&...) + filter.insert(0, "(&").append(')'); //$NON-NLS-1$ + packageDirectives.put(PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString()); + + // fill in cardinality for dynamic wild cards + if (dynamic && packageName.indexOf('*') >= 0) + packageDirectives.put(PackageNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE, PackageNamespace.CARDINALITY_MULTIPLE); + + builder.addRequirement(PackageNamespace.PACKAGE_NAMESPACE, packageDirectives, new HashMap<String, Object>(0)); + } + } + } + + private static void addImplicitImports(ModuleRevisionBuilder builder, Collection<Map<String, Object>> exportedPackages, Collection<String> importPackageNames) { + for (Map<String, Object> exportAttributes : exportedPackages) { + String packageName = (String) exportAttributes.get(PackageNamespace.PACKAGE_NAMESPACE); + if (importPackageNames.contains(packageName)) + continue; + importPackageNames.add(packageName); + Version packageVersion = (Version) exportAttributes.get(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE); + StringBuilder filter = new StringBuilder(); + filter.append("(&(").append(PackageNamespace.PACKAGE_NAMESPACE).append('=').append(packageName).append(')'); //$NON-NLS-1$ + filter.append('(').append(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE).append(">=").append(packageVersion).append("))"); //$NON-NLS-1$//$NON-NLS-2$ + Map<String, String> directives = new HashMap<String, String>(1); + directives.put(PackageNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString()); + builder.addRequirement(PackageNamespace.PACKAGE_NAMESPACE, directives, new HashMap<String, Object>(0)); + } + } + + private static Map<String, String> getDirectives(ManifestElement element) { + Map<String, String> directives = new HashMap<String, String>(); + Enumeration<String> keys = element.getDirectiveKeys(); + if (keys == null) + return directives; + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + directives.put(key, element.getDirective(key)); + } + return directives; + } + + private static void getRequireBundle(ModuleRevisionBuilder builder, ManifestElement[] requireBundles) throws BundleException { + if (requireBundles == null) + return; + for (ManifestElement requireElement : requireBundles) { + String[] bundleNames = requireElement.getValueComponents(); + Map<String, Object> attributes = getAttributes(requireElement); + Map<String, String> directives = getDirectives(requireElement); + directives.remove(BundleNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE); + directives.remove(BundleNamespace.REQUIREMENT_EFFECTIVE_DIRECTIVE); + String versionRangeAttr = (String) attributes.remove(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); + VersionRange versionRange = versionRangeAttr == null ? null : new VersionRange(versionRangeAttr); + for (String bundleName : bundleNames) { + // fill in the filter directive based on the attributes + Map<String, String> bundleDirectives = new HashMap<String, String>(directives); + StringBuilder filter = new StringBuilder(); + filter.append('(').append(BundleNamespace.BUNDLE_NAMESPACE).append('=').append(bundleName).append(')'); + int size = filter.length(); + for (Map.Entry<String, Object> attribute : attributes.entrySet()) + filter.append('(').append(attribute.getKey()).append('=').append(attribute.getValue()).append(')'); + if (versionRange != null) + filter.append(versionRange.toFilterString(BundleNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE)); + if (size != filter.length()) + // need to add (&...) + filter.insert(0, "(&").append(')'); //$NON-NLS-1$ + bundleDirectives.put(BundleNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString()); + builder.addRequirement(BundleNamespace.BUNDLE_NAMESPACE, bundleDirectives, new HashMap<String, Object>(0)); + } + } + } + + private static void getFragmentHost(ModuleRevisionBuilder builder, ManifestElement[] fragmentHosts) throws BundleException { + if (fragmentHosts == null || fragmentHosts.length == 0) + return; + + ManifestElement fragmentHost = fragmentHosts[0]; + String hostName = fragmentHost.getValue(); + Map<String, Object> attributes = getAttributes(fragmentHost); + Map<String, String> directives = getDirectives(fragmentHost); + directives.remove(HostNamespace.REQUIREMENT_CARDINALITY_DIRECTIVE); + directives.remove(HostNamespace.REQUIREMENT_EFFECTIVE_DIRECTIVE); + + String versionRangeAttr = (String) attributes.remove(HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE); + VersionRange versionRange = versionRangeAttr == null ? null : new VersionRange(versionRangeAttr); + + // fill in the filter directive based on the attributes + StringBuilder filter = new StringBuilder(); + filter.append('(').append(HostNamespace.HOST_NAMESPACE).append('=').append(hostName).append(')'); + int size = filter.length(); + for (Map.Entry<String, Object> attribute : attributes.entrySet()) + filter.append('(').append(attribute.getKey()).append('=').append(attribute.getValue()).append(')'); + if (versionRange != null) + filter.append(versionRange.toFilterString(HostNamespace.CAPABILITY_BUNDLE_VERSION_ATTRIBUTE)); + if (size != filter.length()) + // need to add (&...) + filter.insert(0, "(&").append(')'); //$NON-NLS-1$ + directives.put(BundleNamespace.REQUIREMENT_FILTER_DIRECTIVE, filter.toString()); + builder.addRequirement(HostNamespace.HOST_NAMESPACE, directives, new HashMap<String, Object>(0)); + } + + private static void getProvideCapabilities(ModuleRevisionBuilder builder, ManifestElement[] provideElements) throws BundleException { + if (provideElements == null) + return; + for (ManifestElement provideElement : provideElements) { + String[] namespaces = provideElement.getValueComponents(); + Map<String, Object> attributes = getAttributes(provideElement); + Map<String, String> directives = getDirectives(provideElement); + for (String namespace : namespaces) { + builder.addCapability(namespace, directives, attributes); + } + } + } + + private static void getRequireCapabilities(ModuleRevisionBuilder builder, ManifestElement[] requireElements) throws BundleException { + if (requireElements == null) + return; + for (ManifestElement requireElement : requireElements) { + String[] namespaces = requireElement.getValueComponents(); + Map<String, Object> attributes = getAttributes(requireElement); + Map<String, String> directives = getDirectives(requireElement); + for (String namespace : namespaces) { + if (IdentityNamespace.IDENTITY_NAMESPACE.equals(namespace)) + throw new BundleException("A bundle is not allowed to define a capability in the " + IdentityNamespace.IDENTITY_NAMESPACE + " name space."); //$NON-NLS-1$ //$NON-NLS-2$ + builder.addRequirement(namespace, directives, attributes); + } + } + } + + private static void addRequireEclipsePlatform(ModuleRevisionBuilder builder, Map<String, String> manifest) { + String platformFilter = manifest.get(EclipsePlatformNamespace.ECLIPSE_PLATFORM_FILTER_HEADER); + if (platformFilter == null) { + return; + } + // only support one + HashMap<String, String> directives = new HashMap<String, String>(); + directives.put(EclipsePlatformNamespace.REQUIREMENT_FILTER_DIRECTIVE, platformFilter); + builder.addRequirement(EclipsePlatformNamespace.ECLIPSE_PLATFORM_NAMESPACE, directives, Collections.<String, Object> emptyMap()); + } + + private static void getEquinoxDataCapability(ModuleRevisionBuilder builder, Map<String, String> manifest) throws BundleException { + Map<String, Object> attributes = new HashMap<String, Object>(); + + // Get the activation policy attributes + ManifestElement[] policyElements = ManifestElement.parseHeader(Constants.BUNDLE_ACTIVATIONPOLICY, manifest.get(Constants.BUNDLE_ACTIVATIONPOLICY)); + if (policyElements != null) { + ManifestElement policy = policyElements[0]; + String policyName = policy.getValue(); + if (EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY_LAZY.equals(policyName)) { + attributes.put(EquinoxModuleDataNamespace.CAPABILITY_ACTIVATION_POLICY, policyName); + String includeSpec = policy.getDirective(Constants.INCLUDE_DIRECTIVE); + if (includeSpec != null) { + attributes.put(EquinoxModuleDataNamespace.CAPABILITY_LAZY_INCLUDE_ATTRIBUTE, convertValue("List<String>", includeSpec)); + } + String excludeSpec = policy.getDirective(Constants.EXCLUDE_DIRECTIVE); + if (excludeSpec != null) { + attributes.put(EquinoxModuleDataNamespace.CAPABILITY_LAZY_EXCLUDE_ATTRIBUTE, convertValue("List<String>", excludeSpec)); + } + } + } + + // Get the activator + String activator = manifest.get(Constants.BUNDLE_ACTIVATOR); + if (activator != null) { + attributes.put(EquinoxModuleDataNamespace.CAPABILITY_ACTIVATOR, activator); + } + + // Get the class path + ManifestElement[] classpathElements = ManifestElement.parseHeader(Constants.BUNDLE_CLASSPATH, manifest.get(Constants.BUNDLE_CLASSPATH)); + if (classpathElements != null) { + List<String> classpath = new ArrayList<String>(); + for (ManifestElement element : classpathElements) { + String[] components = element.getValueComponents(); + for (String component : components) { + classpath.add(component); + } + } + attributes.put(EquinoxModuleDataNamespace.CAPABILITY_CLASSPATH, classpath); + } + + // only create the capability if the attributes is not empty + if (!attributes.isEmpty()) { + builder.addCapability(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE, Collections.<String, String> emptyMap(), attributes); + } + } + + private static Map<String, Object> getAttributes(ManifestElement element) { + Enumeration<String> keys = element.getKeys(); + Map<String, Object> attributes = new HashMap<String, Object>(); + if (keys == null) + return attributes; + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + String value = element.getAttribute(key); + int colonIndex = key.indexOf(':'); + String type = ATTR_TYPE_STRING; + if (colonIndex > 0) { + type = key.substring(colonIndex + 1).trim(); + key = key.substring(0, colonIndex).trim(); + } + attributes.put(key, convertValue(type, value)); + } + return attributes; + } + + private static Object convertValue(String type, String value) { + + if (ATTR_TYPE_STRING.equalsIgnoreCase(type)) + return value; + + String trimmed = value.trim(); + if (ATTR_TYPE_DOUBLE.equalsIgnoreCase(type)) + return new Double(trimmed); + else if (ATTR_TYPE_LONG.equalsIgnoreCase(type)) + return new Long(trimmed); + else if (ATTR_TYPE_URI.equalsIgnoreCase(type)) + try { + return new URI(trimmed); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + else if (ATTR_TYPE_VERSION.equalsIgnoreCase(type)) + return new Version(trimmed); + else if (ATTR_TYPE_SET.equalsIgnoreCase(type)) + return ManifestElement.getArrayFromList(trimmed, ","); //$NON-NLS-1$ + + // assume list type, anything else will throw an exception + Tokenizer listTokenizer = new Tokenizer(type); + String listType = listTokenizer.getToken("<"); //$NON-NLS-1$ + if (!ATTR_TYPE_LIST.equalsIgnoreCase(listType)) + throw new RuntimeException("Unsupported type: " + type); //$NON-NLS-1$ + char c = listTokenizer.getChar(); + String componentType = ATTR_TYPE_STRING; + if (c == '<') { + componentType = listTokenizer.getToken(">"); //$NON-NLS-1$ + if (listTokenizer.getChar() != '>') + throw new RuntimeException("Invalid type, missing ending '>' : " + type); //$NON-NLS-1$ + } + List<String> tokens = new Tokenizer(value).getEscapedTokens(","); //$NON-NLS-1$ + List<Object> components = new ArrayList<Object>(); + for (String component : tokens) { + components.add(convertValue(componentType, component)); + } + return components; + } + + private static void convertBREEs(ModuleRevisionBuilder builder, Map<String, String> manifest) throws BundleException { + @SuppressWarnings("deprecation") + String[] brees = ManifestElement.getArrayFromList(manifest.get(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT)); + if (brees == null || brees.length == 0) + return; + List<String> breeFilters = new ArrayList<String>(); + for (String bree : brees) + breeFilters.add(createOSGiEERequirementFilter(bree)); + String filterSpec; + if (breeFilters.size() == 1) { + filterSpec = breeFilters.get(0); + } else { + StringBuffer filterBuf = new StringBuffer("(|"); //$NON-NLS-1$ + for (String breeFilter : breeFilters) { + filterBuf.append(breeFilter); + } + filterSpec = filterBuf.append(")").toString(); //$NON-NLS-1$ + } + + Map<String, String> directives = new HashMap<String, String>(1); + directives.put(ExecutionEnvironmentNamespace.REQUIREMENT_FILTER_DIRECTIVE, filterSpec); + builder.addRequirement(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE, directives, new HashMap<String, Object>(0)); + } + + private static String createOSGiEERequirementFilter(String bree) throws BundleException { + String[] nameVersion = getOSGiEENameVersion(bree); + String eeName = nameVersion[0]; + String v = nameVersion[1]; + String filterSpec; + if (v == null) + filterSpec = "(osgi.ee=" + eeName + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + else + filterSpec = "(&(osgi.ee=" + eeName + ")(version=" + v + "))"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + try { + // do a sanity check + FilterImpl.newInstance(filterSpec); + } catch (InvalidSyntaxException e) { + filterSpec = "(osgi.ee=" + bree + ")"; //$NON-NLS-1$ //$NON-NLS-2$ + try { + // do another sanity check + FilterImpl.newInstance(filterSpec); + } catch (InvalidSyntaxException e1) { + throw new BundleException("Error converting required execution environment.", e1); //$NON-NLS-1$ + } + } + return filterSpec; + } + + private static String[] getOSGiEENameVersion(String bree) { + String ee1 = null; + String ee2 = null; + String v1 = null; + String v2 = null; + int separator = bree.indexOf('/'); + if (separator <= 0 || separator == bree.length() - 1) { + ee1 = bree; + } else { + ee1 = bree.substring(0, separator); + ee2 = bree.substring(separator + 1); + } + int v1idx = ee1.indexOf('-'); + if (v1idx > 0 && v1idx < ee1.length() - 1) { + // check for > 0 to avoid EEs starting with - + // check for < len - 1 to avoid ending with - + try { + v1 = ee1.substring(v1idx + 1); + // sanity check version format + Version.parseVersion(v1); + ee1 = ee1.substring(0, v1idx); + } catch (IllegalArgumentException e) { + v1 = null; + } + } + + int v2idx = ee2 == null ? -1 : ee2.indexOf('-'); + if (v2idx > 0 && v2idx < ee2.length() - 1) { + // check for > 0 to avoid EEs starting with - + // check for < len - 1 to avoid ending with - + try { + v2 = ee2.substring(v2idx + 1); + Version.parseVersion(v2); + ee2 = ee2.substring(0, v2idx); + } catch (IllegalArgumentException e) { + v2 = null; + } + } + + if (v1 == null) + v1 = v2; + if (v1 != null && v2 != null && !v1.equals(v2)) { + ee1 = bree; + ee2 = null; + v1 = null; + v2 = null; + } + if ("J2SE".equals(ee1)) //$NON-NLS-1$ + ee1 = "JavaSE"; //$NON-NLS-1$ + if ("J2SE".equals(ee2)) //$NON-NLS-1$ + ee2 = "JavaSE"; //$NON-NLS-1$ + + String eeName = ee1 + (ee2 == null ? "" : '/' + ee2); //$NON-NLS-1$ + + return new String[] {eeName, v1}; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/namespaces/EclipsePlatformNamespace.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/namespaces/EclipsePlatformNamespace.java new file mode 100644 index 000000000..b89822495 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/namespaces/EclipsePlatformNamespace.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2012 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.namespaces; + +import org.osgi.resource.Namespace; + +/** + * Eclipse Platform and Requirement Namespace. + * + * <p> + * This class defines the names for the attributes and directives for this + * namespace. All unspecified capability attributes are of type {@code String} + * and are used as arbitrary matching attributes for the capability. The values + * associated with the specified directive and attribute keys are of type + * {@code String}, unless otherwise indicated. + * + * @Immutable + */ +public class EclipsePlatformNamespace extends Namespace { + + /** + * Namespace name for the eclipse platform. Unlike typical name spaces + * this namespace is not intended to be used as an attribute. + */ + public static final String ECLIPSE_PLATFORM_NAMESPACE = "eclipse.platform"; + + /** + * Manifest header identifying the eclipse platform for the + * bundle. The framework may run this bundle if filter + * specified by this header matches the running eclipse platform. + */ + public static final String ECLIPSE_PLATFORM_FILTER_HEADER = "Eclipse-PlatformFilter"; +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/namespaces/EquinoxModuleDataNamespace.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/namespaces/EquinoxModuleDataNamespace.java new file mode 100644 index 000000000..3b2ab29c5 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/container/namespaces/EquinoxModuleDataNamespace.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2012 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.namespaces; + +import org.osgi.framework.Constants; +import org.osgi.resource.Namespace; + +/** + * Equinox module data capability namespace. This namespace is used + * to store immutable data about a module revision. This includes the following + * <ul> + * <li>The activation policy as specified by the {@link Constants#BUNDLE_ACTIVATIONPOLICY} + * Bundle-ActivationPolicy} header.</li> + * <li>The activator as specified by the {@link Constants#BUNDLE_ACTIVATOR Bundle-Activator} + * header.</li> + * <li>The class path as specified by the {@link Constants#BUNDLE_CLASSPATH Bundle-ClassPath} + * header.</li> + * </ul> + * Activation + * policy capability is provided for informational purposes and + * should not be considered as effective by the resolver. + * <p> + * For bundles, the attributes of this capability are extracted + * from the Bundle-ActivationPolicy header. The directives + * {@link Constants#EXCLUDE_DIRECTIVE} and {@link Constants#INCLUDE_DIRECTIVE} + * are converted to attributes of type {@code List<String>}. + * + * <p> + * This class defines the names for the attributes and directives for this + * namespace. Capabilities in this namespace are not intended to be used to + * match requirements and should not be considered as effective by a resolver. + * + * @Immutable + */ +public class EquinoxModuleDataNamespace extends Namespace { + + /** + * Namespace name for equinox module data. Unlike typical name spaces + * this namespace is not intended to be used as an attribute. + */ + public static final String MODULE_DATA_NAMESPACE = "equinox.module.data"; + + /** + * The directive value identifying a {@link #CAPABILITY_EFFECTIVE_DIRECTIVE + * capability} that is effective for information purposes. Capabilities + * in this namespace must have an effective directive value of information. + * + * @see #CAPABILITY_EFFECTIVE_DIRECTIVE + */ + public final static String EFFECTIVE_INFORMATION = "information"; + + /** + * The capability attribute contains the {@link Constants#BUNDLE_ACTIVATIONPOLICY + * activation policy} for the providing module revision. The value of this + * attribute must be of type {@code String}. When not specified then + * the module revision uses an eager activation policy. + */ + public final static String CAPABILITY_ACTIVATION_POLICY = "activation.policy"; + + /** + * An activation policy name indicating the lazy activation policy + * is used. + */ + public final static String CAPABILITY_ACTIVATION_POLICY_LAZY = "lazy"; + + /** + * When the {@link #CAPABILITY_ACTIVATION_POLICY_LAZY lazy} policy is used this + * attribute contains the package names that must + * trigger the activation when a class is loaded of these packages. + * If the attribute is not defined then the default is all package names. + * The value of this attribute must be of type {@code List<String>}. + */ + public final static String CAPABILITY_LAZY_INCLUDE_ATTRIBUTE = "lazy.include"; + + /** + * When the {@link #CAPABILITY_ACTIVATION_POLICY_LAZY lazy} policy is used this + * attribute contains the package names that must not + * trigger the activation when a class is loaded of these packages. + * If the attribute is not defined then the default is no package names. + * The value of this attribute must be of type {@code List<String>}. + */ + public final static String CAPABILITY_LAZY_EXCLUDE_ATTRIBUTE = "lazy.include"; + + /** + * The capability attribute contains the {@link Constants#BUNDLE_ACTIVATOR activator} + * for the providing module revision. The value of this attribute must be of type + * {@code String}. When not specified then the module revision has no activator. + */ + public final static String CAPABILITY_ACTIVATOR = "activator"; + + /** + * The capability attribute contains the {@link Constants#BUNDLE_CLASSPATH class path} + * for the providing module revision. The value of this attribute must be of type + * {@code List<String>}. When not specified the module revision uses the default + * class path of '.'. + */ + public final static String CAPABILITY_CLASSPATH = "classpath"; +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/Capabilities.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/Capabilities.java new file mode 100644 index 000000000..0fb7cf970 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/Capabilities.java @@ -0,0 +1,236 @@ +/******************************************************************************* + * Copyright (c) 2012 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.internal.container; + +import java.util.*; +import org.eclipse.osgi.container.*; +import org.eclipse.osgi.framework.internal.core.FilterImpl; +import org.eclipse.osgi.util.ManifestElement; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.namespace.*; +import org.osgi.resource.Capability; +import org.osgi.resource.Namespace; + +public class Capabilities { + + static class NamespaceSet { + private final String name; + private final Map<String, Set<ModuleCapability>> indexes = new HashMap<String, Set<ModuleCapability>>(); + private final Set<ModuleCapability> all = new HashSet<ModuleCapability>(); + private final Set<ModuleCapability> nonStringIndexes = new HashSet<ModuleCapability>(0); + private final boolean matchMandatory; + + NamespaceSet(String name) { + this.name = name; + this.matchMandatory = PackageNamespace.PACKAGE_NAMESPACE.equals(name) || BundleNamespace.BUNDLE_NAMESPACE.equals(name) || HostNamespace.HOST_NAMESPACE.equals(name); + } + + void addCapability(ModuleCapability capability) { + if (!name.equals(capability.getNamespace())) { + throw new IllegalArgumentException("Invalid namespace: " + capability.getNamespace() + ": expecting: " + name); + } + all.add(capability); + // by convention we index by the namespace attribute + Object index = capability.getAttributes().get(name); + if (index == null) { + return; + } + Collection<?> indexCollection = null; + if (index instanceof Collection) { + indexCollection = (Collection<?>) index; + } else if (index.getClass().isArray()) { + indexCollection = Arrays.asList((Object[]) index); + } + if (indexCollection == null) { + addIndex(index, capability); + } else { + for (Object indexKey : indexCollection) { + addIndex(indexKey, capability); + } + } + } + + private void addIndex(Object indexKey, ModuleCapability capability) { + if (!(indexKey instanceof String)) { + nonStringIndexes.add(capability); + } else { + Set<ModuleCapability> capabilities = indexes.get(indexKey); + if (capabilities == null) { + capabilities = new HashSet<ModuleCapability>(1); + indexes.put((String) indexKey, capabilities); + } + capabilities.add(capability); + } + } + + void removeCapability(ModuleCapability capability) { + if (!name.equals(capability.getNamespace())) { + throw new IllegalArgumentException("Invalid namespace: " + capability.getNamespace() + ": expecting: " + name); + } + all.remove(capability); + // by convention we index by the namespace attribute + Object index = capability.getAttributes().get(name); + if (index == null) { + return; + } + Collection<?> indexCollection = null; + if (index instanceof Collection) { + indexCollection = (Collection<?>) index; + } else if (index.getClass().isArray()) { + indexCollection = Arrays.asList((Object[]) index); + } + if (indexCollection == null) { + removeIndex(index, capability); + } else { + for (Object indexKey : indexCollection) { + removeIndex(indexKey, capability); + } + } + } + + private void removeIndex(Object indexKey, ModuleCapability capability) { + if (!(indexKey instanceof String)) { + nonStringIndexes.remove(capability); + } else { + Set<ModuleCapability> capabilities = indexes.get(indexKey); + if (capabilities != null) { + capabilities.remove(capability); + } + } + } + + List<ModuleCapability> findCapabilities(ModuleRequirement requirement) { + if (!name.equals(requirement.getNamespace())) { + throw new IllegalArgumentException("Invalid namespace: " + requirement.getNamespace() + ": expecting: " + name); + } + FilterImpl f = null; + String filterSpec = requirement.getDirectives().get(Namespace.REQUIREMENT_FILTER_DIRECTIVE); + if (filterSpec != null) { + try { + f = FilterImpl.newInstance(filterSpec); + } catch (InvalidSyntaxException e) { + return Collections.emptyList(); + } + } + + if (filterSpec == null) { + return match(null, all); + } + + String indexKey = f.getPrimaryKeyValue(name); + if (indexKey == null) { + return match(f, all); + } + + List<ModuleCapability> result; + Set<ModuleCapability> indexed = indexes.get(indexKey); + if (indexed == null) { + result = new ArrayList<ModuleCapability>(0); + } else { + result = match(f, indexed); + } + + if (!nonStringIndexes.isEmpty()) { + List<ModuleCapability> nonStringResult = match(f, nonStringIndexes); + for (ModuleCapability capability : nonStringResult) { + if (!result.contains(capability)) { + result.add(capability); + } + } + } + + return result; + } + + private List<ModuleCapability> match(FilterImpl f, Set<ModuleCapability> candidates) { + List<ModuleCapability> result = new ArrayList<ModuleCapability>(1); + for (ModuleCapability candidate : candidates) { + if (matches(f, candidate, matchMandatory)) { + result.add(candidate); + } + } + return result; + } + } + + public static boolean matches(FilterImpl f, Capability candidate, boolean matchMandatory) { + if (f != null && !f.matches(candidate.getAttributes())) { + return false; + } + if (matchMandatory) { + // check for mandatory directive + String mandatory = candidate.getDirectives().get(AbstractWiringNamespace.CAPABILITY_MANDATORY_DIRECTIVE); + if (mandatory == null) { + return true; + } + if (f == null) { + return false; + } + String[] mandatoryAttrs = ManifestElement.getArrayFromList(mandatory, ","); //$NON-NLS-1$ + boolean allPresent = true; + for (String attribute : mandatoryAttrs) { + allPresent &= f.getPrimaryKeyValue(attribute) != null; + } + return allPresent; + } + return true; + } + + Map<String, NamespaceSet> namespaceSets = new HashMap<String, NamespaceSet>(); + + /** + * Adds the {@link ModuleRevision#getModuleCapabilities(String) capabilities} + * provided by the specified revision to this database. These capabilities must + * become available for lookup with the {@link #findCapabilities(ModuleRequirement)} + * method. + * @param revision the revision which has capabilities to add + */ + public void addCapabilities(ModuleRevision revision) { + for (ModuleCapability capability : revision.getModuleCapabilities(null)) { + NamespaceSet namespaceSet = namespaceSets.get(capability.getNamespace()); + if (namespaceSet == null) { + namespaceSet = new NamespaceSet(capability.getNamespace()); + namespaceSets.put(capability.getNamespace(), namespaceSet); + } + namespaceSet.addCapability(capability); + } + } + + /** + * Removes the {@link ModuleRevision#getModuleCapabilities(String) capabilities} + * provided by the specified revision from this database. These capabilities + * must no longer be available for lookup with the + * {@link #findCapabilities(ModuleRequirement)} method. + * @param revision + */ + public void removeCapabilities(ModuleRevision revision) { + for (ModuleCapability capability : revision.getModuleCapabilities(null)) { + NamespaceSet namespaceSet = namespaceSets.get(capability.getNamespace()); + if (namespaceSet != null) { + namespaceSet.removeCapability(capability); + } + } + } + + /** + * Returns a mutable snapshot of capabilities that are candidates for + * satisfying the specified requirement. + * @param requirement the requirement + * @return the candidates for the requirement + */ + public List<ModuleCapability> findCapabilities(ModuleRequirement requirement) { + NamespaceSet namespaceSet = namespaceSets.get(requirement.getNamespace()); + if (namespaceSet == null) { + return Collections.emptyList(); + } + return namespaceSet.findCapabilities(requirement); + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/Converters.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/Converters.java new file mode 100644 index 000000000..5c2c0fc64 --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/Converters.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright (c) 2012 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.internal.container; + +import java.util.Collection; +import java.util.List; +import org.osgi.framework.wiring.*; +import org.osgi.resource.*; + +public class Converters { + + /** + * Coerce the generic type of a list from List<BundleCapability> + * to List<Capability> + * @param l List to be coerced. + * @return l coerced to List<Capability> + */ + @SuppressWarnings("unchecked") + public static List<Capability> asListCapability(List<? extends Capability> l) { + return (List<Capability>) l; + } + + /** + * Coerce the generic type of a list from List<BundleRequirement> + * to List<Requirement> + * @param l List to be coerced. + * @return l coerced to List<Requirement> + */ + @SuppressWarnings("unchecked") + public static List<Requirement> asListRequirement(List<? extends Requirement> l) { + return (List<Requirement>) l; + } + + /** + * Coerce the generic type of a list from List<? extends BundleCapability> + * to List<BundleCapability> + * @param l List to be coerced. + * @return l coerced to List<BundleCapability> + */ + @SuppressWarnings("unchecked") + public static List<BundleCapability> asListBundleCapability(List<? extends BundleCapability> l) { + return (List<BundleCapability>) l; + } + + /** + * Coerce the generic type of a list from List<? extends BundleRequirement> + * to List<BundleRequirement> + * @param l List to be coerced. + * @return l coerced to List<BundleRequirement> + */ + @SuppressWarnings("unchecked") + public static List<BundleRequirement> asListBundleRequirement(List<? extends BundleRequirement> l) { + return (List<BundleRequirement>) l; + } + + /** + * Coerce the generic type of a list from List<? extends BundleWire> + * to List<BundleWire> + * @param l List to be coerced. + * @return l coerced to List<BundleWire> + */ + @SuppressWarnings("unchecked") + public static List<BundleWire> asListBundleWire(List<? extends BundleWire> l) { + return (List<BundleWire>) l; + } + + /** + * Coerce the generic type of a list from List<? extends BundleWire> + * to List<BundleWire> + * @param l List to be coerced. + * @return l coerced to List<BundleWire> + */ + @SuppressWarnings("unchecked") + public static List<Wire> asListWire(List<? extends Wire> l) { + return (List<Wire>) l; + } + + /** + * Coerce the generic type of a list from List<? extends BundleRevision> + * to List<BundleRevision> + * @param l List to be coerced. + * @return l coerced to List<BundleRevision> + */ + @SuppressWarnings("unchecked") + public static List<BundleRevision> asListBundleRevision(List<? extends BundleRevision> l) { + return (List<BundleRevision>) l; + } + + /** + * Coerce the generic type of a collection from Collection<? extends Resource> + * to Collection<Resource> + * @param c List to be coerced. + * @return c coerced to Collection<Resource> + */ + @SuppressWarnings("unchecked") + public static Collection<Resource> asCollectionResource(Collection<? extends Resource> c) { + return (Collection<Resource>) c; + } +} diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/LockSet.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/LockSet.java new file mode 100644 index 000000000..764c6b69a --- /dev/null +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/container/LockSet.java @@ -0,0 +1,86 @@ +/******************************************************************************* + * Copyright (c) 2012 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.internal.container; + +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +public class LockSet<T> { + private final Map<T, ReentrantLock> locks = new WeakHashMap<T, ReentrantLock>(); + private final Object monitor = new Object(); + private final boolean reentrant; + + public LockSet(boolean reentrant) { + this.reentrant = reentrant; + } + + public boolean lock(T t) { + ReentrantLock lock = getLock(t); + lock.lock(); + if (reentrant) + return true; + if (lock.getHoldCount() > 1) { + lock.unlock(); + return false; + } + return true; + } + + public boolean tryLock(T t) { + ReentrantLock lock = getLock(t); + boolean obtained = lock.tryLock(); + if (obtained) { + if (reentrant) + return true; + if (lock.getHoldCount() > 1) { + lock.unlock(); + return false; + } + } + return obtained; + } + + public boolean tryLock(T t, long time, TimeUnit unit) throws InterruptedException { + ReentrantLock lock = getLock(t); + boolean obtained = lock.tryLock(time, unit); + if (obtained) { + if (reentrant) + return true; + if (lock.getHoldCount() > 1) { + lock.unlock(); + return false; + } + } + return obtained; + } + + public void unlock(T t) { + synchronized (monitor) { + ReentrantLock lock = locks.get(t); + if (lock == null) + throw new IllegalStateException("No lock found."); //$NON-NLS-1$ + lock.unlock(); + } + } + + private ReentrantLock getLock(T t) { + synchronized (monitor) { + ReentrantLock lock = locks.get(t); + if (lock == null) { + lock = new ReentrantLock(); + locks.put(t, lock); + } + return lock; + } + } +} |