| /******************************************************************************* |
| * Copyright (c) 2003, 2006 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.io.File; |
| import java.io.IOException; |
| import java.io.Serializable; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.resources.IProject; |
| 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.IStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.jdt.core.IAccessRule; |
| import org.eclipse.jdt.core.IClasspathAttribute; |
| 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.ClasspathDecorations; |
| import org.eclipse.jst.common.jdt.internal.classpath.ClasspathDecorationsManager; |
| import org.eclipse.jst.common.jdt.internal.javalite.IJavaProjectLite; |
| import org.eclipse.jst.common.jdt.internal.javalite.JavaCoreLite; |
| import org.eclipse.jst.j2ee.componentcore.J2EEModuleVirtualComponent; |
| import org.eclipse.jst.j2ee.internal.common.ClasspathLibraryExpander; |
| import org.eclipse.jst.j2ee.internal.common.J2EECommonMessages; |
| import org.eclipse.jst.j2ee.internal.plugin.J2EEPlugin; |
| import org.eclipse.jst.j2ee.project.EarUtilities; |
| import org.eclipse.jst.j2ee.project.JavaEEProjectUtilities; |
| import org.eclipse.wst.common.componentcore.ComponentCore; |
| import org.eclipse.wst.common.componentcore.internal.StructureEdit; |
| import org.eclipse.wst.common.componentcore.internal.builder.IDependencyGraph; |
| import org.eclipse.wst.common.componentcore.internal.flat.IFlatFolder; |
| import org.eclipse.wst.common.componentcore.internal.flat.IFlatResource; |
| import org.eclipse.wst.common.componentcore.internal.resources.VirtualArchiveComponent; |
| import org.eclipse.wst.common.componentcore.internal.resources.VirtualReference; |
| import org.eclipse.wst.common.componentcore.resources.IVirtualComponent; |
| import org.eclipse.wst.common.componentcore.resources.IVirtualReference; |
| |
| /** |
| * This classpath container is based on the Component references; not the manifest entries. Other |
| * mechanisms are in place to ensure that the component references are updated when the manifest is |
| * updated, and also to make sure the manifest is updated when the component references are updated. |
| * |
| */ |
| public class J2EEComponentClasspathContainer implements IClasspathContainer { |
| |
| public static final String CONTAINER_ID = "org.eclipse.jst.j2ee.internal.module.container"; //$NON-NLS-1$ |
| public static final IPath CONTAINER_PATH = new Path(CONTAINER_ID); |
| |
| private static IPath WEBLIB = new Path("/WEB-INF/lib"); //$NON-NLS-1$ |
| |
| private static ClasspathDecorationsManager decorationsManager = new ClasspathDecorationsManager(J2EEPlugin.PLUGIN_ID); |
| public static ClasspathDecorationsManager getDecorationsManager() { |
| return decorationsManager; |
| } |
| |
| private static Map<String, Object> onlyManifestRefs = new HashMap<String, Object>(); |
| static { |
| onlyManifestRefs.put(IVirtualComponent.REQUESTED_REFERENCE_TYPE, J2EEModuleVirtualComponent.ONLY_MANIFEST_REFERENCES); |
| } |
| |
| private IPath containerPath; |
| private IJavaProject javaProject; |
| private IJavaProjectLite javaProjectLite; |
| private IClasspathEntry[] entries = new IClasspathEntry[0]; |
| private static Map<Integer, Integer> keys = new Hashtable<Integer, Integer>(); |
| private static int MAX_RETRIES = 10; |
| private static Map<Integer, Integer>retries = new Hashtable<Integer, Integer>(); |
| |
| static class LastUpdate implements Serializable{ |
| private static final long serialVersionUID = 362498820763181265L; |
| private boolean exportEntries = true; //the default behavior is to always export these dependencies |
| private int baseRefCount = 0; // count of references returned directly from a component |
| private int baseLibRefCount = 0; // count of references resolved by EAR 5 lib directory |
| private int refCount = 0; |
| private boolean[] isBinary = new boolean[refCount]; |
| transient private IPath[] paths = new IPath[refCount]; |
| transient boolean needToVerify = true; |
| //only used for serialization |
| private String [] pathStrings = null; |
| |
| @Override |
| public boolean equals(Object o) { |
| if(this == o){ |
| return true; |
| } else if(o == null){ |
| return false; |
| } else if (o instanceof LastUpdate){ |
| LastUpdate other = (LastUpdate)o; |
| if(this.exportEntries != other.exportEntries){ |
| return false; |
| } else if(this.baseRefCount != other.baseRefCount){ |
| return false; |
| } else if(this.baseLibRefCount != other.baseLibRefCount){ |
| return false; |
| } else if(this.refCount != other.refCount){ |
| return false; |
| } else if(this.isBinary.length != other.isBinary.length){ |
| return false; |
| } else if(this.paths.length != other.paths.length){ |
| return false; |
| } |
| for(int i=0; i<isBinary.length; i++){ |
| if(this.isBinary[i] != other.isBinary[i]){ |
| return false; |
| } |
| } |
| for(int i=0; i<paths.length; i++){ |
| if(this.paths[i] == null && other.paths[i] != null){ |
| return false; |
| } else if(!this.paths[i].equals(other.paths[i])){ |
| return false; |
| } |
| } |
| return true; |
| } else { |
| return false; |
| } |
| } |
| @Override |
| public int hashCode() { |
| return 3 * baseRefCount + 5 * baseLibRefCount + 7 * refCount + 11 * isBinary.length; |
| } |
| |
| private void writeObject(java.io.ObjectOutputStream out) throws IOException { |
| pathStrings = new String[refCount]; |
| for(int i=0;i<paths.length; i++){ |
| if(paths[i] != null){ |
| pathStrings[i] = paths[i].toString(); |
| } |
| } |
| out.defaultWriteObject(); |
| } |
| |
| private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{ |
| in.defaultReadObject(); |
| needToVerify = true; |
| paths = new IPath[refCount]; |
| for(int i=0;i<pathStrings.length; i++){ |
| if(pathStrings[i] != null){ |
| paths[i] = new Path(pathStrings[i]); |
| } |
| } |
| } |
| |
| private boolean areSame(IVirtualComponent comp, int i){ |
| if (comp.isBinary() != isBinary[i]) { |
| return false; |
| } |
| IPath path = null; |
| if (comp.isBinary()) |
| path = (IPath)comp.getAdapter(IPath.class); |
| else |
| path = comp.getProject().getFullPath(); |
| if (!path.equals(paths[i])) { |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| private LastUpdate lastUpdate = new LastUpdate(); |
| |
| public J2EEComponentClasspathContainer(IPath path, IJavaProject javaProject) { |
| this.containerPath = path; |
| this.javaProject = javaProject; |
| this.javaProjectLite = JavaCoreLite.create(javaProject); |
| } |
| |
| private boolean requiresUpdate() { |
| IVirtualComponent component = ComponentCore.createComponent(javaProjectLite.getProject()); |
| if (component == null) { |
| return false; |
| } |
| |
| IVirtualReference[] refs = component.getReferences(onlyManifestRefs); |
| |
| // avoid updating the container if references haven't changed |
| if (refs.length == lastUpdate.baseRefCount) { |
| for (int i = 0; i < lastUpdate.baseRefCount; i++) { |
| IVirtualComponent comp = null; |
| comp = refs[i].getReferencedComponent(); |
| if(!lastUpdate.areSame(comp, i)){ |
| return true; |
| } |
| } |
| List <IVirtualReference> earRefs = getBaseEARLibRefs(component); |
| if(earRefs.size() != lastUpdate.baseLibRefCount){ |
| return true; |
| } |
| List <IVirtualReference> refsList = new ArrayList<IVirtualReference>(); |
| Set <IVirtualComponent> refedComps = new HashSet<IVirtualComponent>(); |
| refedComps.add(component); |
| for(int i = 0; i<refs.length;i++){ |
| refsList.add(refs[i]); |
| refedComps.add(refs[i].getReferencedComponent()); |
| } |
| int i=lastUpdate.baseRefCount; |
| for(IVirtualReference earRef : earRefs){ |
| IVirtualComponent comp = earRef.getReferencedComponent(); |
| // check if the referenced component is already visited - avoid cycles in the build path |
| if (!refedComps.contains(comp)) { |
| if(i == lastUpdate.refCount){ |
| return true; // found something new and need update |
| } |
| // visit the referenced component |
| refsList.add(earRef); |
| refedComps.add(comp); |
| if(!lastUpdate.areSame(comp, i)){ |
| return true; |
| } |
| i++; |
| } |
| } |
| if(i!= lastUpdate.refCount){ |
| return true; // didn't find them all |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| private void update(LastUpdate restoreState) { |
| if(restoreState != null){ // performance; restore state from last session |
| lastUpdate = restoreState; |
| List <IClasspathEntry>entriesList = new ArrayList<IClasspathEntry>(); |
| for(int i=0; i<lastUpdate.paths.length; i++){ |
| if(lastUpdate.paths[i] != null){ |
| IClasspathEntry newEntry = createEntry(restoreState, i); |
| entriesList.add(newEntry); |
| } |
| } |
| entries = new IClasspathEntry[entriesList.size()]; |
| for (int i = 0; i < entries.length; i++) { |
| entries[i] = entriesList.get(i); |
| } |
| return; |
| } |
| |
| IVirtualComponent component = ComponentCore.createComponent(javaProjectLite.getProject()); |
| if (component == null) { |
| return; |
| } |
| Integer key = null; |
| if(!javaProjectLite.getProject().getFile(StructureEdit.MODULE_META_FILE_NAME).exists()){ |
| Integer hashCode = new Integer(javaProjectLite.getProject().hashCode()); |
| key = keys.get(hashCode); |
| if(key == null){ |
| keys.put(hashCode, hashCode); |
| key = hashCode; |
| } |
| Integer retryCount = retries.get(key); |
| if(retryCount == null){ |
| retryCount = new Integer(1); |
| } else if(retryCount.intValue() > MAX_RETRIES){ |
| return; |
| } else { |
| retryCount = new Integer(retryCount.intValue() + 1); |
| } |
| retries.put(key, retryCount); |
| J2EEComponentClasspathUpdater.getInstance().queueUpdate(javaProjectLite.getProject()); |
| return; |
| } |
| |
| IVirtualReference[] refs = component.getReferences(onlyManifestRefs); |
| |
| List<IVirtualReference> refsList = new ArrayList<IVirtualReference>(); |
| Set<IVirtualComponent> refedComps = new HashSet<IVirtualComponent>(); |
| refedComps.add(component); |
| for(IVirtualReference ref: refs){ |
| if(ref.getDependencyType() == IVirtualReference.DEPENDENCY_TYPE_USES){ |
| refsList.add(ref); |
| refedComps.add(ref.getReferencedComponent()); |
| } |
| } |
| lastUpdate.baseRefCount = refsList.size(); |
| |
| List <IVirtualReference> earLibReferences = getBaseEARLibRefs(component); |
| lastUpdate.baseLibRefCount = earLibReferences.size(); |
| for(IVirtualReference earRef : earLibReferences){ |
| IVirtualComponent earRefComp = earRef.getReferencedComponent(); |
| // check if the referenced component is already visited - avoid cycles in the build path |
| if (!refedComps.contains(earRefComp)) { |
| // visit the referenced component |
| refsList.add(earRef); |
| refedComps.add(earRefComp); |
| } |
| } |
| |
| //iterate with i index because this list may be augmented during iteration |
| for(int i=0; i< refsList.size(); i++){ |
| IVirtualComponent comp = refsList.get(i).getReferencedComponent(); |
| if(comp.isBinary()){ |
| IVirtualReference [] binaryRefs = comp.getReferences(); |
| for(int j = 0; j<binaryRefs.length; j++){ |
| if(!refedComps.contains(binaryRefs[j].getReferencedComponent())){ |
| refsList.add(binaryRefs[j]); |
| refedComps.add(binaryRefs[j].getReferencedComponent()); |
| } |
| } |
| } |
| } |
| |
| lastUpdate.refCount = refsList.size(); |
| lastUpdate.isBinary = new boolean[lastUpdate.refCount]; |
| lastUpdate.paths = new IPath[lastUpdate.refCount]; |
| |
| boolean isWeb = JavaEEProjectUtilities.isDynamicWebProject(component.getProject()); |
| boolean shouldAdd = true; |
| |
| List <IClasspathEntry>entriesList = new ArrayList<IClasspathEntry>(); |
| |
| try { |
| boolean useJDTToControlExport = J2EEComponentClasspathContainerUtils.getDefaultUseEARLibrariesJDTExport(); |
| if(useJDTToControlExport){ |
| //if the default is not enabled, then check whether the container is being exported |
| IClasspathEntry [] rawEntries = javaProjectLite.readRawClasspath(); |
| for(int i=0;i<rawEntries.length; i++){ |
| IClasspathEntry entry = rawEntries[i]; |
| if(entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER){ |
| if(entry.getPath().equals(CONTAINER_PATH)){ |
| lastUpdate.exportEntries = entry.isExported(); |
| break; |
| } |
| } |
| } |
| } |
| |
| IVirtualReference ref = null; |
| IVirtualComponent comp = null; |
| for (int i = 0; i < refsList.size(); i++) { |
| ref = refsList.get(i); |
| comp = ref.getReferencedComponent(); |
| lastUpdate.isBinary[i] = comp.isBinary(); |
| shouldAdd = !(isWeb && ref.getRuntimePath().equals(WEBLIB)); |
| if (!shouldAdd) { |
| continue; |
| } |
| if (lastUpdate.isBinary[i]) { |
| if( comp instanceof VirtualArchiveComponent ) { |
| VirtualArchiveComponent archiveComp = (VirtualArchiveComponent) comp; |
| if (archiveComp.getArchiveType().equals(VirtualArchiveComponent.CLASSPATHARCHIVETYPE)) { |
| // do not process components dynamically computed from the Java classpath |
| continue; |
| } |
| } |
| lastUpdate.paths[i] = (IPath)comp.getAdapter(IPath.class); |
| IClasspathEntry newEntry = createEntry(lastUpdate, i); |
| entriesList.add(newEntry); |
| } else { |
| IProject project = comp.getProject(); |
| lastUpdate.paths[i] = project.getFullPath(); |
| IClasspathEntry newEntry = createEntry(lastUpdate, i); |
| entriesList.add(newEntry); |
| } |
| } |
| } finally { |
| entries = new IClasspathEntry[entriesList.size()]; |
| for (int i = 0; i < entries.length; i++) { |
| entries[i] = entriesList.get(i); |
| } |
| J2EEComponentClasspathContainerStore.saveState(javaProjectLite.getProject().getName(), lastUpdate); |
| } |
| } |
| |
| private IClasspathEntry createEntry(LastUpdate lastUpdate, int index){ |
| if(lastUpdate.isBinary[index]){ |
| ClasspathDecorations dec = decorationsManager.getDecorations( getPath().toString(), lastUpdate.paths[index].toString() ); |
| |
| IPath srcpath = null; |
| IPath srcrootpath = null; |
| IClasspathAttribute[] attrs = {}; |
| IAccessRule[] access = {}; |
| |
| if( dec != null ) { |
| srcpath = dec.getSourceAttachmentPath(); |
| srcrootpath = dec.getSourceAttachmentRootPath(); |
| attrs = dec.getExtraAttributes(); |
| } |
| IClasspathEntry newEntry = JavaCoreLite.newLibraryEntry( lastUpdate.paths[index], srcpath, srcrootpath, access, attrs, lastUpdate.exportEntries ); |
| return newEntry; |
| } |
| IClasspathEntry newEntry = JavaCoreLite.newProjectEntry(lastUpdate.paths[index], lastUpdate.exportEntries); |
| return newEntry; |
| } |
| |
| private List<IVirtualReference> getBaseEARLibRefs(IVirtualComponent component) { |
| List <IVirtualReference> libRefs = new ArrayList<IVirtualReference>(); |
| // check for the references in the lib dirs of the referencing EARs |
| IVirtualComponent[] referencingList = component.getReferencingComponents(); |
| for (IVirtualComponent referencingComp : referencingList) { |
| // check if the referencing component is an EAR |
| if (EarUtilities.isEARProject(referencingComp.getProject())) { |
| IVirtualComponent earComp = referencingComp; |
| // retrieve the EAR's library directory |
| String libDir = EarUtilities.getEARLibDir(earComp); |
| // if the EAR version is lower than 5, then the library directory will be null |
| // or if it is the empty string, do nothing. |
| if (libDir != null && libDir.trim().length() != 0) { |
| IPath libDirPath = new Path(libDir).makeRelative(); |
| // check if the component itself is not in the library directory of this EAR - avoid cycles in the build patch |
| IVirtualReference ref = earComp.getReference(component.getName()); |
| if(ref != null){ |
| IPath refPath = ref.getRuntimePath(); |
| String archiveName = ref.getArchiveName(); |
| if(archiveName != null){ |
| // this check is needed to handle the scenario where the ref.getRuntimePath() is "/" |
| // and the archive name is "/lib/foo.jar" |
| refPath = refPath.append(archiveName); |
| if(refPath.segmentCount() > 0){ |
| refPath = refPath.removeLastSegments(1); |
| } |
| } |
| refPath = refPath.makeRelative(); |
| if (!libDirPath.equals(refPath)) { |
| // retrieve the referenced components from the EAR |
| IVirtualReference[] earRefs = earComp.getReferences(); |
| for (IVirtualReference earRef : earRefs) { |
| if(earRef.getDependencyType() == IVirtualReference.DEPENDENCY_TYPE_USES){ |
| // check if the referenced component is in the library directory |
| IPath runtimePath = earRef.getRuntimePath().makeRelative(); |
| boolean isInLibDir = libDirPath.equals(runtimePath); |
| if(!isInLibDir){ |
| IPath fullPath = earRef.getRuntimePath().append(earRef.getArchiveName()); |
| isInLibDir = fullPath.removeLastSegments(1).makeRelative().equals(libDirPath); |
| } |
| if (isInLibDir) { |
| libRefs.add(earRef); |
| } |
| } |
| } |
| } |
| //add EAR classpath container refs |
| try { |
| ClasspathLibraryExpander classpathLibExpander = new ClasspathLibraryExpander(earComp); |
| IFlatResource flatLibResource = classpathLibExpander.fetchResource(libDirPath); |
| if(flatLibResource instanceof IFlatFolder){ |
| IFlatFolder flatLibFolder = (IFlatFolder)flatLibResource; |
| IFlatResource [] flatLibs = flatLibFolder.members(); |
| for(IFlatResource flatResource : flatLibs){ |
| File file = (File) flatResource.getAdapter(File.class); |
| if(file != null){ |
| IVirtualComponent dynamicComponent = new VirtualArchiveComponent(earComp.getProject(), VirtualArchiveComponent.LIBARCHIVETYPE + "/" + file.getAbsolutePath(), new Path(libDir)); //$NON-NLS-1$ |
| IVirtualReference dynamicRef = ComponentCore.createReference(earComp, dynamicComponent); |
| ((VirtualReference)dynamicRef).setDerived(true); |
| dynamicRef.setArchiveName(file.getName()); |
| libRefs.add(dynamicRef); |
| } |
| } |
| } |
| } catch (CoreException e) { |
| J2EEPlugin.logError(e); |
| } |
| } |
| } |
| } |
| } |
| return libRefs; |
| } |
| |
| public static J2EEComponentClasspathContainer install(IPath containerPath, IJavaProject javaProject, LastUpdate restoreState) { |
| try{ |
| J2EEComponentClasspathUpdater.getInstance().pauseUpdates(); |
| final IJavaProject[] projects = new IJavaProject[]{javaProject}; |
| final J2EEComponentClasspathContainer container = new J2EEComponentClasspathContainer(containerPath, javaProject); |
| container.update(restoreState); |
| final IClasspathContainer[] conts = new IClasspathContainer[]{container}; |
| try { |
| JavaCore.setClasspathContainer(containerPath, projects, conts, null); |
| } catch (JavaModelException e) { |
| J2EEPlugin.logError(e); |
| } |
| return container; |
| } finally { |
| J2EEComponentClasspathUpdater.getInstance().resumeUpdates(); |
| } |
| } |
| |
| public static void install(final IPath containerPath, final IJavaProject javaProject) { |
| final String projectName = javaProject.getProject().getName(); |
| LastUpdate restoreState = J2EEComponentClasspathContainerStore.getRestoreState(projectName); |
| boolean needToVerify = false; |
| if(null != restoreState){ |
| synchronized (restoreState) { |
| needToVerify = restoreState.needToVerify; |
| restoreState.needToVerify = false; |
| } |
| } |
| final J2EEComponentClasspathContainer container = install(containerPath, javaProject, restoreState); |
| if(needToVerify){ |
| Job verifyJob = new Job(Messages.J2EEComponentClasspathUpdater_Verify_EAR_Libraries){ |
| @Override |
| protected IStatus run(IProgressMonitor monitor) { |
| container.refresh(); |
| return Status.OK_STATUS; |
| } |
| }; |
| verifyJob.setSystem(true); |
| verifyJob.setRule(ResourcesPlugin.getWorkspace().getRoot()); |
| verifyJob.schedule(); |
| } |
| } |
| |
| public void refresh(boolean force){ |
| if (!force) { |
| 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(javaProject.getProject()); |
| return; |
| } |
| } |
| if(force || requiresUpdate()){ |
| install(containerPath, javaProject, null); |
| } |
| } |
| |
| public void refresh() { |
| refresh(false); |
| } |
| |
| public IClasspathEntry[] getClasspathEntries() { |
| return entries; |
| } |
| |
| public String getDescription() { |
| return J2EECommonMessages.J2EE_MODULE_CLASSPATH_CONTAINER_NAME; |
| } |
| |
| public int getKind() { |
| return K_APPLICATION; |
| } |
| |
| public IPath getPath() { |
| return containerPath; |
| } |
| |
| } |