blob: 90316ea2ced76b69a786c21b70a97b348b5242a7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2008 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.internal;
import java.util.ArrayList;
import java.util.Iterator;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.resources.IResourceProxyVisitor;
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.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jpt.core.JpaFile;
import org.eclipse.jpt.core.JpaModel;
import org.eclipse.jpt.core.JpaProject;
import org.eclipse.jpt.core.JptCorePlugin;
import org.eclipse.jpt.core.JpaProject.Config;
import org.eclipse.jpt.core.internal.facet.JpaFacetDataModelProperties;
import org.eclipse.jpt.core.resource.orm.OrmArtifactEdit;
import org.eclipse.jpt.core.resource.orm.OrmFactory;
import org.eclipse.jpt.core.resource.orm.OrmResource;
import org.eclipse.jpt.core.resource.orm.XmlEntityMappings;
import org.eclipse.jpt.core.resource.persistence.PersistenceArtifactEdit;
import org.eclipse.jpt.core.resource.persistence.PersistenceResource;
import org.eclipse.jpt.utility.internal.ClassTools;
import org.eclipse.jpt.utility.internal.StringTools;
import org.eclipse.jpt.utility.internal.model.AbstractModel;
import org.eclipse.wst.common.frameworks.datamodel.IDataModel;
import org.eclipse.wst.common.project.facet.core.events.IProjectFacetActionEvent;
/**
* The JPA model is synchronized so all changes to the list of JPA projects
* are thread-safe.
*
* The JPA model holds on to a list of JPA project configs and only instantiates
* their associated JPA projects when necessary. Other than performance,
* this should be transparent to clients.
*/
public class GenericJpaModel
extends AbstractModel
implements JpaModel
{
/** maintain a list of all the current JPA projects */
private final ArrayList<JpaProjectHolder> jpaProjectHolders = new ArrayList<JpaProjectHolder>();
// ********** constructor **********
/**
* Construct a JPA model and populate it with JPA projects for all the
* current Eclipse projects with JPA facets.
* The JPA model can only be instantiated by the JPA model manager.
*/
GenericJpaModel() throws CoreException {
super();
ResourcesPlugin.getWorkspace().getRoot().accept(new ResourceProxyVisitor(), IResource.NONE);
}
// ********** IJpaModel implementation **********
/**
* This will trigger the instantiation of the JPA project associated with the
* specified Eclipse project.
*/
public synchronized JpaProject getJpaProject(IProject project) throws CoreException {
return this.getJpaProjectHolder(project).jpaProject();
}
/**
* We can answer this question without instantiating the
* associated JPA project.
*/
public synchronized boolean containsJpaProject(IProject project) {
return this.getJpaProjectHolder(project).holdsJpaProjectFor(project);
}
/**
* This will trigger the instantiation of all the JPA projects.
*/
public synchronized Iterator<JpaProject> jpaProjects() throws CoreException {
// force the CoreException to occur here (instead of later, in Iterator#next())
ArrayList<JpaProject> jpaProjects = new ArrayList<JpaProject>(this.jpaProjectHolders.size());
for (JpaProjectHolder holder : this.jpaProjectHolders) {
jpaProjects.add(holder.jpaProject());
}
return jpaProjects.iterator();
}
/**
* We can answer this question without instantiating any JPA projects.
*/
public synchronized int jpaProjectsSize() {
return this.jpaProjectHolders.size();
}
/**
* This will trigger the instantiation of the JPA project associated with the
* specified file.
*/
public synchronized JpaFile getJpaFile(IFile file) throws CoreException {
JpaProject jpaProject = this.getJpaProject(file.getProject());
return (jpaProject == null) ? null : jpaProject.getJpaFile(file);
}
// ********** internal methods **********
/**
* never return null
*/
private JpaProjectHolder getJpaProjectHolder(IProject project) {
for (JpaProjectHolder holder : this.jpaProjectHolders) {
if (holder.holdsJpaProjectFor(project)) {
return holder;
}
}
return NullJpaProjectHolder.instance();
}
private JpaProject.Config buildJpaProjectConfig(IProject project) {
SimpleJpaProjectConfig config = new SimpleJpaProjectConfig();
config.setProject(project);
config.setJpaPlatform(JptCorePlugin.getJpaPlatform(project));
config.setConnectionProfileName(JptCorePlugin.getConnectionProfileName(project));
config.setUserOverrideDefaultSchemaName(JptCorePlugin.getUserOverrideDefaultSchemaName(project));
config.setDiscoverAnnotatedClasses(JptCorePlugin.discoverAnnotatedClasses(project));
return config;
}
/* private */ void addJpaProject(IProject project) {
this.addJpaProject(this.buildJpaProjectConfig(project));
}
/**
* Add a JPA project to the JPA model for the specified Eclipse project.
* JPA projects can only be added by the JPA model manager.
* The JPA project will only be instantiated later, on demand.
*/
private void addJpaProject(JpaProject.Config config) {
dumpStackTrace(); // figure out exactly when JPA projects are added
this.jpaProjectHolders.add(this.getJpaProjectHolder(config.getProject()).buildJpaProjectHolder(this, config));
}
/**
* Remove the JPA project corresponding to the specified Eclipse project
* from the JPA model. Return whether the removal actually happened.
* JPA projects can only be removed by the JPA model manager.
*/
private void removeJpaProject(IProject project) {
dumpStackTrace(); // figure out exactly when JPA projects are removed
this.getJpaProjectHolder(project).remove();
}
// ********** Resource events **********
/**
* A project is being deleted. Remove its corresponding
* JPA project if appropriate.
*/
synchronized void projectPreDelete(IProject project) {
this.removeJpaProject(project);
}
/**
* Forward the specified resource delta to the JPA project corresponding
* to the specified Eclipse project.
*/
synchronized void synchronizeFiles(IProject project, IResourceDelta delta) throws CoreException {
this.getJpaProjectHolder(project).synchronizeJpaFiles(delta);
}
// ********** Resource and/or Facet events **********
/**
* Check whether the JPA facet has been added or removed.
*/
synchronized void checkForTransition(IProject project) {
boolean jpaFacet = JptCorePlugin.projectHasJpaFacet(project);
boolean jpaProject = this.containsJpaProject(project);
if (jpaFacet) {
if ( ! jpaProject) { // JPA facet added
this.addJpaProject(project);
}
} else {
if (jpaProject) { // JPA facet removed
this.removeJpaProject(project);
}
}
}
// ********** Facet events **********
// create persistence.xml and orm.xml in jobs so we don't deadlock
// with the Resource Change Event that comes in from another
// thread after the Eclipse project is created by the project facet
// wizard (which happens before the wizard notifies us); the Artifact
// Edits will hang during #save(), waiting for the workspace lock held
// by the Resource Change Notification loop
synchronized void jpaFacetedProjectPostInstall(IProjectFacetActionEvent event) {
IProject project = event.getProject().getProject();
IDataModel dataModel = (IDataModel) event.getActionConfig();
boolean buildOrmXml = dataModel.getBooleanProperty(JpaFacetDataModelProperties.CREATE_ORM_XML);
this.buildProjectXmlJob(project, buildOrmXml).schedule();
// assume(?) this is the first event to indicate we need to add the JPA project to the JPA model
this.addJpaProject(project);
}
private Job buildProjectXmlJob(final IProject project, final boolean buildOrmXml) {
Job job = new Job("Create Project XML files") {
@Override
protected IStatus run(IProgressMonitor monitor) {
GenericJpaModel.this.createProjectXml(project, buildOrmXml);
return Status.OK_STATUS;
}
};
job.setRule(ResourcesPlugin.getWorkspace().getRoot());
return job;
}
/* private */ void createProjectXml(IProject project, boolean buildOrmXml) {
this.createPersistenceXml(project);
if (buildOrmXml) {
this.createOrmXml(project);
}
}
private void createPersistenceXml(IProject project) {
PersistenceArtifactEdit pae = PersistenceArtifactEdit.getArtifactEditForWrite(project);
// 202811 - do not add content if it is already present
PersistenceResource resource = pae.getResource();
if (! resource.getFile().exists()) {
pae.createDefaultResource();
}
pae.dispose();
}
private void createOrmXml(IProject project) {
OrmArtifactEdit oae = OrmArtifactEdit.getArtifactEditForWrite(project);
OrmResource resource = oae.getResource(JptCorePlugin.getDefaultOrmXmlDeploymentURI(project));
// 202811 - do not add content if it is already present
if (resource.getEntityMappings() == null) {
XmlEntityMappings entityMappings = OrmFactory.eINSTANCE.createXmlEntityMappings();
entityMappings.setVersion("1.0");
this.getResourceContents(resource).add(entityMappings);
oae.save(null);
}
oae.dispose();
}
/**
* minimize the scope of the suppressed warnings
*/
@SuppressWarnings("unchecked")
private EList<EObject> getResourceContents(OrmResource resource) {
return resource.getContents();
}
// TODO remove classpath items? persistence.xml? orm.xml?
synchronized void jpaFacetedProjectPreUninstall(IProjectFacetActionEvent event) {
// assume(?) this is the first event to indicate we need to remove the JPA project to the JPA model
this.removeJpaProject(event.getProject().getProject());
}
// ********** Java events **********
/**
* Forward the Java element changed event to all the JPA projects
* because the event could affect multiple projects.
*/
synchronized void javaElementChanged(ElementChangedEvent event) {
for (JpaProjectHolder jpaProjectHolder : this.jpaProjectHolders) {
jpaProjectHolder.javaElementChanged(event);
}
}
// ********** miscellaneous **********
/**
* The JPA settings associated with the specified Eclipse project
* have changed in such a way as to require the associated
* JPA project to be completely rebuilt
* (e.g. when the user changes a project's JPA platform).
*/
synchronized void rebuildJpaProject(IProject project) {
this.removeJpaProject(project);
this.addJpaProject(project);
}
/**
* Dispose the JPA model by disposing and removing all its JPA projects.
* The JPA model can only be disposed by the JPA model manager.
*/
synchronized void dispose() {
// clone the list to prevent concurrent modification exceptions
JpaProjectHolder[] holders = this.jpaProjectHolders.toArray(new JpaProjectHolder[this.jpaProjectHolders.size()]);
for (JpaProjectHolder holder : holders) {
holder.remove();
}
}
@Override
public void toString(StringBuilder sb) {
sb.append("JPA projects size: " + this.jpaProjectsSize());
}
// ********** holder callbacks **********
/**
* called by the JPA project holder when the JPA project is actually
* instantiated
*/
/* private */ void jpaProjectBuilt(JpaProject jpaProject) {
this.fireItemAdded(JPA_PROJECTS_COLLECTION, jpaProject);
}
/**
* called by the JPA project holder if the JPA project has been
* instantiated and we need to remove it
*/
/* private */ void jpaProjectRemoved(JpaProject jpaProject) {
this.fireItemRemoved(JPA_PROJECTS_COLLECTION, jpaProject);
}
/**
* called by the JPA project holder
*/
/* private */ void removeJpaProjectHolder(JpaProjectHolder jpaProjectHolder) {
this.jpaProjectHolders.remove(jpaProjectHolder);
}
// ********** JPA project holders **********
private interface JpaProjectHolder {
boolean holdsJpaProjectFor(IProject project);
JpaProject jpaProject() throws CoreException;
void synchronizeJpaFiles(IResourceDelta delta) throws CoreException;
void javaElementChanged(ElementChangedEvent event);
JpaProjectHolder buildJpaProjectHolder(GenericJpaModel jpaModel, JpaProject.Config config);
void remove();
}
private static class NullJpaProjectHolder implements JpaProjectHolder {
private static final JpaProjectHolder INSTANCE = new NullJpaProjectHolder();
static JpaProjectHolder instance() {
return INSTANCE;
}
// ensure single instance
private NullJpaProjectHolder() {
super();
}
public boolean holdsJpaProjectFor(IProject project) {
return false;
}
public JpaProject jpaProject() throws CoreException {
return null;
}
public void synchronizeJpaFiles(IResourceDelta delta) throws CoreException {
// do nothing
}
public void javaElementChanged(ElementChangedEvent event) {
// do nothing
}
public JpaProjectHolder buildJpaProjectHolder(GenericJpaModel jpaModel, Config config) {
return new DefaultJpaProjectHolder(jpaModel, config);
}
public void remove() {
// do nothing
}
@Override
public String toString() {
return ClassTools.shortClassNameForObject(this);
}
}
/**
* Pair a JPA project config with its lazily-initialized JPA project.
*/
private static class DefaultJpaProjectHolder implements JpaProjectHolder {
private final GenericJpaModel jpaModel;
private final JpaProject.Config config;
private JpaProject jpaProject;
DefaultJpaProjectHolder(GenericJpaModel jpaModel, JpaProject.Config config) {
super();
this.jpaModel = jpaModel;
this.config = config;
}
public boolean holdsJpaProjectFor(IProject project) {
return this.config.getProject().equals(project);
}
public JpaProject jpaProject() throws CoreException {
if (this.jpaProject == null) {
this.jpaProject = this.buildJpaProject();
// notify listeners of the JPA model
this.jpaModel.jpaProjectBuilt(this.jpaProject);
}
return this.jpaProject;
}
private JpaProject buildJpaProject() throws CoreException {
JpaProject jpaProject = this.config.getJpaPlatform().getJpaFactory().buildJpaProject(this.config);
jpaProject.setUpdater(new AsynchronousJpaProjectUpdater(jpaProject));
return jpaProject;
}
public void synchronizeJpaFiles(IResourceDelta delta) throws CoreException {
if (this.jpaProject != null) {
this.jpaProject.synchronizeJpaFiles(delta);
}
}
public void javaElementChanged(ElementChangedEvent event) {
if (this.jpaProject != null) {
this.jpaProject.javaElementChanged(event);
}
}
public JpaProjectHolder buildJpaProjectHolder(GenericJpaModel jm, Config c) {
throw new IllegalArgumentException(c.getProject().getName());
}
public void remove() {
this.jpaModel.removeJpaProjectHolder(this);
if (this.jpaProject != null) {
this.jpaModel.jpaProjectRemoved(this.jpaProject);
this.jpaProject.dispose();
}
}
@Override
public String toString() {
return StringTools.buildToStringFor(this, this.config.getProject().getName());
}
}
// ********** 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) throws CoreException {
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
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);
}
}
// ********** DEBUG **********
// @see JpaModelTests#testDEBUG()
private static final boolean DEBUG = false;
private static void dumpStackTrace() {
if (DEBUG) {
// lock System.out so the stack elements are printed out contiguously
synchronized (System.out) {
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")) {
break; // skip all elements outside of the JUnit test
}
System.out.println("\t" + element);
}
}
}
}
}