blob: 5334a2cc58e5ab157ebe750a06b55346f245e878 [file] [log] [blame]
/***********************************************************************
* Copyright (c) 2008 by SAP AG, Walldorf.
* 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
* https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* SAP AG - initial API and implementation
***********************************************************************/
package org.eclipse.jst.jee.model.internal.common;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.resources.IResourceProxyVisitor;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.ISafeRunnable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.ElementChangedEvent;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IElementChangedListener;
import org.eclipse.jdt.core.IJavaElementDelta;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jst.j2ee.model.IModelProvider;
import org.eclipse.jst.j2ee.model.IModelProviderEvent;
import org.eclipse.jst.j2ee.model.IModelProviderListener;
import org.eclipse.jst.j2ee.project.JavaEEProjectUtilities;
import org.eclipse.jst.j2ee.project.WebUtilities;
import org.eclipse.jst.javaee.core.JavaEEObject;
import org.eclipse.jst.javaee.core.SecurityRole;
import org.eclipse.jst.javaee.core.SecurityRoleRef;
import org.eclipse.jst.javaee.ejb.SessionBean;
import org.eclipse.jst.jee.JEEPlugin;
import org.eclipse.wst.common.componentcore.ComponentCore;
import org.eclipse.wst.common.componentcore.resources.IVirtualComponent;
import org.eclipse.wst.common.project.facet.core.IFacetedProject;
/**
* Base implementation for model providers based on annotations in java files.
*
* Listeners can be registered with {@link #addListener(IModelProviderListener)}
*
* @author Kiril Mitov k.mitov@sap.com
*
*/
public abstract class AbstractAnnotationModelProvider<T> implements IElementChangedListener, IModelProvider {
private static final String JAVA_EXTENSION = "java"; //$NON-NLS-1$
/**
* Find the security role with the given name in the given assembly
* descriptor.
*
* @param assembly
* @param name
* @return <code>null</code> if a security role with this name can not be
* found
*/
private static SecurityRole findRole(Collection<SecurityRole> securityRoles, String name) {
for (SecurityRole role : securityRoles) {
if (role.getRoleName().equals(name))
return role;
}
return null;
}
protected T modelObject;
private Collection<IModelProviderListener> listeners;
private Lock listenersLock = new ReentrantLock();
protected IFacetedProject facetedProject;
private ManyToOneRelation<SecurityRoleRef, SecurityRole> rolesToRolesRef = new ManyToOneRelation<SecurityRoleRef, SecurityRole>();
/**
* Constructs a new AnnotationReader for this faceted project. An illegal
* argument if a project with value <code>null</code> is passed. No loading
* is done in this constructor. Loading the model is made on demand when
* calling {@link #getModelObject()}.
*
* @param project
* the ejb project. Can not be <code>null</code>
*/
public AbstractAnnotationModelProvider(IFacetedProject project) {
if (project == null)
throw new IllegalArgumentException("The project argument can not be null"); //$NON-NLS-1$
this.facetedProject = project;
}
public T getConcreteModel() {
if (modelObject == null) {
preLoad();
try {
loadModel();
/*
* Adding the resource change listener after loading the model.
* No resource change event are acceptable while loading the
* model.
*/
postLoad();
} catch (CoreException e) {
log(e.getStatus());
return null;
}
}
return modelObject;
}
public Object getModelObject() {
return getConcreteModel();
}
public Object getModelObject(IPath modelPath) {
return getConcreteModel();
}
protected abstract void loadModel() throws CoreException;
protected void preLoad() {
}
protected void postLoad() {
JavaCore.addElementChangedListener(this);
}
/**
* Notifies the currently registered listeners with this model event. If the
* { @link IModelProviderEvent#getChangedResources()} is empty or
* <code>null</code> the method returns immediately.
*
* @param event
* the event that should be send to the listeners
*/
protected void notifyListeners(final IModelProviderEvent event) {
if (listeners == null)
return;
listenersLock.lock();
try {
IModelProviderListener[] backup = listeners.toArray(new IModelProviderListener[listeners.size()]);
notifyListeners(backup, event);
backup = null;
} finally {
listenersLock.unlock();
}
}
/**
* Clears the list of listeners. No notifications can occur while clearing
* the listeners.
*/
protected void clearListeners() {
if (listeners == null)
return;
try {
listenersLock.lock();
listeners.clear();
listeners = null;
} finally {
listenersLock.unlock();
}
}
private void notifyListeners(final IModelProviderListener[] aListeners, final IModelProviderEvent event) {
if (event.getChangedResources() == null || event.getChangedResources().isEmpty())
return;
for (final IModelProviderListener listener : aListeners) {
SafeRunner.run(new ISafeRunnable() {
public void handleException(Throwable exception) {
}
public void run() throws Exception {
listener.modelsChanged(event);
}
});
}
}
/**
* @return the currently registered listeners.
*/
protected Collection<IModelProviderListener> getListeners() {
if (listeners == null) {
listeners = new ArrayList<IModelProviderListener>();
}
return listeners;
}
/**
* Adds a listener to this instance. No listeners can be added during
* notifying the current listeners.
*
* @param listener
*/
public void addListener(IModelProviderListener listener) {
listenersLock.lock();
try {
getModelObject();
getListeners().add(listener);
} finally {
listenersLock.unlock();
}
}
/**
* Removes the listener from this instance. Has no effect if an identical
* listener is not registered.
*
* @param listener
* the listener to be removed.
*/
public void removeListener(IModelProviderListener listener) {
listenersLock.lock();
try {
getListeners().remove(listener);
} finally {
listenersLock.unlock();
}
}
/**
* @param javaProject
* @return true if the given project contains resources that are relative to
* the model. This method returns <code>true</code> for the
* ejbProject on which this instance is working a <code>true</code>
* for its client project.
*/
protected boolean isProjectRelative(IJavaProject javaProject) {
if (javaProject == null || facetedProject == null)
return false;
else if (javaProject.getProject().equals(facetedProject.getProject()))
return true;
else if(JavaEEProjectUtilities.isWebFragmentProject(javaProject.getProject()) &&
JavaEEProjectUtilities.isDynamicWebProject(facetedProject.getProject()) &&
isWebFragmentOf(facetedProject.getProject(), javaProject.getProject()))
return true;
return false;
}
private boolean isWebFragmentOf(IProject webProject, IProject webFragment) {
IVirtualComponent componentWebProject = ComponentCore.createComponent(webProject);
IVirtualComponent componentWebFragment = ComponentCore.createComponent(webFragment);
if(componentWebProject != null && componentWebFragment != null){
return WebUtilities.getWebFragments(componentWebProject).contains(componentWebFragment);
}
return false;
}
/**
* Dispose the current instance. The actual dispose may occur in another
* thread. Use {@link #addListener(IModelProviderListener)} to register a
* listener that will be notified when the instance is disposed. After all
* the listeners are notified the list of listeners is cleared.
*/
public void dispose() {
IModelProviderEvent modelEvent = createModelProviderEvent();
modelEvent.addResource(facetedProject.getProject());
modelEvent.setEventCode(IModelProviderEvent.UNLOADED_RESOURCE);
JavaCore.removeElementChangedListener(this);
modelObject = null;
notifyListeners(modelEvent);
clearListeners();
}
/**
* Process a unit as "removed". The method is allowed not to make checks
* whether the unit was added/removed/change. It is processing the unit as
* "removed".
*
* If no model object depends on the given file "modelEvent" is not changed.
*
* @see #processAddedCompilationUnit(IModelProviderEvent, ICompilationUnit)
* @param modelEvent
* subclasses should "fill" modelEvent with information about the
* change that has happened. This event will be propagated to
* model provided listeners.
* @param file
* the file to be removed.
* @throws CoreException
* if there was an error during parsing the file
*/
protected abstract void processRemovedCompilationUnit(IModelProviderEvent modelEvent, ICompilationUnit unit)
throws CoreException;
/**
* Process a unit as "added". The method is allowed not to make checks
* whether the unit was added/removed/change. It is processing the file as
* "added". It is the responsibility of the caller to make sure the
* processing of the file as added will not leave the model in a wrong
* state.
*
* modelEvent is changed to contain information about the added modelObject.
*
* @see #processRemovedCompilationUnit(IModelProviderEvent,
* ICompilationUnit)
* @param modelEvent
* subclasses should "fill" modelEvent with information about the
* change that has happened. This event will be propagated to
* model provided listeners.
* @param file
* the file that was added
* @throws CoreException
*/
protected abstract void processAddedCompilationUnit(IModelProviderEvent modelEvent, ICompilationUnit file)
throws CoreException;
/**
* Process a unit as "changed". The method is allowed not to make checks
* whether the unit was added/removed/change. It is processing the unit as
* "changed". It is the responsibility of the caller to make sure the
* processing of the file as "changed" will not leave the model in a wrong
* state.
*
* @see #processAddedCompilationUnit(IModelProviderEvent, ICompilationUnit)
* @see #processRemovedCompilationUnit(IModelProviderEvent,
* ICompilationUnit)
* @param modelEvent
* subclasses should "fill" modelEvent with information about the
* change that has happened. This event will be propagated to
* model provided listeners.
* @param unit
* the unti that was changed
* @throws CoreException
*/
protected abstract void processChangedCompilationUnit(IModelProviderEvent modelEvent, ICompilationUnit file)
throws CoreException;
protected void log(IStatus status) {
}
protected MyModelProviderEvent createModelProviderEvent() {
return new MyModelProviderEvent(0, null, facetedProject.getProject());
}
// ---------------SECURITY ROLES ---------------------------//
protected abstract Collection<SecurityRole> getSecurityRoles();
protected abstract Collection<SecurityRoleRef> getSecurityRoleRefs(JavaEEObject target);
/**
* Deletes the connection maintained by the given bean and the security
* roles defined in the bean. If this is the only bean in which the role is
* defined, the role will also be deleted. Calling this method makes sense
* only if the bean and the security role and the bean were connected with
* { @link #connectWithRole(SecurityRole, SessionBean)}
*
* <p>
* If the bean is not of type org.eclipse.jst.javaee.ejb.SessionBean the
* method returns immediately.
* </p>
*
* @see #connectWithRole(SecurityRole, SessionBean)
* @see #rolesToRolesRef
* @param bean
*/
protected void disconnectFromRoles(JavaEEObject target) {
Collection<SecurityRole> roles = getSecurityRoles();
if (roles == null)
return;
Collection<SecurityRoleRef> refs = getSecurityRoleRefs(target);
if (refs == null)
return;
for (SecurityRoleRef ref : refs) {
SecurityRole role = rolesToRolesRef.getTarget(ref);
rolesToRolesRef.disconnectSource(ref);
if (!rolesToRolesRef.containsTarget(role)) {
getSecurityRoles().remove(role);
}
}
}
/**
* A security role was found in the given file. Add this security role to
* the assembly descriptor. If the ejbJar does not have an assembly
* descriptor a new one is created.
*
* @see #connectRoleWithBean(SecurityRole, SessionBean)s
* @param file
* @param securityRole
*/
protected void securityRoleFound(JavaEEObject object, SecurityRole securityRole) {
connectWithRole(securityRole, object);
}
/**
* A security role can be defined in more the one bean. A bean can define
* more then one security role. This means we have a many-to-many relation
* between sessionBeans and securityRoles.
*
* <p>
* Luckily a sessionBean contains a list of securityRoleRefs. This method
* creates a connection between the securityRole contained in the assembly
* descriptor and the security role ref contained in the bean.
*
* If a security role is define only in one bean, deleting the bean means
* deleting the security role. But if the security role is defined in two
* beans only deleting both beans will result in deleting the security role.
* </p>
*
* @see #disconnectFromRoles(JavaEEObject)
* @see #rolesToRolesRef
* @param securityRole
* @param target
*/
private void connectWithRole(SecurityRole securityRole, JavaEEObject target) {
Collection<SecurityRole> roles = getSecurityRoles();
if (roles == null)
return;
Collection<SecurityRoleRef> refs = getSecurityRoleRefs(target);
if (refs == null)
return;
/*
* If there is a security role with this name use the existing security
* role.
*/
SecurityRole role = findRole(roles, securityRole.getRoleName());
if (role == null) {
roles.add(securityRole);
role = securityRole;
}
for (SecurityRoleRef ref : refs) {
if (ref.getRoleName().equals(role.getRoleName()))
rolesToRolesRef.connect(ref, role);
}
}
public void elementChanged(final ElementChangedEvent javaEvent) {
if (javaEvent.getType() == ElementChangedEvent.POST_RECONCILE)
internalPostReconcile(javaEvent);
else if (javaEvent.getType() == ElementChangedEvent.POST_CHANGE)
internalPostChange(javaEvent);
}
private void internalPostChange(ElementChangedEvent javaEvent) {
IModelProviderEvent modelEvent = createModelProviderEvent();
// handles ElementChangedEvent.POST_CHANGE - the case when the
// compilation unit has been changed
for (IJavaElementDelta child : javaEvent.getDelta().getAffectedChildren()) {
if (child.getElement() instanceof IJavaProject) {
processChangedProject(modelEvent, child);
notifyListeners(modelEvent);
}
}
}
private void internalPostReconcile(final ElementChangedEvent javaEvent) {
IModelProviderEvent modelEvent = createModelProviderEvent();
if (javaEvent.getDelta().getElement() instanceof ICompilationUnit) {
recursevilyProcessCompilationUnits(modelEvent, javaEvent.getDelta());
notifyListeners(modelEvent);
}
}
protected void processChangedProject(IModelProviderEvent event, IJavaElementDelta projectDelta) {
if (!isProjectRelative(projectDelta.getElement().getJavaProject())) {
return;
}
Assert.isTrue(projectDelta.getElement() instanceof IJavaProject,
"An invalid change notification has occured. Element is <" + projectDelta.getElement() + ">"); //$NON-NLS-1$//$NON-NLS-2$
if (((projectDelta.getFlags() & IJavaElementDelta.F_OPENED) != 0)
|| projectDelta.getKind() == IJavaElementDelta.ADDED) {
try {
loadModel();
} catch (CoreException e) {
JEEPlugin.getDefault().getLog().log(
new Status(IStatus.ERROR, JEEPlugin.getDefault().getPluginID(), e.getMessage(), e));
}
}
if (((projectDelta.getFlags() & IJavaElementDelta.F_CLOSED) != 0)
|| projectDelta.getKind() == IJavaElementDelta.REMOVED) {
dispose();
}
processChangedProjectChildren(event, projectDelta);
}
protected void processChangedProjectChildren(IModelProviderEvent event, IJavaElementDelta projectDelta) {
for (IJavaElementDelta childDelta : projectDelta.getAffectedChildren()) {
if (!(childDelta.getElement() instanceof IPackageFragmentRoot)) {
continue;
}
if ((childDelta.getFlags() & IJavaElementDelta.F_CHILDREN) != 0) {
recursevilyProcessPackages(event, childDelta);
}
}
}
public void recursevilyProcessPackages(IModelProviderEvent modelEvent, IJavaElementDelta delta) {
if (delta.getElement() instanceof IPackageFragment) {
try {
IPackageFragment fragment = (IPackageFragment) delta.getElement();
if (delta.getKind() == IJavaElementDelta.ADDED) {
for (ICompilationUnit unit : fragment.getCompilationUnits()) {
processAddedCompilationUnit(modelEvent, unit);
}
} else if (delta.getKind() == IJavaElementDelta.REMOVED) {
if (delta.getKind() == IJavaElementDelta.REMOVED) {
processRemovedPackage(modelEvent, delta);
}
} else if (delta.getKind() == IJavaElementDelta.CHANGED) {
recursevilyProcessCompilationUnits(modelEvent, delta);
}
} catch (CoreException e) {
JEEPlugin.getDefault().getLog().log(
new Status(IStatus.ERROR, JEEPlugin.getDefault().getPluginID(), e.getMessage(), e));
}
} else {
for (IJavaElementDelta childDelta : delta.getAffectedChildren()) {
recursevilyProcessPackages(modelEvent, childDelta);
}
}
}
protected abstract void processRemovedPackage(IModelProviderEvent modelEvent, IJavaElementDelta delta)
throws CoreException;
public void recursevilyProcessCompilationUnits(IModelProviderEvent modelEvent, IJavaElementDelta delta) {
if (delta.getElement() instanceof ICompilationUnit) {
if (!isProjectRelative(delta.getElement().getJavaProject()))
return;
try {
final ICompilationUnit unit = (ICompilationUnit) delta.getElement();
if (delta.getKind() == IJavaElementDelta.ADDED) {
processAddedCompilationUnit(modelEvent, unit);
}
if (delta.getKind() == IJavaElementDelta.REMOVED) {
processRemovedCompilationUnit(modelEvent, unit);
}
if (delta.getKind() == IJavaElementDelta.CHANGED) {
if (((delta.getFlags() & IJavaElementDelta.F_PRIMARY_RESOURCE) == 0)
|| ((delta.getFlags() & IJavaElementDelta.F_PRIMARY_WORKING_COPY) == 0)) {
modelEvent
.setEventCode(IModelProviderEvent.KNOWN_RESOURCES_CHANGED | modelEvent.getEventCode());
processChangedCompilationUnit(modelEvent, unit);
}
}
} catch (CoreException e) {
JEEPlugin.getDefault().getLog().log(
new Status(IStatus.ERROR, JEEPlugin.getDefault().getPluginID(), e.getMessage(), e));
}
} else {
for (IJavaElementDelta childDelta : delta.getAffectedChildren()) {
recursevilyProcessCompilationUnits(modelEvent, childDelta);
}
}
}
protected void visitJavaFiles(final Collection<ICompilationUnit> javaFiles, final IPackageFragmentRoot root)
throws CoreException {
if (root.getKind() != IPackageFragmentRoot.K_SOURCE)
return;
root.getCorrespondingResource().accept(new IResourceProxyVisitor() {
public boolean visit(IResourceProxy proxy) throws CoreException {
if (proxy.getType() == IResource.FILE) {
if (proxy.getName().endsWith("." + JAVA_EXTENSION)) { //$NON-NLS-1$
IFile file = (IFile) proxy.requestResource();
if (!root.getJavaProject().isOnClasspath(file))
return false;
if (!file.isSynchronized(IResource.DEPTH_ONE))
return false;
javaFiles.add(JavaCore.createCompilationUnitFrom(file));
}
return false;
}
return true;
}
}, IContainer.NONE);
}
}