| /******************************************************************************* |
| * Copyright (c) 2006, 2010 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.jpa.core; |
| |
| import java.util.Vector; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IFolder; |
| 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.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| 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.common.core.internal.JptCommonCoreMessages; |
| import org.eclipse.jpt.common.core.internal.utility.CallbackJobSynchronizer; |
| import org.eclipse.jpt.common.core.internal.utility.JobCommand; |
| import org.eclipse.jpt.common.core.internal.utility.JobSynchronizer; |
| import org.eclipse.jpt.common.utility.Command; |
| import org.eclipse.jpt.common.utility.internal.AsynchronousCommandExecutor; |
| import org.eclipse.jpt.common.utility.internal.SimpleCommandExecutor; |
| import org.eclipse.jpt.common.utility.internal.StatefulCommandExecutor; |
| import org.eclipse.jpt.common.utility.internal.StringTools; |
| import org.eclipse.jpt.common.utility.internal.SynchronizedBoolean; |
| import org.eclipse.jpt.common.utility.internal.iterables.LiveCloneIterable; |
| import org.eclipse.jpt.common.utility.internal.model.AbstractModel; |
| import org.eclipse.jpt.common.utility.synchronizers.CallbackSynchronizer; |
| import org.eclipse.jpt.common.utility.synchronizers.Synchronizer; |
| import org.eclipse.jpt.jpa.core.internal.JptCoreMessages; |
| import org.eclipse.jpt.jpa.core.internal.SimpleJpaProjectConfig; |
| import org.eclipse.osgi.util.NLS; |
| 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 project manager 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 IResourceChangeEvent#POST_CHANGE}) 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>The {@link JptJpaCorePlugin} will "lazily" instantiate and {@link #start() start} |
| * a JPA project manager as appropriate. This will trigger the manager |
| * to find and add all pre-existing JPA projects. |
| * |
| * <li>Project created and facet installed<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * <li>Project facet uninstalled<p> |
| * {@link IFacetedProjectEvent.Type#PRE_UNINSTALL} |
| * |
| * <li>Project opened<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * -> {@link IResource#FILE} |
| * -> {@link IResourceDelta#ADDED} facet settings file |
| * (<code>/.settings/org.eclipse.wst.common.project.facet.core.xml</code>) |
| * <li>Project closed<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * -> {@link IResource#FILE} |
| * -> {@link IResourceDelta#REMOVED} facet settings file |
| * |
| * <li>Pre-existing project imported from directory or archive (created and opened)<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * -> {@link IResource#FILE} |
| * -> {@link IResourceDelta#ADDED} facet settings file |
| * <li>Project renamed<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * -> {@link IResource#FILE} |
| * -> {@link IResourceDelta#REMOVED} facet settings file of old project |
| * -> {@link IResourceDelta#ADDED} facet settings file of new project |
| * <li>Project deleted<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * -> {@link IResource#FILE} |
| * -> {@link IResourceDelta#REMOVED} facet settings file |
| * |
| * <li>Project facet installed by editing the facets settings file directly<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * -> {@link IResource#FILE} |
| * -> {@link IResourceDelta#CHANGED} facet settings file |
| * <li>Project facet uninstalled by editing the facets settings file directly<p> |
| * {@link IResourceChangeEvent#POST_CHANGE} |
| * -> {@link IResource#FILE} |
| * -> {@link IResourceDelta#CHANGED} facet settings file |
| * </ul> |
| */ |
| // TODO remove faceted project listener and rely solely on resource change events |
| // for the faceted project settings file |
| class GenericJpaProjectManager |
| extends AbstractModel |
| implements JpaProjectManager |
| { |
| /** |
| * All the JPA projects in the workspace. |
| */ |
| private final Vector<JpaProject> jpaProjects = new Vector<JpaProject>(); |
| |
| /** |
| * Synchronize access to the JPA projects. |
| */ |
| /* CU private */ final ILock lock = this.getJobManager().newLock(); |
| |
| /** |
| * Determine how Resource and Java change events are |
| * handled (i.e. synchronously or asynchronously). |
| */ |
| private volatile StatefulCommandExecutor eventHandler = new AsynchronousCommandExecutor(JptCommonCoreMessages.DALI_EVENT_HANDLER_THREAD_NAME); |
| |
| /** |
| * Listen for<ul> |
| * <li>changes to projects and files |
| * <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.POST_CHANGE | |
| IResourceChangeEvent.POST_BUILD; |
| |
| /** |
| * Listen for changes to this file to determine when the JPA facet is |
| * added to or removed from a "faceted" project. |
| */ |
| private static final String FACETED_PROJECT_FRAMEWORK_SETTINGS_FILE_NAME = FacetedProjectFramework.PLUGIN_ID + ".xml"; //$NON-NLS-1$ |
| |
| /** |
| * Listen for the JPA facet being added to or removed from a "faceted" project. |
| */ |
| private final IFacetedProjectListener facetedProjectListener = new FacetedProjectListener(); |
| |
| /** |
| * The types of faceted project events that interest |
| * {@link #facetedProjectListener}. |
| */ |
| private static final IFacetedProjectEvent.Type[] FACETED_PROJECT_EVENT_TYPES = new IFacetedProjectEvent.Type[] { |
| IFacetedProjectEvent.Type.PRE_UNINSTALL |
| }; |
| |
| /** |
| * Listen for Java changes (unless the Dali UI is active). |
| * @see #javaElementChangeListenerIsActive() |
| */ |
| private final JavaElementChangeListener javaElementChangeListener = new JavaElementChangeListener(); |
| |
| /** |
| * The types of resource change events that interest |
| * {@link #javaElementChangeListener}. |
| */ |
| private static final int JAVA_CHANGE_EVENT_TYPES = |
| ElementChangedEvent.POST_CHANGE | |
| ElementChangedEvent.POST_RECONCILE; |
| |
| |
| // ********** constructor ********** |
| |
| /** |
| * Internal: called by {@link JptJpaCorePlugin Dali plug-in}. |
| */ |
| GenericJpaProjectManager() { |
| super(); |
| } |
| |
| |
| // ********** plug-in controlled life-cycle ********** |
| |
| /** |
| * Internal: called by {@link JptJpaCorePlugin Dali plug-in}. |
| */ |
| void start() { |
| try { |
| this.lock.acquire(); |
| this.start_(); |
| } finally { |
| this.lock.release(); |
| } |
| } |
| |
| private void start_() { |
| debug("*** JPA project manager START ***"); //$NON-NLS-1$ |
| try { |
| this.buildJpaProjects(); |
| this.eventHandler.start(); |
| this.getWorkspace().addResourceChangeListener(this.resourceChangeListener, RESOURCE_CHANGE_EVENT_TYPES); |
| FacetedProjectFramework.addListener(this.facetedProjectListener, FACETED_PROJECT_EVENT_TYPES); |
| JavaCore.addElementChangedListener(this.javaElementChangeListener, JAVA_CHANGE_EVENT_TYPES); |
| } catch (RuntimeException ex) { |
| JptJpaCorePlugin.log(ex); |
| this.stop_(); |
| } |
| } |
| |
| /** |
| * Side-effect: {@link #jpaProjects} populated. |
| */ |
| private void buildJpaProjects() { |
| try { |
| this.buildJpaProjects_(); |
| } catch (CoreException ex) { |
| // if we have a problem, leave the currently built JPA projects in |
| // place and keep executing (should be OK...) |
| JptJpaCorePlugin.log(ex); |
| } |
| } |
| |
| private void buildJpaProjects_() throws CoreException { |
| this.getWorkspace().getRoot().accept(new ResourceProxyVisitor(), IResource.NONE); |
| } |
| |
| /** |
| * Internal: called by {@link JptJpaCorePlugin Dali plug-in}. |
| */ |
| void stop() throws Exception { |
| try { |
| this.lock.acquire(); |
| this.stop_(); |
| } finally { |
| this.lock.release(); |
| } |
| } |
| |
| private void stop_() { |
| debug("*** JPA project manager 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); |
| } |
| } |
| |
| |
| // ********** JpaProjectManager 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 ********** |
| |
| /* CU 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 will be null if we have any problems building it... |
| // (e.g. if we have problems getting the JPA platform) |
| if (jpaProject != null) { |
| this.addItemToCollection(jpaProject, this.jpaProjects, JPA_PROJECTS_COLLECTION); |
| } |
| } |
| |
| /** |
| * return null if we have any problems... |
| */ |
| private JpaProject buildJpaProject(IProject project) { |
| return this.buildJpaProject(this.buildJpaProjectConfig(project)); |
| } |
| |
| /** |
| * return null if we have any problems... |
| */ |
| private JpaProject buildJpaProject(JpaProject.Config config) { |
| JpaPlatform jpaPlatform = config.getJpaPlatform(); |
| if (jpaPlatform == null) { |
| return null; |
| } |
| JpaProject jpaProject = this.buildJpaProject(jpaPlatform, config); |
| if (jpaProject == null) { |
| return null; |
| } |
| jpaProject.setContextModelSynchronizer(this.buildJobContextModelSynchronizer(jpaProject)); |
| jpaProject.setUpdateSynchronizer(this.buildJobUpdateSynchronizer(jpaProject)); |
| return jpaProject; |
| } |
| |
| /** |
| * return null if we have any problems... |
| */ |
| private JpaProject buildJpaProject(JpaPlatform jpaPlatform, JpaProject.Config config) { |
| try { |
| return jpaPlatform.getJpaFactory().buildJpaProject(config); |
| } catch (RuntimeException ex) { |
| JptJpaCorePlugin.log(ex); |
| return null; |
| } |
| } |
| |
| private JpaProject.Config buildJpaProjectConfig(IProject project) { |
| SimpleJpaProjectConfig config = new SimpleJpaProjectConfig(); |
| config.setProject(project); |
| config.setJpaPlatform(JptJpaCorePlugin.getJpaPlatformManager().buildJpaPlatformImplementation(project)); |
| config.setConnectionProfileName(JptJpaCorePlugin.getConnectionProfileName(project)); |
| config.setUserOverrideDefaultCatalog(JptJpaCorePlugin.getUserOverrideDefaultCatalog(project)); |
| config.setUserOverrideDefaultSchema(JptJpaCorePlugin.getUserOverrideDefaultSchema(project)); |
| config.setDiscoverAnnotatedClasses(JptJpaCorePlugin.discoverAnnotatedClasses(project)); |
| config.setMetamodelSourceFolderName(JptJpaCorePlugin.getMetamodelSourceFolderName(project)); |
| return config; |
| } |
| |
| private Synchronizer buildJobContextModelSynchronizer(JpaProject jpaProject) { |
| return new JobSynchronizer( |
| this.buildContextModelJobName(jpaProject), |
| this.buildContextModelJobCommand(jpaProject), |
| jpaProject.getProject() |
| ); |
| } |
| |
| private String buildContextModelJobName(JpaProject jpaProject) { |
| return NLS.bind(JptCoreMessages.CONTEXT_MODEL_SYNC_JOB_NAME, jpaProject.getName()); |
| } |
| |
| private JobCommand buildContextModelJobCommand(final JpaProject jpaProject) { |
| return new JobCommand() { |
| public IStatus execute(IProgressMonitor monitor) { |
| return jpaProject.synchronizeContextModel(monitor); |
| } |
| }; |
| } |
| |
| private CallbackSynchronizer buildJobUpdateSynchronizer(JpaProject jpaProject) { |
| return new CallbackJobSynchronizer( |
| this.buildUpdateJobName(jpaProject), |
| this.buildUpdateJobCommand(jpaProject), |
| jpaProject.getProject() |
| ); |
| } |
| |
| private String buildUpdateJobName(JpaProject jpaProject) { |
| return NLS.bind(JptCoreMessages.UPDATE_JOB_NAME, jpaProject.getName()); |
| } |
| |
| private JobCommand buildUpdateJobCommand(final JpaProject jpaProject) { |
| return new JobCommand() { |
| public IStatus execute(IProgressMonitor monitor) { |
| return jpaProject.update(monitor); |
| } |
| }; |
| } |
| |
| /* CU 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 ********** |
| |
| /* CU private */ void projectChanged(IResourceDelta delta) { |
| this.eventHandler.execute(this.buildProjectChangedCommand(delta)); |
| } |
| |
| private Command buildProjectChangedCommand(final IResourceDelta delta) { |
| return new EventHandlerCommand("Project POST_CHANGE Command") { //$NON-NLS-1$ |
| @Override |
| void execute_() { |
| GenericJpaProjectManager.this.projectChanged_(delta); |
| } |
| }; |
| } |
| |
| /** |
| * Forward the specified resource delta to all our JPA projects; |
| * they will each determine whether the event is significant. |
| */ |
| /* CU private */ void projectChanged_(IResourceDelta delta) { |
| for (JpaProject jpaProject : this.jpaProjects) { |
| jpaProject.projectChanged(delta); |
| } |
| } |
| |
| |
| // ********** Project POST_BUILD (CLEAN_BUILD) ********** |
| |
| /* CU private */ void projectPostCleanBuild(IProject project) { |
| this.executeAfterEventsHandled(this.buildProjectPostCleanBuildCommand(project)); |
| } |
| |
| private Command buildProjectPostCleanBuildCommand(final IProject project) { |
| return new EventHandlerCommand("Project POST_BUILD (CLEAN_BUILD) Command") { //$NON-NLS-1$ |
| @Override |
| void execute_() { |
| GenericJpaProjectManager.this.projectPostCleanBuild_(project); |
| } |
| }; |
| } |
| |
| /* CU private */ void projectPostCleanBuild_(IProject project) { |
| JpaProject jpaProject = this.getJpaProject_(project); |
| if (jpaProject != null) { |
| this.removeJpaProject(jpaProject); |
| this.addJpaProject(project); |
| } |
| } |
| |
| |
| // ********** File POST_CHANGE ********** |
| |
| /** |
| * The Faceted Project settings file has changed in some fashion, check |
| * whether the JPA facet has been added to or removed from the specified |
| * project. |
| */ |
| /* CU private */ void checkForJpaFacetTransition(IProject project) { |
| JpaProject jpaProject = this.getJpaProject_(project); |
| |
| if (JpaFacet.isInstalled(project)) { |
| if (jpaProject == null) { // JPA facet added |
| this.executeAfterEventsHandled(this.buildAddJpaProjectCommand(project)); |
| } |
| } else { |
| if (jpaProject != null) { // JPA facet removed |
| this.executeAfterEventsHandled(this.buildRemoveJpaProjectCommand(jpaProject)); |
| } |
| } |
| } |
| |
| private Command buildAddJpaProjectCommand(final IProject project) { |
| return new EventHandlerCommand("Add JPA Project Command") { //$NON-NLS-1$ |
| @Override |
| void execute_() { |
| GenericJpaProjectManager.this.addJpaProject(project); |
| } |
| }; |
| } |
| |
| private Command buildRemoveJpaProjectCommand(final JpaProject jpaProject) { |
| return new EventHandlerCommand("Remove JPA Project Command") { //$NON-NLS-1$ |
| @Override |
| void execute_() { |
| GenericJpaProjectManager.this.removeJpaProject(jpaProject); |
| } |
| }; |
| } |
| |
| |
| // ********** FacetedProject PRE_UNINSTALL ********** |
| |
| /* CU private */ void jpaFacetedProjectPreUninstall(IProjectFacetActionEvent event) { |
| IProject project = event.getProject().getProject(); |
| this.executeAfterEventsHandled(this.buildJpaFacetedProjectPreUninstallCommand(project)); |
| } |
| |
| private Command buildJpaFacetedProjectPreUninstallCommand(final IProject project) { |
| return new EventHandlerCommand("Faceted Project PRE_UNINSTALL Command") { //$NON-NLS-1$ |
| @Override |
| void execute_() { |
| GenericJpaProjectManager.this.jpaFacetedProjectPreUninstall_(project); |
| } |
| }; |
| } |
| |
| /* CU private */ void jpaFacetedProjectPreUninstall_(IProject project) { |
| // assume(?) this is the first event to indicate we need to remove the JPA project from the JPA project manager |
| this.removeJpaProject(this.getJpaProject_(project)); |
| } |
| |
| |
| // ********** Java element changed ********** |
| |
| /* CU private */ void javaElementChanged(ElementChangedEvent event) { |
| this.eventHandler.execute(this.buildJavaElementChangedCommand(event)); |
| } |
| |
| private Command buildJavaElementChangedCommand(final ElementChangedEvent event) { |
| return new EventHandlerCommand("Java element changed Command") { //$NON-NLS-1$ |
| @Override |
| void execute_() { |
| GenericJpaProjectManager.this.javaElementChanged_(event); |
| } |
| }; |
| } |
| |
| /** |
| * Forward the Java element changed event to all the JPA projects |
| * because the event could affect multiple projects. |
| */ |
| /* CU private */ void javaElementChanged_(ElementChangedEvent event) { |
| for (JpaProject jpaProject : this.jpaProjects) { |
| jpaProject.javaElementChanged(event); |
| } |
| } |
| |
| |
| // ********** misc ********** |
| |
| @Override |
| public void toString(StringBuilder sb) { |
| sb.append(this.jpaProjects); |
| } |
| |
| |
| // ********** event handler ********** |
| |
| /** |
| * If the event handler is executing asynchronously:<br> |
| * Allow all the commands currently on the command executor's queue to execute. |
| * Once they have executed, suspend the command executor and process the |
| * specified command (on <em>this</em> thread, <em>not</em> the command |
| * executor thread). Once the specified command is finished, allow the |
| * command executor to resume processing its command queue. |
| * <p> |
| * If the event handler is executing synchronously:<br> |
| * All the events have already been handled synchronously, so we simply |
| * execute the specified command [sorta] directly. |
| */ |
| private void executeAfterEventsHandled(Command command) { |
| SynchronizedBoolean flag = new SynchronizedBoolean(false); |
| this.eventHandler.execute(new PauseCommand(flag)); |
| try { |
| flag.waitUntilTrue(); |
| } catch (InterruptedException ex) { |
| // ignore - not sure why this thread would be interrupted |
| } |
| try { |
| command.execute(); |
| } finally { |
| flag.setFalse(); |
| } |
| } |
| |
| /** |
| * If this "pause" command is executing (asynchronously) on a different |
| * thread than the JPA project manager:<ol> |
| * <li>it will set the flag to <code>true</code>, allowing the JPA project |
| * manager to resume executing on its own thread |
| * <li>then it will suspend its command executor until the JPA project |
| * manager sets the flag back to <code>false</code>. |
| * </ol> |
| * If this "pause" command is executing (synchronously) on the same thread |
| * as the JPA project manager, it will simply set the flag to |
| * <code>true</code> and return. |
| */ |
| private static class PauseCommand |
| implements Command |
| { |
| private final Thread producerThread; |
| private final SynchronizedBoolean flag; |
| |
| PauseCommand(SynchronizedBoolean flag) { |
| this(Thread.currentThread(), flag); |
| } |
| |
| PauseCommand(Thread producerThread, SynchronizedBoolean flag) { |
| super(); |
| this.producerThread = producerThread; |
| this.flag = flag; |
| } |
| |
| public void execute() { |
| this.flag.setTrue(); |
| if (Thread.currentThread() != this.producerThread) { |
| try { |
| this.flag.waitUntilFalse(); |
| } catch (InterruptedException ex) { |
| // ignore - the command executor will check for interruptions |
| } |
| } |
| } |
| } |
| |
| /** |
| * This method is called (via reflection) when the test plug-in is loaded. |
| * See JptCoreTestsPlugin#start(BundleContext) |
| */ |
| @SuppressWarnings("unused") |
| private 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 project manager 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.processProject(resourceProxy); |
| return false; // no nested projects |
| case IResource.FOLDER : |
| return false; // ignore |
| case IResource.FILE : |
| return false; // ignore |
| default : |
| return false; |
| } |
| } |
| |
| private void processProject(IResourceProxy resourceProxy) { |
| if (resourceProxy.isAccessible()) { // the project exists and is open |
| IProject project = (IProject) resourceProxy.requestResource(); |
| if (JpaFacet.isInstalled(project)) { |
| GenericJpaProjectManager.this.addJpaProject(project); |
| } |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return StringTools.buildToStringFor(this); |
| } |
| |
| } |
| |
| |
| // ********** event handler command ********** |
| |
| /** |
| * Command that holds the JPA project manager lock while |
| * executing. |
| */ |
| private abstract class EventHandlerCommand |
| implements Command |
| { |
| private final String name; |
| |
| EventHandlerCommand(String name) { |
| super(); |
| this.name = name; |
| } |
| |
| public final void execute() { |
| try { |
| GenericJpaProjectManager.this.lock.acquire(); |
| this.execute_(); |
| } catch (RuntimeException ex) { |
| JptJpaCorePlugin.log(ex); |
| } finally { |
| GenericJpaProjectManager.this.lock.release(); |
| } |
| } |
| |
| abstract void execute_(); |
| |
| @Override |
| public String toString() { |
| return this.name; |
| } |
| } |
| |
| |
| // ********** resource change listener ********** |
| |
| private class ResourceChangeListener |
| implements IResourceChangeListener |
| { |
| ResourceChangeListener() { |
| super(); |
| } |
| |
| /** |
| * PRE_UNINSTALL is the only facet event we use for |
| * removing JPA projects. These are the cases where we listen for resource events. |
| * <p> |
| * Check for:<ul> |
| * <li>facet settings file added/removed/changed |
| * (<code>/.settings/org.eclipse.wst.common.project.facet.core.xml</code>) |
| * <li>file add/remove - forwarded to the individual JPA projects |
| * <li>project clean |
| * </ul> |
| */ |
| public void resourceChanged(IResourceChangeEvent event) { |
| switch (event.getType()) { |
| case IResourceChangeEvent.POST_CHANGE : |
| this.processPostChangeEvent(event); |
| break; |
| |
| // workspace or project events |
| case IResourceChangeEvent.PRE_REFRESH : |
| break; // ignore |
| case IResourceChangeEvent.PRE_BUILD : |
| break; // ignore |
| case IResourceChangeEvent.POST_BUILD : |
| this.processPostBuildEvent(event); |
| break; |
| |
| // project-only events |
| case IResourceChangeEvent.PRE_CLOSE : |
| break; // ignore |
| case IResourceChangeEvent.PRE_DELETE : |
| break; // ignore |
| default : |
| break; |
| } |
| } |
| |
| private void processPostChangeEvent(IResourceChangeEvent event) { |
| debug("Resource POST_CHANGE"); //$NON-NLS-1$ |
| this.processPostChangeDelta(event.getDelta()); |
| } |
| |
| private void processPostChangeDelta(IResourceDelta delta) { |
| IResource resource = delta.getResource(); |
| switch (resource.getType()) { |
| case IResource.ROOT : |
| this.processPostChangeRootDelta(delta); |
| break; |
| case IResource.PROJECT : |
| this.processPostChangeProjectDelta(delta); |
| break; |
| case IResource.FOLDER : |
| this.processPostChangeFolderDelta((IFolder) resource, delta); |
| break; |
| case IResource.FILE : |
| this.processPostChangeFileDelta((IFile) resource, delta); |
| break; |
| default : |
| break; |
| } |
| } |
| |
| // ***** POST_CHANGE ROOT |
| private void processPostChangeRootDelta(IResourceDelta delta) { |
| this.processPostChangeDeltaChildren(delta); |
| } |
| |
| // ***** POST_CHANGE PROJECT |
| /** |
| * Process the project first for the Opening project case. |
| * The JPA project will not be built until the children are processed |
| * and we see that the facet metadata file is added. |
| * Otherwise the JPA project would be built and then we would process |
| * the ADDED deltas for all the files in the project. |
| */ |
| private void processPostChangeProjectDelta(IResourceDelta delta) { |
| GenericJpaProjectManager.this.projectChanged(delta); |
| this.processPostChangeDeltaChildren(delta); |
| } |
| |
| // ***** POST_CHANGE FOLDER |
| private void processPostChangeFolderDelta(IFolder folder, IResourceDelta delta) { |
| if (folder.getName().equals(".settings")) { //$NON-NLS-1$ |
| this.processPostChangeDeltaChildren(delta); |
| } |
| } |
| |
| // ***** POST_CHANGE FILE |
| private void processPostChangeFileDelta(IFile file, IResourceDelta delta) { |
| if (file.getName().equals(FACETED_PROJECT_FRAMEWORK_SETTINGS_FILE_NAME)) { |
| this.checkForFacetFileChanges(file, delta); |
| } |
| } |
| |
| private void checkForFacetFileChanges(IFile file, IResourceDelta delta) { |
| switch (delta.getKind()) { |
| case IResourceDelta.ADDED : |
| case IResourceDelta.REMOVED : |
| case IResourceDelta.CHANGED : |
| GenericJpaProjectManager.this.checkForJpaFacetTransition(file.getProject()); |
| break; |
| case IResourceDelta.ADDED_PHANTOM : |
| break; // ignore |
| case IResourceDelta.REMOVED_PHANTOM : |
| break; // ignore |
| default : |
| break; |
| } |
| } |
| |
| private void processPostChangeDeltaChildren(IResourceDelta delta) { |
| for (IResourceDelta child : delta.getAffectedChildren()) { |
| this.processPostChangeDelta(child); // recurse |
| } |
| } |
| |
| /** |
| * A post build event has occurred. |
| * Check for whether the build was a "clean" build and trigger project update. |
| */ |
| // ***** POST_BUILD |
| private void processPostBuildEvent(IResourceChangeEvent event) { |
| debug("Resource POST_BUILD: ", event.getResource()); //$NON-NLS-1$ |
| if (event.getBuildKind() == IncrementalProjectBuilder.CLEAN_BUILD) { |
| this.processPostCleanBuildDelta(event.getDelta()); |
| } |
| } |
| |
| private void processPostCleanBuildDelta(IResourceDelta delta) { |
| IResource resource = delta.getResource(); |
| switch (resource.getType()) { |
| case IResource.ROOT : |
| this.processPostCleanBuildDeltaChildren(delta); |
| break; |
| case IResource.PROJECT : |
| this.processProjectPostCleanBuild((IProject) resource); |
| break; |
| case IResource.FOLDER : |
| break; // ignore |
| case IResource.FILE : |
| break; // ignore |
| default : |
| break; |
| } |
| } |
| |
| private void processPostCleanBuildDeltaChildren(IResourceDelta delta) { |
| for (IResourceDelta child : delta.getAffectedChildren()) { |
| this.processPostCleanBuildDelta(child); // recurse |
| } |
| } |
| |
| private void processProjectPostCleanBuild(IProject project) { |
| debug("\tProject CLEAN: ", project.getName()); //$NON-NLS-1$ |
| GenericJpaProjectManager.this.projectPostCleanBuild(project); |
| } |
| |
| @Override |
| public String toString() { |
| return StringTools.buildToStringFor(this); |
| } |
| |
| } |
| |
| |
| // ********** faceted project listener ********** |
| |
| /** |
| * Forward the Faceted project change event back to the JPA project manager. |
| */ |
| private class FacetedProjectListener |
| implements IFacetedProjectListener |
| { |
| FacetedProjectListener() { |
| super(); |
| } |
| |
| /** |
| * Check for:<ul> |
| * <li>un-install of JPA facet |
| * </ul> |
| */ |
| public void handleEvent(IFacetedProjectEvent event) { |
| switch (event.getType()) { |
| case PRE_UNINSTALL : |
| this.processPreUninstallEvent((IProjectFacetActionEvent) event); |
| break; |
| default : |
| break; |
| } |
| } |
| |
| private void processPreUninstallEvent(IProjectFacetActionEvent event) { |
| debug("Facet PRE_UNINSTALL: ", event.getProjectFacet()); //$NON-NLS-1$ |
| if (event.getProjectFacet().equals(JpaFacet.FACET)) { |
| GenericJpaProjectManager.this.jpaFacetedProjectPreUninstall(event); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return StringTools.buildToStringFor(this); |
| } |
| |
| } |
| |
| |
| // ********** Java element change listener ********** |
| |
| /** |
| * Forward the Java element change event back to the JPA project manager. |
| */ |
| 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) { |
| GenericJpaProjectManager.this.javaElementChanged(event); |
| } |
| // TODO save unhandled events and process when listener is reactivated? |
| } |
| |
| void setActive(boolean active) { |
| this.active = active; |
| } |
| |
| boolean isActive() { |
| return this.active; |
| } |
| |
| @Override |
| public String toString() { |
| return StringTools.buildToStringFor(this); |
| } |
| |
| } |
| |
| |
| // ********** DEBUG ********** |
| |
| // @see JpaProjectManagerTests#testDEBUG() |
| private static final boolean DEBUG = false; |
| |
| /** |
| * trigger #toString() call and string concatenation only if DEBUG is true |
| */ |
| /* CU private */ static void debug(String message, Object object) { |
| if (DEBUG) { |
| debug_(message + object); |
| } |
| } |
| |
| /* CU 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$ |
| } |
| |
| /* CU private */ static void dumpStackTrace() { |
| dumpStackTrace(null); |
| } |
| |
| /* CU private */ static void dumpStackTrace(String message, Object object) { |
| if (DEBUG) { |
| dumpStackTrace_(message + object); |
| } |
| } |
| |
| /* CU 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$ |
| } |
| } |
| } |
| |
| } |