blob: 28c9ecf5075bdc7ef51a1a8ade63c1dd55741471 [file] [log] [blame]
/*******************************************************************************
* 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.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.jdt.core.ElementChangedEvent;
import org.eclipse.jpt.core.JpaFile;
import org.eclipse.jpt.core.JpaModel;
import org.eclipse.jpt.core.JpaPlatform;
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.internal.resource.orm.OrmXmlResourceProvider;
import org.eclipse.jpt.core.internal.resource.persistence.PersistenceXmlResourceProvider;
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.setUserOverrideDefaultCatalogName(JptCorePlugin.getUserOverrideDefaultCatalogName(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 all our JPA projects;
* they will each determine whether the event is significant.
*/
synchronized void projectChanged(IResourceDelta delta) throws CoreException {
for (JpaProjectHolder holder : this.jpaProjectHolders) {
holder.projectChanged(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 **********
synchronized void jpaFacetedProjectPostInstall(IProjectFacetActionEvent event) {
IProject project = event.getProject().getProject();
IDataModel dataModel = (IDataModel) event.getActionConfig();
boolean buildOrmXml = dataModel.getBooleanProperty(JpaFacetDataModelProperties.CREATE_ORM_XML);
this.createProjectXml(project, buildOrmXml);
// assume(?) this is the first event to indicate we need to add the JPA project to the JPA model
this.addJpaProject(project);
}
private void createProjectXml(IProject project, boolean buildOrmXml) {
this.createPersistenceXml(project);
if (buildOrmXml) {
this.createOrmXml(project);
}
}
private void createPersistenceXml(IProject project) {
PersistenceXmlResourceProvider resourceProvider =
PersistenceXmlResourceProvider.getDefaultXmlResourceProvider(project);
try {
resourceProvider.createFileAndResource();
}
catch (CoreException e) {
JptCorePlugin.log(e);
}
}
private void createOrmXml(IProject project) {
OrmXmlResourceProvider resourceProvider =
OrmXmlResourceProvider.getDefaultXmlResourceProvider(project);
try {
resourceProvider.createFileAndResource();
}
catch (CoreException e) {
JptCorePlugin.log(e);
}
}
// 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()); //$NON-NLS-1$
}
// ********** 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 projectChanged(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 projectChanged(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 {
JpaPlatform jpaPlatform = this.config.getJpaPlatform();
if (jpaPlatform == null) {
return null;
}
JpaProject result = jpaPlatform.getJpaFactory().buildJpaProject(this.config);
result.setUpdater(new AsynchronousJpaProjectUpdater(result));
return result;
}
public void projectChanged(IResourceDelta delta) throws CoreException {
if (this.jpaProject != null) {
this.jpaProject.projectChanged(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")) { //$NON-NLS-1$
break; // skip all elements outside of the JUnit test
}
System.out.println("\t" + element); //$NON-NLS-1$
}
}
}
}
}