/******************************************************************************* * Copyright (c) 2003, 2012 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 path 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(); boolean onlyBinary = false; // If this component is in the library directory, we will allow only binary entries to be // added, to avoid cycles in the build path if (libDirPath.equals(refPath)) { onlyBinary = true; } // 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 && earRef.getArchiveName() != null){ IPath fullPath = earRef.getRuntimePath().append(earRef.getArchiveName()); isInLibDir = fullPath.removeLastSegments(1).makeRelative().equals(libDirPath); } if (isInLibDir) { if (!onlyBinary || (onlyBinary && earRef.getReferencedComponent().isBinary())) 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; } }