blob: da6eb5bc3eecc7a44bc349dfdeac4f210cf5913f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2008 Oracle. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0, which accompanies this distribution
* and is available at http://www.eclipse.org/legal/epl-v10.html.
*
* Contributors:
* Oracle - initial API and implementation
******************************************************************************/
package org.eclipse.jpt.core.internal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
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.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.resources.IResourceProxyVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jpt.core.JpaDataSource;
import org.eclipse.jpt.core.JpaFile;
import org.eclipse.jpt.core.JpaPlatform;
import org.eclipse.jpt.core.JpaProject;
import org.eclipse.jpt.core.JptCorePlugin;
import org.eclipse.jpt.core.ResourceModel;
import org.eclipse.jpt.core.ResourceModelListener;
import org.eclipse.jpt.core.context.JpaRootContextNode;
import org.eclipse.jpt.core.internal.validation.DefaultJpaValidationMessages;
import org.eclipse.jpt.core.internal.validation.JpaValidationMessages;
import org.eclipse.jpt.core.resource.java.JavaResourceModel;
import org.eclipse.jpt.core.resource.java.JavaResourcePersistentType;
import org.eclipse.jpt.core.resource.java.JpaCompilationUnit;
import org.eclipse.jpt.db.ConnectionProfile;
import org.eclipse.jpt.db.Schema;
import org.eclipse.jpt.utility.CommandExecutor;
import org.eclipse.jpt.utility.CommandExecutorProvider;
import org.eclipse.jpt.utility.internal.CollectionTools;
import org.eclipse.jpt.utility.internal.iterators.CloneIterator;
import org.eclipse.jpt.utility.internal.iterators.CompositeIterator;
import org.eclipse.jpt.utility.internal.iterators.EmptyIterator;
import org.eclipse.jpt.utility.internal.iterators.FilteringIterator;
import org.eclipse.jpt.utility.internal.iterators.TransformationIterator;
import org.eclipse.jst.j2ee.internal.J2EEConstants;
import org.eclipse.wst.common.componentcore.internal.util.IModuleConstants;
import org.eclipse.wst.validation.internal.provisional.core.IMessage;
/**
*
*/
public class GenericJpaProject extends AbstractJpaNode implements JpaProject {
/**
* The Eclipse project corresponding to the JPA project.
*/
protected final IProject project;
/**
* The vendor-specific JPA platform that builds the JPA project
* and all its contents.
*/
protected final JpaPlatform jpaPlatform;
/**
* The data source that wraps the DTP model.
*/
protected final JpaDataSource dataSource;
/**
* A schema name used to override the connection's default schema
*/
protected String userOverrideDefaultSchemaName;
/**
* Flag indicating whether the project should "discover" annotated
* classes automatically, as opposed to requiring the classes to be
* listed in persistence.xml.
*/
protected boolean discoversAnnotatedClasses;
/**
* The JPA files associated with the JPA project.
*/
protected final Vector<JpaFile> jpaFiles;
/**
* The root of the model representing the collated resources associated with
* the JPA project.
*/
protected JpaRootContextNode rootContextNode;
/**
* Support for modifying documents shared with the UI.
*/
protected final ThreadLocal<CommandExecutor> threadLocalModifySharedDocumentCommandExecutor;
protected final CommandExecutorProvider modifySharedDocumentCommandExecutorProvider;
/**
* A pluggable updater that can be used to "update" the project either
* synchronously or asynchronously (or not at all). An asynchronous
* updater is the default and is used when the project is being manipulated
* by the UI. A synchronous updater is used when the project is being
* manipulated by a "batch" (or non-UI) client (e.g. when testing the
* "update" behavior). A null updater is used during tests that
* do not care whether "updates" occur. Clients will need to explicitly
* configure the updater if they require something other than an
* asynchronous updater.
*/
protected Updater updater;
/**
* Resource models notify this listener when they change. A project update
* should occur any time a resource model changes.
*/
protected ResourceModelListener resourceModelListener;
// ********** constructor/initialization **********
/**
* The project and JPA platform are required.
*/
public GenericJpaProject(JpaProject.Config config) throws CoreException {
super(null); // JPA project is the root of the containment tree
if ((config.getProject() == null) || (config.getJpaPlatform() == null)) {
throw new NullPointerException();
}
this.project = config.getProject();
this.jpaPlatform = config.getJpaPlatform();
this.dataSource = this.getJpaFactory().buildJpaDataSource(this, config.getConnectionProfileName());
this.userOverrideDefaultSchemaName = config.getUserOverrideDefaultSchemaName();
this.discoversAnnotatedClasses = config.discoverAnnotatedClasses();
this.jpaFiles = this.buildEmptyJpaFiles();
this.threadLocalModifySharedDocumentCommandExecutor = this.buildThreadLocalModifySharedDocumentCommandExecutor();
this.modifySharedDocumentCommandExecutorProvider = this.buildModifySharedDocumentCommandExecutorProvider();
this.resourceModelListener = this.buildResourceModelListener();
// build the JPA files corresponding to the Eclipse project's files
this.project.accept(this.buildInitialResourceProxyVisitor(), IResource.NONE);
this.rootContextNode = this.buildRootContextNode();
}
@Override
protected boolean requiresParent() {
return false;
}
@Override
public IResource getResource() {
return getProject();
}
protected Vector<JpaFile> buildEmptyJpaFiles() {
return new Vector<JpaFile>();
}
protected ResourceDeltaVisitor buildResourceDeltaVisitor() {
return new ResourceDeltaVisitor();
}
protected ThreadLocal<CommandExecutor> buildThreadLocalModifySharedDocumentCommandExecutor() {
return new ThreadLocal<CommandExecutor>();
}
protected CommandExecutorProvider buildModifySharedDocumentCommandExecutorProvider() {
return new ModifySharedDocumentCommandExecutorProvider();
}
protected ResourceModelListener buildResourceModelListener() {
return new DefaultResourceModelListener();
}
protected IResourceProxyVisitor buildInitialResourceProxyVisitor() {
return new InitialResourceProxyVisitor();
}
protected JpaRootContextNode buildRootContextNode() {
return this.getJpaFactory().buildRootContext(this);
}
// ***** inner class
protected class InitialResourceProxyVisitor implements IResourceProxyVisitor {
protected InitialResourceProxyVisitor() {
super();
}
// add a JPA file for every [appropriate] file encountered by the visitor
public boolean visit(IResourceProxy resource) throws CoreException {
switch (resource.getType()) {
case IResource.ROOT : // shouldn't happen
case IResource.PROJECT :
case IResource.FOLDER :
return true; // visit children
case IResource.FILE :
GenericJpaProject.this.addJpaFileInternal((IFile) resource.requestResource());
return false; // no children
default :
return false; // no children
}
}
}
// ********** general queries **********
@Override
public JpaProject getJpaProject() {
return this;
}
public String getName() {
return this.project.getName();
}
@Override
public void toString(StringBuilder sb) {
sb.append(this.getName());
}
public IProject getProject() {
return this.project;
}
public IJavaProject getJavaProject() {
return JavaCore.create(this.project);
}
@Override
public JpaPlatform getJpaPlatform() {
return this.jpaPlatform;
}
public JpaDataSource getDataSource() {
return this.dataSource;
}
@Override
public ConnectionProfile getConnectionProfile() {
return this.dataSource.getConnectionProfile();
}
public Schema getDefaultSchema() {
Schema defaultSchema = getUserOverrideDefaultSchema();
if (defaultSchema != null) {
return defaultSchema;
}
return getConnectionProfile().getDefaultSchema();
}
public Schema getUserOverrideDefaultSchema() {
if (this.userOverrideDefaultSchemaName == null) {
return null;
}
return getConnectionProfile().getDatabase().schemaNamed(this.userOverrideDefaultSchemaName);
}
// **************** user override default schema name **********************
public String getUserOverrideDefaultSchemaName() {
return this.userOverrideDefaultSchemaName;
}
public void setUserOverrideDefaultSchemaName(String newDefaultSchemaName) {
String oldDefaultSchemaName = this.userOverrideDefaultSchemaName;
this.userOverrideDefaultSchemaName = newDefaultSchemaName;
this.firePropertyChanged(USER_OVERRIDE_DEFAULT_SCHEMA_NAME_PROPERTY,
oldDefaultSchemaName, newDefaultSchemaName);
}
// **************** discover annotated classes *****************************
public boolean discoversAnnotatedClasses() {
return this.discoversAnnotatedClasses;
}
public void setDiscoversAnnotatedClasses(boolean discoversAnnotatedClasses) {
boolean old = this.discoversAnnotatedClasses;
this.discoversAnnotatedClasses = discoversAnnotatedClasses;
this.firePropertyChanged(DISCOVERS_ANNOTATED_CLASSES_PROPERTY, old, discoversAnnotatedClasses);
}
// **************** JPA files **********************************************
public Iterator<JpaFile> jpaFiles() {
return new CloneIterator<JpaFile>(this.jpaFiles); // read-only
}
public int jpaFilesSize() {
return this.jpaFiles.size();
}
public JpaFile getJpaFile(IFile file) {
synchronized (this.jpaFiles) {
for (JpaFile jpaFile : this.jpaFiles) {
if (jpaFile.getFile().equals(file)) {
return jpaFile;
}
}
}
return null;
}
public Iterator<JpaFile> jpaFiles(final String resourceType) {
return new FilteringIterator<JpaFile, JpaFile>(this.jpaFiles()) {
@Override
protected boolean accept(JpaFile o) {
return o.getResourceType().equals(resourceType);
}
};
}
/**
* Add a JPA file for the specified file, if appropriate.
* Return true if a JPA File was created and added, false otherwise
*/
protected boolean addJpaFile(IFile file) {
JpaFile jpaFile = this.addJpaFileInternal(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 addJpaFileInternal(IFile file) {
JpaFile jpaFile = this.jpaPlatform.buildJpaFile(this, file);
if (jpaFile == null) {
return null;
}
this.jpaFiles.add(jpaFile);
jpaFile.getResourceModel().addResourceModelChangeListener(this.resourceModelListener);
return jpaFile;
}
/**
* 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
removeJpaFile(jpaFile);
return true;
}
return false;
}
/**
* Remove the JPA file and dispose of it
*/
protected void removeJpaFile(JpaFile jpaFile) {
jpaFile.getResourceModel().removeResourceModelChangeListener(this.resourceModelListener);
jpaFile.dispose();
if ( ! this.removeItemFromCollection(jpaFile, this.jpaFiles, JPA_FILES_COLLECTION)) {
throw new IllegalArgumentException("JPA file: " + jpaFile.getFile().getName());
}
}
protected boolean containsJpaFile(IFile file) {
return (this.getJpaFile(file) != null);
}
// ********** context model **********
public JpaRootContextNode getRootContext() {
return this.rootContextNode;
}
// ********** more queries **********
public Iterator<String> annotatedClassNames() {
return new TransformationIterator<JavaResourcePersistentType, String>(this.annotatedJavaPersistentTypes()) {
@Override
protected String transform(JavaResourcePersistentType next) {
return next.getQualifiedName();
}
};
}
protected Iterator<JavaResourcePersistentType> annotatedJavaPersistentTypes() {
return new FilteringIterator<JavaResourcePersistentType, JavaResourcePersistentType>(this.javaResourcePersistentTypes()) {
@Override
protected boolean accept(JavaResourcePersistentType persistentType) {
return (persistentType == null) ? false : persistentType.isPersisted();
}
};
}
protected Iterator<JavaResourcePersistentType> javaResourcePersistentTypes() {
return new CompositeIterator<JavaResourcePersistentType>(
new TransformationIterator<JpaCompilationUnit, Iterator<JavaResourcePersistentType>>(jpaCompilationUnitResources()) {
@Override
protected Iterator<JavaResourcePersistentType> transform(JpaCompilationUnit next) {
if (next.getPersistentType() == null) {
return EmptyIterator.instance();
}
return new CompositeIterator<JavaResourcePersistentType>(next.getPersistentType(), next.getPersistentType().nestedTypes());
}
});
}
public Iterator<JpaFile> javaJpaFiles() {
return this.jpaFiles(ResourceModel.JAVA_RESOURCE_TYPE);
}
protected Iterator<JpaCompilationUnit> jpaCompilationUnitResources() {
return new TransformationIterator<JpaFile, JpaCompilationUnit>(this.javaJpaFiles()) {
@Override
protected JpaCompilationUnit transform(JpaFile jpaFile) {
return ((JavaResourceModel) jpaFile.getResourceModel()).getJpaCompilationUnit();
}
};
}
// look for binary stuff here...
public JavaResourcePersistentType getJavaPersistentTypeResource(String typeName) {
for (JpaCompilationUnit jpCompilationUnitResource : CollectionTools.iterable(this.jpaCompilationUnitResources())) {
JavaResourcePersistentType jptr = jpCompilationUnitResource.getJavaPersistentTypeResource(typeName);
if (jptr != null) {
return jptr;
}
}
// this.javaProject().findType(typeName);
return null;
}
// ********** Java change **********
public void javaElementChanged(ElementChangedEvent event) {
for (Iterator<JpaFile> stream = this.jpaFiles(); stream.hasNext(); ) {
stream.next().javaElementChanged(event);
}
}
// ********** validation **********
public Iterator<IMessage> validationMessages() {
List<IMessage> messages = new ArrayList<IMessage>();
this.jpaPlatform.addToMessages(this, messages);
return messages.iterator();
}
/* If this is true, it may be assumed that all the requirements are valid
* for further validation. For example, if this is true at the point we
* are validating persistence units, it may be assumed that there is a
* single persistence.xml and that it has valid content down to the
* persistence unit level. */
private boolean okToContinueValidation = true;
public void addToMessages(List<IMessage> messages) {
//start with the project - then down
//project validation
addProjectLevelMessages(messages);
//context model validation
getRootContext().addToMessages(messages);
}
protected void addProjectLevelMessages(List<IMessage> messages) {
addConnectionMessages(messages);
addMultiplePersistenceXmlMessage(messages);
}
protected void addConnectionMessages(List<IMessage> messages) {
addNoConnectionMessage(messages);
addInactiveConnectionMessage(messages);
}
protected boolean okToProceedForConnectionValidation = true;
protected void addNoConnectionMessage(List<IMessage> messages) {
if (! this.getDataSource().hasAConnection()) {
messages.add(
DefaultJpaValidationMessages.buildMessage(
IMessage.NORMAL_SEVERITY,
JpaValidationMessages.PROJECT_NO_CONNECTION,
this)
);
okToProceedForConnectionValidation = false;
}
}
protected void addInactiveConnectionMessage(List<IMessage> messages) {
if (okToProceedForConnectionValidation && ! this.getDataSource().connectionProfileIsActive()) {
messages.add(
DefaultJpaValidationMessages.buildMessage(
IMessage.NORMAL_SEVERITY,
JpaValidationMessages.PROJECT_INACTIVE_CONNECTION,
new String[] {this.getDataSource().getConnectionProfileName()},
this)
);
}
okToProceedForConnectionValidation = true;
}
protected void addMultiplePersistenceXmlMessage(List<IMessage> messages) {
// if (validPersistenceXmlFiles.size() > 1) {
// messages.add(
// JpaValidationMessages.buildMessage(
// IMessage.HIGH_SEVERITY,
// IJpaValidationMessages.PROJECT_MULTIPLE_PERSISTENCE_XML,
// jpaProject)
// );
// okToContinueValidation = false;
// }
}
// ********** root deploy location **********
protected static final String WEB_PROJECT_ROOT_DEPLOY_LOCATION = J2EEConstants.WEB_INF_CLASSES;
public String getRootDeployLocation() {
return this.isWebProject() ? WEB_PROJECT_ROOT_DEPLOY_LOCATION : "";
}
protected static final String JST_WEB_MODULE = IModuleConstants.JST_WEB_MODULE;
protected boolean isWebProject() {
return JptCorePlugin.projectHasWebFacet(this.project);
}
// ********** dispose **********
public void dispose() {
if (this.updater != null) {
this.updater.dispose();
}
// use clone iterator while deleting JPA files
for (Iterator<JpaFile> stream = this.jpaFiles(); stream.hasNext(); ) {
this.removeJpaFile(stream.next());
}
this.dataSource.dispose();
}
// ********** resource model listener **********
protected class DefaultResourceModelListener implements ResourceModelListener {
protected DefaultResourceModelListener() {
super();
}
public void resourceModelChanged() {
GenericJpaProject.this.update();
}
}
// ********** handling resource deltas **********
public void synchronizeJpaFiles(IResourceDelta delta) throws CoreException {
ResourceDeltaVisitor resourceDeltaVisitor = this.buildResourceDeltaVisitor();
delta.accept(resourceDeltaVisitor);
if (resourceDeltaVisitor.jpaFilesChanged()) {
for(JpaFile jpaFile : CollectionTools.iterable(jpaFiles())) {
jpaFile.getResourceModel().resolveTypes();
}
}
}
/**
* 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.addJpaFile(file);
case IResourceDelta.REMOVED :
return this.removeJpaFile(file);
case IResourceDelta.CHANGED :
case IResourceDelta.ADDED_PHANTOM :
case IResourceDelta.REMOVED_PHANTOM :
default :
break; // only worried about added and removed files
}
return false;
}
// ***** inner class
/**
* add a JPA file for every [appropriate] file encountered by the visitor
*/
protected class ResourceDeltaVisitor implements IResourceDeltaVisitor {
private boolean jpaFilesChanged = false;
protected ResourceDeltaVisitor() {
super();
}
public boolean visit(IResourceDelta delta) throws CoreException {
IResource res = delta.getResource();
switch (res.getType()) {
case IResource.ROOT :
case IResource.PROJECT :
case IResource.FOLDER :
return true; // visit children
case IResource.FILE :
if (GenericJpaProject.this.synchronizeJpaFiles((IFile) res, delta.getKind())) {
this.jpaFilesChanged = true;
}
return false; // no children
default :
return false; // no children
}
}
/**
* Used to determine if the JPA files collection was modified while
* traversing the IResourceDelta. Return true if a JPA file was added/removed
*/
protected boolean jpaFilesChanged() {
return this.jpaFilesChanged;
}
}
// ********** support for modifying documents shared with the UI **********
/**
* If there is no thread-specific command executor, use the default
* implementation, which simply executes the command directly.
*/
protected CommandExecutor getThreadLocalModifySharedDocumentCommandExecutor() {
CommandExecutor ce = this.threadLocalModifySharedDocumentCommandExecutor.get();
return (ce != null) ? ce : CommandExecutor.Default.instance();
}
public void setThreadLocalModifySharedDocumentCommandExecutor(CommandExecutor commandExecutor) {
this.threadLocalModifySharedDocumentCommandExecutor.set(commandExecutor);
}
public CommandExecutorProvider getModifySharedDocumentCommandExecutorProvider() {
return this.modifySharedDocumentCommandExecutorProvider;
}
// ***** inner class
protected class ModifySharedDocumentCommandExecutorProvider implements CommandExecutorProvider {
protected ModifySharedDocumentCommandExecutorProvider() {
super();
}
public CommandExecutor getCommandExecutor() {
return GenericJpaProject.this.getThreadLocalModifySharedDocumentCommandExecutor();
}
}
// ********** project "update" **********
public Updater getUpdater() {
return this.updater;
}
public void setUpdater(Updater updater) {
if (this.updater != null) { // first time through, the updater will be null
this.updater.dispose();
}
this.updater = updater;
this.updater.start();
}
/**
* Delegate to the updater so clients can configure how updates occur.
*/
public void update() {
if (this.updater == null) {
throw new IllegalStateException("updater is null, use setUpdater(Updater) after construction of GenericJpaProject");
}
this.updater.update();
}
/**
* Called by the updater.
*/
public IStatus update(IProgressMonitor monitor) {
try {
this.getRootContext().update(monitor);
} catch (OperationCanceledException ex) {
return Status.CANCEL_STATUS;
} catch (Throwable ex) {
// Exceptions can occur when the update is running and changes are
// made concurrently to the Java source. When that happens, our
// model might be in an inconsistent state because it is not yet in
// sync with the changed Java source.
// Log these exceptions and assume they won't happen when the
// update runs again as a result of the concurrent Java source changes.
JptCorePlugin.log(ex);
}
return Status.OK_STATUS;
}
}