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

oracle / opengrok / #3668

26 Oct 2023 04:47PM UTC coverage: 75.16% (+0.004%) from 75.156%
#3668

push

web-flow
opengrok web project code smell fixes (#4457)

---------

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

19 of 19 new or added lines in 4 files covered. (100.0%)

44053 of 58612 relevant lines covered (75.16%)

0.75 hits per line

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

87.78
/opengrok-web/src/main/java/org/opengrok/web/api/v1/controller/ProjectsController.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) 2018, 2023, Oracle and/or its affiliates. All rights reserved.
22
 * Portions Copyright (c) 2020, Chris Fraire <cfraire@me.com>.
23
 */
24
package org.opengrok.web.api.v1.controller;
25

26
import static org.opengrok.indexer.history.RepositoryFactory.getRepository;
27

28
import java.io.File;
29
import java.io.IOException;
30
import java.nio.file.Paths;
31
import java.util.ArrayList;
32
import java.util.Collection;
33
import java.util.Collections;
34
import java.util.List;
35
import java.util.Map;
36
import java.util.Optional;
37
import java.util.Set;
38
import java.util.TreeSet;
39
import java.util.concurrent.CompletableFuture;
40
import java.util.logging.Level;
41
import java.util.logging.Logger;
42
import java.util.stream.Collectors;
43

44
import jakarta.inject.Inject;
45
import jakarta.servlet.http.HttpServletRequest;
46
import jakarta.ws.rs.Consumes;
47
import jakarta.ws.rs.DELETE;
48
import jakarta.ws.rs.GET;
49
import jakarta.ws.rs.NotFoundException;
50
import jakarta.ws.rs.POST;
51
import jakarta.ws.rs.PUT;
52
import jakarta.ws.rs.Path;
53
import jakarta.ws.rs.PathParam;
54
import jakarta.ws.rs.Produces;
55
import jakarta.ws.rs.WebApplicationException;
56
import jakarta.ws.rs.core.Context;
57
import jakarta.ws.rs.core.MediaType;
58
import jakarta.ws.rs.core.Response;
59
import org.opengrok.indexer.configuration.CommandTimeoutType;
60
import org.opengrok.indexer.configuration.Group;
61
import org.opengrok.indexer.configuration.Project;
62
import org.opengrok.indexer.configuration.RuntimeEnvironment;
63
import org.opengrok.indexer.history.HistoryGuru;
64
import org.opengrok.indexer.history.Repository;
65
import org.opengrok.indexer.history.RepositoryInfo;
66
import org.opengrok.indexer.index.IndexDatabase;
67
import org.opengrok.indexer.logger.LoggerFactory;
68
import org.opengrok.indexer.util.ClassUtil;
69
import org.opengrok.indexer.util.IOUtils;
70
import org.opengrok.web.api.ApiTask;
71
import org.opengrok.indexer.web.Laundromat;
72
import org.opengrok.web.api.ApiTaskManager;
73
import org.opengrok.web.api.v1.suggester.provider.service.SuggesterService;
74

75
@Path(ProjectsController.PROJECTS_PATH)
76
public class ProjectsController {
1✔
77

78
    private static final Logger LOGGER = LoggerFactory.getLogger(ProjectsController.class);
1✔
79
    private static final String NO_REPO_LOG_MSG = "no repositories found for project ''{0}''";
80

81
    public static final String PROJECTS_PATH = "projects";
82

83
    private final RuntimeEnvironment env = RuntimeEnvironment.getInstance();
1✔
84

85
    @Inject
86
    private SuggesterService suggester;
87

88
    @POST
89
    @Consumes(MediaType.TEXT_PLAIN)
90
    public Response addProject(@Context HttpServletRequest request, String projectNameParam) {
91
        // Avoid classification as a taint bug.
92
        final String projectName = Laundromat.launderInput(projectNameParam);
1✔
93

94
        LOGGER.log(Level.INFO, "adding project {0}", projectName);
1✔
95

96
        return ApiTaskManager.getInstance().submitApiTask(PROJECTS_PATH,
1✔
97
                new ApiTask(request.getRequestURI(),
1✔
98
                        () -> {
99
                            addProjectWorkHorse(projectName);
1✔
100
                            return null;
1✔
101
                        },
102
                        Response.Status.CREATED));
103
    }
104

105
    private void addProjectWorkHorse(String projectName) {
106
        File srcRoot = env.getSourceRootFile();
1✔
107
        File projDir = new File(srcRoot, projectName);
1✔
108

109
        if (!env.getProjects().containsKey(projectName)) {
1✔
110
            Project project = new Project(projectName, "/" + projectName);
1✔
111

112
            if (env.isHistoryEnabled()) {
1✔
113
                // Add repositories in this project.
114
                List<RepositoryInfo> repos = getRepositoriesInDir(projDir);
1✔
115

116
                env.addRepositories(repos);
1✔
117
                env.getProjectRepositoriesMap().put(project, repos);
1✔
118
            }
119

120
            // Finally, introduce the project to the configuration.
121
            // Note that the project is inactive in the UI until it is indexed.
122
            // See isIndexed()
123
            env.getProjects().put(projectName, project);
1✔
124
            env.populateGroups(new TreeSet<>(env.getGroups().values()), new TreeSet<>(env.getProjectList()));
1✔
125
        } else {
1✔
126
            Project project = env.getProjects().get(projectName);
1✔
127
            Map<Project, List<RepositoryInfo>> map = env.getProjectRepositoriesMap();
1✔
128

129
            if (env.isHistoryEnabled()) {
1✔
130
                // Refresh the list of repositories of this project.
131
                // This is the goal of this action: if an existing project
132
                // is re-added, this means its list of repositories has changed.
133
                List<RepositoryInfo> repos = getRepositoriesInDir(projDir);
1✔
134
                List<RepositoryInfo> allrepos = env.getRepositories();
1✔
135
                synchronized (allrepos) {
1✔
136
                    // newly added repository
137
                    repos.stream()
1✔
138
                            .filter(repo -> !allrepos.contains(repo))
1✔
139
                            .forEach(allrepos::add);
1✔
140
                    // deleted repository
141
                    Optional.ofNullable(map.get(project))
1✔
142
                            .stream().flatMap(Collection::stream)
1✔
143
                            .filter(repo -> !repos.contains(repo))
1✔
144
                            .forEach(allrepos::remove);
1✔
145
                }
1✔
146

147
                map.put(project, repos);
1✔
148
            }
149
        }
150
    }
1✔
151

152
    private List<RepositoryInfo> getRepositoriesInDir(final File projDir) {
153

154
        HistoryGuru histGuru = HistoryGuru.getInstance();
1✔
155

156
        // There is no need to perform the work of invalidateRepositories(),
157
        // since addRepositories() calls getRepository() for each of the repositories.
158
        return new ArrayList<>(histGuru.addRepositories(new File[]{projDir}));
1✔
159
    }
160

161
    private Project disableProject(String projectName) {
162
        Project project = env.getProjects().get(projectName);
1✔
163
        if (project == null) {
1✔
164
            throw new IllegalStateException("cannot get project \"" + projectName + "\"");
×
165
        }
166

167
        // Remove the project from searches so no one can trip over incomplete index data.
168
        project.setIndexed(false);
1✔
169

170
        return project;
1✔
171
    }
172

173
    @DELETE
174
    @Path("/{project}")
175
    public Response deleteProject(@Context HttpServletRequest request, @PathParam("project") String projectNameParam) {
176
        // Avoid classification as a taint bug.
177
        final String projectName = Laundromat.launderInput(projectNameParam);
1✔
178

179
        Project project = disableProject(projectName);
1✔
180
        LOGGER.log(Level.INFO, "deleting configuration for project {0}", projectName);
1✔
181

182
        return ApiTaskManager.getInstance().submitApiTask(PROJECTS_PATH,
1✔
183
                new ApiTask(request.getRequestURI(),
1✔
184
                        () -> {
185
                            deleteProjectWorkHorse(project);
1✔
186
                            return null;
1✔
187
                        },
188
                        Response.Status.NO_CONTENT));
189
    }
190

191
    private void deleteProjectWorkHorse(Project project) {
192
        // Delete index data associated with the project.
193
        deleteProjectDataWorkHorse(project, true);
1✔
194

195
        // Remove the project from its groups.
196
        for (Group group : project.getGroups()) {
1✔
197
            group.getRepositories().remove(project);
1✔
198
            group.getProjects().remove(project);
1✔
199
        }
1✔
200

201
        if (env.isHistoryEnabled()) {
1✔
202
            // Now remove the repositories associated with this project.
203
            List<RepositoryInfo> repos = env.getProjectRepositoriesMap().get(project);
1✔
204
            if (repos != null) {
1✔
205
                env.getRepositories().removeAll(repos);
1✔
206
            }
207
            env.getProjectRepositoriesMap().remove(project);
1✔
208
        }
209

210
        env.getProjects().remove(project.getName(), project);
1✔
211

212
        // Prevent the project to be included in new searches.
213
        env.refreshSearcherManagerMap();
1✔
214
    }
1✔
215

216
    @DELETE
217
    @Path("/{project}/data")
218
    public Response deleteProjectData(@Context HttpServletRequest request,
219
                                      @PathParam("project") String projectNameParam) {
220
        // Avoid classification as a taint bug.
221
        final String projectName = Laundromat.launderInput(projectNameParam);
×
222

223
        Project project = disableProject(projectName);
×
224

225
        return ApiTaskManager.getInstance().submitApiTask(PROJECTS_PATH,
×
226
                new ApiTask(request.getRequestURI(),
×
227
                        () -> {
228
                            deleteProjectDataWorkHorse(project, false);
×
229
                            return null;
×
230
                        },
231
                        Response.Status.NO_CONTENT));
232
    }
233

234
    private void deleteProjectDataWorkHorse(Project project, boolean clearHistoryGuru) {
235
        String projectName = project.getName();
1✔
236
        LOGGER.log(Level.INFO, "deleting data for project ''{0}''", projectName);
1✔
237

238
        // Delete index and xrefs.
239
        for (String dirName: new String[]{IndexDatabase.INDEX_DIR, IndexDatabase.XREF_DIR}) {
1✔
240
            java.nio.file.Path path = Paths.get(env.getDataRootPath(), dirName, projectName);
1✔
241
            try {
242
                IOUtils.removeRecursive(path);
1✔
243
            } catch (IOException e) {
×
244
                LOGGER.log(Level.WARNING, "Could not delete ''{0}''", path);
×
245
            }
1✔
246
        }
247

248
        List<RepositoryInfo> repos = env.getProjectRepositoriesMap().get(project);
1✔
249
        if (repos == null || repos.isEmpty()) {
1✔
250
            LOGGER.log(Level.INFO, NO_REPO_LOG_MSG, projectName);
×
251
            return;
×
252
        }
253

254
        deleteHistoryCacheWorkHorse(projectName, repos);
1✔
255
        deleteAnnotationCacheWorkHorse(projectName, repos);
1✔
256

257
        if (clearHistoryGuru) {
1✔
258
            HistoryGuru.getInstance().removeRepositories(repos.stream().
1✔
259
                    map(RepositoryInfo::getDirectoryName).collect(Collectors.toList()));
1✔
260
        }
261

262
        // Delete suggester data.
263
        suggester.delete(projectName);
1✔
264
    }
1✔
265

266
    @DELETE
267
    @Path("/{project}/annotationcache")
268
    @Produces(MediaType.APPLICATION_JSON)
269
    public Response deleteAnnotationCache(@Context HttpServletRequest request,
270
                                       @PathParam("project") String projectNameParam) {
271

272
        Project project = getProjectFromName(projectNameParam);
1✔
273
        List<RepositoryInfo> repos = env.getProjectRepositoriesMap().get(project);
1✔
274
        if (repos == null || repos.isEmpty()) {
1✔
275
            LOGGER.log(Level.INFO, NO_REPO_LOG_MSG, project.getName());
×
276
            return null;
×
277
        }
278

279
        return ApiTaskManager.getInstance().submitApiTask(PROJECTS_PATH,
1✔
280
                new ApiTask(request.getRequestURI(),
1✔
281
                        () -> deleteAnnotationCacheWorkHorse(project.getName(), repos)));
1✔
282
    }
283

284
    private Project getProjectFromName(String projectNameParam) {
285
        // Avoid classification as a taint bug.
286
        final String projectName = Laundromat.launderInput(projectNameParam);
1✔
287
        Project project = env.getProjects().get(projectName);
1✔
288
        if (project == null) {
1✔
289
            throw new IllegalStateException("cannot get project \"" + projectName + "\"");
×
290
        }
291

292
        return project;
1✔
293
    }
294

295
    @DELETE
296
    @Path("/{project}/historycache")
297
    @Produces(MediaType.APPLICATION_JSON)
298
    public Response deleteHistoryCache(@Context HttpServletRequest request,
299
                                       @PathParam("project") String projectNameParam) {
300

301
        if (!env.isHistoryEnabled()) {
1✔
302
            return Response.status(Response.Status.NO_CONTENT).build();
×
303
        }
304

305
        Project project = getProjectFromName(projectNameParam);
1✔
306
        List<RepositoryInfo> repos = env.getProjectRepositoriesMap().get(project);
1✔
307
        if (repos == null || repos.isEmpty()) {
1✔
308
            LOGGER.log(Level.INFO, NO_REPO_LOG_MSG, project.getName());
×
309
            return null;
×
310
        }
311

312
        return ApiTaskManager.getInstance().submitApiTask(PROJECTS_PATH,
1✔
313
                new ApiTask(request.getRequestURI(),
1✔
314
                        () -> deleteHistoryCacheWorkHorse(project.getName(), repos)));
1✔
315
    }
316

317
    private List<String> deleteHistoryCacheWorkHorse(String projectName, List<RepositoryInfo> repos) {
318
        LOGGER.log(Level.INFO, "deleting history cache for project ''{0}''", projectName);
1✔
319

320
        // Delete history cache data.
321
        return HistoryGuru.getInstance().removeHistoryCache(repos);
1✔
322
    }
323

324
    private List<String> deleteAnnotationCacheWorkHorse(String projectName, List<RepositoryInfo> repos) {
325
        LOGGER.log(Level.INFO, "deleting annotation cache for project ''{0}''", projectName);
1✔
326

327
        // Delete annotation cache data.
328
        return HistoryGuru.getInstance().removeAnnotationCache(repos);
1✔
329
    }
330

331
    @PUT
332
    @Path("/{project}/indexed")
333
    @Consumes(MediaType.TEXT_PLAIN)
334
    public Response markIndexed(@Context HttpServletRequest request, @PathParam("project") String projectNameParam) {
335

336
        // Avoid classification as a taint bug.
337
        final String projectName = Laundromat.launderInput(projectNameParam);
1✔
338

339
        Project project = env.getProjects().get(projectName);
1✔
340
        if (project == null) {
1✔
341
            LOGGER.log(Level.WARNING, "cannot find project ''{0}'' to mark as indexed", projectName);
×
342
            throw new NotFoundException(String.format("project '%s' does not exist", projectName));
×
343
        }
344

345
        project.setIndexed(true);
1✔
346

347
        return ApiTaskManager.getInstance().submitApiTask(PROJECTS_PATH,
1✔
348
                new ApiTask(request.getRequestURI(),
1✔
349
                        () -> {
350
                            // Refresh current version of the project's repositories.
351
                            List<RepositoryInfo> riList = env.getProjectRepositoriesMap().get(project);
1✔
352
                            if (riList != null) {
1✔
353
                                for (RepositoryInfo ri : riList) {
1✔
354
                                    Repository repo = getRepository(ri, CommandTimeoutType.RESTFUL);
1✔
355

356
                                    if (repo != null && repo.getCurrentVersion() != null &&
1✔
357
                                            repo.getCurrentVersion().length() > 0) {
1✔
358
                                        // getRepository() always creates fresh instance
359
                                        // of the Repository object so there is no need
360
                                        // to call setCurrentVersion() on it.
361
                                        ri.setCurrentVersion(repo.determineCurrentVersion());
1✔
362
                                    }
363
                                }
1✔
364
                            }
365

366
                            CompletableFuture.runAsync(() -> suggester.rebuild(projectName));
1✔
367

368
                            // In case this project has just been incrementally indexed,
369
                            // its IndexSearcher needs a poke.
370
                            env.maybeRefreshIndexSearchers(Collections.singleton(projectName));
1✔
371

372
                            env.refreshDateForLastIndexRun();
1✔
373
                            return null;
1✔
374
                        }));
375
    }
376

377
    @PUT
378
    @Path("/{project}/property/{field}")
379
    public void set(
380
            @PathParam("project") String projectName,
381
            @PathParam("field") String field,
382
            final String value
383
    ) throws IOException {
384
        // Avoid classification as a taint bug.
385
        projectName = Laundromat.launderInput(projectName);
1✔
386
        field = Laundromat.launderInput(field);
1✔
387

388
        Project project = env.getProjects().get(projectName);
1✔
389
        if (project != null) {
1✔
390
            // Set the property.
391
            ClassUtil.setFieldValue(project, field, value);
1✔
392

393
            // Refresh field values for project's repositories for this project as well.
394
            List<RepositoryInfo> riList = env.getProjectRepositoriesMap().get(project);
1✔
395
            if (riList != null) {
1✔
396
                for (RepositoryInfo ri : riList) {
1✔
397
                    // Set the property if there is one.
398
                    if (ClassUtil.hasField(ri, field)) {
1✔
399
                        ClassUtil.setFieldValue(ri, field, value);
1✔
400
                    }
401
                }
1✔
402
            }
403
        } else {
1✔
404
            LOGGER.log(Level.WARNING, "cannot find project {0} to set a property", projectName);
×
405
        }
406
    }
1✔
407

408
    @GET
409
    @Path("/{project}/property/{field}")
410
    @Produces(MediaType.APPLICATION_JSON)
411
    public Object get(@PathParam("project") String projectName, @PathParam("field") String field)
412
            throws IOException {
413
        // Avoid classification as a taint bug.
414
        projectName = Laundromat.launderInput(projectName);
1✔
415
        field = Laundromat.launderInput(field);
1✔
416

417
        Project project = env.getProjects().get(projectName);
1✔
418
        if (project == null) {
1✔
419
            throw new WebApplicationException(
×
420
                    "cannot find project " + projectName + " to get a property", Response.Status.BAD_REQUEST);
421
        }
422
        return ClassUtil.getFieldValue(project, field);
1✔
423
    }
424

425
    @GET
426
    @Produces(MediaType.APPLICATION_JSON)
427
    public List<String> listProjects() {
428
        return env.getProjectNames();
1✔
429
    }
430

431
    @GET
432
    @Path("indexed")
433
    @Produces(MediaType.APPLICATION_JSON)
434
    public List<String> listIndexed() {
435
        return env.getProjectList().stream()
1✔
436
                .filter(Project::isIndexed)
1✔
437
                .map(Project::getName)
1✔
438
                .collect(Collectors.toList());
1✔
439
    }
440

441
    @GET
442
    @Path("/{project}/repositories")
443
    @Produces(MediaType.APPLICATION_JSON)
444
    public List<String> getRepositories(@PathParam("project") String projectName) {
445
        // Avoid classification as a taint bug.
446
        projectName = Laundromat.launderInput(projectName);
1✔
447

448
        Project project = env.getProjects().get(projectName);
1✔
449
        if (project != null) {
1✔
450
            List<RepositoryInfo> infos = env.getProjectRepositoriesMap().get(project);
1✔
451
            if (infos != null) {
1✔
452
                return infos.stream()
1✔
453
                        .map(RepositoryInfo::getDirectoryNameRelative)
1✔
454
                        .collect(Collectors.toList());
1✔
455
            }
456
        }
457

458
        return Collections.emptyList();
1✔
459
    }
460

461
    @GET
462
    @Path("/{project}/repositories/type")
463
    @Produces(MediaType.APPLICATION_JSON)
464
    public Set<String> getRepositoriesType(@PathParam("project") String projectName) {
465
        // Avoid classification as a taint bug.
466
        projectName = Laundromat.launderInput(projectName);
1✔
467

468
        Project project = env.getProjects().get(projectName);
1✔
469
        if (project != null) {
1✔
470
            List<RepositoryInfo> infos = env.getProjectRepositoriesMap().get(project);
1✔
471
            if (infos != null) {
1✔
472
                return infos.stream()
1✔
473
                        .map(RepositoryInfo::getType)
1✔
474
                        .collect(Collectors.toSet());
1✔
475
            }
476
        }
477
        return Collections.emptySet();
×
478
    }
479

480
    @GET
481
    @Path("/{project}/files")
482
    @Produces(MediaType.APPLICATION_JSON)
483
    public Set<String> getProjectIndexFiles(@PathParam("project") String projectName) throws IOException {
484
        // Avoid classification as a taint bug.
485
        projectName = Laundromat.launderInput(projectName);
1✔
486

487
        return IndexDatabase.getAllFiles(Collections.singletonList("/" + projectName));
1✔
488
    }
489
}
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

© 2026 Coveralls, Inc