Skip to content

Commit 176ab80

Browse files
committed
TempFileService with session-scoped tracking
Auto-clean temp files/dirs on afterSessionEnd; opt-out via -Dmaven.tempfile.keep Add tests and minimal SessionData map stub
1 parent c40a2c9 commit 176ab80

File tree

4 files changed

+450
-0
lines changed

4 files changed

+450
-0
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.api.services;
20+
21+
import java.io.IOException;
22+
import java.nio.file.Path;
23+
24+
import org.apache.maven.api.Session;
25+
26+
/**
27+
* Service to create and track temporary files/directories for a Maven build.
28+
* All created paths are deleted automatically when the session ends.
29+
*/
30+
public interface TempFileService {
31+
32+
/**
33+
* Creates a temp file in the default temp directory.
34+
*/
35+
Path createTempFile(Session session, String prefix, String suffix) throws IOException;
36+
37+
/**
38+
* Creates a temp file in the given directory.
39+
*/
40+
Path createTempFile(Session session, String prefix, String suffix, Path directory) throws IOException;
41+
42+
/**
43+
* Creates a temp directory in the default temp directory.
44+
*/
45+
Path createTempDirectory(Session session, String prefix) throws IOException;
46+
47+
/**
48+
* Creates a temp directory in the given directory.
49+
*/
50+
Path createTempDirectory(Session session, String prefix, Path directory) throws IOException;
51+
52+
/**
53+
* Registers an externally created path for cleanup at session end.
54+
*/
55+
void register(Session session, Path path);
56+
57+
/**
58+
* Forces cleanup for the given session (normally called by lifecycle).
59+
*/
60+
void cleanup(Session session) throws IOException;
61+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.internal.impl;
20+
21+
import javax.inject.Inject;
22+
import javax.inject.Named;
23+
import javax.inject.Singleton;
24+
25+
import java.io.IOException;
26+
import java.nio.file.FileVisitOption;
27+
import java.nio.file.FileVisitResult;
28+
import java.nio.file.Files;
29+
import java.nio.file.Path;
30+
import java.nio.file.SimpleFileVisitor;
31+
import java.nio.file.attribute.BasicFileAttributes;
32+
import java.util.Collections;
33+
import java.util.EnumSet;
34+
import java.util.Objects;
35+
import java.util.Set;
36+
import java.util.concurrent.ConcurrentHashMap;
37+
import java.util.function.Supplier;
38+
39+
import org.apache.maven.api.Session;
40+
import org.apache.maven.api.SessionData;
41+
import org.apache.maven.api.services.TempFileService;
42+
import org.slf4j.Logger;
43+
import org.slf4j.LoggerFactory;
44+
45+
/**
46+
* Default TempFileService implementation.
47+
* Stores tracked paths in Session-scoped data and removes them after the build.
48+
*/
49+
@Named
50+
@Singleton
51+
public final class DefaultTempFileService implements TempFileService {
52+
53+
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTempFileService.class);
54+
55+
// System property to keep temp material for diagnostics.
56+
private static final String KEEP_PROP = "maven.tempfile.keep";
57+
58+
// unique, typed session key (uses factory; one narrow unchecked cast)
59+
@SuppressWarnings({"unchecked", "rawtypes"})
60+
private static final SessionData.Key<Set<Path>> TMP_KEY =
61+
(SessionData.Key) SessionData.key(Set.class, DefaultTempFileService.class);
62+
63+
// supplier with concrete types (avoids inference noise)
64+
private static final Supplier<Set<Path>> TMP_SUPPLIER =
65+
() -> Collections.newSetFromMap(new ConcurrentHashMap<Path, Boolean>());
66+
67+
@Inject
68+
public DefaultTempFileService() {
69+
// default
70+
}
71+
72+
@Override
73+
public Path createTempFile(final Session session, final String prefix, final String suffix) throws IOException {
74+
Objects.requireNonNull(session, "session");
75+
final Path file = Files.createTempFile(prefix, suffix);
76+
register(session, file);
77+
return file;
78+
}
79+
80+
@Override
81+
public Path createTempFile(final Session session, final String prefix, final String suffix, final Path directory)
82+
throws IOException {
83+
Objects.requireNonNull(session, "session");
84+
Objects.requireNonNull(directory, "directory");
85+
final Path file = Files.createTempFile(directory, prefix, suffix);
86+
register(session, file);
87+
return file;
88+
}
89+
90+
@Override
91+
public Path createTempDirectory(final Session session, final String prefix) throws IOException {
92+
Objects.requireNonNull(session, "session");
93+
final Path dir = Files.createTempDirectory(prefix);
94+
register(session, dir);
95+
return dir;
96+
}
97+
98+
@Override
99+
public Path createTempDirectory(final Session session, final String prefix, final Path directory)
100+
throws IOException {
101+
Objects.requireNonNull(session, "session");
102+
Objects.requireNonNull(directory, "directory");
103+
final Path dir = Files.createTempDirectory(directory, prefix);
104+
register(session, dir);
105+
return dir;
106+
}
107+
108+
@Override
109+
public void register(final Session session, final Path path) {
110+
Objects.requireNonNull(session, "session");
111+
Objects.requireNonNull(path, "path");
112+
final Set<Path> bucket = sessionPaths(session);
113+
bucket.add(path);
114+
if (LOGGER.isDebugEnabled()) {
115+
LOGGER.debug("Temp path registered for cleanup: {}", path);
116+
}
117+
}
118+
119+
@Override
120+
public void cleanup(final Session session) throws IOException {
121+
Objects.requireNonNull(session, "session");
122+
123+
if (Boolean.getBoolean(KEEP_PROP)) {
124+
if (LOGGER.isInfoEnabled()) {
125+
LOGGER.info("Skipping temp cleanup due to -D{}=true", KEEP_PROP);
126+
}
127+
return;
128+
}
129+
130+
final Set<Path> bucket = sessionPaths(session);
131+
IOException first = null;
132+
133+
for (final Path path : bucket) {
134+
try {
135+
deleteTree(path);
136+
} catch (final IOException e) {
137+
if (first == null) {
138+
first = e;
139+
}
140+
LOGGER.warn("Failed to delete temp path {}: {}", path, e.getMessage());
141+
}
142+
}
143+
bucket.clear();
144+
145+
if (first != null) {
146+
throw first;
147+
}
148+
}
149+
150+
// ---- internals ---------------------------------------------------------
151+
152+
private Set<Path> sessionPaths(final Session session) {
153+
return session.getData().computeIfAbsent(TMP_KEY, TMP_SUPPLIER);
154+
}
155+
156+
private static void deleteTree(final Path path) throws IOException {
157+
if (path == null || Files.notExists(path)) {
158+
return;
159+
}
160+
// Walk depth-first and delete files, then directories.
161+
Files.walkFileTree(
162+
path, EnumSet.noneOf(FileVisitOption.class), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
163+
@Override
164+
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs)
165+
throws IOException {
166+
Files.deleteIfExists(file);
167+
return FileVisitResult.CONTINUE;
168+
}
169+
170+
@Override
171+
public FileVisitResult postVisitDirectory(final Path dir, final IOException exc)
172+
throws IOException {
173+
Files.deleteIfExists(dir);
174+
return FileVisitResult.CONTINUE;
175+
}
176+
});
177+
}
178+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.maven.internal.impl;
20+
21+
import javax.inject.Inject;
22+
import javax.inject.Named;
23+
import javax.inject.Singleton;
24+
25+
import org.apache.maven.AbstractMavenLifecycleParticipant;
26+
import org.apache.maven.api.Session;
27+
import org.apache.maven.api.services.TempFileService;
28+
import org.apache.maven.execution.MavenSession;
29+
30+
/**
31+
* Hooks into the Maven lifecycle and removes all temp material after the session.
32+
*/
33+
@Named
34+
@Singleton
35+
public final class TempFileCleanupParticipant extends AbstractMavenLifecycleParticipant {
36+
37+
private final TempFileService tempFileService;
38+
39+
@Inject
40+
public TempFileCleanupParticipant(final TempFileService tempFileService) {
41+
this.tempFileService = tempFileService;
42+
}
43+
44+
@Override
45+
public void afterSessionEnd(final MavenSession mavenSession) {
46+
// Bridge to the API Session (available in Maven 4).
47+
final Session apiSession = mavenSession.getSession();
48+
try {
49+
tempFileService.cleanup(apiSession);
50+
} catch (final Exception e) {
51+
// We’re at session end; just log. Maven already reported build result.
52+
// Use slf4j directly to avoid throwing from the lifecycle callback.
53+
org.slf4j.LoggerFactory.getLogger(TempFileCleanupParticipant.class)
54+
.warn("Temp cleanup failed: {}", e.getMessage());
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)