Skip to content

Commit d5a6db3

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 d5a6db3

File tree

5 files changed

+462
-0
lines changed

5 files changed

+462
-0
lines changed

api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,5 +660,11 @@ public final class Constants {
660660
*/
661661
public static final String MAVEN_LOGGER_LOG_PREFIX = MAVEN_LOGGER_PREFIX + "log.";
662662

663+
/**
664+
* System property to keep temp material for diagnostics.
665+
*
666+
*/
667+
public static final String KEEP_PROP = "maven.tempfile.keep";
668+
663669
private Constants() {}
664670
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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.Service;
25+
import org.apache.maven.api.Session;
26+
import org.apache.maven.api.annotations.Nonnull;
27+
28+
/**
29+
* Service to create and track temporary files/directories for a Maven build.
30+
* All created paths are deleted automatically when the session ends.
31+
*/
32+
public interface TempFileService extends Service {
33+
34+
/**
35+
* Creates a temp file in the default temp directory.
36+
*/
37+
@Nonnull
38+
Path createTempFile(Session session, String prefix, String suffix) throws IOException;
39+
40+
/**
41+
* Creates a temp file in the given directory.
42+
*/
43+
@Nonnull
44+
Path createTempFile(Session session, String prefix, String suffix, Path directory) throws IOException;
45+
46+
/**
47+
* Creates a temp directory in the default temp directory.
48+
*/
49+
@Nonnull
50+
Path createTempDirectory(Session session, String prefix) throws IOException;
51+
52+
/**
53+
* Creates a temp directory in the given directory.
54+
*/
55+
@Nonnull
56+
Path createTempDirectory(Session session, String prefix, Path directory) throws IOException;
57+
58+
/**
59+
* Registers an externally created path for cleanup at session end.
60+
*/
61+
@Nonnull
62+
void register(Session session, Path path);
63+
64+
/**
65+
* Forces cleanup for the given session (normally called by lifecycle).
66+
*/
67+
@Nonnull
68+
void cleanup(Session session) throws IOException;
69+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
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 static org.apache.maven.api.Constants.KEEP_PROP;
22+
23+
import javax.inject.Inject;
24+
import javax.inject.Named;
25+
import javax.inject.Singleton;
26+
27+
import java.io.IOException;
28+
import java.nio.file.FileVisitOption;
29+
import java.nio.file.FileVisitResult;
30+
import java.nio.file.Files;
31+
import java.nio.file.Path;
32+
import java.nio.file.SimpleFileVisitor;
33+
import java.nio.file.attribute.BasicFileAttributes;
34+
import java.util.Collections;
35+
import java.util.EnumSet;
36+
import java.util.Objects;
37+
import java.util.Set;
38+
import java.util.concurrent.ConcurrentHashMap;
39+
import java.util.function.Supplier;
40+
41+
import org.apache.maven.api.Session;
42+
import org.apache.maven.api.SessionData;
43+
import org.apache.maven.api.services.TempFileService;
44+
import org.slf4j.Logger;
45+
import org.slf4j.LoggerFactory;
46+
47+
/**
48+
* Default TempFileService implementation.
49+
* Stores tracked paths in Session-scoped data and removes them after the build.
50+
*/
51+
@Named
52+
@Singleton
53+
public final class DefaultTempFileService implements TempFileService {
54+
55+
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultTempFileService.class);
56+
57+
58+
59+
// unique, typed session key (uses factory; one narrow unchecked cast)
60+
@SuppressWarnings({"unchecked", "rawtypes"})
61+
private static final SessionData.Key<Set<Path>> TMP_KEY =
62+
(SessionData.Key) SessionData.key(Set.class, DefaultTempFileService.class);
63+
64+
// supplier with concrete types (avoids inference noise)
65+
private static final Supplier<Set<Path>> TMP_SUPPLIER =
66+
() -> Collections.newSetFromMap(new ConcurrentHashMap<Path, Boolean>());
67+
68+
@Override
69+
public Path createTempFile(final Session session, final String prefix, final String suffix) throws IOException {
70+
Objects.requireNonNull(session, "session");
71+
final Path file = Files.createTempFile(prefix, suffix);
72+
register(session, file);
73+
return file;
74+
}
75+
76+
@Override
77+
public Path createTempFile(final Session session, final String prefix, final String suffix, final Path directory)
78+
throws IOException {
79+
Objects.requireNonNull(session, "session");
80+
Objects.requireNonNull(directory, "directory");
81+
final Path file = Files.createTempFile(directory, prefix, suffix);
82+
register(session, file);
83+
return file;
84+
}
85+
86+
@Override
87+
public Path createTempDirectory(final Session session, final String prefix) throws IOException {
88+
Objects.requireNonNull(session, "session");
89+
final Path dir = Files.createTempDirectory(prefix);
90+
register(session, dir);
91+
return dir;
92+
}
93+
94+
@Override
95+
public Path createTempDirectory(final Session session, final String prefix, final Path directory)
96+
throws IOException {
97+
Objects.requireNonNull(session, "session");
98+
Objects.requireNonNull(directory, "directory");
99+
final Path dir = Files.createTempDirectory(directory, prefix);
100+
register(session, dir);
101+
return dir;
102+
}
103+
104+
@Override
105+
public void register(final Session session, final Path path) {
106+
Objects.requireNonNull(session, "session");
107+
Objects.requireNonNull(path, "path");
108+
final Set<Path> bucket = sessionPaths(session);
109+
bucket.add(path);
110+
if (LOGGER.isDebugEnabled()) {
111+
LOGGER.debug("Temp path registered for cleanup: {}", path);
112+
}
113+
}
114+
115+
@Override
116+
public void cleanup(final Session session) throws IOException {
117+
Objects.requireNonNull(session, "session");
118+
119+
if (Boolean.getBoolean(KEEP_PROP)) {
120+
if (LOGGER.isInfoEnabled()) {
121+
LOGGER.info("Skipping temp cleanup due to -D{}=true", KEEP_PROP);
122+
}
123+
return;
124+
}
125+
126+
final Set<Path> bucket = sessionPaths(session);
127+
IOException first = null;
128+
129+
for (final Path path : bucket) {
130+
try {
131+
deleteTree(path);
132+
} catch (final IOException e) {
133+
if (first == null) {
134+
first = e;
135+
} else if (e != first) {
136+
first.addSuppressed(e);
137+
}
138+
LOGGER.warn("Failed to delete temp path {}", path, e);
139+
}
140+
}
141+
bucket.clear();
142+
143+
if (first != null) {
144+
throw first;
145+
}
146+
}
147+
148+
// ---- internals ---------------------------------------------------------
149+
150+
private Set<Path> sessionPaths(final Session session) {
151+
return session.getData().computeIfAbsent(TMP_KEY, TMP_SUPPLIER);
152+
}
153+
154+
private static void deleteTree(final Path path) throws IOException {
155+
if (path == null || Files.notExists(path)) {
156+
return;
157+
}
158+
// Walk depth-first and delete files, then directories.
159+
Files.walkFileTree(
160+
path, EnumSet.noneOf(FileVisitOption.class), Integer.MAX_VALUE, new SimpleFileVisitor<Path>() {
161+
@Override
162+
public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs)
163+
throws IOException {
164+
Files.deleteIfExists(file);
165+
return FileVisitResult.CONTINUE;
166+
}
167+
168+
@Override
169+
public FileVisitResult postVisitDirectory(final Path dir, final IOException exc)
170+
throws IOException {
171+
Files.deleteIfExists(dir);
172+
return FileVisitResult.CONTINUE;
173+
}
174+
});
175+
}
176+
}
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)