blob: 74c9be81344fc6d8815a07f6366c9feed21b7e4c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2007 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.internal.IJpaProject.Config;
import org.eclipse.jpt.core.internal.content.orm.EntityMappingsInternal;
import org.eclipse.jpt.core.internal.content.orm.OrmFactory;
import org.eclipse.jpt.core.internal.content.orm.OrmResource;
import org.eclipse.jpt.core.internal.content.orm.resource.OrmArtifactEdit;
import org.eclipse.jpt.core.internal.content.persistence.Persistence;
import org.eclipse.jpt.core.internal.content.persistence.PersistenceFactory;
import org.eclipse.jpt.core.internal.content.persistence.PersistenceUnit;
import org.eclipse.jpt.core.internal.content.persistence.resource.PersistenceArtifactEdit;
import org.eclipse.jpt.core.internal.content.persistence.resource.PersistenceResource;
import org.eclipse.jpt.core.internal.facet.IJpaFacetDataModelProperties;
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 JpaModel
extends AbstractModel
implements IJpaModel
{
/** maintain a list of all the current JPA projects */
private final ArrayList<IJpaProjectHolder> jpaProjectHolders = new ArrayList<IJpaProjectHolder>();
// ********** 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.
*/
JpaModel() 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 IJpaProject jpaProject(IProject project) throws CoreException {
return this.jpaProjectHolder(project).jpaProject();
}
/**
* We can answer this question without instantiating the
* associated JPA project.
*/
public synchronized boolean containsJpaProject(IProject project) {
return this.jpaProjectHolder(project).holdsJpaProjectFor(project);
}
/**
* This will trigger the instantiation of all the JPA projects.
*/
public synchronized Iterator<IJpaProject> jpaProjects() throws CoreException {
// force the CoreException to occur here (instead of later, in Iterator#next())
ArrayList<IJpaProject> jpaProjects = new ArrayList<IJpaProject>(this.jpaProjectHolders.size());
for (IJpaProjectHolder 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 IJpaFile jpaFile(IFile file) throws CoreException {
IJpaProject jpaProject = this.jpaProject(file.getProject());
return (jpaProject == null) ? null : jpaProject.jpaFile(file);
}
// ********** internal methods **********
/**
* never return null
*/
private IJpaProjectHolder jpaProjectHolder(IProject project) {
for (IJpaProjectHolder holder : this.jpaProjectHolders) {
if (holder.holdsJpaProjectFor(project)) {
return holder;
}
}
return NullJpaProjectHolder.instance();
}
private IJpaProject.Config buildJpaProjectConfig(IProject project) {
SimpleJpaProjectConfig config = new SimpleJpaProjectConfig();
config.setProject(project);
config.setJpaPlatform(JptCorePlugin.jpaPlatform(project));
config.setConnectionProfileName(JptCorePlugin.connectionProfileName(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(IJpaProject.Config config) {
dumpStackTrace(); // figure out exactly when JPA projects are added
this.jpaProjectHolders.add(this.jpaProjectHolder(config.project()).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.jpaProjectHolder(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.jpaProjectHolder(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();
this.buildPersistenceXmlJob(project).schedule();
if (dataModel.getBooleanProperty(IJpaFacetDataModelProperties.CREATE_ORM_XML)) {
this.buildOrmXmlJob(project).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 buildPersistenceXmlJob(final IProject project) {
return new Job("Create persistence.xml") {
@Override
protected IStatus run(IProgressMonitor monitor) {
JpaModel.this.createPersistenceXml(project);
return Status.OK_STATUS;
}
};
}
/* private */ void createPersistenceXml(IProject project) {
PersistenceArtifactEdit pae = PersistenceArtifactEdit.getArtifactEditForWrite(project);
PersistenceResource resource = pae.getPersistenceResource(JptCorePlugin.persistenceXmlDeploymentURI(project));
// 202811 - do not add content if it is already present
if (resource.getPersistence() == null) {
Persistence persistence = PersistenceFactory.eINSTANCE.createPersistence();
persistence.setVersion("1.0");
PersistenceUnit pUnit = PersistenceFactory.eINSTANCE.createPersistenceUnit();
pUnit.setName(project.getName());
persistence.getPersistenceUnits().add(pUnit);
this.resourceContents(resource).add(persistence);
pae.save(null);
}
pae.dispose();
}
@SuppressWarnings({ "restriction", "unchecked" })
private EList<EObject> resourceContents(PersistenceResource resource) {
return resource.getContents();
}
private Job buildOrmXmlJob(final IProject project) {
return new Job("Create orm.xml") {
@Override
protected IStatus run(IProgressMonitor monitor) {
JpaModel.this.createOrmXml(project);
return Status.OK_STATUS;
}
};
}
/* private */ void createOrmXml(IProject project) {
OrmArtifactEdit oae = OrmArtifactEdit.getArtifactEditForWrite(project);
OrmResource resource = oae.getOrmResource(JptCorePlugin.ormXmlDeploymentURI(project));
// 202811 - do not add content if it is already present
if (resource.getEntityMappings() == null) {
EntityMappingsInternal entityMappings = OrmFactory.eINSTANCE.createEntityMappingsInternal();
entityMappings.setVersion("1.0");
resource.getContents().add(entityMappings);
oae.save(null);
}
oae.dispose();
}
// 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 (IJpaProjectHolder 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
IJpaProjectHolder[] holders = this.jpaProjectHolders.toArray(new IJpaProjectHolder[this.jpaProjectHolders.size()]);
for (IJpaProjectHolder 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(IJpaProject 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(IJpaProject jpaProject) {
this.fireItemRemoved(JPA_PROJECTS_COLLECTION, jpaProject);
}
/**
* called by the JPA project holder
*/
/* private */ void removeJpaProjectHolder(IJpaProjectHolder jpaProjectHolder) {
this.jpaProjectHolders.remove(jpaProjectHolder);
}
// ********** JPA project holders **********
private interface IJpaProjectHolder {
boolean holdsJpaProjectFor(IProject project);
IJpaProject jpaProject() throws CoreException;
void synchronizeJpaFiles(IResourceDelta delta) throws CoreException;
void javaElementChanged(ElementChangedEvent event);
IJpaProjectHolder buildJpaProjectHolder(JpaModel jpaModel, IJpaProject.Config config);
void remove();
}
private static class NullJpaProjectHolder implements IJpaProjectHolder {
private static final IJpaProjectHolder INSTANCE = new NullJpaProjectHolder();
static IJpaProjectHolder instance() {
return INSTANCE;
}
// ensure single instance
private NullJpaProjectHolder() {
super();
}
public boolean holdsJpaProjectFor(IProject project) {
return false;
}
public IJpaProject jpaProject() throws CoreException {
return null;
}
public void synchronizeJpaFiles(IResourceDelta delta) throws CoreException {
// do nothing
}
public void javaElementChanged(ElementChangedEvent event) {
// do nothing
}
public IJpaProjectHolder buildJpaProjectHolder(JpaModel jpaModel, Config config) {
return new JpaProjectHolder(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 JpaProjectHolder implements IJpaProjectHolder {
private final JpaModel jpaModel;
private final IJpaProject.Config config;
private IJpaProject jpaProject;
JpaProjectHolder(JpaModel jpaModel, IJpaProject.Config config) {
super();
this.jpaModel = jpaModel;
this.config = config;
}
public boolean holdsJpaProjectFor(IProject project) {
return this.config.project().equals(project);
}
public IJpaProject 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 IJpaProject buildJpaProject() throws CoreException {
return this.config.jpaPlatform().getJpaFactory().createJpaProject(this.config);
}
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 IJpaProjectHolder buildJpaProjectHolder(JpaModel jm, Config c) {
throw new IllegalArgumentException(c.project().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.project().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)) {
JpaModel.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);
}
}
}
}
}