/******************************************************************************* * Copyright (c) 2009 SpringSource, a divison of VMware, Inc. * 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, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.virgo.ide.manifest.internal.core; import java.util.Collections; 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.internal.misc.StatusUtil; 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; import org.springframework.ide.eclipse.core.SpringCoreUtils; import org.springframework.ide.eclipse.core.java.JdtUtils; /** * Default {@link IBundleManifestManager} implementation used to manage the life-cycle of the * {@link BundleManifest} instances. * @author Christian Dupuis * @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 = rwl.writeLock(); /** Read lock to protect from reading while writing to the model resources */ private final Lock r = 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 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 List<IBundleManifestChangeListener> bundleManifestChangeListeners = new CopyOnWriteArrayList<IBundleManifestChangeListener>(); private Map<IJavaProject, Long> bundleTimestamps = new ConcurrentHashMap<IJavaProject, Long>(); private 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; /** * {@inheritDoc} */ public BundleManifest getBundleManifest(IJavaProject javaProject) { try { r.lock(); if (bundles.containsKey(javaProject)) { return bundles.get(javaProject); } } finally { 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 { r.lock(); if (testBundles.containsKey(javaProject)) { return testBundles.get(javaProject); } } finally { 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 { r.lock(); if (bundles.containsKey(javaProject)) { BundleManifest bundleManifest = 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 { r.unlock(); } } /** * {@inheritDoc} */ public Set<String> getResolvedPackageImports(IJavaProject javaProject) { try { r.lock(); if (packageImports.containsKey(javaProject)) { return packageImports.get(javaProject); } return Collections.emptySet(); } finally { r.unlock(); } } /** * Starts the model. */ public void start() { this.resourceChangeListener = new ManifestResourceChangeListener(); ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceChangeListener); } /** * Stops the model. */ public void stop() { ResourcesPlugin.getWorkspace().removeResourceChangeListener(resourceChangeListener); bundles = null; packageImports = null; } /** * {@inheritDoc} */ public void updateResolvedPackageImports(IJavaProject javaProject, Set<String> resolvedPackageImports) { try { w.lock(); packageImports.put(javaProject, resolvedPackageImports); } finally { 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 ? testBundles : bundles); Map<IJavaProject, Long> timestamps = (testBundle ? testBundleTimestamps : bundleTimestamps); if (manifestFile == null) { try { w.lock(); manifests.remove(javaProject); timestamps.remove(javaProject); bundleManifestChanged(null, null, null, null, IMPORTS_CHANGED, javaProject); } finally { w.unlock(); } } if (manifestFile != null) { r.lock(); try { if (timestamps.containsKey(javaProject)) { Long oldTimestamp = timestamps.get(javaProject); oldBundleManifest = manifests.get(javaProject); if (oldTimestamp.longValue() >= manifestFile.getLocalTimeStamp()) { return oldBundleManifest; } } } finally { r.unlock(); } BundleManifest bundleManifest = BundleManifestUtils.getBundleManifest(javaProject, testBundle); try { w.lock(); if (bundleManifest != null) { manifests.put(javaProject, bundleManifest); } else { manifests.remove(javaProject); } timestamps.put(javaProject, manifestFile.getLocalTimeStamp()); } finally { 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 (bundleManifestChangeListeners.contains(bundleManifestChangeListener)) { 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 : bundleManifestChangeListeners) { listener.bundleManifestChanged(newBundleManifest, oldBundleManifest, newTestBundleManifest, oldTestBundleManifest, types, javaProject); } } /** * {@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) { return true; } protected boolean resourceChanged(IResource resource, int flags) { if (resource instanceof IFile) { if ((flags & IResourceDelta.CONTENT) != 0) { if ((FacetUtils.isBundleProject(resource) || SpringCoreUtils .hasProjectFacet(resource, FacetCorePlugin.WEB_FACET_ID))) { if (SpringCoreUtils.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 (resource.getName().equals( "org.eclipse.wst.common.project.facet.core.xml") || resource.getName().equals( "org.eclipse.virgo.ide.runtime.core.par.xml")) { // target of a par project or par bundles has changed if (FacetUtils.isParProject(resource)) { Par par = FacetUtils.getParDefinition(resource.getProject()); 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); } } } } // target of bundle project could have changed else if (FacetUtils.isBundleProject(resource)) { updateBundleManifestForResource(resource); } } } return false; } return true; } 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); } protected boolean resourceOpened(IResource resource) { return true; } protected boolean resourceRemoved(IResource resource) { if (SpringCoreUtils.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"); } } }