blob: a07962951ce59e4702630022d793baf4cab2d29d [file] [log] [blame]
/*********************************************************************
* Copyright (c) 2009 - 2013 SpringSource, a division of VMware, Inc.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.virgo.ide.manifest.internal.core;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.virgo.ide.facet.core.FacetCorePlugin;
import org.eclipse.virgo.ide.facet.core.FacetUtils;
import org.eclipse.virgo.ide.manifest.core.BundleManifestCorePlugin;
import org.eclipse.virgo.ide.manifest.core.BundleManifestUtils;
import org.eclipse.virgo.ide.manifest.core.IBundleManifestChangeListener;
import org.eclipse.virgo.ide.manifest.core.IBundleManifestManager;
import org.eclipse.virgo.ide.manifest.core.IBundleManifestMangerWorkingCopy;
import org.eclipse.virgo.ide.par.Bundle;
import org.eclipse.virgo.ide.par.Par;
import org.eclipse.virgo.util.osgi.manifest.BundleManifest;
import org.eclipse.virgo.util.osgi.manifest.ExportedPackage;
/**
* Default {@link IBundleManifestManager} implementation used to manage the life-cycle of the {@link BundleManifest}
* instances.
*
* @author Christian Dupuis
* @author Leo Dos Santos
* @since 1.0.0
*/
public class BundleManifestManager implements IBundleManifestMangerWorkingCopy {
public static final Set<IBundleManifestChangeListener.Type> IMPORTS_CHANGED;
static {
IMPORTS_CHANGED = new HashSet<IBundleManifestChangeListener.Type>();
IMPORTS_CHANGED.add(IBundleManifestChangeListener.Type.IMPORT_PACKAGE);
IMPORTS_CHANGED.add(IBundleManifestChangeListener.Type.IMPORT_BUNDLE);
IMPORTS_CHANGED.add(IBundleManifestChangeListener.Type.IMPORT_LIBRARY);
IMPORTS_CHANGED.add(IBundleManifestChangeListener.Type.REQUIRE_BUNDLE);
}
/** Internal read write lock to protect the read and write operations of the internal caches */
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
/** Write lock to protect the model from concurrent write operations */
private final Lock w = this.rwl.writeLock();
/** Read lock to protect from reading while writing to the model resources */
private final Lock r = this.rwl.readLock();
/** Internal cache of {@link BundleManifest} keyed by {@link IJavaProject} */
private Map<IJavaProject, BundleManifest> bundles = new ConcurrentHashMap<IJavaProject, BundleManifest>();
/** Internal cache of {@link BundleManifest} keyed by {@link IJavaProject}; these represent the */
private final Map<IJavaProject, BundleManifest> testBundles = new ConcurrentHashMap<IJavaProject, BundleManifest>();
/** Internal cache of resolved package imports keyed by {@link IJavaProject} */
private Map<IJavaProject, Set<String>> packageImports = new ConcurrentHashMap<IJavaProject, Set<String>>();
/** Internal listener list */
private final List<IBundleManifestChangeListener> bundleManifestChangeListeners = new CopyOnWriteArrayList<IBundleManifestChangeListener>();
private final Map<IJavaProject, Long> bundleTimestamps = new ConcurrentHashMap<IJavaProject, Long>();
private final Map<IJavaProject, Long> testBundleTimestamps = new ConcurrentHashMap<IJavaProject, Long>();
/**
* Resource change listener that listens to changes of Eclipse file resources and triggers a refresh for
* corresponding class path container
*/
private IResourceChangeListener resourceChangeListener = null;
private IResourceChangeListener parChangeListener = null;
/**
* {@inheritDoc}
*/
public BundleManifest getBundleManifest(IJavaProject javaProject) {
try {
this.r.lock();
if (this.bundles.containsKey(javaProject)) {
return this.bundles.get(javaProject);
}
} finally {
this.r.unlock();
}
// Load and store the bundle if not found; loadBundleManifest will acquire write lock.
return loadBundleManifest(javaProject, false);
}
/**
* {@inheritDoc}
*/
public BundleManifest getTestBundleManifest(IJavaProject javaProject) {
try {
this.r.lock();
if (this.testBundles.containsKey(javaProject)) {
return this.testBundles.get(javaProject);
}
} finally {
this.r.unlock();
}
// Load and store the bundle if not found; loadBundleManifest will acquire write lock.
return loadBundleManifest(javaProject, true);
}
/**
* {@inheritDoc}
*/
public Set<String> getPackageExports(IJavaProject javaProject) {
try {
this.r.lock();
if (this.bundles.containsKey(javaProject)) {
BundleManifest bundleManifest = this.bundles.get(javaProject);
if (bundleManifest.getExportPackage() != null && bundleManifest.getExportPackage().getExportedPackages() != null) {
Set<String> packageExports = new LinkedHashSet<String>();
for (ExportedPackage packageExport : bundleManifest.getExportPackage().getExportedPackages()) {
packageExports.add(packageExport.getPackageName());
}
return packageExports;
}
}
return Collections.emptySet();
} finally {
this.r.unlock();
}
}
/**
* {@inheritDoc}
*/
public Set<String> getResolvedPackageImports(IJavaProject javaProject) {
try {
this.r.lock();
if (this.packageImports.containsKey(javaProject)) {
return this.packageImports.get(javaProject);
}
return Collections.emptySet();
} finally {
this.r.unlock();
}
}
/**
* Starts the model.
*/
public void start() {
this.resourceChangeListener = new ManifestResourceChangeListener();
this.parChangeListener = new ParChangeListener();
ResourcesPlugin.getWorkspace().addResourceChangeListener(this.resourceChangeListener);
ResourcesPlugin.getWorkspace().addResourceChangeListener(this.parChangeListener);
}
/**
* Stops the model.
*/
public void stop() {
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this.resourceChangeListener);
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this.parChangeListener);
this.bundles = null;
this.packageImports = null;
}
/**
* {@inheritDoc}
*/
public void updateResolvedPackageImports(IJavaProject javaProject, Set<String> resolvedPackageImports) {
try {
this.w.lock();
this.packageImports.put(javaProject, resolvedPackageImports);
} finally {
this.w.unlock();
}
}
/**
* Internal method to locate and load {@link BundleManifest}s for given {@link IJavaProject}.
*
* @param testBundle
*/
private BundleManifest loadBundleManifest(IJavaProject javaProject, boolean testBundle) {
IFile manifestFile = BundleManifestUtils.locateManifest(javaProject, testBundle);
BundleManifest oldBundleManifest = null;
Map<IJavaProject, BundleManifest> manifests = testBundle ? this.testBundles : this.bundles;
Map<IJavaProject, Long> timestamps = testBundle ? this.testBundleTimestamps : this.bundleTimestamps;
if (manifestFile == null) {
try {
this.w.lock();
manifests.remove(javaProject);
timestamps.remove(javaProject);
bundleManifestChanged(null, null, null, null, IMPORTS_CHANGED, javaProject);
} finally {
this.w.unlock();
}
}
if (manifestFile != null) {
this.r.lock();
try {
if (timestamps.containsKey(javaProject)) {
Long oldTimestamp = timestamps.get(javaProject);
oldBundleManifest = manifests.get(javaProject);
if (oldTimestamp.longValue() >= manifestFile.getLocalTimeStamp()) {
return oldBundleManifest;
}
}
} finally {
this.r.unlock();
}
BundleManifest bundleManifest = BundleManifestUtils.getBundleManifest(javaProject, testBundle);
try {
this.w.lock();
if (bundleManifest != null) {
manifests.put(javaProject, bundleManifest);
} else {
manifests.remove(javaProject);
}
timestamps.put(javaProject, manifestFile.getLocalTimeStamp());
} finally {
this.w.unlock();
}
if (testBundle) {
bundleManifestChanged(null, null, bundleManifest, oldBundleManifest, javaProject);
} else {
bundleManifestChanged(bundleManifest, oldBundleManifest, null, null, javaProject);
}
return bundleManifest;
}
return null;
}
/**
* Triggers an update to the saved {@link BundleManifest} for the given <code>javaProject</code>
*/
private void updateBundleManifest(IJavaProject javaProject, boolean testBundle) {
// Re-load the bundle manifest
loadBundleManifest(javaProject, testBundle);
}
/**
* {@inheritDoc}
*/
public void addBundleManifestChangeListener(IBundleManifestChangeListener bundleManifestChangeListener) {
this.bundleManifestChangeListeners.add(bundleManifestChangeListener);
}
/**
* {@inheritDoc}
*/
public void removeBundleManifestChangeListener(IBundleManifestChangeListener bundleManifestChangeListener) {
if (this.bundleManifestChangeListeners.contains(bundleManifestChangeListener)) {
this.bundleManifestChangeListeners.remove(bundleManifestChangeListener);
}
}
/**
* Notifies {@link IBundleManifestChangeListener} of changes.
*/
private void bundleManifestChanged(BundleManifest newBundleManifest, BundleManifest oldBundleManifest, BundleManifest newTestBundleManifest,
BundleManifest oldTestBundleManifest, IJavaProject javaProject) {
Set<IBundleManifestChangeListener.Type> types = new HashSet<IBundleManifestChangeListener.Type>();
types.addAll(BundleManifestDiffer.diff(newBundleManifest, oldBundleManifest));
types.addAll(BundleManifestDiffer.diff(newTestBundleManifest, oldTestBundleManifest));
bundleManifestChanged(newBundleManifest, oldBundleManifest, newTestBundleManifest, oldTestBundleManifest, types, javaProject);
}
/**
* Notifies {@link IBundleManifestChangeListener} of changes.
*/
private void bundleManifestChanged(BundleManifest newBundleManifest, BundleManifest oldBundleManifest, BundleManifest newTestBundleManifest,
BundleManifest oldTestBundleManifest, Set<IBundleManifestChangeListener.Type> types, IJavaProject javaProject) {
for (IBundleManifestChangeListener listener : this.bundleManifestChangeListeners) {
listener.bundleManifestChanged(newBundleManifest, oldBundleManifest, newTestBundleManifest, oldTestBundleManifest, types, javaProject);
}
}
private void updateBundleManifestForResource(IResource resource) {
IJavaProject javaProject = JavaCore.create(resource.getProject());
BundleManifest bundleManifest = getBundleManifest(javaProject);
BundleManifest testBundleManifest = getTestBundleManifest(javaProject);
bundleManifestChanged(bundleManifest, bundleManifest, testBundleManifest, testBundleManifest, IMPORTS_CHANGED, javaProject);
}
private void updateParLinkedBundleManifests(Par par) {
if (par != null && par.getBundle() != null) {
for (Bundle bundle : par.getBundle()) {
IProject bundleProject = ResourcesPlugin.getWorkspace().getRoot().getProject(bundle.getSymbolicName());
if (FacetUtils.isBundleProject(bundleProject)) {
updateBundleManifestForResource(bundleProject);
}
}
}
}
/**
* {@link IResourceChangeListener} that listens to changes to PAR project deletions. Collects information about
* dependent bundles during the pre-delete phase, then updates those bundles' classpath containers during the
* post-change phase.
*/
class ParChangeListener implements IResourceChangeListener, IResourceDeltaVisitor {
private final Map<IResource, Par> parProjects = new HashMap<IResource, Par>();
public void resourceChanged(IResourceChangeEvent event) {
if (event.getSource() instanceof IWorkspace) {
int eventType = event.getType();
switch (eventType) {
case IResourceChangeEvent.PRE_DELETE:
// capture information about the par contents before deletion
IResource resource = event.getResource();
if (resource != null && FacetUtils.isParProject(resource)) {
Par par = FacetUtils.getParDefinition(resource.getProject());
if (par != null) {
this.parProjects.put(resource, par);
}
}
break;
case IResourceChangeEvent.POST_CHANGE:
// then update bundle manifests post deletion
IResourceDelta delta = event.getDelta();
if (delta != null) {
try {
delta.accept(this);
} catch (CoreException e) {
StatusManager.getManager().handle(
new Status(IStatus.ERROR, BundleManifestCorePlugin.PLUGIN_ID, "Error while traversing resource change delta", e));
}
}
break;
}
}
}
public boolean visit(IResourceDelta delta) throws CoreException {
IResource resource = delta.getResource();
if (delta.getKind() == IResourceDelta.REMOVED) {
if (this.parProjects.containsKey(resource)) {
updateParLinkedBundleManifests(this.parProjects.get(resource));
this.parProjects.remove(resource);
return false;
}
}
return true;
}
};
/**
* {@link IResourceChangeListener} that listens to changes to the MANIFEST.MF resources.
*/
class ManifestResourceChangeListener implements IResourceChangeListener {
/**
* Internal resource delta visitor.
*/
protected class ManifestResourceVisitor implements IResourceDeltaVisitor {
protected int eventType;
public ManifestResourceVisitor(int eventType) {
this.eventType = eventType;
}
public final boolean visit(IResourceDelta delta) throws CoreException {
IResource resource = delta.getResource();
switch (delta.getKind()) {
case IResourceDelta.ADDED:
return resourceAdded(resource);
case IResourceDelta.OPEN:
return resourceOpened(resource);
case IResourceDelta.CHANGED:
return resourceChanged(resource, delta.getFlags());
case IResourceDelta.REMOVED:
return resourceRemoved(resource);
}
return true;
}
protected boolean resourceAdded(IResource resource) {
if (resource instanceof IFile && isParXml(resource)) {
parResourceChanged(resource);
}
return true;
}
protected boolean resourceChanged(IResource resource, int flags) {
if (resource instanceof IFile) {
if ((flags & IResourceDelta.CONTENT) != 0) {
if (FacetUtils.isBundleProject(resource) || FacetUtils.hasProjectFacet(resource, FacetCorePlugin.WEB_FACET_ID)) {
if (isManifest(resource)) {
updateBundleManifest(JavaCore.create(resource.getProject()), false);
} else if (isTestManifest(resource)) {
updateBundleManifest(JavaCore.create(resource.getProject()), true);
} else if (resource.getName().equals(".classpath") && resource.getParent() instanceof IProject) {
updateBundleManifest(JavaCore.create(resource.getProject()), false);
updateBundleManifest(JavaCore.create(resource.getProject()), true);
}
}
if (isParXml(resource)) {
parResourceChanged(resource);
}
}
return false;
}
return true;
}
private void parResourceChanged(IResource resource) {
// target of a par project or par bundles has changed
if (FacetUtils.isParProject(resource)) {
Par par = FacetUtils.getParDefinition(resource.getProject());
updateParLinkedBundleManifests(par);
}
// target of bundle project could have changed
else if (FacetUtils.isBundleProject(resource)) {
updateBundleManifestForResource(resource);
}
}
protected boolean resourceOpened(IResource resource) {
return true;
}
protected boolean resourceRemoved(IResource resource) {
if (isManifest(resource)) {
updateBundleManifest(JavaCore.create(resource.getProject()), false);
} else if (isTestManifest(resource)) {
updateBundleManifest(JavaCore.create(resource.getProject()), true);
}
return true;
}
}
public static final int LISTENER_FLAGS = IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.PRE_BUILD;
private static final int VISITOR_FLAGS = IResourceDelta.ADDED | IResourceDelta.CHANGED | IResourceDelta.REMOVED;
public void resourceChanged(IResourceChangeEvent event) {
if (event.getSource() instanceof IWorkspace) {
int eventType = event.getType();
switch (eventType) {
case IResourceChangeEvent.POST_CHANGE:
IResourceDelta delta = event.getDelta();
if (delta != null) {
try {
delta.accept(getVisitor(eventType), VISITOR_FLAGS);
} catch (CoreException e) {
StatusManager.getManager().handle(
new Status(IStatus.ERROR, BundleManifestCorePlugin.PLUGIN_ID, "Error while traversing resource change delta", e));
}
}
break;
}
} else if (event.getSource() instanceof IProject) {
int eventType = event.getType();
switch (eventType) {
case IResourceChangeEvent.POST_CHANGE:
IResourceDelta delta = event.getDelta();
if (delta != null) {
try {
delta.accept(getVisitor(eventType), VISITOR_FLAGS);
} catch (CoreException e) {
StatusManager.getManager().handle(
new Status(IStatus.ERROR, BundleManifestCorePlugin.PLUGIN_ID, "Error while traversing resource change delta", e));
}
}
break;
}
}
}
protected IResourceDeltaVisitor getVisitor(int eventType) {
return new ManifestResourceVisitor(eventType);
}
public boolean isTestManifest(IResource resource) {
return resource != null && resource.isAccessible() && resource.getType() == IResource.FILE && resource.getName().equals("TEST.MF")
&& resource.getParent() != null && resource.getParent().getProjectRelativePath().lastSegment().equals("META-INF");
}
public boolean isManifest(IResource resource) {
return resource != null && resource.isAccessible() && resource.getType() == IResource.FILE
&& resource.getName().equals(BundleManifestCorePlugin.MANIFEST_FILE_NAME) && resource.getParent() != null
&& resource.getParent().getProjectRelativePath().lastSegment().equals("META-INF");
}
public boolean isParXml(IResource resource) {
return resource != null && (resource.getName().equals("org.eclipse.wst.common.project.facet.core.xml")
|| resource.getName().equals("org.eclipse.virgo.ide.runtime.core.par.xml"));
}
}
}