Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbvosburgh2009-10-28 17:46:10 +0000
committerbvosburgh2009-10-28 17:46:10 +0000
commit267dda422e19750a8746f265dc0d6469e3aac014 (patch)
tree0afb6f533e2c7fed4907a5d2eeeb479661fdbce1 /jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/GenericJpaModel.java
parentb9beac12764ad3ce59b39a98fc7a0665e8af36f4 (diff)
downloadwebtools.dali-267dda422e19750a8746f265dc0d6469e3aac014.tar.gz
webtools.dali-267dda422e19750a8746f265dc0d6469e3aac014.tar.xz
webtools.dali-267dda422e19750a8746f265dc0d6469e3aac014.zip
rework JPA project locking
Diffstat (limited to 'jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/GenericJpaModel.java')
-rw-r--r--jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/GenericJpaModel.java1076
1 files changed, 1076 insertions, 0 deletions
diff --git a/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/GenericJpaModel.java b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/GenericJpaModel.java
new file mode 100644
index 0000000000..913b48d54d
--- /dev/null
+++ b/jpa/plugins/org.eclipse.jpt.core/src/org/eclipse/jpt/core/GenericJpaModel.java
@@ -0,0 +1,1076 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2009 Oracle. 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:
+ * Oracle - initial API and implementation
+ ******************************************************************************/
+package org.eclipse.jpt.core;
+
+import java.util.Vector;
+
+import org.eclipse.core.commands.ExecutionException;
+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.IResourceProxy;
+import org.eclipse.core.resources.IResourceProxyVisitor;
+import org.eclipse.core.resources.IWorkspace;
+import org.eclipse.core.resources.IncrementalProjectBuilder;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.jobs.IJobManager;
+import org.eclipse.core.runtime.jobs.ILock;
+import org.eclipse.core.runtime.jobs.Job;
+import org.eclipse.jdt.core.ElementChangedEvent;
+import org.eclipse.jdt.core.IElementChangedListener;
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jpt.core.internal.AsynchronousJpaProjectUpdater;
+import org.eclipse.jpt.core.internal.JptCoreMessages;
+import org.eclipse.jpt.core.internal.SimpleJpaProjectConfig;
+import org.eclipse.jpt.core.internal.facet.JpaFacetInstallDataModelProperties;
+import org.eclipse.jpt.core.internal.operations.JpaFileCreationDataModelProperties;
+import org.eclipse.jpt.core.internal.operations.OrmFileCreationDataModelProvider;
+import org.eclipse.jpt.core.internal.operations.PersistenceFileCreationDataModelProvider;
+import org.eclipse.jpt.utility.internal.AsynchronousCommandExecutor;
+import org.eclipse.jpt.utility.internal.BitTools;
+import org.eclipse.jpt.utility.internal.CallbackStatefulCommandExecutor;
+import org.eclipse.jpt.utility.internal.SimpleCommandExecutor;
+import org.eclipse.jpt.utility.internal.StringTools;
+import org.eclipse.jpt.utility.internal.SynchronizedBoolean;
+import org.eclipse.jpt.utility.internal.iterables.LiveCloneIterable;
+import org.eclipse.jpt.utility.internal.model.AbstractModel;
+import org.eclipse.wst.common.frameworks.datamodel.DataModelFactory;
+import org.eclipse.wst.common.frameworks.datamodel.IDataModel;
+import org.eclipse.wst.common.project.facet.core.FacetedProjectFramework;
+import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectEvent;
+import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectListener;
+import org.eclipse.wst.common.project.facet.core.events.IProjectFacetActionEvent;
+
+/**
+ * The JPA model maintains a list of all JPA projects in the workspace.
+ * It keeps the list (and the state of the JPA projects themselves)
+ * synchronized with the workspace by listening for various
+ * changes:<ul>
+ * <li>Resource
+ * <li>Java
+ * <li>Faceted Project
+ * </ul>
+ * We use an Eclipse {@link ILock lock} to synchronize access to the JPA
+ * projects when dealing with these events. In an effort to reduce deadlocks,
+ * the simple Resource and Java change events are dispatched to a background
+ * thread, allowing us to handle the events outside of the workspace lock held
+ * during resource and Java change notifications.
+ * <p>
+ * Events that trigger either the adding or removing of a JPA project (e.g.
+ * {@link IFacetedProjectEvent.Type#POST_INSTALL}) are handled "synchronously"
+ * by allowing the background thread to handle any outstanding events before
+ * updating the list of JPA projects and returning execution to the event
+ * source.
+ * <p>
+ * Various things that cause us to add or remove a JPA project:<ul>
+ * <li>Startup of the Dali plug-in will trigger all the pre-existing JPA
+ * projects to be added
+ *
+ * <li>Project created and facet installed<p>
+ * {@link IFacetedProjectEvent.Type#POST_INSTALL}
+ * <li>Project facet uninstalled<p>
+ * {@link IFacetedProjectEvent.Type#PRE_UNINSTALL}
+ *
+ * <li>Project opened<p>
+ * {@link IFacetedProjectEvent.Type#PROJECT_MODIFIED}
+ * <li>Project closed<p>
+ * {@link IFacetedProjectEvent.Type#PROJECT_MODIFIED}
+ *
+ * <li>Pre-existing project imported from directory or archive (created and opened)<p>
+ * {@link IResourceChangeEvent#POST_CHANGE}
+ * -> {@link IResource#PROJECT}
+ * -> {@link IResourceDelta#ADDED}
+ * -> {@link IResourceDelta#OPEN}
+ * <li>Project renamed<p>
+ * {@link IResourceChangeEvent#POST_CHANGE}
+ * -> {@link IResource#PROJECT}
+ * -> {@link IResourceDelta#ADDED}
+ * -> {@link IResourceDelta#MOVED_FROM}
+ * <li>Project deleted<p>
+ * {@link IResourceChangeEvent#PRE_DELETE}
+ *
+ * <li>Project facet installed by editing the facets settings file directly<p>
+ * {@link IFacetedProjectEvent.Type#PROJECT_MODIFIED}
+ * <li>Project facet uninstalled by editing the facets settings file directly<p>
+ * {@link IFacetedProjectEvent.Type#PROJECT_MODIFIED}
+ * </ul>
+ */
+class GenericJpaModel
+ extends AbstractModel
+ implements JpaModel
+{
+ /**
+ * All the JPA projects in the workspace.
+ */
+ private final Vector<JpaProject> jpaProjects = new Vector<JpaProject>();
+
+ /**
+ * Synchronize access to the JPA projects.
+ */
+ /* private */ final ILock lock = this.getJobManager().newLock();
+
+ /**
+ * Determine how Resource and Java change events are
+ * handled (i.e. synchronously or asynchronously).
+ */
+ private volatile CallbackStatefulCommandExecutor eventHandler = new AsynchronousCommandExecutor(JptCoreMessages.DALI_EVENT_HANDLER_THREAD_NAME);
+
+ /**
+ * Listen for<ul>
+ * <li>changes to projects and files
+ * <li>deleted projects
+ * <li>clean builds
+ * </ul>
+ */
+ private final IResourceChangeListener resourceChangeListener = new ResourceChangeListener();
+
+ /**
+ * The types of resource change events that interest
+ * {@link #resourceChangeListener}.
+ */
+ private static final int RESOURCE_CHANGE_EVENT_TYPES =
+ IResourceChangeEvent.PRE_DELETE |
+ IResourceChangeEvent.POST_CHANGE |
+ IResourceChangeEvent.POST_BUILD;
+
+ /**
+ * Listen for the JPA facet being added to or removed from a "faceted" project.
+ */
+ private final IFacetedProjectListener facetedProjectListener = new FacetedProjectListener();
+
+ /**
+ * Listen for Java changes (unless the Dali UI is active).
+ * @see #javaElementChangeListenerIsActive
+ */
+ private final JavaElementChangeListener javaElementChangeListener = new JavaElementChangeListener();
+
+
+ // ********** constructor **********
+
+ /**
+ * internal - called by the Dali plug-in
+ */
+ GenericJpaModel() {
+ super();
+ }
+
+
+ // ********** plug-in controlled life-cycle **********
+
+ /**
+ * internal - called by the Dali plug-in
+ */
+ void start() {
+ try {
+ this.lock.acquire();
+ this.start_();
+ } finally {
+ this.lock.release();
+ }
+ }
+
+ private void start_() {
+ debug("*** JPA model START ***"); //$NON-NLS-1$
+ try {
+ this.buildJpaProjects();
+ this.eventHandler.start();
+ this.getWorkspace().addResourceChangeListener(this.resourceChangeListener, RESOURCE_CHANGE_EVENT_TYPES);
+ FacetedProjectFramework.addListener(this.facetedProjectListener, IFacetedProjectEvent.Type.values());
+ JavaCore.addElementChangedListener(this.javaElementChangeListener);
+ } catch (RuntimeException ex) {
+ JptCorePlugin.log(ex);
+ this.stop_();
+ }
+ }
+
+ private void buildJpaProjects() {
+ try {
+ this.buildJpaProjects_();
+ } catch (CoreException ex) {
+ // our visitor does not throw any exceptions
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private void buildJpaProjects_() throws CoreException {
+ this.getWorkspace().getRoot().accept(new ResourceProxyVisitor(), IResource.NONE);
+ }
+
+ /**
+ * internal - called by the Dali plug-in
+ */
+ void stop() throws Exception {
+ try {
+ this.lock.acquire();
+ this.stop_();
+ } finally {
+ this.lock.release();
+ }
+ }
+
+ private void stop_() {
+ debug("*** JPA model STOP ***"); //$NON-NLS-1$
+ JavaCore.removeElementChangedListener(this.javaElementChangeListener);
+ FacetedProjectFramework.removeListener(this.facetedProjectListener);
+ this.getWorkspace().removeResourceChangeListener(this.resourceChangeListener);
+ this.eventHandler.stop();
+ this.clearJpaProjects();
+ }
+
+ private void clearJpaProjects() {
+ // clone to prevent concurrent modification exceptions
+ for (JpaProject jpaProject : this.getJpaProjects_()) {
+ this.removeJpaProject(jpaProject);
+ }
+ }
+
+
+ // ********** JpaModel implementation **********
+
+ public Iterable<JpaProject> getJpaProjects() {
+ try {
+ this.lock.acquire();
+ return this.getJpaProjects_();
+ } finally {
+ this.lock.release();
+ }
+ }
+
+ private Iterable<JpaProject> getJpaProjects_() {
+ return new LiveCloneIterable<JpaProject>(this.jpaProjects);
+ }
+
+ public int getJpaProjectsSize() {
+ return this.jpaProjects.size();
+ }
+
+ public JpaProject getJpaProject(IProject project) {
+ try {
+ this.lock.acquire();
+ return this.getJpaProject_(project);
+ } finally {
+ this.lock.release();
+ }
+ }
+
+ private JpaProject getJpaProject_(IProject project) {
+ for (JpaProject jpaProject : this.jpaProjects) {
+ if (jpaProject.getProject().equals(project)) {
+ return jpaProject;
+ }
+ }
+ return null;
+ }
+
+ public JpaFile getJpaFile(IFile file) {
+ JpaProject jpaProject = this.getJpaProject(file.getProject());
+ return (jpaProject == null) ? null : jpaProject.getJpaFile(file);
+ }
+
+ public void rebuildJpaProject(IProject project) {
+ try {
+ this.lock.acquire();
+ this.rebuildJpaProject_(project);
+ } finally {
+ this.lock.release();
+ }
+ }
+
+ /**
+ * assumption: the JPA project holder exists
+ */
+ private void rebuildJpaProject_(IProject project) {
+ this.removeJpaProject(this.getJpaProject_(project));
+ this.addJpaProject(project);
+ }
+
+ public boolean javaElementChangeListenerIsActive() {
+ return this.javaElementChangeListener.isActive();
+ }
+
+ public void setJavaElementChangeListenerIsActive(boolean javaElementChangeListenerIsActive) {
+ this.javaElementChangeListener.setActive(javaElementChangeListenerIsActive);
+ }
+
+ public IWorkspace getWorkspace() {
+ return ResourcesPlugin.getWorkspace();
+ }
+
+ public IJobManager getJobManager() {
+ return Job.getJobManager();
+ }
+
+
+ // ********** adding/removing JPA projects **********
+
+ /* private */ void addJpaProject(IProject project) {
+ this.addJpaProject(this.buildJpaProject(project));
+ }
+
+ private void addJpaProject(JpaProject jpaProject) {
+ // figure out exactly when JPA projects are added
+ dumpStackTrace("add: ", jpaProject); //$NON-NLS-1$
+ // the jpa project can be null if we have problems getting the jpa platform
+ if (jpaProject != null) {
+ this.addItemToCollection(jpaProject, this.jpaProjects, JPA_PROJECTS_COLLECTION);
+ }
+ }
+
+ private JpaProject buildJpaProject(IProject project) {
+ return this.buildJpaProject(this.buildJpaProjectConfig(project));
+ }
+
+ private JpaProject buildJpaProject(JpaProject.Config config) {
+ JpaPlatform jpaPlatform = config.getJpaPlatform();
+ if (jpaPlatform == null) {
+ return null;
+ }
+ JpaProject result = jpaPlatform.getJpaFactory().buildJpaProject(config);
+ result.setUpdater(new AsynchronousJpaProjectUpdater(result));
+ return result;
+ }
+
+ private JpaProject.Config buildJpaProjectConfig(IProject project) {
+ SimpleJpaProjectConfig config = new SimpleJpaProjectConfig();
+ config.setProject(project);
+ config.setJpaPlatform(JptCorePlugin.getJpaPlatform(project));
+ config.setConnectionProfileName(JptCorePlugin.getConnectionProfileName(project));
+ config.setUserOverrideDefaultCatalog(JptCorePlugin.getUserOverrideDefaultCatalog(project));
+ config.setUserOverrideDefaultSchema(JptCorePlugin.getUserOverrideDefaultSchema(project));
+ config.setDiscoverAnnotatedClasses(JptCorePlugin.discoverAnnotatedClasses(project));
+ config.setMetamodelSourceFolderName(JptCorePlugin.getMetamodelSourceFolderName(project));
+ return config;
+ }
+
+ private void removeJpaProject(JpaProject jpaProject) {
+ // figure out exactly when JPA projects are removed
+ dumpStackTrace("remove: ", jpaProject); //$NON-NLS-1$
+ this.removeItemFromCollection(jpaProject, this.jpaProjects, JPA_PROJECTS_COLLECTION);
+ jpaProject.dispose();
+ }
+
+
+ // ********** Project POST_CHANGE **********
+
+ /**
+ * Forward the specified resource delta to all our JPA projects;
+ * they will each determine whether the event is significant.
+ */
+ /* private */ void projectChanged(IResourceDelta delta) {
+ this.eventHandler.execute(this.buildProjectChangedCommand(delta));
+ }
+
+ private Command buildProjectChangedCommand(final IResourceDelta delta) {
+ return new Command() {
+ @Override
+ void execute_() {
+ GenericJpaModel.this.projectChanged_(delta);
+ }
+ };
+ }
+
+ /* private */ void projectChanged_(IResourceDelta delta) {
+ for (JpaProject jpaProject : this.jpaProjects) {
+ jpaProject.projectChanged(delta);
+ }
+ }
+
+
+ // ********** Project POST_BUILD (CLEAN_BUILD) **********
+
+ /* private */ void projectPostCleanBuild(IProject project) {
+ this.waitForExecution(this.buildProjectPostCleanBuildCommand(project));
+ }
+
+ private Command buildProjectPostCleanBuildCommand(final IProject project) {
+ return new Command() {
+ @Override
+ void execute_() {
+ GenericJpaModel.this.projectPostCleanBuild_(project);
+ }
+ };
+ }
+
+ /* private */ void projectPostCleanBuild_(IProject project) {
+ JpaProject jpaProject = this.getJpaProject_(project);
+ if (jpaProject != null) {
+ this.removeJpaProject(jpaProject);
+ this.addJpaProject(project);
+ }
+ }
+
+
+ // ********** Project PRE_DELETE **********
+
+ /**
+ * A project is being deleted. Remove its corresponding
+ * JPA project if appropriate.
+ */
+ /* private */ void projectPreDelete(IProject project) {
+ this.waitForExecution(this.buildProjectPreDeleteCommand(project));
+ }
+
+ private Command buildProjectPreDeleteCommand(final IProject project) {
+ return new Command() {
+ @Override
+ void execute_() {
+ GenericJpaModel.this.projectPreDelete_(project);
+ }
+ };
+ }
+
+ /* private */ void projectPreDelete_(IProject project) {
+ JpaProject jpaProject = this.getJpaProject(project);
+ if (jpaProject != null) {
+ this.removeJpaProject(jpaProject);
+ }
+ }
+
+
+ // ********** Resource and/or Facet events **********
+
+ /**
+ * Check whether the JPA facet has been added or removed.
+ */
+ /* private */ void checkForJpaFacetTransition(IProject project) {
+ this.waitForExecution(this.buildCheckForJpaFacetTransitionCommand(project));
+ }
+
+ private Command buildCheckForJpaFacetTransitionCommand(final IProject project) {
+ return new Command() {
+ @Override
+ void execute_() {
+ GenericJpaModel.this.checkForJpaFacetTransition_(project);
+ }
+ };
+ }
+
+ /* private */ void checkForJpaFacetTransition_(IProject project) {
+ JpaProject jpaProject = this.getJpaProject_(project);
+
+ if (JptCorePlugin.projectHasJpaFacet(project)) {
+ if (jpaProject == null) { // JPA facet added
+ this.addJpaProject(project);
+ }
+ } else {
+ if (jpaProject != null) { // JPA facet removed
+ this.removeJpaProject(jpaProject);
+ }
+ }
+ }
+
+
+ // ********** FacetedProject POST_INSTALL **********
+
+ /* private */ void jpaFacetedProjectPostInstall(IProjectFacetActionEvent event) {
+ this.waitForExecution(this.buildJpaFacetedProjectPostInstallCommand(event));
+ }
+
+ private Command buildJpaFacetedProjectPostInstallCommand(final IProjectFacetActionEvent event) {
+ return new Command() {
+ @Override
+ void execute_() {
+ GenericJpaModel.this.jpaFacetedProjectPostInstall_(event);
+ }
+ };
+ }
+
+ /* private */ void jpaFacetedProjectPostInstall_(IProjectFacetActionEvent event) {
+ IProject project = event.getProject().getProject();
+ IDataModel dataModel = (IDataModel) event.getActionConfig();
+
+ // assume(?) this is the first event to indicate we need to add the JPA project to the JPA model
+ this.addJpaProject(project);
+
+ boolean buildOrmXml = dataModel.getBooleanProperty(JpaFacetInstallDataModelProperties.CREATE_ORM_XML);
+ this.createProjectXml(project, buildOrmXml);
+ }
+
+ private void createProjectXml(IProject project, boolean buildOrmXml) {
+ this.createPersistenceXml(project);
+
+ if (buildOrmXml) {
+ this.createOrmXml(project);
+ }
+ }
+
+ private void createPersistenceXml(IProject project) {
+ IDataModel config = DataModelFactory.createDataModel(new PersistenceFileCreationDataModelProvider());
+ config.setProperty(JpaFileCreationDataModelProperties.PROJECT_NAME, project.getName());
+ // default values for all other properties should suffice
+ try {
+ config.getDefaultOperation().execute(null, null);
+ } catch (ExecutionException ex) {
+ JptCorePlugin.log(ex);
+ }
+ }
+
+ private void createOrmXml(IProject project) {
+ IDataModel config = DataModelFactory.createDataModel(new OrmFileCreationDataModelProvider());
+ config.setProperty(JpaFileCreationDataModelProperties.PROJECT_NAME, project.getName());
+ // default values for all other properties should suffice
+ try {
+ config.getDefaultOperation().execute(null, null);
+ } catch (ExecutionException ex) {
+ JptCorePlugin.log(ex);
+ }
+ }
+
+
+ // ********** FacetedProject PRE_UNINSTALL **********
+
+ /* private */ void jpaFacetedProjectPreUninstall(IProjectFacetActionEvent event) {
+ IProject project = event.getProject().getProject();
+ this.waitForExecution(this.buildJpaFacetedProjectPreUninstallCommand(project));
+ }
+
+ private Command buildJpaFacetedProjectPreUninstallCommand(final IProject project) {
+ return new Command() {
+ @Override
+ void execute_() {
+ GenericJpaModel.this.jpaFacetedProjectPreUninstall_(project);
+ }
+ };
+ }
+
+ /* private */ void jpaFacetedProjectPreUninstall_(IProject project) {
+ // assume(?) this is the first event to indicate we need to remove the JPA project to the JPA model
+ this.removeJpaProject(this.getJpaProject_(project));
+ }
+
+
+ // ********** Java element changed **********
+
+ /**
+ * Forward the Java element changed event to all the JPA projects
+ * because the event could affect multiple projects.
+ */
+ /* private */ void javaElementChanged(ElementChangedEvent event) {
+ this.eventHandler.execute(this.buildJavaElementChangedCommand(event));
+ }
+
+ private Command buildJavaElementChangedCommand(final ElementChangedEvent event) {
+ return new Command() {
+ @Override
+ void execute_() {
+ GenericJpaModel.this.javaElementChanged_(event);
+ }
+ };
+ }
+
+ /* private */ void javaElementChanged_(ElementChangedEvent event) {
+ for (JpaProject jpaProject : this.jpaProjects) {
+ jpaProject.javaElementChanged(event);
+ }
+ }
+
+
+ // ********** miscellaneous **********
+
+ @Override
+ public void toString(StringBuilder sb) {
+ sb.append(this.jpaProjects);
+ }
+
+
+ // ********** event handler **********
+
+ private void waitForExecution(Command command) {
+ SynchronizedBoolean flag = new SynchronizedBoolean(false);
+ org.eclipse.jpt.utility.Command markerCommand = this.buildMarkerCommand();
+ EventHandlerListener listener = new EventHandlerListener(markerCommand, flag);
+ this.eventHandler.addListener(listener);
+ this.eventHandler.execute(markerCommand);
+ try {
+ flag.waitUntilTrue();
+ } catch (InterruptedException ex) {
+ // ignore - not sure why this thread would be interrupted
+ }
+ this.eventHandler.removeListener(listener);
+ command.execute();
+ }
+
+ private org.eclipse.jpt.utility.Command buildMarkerCommand() {
+ return new org.eclipse.jpt.utility.Command() {
+ public void execute() {
+ // do nothing
+ }
+ };
+ }
+
+ private static class EventHandlerListener implements CallbackStatefulCommandExecutor.Listener {
+ private final org.eclipse.jpt.utility.Command command;
+ private final SynchronizedBoolean flag;
+
+ EventHandlerListener(org.eclipse.jpt.utility.Command command, SynchronizedBoolean flag) {
+ super();
+ this.command = command;
+ this.flag = flag;
+ }
+
+ public void commandExecuted(org.eclipse.jpt.utility.Command c) {
+ if (c == this.command) {
+ this.flag.setTrue();
+ }
+ }
+ }
+
+ public void handleEventsSynchronously() {
+ try {
+ this.lock.acquire();
+ this.handleEventsSynchronously_();
+ } finally {
+ this.lock.release();
+ }
+ }
+
+ private void handleEventsSynchronously_() {
+ this.eventHandler.stop();
+ this.eventHandler = new SimpleCommandExecutor();
+ this.eventHandler.start();
+ }
+
+
+ // ********** resource proxy visitor **********
+
+ /**
+ * Visit the workspace resource tree, adding a JPA project to the
+ * JPA model for each open Eclipse project that has a JPA facet.
+ */
+ private class ResourceProxyVisitor implements IResourceProxyVisitor {
+ ResourceProxyVisitor() {
+ super();
+ }
+
+ public boolean visit(IResourceProxy resourceProxy) {
+ switch (resourceProxy.getType()) {
+ case IResource.ROOT :
+ return true; // all projects are in the "root"
+ case IResource.PROJECT :
+ this.checkProject(resourceProxy);
+ return false; // no nested projects
+ case IResource.FOLDER :
+ return false; // ignore
+ case IResource.FILE :
+ return false; // ignore
+ default :
+ return false;
+ }
+ }
+
+ private void checkProject(IResourceProxy resourceProxy) {
+ if (resourceProxy.isAccessible()) { // the project exists and is open
+ IProject project = (IProject) resourceProxy.requestResource();
+ if (JptCorePlugin.projectHasJpaFacet(project)) {
+ GenericJpaModel.this.addJpaProject(project);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return StringTools.buildToStringFor(this);
+ }
+
+ }
+
+
+ // ********** command **********
+
+ /**
+ * Abstract class that holds the JPA model lock while
+ * executing.
+ */
+ private abstract class Command
+ implements org.eclipse.jpt.utility.Command
+ {
+ Command() {
+ super();
+ }
+
+ public final void execute() {
+ try {
+ GenericJpaModel.this.lock.acquire();
+ this.execute_();
+ } finally {
+ GenericJpaModel.this.lock.release();
+ }
+ }
+
+ abstract void execute_();
+
+ }
+
+
+ // ********** resource change listener **********
+
+ private class ResourceChangeListener implements IResourceChangeListener {
+
+ ResourceChangeListener() {
+ super();
+ }
+
+ /**
+ * Check for:<ul>
+ * <li>project close/delete
+ * <li>file add/remove
+ * <li>project open/rename
+ * </ul>
+ */
+ public void resourceChanged(IResourceChangeEvent event) {
+ switch (event.getType()) {
+ case IResourceChangeEvent.POST_CHANGE :
+ this.resourcePostChange(event);
+ break;
+
+ // workspace or project events
+ case IResourceChangeEvent.PRE_REFRESH :
+ break; // ignore
+ case IResourceChangeEvent.PRE_BUILD :
+ break; // ignore
+ case IResourceChangeEvent.POST_BUILD :
+ this.resourcePostBuild(event);
+ break;
+
+ // project-only events
+ case IResourceChangeEvent.PRE_CLOSE :
+ break; // ignore
+ case IResourceChangeEvent.PRE_DELETE :
+ this.resourcePreDelete(event);
+ break;
+ default :
+ break;
+ }
+ }
+
+ /**
+ * A resource has changed somehow.
+ * Check for files being added or removed.
+ * (The JPA project only handles added and removed files here
+ * File changes are handled via the Java Element Changed event.)
+ * Also check for opened projects.
+ */
+ private void resourcePostChange(IResourceChangeEvent event) {
+ debug("Resource POST_CHANGE"); //$NON-NLS-1$
+ this.resourceChanged(event.getDelta());
+ }
+
+ private void resourceChanged(IResourceDelta delta) {
+ IResource resource = delta.getResource();
+ switch (resource.getType()) {
+ case IResource.ROOT :
+ this.resourceChangedChildren(delta);
+ break;
+ case IResource.PROJECT :
+ this.projectChanged((IProject) resource, delta);
+ break;
+ case IResource.FOLDER :
+ break; // ignore
+ case IResource.FILE :
+ break; // ignore
+ default :
+ break;
+ }
+ }
+
+ private void resourceChangedChildren(IResourceDelta delta) {
+ for (IResourceDelta child : delta.getAffectedChildren()) {
+ this.resourceChanged(child); // recurse
+ }
+ }
+
+ private void projectChanged(IProject project, IResourceDelta delta) {
+ GenericJpaModel.this.projectChanged(delta);
+ this.checkForOpenedProject(project, delta);
+ }
+
+
+ /**
+ * Crawl the specified delta, looking for projects being opened.
+ * Projects being deleted are handled in {@link IResourceChangeEvent#PRE_DELETE}.
+ * Projects being closed are handled in {@link IFacetedProjectEvent.Type#PROJECT_MODIFIED}.
+ */
+ private void checkForOpenedProject(IProject project, IResourceDelta delta) {
+ switch (delta.getKind()) {
+ case IResourceDelta.ADDED : // all but project import and rename handled with the facet POST_INSTALL event
+ this.checkDeltaFlagsForOpenedProject(project, delta);
+ this.checkDeltaFlagsForRenamedProject(project, delta);
+ break;
+ case IResourceDelta.REMOVED : // already handled with the PRE_DELETE event
+ break;
+ case IResourceDelta.CHANGED :
+ this.checkDeltaFlagsForOpenedProject(project, delta);
+ break;
+ case IResourceDelta.ADDED_PHANTOM :
+ break; // ignore
+ case IResourceDelta.REMOVED_PHANTOM :
+ break; // ignore
+ default :
+ break;
+ }
+ }
+
+ /**
+ * We don't get any events from the Facets Framework when a pre-existing
+ * project is imported, so we need to check for the newly imported project here.
+ * <p>
+ * This event also occurs when a project is simply opened. Project opening
+ * also triggers a {@link IFacetedProjectEvent.Type#PROJECT_MODIFIED} event
+ * and that is where we add the JPA project, not here.
+ */
+ private void checkDeltaFlagsForOpenedProject(IProject project, IResourceDelta delta) {
+ if (BitTools.flagIsSet(delta.getFlags(), IResourceDelta.OPEN) && project.isOpen()) {
+ debug("\tProject CHANGED - OPEN: ", project.getName()); //$NON-NLS-1$
+ GenericJpaModel.this.checkForJpaFacetTransition(project);
+ }
+ }
+
+ /**
+ * We don't get any events from the Facets Framework when a project is renamed,
+ * so we need to check for the renamed projects here.
+ */
+ private void checkDeltaFlagsForRenamedProject(IProject project, IResourceDelta delta) {
+ if (BitTools.flagIsSet(delta.getFlags(), IResourceDelta.MOVED_FROM) && project.isOpen()) {
+ debug("\tProject ADDED - MOVED_FROM: ", delta.getMovedFromPath()); //$NON-NLS-1$
+ GenericJpaModel.this.checkForJpaFacetTransition(project);
+ }
+ }
+
+ /**
+ * A post build event has occurred.
+ * Check for whether the build was a "clean" build and trigger project update.
+ */
+ private void resourcePostBuild(IResourceChangeEvent event) {
+ debug("Resource POST_BUILD: ", event.getResource()); //$NON-NLS-1$
+ if (event.getBuildKind() == IncrementalProjectBuilder.CLEAN_BUILD) {
+ this.resourcePostCleanBuild(event.getDelta());
+ }
+ }
+
+ private void resourcePostCleanBuild(IResourceDelta delta) {
+ IResource resource = delta.getResource();
+ switch (resource.getType()) {
+ case IResource.ROOT :
+ this.resourcePostCleanBuildChildren(delta);
+ break;
+ case IResource.PROJECT :
+ this.projectPostCleanBuild((IProject) resource);
+ break;
+ case IResource.FOLDER :
+ break; // ignore
+ case IResource.FILE :
+ break; // ignore
+ default :
+ break;
+ }
+ }
+
+ private void resourcePostCleanBuildChildren(IResourceDelta delta) {
+ for (IResourceDelta child : delta.getAffectedChildren()) {
+ this.resourcePostCleanBuild(child); // recurse
+ }
+ }
+
+ private void projectPostCleanBuild(IProject project) {
+ debug("\tProject CLEAN: ", project.getName()); //$NON-NLS-1$
+ GenericJpaModel.this.projectPostCleanBuild(project);
+ }
+
+ /**
+ * A project is being deleted. Remove its corresponding
+ * JPA project if appropriate.
+ */
+ private void resourcePreDelete(IResourceChangeEvent event) {
+ IProject project = (IProject) event.getResource();
+ debug("Resource (Project) PRE_DELETE: ", project); //$NON-NLS-1$
+ GenericJpaModel.this.projectPreDelete(project);
+ }
+
+ @Override
+ public String toString() {
+ return StringTools.buildToStringFor(this);
+ }
+
+ }
+
+
+ // ********** faceted project listener **********
+
+ /**
+ * Forward the Faceted project change event back to the JPA model.
+ */
+ private class FacetedProjectListener implements IFacetedProjectListener {
+
+ FacetedProjectListener() {
+ super();
+ }
+
+ /**
+ * Check for:<ul>
+ * <li>install of JPA facet
+ * <li>un-install of JPA facet
+ * <li>any other appearance or disappearance of the JPA facet
+ * </ul>
+ */
+ public void handleEvent(IFacetedProjectEvent event) {
+ switch (event.getType()) {
+ case POST_INSTALL :
+ this.facetedProjectPostInstall((IProjectFacetActionEvent) event);
+ break;
+ case PRE_UNINSTALL :
+ this.facetedProjectPreUninstall((IProjectFacetActionEvent) event);
+ break;
+ case PROJECT_MODIFIED :
+ this.facetedProjectModified(event.getProject().getProject());
+ break;
+ default :
+ break;
+ }
+ }
+
+ private void facetedProjectPostInstall(IProjectFacetActionEvent event) {
+ debug("Facet POST_INSTALL: ", event.getProjectFacet()); //$NON-NLS-1$
+ if (event.getProjectFacet().getId().equals(JptCorePlugin.FACET_ID)) {
+ GenericJpaModel.this.jpaFacetedProjectPostInstall(event);
+ }
+ }
+
+ private void facetedProjectPreUninstall(IProjectFacetActionEvent event) {
+ debug("Facet PRE_UNINSTALL: ", event.getProjectFacet()); //$NON-NLS-1$
+ if (event.getProjectFacet().getId().equals(JptCorePlugin.FACET_ID)) {
+ GenericJpaModel.this.jpaFacetedProjectPreUninstall(event);
+ }
+ }
+
+ /**
+ * This event is triggered for any change to a faceted project.
+ * We use the event to watch for the following:<ul>
+ * <li>an open project is closed
+ * <li>a closed project is opened
+ * <li>one of a project's (facet) metadata files is edited directly
+ * </ul>
+ */
+ private void facetedProjectModified(IProject project) {
+ debug("Facet PROJECT_MODIFIED: ", project.getName()); //$NON-NLS-1$
+ GenericJpaModel.this.checkForJpaFacetTransition(project);
+ }
+
+ @Override
+ public String toString() {
+ return StringTools.buildToStringFor(this);
+ }
+
+ }
+
+
+ // ********** Java element change listener **********
+
+ /**
+ * Forward the Java element change event back to the JPA model.
+ */
+ private class JavaElementChangeListener implements IElementChangedListener {
+ /**
+ * A flag to activate/deactivate the listener
+ * so we can ignore Java events whenever Dali is manipulating the Java
+ * source code via the Dali model. We do this because the 0.5 sec delay
+ * between the Java source being changed and the corresponding event
+ * being fired causes us no end of pain.
+ */
+ private volatile boolean active = true;
+
+ JavaElementChangeListener() {
+ super();
+ }
+
+ public void elementChanged(ElementChangedEvent event) {
+ if (this.active) {
+ GenericJpaModel.this.javaElementChanged(event);
+ }
+ }
+
+ void setActive(boolean active) {
+ this.active = active;
+ }
+
+ boolean isActive() {
+ return this.active;
+ }
+
+ @Override
+ public String toString() {
+ return StringTools.buildToStringFor(this);
+ }
+
+ }
+
+
+ // ********** DEBUG **********
+
+ // @see JpaModelTests#testDEBUG()
+ private static final boolean DEBUG = false;
+
+ /**
+ * trigger #toString() call and string concatenation only if DEBUG is true
+ */
+ /* private */ static void debug(String message, Object object) {
+ if (DEBUG) {
+ debug_(message + object);
+ }
+ }
+
+ /* private */ static void debug(String message) {
+ if (DEBUG) {
+ debug_(message);
+ }
+ }
+
+ private static void debug_(String message) {
+ System.out.println(Thread.currentThread().getName() + ": " + message); //$NON-NLS-1$
+ }
+
+ /* private */ static void dumpStackTrace() {
+ dumpStackTrace(null);
+ }
+
+ /* private */ static void dumpStackTrace(String message, Object object) {
+ if (DEBUG) {
+ dumpStackTrace_(message + object);
+ }
+ }
+
+ /* private */ static void dumpStackTrace(String message) {
+ if (DEBUG) {
+ dumpStackTrace_(message);
+ }
+ }
+
+ private static void dumpStackTrace_(String message) {
+ // lock System.out so the stack elements are printed out contiguously
+ synchronized (System.out) {
+ if (message != null) {
+ debug_(message);
+ }
+ StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+ // skip the first 3 elements - those are this method and 2 methods in Thread
+ for (int i = 3; i < stackTrace.length; i++) {
+ StackTraceElement element = stackTrace[i];
+ if (element.getMethodName().equals("invoke0")) { //$NON-NLS-1$
+ break; // skip all elements outside of the JUnit test
+ }
+ System.out.println("\t" + element); //$NON-NLS-1$
+ }
+ }
+ }
+
+}

Back to the top