blob: 9b6921fd8bd70dc2789cceb9d457efaad92c9c08 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2016 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0, which accompanies this distribution
* and is available at https://www.eclipse.org/legal/epl-2.0/.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.jpa.core.internal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.resources.IResourceProxyVisitor;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaElementDelta;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jpt.common.core.ContentTypeReference;
import org.eclipse.jpt.common.core.JptResourceModel;
import org.eclipse.jpt.common.core.JptResourceModelListener;
import org.eclipse.jpt.common.core.internal.resource.java.binary.BinaryTypeCache;
import org.eclipse.jpt.common.core.internal.resource.java.source.SourceTypeCompilationUnit;
import org.eclipse.jpt.common.core.internal.utility.PackageFragmentRootTools;
import org.eclipse.jpt.common.core.internal.utility.ValidationMessageTools;
import org.eclipse.jpt.common.core.internal.utility.command.NotifyingRepeatingJobCommandWrapper;
import org.eclipse.jpt.common.core.internal.utility.command.RepeatingJobCommandWrapper;
import org.eclipse.jpt.common.core.resource.ProjectResourceLocator;
import org.eclipse.jpt.common.core.resource.java.JavaResourceAbstractType;
import org.eclipse.jpt.common.core.resource.java.JavaResourceAnnotatedElement;
import org.eclipse.jpt.common.core.resource.java.JavaResourceCompilationUnit;
import org.eclipse.jpt.common.core.resource.java.JavaResourceModel;
import org.eclipse.jpt.common.core.resource.java.JavaResourcePackage;
import org.eclipse.jpt.common.core.resource.java.JavaResourcePackageFragmentRoot;
import org.eclipse.jpt.common.core.resource.java.JavaResourcePackageInfoCompilationUnit;
import org.eclipse.jpt.common.core.resource.java.JavaResourceTypeCache;
import org.eclipse.jpt.common.core.resource.xml.JptXmlResource;
import org.eclipse.jpt.common.core.utility.command.JobCommand;
import org.eclipse.jpt.common.core.utility.command.JobCommandContext;
import org.eclipse.jpt.common.core.utility.command.NotifyingRepeatingJobCommand;
import org.eclipse.jpt.common.core.utility.command.RepeatingJobCommand;
import org.eclipse.jpt.common.utility.internal.BitTools;
import org.eclipse.jpt.common.utility.internal.ObjectTools;
import org.eclipse.jpt.common.utility.internal.StringTools;
import org.eclipse.jpt.common.utility.internal.iterable.EmptyIterable;
import org.eclipse.jpt.common.utility.internal.iterable.IterableTools;
import org.eclipse.jpt.common.utility.internal.transformer.TransformerAdapter;
import org.eclipse.jpt.common.utility.transformer.Transformer;
import org.eclipse.jpt.jpa.core.JpaDataSource;
import org.eclipse.jpt.jpa.core.JpaFile;
import org.eclipse.jpt.jpa.core.JpaModel;
import org.eclipse.jpt.jpa.core.JpaPlatform;
import org.eclipse.jpt.jpa.core.JpaPreferences;
import org.eclipse.jpt.jpa.core.JpaProject;
import org.eclipse.jpt.jpa.core.JptJpaCoreMessages;
import org.eclipse.jpt.jpa.core.context.JpaContextRoot;
import org.eclipse.jpt.jpa.core.context.java.JavaManagedTypeDefinition;
import org.eclipse.jpt.jpa.core.context.java.JavaTypeMappingDefinition;
import org.eclipse.jpt.jpa.core.internal.plugin.JptJpaCorePlugin;
import org.eclipse.jpt.jpa.core.jpa2.JpaMetamodelSynchronizer2_0;
import org.eclipse.jpt.jpa.core.jpa2.JpaProject2_0;
import org.eclipse.jpt.jpa.core.jpa2.context.JpaContextRoot2_0;
import org.eclipse.jpt.jpa.core.jpa2_2.JpaProject2_2;
import org.eclipse.jpt.jpa.core.libprov.JpaLibraryProviderInstallOperationConfig;
import org.eclipse.jpt.jpa.core.resource.ResourceMappingFile;
import org.eclipse.jpt.jpa.core.resource.orm.XmlEntityMappings;
import org.eclipse.jpt.jpa.core.resource.persistence.XmlPersistence;
import org.eclipse.jpt.jpa.core.validation.JptJpaCoreValidationMessages;
import org.eclipse.jpt.jpa.db.Catalog;
import org.eclipse.jpt.jpa.db.ConnectionProfile;
import org.eclipse.jpt.jpa.db.Database;
import org.eclipse.jpt.jpa.db.Schema;
import org.eclipse.jpt.jpa.db.SchemaContainer;
import org.eclipse.jst.common.project.facet.core.libprov.ILibraryProvider;
import org.eclipse.jst.common.project.facet.core.libprov.LibraryProviderFramework;
import org.eclipse.jst.j2ee.model.internal.validation.ValidationCancelledException;
import org.eclipse.osgi.util.NLS;
import org.eclipse.wst.common.internal.emfworkbench.WorkbenchResourceHelper;
import org.eclipse.wst.common.project.facet.core.IFacetedProject;
import org.eclipse.wst.common.project.facet.core.IProjectFacetVersion;
import org.eclipse.wst.common.project.facet.core.ProjectFacetsManager;
import org.eclipse.wst.validation.internal.provisional.core.IMessage;
import org.eclipse.wst.validation.internal.provisional.core.IReporter;
/**
* JPA project. Holds all the JPA stuff.
* <p>
* The JPA platform provides the hooks for vendor-specific stuff.
* <p>
* The JPA files are the "resource" model (i.e. objects that correspond directly
* to Eclipse resources; e.g. Java source code files, XML files, JAR files).
* <p>
* The root context node is the "context" model (i.e. objects that attempt to
* model the JPA spec, using the "resource" model as an adapter to the Eclipse
* resources).
* <p>
* The data source is an adapter to the DTP meta-data model.
*/
public abstract class AbstractJpaProject
extends AbstractJpaModel<JpaModel>
implements JpaProject2_2
{
/**
* The JPA project manager.
*/
protected final Manager manager;
/**
* The Eclipse project corresponding to the JPA project.
*/
protected final IProject project;
protected final ProjectResourceLocator projectResourceLocator;
/**
* The vendor-specific JPA platform that builds the JPA project
* and all its contents.
*/
protected final JpaPlatform jpaPlatform;
/**
* key - IFile associated with the JpaFile.
* value - the JpaFile
* The JPA files associated with the JPA project:
* persistence.xml
* orm.xml
* java
*/
protected final Hashtable<IFile, JpaFile> jpaFiles = new Hashtable<>();
/**
* The "external" Java resource compilation units (source). Populated upon demand.
*/
protected final Vector<JavaResourceCompilationUnit> externalJavaResourceCompilationUnits = new Vector<>();
/**
* The "external" Java resource types (binary). Populated upon demand.
*/
protected final JavaResourceTypeCache externalJavaResourceTypeCache;
/**
* Resource models notify this listener when they change. A project update
* should occur any time a resource model changes.
*/
protected final JptResourceModelListener resourceModelListener;
/**
* The root of the model representing the collated resources associated with
* the JPA project.
*/
protected final JpaContextRoot contextRoot;
/**
* A repeating command that keeps the JPA project's context model
* synchronized with its resource model, either synchronously or
* asynchronously (or not at all), depending on the JPA project manager.
*/
protected volatile RepeatingJobCommand synchronizeContextModelCommand;
protected volatile boolean synchronizingContextModel = false;
/**
* A pluggable synchronizer that "updates" the JPA project, either
* synchronously or asynchronously (or not at all), depending on the JPA
* project manager.
*/
protected volatile NotifyingRepeatingJobCommand updateCommand;
protected final NotifyingRepeatingJobCommand.Listener updateCommandListener;
/**
* The data source that wraps the DTP model.
*/
// TODO move to persistence unit... :-(
protected final JpaDataSource dataSource;
/**
* A catalog name used to override the connection's default catalog.
*/
protected volatile String userOverrideDefaultCatalog;
/**
* A schema name used to override the connection's default schema.
*/
protected volatile String userOverrideDefaultSchema;
/**
* Flag indicating whether the project should "discover" annotated
* classes automatically, as opposed to requiring the classes to be
* listed in <code>persistence.xml</code>. Stored as a preference.
* @see #setDiscoversAnnotatedClasses(boolean)
*/
protected volatile boolean discoversAnnotatedClasses;
/**
* The name of the Java project source folder that holds the generated
* metamodel. If the name is <code>null</code> the metamodel is not
* generated.
*/
protected volatile String metamodelSourceFolderName;
// ********** constructor/initialization **********
protected AbstractJpaProject(JpaProject.Config config, IProgressMonitor monitor) {
super(null); // JPA project is the root of the containment tree
if ((config.getJpaProjectManager() == null) || (config.getProject() == null) || (config.getJpaPlatform() == null)) {
throw new NullPointerException();
}
this.manager = config.getJpaProjectManager();
this.project = config.getProject();
this.projectResourceLocator = this.buildProjectResourceLocator();
this.synchronizeContextModelCommand = this.buildSynchronizeContextModelCommand();
this.updateCommand = this.buildTempUpdateCommand(monitor); // temporary command
this.jpaPlatform = config.getJpaPlatform();
this.dataSource = this.getJpaFactory().buildJpaDataSource(this, config.getConnectionProfileName());
this.userOverrideDefaultCatalog = config.getUserOverrideDefaultCatalog();
this.userOverrideDefaultSchema = config.getUserOverrideDefaultSchema();
this.discoversAnnotatedClasses = config.discoverAnnotatedClasses();
this.resourceModelListener = this.buildResourceModelListener();
// build the JPA files corresponding to the Eclipse project's files
InitialResourceProxyVisitor visitor = this.buildInitialResourceProxyVisitor();
visitor.visitProject(this.project);
this.externalJavaResourceTypeCache = this.buildExternalJavaResourceTypeCache();
if (this.isJpa2_0Compatible()) {
this.metamodelSourceFolderName = ((JpaProject2_0.Config) config).getMetamodelSourceFolderName();
if (this.metamodelSoureFolderNameIsInvalid()) {
this.metamodelSourceFolderName = null;
}
}
this.contextRoot = this.buildContextRoot();
this.updateCommandListener = this.buildUpdateCommandListener();
this.initializeContextModel();
// start listening to this cache once the context model has been built
// and all the external types are faulted in
this.externalJavaResourceTypeCache.addResourceModelListener(this.resourceModelListener);
}
@Override
protected boolean requiresParent() {
return false;
}
@Override
public IResource getResource() {
return this.project;
}
protected ProjectResourceLocator buildProjectResourceLocator() {
return this.project.getAdapter(ProjectResourceLocator.class);
}
protected JavaResourceTypeCache buildExternalJavaResourceTypeCache() {
return new BinaryTypeCache(this.jpaPlatform.getAnnotationProvider());
}
protected JpaContextRoot buildContextRoot() {
return this.getJpaFactory().buildContextRoot(this);
}
/**
* Execute a synchronous <em>sync</em> and <em>update</em> before returning
* from the constructor.
*/
protected void initializeContextModel() {
// there *shouldn't* be any changes to the resource model yet...
this.synchronizeContextModelCommand.start();
// perform a synchronous update...
this.updateCommand.start();
this.update();
try {
this.updateCommand.stop();
} catch (InterruptedException ex) {
// the initial update is synchronous, so this shouldn't happen...
// but let our thread know it was interrupted during a wait
Thread.currentThread().interrupt();
}
// ...then delegate further updates to the JPA project manager
this.updateCommand = this.buildUpdateCommand();
this.updateCommand.addListener(this.updateCommandListener);
this.updateCommand.start();
}
// ********** initial resource proxy visitor **********
protected InitialResourceProxyVisitor buildInitialResourceProxyVisitor() {
return new InitialResourceProxyVisitor();
}
protected class InitialResourceProxyVisitor
implements IResourceProxyVisitor
{
protected InitialResourceProxyVisitor() {
super();
}
protected void visitProject(IProject p) {
try {
p.accept(this, IResource.NONE);
} catch (CoreException ex) {
// shouldn't happen - we don't throw any CoreExceptions
throw new RuntimeException(ex);
}
}
// add a JPA file for every [appropriate] file encountered by the visitor
public boolean visit(IResourceProxy resource) {
switch (resource.getType()) {
case IResource.ROOT : // shouldn't happen
return true; // visit children
case IResource.PROJECT :
return true; // visit children
case IResource.FOLDER :
return true; // visit children
case IResource.FILE :
AbstractJpaProject.this.addJpaFileMaybe_((IFile) resource.requestResource());
return false; // no children
default :
return false; // no children
}
}
@Override
public String toString() {
return ObjectTools.toString(this, AbstractJpaProject.this);
}
}
// ********** misc **********
/**
* Ignore changes to this collection. Adds can be ignored since they are triggered
* by requests that will, themselves, trigger updates (typically during the
* update of an object that calls a setter with the newly-created resource
* type). Deletes will be accompanied by manual updates.
*/
@Override
protected void addNonUpdateAspectNamesTo(Set<String> nonUpdateAspectNames) {
super.addNonUpdateAspectNamesTo(nonUpdateAspectNames);
nonUpdateAspectNames.add(EXTERNAL_JAVA_RESOURCE_COMPILATION_UNITS_COLLECTION);
}
// ********** general queries **********
@Override
public JpaProject getJpaProject() {
return this;
}
public Manager getManager() {
return this.manager;
}
public String getName() {
return this.project.getName();
}
@Override
public void toString(StringBuilder sb) {
sb.append(this.getName());
}
public IProject getProject() {
return this.project;
}
@Override
public IJavaProject getJavaProject() {
return JavaCore.create(this.project);
}
@Override
public JpaPlatform getJpaPlatform() {
return this.jpaPlatform;
}
@SuppressWarnings("unchecked")
protected Iterable<JavaResourceCompilationUnit> getCombinedJavaResourceCompilationUnits() {
return IterableTools.concatenate(
this.getInternalJavaResourceCompilationUnits(),
this.getExternalJavaResourceCompilationUnits()
);
}
// ********** database **********
@Override
public JpaDataSource getDataSource() {
return this.dataSource;
}
public ConnectionProfile getConnectionProfile() {
return this.dataSource.getConnectionProfile();
}
/**
* If we don't have a catalog (i.e. we don't even have a <em>default</em>
* catalog), then the database probably does not support catalogs.
*/
public Catalog getDefaultDbCatalog() {
String catalog = this.getDefaultCatalog();
return (catalog == null) ? null : this.resolveDbCatalog(catalog);
}
public String getDefaultCatalog() {
String catalog = this.getUserOverrideDefaultCatalog();
return (catalog != null) ? catalog : this.getDatabaseDefaultCatalog();
}
protected String getDatabaseDefaultCatalog() {
Database db = this.getDatabase();
return (db == null ) ? null : db.getDefaultCatalogIdentifier();
}
/**
* If we don't have a catalog (i.e. we don't even have a <em>default</em> catalog),
* then the database probably does not support catalogs; and we need to
* get the schema directly from the database.
*/
public SchemaContainer getDefaultDbSchemaContainer() {
String catalog = this.getDefaultCatalog();
return (catalog != null) ? this.resolveDbCatalog(catalog) : this.getDatabase();
}
public Schema getDefaultDbSchema() {
SchemaContainer sc = this.getDefaultDbSchemaContainer();
return (sc == null) ? null : sc.getSchemaForIdentifier(this.getDefaultSchema());
}
public String getDefaultSchema() {
String schema = this.getUserOverrideDefaultSchema();
if (schema != null) {
return schema;
}
String catalog = this.getDefaultCatalog();
if (catalog == null) {
// if there is no default catalog (either user-override or database-determined),
// the database probably does not support catalogs;
// return the database's default schema
return this.getDatabaseDefaultSchema();
}
Catalog dbCatalog = this.resolveDbCatalog(catalog);
if (dbCatalog != null) {
return dbCatalog.getDefaultSchemaIdentifier();
}
// if we could not find a catalog on the database that matches the default
// catalog name, return the database's default schema(?) - hmmm
return this.getDatabaseDefaultSchema();
}
protected String getDatabaseDefaultSchema() {
Database db = this.getDatabase();
return (db == null ) ? null : db.getDefaultSchemaIdentifier();
}
// ********** user override default catalog **********
public String getUserOverrideDefaultCatalog() {
return this.userOverrideDefaultCatalog;
}
public void setUserOverrideDefaultCatalog(String catalog) {
String old = this.userOverrideDefaultCatalog;
this.userOverrideDefaultCatalog = catalog;
if (this.firePropertyChanged(USER_OVERRIDE_DEFAULT_CATALOG_PROPERTY, old, catalog)) {
JpaPreferences.setUserOverrideDefaultCatalog(this.project, catalog);
}
}
// ********** user override default schema **********
public String getUserOverrideDefaultSchema() {
return this.userOverrideDefaultSchema;
}
public void setUserOverrideDefaultSchema(String schema) {
String old = this.userOverrideDefaultSchema;
this.userOverrideDefaultSchema = schema;
if (this.firePropertyChanged(USER_OVERRIDE_DEFAULT_SCHEMA_PROPERTY, old, schema)) {
JpaPreferences.setUserOverrideDefaultSchema(this.project, schema);
}
}
// ********** discover annotated classes **********
public boolean discoversAnnotatedClasses() {
return this.discoversAnnotatedClasses;
}
public void setDiscoversAnnotatedClasses(boolean discoversAnnotatedClasses) {
boolean old = this.discoversAnnotatedClasses;
this.discoversAnnotatedClasses = discoversAnnotatedClasses;
JpaPreferences.setDiscoverAnnotatedClasses(this.project, discoversAnnotatedClasses);
this.firePropertyChanged(DISCOVERS_ANNOTATED_CLASSES_PROPERTY, old, discoversAnnotatedClasses);
}
// ********** JPA files **********
public Iterable<JpaFile> getJpaFiles() {
return IterableTools.cloneLive(this.jpaFiles.values()); // read-only
}
public int getJpaFilesSize() {
return this.jpaFiles.size();
}
protected Iterable<JpaFile> getJpaFiles(final IContentType contentType) {
return IterableTools.filter(
this.getJpaFiles(),
new ContentTypeReference.ContentTypeIsKindOf(contentType)
);
}
@Override
public JpaFile getJpaFile(IFile file) {
return file == null ? null : this.jpaFiles.get(file);
}
/**
* Add a JPA file for the specified file, if appropriate.
* Return true if a JPA File was created and added, false otherwise
*/
protected boolean addJpaFileMaybe(IFile file) {
JpaFile jpaFile = this.addJpaFileMaybe_(file);
if (jpaFile != null) {
this.fireItemAdded(JPA_FILES_COLLECTION, jpaFile);
return true;
}
return false;
}
/**
* Add a JPA file for the specified file, if appropriate, without firing
* an event; useful during construction.
* Return the new JPA file, null if it was not created.
*/
protected JpaFile addJpaFileMaybe_(IFile file) {
if (this.fileIsJavaRelated(file)) {
if ( ! this.getJavaProject().isOnClasspath(file)) {
return null; // java-related files must be on the Java classpath
}
}
else if ( ! this.fileResourceLocationIsValid(file)) {
return null;
}
JpaFile jpaFile = this.buildJpaFile(file);
if (jpaFile == null) {
return null;
}
jpaFile.getResourceModel().addResourceModelListener(this.resourceModelListener);
this.jpaFiles.put(file, jpaFile);
return jpaFile;
}
/**
* <code>.java</code> or <code>.jar</code>
*/
protected boolean fileIsJavaRelated(IFile file) {
IContentType contentType = getContentType(file);
return (contentType != null) && this.contentTypeIsJavaRelated(contentType);
}
/**
* pre-condition: content type is not <code>null</code>
*/
protected boolean contentTypeIsJavaRelated(IContentType contentType) {
return contentType.isKindOf(JavaResourceCompilationUnit.CONTENT_TYPE) ||
contentType.isKindOf(JavaResourcePackageFragmentRoot.JAR_CONTENT_TYPE);
}
protected boolean fileResourceLocationIsValid(IFile file) {
return this.projectResourceLocator.locationIsValid(file.getParent());
}
/**
* Log any developer exceptions and don't build a JPA file rather
* than completely failing to build the JPA Project.
*/
protected JpaFile buildJpaFile(IFile file) {
try {
return this.getJpaPlatform().buildJpaFile(this, file);
} catch (Exception ex) {
JptJpaCorePlugin.instance().logError(ex, "Error building JPA file: " + file.getFullPath()); //$NON-NLS-1$
return null;
}
}
/**
* Remove the JPA file corresponding to the specified IFile, if it exists.
* Return true if a JPA File was removed, false otherwise
*/
protected boolean removeJpaFile(IFile file) {
JpaFile jpaFile = this.getJpaFile(file);
if (jpaFile != null) { // a JpaFile is not added for every IFile
this.removeJpaFile(jpaFile);
return true;
}
return false;
}
/**
* Stop listening to the JPA file and remove it.
*/
protected void removeJpaFile(JpaFile jpaFile) {
jpaFile.getResourceModel().removeResourceModelListener(this.resourceModelListener);
if (this.jpaFiles.remove(jpaFile.getFile()) == null) {
throw new IllegalArgumentException(jpaFile.toString());
}
this.fireItemRemoved(JPA_FILES_COLLECTION, jpaFile);
}
// ********** external Java resource types (source or binary) **********
protected JavaResourceAbstractType buildExternalJavaResourceType(String typeName) {
IType jdtType = this.findType(typeName);
return (jdtType == null) ? null : this.buildExternalJavaResourceType(jdtType);
}
/**
* If the Java project has a class named <code>Foo</code> in the default package,
* {@link IJavaProject#findType(String)} will return the {@link IType}
* corresponding to <code>Foo</code> if the named passed to it is <code>".Foo"</code>.
* This is not what we are expecting! So we had to put in a check for any
* type name beginning with <code>'.'</code>.
* See JDT bug 377710.
*/
protected IType findType(String typeName) {
try {
return typeName.startsWith(".") ? null : this.getJavaProject().findType(typeName); //$NON-NLS-1$
} catch (JavaModelException ex) {
return null; // ignore exception? resource exception was probably already logged by JDT
}
}
protected JavaResourceAbstractType buildExternalJavaResourceType(IType jdtType) {
return jdtType.isBinary() ?
this.buildBinaryExternalJavaResourceType(jdtType) :
this.buildSourceExternalJavaResourceType(jdtType);
}
protected JavaResourceAbstractType buildBinaryExternalJavaResourceType(IType jdtType) {
return this.externalJavaResourceTypeCache.addType(jdtType);
}
protected JavaResourceAbstractType buildSourceExternalJavaResourceType(IType jdtType) {
JavaResourceCompilationUnit jrcu = this.getExternalJavaResourceCompilationUnit(jdtType.getCompilationUnit());
String jdtTypeName = jdtType.getFullyQualifiedName('.'); // JDT member type names use '$'
for (JavaResourceAbstractType jrat : jrcu.getTypes()) {
if (jrat.getTypeBinding().getQualifiedName().equals(jdtTypeName)) {
return jrat;
}
}
// we can get here if the project JRE is removed;
// see SourceCompilationUnit#getPrimaryType(CompilationUnit)
// bug 225332
return null;
}
// ********** external Java resource persistent types (binary) **********
public JavaResourceTypeCache getExternalJavaResourceTypeCache() {
return this.externalJavaResourceTypeCache;
}
// ********** external Java resource compilation units (source) **********
public Iterable<JavaResourceCompilationUnit> getExternalJavaResourceCompilationUnits() {
return IterableTools.cloneLive(this.externalJavaResourceCompilationUnits); // read-only
}
public int getExternalJavaResourceCompilationUnitsSize() {
return this.externalJavaResourceCompilationUnits.size();
}
/**
* Return the resource model compilation unit corresponding to the specified
* JDT compilation unit. If it does not exist, build it.
*/
protected JavaResourceCompilationUnit getExternalJavaResourceCompilationUnit(ICompilationUnit jdtCompilationUnit) {
for (JavaResourceCompilationUnit jrcu : this.getExternalJavaResourceCompilationUnits()) {
if (jrcu.getCompilationUnit().equals(jdtCompilationUnit)) {
// we will get here if the JRCU could not build its persistent type...
return jrcu;
}
}
return this.addExternalJavaResourceCompilationUnit(jdtCompilationUnit);
}
/**
* Add an external Java resource compilation unit.
*/
protected JavaResourceCompilationUnit addExternalJavaResourceCompilationUnit(ICompilationUnit jdtCompilationUnit) {
JavaResourceCompilationUnit jrcu = this.buildJavaResourceCompilationUnit(jdtCompilationUnit);
this.addItemToCollection(jrcu, this.externalJavaResourceCompilationUnits, EXTERNAL_JAVA_RESOURCE_COMPILATION_UNITS_COLLECTION);
jrcu.addResourceModelListener(this.resourceModelListener);
return jrcu;
}
protected JavaResourceCompilationUnit buildJavaResourceCompilationUnit(ICompilationUnit jdtCompilationUnit) {
return new SourceTypeCompilationUnit(
jdtCompilationUnit,
this.jpaPlatform.getAnnotationProvider(),
this.jpaPlatform.getAnnotationEditFormatter(),
this.manager.getModifySharedDocumentCommandContext()
);
}
protected boolean removeExternalJavaResourceCompilationUnit(IFile file) {
for (JavaResourceCompilationUnit jrcu : this.getExternalJavaResourceCompilationUnits()) {
if (jrcu.getFile().equals(file)) {
this.removeExternalJavaResourceCompilationUnit(jrcu);
return true;
}
}
return false;
}
protected void removeExternalJavaResourceCompilationUnit(JavaResourceCompilationUnit jrcu) {
jrcu.removeResourceModelListener(this.resourceModelListener);
this.removeItemFromCollection(jrcu, this.externalJavaResourceCompilationUnits, EXTERNAL_JAVA_RESOURCE_COMPILATION_UNITS_COLLECTION);
}
// ********** context model **********
public JpaContextRoot getContextRoot() {
return this.contextRoot;
}
// ********** utility **********
public IFile getPlatformFile(IPath runtimePath) {
return this.projectResourceLocator.getPlatformFile(runtimePath);
}
// ********** XML files **********
public JptXmlResource getPersistenceXmlResource() {
return (JptXmlResource) this.getResourceModel(
XmlPersistence.DEFAULT_RUNTIME_PATH,
XmlPersistence.CONTENT_TYPE
);
}
public JptXmlResource getDefaultOrmXmlResource() {
return this.getMappingFileXmlResource(XmlEntityMappings.DEFAULT_RUNTIME_PATH);
}
public JptXmlResource getMappingFileXmlResource(IPath runtimePath) {
return (JptXmlResource) this.getResourceModel(runtimePath, ResourceMappingFile.Root.CONTENT_TYPE);
}
/**
* If the specified file exists, is significant to the JPA project, and its
* content is a "kind of" the specified content type, return the JPA
* resource model corresponding to the file; otherwise, return null.
*/
protected JptResourceModel getResourceModel(IPath runtimePath, IContentType contentType) {
IFile file = this.getPlatformFile(runtimePath);
return ((file != null) && file.exists()) ? this.getResourceModel(file, contentType) : null;
}
/**
* If the specified file is significant to the JPA project and its content
* is a "kind of" the specified content type, return the JPA resource model
* corresponding to the file; otherwise, return null.
*/
protected JptResourceModel getResourceModel(IFile file, IContentType contentType) {
JpaFile jpaFile = this.getJpaFile(file);
return (jpaFile == null) ? null : jpaFile.getResourceModel(contentType);
}
// ********** annotated Java source classes **********
/**
* Return only those valid annotated Java resource types that are
* directly part of the JPA project, ignoring those in JARs referenced in
* <code>persistence.xml</code>.
* @see JavaResourceAbstractType#isAnnotated()
*/
public Iterable<JavaResourceAbstractType> getAnnotatedJavaSourceTypes() {
// i.e. the type has a valid JPA type annotation
return IterableTools.filter(this.getInternalSourceJavaResourceTypes(), JavaResourceAnnotatedElement.IS_ANNOTATED);
}
/**
* Return only the types of those valid <em>managed</em> (i.e. annotated with
* <code>@Entity</code>, <code>@Embeddable</code>, <code>@Converter</code>etc.)
* Java resource types that are directly part of the JPA project, ignoring
* those in JARs referenced in <code>persistence.xml</code>.
*/
public Iterable<JavaResourceAbstractType> getPotentialJavaSourceTypes() {
return getInternalPotentialSourceJavaResourceTypes();
}
/**
* Return only those valid <em>managed</em> (i.e. annotated with
* <code>@Entity</code>, <code>@Embeddable</code>, <code>@Converter</code>etc.) Java resource
* types that are directly part of the JPA project, ignoring
* those in JARs referenced in <code>persistence.xml</code>.
*/
protected Iterable<JavaResourceAbstractType> getInternalPotentialSourceJavaResourceTypes() {
Iterable<String> annotationNames = this.getManagedTypeAnnotationNames();
return IterableTools.filter(
this.getAnnotatedJavaSourceTypes(),
new JavaResourceAnnotatedElement.IsAnnotatedWithAnyOf(annotationNames)
);
}
public Iterable<String> getTypeMappingAnnotationNames() {
return IterableTools.transform(this.getJpaPlatform().getJavaTypeMappingDefinitions(), JavaTypeMappingDefinition.ANNOTATION_NAME_TRANSFORMER);
}
public Iterable<String> getManagedTypeAnnotationNames() {
return IterableTools.concatenate(
IterableTools.transform(
this.getJpaPlatform().getJavaManagedTypeDefinitions(),
new JavaManagedTypeDefinition.AnnotationNamesTransformer(this.getJpaProject())));
}
/**
* Return only those Java resource persistent types that are directly
* part of the JPA project, ignoring those in JARs referenced in
* <code>persistence.xml</code>
*/
protected Iterable<JavaResourceAbstractType> getInternalSourceJavaResourceTypes() {
// get *all* the types in each compilation unit
return IterableTools.children(this.getInternalJavaResourceCompilationUnits(), JavaResourceModel.Root.TYPES_TRANSFORMER);
}
/**
* Return the JPA project's resource compilation units.
*/
protected Iterable<JavaResourceCompilationUnit> getInternalJavaResourceCompilationUnits() {
return IterableTools.downCast(IterableTools.transform(this.getJavaSourceJpaFiles(), JpaFile.RESOURCE_MODEL_TRANSFORMER));
}
/**
* Return the JPA project's JPA files with Java source <em>content</em>.
*/
protected Iterable<JpaFile> getJavaSourceJpaFiles() {
return this.getJpaFiles(JavaResourceCompilationUnit.CONTENT_TYPE);
}
// ********** Java resource persistent type look-up **********
public JavaResourceAbstractType getJavaResourceType(String typeName) {
for (JavaResourceAbstractType jraType : this.getJavaResourceTypes()) {
if (jraType.getTypeBinding().getQualifiedName().equals(typeName)) {
return jraType;
}
}
// if we don't have a type already, try to build new one from the project classpath
return this.buildExternalJavaResourceType(typeName);
}
public JavaResourceAbstractType getJavaResourceType(String typeName, JavaResourceAnnotatedElement.AstNodeType astNodeType) {
JavaResourceAbstractType resourceType = this.getJavaResourceType(typeName);
if ((resourceType == null) || (resourceType.getAstNodeType() != astNodeType)) {
return null;
}
return resourceType;
}
/**
* return *all* the Java resource persistent types, including those in JARs referenced in
* persistence.xml
*/
protected Iterable<JavaResourceAbstractType> getJavaResourceTypes() {
return IterableTools.children(this.getJavaResourceModelRoots(), JavaResourceModel.Root.TYPES_TRANSFORMER);
}
@SuppressWarnings("unchecked")
protected Iterable<JavaResourceModel.Root> getJavaResourceModelRoots() {
return IterableTools.concatenate(
this.getInternalJavaResourceCompilationUnits(),
this.getInternalJavaResourcePackageFragmentRoots(),
this.getExternalJavaResourceCompilationUnits(),
Collections.singleton(this.externalJavaResourceTypeCache)
);
}
// ********** Java resource persistent package look-up **********
public JavaResourcePackage getJavaResourcePackage(String packageName) {
for (JavaResourcePackage jrp : this.getJavaResourcePackages()) {
String name = jrp.getName();
// name can be null if package name is wrong(?)
if ((name != null) && name.equals(packageName)) {
return jrp;
}
}
return null;
}
public Iterable<JavaResourcePackage> getJavaResourcePackages(){
return IterableTools.removeNulls(IterableTools.transform(this.getPackageInfoSourceJpaFiles(), JPA_FILE_JAVA_RESOURCE_PACKAGE_TRANSFORMER));
}
protected static final Transformer<JpaFile, JavaResourcePackage> JPA_FILE_JAVA_RESOURCE_PACKAGE_TRANSFORMER = new JpaFileJavaResourcePackageTransformer();
public static class JpaFileJavaResourcePackageTransformer
extends TransformerAdapter<JpaFile, JavaResourcePackage>
{
@Override
public JavaResourcePackage transform(JpaFile jpaFile) {
return ((JavaResourcePackageInfoCompilationUnit) jpaFile.getResourceModel()).getPackage();
}
}
/**
* return JPA files with package-info source "content"
*/
protected Iterable<JpaFile> getPackageInfoSourceJpaFiles() {
return this.getJpaFiles(JavaResourceCompilationUnit.PACKAGE_INFO_CONTENT_TYPE);
}
// ********** JARs **********
// TODO
public JavaResourcePackageFragmentRoot getJavaResourcePackageFragmentRoot(String jarFileName) {
// return this.getJarResourcePackageFragmentRoot(this.convertToPlatformFile(jarFileName));
return this.getJavaResourcePackageFragmentRoot(this.project.getFile(jarFileName));
}
protected JavaResourcePackageFragmentRoot getJavaResourcePackageFragmentRoot(IFile jarFile) {
for (JavaResourcePackageFragmentRoot pfr : this.getInternalJavaResourcePackageFragmentRoots()) {
if (pfr.getFile().equals(jarFile)) {
return pfr;
}
}
return null;
}
protected Iterable<JavaResourcePackageFragmentRoot> getInternalJavaResourcePackageFragmentRoots() {
return IterableTools.downCast(IterableTools.transform(this.getJarJpaFiles(), JpaFile.RESOURCE_MODEL_TRANSFORMER));
}
/**
* return JPA files with JAR "content"
*/
public Iterable<JpaFile> getJarJpaFiles() {
return this.getJpaFiles(JavaResourcePackageFragmentRoot.JAR_CONTENT_TYPE);
}
// ********** metamodel **********
public Iterable<JavaResourceAbstractType> getGeneratedMetamodelTopLevelTypes() {
if (this.metamodelSourceFolderName == null) {
return EmptyIterable.instance();
}
IPackageFragmentRoot genSourceFolder = this.getMetamodelPackageFragmentRoot();
return IterableTools.filter(
this.getInternalSourceJavaResourceTypes(),
new JpaMetamodelSynchronizer2_0.MetamodelTools.IsGeneratedMetamodelTopLevelType(genSourceFolder)
);
}
public JavaResourceAbstractType getGeneratedMetamodelTopLevelType(IFile file) {
JavaResourceCompilationUnit jrcu = this.getJavaResourceCompilationUnit(file);
if (jrcu == null) {
return null; // hmmm...
}
JavaResourceAbstractType primaryType = jrcu.getPrimaryType();
if (primaryType == null) {
return null; // no types in the file
}
return JpaMetamodelSynchronizer2_0.MetamodelTools.isGeneratedMetamodelTopLevelType(primaryType) ? primaryType : null;
}
protected JavaResourceCompilationUnit getJavaResourceCompilationUnit(IFile file) {
return (JavaResourceCompilationUnit) this.getResourceModel(file, JavaResourceCompilationUnit.CONTENT_TYPE);
}
public String getMetamodelSourceFolderName() {
return this.metamodelSourceFolderName;
}
public void setMetamodelSourceFolderName(String folderName) {
if (this.setMetamodelSourceFolderName_(folderName)) {
JpaPreferences.setMetamodelSourceFolderName(this.project, folderName);
if (folderName == null) {
this.disposeMetamodel();
} else {
this.initializeMetamodel();
}
}
}
protected boolean setMetamodelSourceFolderName_(String folderName) {
String old = this.metamodelSourceFolderName;
this.metamodelSourceFolderName = folderName;
return this.firePropertyChanged(METAMODEL_SOURCE_FOLDER_NAME_PROPERTY, old, folderName);
}
protected void initializeMetamodel() {
((JpaContextRoot2_0) this.contextRoot).initializeMetamodel();
}
/**
* Synchronize the metamodel for 2.0-compatible JPA projects.
*/
protected void synchronizeMetamodel() {
if (this.isJpa2_0Compatible()) {
if (this.metamodelSourceFolderName != null) {
this.scheduleSynchronizeMetamodelJob();
}
}
}
/**
* We dispatch a job even when the JPA project manager is "synchronous"
* because we will synchronously execute an update when we receive a
* resource change event that is fired when a file is changed; and we
* cannot modify another file in response to a file change because the
* rules are incompatible. For example, if the <code>persistence.xml</code>
* file changes, we will receive a resource change event while the
* <code>persistence.xml</code> file is locked. We will not be able to
* lock and modify the necessary metamodel source files under this lock.
*/
protected void scheduleSynchronizeMetamodelJob() {
this.buildSynchronizeMetamodelJob().schedule();
}
protected Job buildSynchronizeMetamodelJob() {
Job job = this.buildSynchronizeMetamodelJob_();
// lock the project so we are synchronized with the JPA project manager
job.setRule(this.project);
return job;
}
protected Job buildSynchronizeMetamodelJob_() {
return new SynchronizeMetamodelJob(this.buildSynchronizeMetamodelJobName());
}
protected String buildSynchronizeMetamodelJobName() {
return NLS.bind(JptJpaCoreMessages.METAMODEL_SYNC_JOB_NAME, this.getName());
}
/**
* Use a {@link WorkspaceJob} to
* suppress the resource change events until we are finished synchronizing
* the metamodel.
*/
protected class SynchronizeMetamodelJob
extends WorkspaceJob
{
protected SynchronizeMetamodelJob(String name) {
super(name);
}
@Override
public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
return AbstractJpaProject.this.synchronizeMetamodel_(monitor);
}
@Override
public String toString() {
return ObjectTools.toString(this, AbstractJpaProject.this);
}
}
/**
* Called by the {@link SynchronizeMetamodelJob#runInWorkspace(IProgressMonitor)}.
*/
protected IStatus synchronizeMetamodel_(IProgressMonitor monitor) {
return ((JpaContextRoot2_0) this.contextRoot).synchronizeMetamodel(monitor);
}
protected void disposeMetamodel() {
((JpaContextRoot2_0) this.contextRoot).disposeMetamodel();
}
public IPackageFragmentRoot getMetamodelPackageFragmentRoot() {
return this.getJavaProject().getPackageFragmentRoot(this.getMetaModelSourceFolder());
}
protected IFolder getMetaModelSourceFolder() {
return this.project.getFolder(this.metamodelSourceFolderName);
}
/**
* If the metamodel source folder is no longer a Java project source
* folder, clear it out.
*/
protected void checkMetamodelSourceFolderName() {
if (this.metamodelSoureFolderNameIsInvalid()) {
this.setMetamodelSourceFolderName(null);
}
}
protected boolean metamodelSoureFolderNameIsInvalid() {
return ! this.metamodelSourceFolderNameIsValid();
}
protected boolean metamodelSourceFolderNameIsValid() {
return IterableTools.contains(this.getJavaSourceFolderNames(), this.metamodelSourceFolderName);
}
// ********** Java source folder names **********
public Iterable<String> getJavaSourceFolderNames() {
try {
return this.getJavaSourceFolderNames_();
} catch (JavaModelException ex) {
JptJpaCorePlugin.instance().logError(ex);
return EmptyIterable.instance();
}
}
protected Iterable<String> getJavaSourceFolderNames_() throws JavaModelException {
return IterableTools.transform(this.getJavaSourceFolders(), PACKAGE_FRAGMENT_ROOT_PATH_TRANSFORMER);
}
protected static final Transformer<IPackageFragmentRoot, String> PACKAGE_FRAGMENT_ROOT_PATH_TRANSFORMER = new PackageFragmentRootPathTransformer();
public static class PackageFragmentRootPathTransformer
extends TransformerAdapter<IPackageFragmentRoot, String>
{
@Override
public String transform(IPackageFragmentRoot pfr) {
try {
return this.transform_(pfr);
} catch (JavaModelException ex) {
return "Error: " + pfr.getPath(); //$NON-NLS-1$
}
}
protected String transform_(IPackageFragmentRoot pfr) throws JavaModelException {
return pfr.getUnderlyingResource().getProjectRelativePath().toString();
}
}
protected Iterable<IPackageFragmentRoot> getJavaSourceFolders() throws JavaModelException {
return IterableTools.filter(
this.getPackageFragmentRoots(),
PackageFragmentRootTools.IS_SOURCE_FOLDER
);
}
protected Iterable<IPackageFragmentRoot> getPackageFragmentRoots() throws JavaModelException {
return IterableTools.iterable(this.getJavaProject().getPackageFragmentRoots());
}
// ********** Java events **********
// TODO handle changes to external projects
public void javaElementChanged(ElementChangedEvent event) {
this.processJavaDelta(event.getDelta());
}
/**
* We recurse back here from {@link #processJavaDeltaChildren(IJavaElementDelta)}.
*
* <br> <b>
* This code has been copied and modified in InternalJpaProjectManager, so make sure
* to make changes in both locations.
* </b>
* @see InternalJpaProjectManager#javaElementChanged(ElementChangedEvent)
*/
protected void processJavaDelta(IJavaElementDelta delta) {
switch (delta.getElement().getElementType()) {
case IJavaElement.JAVA_MODEL :
this.processJavaModelDelta(delta);
break;
case IJavaElement.JAVA_PROJECT :
this.processJavaProjectDelta(delta);
break;
case IJavaElement.PACKAGE_FRAGMENT_ROOT :
this.processJavaPackageFragmentRootDelta(delta);
break;
case IJavaElement.PACKAGE_FRAGMENT :
this.processJavaPackageFragmentDelta(delta);
break;
case IJavaElement.COMPILATION_UNIT :
this.processJavaCompilationUnitDelta(delta);
break;
default :
break; // ignore the elements inside a compilation unit
}
}
protected void processJavaDeltaChildren(IJavaElementDelta delta) {
for (IJavaElementDelta child : delta.getAffectedChildren()) {
this.processJavaDelta(child); // recurse
}
}
/**
* Return whether the specified Java element delta is for a
* {@link IJavaElementDelta#CHANGED CHANGED}
* (as opposed to {@link IJavaElementDelta#ADDED ADDED} or
* {@link IJavaElementDelta#REMOVED REMOVED}) Java element
* and the specified flag is set.
* (The delta flags are only significant if the delta
* {@link IJavaElementDelta#getKind() kind} is
* {@link IJavaElementDelta#CHANGED CHANGED}.)
*/
protected static boolean deltaFlagIsSet(IJavaElementDelta delta, int flag) {
return (delta.getKind() == IJavaElementDelta.CHANGED) &&
BitTools.flagIsSet(delta.getFlags(), flag);
}
// ***** model
protected void processJavaModelDelta(IJavaElementDelta delta) {
// process the Java model's projects
this.processJavaDeltaChildren(delta);
}
// ***** project
protected void processJavaProjectDelta(IJavaElementDelta delta) {
// process the Java project's package fragment roots
this.processJavaDeltaChildren(delta);
// a classpath change can have pretty far-reaching effects...
if (classpathHasChanged(delta)) {
this.rebuild((IJavaProject) delta.getElement());
}
}
/**
* The specified Java project's classpath changed. Rebuild the JPA project
* as appropriate.
*/
protected void rebuild(IJavaProject javaProject) {
// if the classpath has changed, we need to update everything since
// class references could now be resolved (or not) etc.
if (javaProject.equals(this.getJavaProject())) {
this.removeDeadJpaFiles();
this.checkMetamodelSourceFolderName();
this.synchronizeWithJavaSource(this.getInternalJavaResourceCompilationUnits());
} else {
// TODO see if changed project is on our classpath?
this.synchronizeWithJavaSource(this.getExternalJavaResourceCompilationUnits());
}
}
/**
* Loop through all our JPA files, remove any that are no longer on the
* classpath.
*/
protected void removeDeadJpaFiles() {
for (JpaFile jpaFile : this.getJpaFiles()) {
if (this.jpaFileIsDead(jpaFile)) {
this.removeJpaFile(jpaFile);
}
}
}
protected boolean jpaFileIsDead(JpaFile jpaFile) {
return ! this.jpaFileIsAlive(jpaFile);
}
/**
* Sometimes (e.g. during tests), when a project has been deleted, we get a
* Java change event that indicates the Java project is CHANGED (as
* opposed to REMOVED, which is what typically happens). The event's delta
* indicates that everything in the Java project has been deleted and the
* classpath has changed. All entries in the classpath have been removed;
* but single entry for the Java project's root folder has been added. (!)
* This means any file in the project is on the Java project's classpath.
* This classpath change is what triggers us to rebuild the JPA project; so
* we put an extra check here to make sure the JPA file's resource file is
* still present.
* <p>
* This would not be a problem if Dali received the resource change event
* <em>before</em> JDT and simply removed the JPA project; but JDT receives
* the resource change event first and converts it into the problematic
* Java change event....
*/
protected boolean jpaFileIsAlive(JpaFile jpaFile) {
IFile file = jpaFile.getFile();
if ( ! file.exists()) {
return false;
}
if (this.fileIsJavaRelated(file)) {
return this.getJavaProject().isOnClasspath(file);
}
return this.fileResourceLocationIsValid(file);
}
/**
* pre-condition:
* delta.getElement().getElementType() == IJavaElement.JAVA_PROJECT
*/
protected static boolean classpathHasChanged(IJavaElementDelta delta) {
return deltaFlagIsSet(delta, IJavaElementDelta.F_RESOLVED_CLASSPATH_CHANGED);
}
protected void synchronizeWithJavaSource(Iterable<JavaResourceCompilationUnit> javaResourceCompilationUnits) {
for (JavaResourceCompilationUnit javaResourceCompilationUnit : javaResourceCompilationUnits) {
javaResourceCompilationUnit.synchronizeWithJavaSource();
}
}
// ***** package fragment root
protected void processJavaPackageFragmentRootDelta(IJavaElementDelta delta) {
// process the Java package fragment root's package fragments
this.processJavaDeltaChildren(delta);
if (classpathEntryHasBeenAdded(delta)) {
// TODO bug 277218
} else if (classpathEntryHasBeenRemoved(delta)) { // should be mutually-exclusive w/added (?)
// TODO bug 277218
}
}
/**
* pre-condition:
* delta.getElement().getElementType() == IJavaElement.PACKAGE_FRAGMENT_ROOT
*/
protected static boolean classpathEntryHasBeenAdded(IJavaElementDelta delta) {
return deltaFlagIsSet(delta, IJavaElementDelta.F_ADDED_TO_CLASSPATH);
}
/**
* pre-condition:
* delta.getElement().getElementType() == IJavaElement.PACKAGE_FRAGMENT_ROOT
*/
protected static boolean classpathEntryHasBeenRemoved(IJavaElementDelta delta) {
return deltaFlagIsSet(delta, IJavaElementDelta.F_REMOVED_FROM_CLASSPATH);
}
// ***** package fragment
protected void processJavaPackageFragmentDelta(IJavaElementDelta delta) {
// process the java package fragment's compilation units
this.processJavaDeltaChildren(delta);
}
// ***** compilation unit
protected void processJavaCompilationUnitDelta(IJavaElementDelta delta) {
if (javaCompilationUnitDeltaIsRelevant(delta)) {
ICompilationUnit compilationUnit = (ICompilationUnit) delta.getElement();
for (JavaResourceCompilationUnit jrcu : this.getCombinedJavaResourceCompilationUnits()) {
if (jrcu.getCompilationUnit().equals(compilationUnit)) {
CompilationUnit astRoot = delta.getCompilationUnitAST(); //this will be null for POST_CHANGE jdt events
//I don't think we can guarantee hasResolvedBindings() is true, so check for this and don't pass it in if false.
if (astRoot != null && astRoot.getAST().hasResolvedBindings()) {
jrcu.synchronizeWithJavaSource(astRoot);
}
else {
jrcu.synchronizeWithJavaSource();
}
// TODO ? this.resolveJavaTypes(); // might have new member types now...
break; // there *shouldn't* be any more...
}
}
}
// ignore the java compilation unit's children
}
protected static boolean javaCompilationUnitDeltaIsRelevant(IJavaElementDelta delta) {
// ignore Java notification for ADDED or REMOVED;
// these are handled via resource notification
if (delta.getKind() != IJavaElementDelta.CHANGED) {
return false;
}
// ignore changes to/from primary working copy - no content has changed;
// and make sure there are no other flags set that indicate *both* a
// change to/from primary working copy *and* content has changed
if (BitTools.onlyFlagIsSet(delta.getFlags(), IJavaElementDelta.F_PRIMARY_WORKING_COPY)) {
return false;
}
// ignore when the compilation unit's resource is deleted;
// because the AST parser will log an exception for the missing file.
// IJavaElementDelta.F_PRIMARY_RESOURCE is the only flag set when refactor
// renaming a type directly in the java editor when the file is modified..
// IJavaElementDelta.F_AST_AFFECTED is the only flag set when I refactor
// rename a type directly in the java editor and the file is *not* modified.
if (BitTools.onlyFlagIsSet(delta.getFlags(), IJavaElementDelta.F_PRIMARY_RESOURCE)
|| BitTools.onlyFlagIsSet(delta.getFlags(), IJavaElementDelta.F_AST_AFFECTED)) {
ICompilationUnit compilationUnit = (ICompilationUnit) delta.getElement();
if ( !compilationUnitResourceExists(compilationUnit)) {
return false;
}
}
return true;
}
protected static boolean compilationUnitResourceExists(ICompilationUnit compilationUnit) {
try {
return compilationUnit.getCorrespondingResource().exists();
} catch (JavaModelException ex) {
return false;
}
}
// ********** validation **********
public Iterable<IMessage> getValidationMessages(IReporter reporter) {
ArrayList<IMessage> messages = new ArrayList<>();
this.validate(messages, reporter);
return messages;
}
// TODO about the only use for the reporter is to check for cancellation;
// we should check for cancellation...
protected void validate(List<IMessage> messages, IReporter reporter) {
if (reporter.isCancelled()) {
throw new ValidationCancelledException();
}
this.validateLibraryProvider(messages);
this.validateConnection(messages);
this.contextRoot.validate(messages, reporter);
}
protected void validateLibraryProvider(List<IMessage> messages) {
try {
this.validateLibraryProvider_(messages);
} catch (CoreException ex) {
JptJpaCorePlugin.instance().logError(ex);
}
}
protected void validateLibraryProvider_(List<IMessage> messages) throws CoreException {
Map<String, Object> enablementVariables = new HashMap<>();
enablementVariables.put(JpaLibraryProviderInstallOperationConfig.JPA_PLATFORM_ENABLEMENT_EXP, this.getJpaPlatform().getId());
enablementVariables.put(JpaLibraryProviderInstallOperationConfig.JPA_PLATFORM_DESCRIPTION_ENABLEMENT_EXP, this.getJpaPlatform().getConfig());
ILibraryProvider libraryProvider = LibraryProviderFramework.getCurrentProvider(this.project, JpaProject.FACET);
IFacetedProject facetedProject = ProjectFacetsManager.create(this.project);
IProjectFacetVersion facetVersion = facetedProject.getInstalledVersion(JpaProject.FACET);
if ( ! libraryProvider.isEnabledFor(facetedProject, facetVersion, enablementVariables)) {
messages.add(
ValidationMessageTools.buildValidationMessage(
this.getResource(),
JptJpaCoreValidationMessages.PROJECT_INVALID_LIBRARY_PROVIDER
)
);
}
}
protected void validateConnection(List<IMessage> messages) {
String cpName = this.dataSource.getConnectionProfileName();
if (StringTools.isBlank(cpName)) {
messages.add(
ValidationMessageTools.buildValidationMessage(
this.getResource(),
JptJpaCoreValidationMessages.PROJECT_NO_CONNECTION
)
);
return;
}
ConnectionProfile cp = this.dataSource.getConnectionProfile();
if (cp == null) {
messages.add(
ValidationMessageTools.buildValidationMessage(
this.getResource(),
JptJpaCoreValidationMessages.PROJECT_INVALID_CONNECTION,
cpName
)
);
return;
}
if (cp.isInactive()) {
messages.add(
ValidationMessageTools.buildValidationMessage(
this.getResource(),
JptJpaCoreValidationMessages.PROJECT_INACTIVE_CONNECTION,
cpName
)
);
}
}
// ********** dispose **********
public void dispose() {
this.stopCommand(this.synchronizeContextModelCommand);
this.stopCommand(this.updateCommand);
this.updateCommand.removeListener(this.updateCommandListener);
this.dataSource.dispose();
// the XML resources are held indefinitely by the WTP translator framework,
// so we better remove our listener or the JPA project will not be GCed
for (JpaFile jpaFile : this.getJpaFiles()) {
jpaFile.getResourceModel().removeResourceModelListener(this.resourceModelListener);
}
}
protected void stopCommand(RepeatingJobCommand command) {
try {
command.stop();
} catch (InterruptedException ex) {
// allow the dispose to complete;
// but let our thread know it was interrupted during a wait
Thread.currentThread().interrupt();
}
}
// ********** resource model listener **********
protected JptResourceModelListener buildResourceModelListener() {
return new ResourceModelListener();
}
protected class ResourceModelListener
implements JptResourceModelListener
{
protected ResourceModelListener() {
super();
}
public void resourceModelChanged(JptResourceModel jpaResourceModel) {
// String msg = Thread.currentThread() + " resource model change: " + jpaResourceModel;
// System.out.println(msg);
// new Exception(msg).printStackTrace(System.out);
AbstractJpaProject.this.synchronizeContextModel(jpaResourceModel);
}
public void resourceModelReverted(JptResourceModel jpaResourceModel) {
IFile file = WorkbenchResourceHelper.getFile((JptXmlResource)jpaResourceModel);
AbstractJpaProject.this.removeJpaFile(file);
AbstractJpaProject.this.addJpaFileMaybe(file);
}
public void resourceModelUnloaded(JptResourceModel jpaResourceModel) {
IFile file = WorkbenchResourceHelper.getFile((JptXmlResource)jpaResourceModel);
AbstractJpaProject.this.removeJpaFile(file);
if (file.exists()) { //false if file delete caused the unload event
//go ahead and re-add the JPA file here, otherwise a resource change event
//will cause it to be added.
AbstractJpaProject.this.addJpaFileMaybe(file);
}
}
@Override
public String toString() {
return ObjectTools.toString(this, AbstractJpaProject.this);
}
}
/**
* Called from {@link ResourceModelListener#resourceModelChanged(JptResourceModel)}.
*/
// TODO pass down the resource model (for a possible optimization?)
protected void synchronizeContextModel(@SuppressWarnings("unused") JptResourceModel jpaResourceModel) {
this.synchronizeContextModel();
}
// ********** resource events **********
// TODO need to do the same thing for external projects and compilation units
public void projectChanged(IResourceDelta delta) {
if (delta.getResource().equals(this.project)) {
this.internalProjectChanged(delta);
} else {
this.externalProjectChanged(delta);
}
}
protected void internalProjectChanged(IResourceDelta delta) {
ResourceDeltaVisitor resourceDeltaVisitor = this.buildInternalResourceDeltaVisitor();
resourceDeltaVisitor.visitDelta(delta);
// at this point, if we have added and/or removed JpaFiles, an "update" will have been triggered;
// any changes to the resource model during the "resolve" will trigger further "updates";
// there should be no need to "resolve" external Java types (they can't have references to
// the internal Java types)
if (resourceDeltaVisitor.encounteredSignificantChange()) {
this.resolveInternalJavaTypes();
}
}
protected ResourceDeltaVisitor buildInternalResourceDeltaVisitor() {
return new InternalResourceDeltaVisitor();
}
protected class InternalResourceDeltaVisitor
extends ResourceDeltaVisitor
{
protected InternalResourceDeltaVisitor() {
super();
}
@Override
public boolean fileChangeIsSignificant(IFile file, int deltaKind) {
return AbstractJpaProject.this.synchronizeJpaFiles(file, deltaKind);
}
@Override
public String toString() {
return ObjectTools.toString(this, AbstractJpaProject.this);
}
}
/**
* Internal resource delta visitor callback.
* Return true if a JpaFile was either added or removed.
*/
protected boolean synchronizeJpaFiles(IFile file, int deltaKind) {
switch (deltaKind) {
case IResourceDelta.ADDED :
return this.addJpaFileMaybe(file);
case IResourceDelta.REMOVED :
return this.removeJpaFile(file);
case IResourceDelta.CHANGED :
return this.checkForChangedFileContent(file);
case IResourceDelta.ADDED_PHANTOM :
break; // ignore
case IResourceDelta.REMOVED_PHANTOM :
break; // ignore
default :
break; // only worried about added/removed/changed files
}
return false;
}
protected boolean checkForChangedFileContent(IFile file) {
JpaFile jpaFile = this.getJpaFile(file);
if (jpaFile == null) {
// the file might have changed its content to something significant to Dali
return this.addJpaFileMaybe(file);
}
if (jpaFile.getContentType().equals(getContentType(file))) {
// content has not changed - ignore
return false;
}
// the content type changed, we need to remove the old JPA file and build a new one
// (e.g. the schema of an orm.xml file changed from JPA to EclipseLink)
this.removeJpaFile(jpaFile);
this.addJpaFileMaybe(file);
return true; // at the least, we have removed a JPA file
}
protected void resolveInternalJavaTypes() {
for (JavaResourceCompilationUnit jrcu : this.getInternalJavaResourceCompilationUnits()) {
jrcu.resolveTypes();
}
}
protected void externalProjectChanged(IResourceDelta delta) {
if (this.getJavaProject().isOnClasspath(delta.getResource())) {
ResourceDeltaVisitor resourceDeltaVisitor = this.buildExternalResourceDeltaVisitor();
resourceDeltaVisitor.visitDelta(delta);
// force an "update" here since adding and/or removing an external Java type
// will only trigger an "update" if the "resolve" causes something in the resource model to change
if (resourceDeltaVisitor.encounteredSignificantChange()) {
this.update();
this.resolveExternalJavaTypes();
this.resolveInternalJavaTypes();
}
}
}
protected ResourceDeltaVisitor buildExternalResourceDeltaVisitor() {
return new ExternalResourceDeltaVisitor();
}
protected class ExternalResourceDeltaVisitor
extends ResourceDeltaVisitor
{
protected ExternalResourceDeltaVisitor() {
super();
}
@Override
public boolean fileChangeIsSignificant(IFile file, int deltaKind) {
return AbstractJpaProject.this.synchronizeExternalFiles(file, deltaKind);
}
@Override
public String toString() {
return ObjectTools.toString(this, AbstractJpaProject.this);
}
}
/**
* external resource delta visitor callback
* Return true if an "external" Java resource compilation unit
* was added or removed.
*/
protected boolean synchronizeExternalFiles(IFile file, int deltaKind) {
switch (deltaKind) {
case IResourceDelta.ADDED :
return this.externalFileAdded(file);
case IResourceDelta.REMOVED :
return this.externalFileRemoved(file);
case IResourceDelta.CHANGED :
break; // ignore
case IResourceDelta.ADDED_PHANTOM :
break; // ignore
case IResourceDelta.REMOVED_PHANTOM :
break; // ignore
default :
break; // only worried about added/removed/changed files
}
return false;
}
protected boolean externalFileAdded(IFile file) {
IContentType contentType = getContentType(file);
if (contentType == null) {
return false;
}
if (contentType.equals(JavaResourceCompilationUnit.CONTENT_TYPE)) {
return true;
}
if (contentType.equals(JavaResourcePackageFragmentRoot.JAR_CONTENT_TYPE)) {
return true;
}
return false;
}
protected boolean externalFileRemoved(IFile file) {
IContentType contentType = getContentType(file);
if (contentType == null) {
return false;
}
if (contentType.equals(JavaResourceCompilationUnit.CONTENT_TYPE)) {
return this.removeExternalJavaResourceCompilationUnit(file);
}
if (contentType.equals(JavaResourcePackageFragmentRoot.JAR_CONTENT_TYPE)) {
return this.externalJavaResourceTypeCache.removeTypes(file);
}
return false;
}
protected IContentType getContentType(IFile file) {
return this.getJpaPlatform().getContentType(file);
}
protected void resolveExternalJavaTypes() {
for (JavaResourceCompilationUnit jrcu : this.getExternalJavaResourceCompilationUnits()) {
jrcu.resolveTypes();
}
}
// ***** resource delta visitors
/**
* add or remove a JPA file for every [appropriate] file encountered by the visitor
*/
protected abstract class ResourceDeltaVisitor
implements IResourceDeltaVisitor
{
protected boolean encounteredSignificantChange = false;
protected ResourceDeltaVisitor() {
super();
}
protected void visitDelta(IResourceDelta delta) {
try {
delta.accept(this);
} catch (CoreException ex) {
// shouldn't happen - we don't throw any CoreExceptions
throw new RuntimeException(ex);
}
}
public boolean visit(IResourceDelta delta) {
IResource res = delta.getResource();
switch (res.getType()) {
case IResource.ROOT :
return true; // visit children
case IResource.PROJECT :
return true; // visit children
case IResource.FOLDER :
return true; // visit children
case IResource.FILE :
this.fileChanged((IFile) res, delta.getKind());
return false; // no children
default :
return false; // no children (probably shouldn't get here...)
}
}
protected void fileChanged(IFile file, int deltaKind) {
if (this.fileChangeIsSignificant(file, deltaKind)) {
this.encounteredSignificantChange = true;
}
}
protected abstract boolean fileChangeIsSignificant(IFile file, int deltaKind);
/**
* Return whether the visitor encountered some sort of "significant"
* change while traversing the IResourceDelta
* (e.g. a JPA file was added or removed).
*/
protected boolean encounteredSignificantChange() {
return this.encounteredSignificantChange;
}
@Override
public String toString() {
return ObjectTools.toString(this, AbstractJpaProject.this);
}
}
// ********** synchronize context model with resource model **********
protected RepeatingJobCommand buildSynchronizeContextModelCommand() {
return new RepeatingJobCommandWrapper(
this.buildSynchronizeContextModelJobCommand(),
this.buildStartSynchronizeContextModelJobCommandContext(),
JptJpaCorePlugin.instance().getExceptionHandler()
);
}
protected JobCommand buildSynchronizeContextModelJobCommand() {
return new SynchronizeContextModelJobCommand();
}
protected JobCommandContext buildStartSynchronizeContextModelJobCommandContext() {
return new ManagerJobCommandContext(this.buildSynchronizeContextModelJobName());
}
protected String buildSynchronizeContextModelJobName() {
return NLS.bind(JptJpaCoreMessages.CONTEXT_MODEL_SYNC_JOB_NAME, this.getName());
}
/**
* The JPA project's resource model has changed; synchronize the JPA
* project's context model with it. This method is typically called when the
* resource model state has changed when it is synchronized with its
* underlying Eclipse resource as the result of an Eclipse resource change
* event. This method can also be called when a client (e.g. a JUnit test
* case) has manipulated the resource model via its API (as opposed to
* modifying the underlying Eclipse resource directly) and needs the context
* model to be synchronized accordingly (since manipulating the resource
* model via its API will not trigger this method). Whether the context
* model is synchronously (or asynchronously) depends on the JPA project
* manager.
*/
public void synchronizeContextModel() {
try {
this.synchronizingContextModel = true;
this.synchronizeContextModelCommand.execute(null); // this progress monitor is ignored
} finally {
this.synchronizingContextModel = false;
}
// There are some changes to the resource model that will not change
// the existing context model and trigger an update (e.g. adding an
// @Entity annotation when the JPA project is automatically
// discovering annotated classes); so we explicitly execute an update
// here to discover those changes.
// TODO change sync so it will *always* trigger an update?
this.update();
}
protected class SynchronizeContextModelJobCommand
implements JobCommand
{
public IStatus execute(IProgressMonitor monitor) {
return AbstractJpaProject.this.synchronizeContextModel(monitor);
}
@Override
public String toString() {
return ObjectTools.toString(this, AbstractJpaProject.this);
}
}
/**
* Called by the {@link SynchronizeContextModelJobCommand#execute(IProgressMonitor)}.
*/
protected IStatus synchronizeContextModel(IProgressMonitor monitor) {
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
this.contextRoot.synchronizeWithResourceModel(monitor);
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
return Status.OK_STATUS;
}
// ********** JPA project "update" **********
/**
* The first update is executed synchronously during construction.
* Once that is complete, we delegate to the JPA project manager.
*/
protected NotifyingRepeatingJobCommand buildTempUpdateCommand(IProgressMonitor monitor) {
return new NotifyingRepeatingJobCommandWrapper(
this.buildUpdateJobCommand(),
this.buildTempStartUpdateJobCommandContext(monitor),
JptJpaCorePlugin.instance().getExceptionHandler()
);
}
protected JobCommandContext buildTempStartUpdateJobCommandContext(IProgressMonitor monitor) {
return new TempUpdateJobCommandContext(monitor);
}
protected NotifyingRepeatingJobCommand buildUpdateCommand() {
return new NotifyingRepeatingJobCommandWrapper(
this.buildUpdateJobCommand(),
this.buildStartUpdateJobCommandContext(),
JptJpaCorePlugin.instance().getExceptionHandler()
);
}
protected JobCommand buildUpdateJobCommand() {
return new UpdateJobCommand();
}
protected JobCommandContext buildStartUpdateJobCommandContext() {
return new ManagerJobCommandContext(this.buildUpdateJobName());
}
protected String buildUpdateJobName() {
return NLS.bind(JptJpaCoreMessages.UPDATE_JOB_NAME, this.getName());
}
@Override
public void stateChanged() {
super.stateChanged();
this.update();
}
/**
* The JPA project's state has changed, "update" those parts of the
* JPA project that are dependent on other parts of the JPA project.
* <p>
* Delegate to the JPA project manager so clients can configure how
* updates occur.
* <p>
* Ignore any <em>updates</em> that occur while we are synchronizing
* the context model with the resource model because we will <em>update</em>
* the context model at the completion of the <em>sync</em>. This is really
* only useful for synchronous <em>syncs</em> and <em>updates</em>; since
* the job scheduling rules will prevent the <em>sync</em> and
* <em>update</em> jobs from running concurrently.
*/
protected void update() {
if ( ! this.synchronizingContextModel) {
this.updateCommand.execute(null); // this progress monitor is ignored
}
}
protected class UpdateJobCommand
implements JobCommand
{
public IStatus execute(IProgressMonitor monitor) {
return AbstractJpaProject.this.update(monitor);
}
@Override
public String toString() {
return ObjectTools.toString(this, AbstractJpaProject.this);
}
}
/**
* Called by the {@link UpdateJobCommand#execute(IProgressMonitor)}.
*/
protected IStatus update(IProgressMonitor monitor) {
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
this.contextRoot.update(monitor);
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
this.updateRootStructureNodes(monitor);
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
return Status.OK_STATUS;
}
protected void updateRootStructureNodes(IProgressMonitor monitor) {
for (JpaFile jpaFile : this.getJpaFiles()) {
if (monitor.isCanceled()) {
return;
}
jpaFile.updateRootStructureNodes();
}
}
// ********** update command listener **********
protected NotifyingRepeatingJobCommand.Listener buildUpdateCommandListener() {
return new UpdateCommandListener();
}
protected class UpdateCommandListener
implements NotifyingRepeatingJobCommand.Listener
{
public void executionQuiesced(JobCommand command) {
AbstractJpaProject.this.updateQuiesced();
}
public void executionCanceled(JobCommand command) {
AbstractJpaProject.this.updateCanceled();
}
@Override
public String toString() {
return ObjectTools.toString(this, AbstractJpaProject.this);
}
}
/**
* This is the callback used by the update command to notify the JPA
* project that the "update" has quiesced (i.e. the "update" has completed
* and there are no outstanding requests for further "updates").
* Called by {@link UpdateCommandListener#executionQuiesced(JobCommand)}.
*/
protected void updateQuiesced() {
this.synchronizeMetamodel();
}
/**
* This is the callback used by the update command to notify the JPA
* project that the "update" has been canceled (typically by the user).
* Called by {@link UpdateCommandListener#executionCanceled(JobCommand)}.
*/
protected void updateCanceled() {
// NOP
}
// ********** job command context **********
/**
* Delegate execution to the JPA project manager, which will determine
* whether commands are executed synchronously or asynchronously.
*
* @see #buildStartSynchronizeContextModelJobCommandContext()
* @see #buildStartUpdateJobCommandContext()
*/
protected class ManagerJobCommandContext
implements JobCommandContext
{
protected final String defaultJobName;
protected ManagerJobCommandContext(String defaultJobName) {
super();
if (defaultJobName == null) {
throw new NullPointerException();
}
this.defaultJobName = defaultJobName;
}
/**
* This should be the only method called on this context....
*/
public void execute(JobCommand command) {
this.execute(command, this.defaultJobName);
}
public void execute(JobCommand command, String jobName) {
AbstractJpaProject.this.manager.execute(command, jobName, AbstractJpaProject.this);
}
public void execute(JobCommand command, String jobName, ISchedulingRule schedulingRule) {
// the JPA project manager will supply the scheduling rule
this.execute(command, jobName);
}
@Override
public String toString() {
return ObjectTools.toString(this, this.defaultJobName);
}
}
// ********** temp update job command context **********
/**
* Pass through a progress monitor.
* <br>
* <strong>NB:</strong> One-time use only.
*
* @see #buildTempStartUpdateJobCommandContext(IProgressMonitor)
*/
protected class TempUpdateJobCommandContext
implements JobCommandContext
{
protected IProgressMonitor monitor;
protected TempUpdateJobCommandContext(IProgressMonitor monitor) {
super();
if (monitor == null) {
throw new NullPointerException();
}
this.monitor = monitor;
}
public void execute(JobCommand command) {
IProgressMonitor m = null;
synchronized (this) {
if (this.monitor == null) {
throw new NullPointerException();
}
m = this.monitor;
this.monitor = null; // one-time use...
}
command.execute(m);
}
public void execute(JobCommand command, String jobName) {
this.execute(command);
}
public void execute(JobCommand command, String jobName, ISchedulingRule schedulingRule) {
this.execute(command);
}
@Override
public String toString() {
return ObjectTools.toString(this, this.monitor);
}
}
}