• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

oracle / opengrok / #3670

01 Nov 2023 10:10AM UTC coverage: 74.437% (-0.7%) from 75.16%
#3670

push

web-flow
Fix Sonar codesmell issues (#4460)

Signed-off-by: Gino Augustine <ginoaugustine@gmail.com>

308 of 308 new or added lines in 27 files covered. (100.0%)

43623 of 58604 relevant lines covered (74.44%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.27
/opengrok-web/src/main/java/org/opengrok/web/ProjectHelper.java
1
/*
2
 * CDDL HEADER START
3
 *
4
 * The contents of this file are subject to the terms of the
5
 * Common Development and Distribution License (the "License").
6
 * You may not use this file except in compliance with the License.
7
 *
8
 * See LICENSE.txt included in this distribution for the specific
9
 * language governing permissions and limitations under the License.
10
 *
11
 * When distributing Covered Code, include this CDDL HEADER in each
12
 * file and include the License file at LICENSE.txt.
13
 * If applicable, add the following below this CDDL HEADER, with the
14
 * fields enclosed by brackets "[]" replaced with your own identifying
15
 * information: Portions Copyright [yyyy] [name of copyright owner]
16
 *
17
 * CDDL HEADER END
18
 */
19

20
/*
21
 * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
22
 * Portions Copyright (c) 2018, Chris Fraire <cfraire@me.com>.
23
 * Portions Copyright (c) 2019, Krystof Tulinger <k.tulinger@seznam.cz>.
24
 */
25
package org.opengrok.web;
26

27
import java.util.ArrayList;
28
import java.util.Comparator;
29
import java.util.List;
30
import java.util.Locale;
31
import java.util.Map;
32
import java.util.Optional;
33
import java.util.Set;
34
import java.util.TreeMap;
35
import java.util.TreeSet;
36
import java.util.stream.Collectors;
37

38
import org.opengrok.indexer.configuration.Group;
39
import org.opengrok.indexer.configuration.Project;
40
import org.opengrok.indexer.history.RepositoryInfo;
41

42
import static org.opengrok.web.PageConfig.OPEN_GROK_PROJECT;
43

44
/**
45
 * Preprocessing of projects, repositories and groups for the UI.
46
 *
47
 * @author Krystof Tulinger
48
 */
49
public final class ProjectHelper {
50

51
    private static final String ATTR_NAME = "project_helper";
52

53
    private static final String PROJECT_HELPER_GROUPS = "project_helper_groups";
54
    private static final String PROJECT_HELPER_UNGROUPED_PROJECTS = "project_helper_ungrouped_projects";
55
    private static final String PROJECT_HELPER_UNGROUPED_REPOSITORIES = "project_helper_ungrouped_repositories";
56
    private static final String PROJECT_HELPER_GROUPED_PROJECT_GROUP = "project_helper_grouped_project_group_";
57
    private static final String PROJECT_HELPER_GROUPED_REPOSITORIES = "project_helper_grouped_repositories";
58
    private static final String PROJECT_HELPER_ALLOWED_SUBGROUP = "project_helper_allowed_subgroup";
59
    private static final String PROJECT_HELPER_GROUPED_REPOSITORIES_GROUP = "project_helper_grouped_repositories_group_";
60
    private static final String PROJECT_HELPER_GROUPED_PROJECTS = "project_helper_grouped_projects";
61
    private static final String PROJECT_HELPER_SUBGROUPS_OF = "project_helper_subgroups_of_";
62
    private static final String PROJECT_HELPER_FAVOURITE_GROUP = "project_helper_favourite_group";
63

64
    private static final Comparator<RepositoryInfo> REPOSITORY_NAME_COMPARATOR = Comparator.comparing(RepositoryInfo::getDirectoryName);
1✔
65

66
    private PageConfig cfg;
67
    /**
68
     * Set of groups.
69
     */
70
    private final Set<Group> groups;
71
    /**
72
     * Set of projects (not repositories) without group.
73
     */
74
    private final Set<Project> ungroupedProjects;
75
    /**
76
     * Set of all repositories without group.
77
     */
78
    private final Set<Project> ungroupedRepositories;
79
    /**
80
     * Set of all projects with group.
81
     */
82
    private final Set<Project> allProjects = new TreeSet<>();
1✔
83
    /**
84
     * Set of all repositories with group.
85
     */
86
    private final Set<Project> allRepositories = new TreeSet<>();
1✔
87

88
    private ProjectHelper(PageConfig cfg) {
1✔
89
        this.cfg = cfg;
1✔
90
        groups = new TreeSet<>(cfg.getEnv().getGroups().values());
1✔
91
        ungroupedProjects = new TreeSet<>();
1✔
92
        ungroupedRepositories = new TreeSet<>();
1✔
93

94
        populateGroups();
1✔
95
    }
1✔
96

97
    /**
98
     * Object of project helper should be ONLY obtained by calling
99
     * PageConfig#getProjectHelper.
100
     *
101
     * @param cfg current page config
102
     * @return instance of ProjectHelper
103
     * @see PageConfig#getProjectHelper()
104
     */
105
    public static ProjectHelper getInstance(PageConfig cfg) {
106
        ProjectHelper instance = (ProjectHelper) cfg.getRequestAttribute(ATTR_NAME);
1✔
107
        if (instance == null) {
1✔
108
            instance = new ProjectHelper(cfg);
1✔
109
            cfg.setRequestAttribute(ATTR_NAME, instance);
1✔
110
        }
111
        return instance;
1✔
112
    }
113

114
    /**
115
     * Get repository info list for particular project. A copy of the list is
116
     * returned always to allow concurrent modifications of the list in the
117
     * caller. The items in the list shall not be modified concurrently, though.
118
     *
119
     * @param p the project for which we find the repository info list
120
     * @return Copy of a list of repository info or empty list if no info is
121
     * found
122
     */
123
    public List<RepositoryInfo> getRepositoryInfo(Project p) {
124
        if (!cfg.isAllowed(p)) {
1✔
125
            return new ArrayList<>();
1✔
126
        }
127
        Map<Project, List<RepositoryInfo>> map = cfg.getEnv().getProjectRepositoriesMap();
1✔
128
        List<RepositoryInfo> info = map.get(p);
1✔
129
        return info == null ? new ArrayList<>() : new ArrayList<>(info);
1✔
130
    }
131

132
    /**
133
     * Get repository info list for particular project. A copy of the list is
134
     * returned always to allow concurrent modifications of the list in the
135
     * caller. The items in the list shall not be modified concurrently, though.
136
     * This list is sorted with respect {@link #REPOSITORY_NAME_COMPARATOR}.
137
     *
138
     * @param p the project for which we find the repository info list
139
     * @return Copy of a list of repository info or empty list if no info is
140
     * found
141
     */
142
    public List<RepositoryInfo> getSortedRepositoryInfo(Project p) {
143
        return getRepositoryInfo(p)
×
144
                .stream()
×
145
                .sorted(REPOSITORY_NAME_COMPARATOR)
×
146
                .collect(Collectors.toList());
×
147
    }
148

149
    /**
150
     * Generates ungrouped projects and repositories.
151
     */
152
    private void populateGroups() {
153
        groups.addAll(cfg.getEnv().getGroups().values());
1✔
154
        for (Project project : cfg.getEnv().getProjectList()) {
1✔
155
            // filterProjects() only adds groups which match project's name.
156
            Set<Group> copy = Group.matching(project, groups);
1✔
157

158
            // If no group matches the project, add it to not-grouped projects.
159
            if (copy.isEmpty()) {
1✔
160
                if (cfg.getEnv().getProjectRepositoriesMap().get(project) == null) {
1✔
161
                    ungroupedProjects.add(project);
1✔
162
                } else {
163
                    ungroupedRepositories.add(project);
1✔
164
                }
165
            }
166
        }
1✔
167

168
        // populate all grouped
169
        for (Group g : getGroups()) {
1✔
170
            allProjects.addAll(g.getProjects());
1✔
171
            allRepositories.addAll(g.getRepositories());
1✔
172
        }
1✔
173
    }
1✔
174

175
    /**
176
     * Filters set of projects based on the authorizer options
177
     * and whether the project is indexed.
178
     *
179
     * @param p set of projects
180
     * @return filtered set of projects
181
     */
182
    private Set<Project> filterProjects(Set<Project> p) {
183
        Set<Project> repos = new TreeSet<>(p);
1✔
184
        repos.removeIf(t -> !cfg.isAllowed(t) || !t.isIndexed());
1✔
185
        return repos;
1✔
186
    }
187

188
    /**
189
     * Filters set of groups based on the authorizer options.
190
     *
191
     * @param p set of groups
192
     * @return filtered set of groups
193
     */
194
    private Set<Group> filterGroups(Set<Group> p) {
195
        Set<Group> grps = new TreeSet<>(p);
1✔
196
        grps.removeIf(t -> !(cfg.isAllowed(t) || hasAllowedSubgroup(t)));
1✔
197
        return grps;
1✔
198
    }
199

200
    /**
201
     * Filters and saves the original set of projects into request's attribute.
202
     *
203
     * @param name attribute name
204
     * @param original original set
205
     * @return filtered set
206
     */
207
    @SuppressWarnings(value = "unchecked")
208
    private Set<Project> cacheProjects(String name, Set<Project> original) {
209
        Set<Project> p = (Set<Project>) cfg.getRequestAttribute(name);
1✔
210
        if (p == null) {
1✔
211
            p = filterProjects(original);
1✔
212
            cfg.setRequestAttribute(name, p);
1✔
213
        }
214
        return p;
1✔
215
    }
216

217
    /**
218
     * Filters and saves the original set of groups into request's attribute.
219
     *
220
     * @param name attribute name
221
     * @param original original set
222
     * @return filtered set
223
     */
224
    @SuppressWarnings(value = "unchecked")
225
    private Set<Group> cacheGroups(String name, Set<Group> original) {
226
        Set<Group> p = (Set<Group>) cfg.getRequestAttribute(name);
1✔
227
        if (p == null) {
1✔
228
            p = filterGroups(original);
1✔
229
            cfg.setRequestAttribute(name, p);
1✔
230
        }
231
        return p;
1✔
232
    }
233

234
    /**
235
     * @return filtered groups
236
     */
237
    public Set<Group> getGroups() {
238
        return cacheGroups(PROJECT_HELPER_GROUPS, groups);
1✔
239
    }
240

241
    /**
242
     * @return filtered ungrouped projects
243
     */
244
    public Set<Project> getProjects() {
245
        return cacheProjects(PROJECT_HELPER_UNGROUPED_PROJECTS, ungroupedProjects);
1✔
246
    }
247

248
    /**
249
     * @return filtered ungrouped repositories
250
     */
251
    public Set<Project> getRepositories() {
252
        return cacheProjects(PROJECT_HELPER_UNGROUPED_REPOSITORIES, ungroupedRepositories);
1✔
253
    }
254

255
    /**
256
     * @param g group
257
     * @return filtered group's projects
258
     */
259
    public Set<Project> getProjects(Group g) {
260
        if (!cfg.isAllowed(g)) {
1✔
261
            return new TreeSet<>();
1✔
262
        }
263
        return cacheProjects(PROJECT_HELPER_GROUPED_PROJECT_GROUP +
1✔
264
                g.getName().toLowerCase(Locale.ROOT), g.getProjects());
1✔
265
    }
266

267
    /**
268
     * @param g group
269
     * @return filtered group's repositories
270
     */
271
    public Set<Project> getRepositories(Group g) {
272
        if (!cfg.isAllowed(g)) {
1✔
273
            return new TreeSet<>();
1✔
274
        }
275
        return cacheProjects(PROJECT_HELPER_GROUPED_REPOSITORIES_GROUP +
1✔
276
                g.getName().toLowerCase(Locale.ROOT), g.getRepositories());
1✔
277
    }
278

279
    /**
280
     * @return filtered grouped projects
281
     */
282
    public Set<Project> getGroupedProjects() {
283
        return cacheProjects(PROJECT_HELPER_GROUPED_PROJECTS, allProjects);
1✔
284
    }
285

286
    /**
287
     * @return filtered grouped repositories
288
     */
289
    public Set<Project> getGroupedRepositories() {
290
        return cacheProjects(PROJECT_HELPER_GROUPED_REPOSITORIES, allRepositories);
1✔
291
    }
292

293
    /**
294
     * @see #getProjects()
295
     * @return filtered ungrouped projects
296
     */
297
    public Set<Project> getUngroupedProjects() {
298
        return cacheProjects(PROJECT_HELPER_UNGROUPED_PROJECTS, ungroupedProjects);
1✔
299
    }
300

301
    /**
302
     * @see #getRepositories()
303
     * @return filtered ungrouped projects
304
     */
305
    public Set<Project> getUngroupedRepositories() {
306
        return cacheProjects(PROJECT_HELPER_UNGROUPED_REPOSITORIES, ungroupedRepositories);
1✔
307
    }
308

309
    /**
310
     * @return filtered projects and repositories
311
     */
312
    public Set<Project> getAllGrouped() {
313
        return mergeProjects(getGroupedProjects(), getGroupedRepositories());
1✔
314
    }
315

316
    /**
317
     * @param g group
318
     * @return filtered set of all projects and repositories in group g
319
     */
320
    public Set<Project> getAllGrouped(Group g) {
321
        if (!cfg.isAllowed(g)) {
1✔
322
            return new TreeSet<>();
1✔
323
        }
324
        return mergeProjects(filterProjects(g.getProjects()), filterProjects(g.getRepositories()));
1✔
325
    }
326

327
    /**
328
     * @return filtered set of all projects and repositories without group
329
     */
330
    public Set<Project> getAllUngrouped() {
331
        return mergeProjects(getUngroupedProjects(), getUngroupedRepositories());
1✔
332
    }
333

334
    /**
335
     * @return filtered set of all projects and repositories no matter if
336
     * grouped or ungrouped
337
     */
338
    public Set<Project> getAllProjects() {
339
        return mergeProjects(getAllUngrouped(), getAllGrouped());
1✔
340
    }
341

342
    /**
343
     * @param g group
344
     * @return filtered set of subgroups
345
     */
346
    public Set<Group> getSubgroups(Group g) {
347
        if (!cfg.isAllowed(g)) {
1✔
348
            return new TreeSet<>();
1✔
349
        }
350
        return cacheGroups(PROJECT_HELPER_SUBGROUPS_OF +
1✔
351
                g.getName().toLowerCase(Locale.ROOT), g.getSubgroups());
1✔
352
    }
353

354
    /**
355
     * Checks if given group contains a subgroup which is allowed by the
356
     * AuthorizationFramework.
357
     *
358
     * This should be used for deciding if this group should be written in the
359
     * group hierarchy in the resulting html because it contains other allowed
360
     * groups.
361
     *
362
     * @param group group
363
     * @return true it it has an allowed subgroup
364
     */
365
    @SuppressWarnings(value = "unchecked")
366
    public boolean hasAllowedSubgroup(Group group) {
367
        Boolean val;
368
        Map<String, Boolean> p = (Map<String, Boolean>) cfg.getRequestAttribute(PROJECT_HELPER_ALLOWED_SUBGROUP);
1✔
369
        if (p == null) {
1✔
370
            p = new TreeMap<>();
1✔
371
            cfg.setRequestAttribute(PROJECT_HELPER_ALLOWED_SUBGROUP, p);
1✔
372
        }
373
        val = p.get(group.getName());
1✔
374
        if (val == null) {
1✔
375
            val = cfg.isAllowed(group);
1✔
376
            val = val && !filterGroups(group.getDescendants()).isEmpty();
1✔
377
            p = (Map<String, Boolean>) cfg.getRequestAttribute(PROJECT_HELPER_ALLOWED_SUBGROUP);
1✔
378
            p.put(group.getName(), val);
1✔
379
        }
380
        cfg.setRequestAttribute(PROJECT_HELPER_ALLOWED_SUBGROUP, p);
1✔
381
        return val;
1✔
382
    }
383

384
    /**
385
     * Checks if given group contains a favourite project.
386
     *
387
     * Favourite project is a project which is contained in the OpenGrokProject
388
     * cookie, i. e. it has been searched or viewed by the user.
389
     *
390
     * This should by used to determine if this group should be displayed
391
     * expanded or rolled up.
392
     *
393
     * @param group group
394
     * @return true if it has favourite project
395
     */
396
    @SuppressWarnings(value = "unchecked")
397
    public boolean hasFavourite(Group group) {
398
        Map<String, Boolean> p = (Map<String, Boolean>) Optional.ofNullable(
1✔
399
                    cfg.getRequestAttribute(PROJECT_HELPER_FAVOURITE_GROUP)
1✔
400
                )
401
                .orElseGet(TreeMap::new);
1✔
402
        var val = p.get(group.getName());
1✔
403

404
        if (val == null) {
1✔
405
            val = getAllGrouped().stream()
1✔
406
                    .filter(this::isFavourite)
1✔
407
                    .anyMatch(project -> isInProjectsReposOrDescendants(group, project));
1✔
408
            p.put(group.getName(), val);
1✔
409
        }
410
        cfg.setRequestAttribute(PROJECT_HELPER_FAVOURITE_GROUP, p);
1✔
411
        return val;
1✔
412
    }
413

414
    private boolean isInProjectsReposOrDescendants(Group group, Project project ) {
415
        if (isInProjectsOrRepos(group, project)) {
1✔
416
            return true;
1✔
417
        }
418
        // project is contained in group projects
419
        return filterGroups(group.getDescendants())
×
420
                .stream()
×
421
                .anyMatch(descendant -> isInProjectsOrRepos(descendant, project));
×
422
    }
423

424
    private boolean isInProjectsOrRepos(Group group, Project project ) {
425
        // project is contained in group repositories
426
        if (getRepositories(group).contains(project) ) {
1✔
427
            return true;
×
428
        }
429
        // project is contained in group projects
430
        return getProjects(group).contains(project);
1✔
431
    }
432

433
    /**
434
     * Checks if the project is a favourite project.
435
     *
436
     * @param project project
437
     * @return true if it is favourite
438
     */
439
    public boolean isFavourite(Project project) {
440
        return cfg.getCookieVals(OPEN_GROK_PROJECT).contains(project.getName());
1✔
441
    }
442

443
    /**
444
     * Checks if there is a favourite project in ungrouped projects.
445
     *
446
     * This should by used to determine if this 'other' section should be
447
     * displayed expanded or rolled up.
448
     *
449
     * @return true if there is
450
     */
451
    public boolean hasUngroupedFavourite() {
452
        for (Project p : getAllUngrouped()) {
1✔
453
            if (isFavourite(p)) {
1✔
454
                return true;
1✔
455
            }
456
        }
1✔
457
        return false;
1✔
458
    }
459

460
    private static Set<Project> mergeProjects(Set<Project> p1, Set<Project> p2) {
461
        Set<Project> set = new TreeSet<>();
1✔
462
        set.addAll(p1);
1✔
463
        set.addAll(p2);
1✔
464
        return set;
1✔
465
    }
466

467
    public static void cleanup(PageConfig cfg) {
468
        if (cfg != null) {
1✔
469
            ProjectHelper helper = (ProjectHelper) cfg.getRequestAttribute(ATTR_NAME);
1✔
470
            if (helper == null) {
1✔
471
                return;
1✔
472
            }
473
            cfg.removeAttribute(ATTR_NAME);
×
474
            helper.cfg = null;
×
475
        }
476
    }
×
477
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc