/******************************************************************************* * Copyright (c) 2011, 2015 VMware Inc. 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: * SpringSource, a division of VMware - initial API and implementation and/or initial documentation *******************************************************************************/ package org.eclipse.equinox.internal.region; import java.util.*; import java.util.concurrent.atomic.AtomicLong; import org.eclipse.equinox.internal.region.hook.*; import org.eclipse.equinox.region.*; import org.osgi.framework.*; import org.osgi.framework.hooks.bundle.*; import org.osgi.framework.hooks.resolver.ResolverHookFactory; /** * {@link StandardRegionDigraph} is the default implementation of {@link RegionDigraph}. *

* * Concurrent Semantics
* * Thread safe. * */ public final class StandardRegionDigraph implements BundleIdToRegionMapping, RegionDigraph { private static final Set EMPTY_EDGE_SET = Collections.unmodifiableSet(new HashSet()); // This monitor guards the modifications and read operations on the digraph as well as // bundle id modifications of all regions in this digraph private final Object monitor = new Object(); private final Map regions = new HashMap(); // Alien calls may be made to the following object while this.monitor is locked // as this.monitor is higher in the lock hierarchy than this object's own monitor. private final BundleIdToRegionMapping bundleIdToRegionMapping; /* edges maps a given region to an immutable set of edges with their tail at the given region. To update * the edges for a region, the corresponding immutable set is replaced atomically. */ private final Map> edges = new HashMap>(); private final BundleContext bundleContext; private final ThreadLocal threadLocal; private final SubgraphTraverser subgraphTraverser; private final org.osgi.framework.hooks.bundle.CollisionHook bundleCollisionHook; private final org.osgi.framework.hooks.bundle.EventHook bundleEventHook; private final org.osgi.framework.hooks.bundle.FindHook bundleFindHook; @SuppressWarnings("deprecation") private final org.osgi.framework.hooks.service.EventHook serviceEventHook; private final org.osgi.framework.hooks.service.FindHook serviceFindHook; private final ResolverHookFactory resolverHookFactory; private final StandardRegionDigraph origin; // Guarded by the origin monitor private long originUpdateCount; private final AtomicLong updateCount = new AtomicLong(); private volatile Region defaultRegion; public StandardRegionDigraph(StandardRegionDigraph origin) throws BundleException { this(null, null, origin); } public StandardRegionDigraph(BundleContext bundleContext, ThreadLocal threadLocal) throws BundleException { this(bundleContext, threadLocal, null); } private StandardRegionDigraph(BundleContext bundleContext, ThreadLocal threadLocal, StandardRegionDigraph origin) throws BundleException { this.subgraphTraverser = new SubgraphTraverser(); this.bundleIdToRegionMapping = new StandardBundleIdToRegionMapping(); this.bundleContext = bundleContext; this.threadLocal = threadLocal; // Note we are safely escaping this only because we know the hook impls // do not escape the digraph to other threads on construction. this.resolverHookFactory = new RegionResolverHookFactory(this); this.bundleFindHook = new RegionBundleFindHook(this, bundleContext == null ? 0 : bundleContext.getBundle().getBundleId()); this.bundleEventHook = new RegionBundleEventHook(this, this.threadLocal, bundleContext == null ? 0 : bundleContext.getBundle().getBundleId()); this.bundleCollisionHook = new RegionBundleCollisionHook(this, this.threadLocal); this.serviceFindHook = new RegionServiceFindHook(this); this.serviceEventHook = new RegionServiceEventHook(this); this.origin = origin; if (origin != null) { synchronized (origin.monitor) { this.originUpdateCount = origin.updateCount.get(); this.replace(origin, false); } } else { this.originUpdateCount = -1; } this.defaultRegion = null; } /** * {@inheritDoc} */ public Region createRegion(String regionName) throws BundleException { return createRegion(regionName, true); } private Region createRegion(String regionName, boolean notify) throws BundleException { Region region = new BundleIdBasedRegion(regionName, this, this, this.bundleContext, this.threadLocal); synchronized (this.monitor) { if (getRegion(regionName) != null) { throw new BundleException("Region '" + regionName + "' already exists", BundleException.UNSUPPORTED_OPERATION); //$NON-NLS-1$ //$NON-NLS-2$ } this.regions.put(region.getName(), region); this.edges.put(region, EMPTY_EDGE_SET); incrementUpdateCount(); } if (notify) { notifyAdded(region); } return region; } /** * {@inheritDoc} */ public void connect(Region tailRegion, RegionFilter filter, Region headRegion) throws BundleException { createConnection(tailRegion, filter, headRegion, false); } /** * {@inheritDoc} */ public RegionFilter replaceConnection(Region tailRegion, RegionFilter filter, Region headRegion) throws BundleException { return createConnection(tailRegion, filter, headRegion, true); } private RegionFilter createConnection(Region tailRegion, RegionFilter filter, Region headRegion, boolean replace) throws BundleException { if (tailRegion == null) throw new IllegalArgumentException("The tailRegion must not be null."); //$NON-NLS-1$ if (!replace && filter == null) throw new IllegalArgumentException("The filter must not be null."); //$NON-NLS-1$ if (headRegion == null) throw new IllegalArgumentException("The headRegion must not be null."); //$NON-NLS-1$ if (headRegion.equals(tailRegion)) { throw new BundleException("Cannot connect region '" + headRegion + "' to itself", BundleException.UNSUPPORTED_OPERATION); //$NON-NLS-1$ //$NON-NLS-2$ } if (tailRegion.getRegionDigraph() != this) throw new IllegalArgumentException("The tailRegion does not belong to this digraph."); //$NON-NLS-1$ if (headRegion.getRegionDigraph() != this) throw new IllegalArgumentException("The headRegion does not belong to this digraph."); //$NON-NLS-1$ FilteredRegion existing = null; boolean tailAdded = false; boolean headAdded = false; synchronized (this.monitor) { Set connections = this.edges.get(tailRegion); if (connections == null) { connections = new HashSet(); } else { connections = new HashSet(connections); for (FilteredRegion edge : connections) { if (headRegion.equals(edge.getRegion())) { if (replace) { existing = edge; } else { throw new BundleException("Region '" + tailRegion + "' is already connected to region '" + headRegion, BundleException.UNSUPPORTED_OPERATION); //$NON-NLS-1$ //$NON-NLS-2$ } } } } checkFilterDoesNotAllowExistingBundle(tailRegion, filter); tailAdded = this.regions.put(tailRegion.getName(), tailRegion) == null; headAdded = this.regions.put(headRegion.getName(), headRegion) == null; if (existing != null) { connections.remove(existing); } if (filter != null) { connections.add(new StandardFilteredRegion(headRegion, filter)); } this.edges.put(tailRegion, Collections.unmodifiableSet(connections)); incrementUpdateCount(); } if (tailAdded) { notifyAdded(tailRegion); } if (headAdded) { notifyAdded(headRegion); } return existing == null ? null : existing.getFilter(); } private void checkFilterDoesNotAllowExistingBundle(Region tailRegion, RegionFilter filter) { // TODO: enumerate the bundles in the region and check the filter does not allow any of them } /** * {@inheritDoc} */ public Iterator iterator() { synchronized (this.monitor) { Set snapshot = new HashSet(this.regions.size()); snapshot.addAll(this.regions.values()); return snapshot.iterator(); } } /** * {@inheritDoc} */ public Set getEdges(Region tailRegion) { synchronized (this.monitor) { // Cope with the case where tailRegion is not in the digraph Set edgeSet = this.edges.get(tailRegion); return edgeSet == null ? EMPTY_EDGE_SET : edgeSet; } } static class StandardFilteredRegion implements FilteredRegion { private Region region; private RegionFilter regionFilter; StandardFilteredRegion(Region region, RegionFilter regionFilter) { this.region = region; this.regionFilter = regionFilter; } public Region getRegion() { return this.region; } public RegionFilter getFilter() { return this.regionFilter; } } /** * {@inheritDoc} */ public Region getRegion(String regionName) { synchronized (this.monitor) { return regions.get(regionName); } } /** * {@inheritDoc} */ public Region getRegion(Bundle bundle) { return getRegion(bundle.getBundleId()); } /** * {@inheritDoc} */ public Region getRegion(long bundleId) { synchronized (this.monitor) { return this.bundleIdToRegionMapping.getRegion(bundleId); } } /** * {@inheritDoc} */ public void removeRegion(Region region) { if (region == null) throw new IllegalArgumentException("The region cannot be null."); //$NON-NLS-1$ notifyRemoving(region); synchronized (this.monitor) { if (this.defaultRegion != null && this.defaultRegion.equals(region)) { this.defaultRegion = null; } this.regions.remove(region.getName()); this.edges.remove(region); for (Region r : this.edges.keySet()) { Set edgeSet = this.edges.get(r); for (FilteredRegion edge : edgeSet) { if (region.equals(edge.getRegion())) { Set mutableEdgeSet = new HashSet(edgeSet); mutableEdgeSet.remove(edge); this.edges.put(r, Collections.unmodifiableSet(mutableEdgeSet)); break; } } } this.bundleIdToRegionMapping.dissociateRegion(region); incrementUpdateCount(); } } /** * {@inheritDoc} */ public String toString() { synchronized (this.monitor) { StringBuffer s = new StringBuffer(); boolean first = true; s.append("RegionDigraph{"); //$NON-NLS-1$ for (Region r : this) { if (!first) { s.append(", "); //$NON-NLS-1$ } s.append(r); first = false; } s.append("}"); //$NON-NLS-1$ s.append("["); //$NON-NLS-1$ first = true; for (Region r : this) { Set edgeSet = this.edges.get(r); if (edgeSet != null) { for (FilteredRegion filteredRegion : edgeSet) { if (!first) { s.append(", "); //$NON-NLS-1$ } s.append(r + "->" + filteredRegion.getRegion()); //$NON-NLS-1$ first = false; } } } s.append("]"); //$NON-NLS-1$ return s.toString(); } } public Set getRegions() { Set result = new HashSet(); synchronized (this.monitor) { result.addAll(this.regions.values()); } return result; } public RegionFilterBuilder createRegionFilterBuilder() { return new StandardRegionFilterBuilder(); } private void notifyAdded(Region region) { Set listeners = getListeners(); for (RegionLifecycleListener listener : listeners) { listener.regionAdded(region); } } private void notifyRemoving(Region region) { Set listeners = getListeners(); for (RegionLifecycleListener listener : listeners) { listener.regionRemoving(region); } } private Set getListeners() { if (this.bundleContext == null) return Collections.emptySet(); Set listeners = new HashSet(); try { Collection> listenerServiceReferences = this.bundleContext.getServiceReferences(RegionLifecycleListener.class, null); for (ServiceReference listenerServiceReference : listenerServiceReferences) { RegionLifecycleListener regionLifecycleListener = this.bundleContext.getService(listenerServiceReference); if (regionLifecycleListener != null) { listeners.add(regionLifecycleListener); } } } catch (InvalidSyntaxException e) { e.printStackTrace(); } return listeners; } /** * {@inheritDoc} */ public void visitSubgraph(Region startingRegion, RegionDigraphVisitor visitor) { this.subgraphTraverser.visitSubgraph(startingRegion, visitor); } /** * Returns a snapshot of filtered regions * * @return a snapshot of filtered regions */ Map> getFilteredRegions() { synchronized (this.monitor) { return new HashMap>(this.edges); } } /** * {@inheritDoc} */ public RegionDigraphPersistence getRegionDigraphPersistence() { return new StandardRegionDigraphPersistence(); } /** * {@inheritDoc} */ @Override public RegionDigraph copy() throws BundleException { return new StandardRegionDigraph(this); } /** * {@inheritDoc} */ @Override public void replace(RegionDigraph digraph) throws BundleException { replace(digraph, true); } private void replace(RegionDigraph digraph, boolean check) throws BundleException { if (!(digraph instanceof StandardRegionDigraph)) throw new IllegalArgumentException("Only digraphs of type '" + StandardRegionDigraph.class.getName() + "' are allowed: " + digraph.getClass().getName()); //$NON-NLS-1$ //$NON-NLS-2$ StandardRegionDigraph replacement = (StandardRegionDigraph) digraph; if (check && replacement.origin != this) throw new IllegalArgumentException("The replacement digraph is not a copy of this digraph."); //$NON-NLS-1$ // notify removing first, and outside the monitor lock final Set removed = getRegions(); removed.removeAll(replacement.getRegions()); for (Region region : removed) { notifyRemoving(region); } Map> filteredRegions = replacement.getFilteredRegions(); final Set added = new HashSet(); synchronized (this.monitor) { if (check && this.updateCount.get() != replacement.originUpdateCount) { throw new BundleException("The origin update count has changed since the replacement copy was created.", BundleException.INVALID_OPERATION); //$NON-NLS-1$ } Map nameToRegion = new HashMap(regions); this.regions.clear(); this.edges.clear(); this.bundleIdToRegionMapping.clear(); for (Region original : filteredRegions.keySet()) { Region copy = nameToRegion.get(original.getName()); if (copy != null) { // reuse the previous region object regions.put(copy.getName(), copy); edges.put(copy, EMPTY_EDGE_SET); } else { // create a new one copy = this.createRegion(original.getName(), false); // collect added for notifying later ouside the lock added.add(copy); } for (Long id : original.getBundleIds()) { copy.addBundle(id); } } for (Map.Entry> connection : filteredRegions.entrySet()) { Region tailRegion = this.getRegion(connection.getKey().getName()); for (FilteredRegion headFilter : connection.getValue()) { Region headRegion = this.getRegion(headFilter.getRegion().getName()); this.connect(tailRegion, headFilter.getFilter(), headRegion); } } incrementUpdateCount(); if (check) { replacement.originUpdateCount = this.updateCount.get(); } } // Now notify of additions outside the lock for (Region region : added) { notifyAdded(region); } } /** * {@inheritDoc} */ @Override public ResolverHookFactory getResolverHookFactory() { return resolverHookFactory; } CollisionHook getBundleCollisionHook() { return bundleCollisionHook; } /** * {@inheritDoc} */ @Override public EventHook getBundleEventHook() { return bundleEventHook; } /** * {@inheritDoc} */ @Override public FindHook getBundleFindHook() { return bundleFindHook; } /** * {@inheritDoc} */ @SuppressWarnings("deprecation") @Override public org.osgi.framework.hooks.service.EventHook getServiceEventHook() { return serviceEventHook; } /** * {@inheritDoc} */ @Override public org.osgi.framework.hooks.service.FindHook getServiceFindHook() { return serviceFindHook; } /** * {@inheritDoc} */ @Override public void setDefaultRegion(Region defaultRegion) { synchronized (this.monitor) { if (defaultRegion != null) { checkRegionExists(defaultRegion); } this.defaultRegion = defaultRegion; } } /** * {@inheritDoc} */ @Override public Region getDefaultRegion() { return this.defaultRegion; } /** * {@inheritDoc} */ @Override public void associateBundleWithRegion(long bundleId, Region region) throws BundleException { synchronized (this.monitor) { checkRegionExists(region); this.bundleIdToRegionMapping.associateBundleWithRegion(bundleId, region); incrementUpdateCount(); } } private void checkRegionExists(Region region) { if (this.regions.get(region.getName()) == null) { throw new IllegalStateException("Operation not allowed on region " + region.getName() + " which is not part of a digraph"); //$NON-NLS-1$ //$NON-NLS-2$ } } private void incrementUpdateCount() { synchronized (this.monitor) { this.updateCount.incrementAndGet(); } } /** * {@inheritDoc} */ @Override public void dissociateBundleFromRegion(long bundleId, Region region) { synchronized (this.monitor) { checkRegionExists(region); this.bundleIdToRegionMapping.dissociateBundleFromRegion(bundleId, region); incrementUpdateCount(); } } /** * {@inheritDoc} */ @Override public boolean isBundleAssociatedWithRegion(long bundleId, Region region) { synchronized (this.monitor) { return this.bundleIdToRegionMapping.isBundleAssociatedWithRegion(bundleId, region); } } /** * {@inheritDoc} */ @Override public Set getBundleIds(Region region) { synchronized (this.monitor) { return this.bundleIdToRegionMapping.getBundleIds(region); } } /** * {@inheritDoc} */ @Override public void clear() { synchronized (this.monitor) { this.bundleIdToRegionMapping.clear(); incrementUpdateCount(); } } /** * {@inheritDoc} */ @Override public void dissociateRegion(Region region) { synchronized (this.monitor) { this.bundleIdToRegionMapping.dissociateRegion(region); incrementUpdateCount(); } } }