/******************************************************************************* * Copyright (c) 2003, 2007 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.jst.j2ee.internal.common.classpath; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IContainer; 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.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jdt.core.IClasspathContainer; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jst.common.jdt.internal.classpath.FlexibleProjectContainer; import org.eclipse.jst.j2ee.componentcore.J2EEModuleVirtualComponent; import org.eclipse.jst.j2ee.componentcore.util.EARVirtualComponent; import org.eclipse.jst.j2ee.internal.J2EEConstants; import org.eclipse.jst.j2ee.internal.componentcore.JavaEEBinaryComponentHelper; import org.eclipse.jst.j2ee.internal.plugin.IJ2EEModuleConstants; import org.eclipse.jst.j2ee.internal.plugin.J2EEPlugin; import org.eclipse.jst.j2ee.internal.project.J2EEProjectUtilities; import org.eclipse.jst.j2ee.model.IModelProvider; import org.eclipse.jst.j2ee.model.ModelProviderManager; import org.eclipse.jst.j2ee.project.EarUtilities; import org.eclipse.jst.j2ee.project.JavaEEProjectUtilities; import org.eclipse.jst.javaee.application.Application; import org.eclipse.wst.common.componentcore.ComponentCore; import org.eclipse.wst.common.componentcore.ModuleCoreNature; import org.eclipse.wst.common.componentcore.internal.StructureEdit; import org.eclipse.wst.common.componentcore.internal.WorkbenchComponent; import org.eclipse.wst.common.componentcore.internal.builder.IDependencyGraph; import org.eclipse.wst.common.componentcore.internal.builder.IDependencyGraphListener; import org.eclipse.wst.common.componentcore.internal.builder.IDependencyGraphUpdateEvent; import org.eclipse.wst.common.componentcore.internal.impl.ResourceTreeRootAdapter; import org.eclipse.wst.common.componentcore.internal.impl.WTPModulesResourceFactory; import org.eclipse.wst.common.componentcore.internal.resources.VirtualComponent; import org.eclipse.wst.common.componentcore.resources.IVirtualComponent; import org.eclipse.wst.common.componentcore.resources.IVirtualFolder; import org.eclipse.wst.common.componentcore.resources.IVirtualReference; import org.eclipse.wst.common.internal.emf.utilities.ExtendedEcoreUtil; import org.eclipse.wst.common.project.facet.core.FacetedProjectFramework; public class J2EEComponentClasspathUpdater implements IResourceChangeListener, IResourceDeltaVisitor, IDependencyGraphListener { private static J2EEComponentClasspathUpdater instance = null; private static boolean suspendPostChangeEvents = false; private static boolean updateDependencyGraph = true; private int pauseCount = 0; public static IPath WEB_APP_LIBS_PATH = new Path("org.eclipse.jst.j2ee.internal.web.container"); //$NON-NLS-1$ public static J2EEComponentClasspathUpdater getInstance() { if (instance == null) { init(); } return instance; } private static void init() { if (instance == null) { instance = new J2EEComponentClasspathUpdater(); } } /** * Pauses updates; any caller of this method must ensure through a * try/finally block that resumeUpdates is subsequently called. */ public void pauseUpdates() { synchronized (this) { pauseCount++; } } public void resumeUpdates() { resumeUpdates(true); } private void resumeUpdates(boolean scheduleJob){ synchronized (this) { if (pauseCount > 0) { pauseCount--; } if (pauseCount > 0) { return; } } if(scheduleJob){ moduleUpdateJob.schedule(MODULE_UPDATE_DELAY); } } public void forceUpdate(Collection projects){ forceUpdate(projects, true); } /** * Collection of type IProject * @param projects */ public void forceUpdate(Collection projects, boolean runAsJob) { try { pauseUpdates(); Iterator iterator = projects.iterator(); while(iterator.hasNext()){ queueUpdate((IProject)iterator.next()); } } finally { forceUpdateOnNextRun = true; // the following code is in place of the normal call to // resume updates. This restores the pauseCount and forces // the job to be scheduled immediately synchronized (this) { if (pauseCount > 0) { pauseCount--; } } if(runAsJob){ moduleUpdateJob.schedule(0); } else { try { updateDependencyGraph = false; moduleUpdateJob.run(new NullProgressMonitor()); } finally { updateDependencyGraph = true; } } } } private boolean forceUpdateOnNextRun = false; public void queueUpdate(IProject project) { if (JavaEEProjectUtilities.isEARProject(project)) { queueUpdateEAR(project); } else if (JavaEEProjectUtilities.isApplicationClientProject(project) || JavaEEProjectUtilities.isEJBProject(project) || JavaEEProjectUtilities.isDynamicWebProject(project) || JavaEEProjectUtilities.isJCAProject(project) || JavaEEProjectUtilities.isUtilityProject(project)) { queueUpdateModule(project); } } public void queueUpdateModule(IProject project) { moduleUpdateJob.queueModule(project); synchronized (this) { if (pauseCount > 0) { return; } } moduleUpdateJob.schedule(MODULE_UPDATE_DELAY); } public void queueUpdateEAR(IProject earProject) { moduleUpdateJob.queueEAR(earProject); synchronized (this) { if (pauseCount > 0) { return; } } moduleUpdateJob.schedule(MODULE_UPDATE_DELAY); } public boolean projectsQueued() { return moduleUpdateJob.projectsQueued() || moduleUpdateJob.getState() != Job.NONE; } private static final int MODULE_UPDATE_DELAY = 30; public static final String MODULE_UPDATE_JOB_NAME = Messages.J2EEComponentClasspathUpdater_EAR_Libraries_Update_Jo_; public static final String FIND_NODE_JOB_NAME = Messages.J2EEComponentClasspathUpdater_Find_Node; private final ModuleUpdateJob moduleUpdateJob = new ModuleUpdateJob(); public class FindNodeJob extends Job { IResourceDelta[] delta; @Override public boolean belongsTo(Object family) { if(family == FIND_NODE_JOB_NAME){ return true; } return super.belongsTo(family); } /* * Needs to notice changes to MANIFEST.MF in any J2EE projects, changes to * .component in any J2EE Projects, and any archive changes in EAR projects */ private boolean findNode(IResourceDelta[] delta) { for (int i = 0; i < delta.length; i++) { if (delta[i].toString().indexOf(IJ2EEModuleConstants.COMPONENT_FILE_NAME) != -1) { StructureEdit core = StructureEdit .getStructureEditForRead(delta[i].getResource() .getProject()); if(null != core){ WorkbenchComponent component = core.getComponent(); if(component != null){ clearResourceTreeRootCache(component); } } } else { findNode(delta[i].getAffectedChildren()); } } return true; } public FindNodeJob(IResourceDelta[] d) { super(FIND_NODE_JOB_NAME); setRule(ResourcesPlugin.getWorkspace().getRoot()); setSystem(true); setDelta(d); } @Override protected IStatus run(IProgressMonitor monitor) { SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable e) { J2EEPlugin.logError(e); } public void run() throws Exception { findNode(delta); } }); return Status.OK_STATUS; } public void setDelta(IResourceDelta[] delta) { this.delta = delta; } } public class ModuleUpdateJob extends Job { @Override public boolean belongsTo(Object family) { if(family == MODULE_UPDATE_JOB_NAME){ return true; } return super.belongsTo(family); } // We use the listener list as a thread safe queue. private class Queue extends ListenerList { @Override public synchronized Object[] getListeners() { Object[] data = super.getListeners(); clear(); return data; } } private Queue moduleQueue = new Queue(); private Queue earQueue = new Queue(); //a private queue for adding modules queued by the EAR private Queue earAddedModuleQueue = new Queue(); public ModuleUpdateJob() { super(MODULE_UPDATE_JOB_NAME); setRule(ResourcesPlugin.getWorkspace().getRoot()); setSystem(true); } public void queueEAR(IProject ear) { earQueue.add(ear); } public void queueModule(IProject project) { moduleQueue.add(project); } public boolean projectsQueued() { return !earQueue.isEmpty() || !moduleQueue.isEmpty(); } /** * Add referenced EARs from the queued modules into the EARs queue */ private void queueReferencingEars(Object[] projects) { for (int p = 0; p < projects.length; p++) { IProject project = (IProject) projects[p]; if (!isKnown(project)) { if(IDependencyGraph.INSTANCE.isStale()){ //avoid deadlock https://bugs.eclipse.org/bugs/show_bug.cgi?id=334050 //if the data is stale abort and attempt to update again in the near future J2EEComponentClasspathUpdater.getInstance().queueUpdate(project); continue; } IProject[] earProjects = EarUtilities.getReferencingEARProjects(project); for (int i = 0; i < earProjects.length; i++) { queueEAR(earProjects[i]); } } } } private void processEars() { Object[] earProjects = earQueue.getListeners(); for (int i = 0; i < earProjects.length; i++) { IProject earProject = (IProject) earProjects[i]; if (JavaEEProjectUtilities.isEARProject(earProject)) { IVirtualComponent earComponent = ComponentCore.createComponent(earProject); if (earComponent instanceof VirtualComponent) { ((VirtualComponent)earComponent).flushCache(); } IVirtualReference[] refs = J2EEProjectUtilities.getComponentReferences(earComponent); IVirtualComponent comp = null; for (int j = 0; j < refs.length; j++) { comp = refs[j].getReferencedComponent(); if (!comp.isBinary()) { earAddedModuleQueue.add(comp.getProject()); } } if(null != earComponent){ JavaEEBinaryComponentHelper.clearDisconnectedArchivesInEAR(earComponent); } } } } private void processModules(Object[] projects) { for (int i = 0; i < projects.length; i++) { IProject project = (IProject) projects[i]; // this block is for Web app Libraries if (JavaEEProjectUtilities.isDynamicWebProject(project)) { IClasspathContainer webAppLibrariesContainer = J2EEComponentClasspathContainerUtils.getInstalledWebAppLibrariesContainer(project); // If the container is present, refresh it if (webAppLibrariesContainer != null) { ((FlexibleProjectContainer) webAppLibrariesContainer).refresh(forceUpdateOnNextRun); } } // ******************** The following is for EAR Libraries IClasspathContainer earLibrariesContainer = J2EEComponentClasspathContainerUtils.getInstalledEARLibrariesContainer(project); // If the container is present, refresh it if (earLibrariesContainer != null) { ((J2EEComponentClasspathContainer) earLibrariesContainer).refresh(forceUpdateOnNextRun); } } // [202820] updateDependencyGraph = true; } @Override protected IStatus run(IProgressMonitor monitor) { SafeRunner.run(new ISafeRunnable() { public void handleException(Throwable e) { J2EEPlugin.logError(e); } public void run() throws Exception { try { Object[] moduleProjects = moduleQueue.getListeners(); queueReferencingEars(moduleProjects); processEars(); Object [] earQueuedModuleProjects = earAddedModuleQueue.getListeners(); Set modulesSet = new HashSet(); modulesSet.addAll(Arrays.asList(moduleProjects)); modulesSet.addAll(Arrays.asList(earQueuedModuleProjects)); Object [] modulesArray = modulesSet.toArray(); processModules(modulesArray); } finally { forceUpdateOnNextRun = false; } } }); return Status.OK_STATUS; } } public IClasspathContainer getWebAppLibrariesContainer(IProject webProject, boolean create) { IJavaProject jproj = JavaCore.create(webProject); IClasspathContainer container = null; IClasspathEntry entry = create ? null : getExistingContainer(jproj, WEB_APP_LIBS_PATH); if (entry != null || create) { try { container = JavaCore.getClasspathContainer(WEB_APP_LIBS_PATH, jproj); } catch (JavaModelException e) { J2EEPlugin.logError(e); } } return container; } /** * Returns the existing classpath container if it is already on the classpath. This will not * create a new container. * * @param jproj * @param classpathContainerID * @return */ public IClasspathEntry getExistingContainer(IJavaProject jproj, IPath classpathContainerPath) { return J2EEComponentClasspathContainerUtils.getInstalledContainerEntry(jproj, classpathContainerPath); } private Set knownProjects = new HashSet(); private boolean isKnown(IProject project){ return !knownProjects.add(project.getName()); } private void forgetProject(IProject project){ knownProjects.remove(project.getName()); } public void dependencyGraphUpdate(IDependencyGraphUpdateEvent event) { if((event.getType() & IDependencyGraphUpdateEvent.ADDED) == IDependencyGraphUpdateEvent.ADDED){ Map<IProject, Set<IProject>> addedReferences = event.getAddedReferences(); for(IProject referencedProject : addedReferences.keySet()){ queueUpdate(referencedProject); for(IProject referencingProject : addedReferences.get(referencedProject)){ queueUpdate(referencingProject); } } } } public void resourceChanged(IResourceChangeEvent event) { boolean scheduleJob = false; try { pauseUpdates(); switch (event.getType()){ case IResourceChangeEvent.PRE_CLOSE: case IResourceChangeEvent.PRE_DELETE: IResource resource = event.getResource(); if(resource.getType() == IResource.PROJECT){ if(ModuleCoreNature.isFlexibleProject((IProject) resource)){ if(JavaEEProjectUtilities.isEARProject((IProject)resource)){ IProject earProject = (IProject) resource; IVirtualReference[] refs = EarUtilities.getComponentReferences(ComponentCore.createComponent(earProject)); IVirtualComponent comp = null; for (int j = 0; j < refs.length; j++) { comp = refs[j].getReferencedComponent(); if (!comp.isBinary()) { queueUpdateModule(comp.getProject()); } } } else { IProject[] earProjects = EarUtilities.getReferencingEARProjects((IProject)resource); for(int i=0; i<earProjects.length; i++){ queueUpdateEAR(earProjects[i]); } } forgetProject((IProject)resource); } JavaEEBinaryComponentHelper.clearAllArchivesInProject((IProject)resource); } break; case IResourceChangeEvent.POST_CHANGE: if(!suspendPostChangeEvents) { scheduleJob = true; event.getDelta().accept(this); IResourceDelta[] d = event.getDelta().getAffectedChildren(); FindNodeJob newJob = createFindNodeJob(d); newJob.schedule(); } break; } } catch (CoreException e) { J2EEPlugin.logError(e); } finally { resumeUpdates(scheduleJob); } } private FindNodeJob createFindNodeJob(IResourceDelta[] d) { return new FindNodeJob(d); } public static void clearResourceTreeRootCache(WorkbenchComponent aModule) { ResourceTreeRootAdapter resourceTreeAdapter = (ResourceTreeRootAdapter) ExtendedEcoreUtil .getAdapter(aModule, aModule.eAdapters(), ResourceTreeRootAdapter.DEPLOY_ADAPTER_TYPE); if(null != resourceTreeAdapter) { resourceTreeAdapter.setResourceTreeRoot(null); } resourceTreeAdapter = (ResourceTreeRootAdapter) ExtendedEcoreUtil .getAdapter(aModule, aModule.eAdapters(), ResourceTreeRootAdapter.SOURCE_ADAPTER_TYPE); if(null != resourceTreeAdapter){ resourceTreeAdapter.setResourceTreeRoot(null); } } public boolean visit(IResourceDelta delta) { // If it is only a marker change, ignore the change if(delta.getFlags() == IResourceDelta.MARKERS) { return false; } IResource resource = delta.getResource(); switch (resource.getType()) { case IResource.ROOT: return true; case IResource.PROJECT: return ModuleCoreNature.isFlexibleProject((IProject) resource); case IResource.FOLDER: { if (resource.getName().equals(IJ2EEModuleConstants.DOT_SETTINGS)) { return true; } IVirtualComponent comp = ComponentCore.createComponent(resource.getProject()); if (comp instanceof J2EEModuleVirtualComponent || comp instanceof EARVirtualComponent) { IVirtualFolder rootFolder = comp.getRootFolder(); if (comp instanceof EARVirtualComponent) { return isRootAncester(resource, rootFolder) || isEARLibraryDirectory(resource, comp); }//else J2EEModuleVirtualComponent return isRootAncester(resource, rootFolder) || isFolder(resource, rootFolder.getFolder(J2EEConstants.META_INF)); } return false; } case IResource.FILE: { String name = resource.getName(); if (name.equals(WTPModulesResourceFactory.WTP_MODULES_SHORT_NAME)) { queueUpdate(resource.getProject()); } else if (name.equals(J2EEConstants.MANIFEST_SHORT_NAME)) { // MANIFEST.MF must be all caps per spec IFile manifestFile = J2EEProjectUtilities.getManifestFile(resource.getProject(), false); if (null == manifestFile || resource.equals(manifestFile)) { queueUpdateModule(resource.getProject()); } } else if (endsWithIgnoreCase(name, IJ2EEModuleConstants.JAR_EXT) || endsWithIgnoreCase(name, ".zip")) { //$NON-NLS-1$ try { if (FacetedProjectFramework.hasProjectFacet(resource.getProject(), J2EEProjectUtilities.ENTERPRISE_APPLICATION)) { IVirtualComponent comp = ComponentCore.createComponent(resource.getProject()); if(isFolder(resource.getParent(), comp.getRootFolder()) || isEARLibraryDirectory(resource, comp)){ queueUpdateEAR(resource.getProject()); } } } catch (CoreException e) { J2EEPlugin.logError(e); } } } } return false; } private boolean isEARLibraryDirectory(IResource resource, IVirtualComponent earComponent) { // check if the EAR component's version is 5 or greater IProject project = earComponent.getProject(); if (!JavaEEProjectUtilities.isJEEComponent(earComponent, JavaEEProjectUtilities.DD_VERSION) || !JavaEEProjectUtilities.isJEEComponent(earComponent, JavaEEProjectUtilities.FACET_VERSION)) { return false; } // retrieve the model provider IModelProvider modelProvider = ModelProviderManager.getModelProvider(project); if (modelProvider == null) return false; // retrieve the EAR's model object Application app = (Application) modelProvider.getModelObject(); if (app == null) return false; // retrieve the library directory from the model String libDir = app.getLibraryDirectory(); if (libDir == null) { // the library directory is not set - use the default one libDir = J2EEConstants.EAR_DEFAULT_LIB_DIR; } IVirtualFolder libFolder = earComponent.getRootFolder().getFolder(libDir); if(resource.getType() == IResource.FILE){ return isRootAncester(resource.getParent(), libFolder); } return isRootAncester(resource, libFolder); } public static boolean endsWithIgnoreCase(String str, String sfx) { return str.regionMatches(true, str.length() - sfx.length(), sfx, 0, sfx.length()); } public static boolean isFolder(IResource resource, IVirtualFolder folder) { IContainer[] realFolders = folder.getUnderlyingFolders(); for (int i = 0; i < realFolders.length; i++) { if (realFolders[i].equals(resource)) { return true; } } return false; } public static boolean isRootAncester(IResource resource, IVirtualFolder rootFolder) { IContainer[] realRoots = rootFolder.getUnderlyingFolders(); IPath currentResourcePath = resource.getFullPath(); for (int i = 0; i < realRoots.length; i++) { if (currentResourcePath.isPrefixOf(realRoots[i].getFullPath())) return true; } return false; } public static boolean shouldUpdateDependencyGraph() { return updateDependencyGraph; } // [202820] public static void setUpdateDependencyGraph(boolean value) { updateDependencyGraph = value; } public void setSuspendPostChangeEvents(boolean suspendAction) { J2EEComponentClasspathUpdater.suspendPostChangeEvents = suspendAction; } public boolean isSuspendPostChangeEvents() { return suspendPostChangeEvents; } }