Bug 375834 - Content of Classpath Containers not persistet on project close
Summary: Content of Classpath Containers not persistet on project close
Status: VERIFIED NOT_ECLIPSE
Alias: None
Product: JDT
Classification: Eclipse Project
Component: Core (show other bugs)
Version: 3.8   Edit
Hardware: PC Windows XP
: P3 major (vote)
Target Milestone: 3.8 M7   Edit
Assignee: JDT-Core-Inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords: needinfo
Depends on:
Blocks:
 
Reported: 2012-04-02 08:14 EDT by Roland Haas CLA
Modified: 2012-05-02 02:13 EDT (History)
5 users (show)

See Also:


Attachments
The classpath containers and its content. (23.80 KB, image/png)
2012-04-02 08:16 EDT, Roland Haas CLA
no flags Details
Screenshots and other files (115.55 KB, application/octet-stream)
2012-04-10 08:18 EDT, Roland Haas CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Roland Haas CLA 2012-04-02 08:14:19 EDT
Build Identifier: Version: 3.7.0, Build id: I20110613-1736

Our company uses a self developed eclipse plugin for resolving maven dependencies. For each maven scope we define an own classpath container. 

The containers are defined using the standard extension points and implementations of ClasspathContainerInitializer and IClasspathContainer.
The content of the containers is set using JavaCore.setClasspathContainer(...).

Now we're faced with the problem that the content is not properly restored when a project is closed and afterwards opened again (withouth closing eclipse). When eclipse is closed, the content is sometimes restored for some projects, but not for all projects.

When looking into the .metadata\.plugins\org.eclipse.jdt.core\variablesAndContainers.dat file within the workspace we observed that the content of the classpath containers is available for some of the projects, but not for all of them.
We tried to force an update by using IJavaProject.save() or IJavaProject.makeConsistent().



Reproducible: Always

Steps to Reproduce:
1. use a plugin which defines own classpath containers and fill these containers
2. close the project
3. open the project
4. validate the content of the classpath container (the container itself is available, but not the content).
Comment 1 Roland Haas CLA 2012-04-02 08:16:06 EDT
Created attachment 213445 [details]
The classpath containers and its content.
Comment 2 Srikanth Sankaran CLA 2012-04-03 00:30:56 EDT
Jay, please take a look.
Comment 3 Jay Arthanareeswaran CLA 2012-04-03 07:34:07 EDT
Roland, 

Do you notice anything in the log file? It's here: <workspace_loc>/.metadata/.log

It would also help if you let us know what you find after running with the following debug options:

org.eclipse.jdt.core/debug/cpresolution=true
org.eclipse.jdt.core/debug/cpresolution/advanced=true
org.eclipse.jdt.core/debug/cpresolution/failure=true
Comment 4 Roland Haas CLA 2012-04-10 08:17:38 EDT
Hi Jay

Sorry for my late reply.

I did another test with the options turned on, but the log doesn't contain any special information regarding my problem.

I added a zip-file with some additional information from my test. Hopefully they're useful.

1.) I started eclipse with a new and clean workspace
2.) I import one single project and resolve the maven dependencies which generates the classpath containers as shown in "after_import_and_resolve.png" -> the full build shows no errors
3.) I close the project using "close project"
4.) I open the project using "open project"
5.) The project doesn't compile anymore since the classpath containers are empty (they're available within the project's property (empty), but not visible in the project tree on the left side) -> see "after_close_open.png"
6.) As visual check of "variablesAndContainers.dat" shows that most of the information is persisted and therefore available.

All information is also attached within the zip file.

Ps. I realized that the official m2plugin seems to have this problem too. I found a workaround for the problem. Within the classpath initializer the m2plugin restores a previously saved classpath container content. Any change of the classpath container forces a save to the local drive within the workspace. The m2plugin uses a simple object serialization for the storage.

Cheers 
Roland
Comment 5 Roland Haas CLA 2012-04-10 08:18:16 EDT
Created attachment 213798 [details]
Screenshots and other files
Comment 6 Jay Arthanareeswaran CLA 2012-04-11 14:01:01 EDT
Thanks for the update, Roland. Looking at the buildpath page where the dependencies look to be resolved, it appears that the persisted containers are indeed restored. But something seem to be wrong with the package explorer tree. Looking at the UI code, I understand we use the same code (IClasspathContainer#getClasspathEntries) to get the entries. Not sure what the problem is. 

Moving to JDT/UI for further investigation.
Comment 7 Roland Haas CLA 2012-04-11 14:23:27 EDT
Hi Jay

You got me wrong: 
As you can see in the screenshot after_close_open.png within the attached zip-file. The containers itself are restored since they are defined within the .classpath settings file. But the content is definitely not restored (therefore I expanded each defined folder).
It's a standard behaviour of the package explorer that only classpath containers with at least one classpath entry are displayed. In the project's buildpath page you always see all defined conainers.
Another indicator that the content is not restored is that the project itself is not compilable. None of the import statements can be resolved. It's definitely not a problem of the package view.

Addition to the variablesAndContainers.dat:
This file is often out of sync. When I looked into it while doing a test with multiple projects I observed that for some projects the content of the containers are persisted in this file, but other projects aren't. Interestingly none of the projects can be restored after a close and open operation (independent of the content of the variablesAndContainers.dat).

The fact that even the m2plugin has an own workaround for restoring the content of the classpath containers is a strong indicator that there seems to be a problem within Eclipse JDT itself.

Cheers
Roland
Comment 8 Deepak Azad CLA 2012-04-11 14:54:05 EDT
(In reply to comment #4)
> Ps. I realized that the official m2plugin seems to have this problem too. I
> found a workaround for the problem. Within the classpath initializer the
> m2plugin restores a previously saved classpath container content. 
Do you refer to org.eclipse.jdt.core.ClasspathContainerInitializer.requestClasspathContainerUpdate(IPath, IJavaProject, IClasspathContainer)? Note that by default it does not do anything, each classpath container implementation needs to take care of this. 

(In reply to comment #6)
> Thanks for the update, Roland. Looking at the buildpath page where the
> dependencies look to be resolved, it appears that the persisted containers are
> indeed restored. But something seem to be wrong with the package explorer tree.
> Looking at the UI code, I understand we use the same code
> (IClasspathContainer#getClasspathEntries) to get the entries. 
> Not sure what the
> problem is. 
> 
> Moving to JDT/UI for further investigation.

Jay, all the API's mentioned here JDT/Core APIs. I fail to see how this is a UI bug.
Comment 9 Roland Haas CLA 2012-04-12 01:12:54 EDT
(In reply to comment #8)
> (In reply to comment #4)
> > Ps. I realized that the official m2plugin seems to have this problem too. I
> > found a workaround for the problem. Within the classpath initializer the
> > m2plugin restores a previously saved classpath container content. 
> Do you refer to
> org.eclipse.jdt.core.ClasspathContainerInitializer.requestClasspathContainerUpdate(IPath,
> IJavaProject, IClasspathContainer)? Note that by default it does not do
> anything, each classpath container implementation needs to take care of this. 
> 

True, but setting a break point at any method in this abstract class shows you that the content of the container is not delivered by Eclipse JDT. The content of the containers is always empty. So therefore you're forced to implement an own persistency layer which saves and loads the content of the classpath containers from and to the disk (or somewhere else).


I attached some snippets of the m2plugin's workaround. 

/**
 * MavenClasspathContainerInitializer
 * 
 * @author Eugene Kuleshov
 */
public class MavenClasspathContainerInitializer extends ClasspathContainerInitializer {
  private static final Logger log = LoggerFactory.getLogger(MavenClasspathContainerInitializer.class);

  public void initialize(IPath containerPath, IJavaProject project) {
    if(MavenClasspathHelpers.isMaven2ClasspathContainer(containerPath)) {
      try {
        IClasspathContainer mavenContainer = getBuildPathManager().getSavedContainer(project.getProject());
        if(mavenContainer != null) {
          JavaCore.setClasspathContainer(containerPath, new IJavaProject[] {project},
              new IClasspathContainer[] {mavenContainer}, new NullProgressMonitor());
          return;
        }
      } catch(CoreException ex) {
        log.error("Exception initializing classpath container " + containerPath.toString(), ex);
      }

      // force refresh if can't read persisted state
      IMavenConfiguration configuration = MavenPlugin.getMavenConfiguration();
      MavenUpdateRequest request = new MavenUpdateRequest(project.getProject(), configuration.isOffline(), false);
      getMavenProjectManager().refresh(request);
    }
  }

  public boolean canUpdateClasspathContainer(IPath containerPath, IJavaProject project) {
    return true;
  }

  public void requestClasspathContainerUpdate(IPath containerPath, final IJavaProject project,
      final IClasspathContainer containerSuggestion) {
    // one job per request. assumption that users are not going to change hundreds of containers simultaneously.
    new Job(Messages.MavenClasspathContainerInitializer_job_name) {
      protected IStatus run(IProgressMonitor monitor) {
        try {
          getBuildPathManager().persistAttachedSourcesAndJavadoc(project, containerSuggestion, monitor);
        } catch(CoreException ex) {
          log.error(ex.getMessage(), ex);
          return new Status(IStatus.ERROR, IMavenConstants.PLUGIN_ID, 0, Messages.MavenClasspathContainerInitializer_error_cannot_persist, ex);
        }
        return Status.OK_STATUS;
      }
    }.schedule();
  }

  BuildPathManager getBuildPathManager() {
    return (BuildPathManager) MavenJdtPlugin.getDefault().getBuildpathManager();
  }

  IMavenProjectRegistry getMavenProjectManager() {
    return MavenPlugin.getMavenProjectRegistry();
  }
}

----
  public IClasspathContainer getSavedContainer(IProject project) throws CoreException {
    File containerStateFile = getContainerStateFile(project);
    if(!containerStateFile.exists()) {
      return null;
    }

    FileInputStream is = null;
    try {
      is = new FileInputStream(containerStateFile);
      return new MavenClasspathContainerSaveHelper().readContainer(is);
    } catch(IOException ex) {
      throw new CoreException(new Status(IStatus.ERROR, MavenJdtPlugin.PLUGIN_ID, -1, //
          "Can't read classpath container state for " + project.getName(), ex));
    } catch(ClassNotFoundException ex) {
      throw new CoreException(new Status(IStatus.ERROR, MavenJdtPlugin.PLUGIN_ID, -1, //
          "Can't read classpath container state for " + project.getName(), ex));
    } finally {
      if(is != null) {
        try {
          is.close();
        } catch(IOException ex) {
          log.error("Can't close output stream for " + containerStateFile.getAbsolutePath(), ex); //$NON-NLS-1$
        }
      }
    }
  }

---
  public IClasspathContainer readContainer(InputStream input) throws IOException, ClassNotFoundException {
    ObjectInputStream is = new ObjectInputStream(new BufferedInputStream(input)) {
      {
        enableResolveObject(true);
      }
      protected Object resolveObject(Object o) throws IOException {
        if(o instanceof ProjectEntryReplace) {
          return ((ProjectEntryReplace) o).getEntry();
        } else if(o instanceof LibraryEntryReplace) {
          return ((LibraryEntryReplace) o).getEntry();
        } else if(o instanceof ClasspathAttributeReplace) {
          return ((ClasspathAttributeReplace) o).getAttribute();
        } else if(o instanceof AccessRuleReplace) {
          return ((AccessRuleReplace) o).getAccessRule();
        } else if(o instanceof PathReplace) {
          return ((PathReplace) o).getPath();
        }
        return super.resolveObject(o);
      }
    };
    return (IClasspathContainer) is.readObject();
  }
Comment 10 Deepak Azad CLA 2012-04-12 01:26:37 EDT
(In reply to comment #9)
> True, but setting a break point at any method in this abstract class shows you
> that the content of the container is not delivered by Eclipse JDT. The content
> of the containers is always empty. So therefore you're forced to implement an
> own persistency layer which saves and loads the content of the classpath
> containers from and to the disk (or somewhere else).

Right, so the bug is in the code of your custom classpath container, no? Or are you asking for a default implementation of ClasspathContainerInitializer.requestClasspathContainerUpdate(..) ?
Comment 11 Dani Megert CLA 2012-04-12 03:43:48 EDT
I agree that this looks like a bug in the container. Roland, if you disagree, please attach an example / test case that allows us to reproduce the problem.
Comment 12 Jay Arthanareeswaran CLA 2012-05-02 02:13:41 EDT
Verified for 3.8 M7