/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * See LICENSE.txt included in this distribution for the specific * language governing permissions and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at LICENSE.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved. */ package org.opensolaris.opengrok.web; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.function.Predicate; import org.opensolaris.opengrok.configuration.Group; import org.opensolaris.opengrok.configuration.Project; import org.opensolaris.opengrok.history.RepositoryInfo; import static org.opensolaris.opengrok.web.PageConfig.OPEN_GROK_PROJECT; /** * Preprocessing of projects, repositories and groups for the UI * * @author Krystof Tulinger */ public final class ProjectHelper { private static final String ATTR_NAME = "project_helper"; private static final String PROJECT_HELPER_GROUPS = "project_helper_groups"; private static final String PROJECT_HELPER_UNGROUPED_PROJECTS = "project_helper_ungrouped_projects"; private static final String PROJECT_HELPER_UNGROUPED_REPOSITORIES = "project_helper_ungrouped_repositories"; private static final String PROJECT_HELPER_GROUPED_PROJECT_GROUP = "project_helper_grouped_project_group_"; private static final String PROJECT_HELPER_GROUPED_REPOSITORIES = "project_helper_grouped_repositories"; private static final String PROJECT_HELPER_ALLOWED_SUBGROUP = "project_helper_allowed_subgroup"; private static final String PROJECT_HELPER_GROUPED_REPOSITORIES_GROUP = "project_helper_grouped_repositories_group_"; private static final String PROJECT_HELPER_GROUPED_PROJECTS = "project_helper_grouped_projects"; private static final String PROJECT_HELPER_SUBGROUPS_OF = "project_helper_subgroups_of_"; private static final String PROJECT_HELPER_FAVOURITE_GROUP = "project_helper_favourite_group"; private PageConfig cfg; /** * Set of groups */ private final Set<Group> groups; /** * Set of projects (not repositories) without group */ private final Set<Project> projects; /** * Set of all repositories without group */ private final Set<Project> repositories; /** * Set of all projects with group */ private final Set<Project> all_projects = new TreeSet<>(); /** * Set of all repositories with group */ private final Set<Project> all_repositories = new TreeSet<>(); private ProjectHelper(PageConfig cfg) { this.cfg = cfg; groups = new TreeSet<>(cfg.getEnv().getGroups()); projects = new TreeSet<>(); repositories = new TreeSet<>(); populateGroups(); } /** * Object of project helper should be ONLY obtained by calling * PageConfig#getProjectHelper. * * @param cfg current page config * @return instance of ProjectHelper * @see PageConfig#getProjectHelper() */ public static ProjectHelper getInstance(PageConfig cfg) { ProjectHelper instance = (ProjectHelper) cfg.getRequestAttribute(ATTR_NAME); if (instance == null) { instance = new ProjectHelper(cfg); cfg.setRequestAttribute(ATTR_NAME, instance); } return instance; } /** * Get repository info list for particular project. A copy of the list is * returned always to allow concurrent modifications of the list in the * caller. The items in the list shall not be modified concurrently, though. * * @param p the project for which we find the repository info list * @return Copy of a list of repository info or empty list if no info is * found */ public List<RepositoryInfo> getRepositoryInfo(Project p) { if (!cfg.isAllowed(p)) { return new ArrayList<>(); } Map<Project, List<RepositoryInfo>> map = cfg.getEnv().getProjectRepositoriesMap(); List<RepositoryInfo> info = map.get(p); return info == null ? new ArrayList<>() : new ArrayList<>(info); } /** * Generates ungrouped projects and repositories. */ private void populateGroups() { groups.addAll(cfg.getEnv().getGroups()); for (Project project : cfg.getEnv().getProjectList()) { // filterProjects only groups which match project's description Set<Group> copy = new TreeSet<>(groups); copy.removeIf(new Predicate<Group>() { @Override public boolean test(Group g) { return !g.match(project); } }); // if no group matches the project, add it to not-grouped projects if (copy.isEmpty()) { if (cfg.getEnv().getProjectRepositoriesMap().get(project) == null) { projects.add(project); } else { repositories.add(project); } } } // populate all grouped for (Group g : getGroups()) { all_projects.addAll(g.getProjects()); all_repositories.addAll(g.getRepositories()); } } /** * Filters set of projects based on the authorizator options. * * @param p set of projects * @return filtered set of projects */ private Set<Project> filterProjects(Set<Project> p) { Set<Project> repos = new TreeSet<>(p); repos.removeIf(new Predicate<Project>() { @Override public boolean test(Project t) { return !cfg.isAllowed(t); } }); return repos; } /** * Filters set of groups based on the authorizator options. * * @param p set of groups * @return filtered set of groups */ private Set<Group> filterGroups(Set<Group> p) { Set<Group> grps = new TreeSet<>(p); grps.removeIf(new Predicate<Group>() { @Override public boolean test(Group t) { return !(cfg.isAllowed(t) || hasAllowedSubgroup(t)); } }); return grps; } /** * Filters and saves the original set of projects into request's attribute. * * @param name attribute name * @param original original set * @return filtered set */ @SuppressWarnings(value = "unchecked") private Set<Project> cacheProjects(String name, Set<Project> original) { Set<Project> p = (Set<Project>) cfg.getRequestAttribute(name); if (p == null) { p = filterProjects(original); cfg.setRequestAttribute(name, p); } return p; } /** * Filters and saves the original set of groups into request's attribute. * * @param name attribute name * @param original original set * @return filtered set */ @SuppressWarnings(value = "unchecked") private Set<Group> cacheGroups(String name, Set<Group> original) { Set<Group> p = (Set<Group>) cfg.getRequestAttribute(name); if (p == null) { p = filterGroups(original); cfg.setRequestAttribute(name, p); } return p; } /** * @return filtered groups */ public Set<Group> getGroups() { return cacheGroups(PROJECT_HELPER_GROUPS, groups); } /** * @return filtered ungrouped projects */ public Set<Project> getProjects() { return cacheProjects(PROJECT_HELPER_UNGROUPED_PROJECTS, projects); } /** * @return filtered ungrouped repositories */ public Set<Project> getRepositories() { return cacheProjects(PROJECT_HELPER_UNGROUPED_REPOSITORIES, repositories); } /** * @param g group * @return filtered group's projects */ public Set<Project> getProjects(Group g) { if (!cfg.isAllowed(g)) { return new TreeSet<>(); } return cacheProjects(PROJECT_HELPER_GROUPED_PROJECT_GROUP + g.getName().toLowerCase(), g.getProjects()); } /** * @param g group * @return filtered group's repositories */ public Set<Project> getRepositories(Group g) { if (!cfg.isAllowed(g)) { return new TreeSet<>(); } return cacheProjects(PROJECT_HELPER_GROUPED_REPOSITORIES_GROUP + g.getName().toLowerCase(), g.getRepositories()); } /** * @return filtered grouped projects */ public Set<Project> getGroupedProjects() { return cacheProjects(PROJECT_HELPER_GROUPED_PROJECTS, all_projects); } /** * @return filtered grouped repositories */ public Set<Project> getGroupedRepositories() { return cacheProjects(PROJECT_HELPER_GROUPED_REPOSITORIES, all_repositories); } /** * @see #getProjects() * @return filtered ungrouped projects */ public Set<Project> getUngroupedProjects() { return cacheProjects(PROJECT_HELPER_UNGROUPED_PROJECTS, projects); } /** * @see #getRepositories() * @return filtered ungrouped projects */ public Set<Project> getUngroupedRepositories() { return cacheProjects(PROJECT_HELPER_UNGROUPED_REPOSITORIES, repositories); } /** * @return filtered projects and repositories */ public Set<Project> getAllGrouped() { return mergeProjects(getGroupedProjects(), getGroupedRepositories()); } /** * @param g group * @return filtered set of all projects and repositories in group g */ public Set<Project> getAllGrouped(Group g) { if (!cfg.isAllowed(g)) { return new TreeSet<>(); } return mergeProjects(filterProjects(g.getProjects()), filterProjects(g.getRepositories())); } /** * @return filtered set of all projects and repositories without group */ public Set<Project> getAllUngrouped() { return mergeProjects(getUngroupedProjects(), getUngroupedRepositories()); } /** * @return filtered set of all projects and repositories no matter if * grouped or ungrouped */ public Set<Project> getAllProjects() { return mergeProjects(getAllUngrouped(), getAllGrouped()); } /** * @param g group * @return filtered set of subgroups */ public Set<Group> getSubgroups(Group g) { if (!cfg.isAllowed(g)) { return new TreeSet<>(); } return cacheGroups(PROJECT_HELPER_SUBGROUPS_OF + g.getName().toLowerCase(), g.getSubgroups()); } /** * Checks if given group contains a subgroup which is allowed by the * AuthorizationFramework. * * This should be used for deciding if this group should be written in the * group hierarchy in the resulting html because it contains other allowed * groups. * * @param group group * @return true it it has an allowed subgroup */ @SuppressWarnings(value = "unchecked") public boolean hasAllowedSubgroup(Group group) { Boolean val; Map<String, Boolean> p = (Map<String, Boolean>) cfg.getRequestAttribute(PROJECT_HELPER_ALLOWED_SUBGROUP); if (p == null) { p = new TreeMap<String, Boolean>(); cfg.setRequestAttribute(PROJECT_HELPER_ALLOWED_SUBGROUP, p); } val = p.get(group.getName()); if (val == null) { val = cfg.isAllowed(group); val = val && !filterGroups(group.getDescendants()).isEmpty(); p = (Map<String, Boolean>) cfg.getRequestAttribute(PROJECT_HELPER_ALLOWED_SUBGROUP); p.put(group.getName(), val); } cfg.setRequestAttribute(PROJECT_HELPER_ALLOWED_SUBGROUP, p); return val; } /** * Checks if given group contains a favourite project. * * Favourite project is a project which is contained in the OpenGrokProject * cookie, i. e. it has been searched or viewed by the user. * * This should by used to determine if this group should be displayed * expanded or rolled up. * * @param group group * @return true if it has favourite project */ @SuppressWarnings(value = "unchecked") public boolean hasFavourite(Group group) { Boolean val; Map<String, Boolean> p = (Map<String, Boolean>) cfg.getRequestAttribute(PROJECT_HELPER_FAVOURITE_GROUP); if (p == null) { p = new TreeMap<String, Boolean>(); cfg.setRequestAttribute(PROJECT_HELPER_FAVOURITE_GROUP, p); } val = p.get(group.getName()); if (val == null) { Set<Project> favourite = getAllGrouped(); favourite.removeIf(new Predicate<Project>() { @Override public boolean test(Project t) { // project is favourite if (!isFavourite(t)) { return true; } // project is contained in group repositories if (getRepositories(group).contains(t)) { return false; } // project is contained in group projects if (getProjects(group).contains(t)) { return false; } // project is contained in subgroup's repositories and projects for (Group g : filterGroups(group.getDescendants())) { if (getProjects(g).contains(t)) { return false; } if (getRepositories(g).contains(t)) { return false; } } return true; } }); val = !favourite.isEmpty(); p.put(group.getName(), val); } cfg.setRequestAttribute(PROJECT_HELPER_FAVOURITE_GROUP, p); return val; } /** * Checks if the project is a favourite project * * @param project project * @return true if it is favourite */ public boolean isFavourite(Project project) { return cfg.getCookieVals(OPEN_GROK_PROJECT).contains(project.getName()); } /** * Checks if there is a favourite project in ungrouped projects. * * This should by used to determine if this 'other' section should be * displayed expanded or rolled up. * * @return true if there is */ public boolean hasUngroupedFavourite() { for (Project p : getAllUngrouped()) { if (isFavourite(p)) { return true; } } return false; } private static Set<Project> mergeProjects(Set<Project> p1, Set<Project> p2) { Set<Project> set = new TreeSet<Project>(); set.addAll(p1); set.addAll(p2); return set; } public static void cleanup(PageConfig cfg) { if (cfg != null) { ProjectHelper helper = (ProjectHelper) cfg.getRequestAttribute(ATTR_NAME); if (helper == null) { return; } cfg.removeAttribute(ATTR_NAME); helper.cfg = null; } } }