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.nio.file.Path;
25 import java.nio.file.Paths;
26 import java.util.Arrays;
27 import java.util.Collection;
28 import java.util.Collections;
29 import java.util.EnumSet;
30 import java.util.HashMap;
31 import java.util.LinkedHashMap;
32 import java.util.LinkedHashSet;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.Set;
36 import java.util.concurrent.ConcurrentHashMap;
37
38 import org.apache.maven.eventspy.AbstractEventSpy;
39 import org.apache.maven.execution.ExecutionEvent;
40 import org.apache.maven.execution.MavenSession;
41 import org.apache.maven.model.InputLocation;
42 import org.apache.maven.plugin.PluginValidationManager;
43 import org.apache.maven.plugin.descriptor.MojoDescriptor;
44 import org.apache.maven.plugin.descriptor.PluginDescriptor;
45 import org.eclipse.aether.RepositorySystemSession;
46 import org.eclipse.aether.artifact.Artifact;
47 import org.eclipse.aether.util.ConfigUtils;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51 @Singleton
52 @Named
53 public final class DefaultPluginValidationManager extends AbstractEventSpy implements PluginValidationManager {
54
55
56
57
58 static final Collection<String> EXPECTED_PROVIDED_SCOPE_EXCLUSIONS_GA =
59 Collections.unmodifiableCollection(Arrays.asList(
60 "org.apache.maven:maven-archiver", "org.apache.maven:maven-jxr", "org.apache.maven:plexus-utils"));
61
62 private static final String ISSUES_KEY = DefaultPluginValidationManager.class.getName() + ".issues";
63
64 private static final String MAVEN_PLUGIN_VALIDATION_KEY = "maven.plugin.validation";
65
66 private static final ValidationReportLevel DEFAULT_VALIDATION_LEVEL = ValidationReportLevel.INLINE;
67
68 private static final Collection<ValidationReportLevel> INLINE_VALIDATION_LEVEL = Collections.unmodifiableCollection(
69 Arrays.asList(ValidationReportLevel.INLINE, ValidationReportLevel.BRIEF));
70
71 private enum ValidationReportLevel {
72 NONE,
73 INLINE,
74 SUMMARY,
75 BRIEF,
76
77 VERBOSE
78 }
79
80 private final Logger logger = LoggerFactory.getLogger(getClass());
81
82 @Override
83 public void onEvent(Object event) {
84 if (event instanceof ExecutionEvent) {
85 ExecutionEvent executionEvent = (ExecutionEvent) event;
86 if (executionEvent.getType() == ExecutionEvent.Type.SessionStarted) {
87 RepositorySystemSession repositorySystemSession =
88 executionEvent.getSession().getRepositorySession();
89 validationReportLevel(repositorySystemSession);
90 } else if (executionEvent.getType() == ExecutionEvent.Type.SessionEnded) {
91 reportSessionCollectedValidationIssues(executionEvent.getSession());
92 }
93 }
94 }
95
96 private ValidationReportLevel validationReportLevel(RepositorySystemSession session) {
97 return (ValidationReportLevel) session.getData()
98 .computeIfAbsent(ValidationReportLevel.class, () -> parseValidationReportLevel(session));
99 }
100
101 private ValidationReportLevel parseValidationReportLevel(RepositorySystemSession session) {
102 String level = ConfigUtils.getString(session, null, MAVEN_PLUGIN_VALIDATION_KEY);
103 if (level == null || level.isEmpty()) {
104 return DEFAULT_VALIDATION_LEVEL;
105 }
106 try {
107 return ValidationReportLevel.valueOf(level.toUpperCase(Locale.ENGLISH));
108 } catch (IllegalArgumentException e) {
109 logger.warn(
110 "Invalid value specified for property {}: '{}'. Supported values are (case insensitive): {}",
111 MAVEN_PLUGIN_VALIDATION_KEY,
112 level,
113 Arrays.toString(ValidationReportLevel.values()));
114 return DEFAULT_VALIDATION_LEVEL;
115 }
116 }
117
118 private String pluginKey(String groupId, String artifactId, String version) {
119 return groupId + ":" + artifactId + ":" + version;
120 }
121
122 private String pluginKey(MojoDescriptor mojoDescriptor) {
123 PluginDescriptor pd = mojoDescriptor.getPluginDescriptor();
124 return pluginKey(pd.getGroupId(), pd.getArtifactId(), pd.getVersion());
125 }
126
127 private String pluginKey(Artifact pluginArtifact) {
128 return pluginKey(pluginArtifact.getGroupId(), pluginArtifact.getArtifactId(), pluginArtifact.getVersion());
129 }
130
131 private void mayReportInline(RepositorySystemSession session, IssueLocality locality, String issue) {
132 if (locality == IssueLocality.INTERNAL) {
133 ValidationReportLevel validationReportLevel = validationReportLevel(session);
134 if (INLINE_VALIDATION_LEVEL.contains(validationReportLevel)) {
135 logger.warn(" {}", issue);
136 }
137 }
138 }
139
140 @Override
141 public void reportPluginValidationIssue(
142 IssueLocality locality, RepositorySystemSession session, Artifact pluginArtifact, String issue) {
143 String pluginKey = pluginKey(pluginArtifact);
144 PluginValidationIssues pluginIssues =
145 pluginIssues(session).computeIfAbsent(pluginKey, k -> new PluginValidationIssues());
146 pluginIssues.reportPluginIssue(locality, null, issue);
147 mayReportInline(session, locality, issue);
148 }
149
150 @Override
151 public void reportPluginValidationIssue(
152 IssueLocality locality, MavenSession mavenSession, MojoDescriptor mojoDescriptor, String issue) {
153 String pluginKey = pluginKey(mojoDescriptor);
154 PluginValidationIssues pluginIssues = pluginIssues(mavenSession.getRepositorySession())
155 .computeIfAbsent(pluginKey, k -> new PluginValidationIssues());
156 pluginIssues.reportPluginIssue(locality, pluginDeclaration(mavenSession, mojoDescriptor), issue);
157 mayReportInline(mavenSession.getRepositorySession(), locality, issue);
158 }
159
160 @Override
161 public void reportPluginMojoValidationIssue(
162 IssueLocality locality,
163 MavenSession mavenSession,
164 MojoDescriptor mojoDescriptor,
165 Class<?> mojoClass,
166 String issue) {
167 String pluginKey = pluginKey(mojoDescriptor);
168 PluginValidationIssues pluginIssues = pluginIssues(mavenSession.getRepositorySession())
169 .computeIfAbsent(pluginKey, k -> new PluginValidationIssues());
170 pluginIssues.reportPluginMojoIssue(
171 locality, pluginDeclaration(mavenSession, mojoDescriptor), mojoInfo(mojoDescriptor, mojoClass), issue);
172 mayReportInline(mavenSession.getRepositorySession(), locality, issue);
173 }
174
175 private void reportSessionCollectedValidationIssues(MavenSession mavenSession) {
176 if (!logger.isWarnEnabled()) {
177 return;
178 }
179 ValidationReportLevel validationReportLevel = validationReportLevel(mavenSession.getRepositorySession());
180 if (validationReportLevel == ValidationReportLevel.NONE
181 || validationReportLevel == ValidationReportLevel.INLINE) {
182 return;
183 }
184 ConcurrentHashMap<String, PluginValidationIssues> issuesMap = pluginIssues(mavenSession.getRepositorySession());
185 EnumSet<IssueLocality> issueLocalitiesToReport = validationReportLevel == ValidationReportLevel.SUMMARY
186 || validationReportLevel == ValidationReportLevel.VERBOSE
187 ? EnumSet.allOf(IssueLocality.class)
188 : EnumSet.of(IssueLocality.EXTERNAL);
189
190 if (hasAnythingToReport(issuesMap, issueLocalitiesToReport)) {
191 logger.warn("");
192 logger.warn("Plugin {} validation issues were detected in following plugin(s)", issueLocalitiesToReport);
193 logger.warn("");
194 for (Map.Entry<String, PluginValidationIssues> entry : issuesMap.entrySet()) {
195 PluginValidationIssues issues = entry.getValue();
196 if (!hasAnythingToReport(issues, issueLocalitiesToReport)) {
197 continue;
198 }
199 logger.warn(" * {}", entry.getKey());
200 if (validationReportLevel == ValidationReportLevel.VERBOSE) {
201 if (!issues.pluginDeclarations.isEmpty()) {
202 logger.warn(" Declared at location(s):");
203 for (String pluginDeclaration : issues.pluginDeclarations) {
204 logger.warn(" * {}", pluginDeclaration);
205 }
206 }
207 if (!issues.pluginIssues.isEmpty()) {
208 for (IssueLocality issueLocality : issueLocalitiesToReport) {
209 Set<String> pluginIssues = issues.pluginIssues.get(issueLocality);
210 if (pluginIssues != null && !pluginIssues.isEmpty()) {
211 logger.warn(" Plugin {} issue(s):", issueLocality);
212 for (String pluginIssue : pluginIssues) {
213 logger.warn(" * {}", pluginIssue);
214 }
215 }
216 }
217 }
218 if (!issues.mojoIssues.isEmpty()) {
219 for (IssueLocality issueLocality : issueLocalitiesToReport) {
220 Map<String, LinkedHashSet<String>> mojoIssues = issues.mojoIssues.get(issueLocality);
221 if (mojoIssues != null && !mojoIssues.isEmpty()) {
222 logger.warn(" Mojo {} issue(s):", issueLocality);
223 for (String mojoInfo : mojoIssues.keySet()) {
224 logger.warn(" * Mojo {}", mojoInfo);
225 for (String mojoIssue : mojoIssues.get(mojoInfo)) {
226 logger.warn(" - {}", mojoIssue);
227 }
228 }
229 }
230 }
231 }
232 logger.warn("");
233 }
234 }
235 logger.warn("");
236 if (validationReportLevel == ValidationReportLevel.VERBOSE) {
237 logger.warn(
238 "Fix reported issues by adjusting plugin configuration or by upgrading above listed plugins. If no upgrade available, please notify plugin maintainers about reported issues.");
239 }
240 logger.warn(
241 "For more or less details, use 'maven.plugin.validation' property with one of the values (case insensitive): {}",
242 Arrays.toString(ValidationReportLevel.values()));
243 logger.warn("");
244 }
245 }
246
247 private boolean hasAnythingToReport(
248 Map<String, PluginValidationIssues> issuesMap, EnumSet<IssueLocality> issueLocalitiesToReport) {
249 for (PluginValidationIssues issues : issuesMap.values()) {
250 if (hasAnythingToReport(issues, issueLocalitiesToReport)) {
251 return true;
252 }
253 }
254 return false;
255 }
256
257 private boolean hasAnythingToReport(PluginValidationIssues issues, EnumSet<IssueLocality> issueLocalitiesToReport) {
258 for (IssueLocality issueLocality : issueLocalitiesToReport) {
259 Set<String> pluginIssues = issues.pluginIssues.get(issueLocality);
260 if (pluginIssues != null && !pluginIssues.isEmpty()) {
261 return true;
262 }
263 Map<String, LinkedHashSet<String>> mojoIssues = issues.mojoIssues.get(issueLocality);
264 if (mojoIssues != null && !mojoIssues.isEmpty()) {
265 return true;
266 }
267 }
268 return false;
269 }
270
271 private String pluginDeclaration(MavenSession mavenSession, MojoDescriptor mojoDescriptor) {
272 InputLocation inputLocation =
273 mojoDescriptor.getPluginDescriptor().getPlugin().getLocation("");
274 if (inputLocation != null && inputLocation.getSource() != null) {
275 StringBuilder stringBuilder = new StringBuilder();
276 stringBuilder.append(inputLocation.getSource().getModelId());
277 String location = inputLocation.getSource().getLocation();
278 if (location != null) {
279 if (location.contains("://")) {
280 stringBuilder.append(" (").append(location).append(")");
281 } else {
282 Path topDirectory = mavenSession.getTopDirectory();
283 Path locationPath = Paths.get(location).toAbsolutePath().normalize();
284 if (locationPath.startsWith(topDirectory)) {
285 locationPath = topDirectory.relativize(locationPath);
286 }
287 stringBuilder.append(" (").append(locationPath).append(")");
288 }
289 }
290 stringBuilder.append(" @ line ").append(inputLocation.getLineNumber());
291 return stringBuilder.toString();
292 } else {
293 return "unknown";
294 }
295 }
296
297 private String mojoInfo(MojoDescriptor mojoDescriptor, Class<?> mojoClass) {
298 return mojoDescriptor.getFullGoalName() + " (" + mojoClass.getName() + ")";
299 }
300
301 @SuppressWarnings("unchecked")
302 private ConcurrentHashMap<String, PluginValidationIssues> pluginIssues(RepositorySystemSession session) {
303 return (ConcurrentHashMap<String, PluginValidationIssues>)
304 session.getData().computeIfAbsent(ISSUES_KEY, ConcurrentHashMap::new);
305 }
306
307 private static class PluginValidationIssues {
308 private final LinkedHashSet<String> pluginDeclarations;
309
310 private final HashMap<IssueLocality, LinkedHashSet<String>> pluginIssues;
311
312 private final HashMap<IssueLocality, LinkedHashMap<String, LinkedHashSet<String>>> mojoIssues;
313
314 private PluginValidationIssues() {
315 this.pluginDeclarations = new LinkedHashSet<>();
316 this.pluginIssues = new HashMap<>();
317 this.mojoIssues = new HashMap<>();
318 }
319
320 private synchronized void reportPluginIssue(
321 IssueLocality issueLocality, String pluginDeclaration, String issue) {
322 if (pluginDeclaration != null) {
323 pluginDeclarations.add(pluginDeclaration);
324 }
325 pluginIssues
326 .computeIfAbsent(issueLocality, k -> new LinkedHashSet<>())
327 .add(issue);
328 }
329
330 private synchronized void reportPluginMojoIssue(
331 IssueLocality issueLocality, String pluginDeclaration, String mojoInfo, String issue) {
332 if (pluginDeclaration != null) {
333 pluginDeclarations.add(pluginDeclaration);
334 }
335 mojoIssues
336 .computeIfAbsent(issueLocality, k -> new LinkedHashMap<>())
337 .computeIfAbsent(mojoInfo, k -> new LinkedHashSet<>())
338 .add(issue);
339 }
340 }
341 }