com.atlassian.jira.applinks.JiraAppLinksHostApplication.java Source code

Java tutorial

Introduction

Here is the source code for com.atlassian.jira.applinks.JiraAppLinksHostApplication.java

Source

package com.atlassian.jira.applinks;

import com.atlassian.applinks.api.ApplicationId;
import com.atlassian.applinks.api.ApplicationType;
import com.atlassian.applinks.api.EntityType;
import com.atlassian.applinks.api.application.jira.JiraApplicationType;
import com.atlassian.applinks.api.application.jira.JiraProjectEntityType;
import com.atlassian.applinks.host.OsgiServiceProxyFactory;
import com.atlassian.applinks.host.spi.AbstractInternalHostApplication;
import com.atlassian.applinks.host.spi.DefaultEntityReference;
import com.atlassian.applinks.host.spi.EntityReference;
import com.atlassian.applinks.spi.util.TypeAccessor;
import com.atlassian.crowd.embedded.api.User;
import com.atlassian.jira.bc.license.JiraLicenseService;
import com.atlassian.jira.config.properties.APKeys;
import com.atlassian.jira.config.properties.ApplicationProperties;
import com.atlassian.jira.extension.Startable;
import com.atlassian.jira.project.Project;
import com.atlassian.jira.project.ProjectManager;
import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.jira.security.PermissionManager;
import com.atlassian.jira.security.Permissions;
import com.atlassian.jira.util.velocity.VelocityRequestContextFactory;
import com.atlassian.plugin.PluginAccessor;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import org.apache.commons.lang.StringUtils;

import javax.annotation.Nullable;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.UUID;

import static com.google.common.collect.Lists.newArrayList;

/**
 * @since v4.3
 */
public class JiraAppLinksHostApplication extends AbstractInternalHostApplication implements Startable {
    private final ApplicationProperties applicationProperties;
    private final JiraLicenseService jiraLicenseService;
    private final VelocityRequestContextFactory velocityRequestContextFactory;
    private final ProjectManager projectManager;
    private final JiraAuthenticationContext jiraAuthenticationContext;
    private final OsgiServiceProxyFactory osgiServiceProxyFactory;
    private final PermissionManager permissionManager;
    public static final long TIMEOUT = 10000L;
    private TypeAccessor typeAccessor;

    private final ThreadLocal<Boolean> skipPermissionCheck = new ThreadLocal<Boolean>();

    /**
     * Creates a new JiraAppLinksHostApplication instance.
     */
    public JiraAppLinksHostApplication(PluginAccessor pluginAccessor, ApplicationProperties applicationProperties,
            JiraLicenseService jiraLicenseService, VelocityRequestContextFactory velocityRequestContextFactory,
            ProjectManager projectManager, JiraAuthenticationContext jiraAuthenticationContext,
            OsgiServiceProxyFactory osgiServiceProxyFactory, PermissionManager permissionManager) {
        super(pluginAccessor);
        this.applicationProperties = applicationProperties;
        this.jiraLicenseService = jiraLicenseService;
        this.velocityRequestContextFactory = velocityRequestContextFactory;
        this.projectManager = projectManager;
        this.jiraAuthenticationContext = jiraAuthenticationContext;
        this.osgiServiceProxyFactory = osgiServiceProxyFactory;
        this.permissionManager = permissionManager;
    }

    /**
     * @return an absolute {@link java.net.URI} used as the base for constructing links to help pages. e.g. {@code
     *         http://docs.atlassian.com/fisheye/docs-023} or {@code http://confluence.atlassian.com/display/APPLINKS}.
     *         The returned {@link java.net.URI} should not have a trailing slash.
     */
    @Override
    public URI getDocumentationBaseUrl() {
        return URI.create("http://confluence.atlassian.com/display/APPLINKS");
    }

    /**
     * @return the name of this application instance, e.g. "My JIRA Server". If this application type doesn't support
     *         customising the name of the instance, implementations should delegate to {@link
     *         com.atlassian.applinks.host.util.InstanceNameGenerator} to generate a name from the application's base
     *         URL.
     */
    @Override
    public String getName() {
        return applicationProperties.getString(APKeys.JIRA_TITLE);
    }

    /**
     * @return the {@link com.atlassian.applinks.api.ApplicationType} for this application instance. Implementations
     *         should delegate to the {@link com.atlassian.applinks.spi.util.TypeAccessor} to resolve an instance of the
     *         desired type.
     */
    @Override
    public ApplicationType getType() {
        return typeAccessor.getApplicationType(JiraApplicationType.class);
    }

    /**
     * @return an {@link Iterable} containing an {@link com.atlassian.applinks.host.spi.EntityReference} for every
     *         entity in the local instance visible to the currently logged in user. Note, the implementation
     *         <strong>must perform a permission check</strong> and return only entities visible the context user (who
     *         may be anonymous).
     *         User requires to have either the BROWSE project, JIRA Administrator or PROJECT ADMIN permission.
     */
    @Override
    public Iterable<EntityReference> getLocalEntities() {
        final Iterable<Project> projects = Iterables.filter(projectManager.getProjectObjects(),
                new Predicate<Project>() {
                    @Override
                    public boolean apply(@Nullable Project input) {
                        return (input != null) && checkProjectPermissions(input, true);
                    }
                });
        if (!projects.iterator().hasNext()) {
            return Collections.emptyList();
        }
        return newArrayList(Iterables.transform(projects, new ProjectToEntityRef()));
    }

    /**
     * @param key the key of an entity local to this application (e.g. JRA, CONF)
     * @param type the class of the {@link com.atlassian.applinks.api.EntityType} of the entity (e.g. {@link
     * com.atlassian.applinks.api.application.jira.JiraProjectEntityType})
     *
     * @return true, if the specified entity exists, false otherwise. Note, the implementation <strong>must perform a
     *         permission check</strong> and return true if, and only if, the specified entity exists and is visible to
     *         the context user (who may be anonymous). User requires to have either the BROWSE project, JIRA Administrator or PROJECT ADMIN permission.
     */
    @Override
    public boolean doesEntityExist(String key, Class<? extends EntityType> type) {
        if (JiraProjectEntityType.class.isAssignableFrom(type)) {
            Project project = projectManager.getProjectObjByKey(key);
            if (project != null) {
                return checkProjectPermissions(project, true);
            }
        }
        return false;
    }

    /**
     * @param key  the key of an entity local to this application (e.g. JRA, CONF)
     * @param type the class of the {@link EntityType} of the entity (e.g. {@link JiraProjectEntityType})
     * @return true, if the specified entity exists, false otherwise. For a variant on this method with permission
     * checking, use {@link #doesEntityExist(String, Class)}.
     * @since 3.6
     */
    public boolean doesEntityExistNoPermissionCheck(String key, Class<? extends EntityType> type) {
        if (JiraProjectEntityType.class.isAssignableFrom(type)) {
            Project project = projectManager.getProjectObjByKey(key);
            if (project != null) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param domainObject an entity domain object from the application's API (e.g. com.atlassian.jira.project.Project,
     * com.atlassian.confluence.spaces.Space). Implementations are free to choose which objects supported by this class,
     * but the complete list should be maintained on the {@link com.atlassian.applinks.api.EntityLinkService} javadoc.
     * @return an {@link com.atlassian.applinks.host.spi.EntityReference} initialised with the key and type of the
     *         supplied domain object. This method need not perform any permission checking. Implementations should
     *         delegate to the {@link com.atlassian.applinks.spi.util.TypeAccessor} to resolve an instance of the
     *         desired {@link com.atlassian.applinks.api.EntityType}.
     */
    @Override
    public EntityReference toEntityReference(Object domainObject) {
        if (domainObject instanceof Project) {
            Project jiraProject = (Project) domainObject;
            return new DefaultEntityReference(jiraProject.getKey(), jiraProject.getName(),
                    entityType(JiraProjectEntityType.class));
        }

        throw new IllegalArgumentException("Entity is not supported: " + domainObject);
    }

    /**
     * @param key the key of a local entity (e.g. "JRA", "FECRUDEV", "CR-BAM")
     * @param type the class of the {@link com.atlassian.applinks.api.EntityType} of the entity (e.g. {@link
     * com.atlassian.applinks.api.application.jira.JiraProjectEntityType})
     * @return an {@link com.atlassian.applinks.host.spi.EntityReference} initialised with the key and type of the
     *         supplied domain object. This method need not perform any permission checking. Implementations should
     *         delegate to the {@link com.atlassian.applinks.spi.util.TypeAccessor} to resolve an instance of the
     *         specified {@link com.atlassian.applinks.api.EntityType}.
     * @throws IllegalArgumentException if the specified type is not assignable to {@link JiraProjectEntityType} or
     *         if no project with the specified key can be found
     */
    @Override
    public EntityReference toEntityReference(String key, Class<? extends EntityType> type) {
        if (type != null && JiraProjectEntityType.class.isAssignableFrom(type)) {
            Project project = projectManager.getProjectObjByKey(key);
            if (project == null) {
                throw new IllegalArgumentException("The specified key could not be resolved to a project: " + key);
            }
            return new DefaultEntityReference(project.getKey(), project.getName(),
                    entityType(JiraProjectEntityType.class));
        }

        throw new IllegalArgumentException("Entity type is not supported: " + type);
    }

    /**
     * @param entityReference an {@link com.atlassian.applinks.host.spi.EntityReference} representing an entity
     * contained in the local application instance.
     * @return {@code true} if the current user has permission to link or unlink the specified {@link
     *         com.atlassian.applinks.host.spi.EntityReference} to other entities, {@code false} otherwise.
     *         User requires to have either the JIRA Administrator or PROJECT ADMIN permission.
     */
    @Override
    public boolean canManageEntityLinksFor(EntityReference entityReference) {
        if (entityReference != null && entityReference.getType() instanceof JiraProjectEntityType) {
            Project project = projectManager.getProjectObjByKey(entityReference.getKey());
            if (project != null) {
                return checkProjectPermissions(project, false);
            }
        }
        return false;
    }

    /**
     * Method used to perform permission checking for UAL.
     *
     * @param project the project to check the permissions for.
     * @param allowBrowse whether to pass the permission check if the user has BROWSE project permission.
     * @return true if the user has the required permission.
     */
    private boolean checkProjectPermissions(Project project, boolean allowBrowse) {
        if (skipPermissions()) {
            return true;
        }
        if (project == null) {
            return false;
        }

        return (allowBrowse && permissionManager.hasPermission(Permissions.BROWSE, project,
                jiraAuthenticationContext.getUser()))
                || permissionManager.hasPermission(Permissions.ADMINISTER, jiraAuthenticationContext.getUser())
                || permissionManager.hasPermission(Permissions.PROJECT_ADMIN, project,
                        jiraAuthenticationContext.getUser());
    }

    private boolean skipPermissions() {
        return skipPermissionCheck.get() == null ? false : skipPermissionCheck.get().booleanValue();
    }

    public void setSkipPermissionCheck(boolean skip) {
        skipPermissionCheck.set(skip);
    }

    public void clearSkipPermissionCheck() {
        skipPermissionCheck.remove();
    }

    /**
     * @return {@code true} if the host application allows public signup, {@code false} otherwise.
     */
    @Override
    public boolean hasPublicSignup() {
        return "public".equals(applicationProperties.getString(APKeys.JIRA_MODE));
    }

    @Override
    public ApplicationId getId() {
        String serverId = jiraLicenseService.getServerId();
        try {
            return new ApplicationId(UUID.nameUUIDFromBytes(serverId.getBytes("UTF-8")).toString());
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException("UTF-8 is not supported. WTF?", e);
        }
    }

    /**
     * Returns the base URL that is configured for this JIRA instance.
     *
     * @return a String containing the base URL that is configured for this JIRA instance
     */
    @Override
    public URI getBaseUrl() {
        String baseUrl = applicationProperties.getString(APKeys.JIRA_BASEURL);
        if (StringUtils.isEmpty(baseUrl)) {
            baseUrl = velocityRequestContextFactory.getJiraVelocityRequestContext().getCanonicalBaseUrl();
        }
        try {
            return new URI(baseUrl);
        } catch (URISyntaxException e) {
            throw new IllegalStateException("Invalid base url: " + baseUrl, e);
        }
    }

    /**
     * Uses the TypeAccessor that is made available by the UAL plugin to create an EntityType instance for the given
     * entity type class.
     *
     * @param entityTypeClass an EntityType class
     * @return an EntityType
     */
    private <T extends com.atlassian.applinks.api.EntityType> T entityType(Class<T> entityTypeClass) {
        return typeAccessor.getEntityType(entityTypeClass);
    }

    /**
     * Returns the currently logged-in user.
     *
     * @return a User object for the currently logged-in user
     */
    private User getUser() {
        return jiraAuthenticationContext.getLoggedInUser();
    }

    /**
    * The OsgiServiceProxyFactory creates a proxy to the TypeAccessor which is a component of the applinks-plugin. The
    * TypeAccessor is not available when the host application starts, but when the plugins framework has finished
    * starting.
    */
    @Override
    public void start() throws Exception {
        typeAccessor = osgiServiceProxyFactory.createProxy(TypeAccessor.class, TIMEOUT);
    }

    /**
     * Function class to convert from a JIRA Project into an EntityReference.
     */
    private class ProjectToEntityRef implements Function<Project, EntityReference> {

        @Override
        public EntityReference apply(@Nullable Project from) {
            return new DefaultEntityReference(from.getKey(), from.getName(),
                    entityType(JiraProjectEntityType.class));
        }
    }

}