1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugin.internal;
20
21 import javax.inject.Named;
22 import javax.inject.Singleton;
23
24 import java.io.File;
25 import java.util.Arrays;
26 import java.util.LinkedHashMap;
27 import java.util.LinkedHashSet;
28 import java.util.Locale;
29 import java.util.Map;
30 import java.util.concurrent.ConcurrentHashMap;
31
32 import org.apache.maven.AbstractMavenLifecycleParticipant;
33 import org.apache.maven.execution.MavenSession;
34 import org.apache.maven.model.InputLocation;
35 import org.apache.maven.plugin.PluginValidationManager;
36 import org.apache.maven.plugin.descriptor.MojoDescriptor;
37 import org.apache.maven.plugin.descriptor.PluginDescriptor;
38 import org.apache.maven.project.MavenProject;
39 import org.eclipse.aether.RepositorySystemSession;
40 import org.eclipse.aether.artifact.Artifact;
41 import org.eclipse.aether.util.ConfigUtils;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 @Singleton
46 @Named
47 public final class DefaultPluginValidationManager extends AbstractMavenLifecycleParticipant
48 implements PluginValidationManager {
49
50 private static final String ISSUES_KEY = DefaultPluginValidationManager.class.getName() + ".issues";
51
52 private static final String MAVEN_PLUGIN_VALIDATION_KEY = "maven.plugin.validation";
53
54 private enum ValidationLevel {
55 BRIEF,
56 DEFAULT,
57 VERBOSE
58 }
59
60 private final Logger logger = LoggerFactory.getLogger(getClass());
61
62 @Override
63 public void afterSessionEnd(MavenSession session) {
64 reportSessionCollectedValidationIssues(session);
65 }
66
67 private ValidationLevel validationLevel(RepositorySystemSession session) {
68 String level = ConfigUtils.getString(session, null, MAVEN_PLUGIN_VALIDATION_KEY);
69 if (level == null || level.isEmpty()) {
70 return ValidationLevel.DEFAULT;
71 }
72 try {
73 return ValidationLevel.valueOf(level.toUpperCase(Locale.ENGLISH));
74 } catch (IllegalArgumentException e) {
75 logger.warn(
76 "Invalid value specified for property {}: '{}'. Supported values are (case insensitive): {}",
77 MAVEN_PLUGIN_VALIDATION_KEY,
78 level,
79 Arrays.toString(ValidationLevel.values()));
80 return ValidationLevel.DEFAULT;
81 }
82 }
83
84 private String pluginKey(String groupId, String artifactId, String version) {
85 return groupId + ":" + artifactId + ":" + version;
86 }
87
88 private String pluginKey(MojoDescriptor mojoDescriptor) {
89 PluginDescriptor pd = mojoDescriptor.getPluginDescriptor();
90 return pluginKey(pd.getGroupId(), pd.getArtifactId(), pd.getVersion());
91 }
92
93 private String pluginKey(Artifact pluginArtifact) {
94 return pluginKey(pluginArtifact.getGroupId(), pluginArtifact.getArtifactId(), pluginArtifact.getVersion());
95 }
96
97 @Override
98 public void reportPluginValidationIssue(RepositorySystemSession session, Artifact pluginArtifact, String issue) {
99 String pluginKey = pluginKey(pluginArtifact);
100 PluginValidationIssues pluginIssues =
101 pluginIssues(session).computeIfAbsent(pluginKey, k -> new PluginValidationIssues());
102 pluginIssues.reportPluginIssue(null, null, issue);
103 }
104
105 @Override
106 public void reportPluginValidationIssue(MavenSession mavenSession, MojoDescriptor mojoDescriptor, String issue) {
107 String pluginKey = pluginKey(mojoDescriptor);
108 PluginValidationIssues pluginIssues = pluginIssues(mavenSession.getRepositorySession())
109 .computeIfAbsent(pluginKey, k -> new PluginValidationIssues());
110 pluginIssues.reportPluginIssue(
111 pluginDeclaration(mavenSession, mojoDescriptor), pluginOccurrence(mavenSession), issue);
112 }
113
114 @Override
115 public void reportPluginMojoValidationIssue(
116 MavenSession mavenSession, MojoDescriptor mojoDescriptor, Class<?> mojoClass, String issue) {
117 String pluginKey = pluginKey(mojoDescriptor);
118 PluginValidationIssues pluginIssues = pluginIssues(mavenSession.getRepositorySession())
119 .computeIfAbsent(pluginKey, k -> new PluginValidationIssues());
120 pluginIssues.reportPluginMojoIssue(
121 pluginDeclaration(mavenSession, mojoDescriptor),
122 pluginOccurrence(mavenSession),
123 mojoInfo(mojoDescriptor, mojoClass),
124 issue);
125 }
126
127 private void reportSessionCollectedValidationIssues(MavenSession mavenSession) {
128 if (!logger.isWarnEnabled()) {
129 return;
130 }
131 ValidationLevel validationLevel = validationLevel(mavenSession.getRepositorySession());
132 ConcurrentHashMap<String, PluginValidationIssues> issuesMap = pluginIssues(mavenSession.getRepositorySession());
133 if (!issuesMap.isEmpty()) {
134
135 logger.warn("");
136 logger.warn("Plugin validation issues were detected in {} plugin(s)", issuesMap.size());
137 logger.warn("");
138 if (validationLevel == ValidationLevel.BRIEF) {
139 return;
140 }
141
142 for (Map.Entry<String, PluginValidationIssues> entry : issuesMap.entrySet()) {
143 logger.warn(" * {}", entry.getKey());
144 if (validationLevel == ValidationLevel.VERBOSE) {
145 PluginValidationIssues issues = entry.getValue();
146 if (!issues.pluginDeclarations.isEmpty()) {
147 logger.warn(" Declared at location(s):");
148 for (String pluginDeclaration : issues.pluginDeclarations) {
149 logger.warn(" * {}", pluginDeclaration);
150 }
151 }
152 if (!issues.pluginOccurrences.isEmpty()) {
153 logger.warn(" Used in module(s):");
154 for (String pluginOccurrence : issues.pluginOccurrences) {
155 logger.warn(" * {}", pluginOccurrence);
156 }
157 }
158 if (!issues.pluginIssues.isEmpty()) {
159 logger.warn(" Plugin issue(s):");
160 for (String pluginIssue : issues.pluginIssues) {
161 logger.warn(" * {}", pluginIssue);
162 }
163 }
164 if (!issues.mojoIssues.isEmpty()) {
165 logger.warn(" Mojo issue(s):");
166 for (String mojoInfo : issues.mojoIssues.keySet()) {
167 logger.warn(" * Mojo {}", mojoInfo);
168 for (String mojoIssue : issues.mojoIssues.get(mojoInfo)) {
169 logger.warn(" - {}", mojoIssue);
170 }
171 }
172 }
173 logger.warn("");
174 }
175 }
176 logger.warn("");
177 if (validationLevel == ValidationLevel.VERBOSE) {
178 logger.warn(
179 "Fix reported issues by adjusting plugin configuration or by upgrading above listed plugins. If no upgrade available, please notify plugin maintainers about reported issues.");
180 }
181 logger.warn(
182 "For more or less details, use 'maven.plugin.validation' property with one of the values (case insensitive): {}",
183 Arrays.toString(ValidationLevel.values()));
184 logger.warn("");
185 }
186 }
187
188 private String pluginDeclaration(MavenSession mavenSession, MojoDescriptor mojoDescriptor) {
189 InputLocation inputLocation =
190 mojoDescriptor.getPluginDescriptor().getPlugin().getLocation("");
191 if (inputLocation != null && inputLocation.getSource() != null) {
192 StringBuilder stringBuilder = new StringBuilder();
193 stringBuilder.append(inputLocation.getSource().getModelId());
194 String location = inputLocation.getSource().getLocation();
195 if (location != null) {
196 if (location.contains("://")) {
197 stringBuilder.append(" (").append(location).append(")");
198 } else {
199 File rootBasedir = mavenSession.getTopLevelProject().getBasedir();
200 File locationFile = new File(location);
201 if (location.startsWith(rootBasedir.getPath())) {
202 stringBuilder
203 .append(" (")
204 .append(rootBasedir.toPath().relativize(locationFile.toPath()))
205 .append(")");
206 } else {
207 stringBuilder.append(" (").append(location).append(")");
208 }
209 }
210 }
211 stringBuilder.append(" @ line ").append(inputLocation.getLineNumber());
212 return stringBuilder.toString();
213 } else {
214 return "unknown";
215 }
216 }
217
218 private String pluginOccurrence(MavenSession mavenSession) {
219 MavenProject prj = mavenSession.getCurrentProject();
220 String result = prj.getGroupId() + ":" + prj.getArtifactId() + ":" + prj.getVersion();
221 File currentPom = prj.getFile();
222 if (currentPom != null) {
223 File rootBasedir = mavenSession.getTopLevelProject().getBasedir();
224 result += " (" + rootBasedir.toPath().relativize(currentPom.toPath()) + ")";
225 }
226 return result;
227 }
228
229 private String mojoInfo(MojoDescriptor mojoDescriptor, Class<?> mojoClass) {
230 return mojoDescriptor.getFullGoalName() + " (" + mojoClass.getName() + ")";
231 }
232
233 @SuppressWarnings("unchecked")
234 private ConcurrentHashMap<String, PluginValidationIssues> pluginIssues(RepositorySystemSession session) {
235 return (ConcurrentHashMap<String, PluginValidationIssues>)
236 session.getData().computeIfAbsent(ISSUES_KEY, ConcurrentHashMap::new);
237 }
238
239 private static class PluginValidationIssues {
240 private final LinkedHashSet<String> pluginDeclarations;
241
242 private final LinkedHashSet<String> pluginOccurrences;
243
244 private final LinkedHashSet<String> pluginIssues;
245
246 private final LinkedHashMap<String, LinkedHashSet<String>> mojoIssues;
247
248 private PluginValidationIssues() {
249 this.pluginDeclarations = new LinkedHashSet<>();
250 this.pluginOccurrences = new LinkedHashSet<>();
251 this.pluginIssues = new LinkedHashSet<>();
252 this.mojoIssues = new LinkedHashMap<>();
253 }
254
255 private synchronized void reportPluginIssue(String pluginDeclaration, String pluginOccurrence, String issue) {
256 if (pluginDeclaration != null) {
257 pluginDeclarations.add(pluginDeclaration);
258 }
259 if (pluginOccurrence != null) {
260 pluginOccurrences.add(pluginOccurrence);
261 }
262 pluginIssues.add(issue);
263 }
264
265 private synchronized void reportPluginMojoIssue(
266 String pluginDeclaration, String pluginOccurrence, String mojoInfo, String issue) {
267 if (pluginDeclaration != null) {
268 pluginDeclarations.add(pluginDeclaration);
269 }
270 if (pluginOccurrence != null) {
271 pluginOccurrences.add(pluginOccurrence);
272 }
273 mojoIssues.computeIfAbsent(mojoInfo, k -> new LinkedHashSet<>()).add(issue);
274 }
275 }
276 }