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

oracle / opengrok / #3727

30 Nov 2023 04:39PM UTC coverage: 66.144% (-9.8%) from 75.91%
#3727

push

vladak
update Tomcat to 10.1.16

fixes #4492

38756 of 58593 relevant lines covered (66.14%)

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.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 {
77

78
    private static final Logger LOGGER = LoggerFactory.getLogger(ProjectsController.class);
×
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();
×
84

85
    private final SuggesterService suggester;
86

87
    @Inject
88
    public ProjectsController(SuggesterService suggester) {
×
89
        this.suggester = suggester;
×
90
    }
×
91

92
    @POST
93
    @Consumes(MediaType.TEXT_PLAIN)
94
    public Response addProject(@Context HttpServletRequest request, String projectNameParam) {
95
        // Avoid classification as a taint bug.
96
        final String projectName = Laundromat.launderInput(projectNameParam);
×
97

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

100
        return ApiTaskManager.getInstance().submitApiTask(PROJECTS_PATH,
×
101
                new ApiTask(request.getRequestURI(),
×
102
                        () -> {
103
                            addProjectWorkHorse(projectName);
×
104
                            return null;
×
105
                        },
106
                        Response.Status.CREATED));
107
    }
108

109
    private void addProjectWorkHorse(String projectName) {
110
        File srcRoot = env.getSourceRootFile();
×
111
        File projDir = new File(srcRoot, projectName);
×
112

113
        if (!env.getProjects().containsKey(projectName)) {
×
114
            Project project = new Project(projectName, "/" + projectName);
×
115

116
            if (env.isHistoryEnabled()) {
×
117
                // Add repositories in this project.
118
                List<RepositoryInfo> repos = getRepositoriesInDir(projDir);
×
119

120
                env.addRepositories(repos);
×
121
                env.getProjectRepositoriesMap().put(project, repos);
×
122
            }
123

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

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

151
                map.put(project, repos);
×
152
            }
153
        }
154
    }
×
155

156
    private List<RepositoryInfo> getRepositoriesInDir(final File projDir) {
157

158
        HistoryGuru histGuru = HistoryGuru.getInstance();
×
159

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

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

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

174
        return project;
×
175
    }
176

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

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

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

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

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

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

214
        env.getProjects().remove(project.getName(), project);
×
215

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

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

227
        Project project = disableProject(projectName);
×
228

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

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

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

252
        List<RepositoryInfo> repos = env.getProjectRepositoriesMap().get(project);
×
253
        if (repos == null || repos.isEmpty()) {
×
254
            LOGGER.log(Level.INFO, NO_REPO_LOG_MSG, projectName);
×
255
            return;
×
256
        }
257

258
        deleteHistoryCacheWorkHorse(projectName, repos);
×
259
        deleteAnnotationCacheWorkHorse(projectName, repos);
×
260

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

266
        // Delete suggester data.
267
        suggester.delete(projectName);
×
268
    }
×
269

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

276
        Project project = getProjectFromName(projectNameParam);
×
277
        List<RepositoryInfo> repos = env.getProjectRepositoriesMap().get(project);
×
278
        if (repos == null || repos.isEmpty()) {
×
279
            LOGGER.log(Level.INFO, NO_REPO_LOG_MSG, project.getName());
×
280
            return null;
×
281
        }
282

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

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

296
        return project;
×
297
    }
298

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

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

309
        Project project = getProjectFromName(projectNameParam);
×
310
        List<RepositoryInfo> repos = env.getProjectRepositoriesMap().get(project);
×
311
        if (repos == null || repos.isEmpty()) {
×
312
            LOGGER.log(Level.INFO, NO_REPO_LOG_MSG, project.getName());
×
313
            return null;
×
314
        }
315

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

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

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

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

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

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

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

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

349
        project.setIndexed(true);
×
350

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

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

370
                            CompletableFuture.runAsync(() -> suggester.rebuild(projectName));
×
371

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

376
                            env.refreshDateForLastIndexRun();
×
377
                            return null;
×
378
                        }));
379
    }
380

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

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

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

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

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

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

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

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

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

462
        return Collections.emptyList();
×
463
    }
464

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

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

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

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