blob: d2d2a9814764c161b0b49670b57e075c7b7e7815 [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;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jdt.core.ICompilationUnit;
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.jst.j2ee.model.IModelProviderEvent;
import org.eclipse.jst.j2ee.model.IModelProviderListener;
import org.eclipse.jst.javaee.core.EjbLocalRef;
import org.eclipse.jst.javaee.core.JavaEEObject;
import org.eclipse.jst.javaee.core.ResourceRef;
import org.eclipse.jst.javaee.core.SecurityRole;
import org.eclipse.jst.javaee.core.SecurityRoleRef;
import org.eclipse.jst.javaee.ejb.EJBJar;
import org.eclipse.jst.javaee.ejb.EjbFactory;
import org.eclipse.jst.javaee.ejb.EnterpriseBeans;
import org.eclipse.jst.javaee.ejb.MessageDrivenBean;
import org.eclipse.jst.javaee.ejb.SessionBean;
import org.eclipse.jst.jee.model.internal.common.AbstractAnnotationModelProvider;
import org.eclipse.jst.jee.model.internal.common.ManyToOneRelation;
import org.eclipse.jst.jee.model.internal.common.Result;
import org.eclipse.wst.common.project.facet.core.IFacetedProject;
/**
* This class provides a javaee ejb EMF model builded from the *.java files
* located in the project, over which the instance is working on.
*
* The project is passed in the constructor
* {@link #EJBAnnotationReader(IFacetedProject, IProject))}.
*
* The loaded model is returned from {@link #getEJBJar()}.
*
* Model changes can occur if a java file in the project is changed. When JDT
* model is changed, EJBAnnotationReader will rebuild is model. The current
* implementation is doing a best try to minimize the number of change events
* from JDT that cause the model to be rebuild.
*
* A listener is notified for changes in the model. The notification of the
* listener may occur in the same or in another thread from the one changing the
* resource.
*
* <p>
* The class is maintaining a reference between the bean and the interfaces it
* depends on. A bean depends on the resources of it business interfaces, of the
* interfaces of the resource and ejb annotation and probably other. This is an
* implementation detail. What the EJBAnnotationReader guarantees is that when a
* file is changed and there is a model object depending on this file the ejbJar
* will be successfully rebuilded.
*
* For example two bean can implements the same interface
* <p>
* <code>class Bean1 implements Interface {}</code>
* <code>class Bean2 implements Interface {}</code>
* <code>@Local interface Interface {}</code>
*
* If in this moment Interface is changed to :
* <code>@Remote interface Interface {}</code>
*
* then Bean1 and Bean2 will be rebuilded and will contain Interface1 in their
* business remotes list. Not it their business locals list
* </p>
* </p>
*
* @author Kiril Mitov k.mitov@sap.com
*
*/
public class EJBAnnotationReader extends AbstractAnnotationModelProvider<EJBJar>{
/**
* An abstraction of a reference between a model object and an interface.
* This class can be used in collections as a key for differing a reference
* from a bean to an interface.
*
* <p>
* One bean can refer to many interfaces. One interface can be referred by
* many beans. This leads to a situation of many to many relation which is
* model using {@link ManyToOneRelation} with this BeanInterfaceRef class.
* Many unique BeanInterfaceRefs can refer to one interface.
* </p>
*
* One BeanInterfaceRef is considered equal to another BeanInterfaceRef if
* they refer to the same "modelObject" with the same "interface" value.
*
* @see EJBAnnotationReader#beanRefToResolvedInterface
* @author Kiril Mitov k.mitov@sap.com
*
*/
private static class BeanInterfaceRef {
private String interfacee;
private JavaEEObject modelObject;
public BeanInterfaceRef(String interfacee, JavaEEObject modelObject) {
super();
this.interfacee = interfacee;
this.modelObject = modelObject;
}
public JavaEEObject getModelObject() {
return modelObject;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((interfacee == null) ? 0 : interfacee.hashCode());
result = prime * result + ((modelObject == null) ? 0 : modelObject.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BeanInterfaceRef other = (BeanInterfaceRef) obj;
if (interfacee == null) {
if (other.interfacee != null)
return false;
} else if (!interfacee.equals(other.interfacee))
return false;
if (modelObject == null) {
if (other.modelObject != null)
return false;
} else if (!modelObject.equals(other.modelObject))
return false;
return true;
}
}
private IProject clientProject;
public Map<ICompilationUnit, JavaEEObject> unitToModel;
/**
* An instance of {@link ManyToOneRelation}. This instance is used to
* maintain the references between a compilation unit which stores an
* interface and the beans that depend on this interface.
*
* Since one bean can refer to many interfaces and one interface can refer
* to many beans a many-to-many relation must be modeled. This is done using
* {@link BeanInterfaceRef} and {@link ManyToOneRelation}
*
* @see BeanInterfaceRef
*/
private ManyToOneRelation<BeanInterfaceRef, ICompilationUnit> beanRefToResolvedInterfaceUnit;
private EjbAnnotationFactory annotationFactory;
/**
* Constructs a new EJBAnnotation reader 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 #getEJBJar()}.
*
* It is possible to specify the additional clientProject for this
* ejbProject. A client project can contain part of the java files that make
* up the model. In this case EJBAnnotationReader will also listen for
* resources changes in the client project.
*
* @param project
* the ejb project. Can not be <code>null</code>
* @param clientProject
* the client ejb project or <code>null</code> if there is no
* client project.
*/
public EJBAnnotationReader(IFacetedProject project, IProject clientProject) {
super(project);
this.clientProject = clientProject;
}
/**
* Loads the model from the related java files.
*
* @throws CoreException
*/
@Override
public void loadModel() throws CoreException {
IJavaProject javaProject = JavaCore.create(facetedProject.getProject());
final Collection<ICompilationUnit> javaFiles = new HashSet<ICompilationUnit>();
for (final IPackageFragmentRoot root : javaProject.getAllPackageFragmentRoots()) {
visitJavaFiles(javaFiles, root);
}
if (clientProject != null) {
IJavaProject clientProjectJavaView = JavaCore.create(facetedProject.getProject());
for (final IPackageFragmentRoot root : clientProjectJavaView.getAllPackageFragmentRoots()) {
visitJavaFiles(javaFiles, root);
}
}
unitToModel = new HashMap<ICompilationUnit, JavaEEObject>();
beanRefToResolvedInterfaceUnit = new ManyToOneRelation<BeanInterfaceRef, ICompilationUnit>();
for (ICompilationUnit unit : javaFiles) {
Result result = analyzeUnitForBean(unit);
if (result == null)
continue;
processResult(unit, result);
}
}
@Override
protected void preLoad() {
super.preLoad();
modelObject = EjbFactory.eINSTANCE.createEJBJar();
annotationFactory = EjbAnnotationFactory.createFactory();
}
/**
* Process the result from parsing the compilation unit. Depending on the
* result this might include adding a session bean, message driven bean,
* securityRole etc.
*
* @see #sessionBeanFound(ICompilationUnit, SessionBean, Collection)
* @see #messageBeanFound(ICompilationUnit, MessageDrivenBean, Collection)
* @see #securityRoleFound(ICompilationUnit, SecurityRole)
* @param unit
* @param result
* @throws JavaModelException
*/
private void processResult(ICompilationUnit unit, Result result) throws JavaModelException {
JavaEEObject modelObject = result.getMainObject();
if (SessionBean.class.isInstance(modelObject)) {
sessionBeanFound(unit, (SessionBean) modelObject, result.getDependedTypes());
} else if (MessageDrivenBean.class.isInstance(modelObject)) {
messageBeanFound(unit, (MessageDrivenBean) modelObject, result.getDependedTypes());
}
for (JavaEEObject additional : result.getAdditional()) {
if (SecurityRole.class.isInstance(additional)) {
securityRoleFound((SessionBean) unitToModel.get(unit), (SecurityRole) additional);
}
}
}
/**
* Analyze this compilation unit for a bean. If the file is not a valid java
* compilation unit or it does not contain a class the method returns
* <code>null</code>
*
* Only the primary type of the compilation unit is processed.
*
* @see EjbAnnotationFactory
* @param unit
* the file to be processed.
* @return result from processing the file
* @throws JavaModelException
*/
private Result analyzeUnitForBean(ICompilationUnit compilationUnit) throws JavaModelException {
if (compilationUnit == null)
return null;
IType rootType = compilationUnit.findPrimaryType();
/*
* If the compilation unit is not valid and can not be compiled the
* rootType may be null. This can happen if a class is define as follows
* <code> @Stateless public SomeClass implements SomeInterface{}</code>.
* Here the "class" word is missed and the type is not valid.
*/
if (rootType == null || !rootType.isClass())
return null;
return annotationFactory.createJavaeeObject(rootType);
}
/**
* A message driven bean was found in the given compilation unit. Add this
* messageDriven bean to the list of mdbs in the
* {@link EnterpriseBeans#getMessageDrivenBeans()}
*
* @param unit
* @param messageBean
* @param dependedTypes
* the types on which this mdb depends. This list specifies
* changes on which types should lead to rebuilding the mdb.
* @throws JavaModelException
*/
private void messageBeanFound(ICompilationUnit unit, MessageDrivenBean messageBean, Collection<IType> dependedTypes)
throws JavaModelException {
if (modelObject.getEnterpriseBeans() == null)
modelObject.setEnterpriseBeans(EjbFactory.eINSTANCE.createEnterpriseBeans());
modelObject.getEnterpriseBeans().getMessageDrivenBeans().add(messageBean);
connectBeanWithTypes(unit, messageBean, dependedTypes);
}
/**
* A session bean was found in the given compilation unit. The session bean
* is also depended on the types in dependedTypes. Here "depended" means
* that if any of the types in dependedTypes is change the bean will also be
* rebuilded. For example a session bean is depended on the types of it
* local and remote interfaces.
*
* <p>
* Since a bean can depended on more then one compilation unit this method
* also establish an appropriate connection between the bean and all the
* units it depends on. This are the "unit" and the units of
* "dependedTypes".
* </p>
*
* @see #getFilesFromTypes(Collection)
*
* @param unit
* @param sessionBean
* @param dependedTypes
* @throws JavaModelException
*/
private void sessionBeanFound(ICompilationUnit unit, SessionBean sessionBean, Collection<IType> dependedTypes)
throws JavaModelException {
if (modelObject.getEnterpriseBeans() == null)
modelObject.setEnterpriseBeans(EjbFactory.eINSTANCE.createEnterpriseBeans());
modelObject.getEnterpriseBeans().getSessionBeans().add(sessionBean);
connectBeanWithTypes(unit, sessionBean, dependedTypes);
}
/**
*
* @param unit
* @param bean
* @param dependedTypes
* @throws JavaModelException
*/
private void connectBeanWithTypes(ICompilationUnit unit, JavaEEObject bean, Collection<IType> dependedTypes)
throws JavaModelException {
unitToModel.put(unit, bean);
for (IType type : dependedTypes) {
if (type.isBinary() || type.isInterface() == false)
continue;
beanRefToResolvedInterfaceUnit.connect(new BeanInterfaceRef(type.getFullyQualifiedName(), bean), type
.getCompilationUnit());
}
}
/**
* 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.
*/
@Override
public void dispose() {
beanRefToResolvedInterfaceUnit = null;
if (unitToModel != null)
// bug 249234. If the model is not loaded and dispose is called a
// NPE occurs.
unitToModel.clear();
super.dispose();
}
/**
* @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.
*/
@Override
protected boolean isProjectRelative(IJavaProject javaProject) {
if (super.isProjectRelative(javaProject) == true)
return true;
else if (clientProject != null && javaProject.getProject().equals(clientProject.getProject()))
return true;
return false;
}
/**
* {non-Javadoc}
*
* @see org.eclipse.jst.jee.model.internal.common.AbstractAnnotationModelProvider#processAddedCompilationUnit(org.eclipse.jst.j2ee.model.IModelProviderEvent,
* org.eclipse.core.resources.ICompilationUnit)
*/
@Override
protected void processAddedCompilationUnit(IModelProviderEvent modelEvent, ICompilationUnit unit)
throws CoreException {
if (unit == null)
return;
Collection<ICompilationUnit> unitsToRebuild = new HashSet<ICompilationUnit>();
IType rootType = unit.findPrimaryType();
if (rootType == null)
return;
if (rootType.isClass()) {
Result result = analyzeUnitForBean(unit);
if (result == null || result.isEmpty())
return;
processResult(unit, result);
modelEvent.addResource(unit);
} else if (rootType.isInterface()) {
unitsToRebuild.addAll(processAddedInterface(rootType));
}
if (unitsToRebuild.isEmpty())
return;
for (ICompilationUnit changedUnit : unitsToRebuild) {
processRemovedCompilationUnit(modelEvent, changedUnit);
processAddedCompilationUnit(modelEvent, changedUnit);
}
unitsToRebuild.clear();
}
private Collection<ICompilationUnit> processAddedInterface(IType rootType) {
Collection<ICompilationUnit> unitsToRebuild = new HashSet<ICompilationUnit>();
String rootTypeSimpleName = rootType.getElementName();
if (getConcreteModel().getEnterpriseBeans() == null)
return unitsToRebuild;
for (Iterator beanIter = getConcreteModel().getEnterpriseBeans().getSessionBeans().iterator(); beanIter
.hasNext();) {
SessionBean bean = (SessionBean) beanIter.next();
for (Iterator interfaceIter = bean.getBusinessLocals().iterator(); interfaceIter.hasNext();) {
String inter = (String) interfaceIter.next();
if (rootTypeSimpleName.equals(inter))
unitsToRebuild.add(getCompilationUnitFromModel(bean));
}
if (rootTypeSimpleName.equals(bean.getLocalHome()) || rootTypeSimpleName.equals(bean.getHome()))
unitsToRebuild.add(getCompilationUnitFromModel(bean));
findDependedFiles(bean, rootTypeSimpleName, bean.getEjbLocalRefs(), bean.getResourceRefs(), unitsToRebuild);
}
for (Iterator beanIter = getConcreteModel().getEnterpriseBeans().getMessageDrivenBeans().iterator(); beanIter
.hasNext();) {
MessageDrivenBean bean = (MessageDrivenBean) beanIter.next();
findDependedFiles(bean, rootTypeSimpleName, bean.getEjbLocalRefs(), bean.getResourceRefs(), unitsToRebuild);
}
return unitsToRebuild;
}
private ICompilationUnit getCompilationUnitFromModel(JavaEEObject bean) {
for (Map.Entry<ICompilationUnit, JavaEEObject> entry : unitToModel.entrySet()) {
if (entry.getValue().equals(bean))
return entry.getKey();
}
return null;
}
private void findDependedFiles(JavaEEObject bean, String rootTypeSimpleName, Collection<EjbLocalRef> ejbRefs,
Collection<ResourceRef> resourceRefs, Collection<ICompilationUnit> filesToRebuild) {
for (Iterator refsIter = ejbRefs.iterator(); refsIter.hasNext();) {
String localRefInterface = ((EjbLocalRef) refsIter.next()).getLocal();
if (rootTypeSimpleName.equals(localRefInterface))
filesToRebuild.add(getCompilationUnitFromModel(bean));
}
for (Iterator refsIter = resourceRefs.iterator(); refsIter.hasNext();) {
String resourceRef = ((ResourceRef) refsIter.next()).getResType();
if (rootTypeSimpleName.equals(resourceRef)) {
filesToRebuild.add(getCompilationUnitFromModel(bean));
}
}
}
@Override
protected void processRemovedCompilationUnit(IModelProviderEvent modelEvent, ICompilationUnit unit)
throws CoreException {
if (unitToModel.containsKey(unit))
processRemoveBean(modelEvent, unit);
else if (beanRefToResolvedInterfaceUnit.containsTarget(unit))
processRemovedInterface(modelEvent, unit);
}
/**
* Process a unit as a removed interface. It is the responsibility of the
* caller to make sure the unit really contains an interface and that this
* interface is really removed.
*
* Removing an interface will also remove the connections between the
* interface and the beans that depend on it -
* {@link #beanRefToResolvedInterfaceUnit}.
*
* Removing an interface will result in rebuilding all the modelObjects that
* depend on this interface.
*
* @see #beanRefToResolvedInterfaceUnit
* @see #processRemovedCompilationUnit(IModelProviderEvent,
* ICompilationUnit)
* @see #processRemoveBean(IModelProviderEvent, ICompilationUnit)
* @param modelEvent
* @param unit
* @throws JavaModelException
*/
private void processRemovedInterface(IModelProviderEvent modelEvent, ICompilationUnit unit) throws CoreException {
Collection<BeanInterfaceRef> refs = beanRefToResolvedInterfaceUnit.getSources(unit);
Collection<ICompilationUnit> filesToRebuild = new HashSet<ICompilationUnit>();
for (BeanInterfaceRef ref : refs) {
filesToRebuild.add(getCompilationUnitFromModel(ref.getModelObject()));
}
if (filesToRebuild.isEmpty())
return;
for (ICompilationUnit changedFile : filesToRebuild) {
processRemovedCompilationUnit(modelEvent, changedFile);
processAddedCompilationUnit(modelEvent, changedFile);
}
filesToRebuild.clear();
beanRefToResolvedInterfaceUnit.disconnect(unit);
}
/**
* Process a unit as a removed bean. It is the responsibility of the caller
* to make sure the file really contains a bean and that this bean is really
* removed.
*
* @see #processRemovedCompilationUnit(IModelProviderEvent,
* ICompilationUnit)
* @see #processRemovedInterface(IModelProviderEvent, ICompilationUnit)
* @see #beanRefToResolvedInterface
* @param modelEvent
* @param unit
* the removed compilation unit
* @throws JavaModelException
*/
private void processRemoveBean(IModelProviderEvent modelEvent, ICompilationUnit unit) throws JavaModelException {
EObject modelObject = (EObject) unitToModel.get(unit);
EcoreUtil.remove(modelObject);
if (getConcreteModel().getEnterpriseBeans().getGroup().isEmpty())
getConcreteModel().setEnterpriseBeans(null);
unitToModel.remove(unit);
modelEvent.setEventCode(modelEvent.getEventCode() | IModelProviderEvent.REMOVED_RESOURCE);
modelEvent.addResource(unit);
disconnectFromRoles((JavaEEObject) modelObject);
}
/**
* Process a unit as "changed". If no model object depends on this unit the
* unit will be process as added (since it may not have been a bean till
* now, but an annotation was added).
*
* It is the responsibility of the caller to make sure the unit really
* contains a bean/interface and that this bean is really changed and can be
* accessed.
*
* @see #processChangedBean(IModelProviderEvent, ICompilationUnit)
* @see #processChangedInterface(IModelProviderEvent, ICompilationUnit)
* @param modelEvent
* @param unit
* @throws JavaModelException
*/
@Override
protected void processChangedCompilationUnit(IModelProviderEvent modelEvent, ICompilationUnit unit)
throws CoreException {
if (unitToModel.containsKey(unit))
processChangedBean(modelEvent, unit);
else if (beanRefToResolvedInterfaceUnit.containsTarget(unit))
processChangedInterface(modelEvent, unit);
else
processAddedCompilationUnit(modelEvent, unit);
}
/**
* Process a unit as a changed bean. It is the responsibility of the caller
* to make sure the unit really contains a bean and that this bean is really
* changed.
*
* Changing a bean may mean removing it from the model (if it was a bean
* till now, but the annotation was deleted)
*
* @see #processChangedBean(IModelProviderEvent, ICompilationUnit)
* @see {@link #processChangedInterface(IModelProviderEvent, ICompilationUnit)}
* @param modelEvent
* @param unit
* @throws JavaModelException
*/
private void processChangedBean(IModelProviderEvent modelEvent, ICompilationUnit unit) throws CoreException {
EObject oldBean = (EObject) unitToModel.get(unit);
ICompilationUnit beanUnit = getCompilationUnitFromModel((JavaEEObject) oldBean);
processRemovedCompilationUnit(modelEvent, beanUnit);
processAddedCompilationUnit(modelEvent, beanUnit);
}
/**
* Process a unit as a changed interface. It is the responsibility of the
* caller to make sure the file really contains an interface and that this
* interface is really changed.
*
* Changing an interface may mean rebuilding all the beans that depend on
* this interface (if it was annotated with "@Local", but this annotation
* was changed to "@Remote" )
*
* @param modelEvent
* @param unit
* @throws JavaModelException
*/
private void processChangedInterface(IModelProviderEvent modelEvent, ICompilationUnit unit) throws CoreException {
Collection<BeanInterfaceRef> references = beanRefToResolvedInterfaceUnit.getSources(unit);
for (BeanInterfaceRef ref : references) {
ICompilationUnit next = getCompilationUnitFromModel(ref.getModelObject());
processRemovedCompilationUnit(modelEvent, next);
processAddedCompilationUnit(modelEvent, next);
}
}
public void modify(Runnable runnable, IPath modelPath) {
}
public IStatus validateEdit(IPath modelPath, Object context) {
return null;
}
@Override
protected Collection<SecurityRoleRef> getSecurityRoleRefs(JavaEEObject target) {
if (SessionBean.class.isInstance(target))
return ((SessionBean) target).getSecurityRoleRefs();
return null;
}
@Override
protected Collection<SecurityRole> getSecurityRoles() {
if (modelObject.getAssemblyDescriptor() == null)
modelObject.setAssemblyDescriptor(EjbFactory.eINSTANCE.createAssemblyDescriptor());
return modelObject.getAssemblyDescriptor().getSecurityRoles();
}
@Override
protected void processRemovedPackage(IModelProviderEvent modelEvent, IJavaElementDelta delta) throws CoreException {
Set<ICompilationUnit> keySet = new HashSet<ICompilationUnit>(unitToModel.keySet());
for (ICompilationUnit unit : keySet) {
if (unit.getParent().getElementName().equals(delta.getElement().getElementName())) {
processRemovedCompilationUnit(modelEvent, unit);
}
}
}
}