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

oracle / opengrok / #3662

26 Oct 2023 08:39AM UTC coverage: 66.013% (-9.1%) from 75.137%
#3662

push

vladak
propagate the error rather than log it

fixes #4411

1 of 1 new or added line in 1 file covered. (100.0%)

38693 of 58614 relevant lines covered (66.01%)

0.66 hits per line

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

0.0
/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.Collections;
33
import java.util.List;
34
import java.util.Map;
35
import java.util.Set;
36
import java.util.TreeSet;
37
import java.util.concurrent.CompletableFuture;
38
import java.util.logging.Level;
39
import java.util.logging.Logger;
40
import java.util.stream.Collectors;
41

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

73
@Path(ProjectsController.PROJECTS_PATH)
74
public class ProjectsController {
×
75

76
    private static final Logger LOGGER = LoggerFactory.getLogger(ProjectsController.class);
×
77

78
    public static final String PROJECTS_PATH = "/projects";
79

80
    private final RuntimeEnvironment env = RuntimeEnvironment.getInstance();
×
81

82
    @Inject
83
    private SuggesterService suggester;
84

85
    @POST
86
    @Consumes(MediaType.TEXT_PLAIN)
87
    public Response addProject(@Context HttpServletRequest request, String projectNameParam) {
88
        // Avoid classification as a taint bug.
89
        final String projectName = Laundromat.launderInput(projectNameParam);
×
90

91
        LOGGER.log(Level.INFO, "adding project {0}", projectName);
×
92

93
        return ApiTaskManager.getInstance().submitApiTask(PROJECTS_PATH,
×
94
                new ApiTask(request.getRequestURI(),
×
95
                        () -> {
96
                            addProjectWorkHorse(projectName);
×
97
                            return null;
×
98
                        },
99
                        Response.Status.CREATED));
100
    }
101

102
    private void addProjectWorkHorse(String projectName) {
103
        File srcRoot = env.getSourceRootFile();
×
104
        File projDir = new File(srcRoot, projectName);
×
105

106
        if (!env.getProjects().containsKey(projectName)) {
×
107
            Project project = new Project(projectName, "/" + projectName);
×
108

109
            if (env.isHistoryEnabled()) {
×
110
                // Add repositories in this project.
111
                List<RepositoryInfo> repos = getRepositoriesInDir(projDir);
×
112

113
                env.addRepositories(repos);
×
114
                env.getProjectRepositoriesMap().put(project, repos);
×
115
            }
116

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

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

149
                map.put(project, repos);
×
150
            }
151
        }
152
    }
×
153

154
    private List<RepositoryInfo> getRepositoriesInDir(final File projDir) {
155

156
        HistoryGuru histGuru = HistoryGuru.getInstance();
×
157

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

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

169
        // Remove the project from searches so no one can trip over incomplete index data.
170
        project.setIndexed(false);
×
171

172
        return project;
×
173
    }
174

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

181
        Project project = disableProject(projectName);
×
182
        LOGGER.log(Level.INFO, "deleting configuration for project {0}", projectName);
×
183

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

193
    private void deleteProjectWorkHorse(Project project) {
194
        // Delete index data associated with the project.
195
        deleteProjectDataWorkHorse(project, true);
×
196

197
        // Remove the project from its groups.
198
        for (Group group : project.getGroups()) {
×
199
            group.getRepositories().remove(project);
×
200
            group.getProjects().remove(project);
×
201
        }
×
202

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

212
        env.getProjects().remove(project.getName(), project);
×
213

214
        // Prevent the project to be included in new searches.
215
        env.refreshSearcherManagerMap();
×
216
    }
×
217

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

225
        Project project = disableProject(projectName);
×
226

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

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

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

250
        List<RepositoryInfo> repos = env.getProjectRepositoriesMap().get(project);
×
251
        if (repos == null || repos.isEmpty()) {
×
252
            LOGGER.log(Level.INFO, "no repositories found for project ''{0}''", projectName);
×
253
            return;
×
254
        }
255

256
        deleteHistoryCacheWorkHorse(projectName, repos);
×
257
        deleteAnnotationCacheWorkHorse(projectName, repos);
×
258

259
        if (clearHistoryGuru) {
×
260
            HistoryGuru.getInstance().removeRepositories(repos.stream().
×
261
                    map(RepositoryInfo::getDirectoryName).collect(Collectors.toList()));
×
262
        }
263

264
        // Delete suggester data.
265
        suggester.delete(projectName);
×
266
    }
×
267

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

274
        Project project = getProjectFromName(projectNameParam);
×
275
        List<RepositoryInfo> repos = env.getProjectRepositoriesMap().get(project);
×
276
        if (repos == null || repos.isEmpty()) {
×
277
            LOGGER.log(Level.INFO, "no repositories found for project ''{0}''", project.getName());
×
278
            return null;
×
279
        }
280

281
        return ApiTaskManager.getInstance().submitApiTask(PROJECTS_PATH,
×
282
                new ApiTask(request.getRequestURI(),
×
283
                        () -> deleteAnnotationCacheWorkHorse(project.getName(), repos)));
×
284
    }
285

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

294
        return project;
×
295
    }
296

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

303
        if (!env.isHistoryEnabled()) {
×
304
            return Response.status(Response.Status.NO_CONTENT).build();
×
305
        }
306

307
        Project project = getProjectFromName(projectNameParam);
×
308
        List<RepositoryInfo> repos = env.getProjectRepositoriesMap().get(project);
×
309
        if (repos == null || repos.isEmpty()) {
×
310
            LOGGER.log(Level.INFO, "no repositories found for project ''{0}''", project.getName());
×
311
            return null;
×
312
        }
313

314
        return ApiTaskManager.getInstance().submitApiTask(PROJECTS_PATH,
×
315
                new ApiTask(request.getRequestURI(),
×
316
                        () -> deleteHistoryCacheWorkHorse(project.getName(), repos)));
×
317
    }
318

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

322
        // Delete history cache data.
323
        return HistoryGuru.getInstance().removeHistoryCache(repos);
×
324
    }
325

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

329
        // Delete annotation cache data.
330
        return HistoryGuru.getInstance().removeAnnotationCache(repos);
×
331
    }
332

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

338
        // Avoid classification as a taint bug.
339
        final String projectName = Laundromat.launderInput(projectNameParam);
×
340

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

347
        project.setIndexed(true);
×
348

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

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

368
                            CompletableFuture.runAsync(() -> suggester.rebuild(projectName));
×
369

370
                            // In case this project has just been incrementally indexed,
371
                            // its IndexSearcher needs a poke.
372
                            env.maybeRefreshIndexSearchers(Collections.singleton(projectName));
×
373

374
                            env.refreshDateForLastIndexRun();
×
375
                            return null;
×
376
                        }));
377
    }
378

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

390
        Project project = env.getProjects().get(projectName);
×
391
        if (project != null) {
×
392
            // Set the property.
393
            ClassUtil.setFieldValue(project, field, value);
×
394

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

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

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

427
    @GET
428
    @Produces(MediaType.APPLICATION_JSON)
429
    public List<String> listProjects() {
430
        return env.getProjectNames();
×
431
    }
432

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

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

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

460
        return Collections.emptyList();
×
461
    }
462

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

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

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

489
        return IndexDatabase.getAllFiles(Collections.singletonList("/" + projectName));
×
490
    }
491
}
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