org.eclipse.jst.jee.model.internal.common.AbstractAnnotationModelProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jst.jee.model.internal.common.AbstractAnnotationModelProvider.java

Source

/***********************************************************************
 * 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
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * 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);

    }
}