Skip to content

Commit e037a74

Browse files
authored
Add action after batch delete to clean up empty parent folders (#66)
1 parent 5102f9c commit e037a74

File tree

5 files changed

+180
-2
lines changed

5 files changed

+180
-2
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/**
2+
* Copyright (C) 2022-2023 Red Hat, Inc. (https://github.com/Commonjava/indy-tracking-service)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.commonjava.indy.service.tracking.client.storage;
17+
18+
import java.util.Set;
19+
20+
/**
21+
* Delete multiple paths in one filesystem.
22+
*/
23+
public class StorageBatchDeleteRequest {
24+
private Set<String> paths;
25+
26+
private String filesystem;
27+
28+
public Set<String> getPaths() {
29+
return paths;
30+
}
31+
32+
public void setPaths(Set<String> paths) {
33+
this.paths = paths;
34+
}
35+
36+
public String getFilesystem() {
37+
return filesystem;
38+
}
39+
40+
public void setFilesystem(String filesystem) {
41+
this.filesystem = filesystem;
42+
}
43+
44+
@Override
45+
public String toString() {
46+
return "BatchDeleteRequest{" +
47+
"paths=" + paths +
48+
", filesystem='" + filesystem + '\'' +
49+
'}';
50+
}
51+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* Copyright (C) 2022-2023 Red Hat, Inc. (https://github.com/Commonjava/indy-tracking-service)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.commonjava.indy.service.tracking.client.storage;
17+
18+
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
19+
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
20+
import org.commonjava.indy.service.security.jaxrs.CustomClientRequestFilter;
21+
22+
import jakarta.ws.rs.DELETE;
23+
import jakarta.ws.rs.Path;
24+
import jakarta.ws.rs.core.Response;
25+
import jakarta.ws.rs.Consumes;
26+
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
27+
28+
@Path("/api/storage")
29+
@RegisterRestClient(configKey = "storage-service-api")
30+
@RegisterProvider(CustomClientRequestFilter.class)
31+
public interface StorageService {
32+
/**
33+
* Delete empty folders by Storage BatchDeleteRequest as JSON body.
34+
*/
35+
@DELETE
36+
@Path("/maint/folders/empty")
37+
@Consumes(APPLICATION_JSON)
38+
Response cleanupEmptyFolders(StorageBatchDeleteRequest request);
39+
}

src/main/java/org/commonjava/indy/service/tracking/controller/AdminController.java

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858

5959
import static org.commonjava.indy.service.tracking.util.TrackingUtils.readZipInputStreamAnd;
6060
import static org.commonjava.indy.service.tracking.util.TrackingUtils.zipTrackedContent;
61+
import org.commonjava.indy.service.tracking.client.storage.StorageBatchDeleteRequest;
62+
import org.commonjava.indy.service.tracking.client.storage.StorageService;
6163

6264
@ApplicationScoped
6365
public class AdminController
@@ -76,6 +78,10 @@ public class AdminController
7678
@RestClient
7779
PromoteService promoteService;
7880

81+
@Inject
82+
@RestClient
83+
StorageService storageService;
84+
7985
@Inject
8086
private IndyTrackingConfiguration config;
8187

@@ -410,6 +416,42 @@ public boolean deletionAdditionalGuardCheck( BatchDeleteRequest deleteRequest )
410416
return isOk.get();
411417
}
412418

419+
/**
420+
* Post-action after successful batch delete: cleans up empty parent folders.
421+
* <p>
422+
* For each deleted path, collects its immediate parent folder (one level up).
423+
* Then calls the storage service to clean up these folders, relying on the
424+
* storage API to handle ancestor folders as needed.
425+
* </p>
426+
*
427+
* @param filesystem the target filesystem/storeKey as a string
428+
* @param paths the set of deleted file paths
429+
*/
430+
public void cleanupEmptyFolders(String filesystem, Set<String> paths) {
431+
logger.info("Post-action: cleanupEmptyFolder, filesystem={}, paths={}", filesystem, paths);
432+
if (paths == null || paths.isEmpty()) {
433+
logger.info("No paths to process for cleanup.");
434+
return;
435+
}
436+
Set<String> folders = new HashSet<>();
437+
for (String path : paths) {
438+
int idx = path.lastIndexOf('/');
439+
if (idx > 0) {
440+
String folder = path.substring(0, idx);
441+
folders.add(folder);
442+
}
443+
}
444+
StorageBatchDeleteRequest req = new StorageBatchDeleteRequest();
445+
req.setFilesystem(filesystem);
446+
req.setPaths(folders);
447+
try {
448+
Response resp = storageService.cleanupEmptyFolders(req);
449+
logger.info("Cleanup empty folders, req: {}, status {}", req, resp.getStatus());
450+
} catch (Exception e) {
451+
logger.warn("Failed to cleanup folders, request: {}, error: {}", req, e.getMessage(), e);
452+
}
453+
}
454+
413455
private boolean isSuccess(Response resp) {
414456
return Response.Status.fromStatusCode(resp.getStatus()).getFamily()
415457
== Response.Status.Family.SUCCESSFUL;

src/main/java/org/commonjava/indy/service/tracking/jaxrs/AdminResource.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
import java.util.HashSet;
6464
import java.util.List;
6565
import java.util.Set;
66+
import java.util.concurrent.CompletableFuture;
67+
import java.util.concurrent.Executor;
6668

6769
import static java.util.Collections.emptySet;
6870
import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON;
@@ -95,6 +97,10 @@ public class AdminResource
9597
@Inject
9698
private IndyTrackingConfiguration config;
9799

100+
// Inject a managed Executor for running async post-actions without blocking the main thread
101+
@Inject
102+
Executor executor;
103+
98104
public AdminResource()
99105
{
100106
}
@@ -424,7 +430,12 @@ public Response doDelete( @Context final UriInfo uriInfo, final BatchDeleteReque
424430
}
425431
}
426432

427-
return maintenanceService.doDelete( request );
433+
Response response = maintenanceService.doDelete( request );
434+
if (response.getStatus() == Response.Status.OK.getStatusCode()) {
435+
// Run the cleanupEmptyFolder post-action asynchronously
436+
CompletableFuture.runAsync(() -> controller.cleanupEmptyFolders(
437+
request.getStoreKey().toString(), request.getPaths()), executor);
438+
}
439+
return response;
428440
}
429-
430441
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Copyright (C) 2022-2023 Red Hat, Inc. (https://github.com/Commonjava/indy-tracking-service)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.commonjava.indy.service.tracking.handler;
17+
18+
import io.quarkus.test.Mock;
19+
import jakarta.ws.rs.core.Response;
20+
import org.apache.http.HttpStatus;
21+
import org.commonjava.indy.service.tracking.client.storage.StorageBatchDeleteRequest;
22+
import org.commonjava.indy.service.tracking.client.storage.StorageService;
23+
import org.eclipse.microprofile.rest.client.inject.RestClient;
24+
25+
@Mock
26+
@RestClient
27+
public class MockableStorageService
28+
implements StorageService
29+
{
30+
@Override
31+
public Response cleanupEmptyFolders( StorageBatchDeleteRequest request )
32+
{
33+
return Response.status( HttpStatus.SC_OK ).build();
34+
}
35+
}

0 commit comments

Comments
 (0)